@crawlee/cheerio 4.0.0-rc.0 → 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,65 +1,16 @@
1
- import type { AddRequestsBatchedResult, CrawlingContext, EnqueueLinksOptions, ErrorHandler, ExtractLinksOptions, GetUserDataFromRequest, HttpCrawlerOptions, InternalHttpCrawlingContext, InternalHttpHook, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/http';
2
- import { HttpCrawler } from '@crawlee/http';
1
+ import type { CrawlingContext, DOMCrawlingContext, ErrorHandler, GetUserDataFromRequest, HttpCrawlerOptions, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/http';
2
+ import { DOMCrawler } from '@crawlee/http';
3
3
  import type { Dictionary } from '@crawlee/types';
4
- import { type CheerioRoot } from '@crawlee/utils/internal';
5
- import type { CheerioAPI } from 'cheerio';
6
- import * as cheerio from 'cheerio';
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>, StatisticStateExtension extends object = {}> extends HttpCrawlerOptions<CheerioCrawlingContext<UserData, JSONData>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
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
- * Extracts URLs from the parsed HTML, without adding them to the request queue.
57
- */
58
- extractLinks(options?: ExtractLinksOptions): Promise<string[]>;
59
- /**
60
- * Helper function for extracting URLs from the parsed HTML and adding them to the request queue.
61
- */
62
- enqueueLinks(options?: EnqueueLinksOptions): Promise<AddRequestsBatchedResult>;
13
+ JSONData extends Dictionary = any> extends DOMCrawlingContext<CheerioParseResult, UserData, JSONData> {
63
14
  }
64
15
  export type CheerioRequestHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
65
16
  JSONData extends Dictionary = any> = RequestHandler<CheerioCrawlingContext<UserData, JSONData>>;
@@ -142,23 +93,11 @@ JSONData extends Dictionary = any> = RequestHandler<CheerioCrawlingContext<UserD
142
93
  * ```
143
94
  * @category Crawlers
144
95
  */
145
- 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 HttpCrawler<CheerioCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
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> {
146
97
  /**
147
98
  * All `CheerioCrawler` parameters are passed via an options object.
148
99
  */
149
100
  constructor(options?: CheerioCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes, StatisticStateExtension>);
150
- // @ts-ignore optional peer dependency or compatibility with es2022
151
- protected buildContextPipeline(): import("@crawlee/http").ContextPipeline<CrawlingContext<Dictionary>, InternalHttpCrawlingContext<any, any> & {
152
- readonly body: string;
153
- readonly $: CheerioAPI;
154
- } & {
155
- extractLinks: (options?: ExtractLinksOptions) => Promise<string[]>;
156
- enqueueLinks: (options?: EnqueueLinksOptions) => Promise<AddRequestsBatchedResult>;
157
- waitForSelector: (selector: string, _timeoutMs?: number) => Promise<void>;
158
- parseWithCheerio: (selector?: string, timeoutMs?: number) => Promise<CheerioAPI>;
159
- }>;
160
- private parseContent;
161
- private addHelpers;
162
101
  }
163
102
  /**
164
103
  * Creates new {@link Router} instance that works based on request labels.
@@ -1,7 +1,5 @@
1
- import { EnqueueStrategy, HttpCrawler, NavigationSkippedError, resolveBaseUrlForEnqueueLinksFiltering, Router, } from '@crawlee/http';
2
- import { extractUrlsFromCheerio } from '@crawlee/utils/internal';
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,90 +79,12 @@ 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
- });
94
- }
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 addRequests = crawlingContext.addRequests;
134
- const extractLinks = async (options) => {
135
- if (!crawlingContext.$) {
136
- throw new Error('Cannot extract links because the DOM is not available.');
137
- }
138
- return extractUrlsFromCheerio(crawlingContext.$, options?.selector ?? 'a', options?.baseUrl ?? crawlingContext.request.loadedUrl ?? crawlingContext.request.url);
139
- };
140
- return {
141
- extractLinks,
142
- enqueueLinks: async (options = {}) => {
143
- const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
144
- enqueueStrategy: options.strategy,
145
- finalRequestUrl: crawlingContext.request.loadedUrl,
146
- originalRequestUrl: crawlingContext.request.url,
147
- userProvidedBaseUrl: options.baseUrl,
148
- });
149
- const urls = await extractLinks(options);
150
- return addRequests(urls, {
151
- ...options,
152
- baseUrl,
153
- strategy: options.strategy ?? EnqueueStrategy.SameHostname,
154
- });
155
- },
156
- waitForSelector: async (selector, _timeoutMs) => {
157
- if (crawlingContext.$(selector).get().length === 0) {
158
- throw new Error(`Selector '${selector}' not found.`);
159
- }
160
- },
161
- parseWithCheerio: async (selector, timeoutMs) => {
162
- if (selector) {
163
- await crawlingContext.waitForSelector(selector, timeoutMs);
164
- }
165
- return crawlingContext.$;
166
- },
167
- };
87
+ super({ ...options, parser: cheerioParser() });
168
88
  }
169
89
  }
170
90
  export function createCheerioRouter(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-rc.0",
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-rc.0",
51
- "@crawlee/types": "4.0.0-rc.0",
52
- "@crawlee/utils": "4.0.0-rc.0",
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": "79ab33dacdacb83e0197e6516d145f3aceef80c7"
64
+ "gitHead": "f354ca5e943bed1a5c657a1fce1974c053f9fff0"
65
65
  }