@crawlee/cheerio 4.0.0-beta.99 → 4.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ Crawlee is available as the [`crawlee`](https://www.npmjs.com/package/crawlee) N
34
34
 
35
35
  We recommend visiting the [Introduction tutorial](https://crawlee.dev/js/docs/introduction) in Crawlee documentation for more information.
36
36
 
37
- > Crawlee requires **Node.js 16 or higher**.
37
+ > Crawlee requires **Node.js 22.13 or higher**.
38
38
 
39
39
  ### With Crawlee CLI
40
40
 
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from '@crawlee/http';
2
2
  export * from './internals/cheerio-crawler.js';
3
+ export type { CheerioParseResult } from './internals/cheerio-parser.js';
@@ -1,61 +1,16 @@
1
- import type { BasicCrawlingContext, CrawlingContext, EnqueueLinksOptions, ErrorHandler, GetUserDataFromRequest, HttpCrawlerOptions, InternalHttpCrawlingContext, InternalHttpHook, IRequestManager, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas, SkippedRequestCallback } from '@crawlee/http';
2
- import { HttpCrawler } from '@crawlee/http';
3
- import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
4
- import { type CheerioRoot, type RobotsTxtFile } from '@crawlee/utils';
5
- import type { CheerioAPI } from 'cheerio';
6
- import * as cheerio from 'cheerio';
1
+ import type { CrawlingContext, DOMCrawlingContext, ErrorHandler, GetUserDataFromRequest, HttpCrawlerOptions, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/http';
2
+ import { DOMCrawler } from '@crawlee/http';
3
+ import type { Dictionary } from '@crawlee/types';
4
+ import type { CheerioParseResult } from './cheerio-parser.js';
7
5
  export type CheerioErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
8
6
  JSONData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
9
7
  ContextExtension = Dictionary<never>> = ErrorHandler<CrawlingContext, CheerioCrawlingContext<UserData, JSONData> & ContextExtension>;
10
8
  export interface CheerioCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends CheerioCrawlingContext = CheerioCrawlingContext & ContextExtension, UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
11
9
  JSONData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
12
- Routes extends Record<keyof Routes, Dictionary> = Record<string, UserData>> extends HttpCrawlerOptions<CheerioCrawlingContext<UserData, JSONData>, ContextExtension, ExtendedContext, Routes> {
10
+ Routes extends Record<keyof Routes, Dictionary> = Record<string, UserData>, StatisticStateExtension extends object = {}> extends Omit<HttpCrawlerOptions<CheerioCrawlingContext<UserData, JSONData>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>, 'contextPipelineBuilder'> {
13
11
  }
14
- export type CheerioHook<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
15
- JSONData extends Dictionary = any> = InternalHttpHook<CheerioCrawlingContext<UserData, JSONData>>;
16
12
  export interface CheerioCrawlingContext<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
17
- JSONData extends Dictionary = any> extends InternalHttpCrawlingContext<UserData, JSONData> {
18
- /**
19
- * The raw HTML content of the web page as a string.
20
- */
21
- body: string;
22
- /**
23
- * The [Cheerio](https://cheerio.js.org/) object with parsed HTML.
24
- * Cheerio is available only for HTML and XML content types.
25
- */
26
- $: cheerio.CheerioAPI;
27
- /**
28
- * Wait for an element matching the selector to appear. Timeout is ignored.
29
- *
30
- * **Example usage:**
31
- * ```ts
32
- * async requestHandler({ waitForSelector, parseWithCheerio }) {
33
- * await waitForSelector('article h1');
34
- * const $ = await parseWithCheerio();
35
- * const title = $('title').text();
36
- * });
37
- * ```
38
- */
39
- waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
40
- /**
41
- * Returns Cheerio handle, this is here to unify the crawler API, so they all have this handy method.
42
- * It has the same return type as the `$` context property, use it only if you are abstracting your workflow to
43
- * support different context types in one handler.
44
- * When provided with the `selector` argument, it will throw if it's not available.
45
- *
46
- * **Example usage:**
47
- * ```ts
48
- * async requestHandler({ parseWithCheerio }) {
49
- * const $ = await parseWithCheerio();
50
- * const title = $('title').text();
51
- * });
52
- * ```
53
- */
54
- parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioRoot>;
55
- /**
56
- * Helper function for extracting URLs from the parsed HTML and adding them to the request queue.
57
- */
58
- enqueueLinks(options?: EnqueueLinksOptions): Promise<BatchAddRequestsResult>;
13
+ JSONData extends Dictionary = any> extends DOMCrawlingContext<CheerioParseResult, UserData, JSONData> {
59
14
  }
60
15
  export type CheerioRequestHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
61
16
  JSONData extends Dictionary = any> = RequestHandler<CheerioCrawlingContext<UserData, JSONData>>;
@@ -138,41 +93,12 @@ JSONData extends Dictionary = any> = RequestHandler<CheerioCrawlingContext<UserD
138
93
  * ```
139
94
  * @category Crawlers
140
95
  */
141
- export declare class CheerioCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends CheerioCrawlingContext = CheerioCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<CheerioCrawlingContext['request']>>> extends HttpCrawler<CheerioCrawlingContext, ContextExtension, ExtendedContext, Routes> {
96
+ export declare class CheerioCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends CheerioCrawlingContext = CheerioCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<CheerioCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends DOMCrawler<CheerioParseResult, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
142
97
  /**
143
98
  * All `CheerioCrawler` parameters are passed via an options object.
144
99
  */
145
- constructor(options?: CheerioCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes>);
146
- // @ts-ignore optional peer dependency or compatibility with es2022
147
- protected buildContextPipeline(): import("@crawlee/http").ContextPipeline<CrawlingContext<Dictionary>, InternalHttpCrawlingContext<any, any> & {
148
- readonly body: string;
149
- readonly $: CheerioAPI;
150
- } & {
151
- enqueueLinks: (enqueueOptions?: EnqueueLinksOptions) => Promise<BatchAddRequestsResult>;
152
- waitForSelector: (selector: string, _timeoutMs?: number) => Promise<void>;
153
- parseWithCheerio: (selector?: string, timeoutMs?: number) => Promise<CheerioAPI>;
154
- }>;
155
- private parseContent;
156
- private addHelpers;
157
- }
158
- interface EnqueueLinksInternalOptions {
159
- options?: EnqueueLinksOptions;
160
- $: cheerio.CheerioAPI | null;
161
- requestManager: IRequestManager;
162
- robotsTxtFile?: RobotsTxtFile;
163
- onSkippedRequest?: SkippedRequestCallback;
164
- originalRequestUrl: string;
165
- finalRequestUrl?: string;
166
- }
167
- interface BoundEnqueueLinksInternalOptions {
168
- enqueueLinks: BasicCrawlingContext['enqueueLinks'];
169
- options?: EnqueueLinksOptions;
170
- $: cheerio.CheerioAPI | null;
171
- originalRequestUrl: string;
172
- finalRequestUrl?: string;
100
+ constructor(options?: CheerioCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes, StatisticStateExtension>);
173
101
  }
174
- /** @internal */
175
- export declare function cheerioCrawlerEnqueueLinks(options: EnqueueLinksInternalOptions | BoundEnqueueLinksInternalOptions): Promise<unknown>;
176
102
  /**
177
103
  * Creates new {@link Router} instance that works based on request labels.
178
104
  * This instance can then serve as a `requestHandler` of your {@link CheerioCrawler}.
@@ -200,4 +126,3 @@ export declare function cheerioCrawlerEnqueueLinks(options: EnqueueLinksInternal
200
126
  export declare function createCheerioRouter<Context extends CheerioCrawlingContext = CheerioCrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
201
127
  export declare function createCheerioRouter<Context extends CheerioCrawlingContext = CheerioCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
202
128
  export declare function createCheerioRouter<Context extends CheerioCrawlingContext = CheerioCrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
203
- export {};
@@ -1,7 +1,5 @@
1
- import { enqueueLinks, HttpCrawler, NavigationSkippedError, resolveBaseUrlForEnqueueLinksFiltering, Router, } from '@crawlee/http';
2
- import { extractUrlsFromCheerio } from '@crawlee/utils';
3
- import * as cheerio from 'cheerio';
4
- import { parseDocument } from 'htmlparser2';
1
+ import { DOMCrawler, Router } from '@crawlee/http';
2
+ import { cheerioParser } from './cheerio-parser.js';
5
3
  /**
6
4
  * Provides a framework for the parallel crawling of web pages using plain HTTP requests and
7
5
  * [cheerio](https://www.npmjs.com/package/cheerio) HTML parser.
@@ -81,118 +79,13 @@ import { parseDocument } from 'htmlparser2';
81
79
  * ```
82
80
  * @category Crawlers
83
81
  */
84
- export class CheerioCrawler extends HttpCrawler {
82
+ export class CheerioCrawler extends DOMCrawler {
85
83
  /**
86
84
  * All `CheerioCrawler` parameters are passed via an options object.
87
85
  */
88
86
  constructor(options) {
89
- const { contextPipelineBuilder, ...rest } = options ?? {};
90
- super({
91
- ...rest,
92
- contextPipelineBuilder: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
93
- });
87
+ super({ ...options, parser: cheerioParser() });
94
88
  }
95
- buildContextPipeline() {
96
- return super
97
- .buildContextPipeline()
98
- .compose({
99
- action: async (context) => await this.parseContent(context),
100
- })
101
- .compose({ action: async (context) => await this.addHelpers(context) });
102
- }
103
- async parseContent(crawlingContext) {
104
- try {
105
- const isXml = crawlingContext.contentType.type.includes('xml');
106
- const body = Buffer.isBuffer(crawlingContext.body)
107
- ? crawlingContext.body.toString(crawlingContext.contentType.encoding)
108
- : crawlingContext.body;
109
- const dom = parseDocument(body, { decodeEntities: true, xmlMode: isXml });
110
- const $ = cheerio.load(dom, {
111
- xml: { decodeEntities: true, xmlMode: isXml },
112
- });
113
- return {
114
- $,
115
- body,
116
- };
117
- }
118
- catch (err) {
119
- if (err instanceof NavigationSkippedError) {
120
- return {
121
- get body() {
122
- throw new NavigationSkippedError('The `body` property is not available - `skipNavigation` was used', { cause: err });
123
- },
124
- get $() {
125
- throw new NavigationSkippedError('The `$` property is not available - `skipNavigation` was used', { cause: err });
126
- },
127
- };
128
- }
129
- throw err;
130
- }
131
- }
132
- async addHelpers(crawlingContext) {
133
- const originalEnqueueLinks = crawlingContext.enqueueLinks;
134
- return {
135
- enqueueLinks: async (enqueueOptions) => {
136
- return (await cheerioCrawlerEnqueueLinks({
137
- options: {
138
- ...enqueueOptions,
139
- limit: await this.calculateEnqueuedRequestLimit(enqueueOptions?.limit),
140
- },
141
- $: crawlingContext.$,
142
- requestManager: await this.getRequestManager(),
143
- robotsTxtFile: await this.getRobotsTxtFileForUrl(crawlingContext.request.url),
144
- onSkippedRequest: this.handleSkippedRequest,
145
- originalRequestUrl: crawlingContext.request.url,
146
- finalRequestUrl: crawlingContext.request.loadedUrl,
147
- enqueueLinks: originalEnqueueLinks,
148
- })); // TODO make this type safe
149
- },
150
- waitForSelector: async (selector, _timeoutMs) => {
151
- if (crawlingContext.$(selector).get().length === 0) {
152
- throw new Error(`Selector '${selector}' not found.`);
153
- }
154
- },
155
- parseWithCheerio: async (selector, timeoutMs) => {
156
- if (selector) {
157
- await crawlingContext.waitForSelector(selector, timeoutMs);
158
- }
159
- return crawlingContext.$;
160
- },
161
- };
162
- }
163
- }
164
- /** @internal */
165
- function containsEnqueueLinks(options) {
166
- return !!options.enqueueLinks;
167
- }
168
- /** @internal */
169
- export async function cheerioCrawlerEnqueueLinks(options) {
170
- const { options: enqueueLinksOptions, $, originalRequestUrl, finalRequestUrl } = options;
171
- if (!$) {
172
- throw new Error('Cannot enqueue links because the DOM is not available.');
173
- }
174
- const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
175
- enqueueStrategy: enqueueLinksOptions?.strategy,
176
- finalRequestUrl,
177
- originalRequestUrl,
178
- userProvidedBaseUrl: enqueueLinksOptions?.baseUrl,
179
- });
180
- const urls = extractUrlsFromCheerio($, enqueueLinksOptions?.selector ?? 'a', enqueueLinksOptions?.baseUrl ?? finalRequestUrl ?? originalRequestUrl);
181
- if (containsEnqueueLinks(options)) {
182
- return options.enqueueLinks({
183
- urls,
184
- baseUrl,
185
- ...enqueueLinksOptions,
186
- });
187
- }
188
- return enqueueLinks({
189
- requestManager: options.requestManager,
190
- robotsTxtFile: options.robotsTxtFile,
191
- onSkippedRequest: options.onSkippedRequest,
192
- urls,
193
- baseUrl,
194
- ...enqueueLinksOptions,
195
- });
196
89
  }
197
90
  export function createCheerioRouter(routesOrSchemas) {
198
91
  return Router.create(routesOrSchemas);
@@ -0,0 +1,11 @@
1
+ import type { DOMParser } from '@crawlee/http';
2
+ import type { CheerioAPI } from 'cheerio';
3
+ export interface CheerioParseResult {
4
+ $: CheerioAPI;
5
+ body: string;
6
+ }
7
+ /**
8
+ * A {@link DOMParser} backed by [cheerio](https://www.npmjs.com/package/cheerio). Pass it to a
9
+ * {@link DOMCrawler} to get the crawling context {@link CheerioCrawler} provides.
10
+ */
11
+ export declare function cheerioParser(): DOMParser<CheerioParseResult>;
@@ -0,0 +1,27 @@
1
+ import { extractUrlsFromCheerio } from '@crawlee/utils/internal';
2
+ // slim uses faster htmlparser2 parser and doesn't load extra libs like undici
3
+ import * as cheerio from 'cheerio/slim';
4
+ import { parseDocument } from 'htmlparser2';
5
+ /**
6
+ * A {@link DOMParser} backed by [cheerio](https://www.npmjs.com/package/cheerio). Pass it to a
7
+ * {@link DOMCrawler} to get the crawling context {@link CheerioCrawler} provides.
8
+ */
9
+ export function cheerioParser() {
10
+ return {
11
+ placeholderMembers: { $: true, body: true },
12
+ parse(context) {
13
+ const isXml = context.contentType.type.includes('xml');
14
+ const body = Buffer.isBuffer(context.body)
15
+ ? context.body.toString(context.contentType.encoding)
16
+ : context.body;
17
+ const dom = parseDocument(body, { decodeEntities: true, xmlMode: isXml });
18
+ const $ = cheerio.load(dom, {
19
+ xml: { decodeEntities: true, xmlMode: isXml },
20
+ });
21
+ return { $, body };
22
+ },
23
+ extractLinks: ({ $ }, selector, baseUrl) => extractUrlsFromCheerio($, selector, baseUrl),
24
+ select: ({ $ }, selector) => $(selector).get(),
25
+ toCheerio: ({ $ }) => $,
26
+ };
27
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@crawlee/cheerio",
3
- "version": "4.0.0-beta.99",
3
+ "version": "4.0.0-rc.1",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
- "node": ">=22.0.0"
6
+ "node": ">=22.13.0"
7
7
  },
8
8
  "type": "module",
9
9
  "exports": {
@@ -47,9 +47,9 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@crawlee/http": "4.0.0-beta.99",
51
- "@crawlee/types": "4.0.0-beta.99",
52
- "@crawlee/utils": "4.0.0-beta.99",
50
+ "@crawlee/http": "4.0.0-rc.1",
51
+ "@crawlee/types": "4.0.0-rc.1",
52
+ "@crawlee/utils": "4.0.0-rc.1",
53
53
  "cheerio": "^1.0.0",
54
54
  "htmlparser2": "^10.0.0",
55
55
  "tslib": "^2.8.1"
@@ -61,5 +61,5 @@
61
61
  }
62
62
  }
63
63
  },
64
- "gitHead": "ad2748380941842bb10cff100f4b4caad92049e3"
64
+ "gitHead": "f354ca5e943bed1a5c657a1fce1974c053f9fff0"
65
65
  }