@crawlee/http 4.0.0-beta.230 → 4.0.0-beta.232
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/internals/dom-crawler.d.ts +26 -12
- package/internals/dom-crawler.js +18 -9
- package/internals/http-crawler.d.ts +15 -1
- package/internals/http-crawler.js +16 -3
- package/package.json +7 -6
|
@@ -31,7 +31,7 @@ export interface DOMParseResult {
|
|
|
31
31
|
* });
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
-
export interface DOMParser<Parsed extends DOMParseResult> {
|
|
34
|
+
export interface DOMParser<Parsed extends DOMParseResult, Selection extends ArrayLike<unknown> = ArrayLike<unknown>, Single = unknown> {
|
|
35
35
|
/**
|
|
36
36
|
* The context members {@link DOMParser.parse|`parse`} contributes, mapped to `true`. Used to build the
|
|
37
37
|
* placeholders that report a helpful error when the members are accessed after `skipNavigation` - the `Record`
|
|
@@ -45,10 +45,15 @@ export interface DOMParser<Parsed extends DOMParseResult> {
|
|
|
45
45
|
*/
|
|
46
46
|
extractLinks(parsed: Parsed, selector: string, baseUrl: string): Awaitable<string[]>;
|
|
47
47
|
/**
|
|
48
|
-
* Returns the current matches of `selector
|
|
49
|
-
* {@link DOMCrawlingContext.
|
|
48
|
+
* Returns the current matches of `selector`, in whatever form the DOM implementation uses - this is what
|
|
49
|
+
* {@link DOMCrawlingContext.querySelectorAll|`querySelectorAll`} hands to the request handler.
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
querySelectorAll(parsed: Parsed, selector: string): Awaitable<Selection>;
|
|
52
|
+
/**
|
|
53
|
+
* Returns the first match of `selector`, or `undefined` when there is none - this is what
|
|
54
|
+
* {@link DOMCrawlingContext.querySelectorOne|`querySelectorOne`} hands to the request handler.
|
|
55
|
+
*/
|
|
56
|
+
querySelectorOne(parsed: Parsed, selector: string): Awaitable<Single | undefined>;
|
|
52
57
|
/**
|
|
53
58
|
* Whether the parse result can change after {@link DOMParser.parse|`parse`} returned - the case when the DOM
|
|
54
59
|
* implementation runs the page scripts. If it can, {@link DOMCrawlingContext.waitForSelector|`waitForSelector`}
|
|
@@ -67,7 +72,7 @@ export interface DOMParser<Parsed extends DOMParseResult> {
|
|
|
67
72
|
*/
|
|
68
73
|
cleanup?(parsed: Parsed): Awaitable<void>;
|
|
69
74
|
}
|
|
70
|
-
export interface DOMCrawlingHelpers {
|
|
75
|
+
export interface DOMCrawlingHelpers<Selection extends ArrayLike<unknown> = ArrayLike<unknown>, Single = unknown> {
|
|
71
76
|
/**
|
|
72
77
|
* Extracts URLs from the parsed DOM, without adding them to the request queue.
|
|
73
78
|
*/
|
|
@@ -92,6 +97,15 @@ export interface DOMCrawlingHelpers {
|
|
|
92
97
|
* ```
|
|
93
98
|
*/
|
|
94
99
|
waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Like {@link DOMCrawlingHelpers.waitForSelector|`waitForSelector`}, but returns the matches in the form
|
|
102
|
+
* the parser produces them - a Cheerio object in {@link CheerioCrawler}. Throws when nothing matches.
|
|
103
|
+
*/
|
|
104
|
+
querySelectorAll(selector: string, timeoutMs?: number): Promise<Selection>;
|
|
105
|
+
/**
|
|
106
|
+
* Like {@link DOMCrawlingHelpers.querySelectorAll|`querySelectorAll`}, but returns only the first match.
|
|
107
|
+
*/
|
|
108
|
+
querySelectorOne(selector: string, timeoutMs?: number): Promise<Single>;
|
|
95
109
|
/**
|
|
96
110
|
* Returns Cheerio handle, allowing to work with the data same way as with {@link CheerioCrawler}.
|
|
97
111
|
* When provided with the `selector` argument, it will throw if it's not available.
|
|
@@ -106,14 +120,14 @@ export interface DOMCrawlingHelpers {
|
|
|
106
120
|
*/
|
|
107
121
|
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioAPI>;
|
|
108
122
|
}
|
|
109
|
-
export type DOMCrawlingContext<Parsed extends DOMParseResult = DOMParseResult, UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
110
|
-
JSONData extends Dictionary = any> = InternalHttpCrawlingContext<UserData, JSONData> & Parsed & DOMCrawlingHelpers
|
|
111
|
-
export interface DOMCrawlerOptions<Parsed extends DOMParseResult = DOMParseResult, ContextExtension = Dictionary<never>, ExtendedContext extends DOMCrawlingContext<Parsed> = DOMCrawlingContext<Parsed> & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, any>, StatisticStateExtension extends object = {}> extends Omit<HttpCrawlerOptions<DOMCrawlingContext<Parsed>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>, 'contextPipelineBuilder'> {
|
|
123
|
+
export type DOMCrawlingContext<Parsed extends DOMParseResult = DOMParseResult, Selection extends ArrayLike<unknown> = ArrayLike<unknown>, Single = unknown, UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
124
|
+
JSONData extends Dictionary = any> = Omit<InternalHttpCrawlingContext<UserData, JSONData>, keyof DOMCrawlingHelpers> & Parsed & DOMCrawlingHelpers<Selection, Single>;
|
|
125
|
+
export interface DOMCrawlerOptions<Parsed extends DOMParseResult = DOMParseResult, Selection extends ArrayLike<unknown> = ArrayLike<unknown>, Single = unknown, ContextExtension = Dictionary<never>, ExtendedContext extends DOMCrawlingContext<Parsed, Selection, Single> = DOMCrawlingContext<Parsed, Selection, Single> & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, any>, StatisticStateExtension extends object = {}> extends Omit<HttpCrawlerOptions<DOMCrawlingContext<Parsed, Selection, Single>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>, 'contextPipelineBuilder'> {
|
|
112
126
|
/**
|
|
113
127
|
* The DOM implementation to parse the response bodies with. Its parse result becomes part of the crawling
|
|
114
128
|
* context, so the members the request handler receives follow from the parser you pass.
|
|
115
129
|
*/
|
|
116
|
-
parser: DOMParser<Parsed>;
|
|
130
|
+
parser: DOMParser<Parsed, Selection, Single>;
|
|
117
131
|
}
|
|
118
132
|
/**
|
|
119
133
|
* An {@link HttpCrawler} that parses each response into a DOM using the {@link DOMCrawlerOptions.parser|`parser`}
|
|
@@ -124,8 +138,8 @@ export interface DOMCrawlerOptions<Parsed extends DOMParseResult = DOMParseResul
|
|
|
124
138
|
*
|
|
125
139
|
* @category Crawlers
|
|
126
140
|
*/
|
|
127
|
-
export declare class DOMCrawler<Parsed extends DOMParseResult = DOMParseResult, ContextExtension = Dictionary<never>, ExtendedContext extends DOMCrawlingContext<Parsed> = DOMCrawlingContext<Parsed> & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<DOMCrawlingContext<Parsed>['request']>>, StatisticStateExtension extends object = {}> extends HttpCrawler<DOMCrawlingContext<Parsed>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
141
|
+
export declare class DOMCrawler<Parsed extends DOMParseResult = DOMParseResult, Selection extends ArrayLike<unknown> = ArrayLike<unknown>, Single = unknown, ContextExtension = Dictionary<never>, ExtendedContext extends DOMCrawlingContext<Parsed, Selection, Single> = DOMCrawlingContext<Parsed, Selection, Single> & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<DOMCrawlingContext<Parsed, Selection, Single>['request']>>, StatisticStateExtension extends object = {}> extends HttpCrawler<DOMCrawlingContext<Parsed, Selection, Single>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
128
142
|
#private;
|
|
129
|
-
constructor(options: DOMCrawlerOptions<Parsed, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
130
|
-
protected buildContextPipeline(): ContextPipeline<CrawlingContext, DOMCrawlingContext<Parsed>>;
|
|
143
|
+
constructor(options: DOMCrawlerOptions<Parsed, Selection, Single, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
144
|
+
protected buildContextPipeline(): ContextPipeline<CrawlingContext, DOMCrawlingContext<Parsed, Selection, Single>>;
|
|
131
145
|
}
|
package/internals/dom-crawler.js
CHANGED
|
@@ -50,19 +50,29 @@ export class DOMCrawler extends HttpCrawler {
|
|
|
50
50
|
const { addRequests } = context;
|
|
51
51
|
const parser = this.#parser;
|
|
52
52
|
const extractLinks = async (options) => parser.extractLinks(context, options?.selector ?? 'a', options?.baseUrl ?? context.request.loadedUrl ?? context.request.url);
|
|
53
|
-
|
|
53
|
+
// Polls `query` while `isEmpty` holds, for as long as a mutable parser allows; a static one gets one shot.
|
|
54
|
+
const poll = async (selector, timeoutMs, query, isEmpty) => {
|
|
54
55
|
let remaining = parser.mutable ? timeoutMs : 0;
|
|
55
|
-
|
|
56
|
+
let result = await query();
|
|
57
|
+
while (isEmpty(result)) {
|
|
56
58
|
if (remaining <= 0) {
|
|
57
59
|
throw new Error(`Selector '${selector}' not found.`);
|
|
58
60
|
}
|
|
59
61
|
await sleep(50);
|
|
60
62
|
remaining -= 50;
|
|
63
|
+
result = await query();
|
|
61
64
|
}
|
|
65
|
+
return result;
|
|
66
|
+
};
|
|
67
|
+
const querySelectorAll = async (selector, timeoutMs = 5_000) => poll(selector, timeoutMs, () => parser.querySelectorAll(context, selector), (matches) => matches.length === 0);
|
|
68
|
+
const waitForSelector = async (selector, timeoutMs) => {
|
|
69
|
+
await querySelectorAll(selector, timeoutMs);
|
|
62
70
|
};
|
|
63
71
|
return {
|
|
64
72
|
extractLinks,
|
|
65
73
|
waitForSelector,
|
|
74
|
+
querySelectorAll,
|
|
75
|
+
querySelectorOne: async (selector, timeoutMs = 5_000) => poll(selector, timeoutMs, () => parser.querySelectorOne(context, selector), (match) => match === undefined),
|
|
66
76
|
enqueueLinks: async (options = {}) => {
|
|
67
77
|
const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
|
|
68
78
|
enqueueStrategy: options.strategy,
|
|
@@ -77,16 +87,15 @@ export class DOMCrawler extends HttpCrawler {
|
|
|
77
87
|
strategy: options.strategy ?? EnqueueStrategy.SameHostname,
|
|
78
88
|
});
|
|
79
89
|
},
|
|
80
|
-
async parseWithCheerio(selector,
|
|
90
|
+
async parseWithCheerio(selector, timeoutMs) {
|
|
91
|
+
if (selector) {
|
|
92
|
+
await waitForSelector(selector, timeoutMs);
|
|
93
|
+
}
|
|
81
94
|
// Import full cheerio (not cheerio/slim) to be browser-compliant for DOM Crawlers (not CheerioCrawler).
|
|
82
|
-
|
|
95
|
+
return ((await parser.toCheerio?.(context)) ??
|
|
83
96
|
(await import('cheerio')).load(context.body, {
|
|
84
97
|
xmlMode: context.contentType.type.includes('xml'),
|
|
85
|
-
});
|
|
86
|
-
if (selector && $(selector).get().length === 0) {
|
|
87
|
-
throw new Error(`Selector '${selector}' not found.`);
|
|
88
|
-
}
|
|
89
|
-
return $;
|
|
98
|
+
}));
|
|
90
99
|
},
|
|
91
100
|
};
|
|
92
101
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BasicCrawlerOptions, ConcurrencySystem, ConcurrencySystemOptions, CrawlingContext, ErrorHandler, GetUserDataFromRequest, LoadedRequest, CrawlingRequest, RequireContextPipeline, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/basic';
|
|
2
2
|
import { BasicCrawler, ContextPipeline } from '@crawlee/basic';
|
|
3
3
|
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
4
|
-
import type { CheerioAPI } from 'cheerio';
|
|
4
|
+
import type { Cheerio, CheerioAPI } from 'cheerio';
|
|
5
|
+
import type { AnyNode } from 'domhandler';
|
|
5
6
|
import type { JsonValue } from 'type-fest';
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
/**
|
|
@@ -162,6 +163,17 @@ JSONData extends JsonValue = any> extends CrawlingContextWithResponse<UserData>
|
|
|
162
163
|
* ```
|
|
163
164
|
*/
|
|
164
165
|
waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Like {@link InternalHttpCrawlingContext.waitForSelector|`waitForSelector`}, but returns the matched
|
|
168
|
+
* elements - a Cheerio object here, whatever the parser produces in {@link DOMCrawler}. Throws when nothing
|
|
169
|
+
* matches.
|
|
170
|
+
*/
|
|
171
|
+
querySelectorAll(selector: string, timeoutMs?: number): Promise<ArrayLike<unknown>>;
|
|
172
|
+
/**
|
|
173
|
+
* Like {@link InternalHttpCrawlingContext.querySelectorAll|`querySelectorAll`}, but returns only the first
|
|
174
|
+
* match.
|
|
175
|
+
*/
|
|
176
|
+
querySelectorOne(selector: string, timeoutMs?: number): Promise<unknown>;
|
|
165
177
|
/**
|
|
166
178
|
* Returns Cheerio handle for `page.content()`, allowing to work with the data same way as with {@link CheerioCrawler}.
|
|
167
179
|
* This is here to unify the crawler API, so they all have this handy method - in {@link CheerioCrawler} it has
|
|
@@ -182,6 +194,8 @@ JSONData extends JsonValue = any> extends CrawlingContextWithResponse<UserData>
|
|
|
182
194
|
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioAPI>;
|
|
183
195
|
}
|
|
184
196
|
export interface HttpCrawlingContext<UserData extends Dictionary = any, JSONData extends JsonValue = any> extends InternalHttpCrawlingContext<UserData, JSONData> {
|
|
197
|
+
querySelectorAll(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
198
|
+
querySelectorOne(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
185
199
|
}
|
|
186
200
|
export type HttpRequestHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
187
201
|
JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData, JSONData>>;
|
|
@@ -258,6 +258,12 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
258
258
|
get parseWithCheerio() {
|
|
259
259
|
throw new NavigationSkippedError('The `parseWithCheerio` method is not available - `skipNavigation` was used');
|
|
260
260
|
},
|
|
261
|
+
get querySelectorAll() {
|
|
262
|
+
throw new NavigationSkippedError('The `querySelectorAll` method is not available - `skipNavigation` was used');
|
|
263
|
+
},
|
|
264
|
+
get querySelectorOne() {
|
|
265
|
+
throw new NavigationSkippedError('The `querySelectorOne` method is not available - `skipNavigation` was used');
|
|
266
|
+
},
|
|
261
267
|
};
|
|
262
268
|
}
|
|
263
269
|
tryCancel();
|
|
@@ -287,11 +293,16 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
287
293
|
const { load } = await import('cheerio/slim');
|
|
288
294
|
return load(parsed.body.toString(), { xmlMode: contentType.type.includes('xml') });
|
|
289
295
|
};
|
|
290
|
-
const
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
296
|
+
const querySelectorAll = async (selector, _timeoutMs) => {
|
|
297
|
+
const matches = (await loadBody())(selector);
|
|
298
|
+
if (matches.length === 0) {
|
|
293
299
|
throw new Error(`Selector '${selector}' not found.`);
|
|
294
300
|
}
|
|
301
|
+
return matches;
|
|
302
|
+
};
|
|
303
|
+
const querySelectorOne = async (selector, timeoutMs) => (await querySelectorAll(selector, timeoutMs)).first();
|
|
304
|
+
const waitForSelector = async (selector, timeoutMs) => {
|
|
305
|
+
await querySelectorAll(selector, timeoutMs);
|
|
295
306
|
};
|
|
296
307
|
const parseWithCheerio = async (selector, timeoutMs) => {
|
|
297
308
|
const $ = await loadBody();
|
|
@@ -328,6 +339,8 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
328
339
|
return JSON.parse(jsonString);
|
|
329
340
|
},
|
|
330
341
|
waitForSelector,
|
|
342
|
+
querySelectorOne,
|
|
343
|
+
querySelectorAll,
|
|
331
344
|
parseWithCheerio,
|
|
332
345
|
contentType,
|
|
333
346
|
body: parsed.body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/http",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.232",
|
|
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
6
|
"node": ">=22.13.0"
|
|
@@ -48,12 +48,13 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/timeout": "^1.0.1",
|
|
51
|
-
"@crawlee/basic": "4.0.0-beta.
|
|
52
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
53
|
-
"@crawlee/types": "4.0.0-beta.
|
|
54
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
51
|
+
"@crawlee/basic": "4.0.0-beta.232",
|
|
52
|
+
"@crawlee/http-client": "4.0.0-beta.232",
|
|
53
|
+
"@crawlee/types": "4.0.0-beta.232",
|
|
54
|
+
"@crawlee/utils": "4.0.0-beta.232",
|
|
55
55
|
"cheerio": "^1.0.0",
|
|
56
56
|
"content-type": "^3.1.1",
|
|
57
|
+
"domhandler": "^6.0.0",
|
|
57
58
|
"iconv-lite": "^0.7.2",
|
|
58
59
|
"mime-types": "^3.0.1",
|
|
59
60
|
"tslib": "^2.8.1",
|
|
@@ -67,5 +68,5 @@
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
},
|
|
70
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "5eab8f877242d46d7cae13b640d8e1adbf194911"
|
|
71
72
|
}
|