@crawlee/http 4.0.0-beta.80 → 4.0.0-beta.82
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/http-crawler.d.ts +23 -53
- package/internals/http-crawler.js +20 -13
- package/package.json +7 -7
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { BasicCrawlerOptions, CrawlingContext, ErrorHandler, GetUserDataFromRequest, Request as CrawleeRequest, RequestHandler, RequireContextPipeline, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/basic';
|
|
2
2
|
import { BasicCrawler, ContextPipeline } from '@crawlee/basic';
|
|
3
3
|
import { type LoadedRequest } from '@crawlee/core';
|
|
4
|
-
import type { Awaitable, Dictionary
|
|
4
|
+
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
5
5
|
import { type CheerioRoot } from '@crawlee/utils';
|
|
6
|
-
import type { RequestLike, ResponseLike } from 'content-type';
|
|
7
6
|
import type { JsonValue } from 'type-fest';
|
|
8
7
|
export type HttpErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
9
8
|
JSONData extends JsonValue = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
@@ -24,6 +23,11 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
|
|
|
24
23
|
*
|
|
25
24
|
* A hook may optionally return a partial object whose properties are merged into the crawling context,
|
|
26
25
|
* allowing the hook to override context members for subsequent hooks and pipeline stages.
|
|
26
|
+
*
|
|
27
|
+
* The context is built up in the following order: base context (`request`, `session`, helpers, ...) ->
|
|
28
|
+
* `extendContext` -> `preNavigationHooks` -> navigation -> `postNavigationHooks` -> `requestHandler`.
|
|
29
|
+
* This means the members added by `extendContext` are already available here, but navigation-dependent
|
|
30
|
+
* members (e.g. `response`, `body`, `$`) are not.
|
|
27
31
|
* Example:
|
|
28
32
|
* ```
|
|
29
33
|
* preNavigationHooks: [
|
|
@@ -33,7 +37,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
|
|
|
33
37
|
* ]
|
|
34
38
|
* ```
|
|
35
39
|
*/
|
|
36
|
-
preNavigationHooks?: InternalHttpHook<CrawlingContext>[];
|
|
40
|
+
preNavigationHooks?: InternalHttpHook<CrawlingContext, ContextExtension>[];
|
|
37
41
|
/**
|
|
38
42
|
* Async functions that are sequentially evaluated after the navigation. Good for checking if the navigation was successful.
|
|
39
43
|
* The function accepts `crawlingContext` as the only parameter.
|
|
@@ -51,7 +55,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
|
|
|
51
55
|
* ]
|
|
52
56
|
* ```
|
|
53
57
|
*/
|
|
54
|
-
postNavigationHooks?: ((crawlingContext: CrawlingContextWithResponse) => Awaitable<void | Partial<CrawlingContextWithResponse>>)[];
|
|
58
|
+
postNavigationHooks?: ((crawlingContext: CrawlingContextWithResponse & ContextExtension) => Awaitable<void | Partial<CrawlingContextWithResponse>>)[];
|
|
55
59
|
/**
|
|
56
60
|
* An array of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)
|
|
57
61
|
* you want the crawler to load and process. By default, only `text/html`, `application/xhtml+xml`, `text/xml`, `application/xml`,
|
|
@@ -91,7 +95,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
|
|
|
91
95
|
/**
|
|
92
96
|
* @internal
|
|
93
97
|
*/
|
|
94
|
-
export type InternalHttpHook<Context> = (crawlingContext: Context) => Awaitable<void | Partial<Context>>;
|
|
98
|
+
export type InternalHttpHook<Context, ContextExtension = {}> = (crawlingContext: Context & ContextExtension) => Awaitable<void | Partial<Context>>;
|
|
95
99
|
export type HttpHook<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
96
100
|
JSONData extends JsonValue = any> = InternalHttpHook<HttpCrawlingContext<UserData, JSONData>>;
|
|
97
101
|
interface CrawlingContextWithResponse<UserData extends Dictionary = any> extends CrawlingContext<UserData> {
|
|
@@ -228,14 +232,14 @@ JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData,
|
|
|
228
232
|
* @category Crawlers
|
|
229
233
|
*/
|
|
230
234
|
export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension> extends BasicCrawler<Context, ContextExtension, ExtendedContext> {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
private preNavigationHooks;
|
|
236
|
+
private postNavigationHooks;
|
|
237
|
+
private saveResponseCookies;
|
|
238
|
+
private navigationTimeoutMillis;
|
|
239
|
+
private ignoreSslErrors;
|
|
240
|
+
private suggestResponseEncoding?;
|
|
241
|
+
private forceResponseEncoding?;
|
|
242
|
+
private readonly supportedMimeTypes;
|
|
239
243
|
protected static optionsShape: {
|
|
240
244
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
241
245
|
navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
|
|
@@ -337,64 +341,30 @@ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any
|
|
|
337
341
|
* on the request such as only downloading the request body if the
|
|
338
342
|
* received content type matches text/html, application/xml, application/xhtml+xml.
|
|
339
343
|
*/
|
|
340
|
-
|
|
344
|
+
private requestFunction;
|
|
341
345
|
/**
|
|
342
346
|
* Encodes and parses response according to the provided content type
|
|
343
347
|
*/
|
|
344
|
-
|
|
345
|
-
response: Response;
|
|
346
|
-
contentType: {
|
|
347
|
-
type: string;
|
|
348
|
-
encoding: BufferEncoding;
|
|
349
|
-
};
|
|
350
|
-
body: string;
|
|
351
|
-
} | {
|
|
352
|
-
body: Buffer<ArrayBuffer>;
|
|
353
|
-
response: Response;
|
|
354
|
-
contentType: {
|
|
355
|
-
type: string;
|
|
356
|
-
encoding: BufferEncoding;
|
|
357
|
-
};
|
|
358
|
-
}>;
|
|
348
|
+
private parseResponse;
|
|
359
349
|
/**
|
|
360
350
|
* Combines the provided `requestOptions` with mandatory (non-overridable) values.
|
|
361
351
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
365
|
-
method: import("@crawlee/types").AllowedHttpMethods;
|
|
366
|
-
proxyUrl: string | undefined;
|
|
367
|
-
timeout: number;
|
|
368
|
-
sessionToken: ISession;
|
|
369
|
-
headers: Record<string, string> | undefined;
|
|
370
|
-
https: {
|
|
371
|
-
rejectUnauthorized: boolean;
|
|
372
|
-
};
|
|
373
|
-
body: string | undefined;
|
|
374
|
-
};
|
|
375
|
-
protected _encodeResponse(request: CrawleeRequest, response: Response, encoding: BufferEncoding): {
|
|
376
|
-
encoding: BufferEncoding;
|
|
377
|
-
response: Response;
|
|
378
|
-
};
|
|
352
|
+
private getRequestOptions;
|
|
353
|
+
private encodeResponse;
|
|
379
354
|
/**
|
|
380
355
|
* Checks and extends supported mime types
|
|
381
356
|
*/
|
|
382
|
-
|
|
357
|
+
private extendSupportedMimeTypes;
|
|
383
358
|
/**
|
|
384
359
|
* Handles timeout request
|
|
385
360
|
*/
|
|
386
|
-
|
|
361
|
+
private handleRequestTimeout;
|
|
387
362
|
private _abortDownloadOfBody;
|
|
388
363
|
/**
|
|
389
364
|
* @internal wraps public utility for mocking purposes
|
|
390
365
|
*/
|
|
391
366
|
private _requestAsBrowser;
|
|
392
367
|
}
|
|
393
|
-
interface RequestFunctionOptions {
|
|
394
|
-
request: CrawleeRequest;
|
|
395
|
-
session: ISession;
|
|
396
|
-
proxyUrl?: string;
|
|
397
|
-
}
|
|
398
368
|
/**
|
|
399
369
|
* Creates new {@link Router} instance that works based on request labels.
|
|
400
370
|
* This instance can then serve as a `requestHandler` of your {@link HttpCrawler}.
|
|
@@ -94,6 +94,10 @@ const HTTP_OPTIMIZED_AUTOSCALED_POOL_OPTIONS = {
|
|
|
94
94
|
* @category Crawlers
|
|
95
95
|
*/
|
|
96
96
|
export class HttpCrawler extends BasicCrawler {
|
|
97
|
+
// Internal storage uses the base (non-extended) context types. The public option types are
|
|
98
|
+
// extension-aware for consumer DX, but internally the pipeline composes hooks against the
|
|
99
|
+
// concrete crawling context, which does not statically carry `ContextExtension`. The members
|
|
100
|
+
// added by `extendContext` are present at runtime regardless.
|
|
97
101
|
preNavigationHooks;
|
|
98
102
|
postNavigationHooks;
|
|
99
103
|
saveResponseCookies;
|
|
@@ -129,7 +133,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
129
133
|
});
|
|
130
134
|
this.supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
|
|
131
135
|
if (additionalMimeTypes.length)
|
|
132
|
-
this.
|
|
136
|
+
this.extendSupportedMimeTypes(additionalMimeTypes);
|
|
133
137
|
if (suggestResponseEncoding && forceResponseEncoding) {
|
|
134
138
|
this.log.warning('Both forceResponseEncoding and suggestResponseEncoding options are set. Using forceResponseEncoding.');
|
|
135
139
|
}
|
|
@@ -137,6 +141,9 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
137
141
|
this.ignoreSslErrors = ignoreSslErrors;
|
|
138
142
|
this.suggestResponseEncoding = suggestResponseEncoding;
|
|
139
143
|
this.forceResponseEncoding = forceResponseEncoding;
|
|
144
|
+
// Cast away the extension-aware option types to the base internal storage types (see the field
|
|
145
|
+
// declarations above). This is sound - the hooks only ever receive the base context plus the
|
|
146
|
+
// members `extendContext` added at runtime.
|
|
140
147
|
this.preNavigationHooks = preNavigationHooks;
|
|
141
148
|
this.postNavigationHooks = [
|
|
142
149
|
({ request, response }) => this._abortDownloadOfBody(request, response),
|
|
@@ -188,7 +195,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
188
195
|
tryCancel();
|
|
189
196
|
const { request, session } = crawlingContext;
|
|
190
197
|
const proxyUrl = crawlingContext.proxyInfo?.url;
|
|
191
|
-
const httpResponse = await addTimeoutToPromise(async () => this.
|
|
198
|
+
const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), this.navigationTimeoutMillis, `request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
|
|
192
199
|
tryCancel();
|
|
193
200
|
request.loadedUrl = httpResponse?.url;
|
|
194
201
|
request.state = RequestState.AFTER_NAV;
|
|
@@ -215,7 +222,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
215
222
|
};
|
|
216
223
|
}
|
|
217
224
|
tryCancel();
|
|
218
|
-
const parsed = await this.
|
|
225
|
+
const parsed = await this.parseResponse(crawlingContext.request, crawlingContext.response);
|
|
219
226
|
tryCancel();
|
|
220
227
|
const response = parsed.response;
|
|
221
228
|
const contentType = parsed.contentType;
|
|
@@ -291,15 +298,15 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
291
298
|
* on the request such as only downloading the request body if the
|
|
292
299
|
* received content type matches text/html, application/xml, application/xhtml+xml.
|
|
293
300
|
*/
|
|
294
|
-
async
|
|
295
|
-
const opts = this.
|
|
301
|
+
async requestFunction({ request, session, proxyUrl }) {
|
|
302
|
+
const opts = this.getRequestOptions(request, session, proxyUrl);
|
|
296
303
|
try {
|
|
297
304
|
return await this._requestAsBrowser(opts, session);
|
|
298
305
|
}
|
|
299
306
|
catch (e) {
|
|
300
307
|
if (e instanceof Error && e.constructor.name === 'TimeoutError') {
|
|
301
|
-
this.
|
|
302
|
-
return new Response(); // this will never happen, as
|
|
308
|
+
this.handleRequestTimeout(session);
|
|
309
|
+
return new Response(); // this will never happen, as handleRequestTimeout always throws
|
|
303
310
|
}
|
|
304
311
|
if (this.isProxyError(e)) {
|
|
305
312
|
throw new SessionError(this._getMessageFromError(e));
|
|
@@ -312,10 +319,10 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
312
319
|
/**
|
|
313
320
|
* Encodes and parses response according to the provided content type
|
|
314
321
|
*/
|
|
315
|
-
async
|
|
322
|
+
async parseResponse(request, response) {
|
|
316
323
|
const { status } = response;
|
|
317
324
|
const { type, charset } = parseContentTypeFromResponse(response);
|
|
318
|
-
const { response: reencodedResponse, encoding } = this.
|
|
325
|
+
const { response: reencodedResponse, encoding } = this.encodeResponse(request, response, charset);
|
|
319
326
|
const contentType = { type, encoding };
|
|
320
327
|
if (status >= 400 && status <= 599) {
|
|
321
328
|
this.stats.registerStatusCode(status);
|
|
@@ -361,7 +368,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
361
368
|
/**
|
|
362
369
|
* Combines the provided `requestOptions` with mandatory (non-overridable) values.
|
|
363
370
|
*/
|
|
364
|
-
|
|
371
|
+
getRequestOptions(request, session, proxyUrl) {
|
|
365
372
|
const requestOptions = {
|
|
366
373
|
url: request.url,
|
|
367
374
|
method: request.method,
|
|
@@ -389,7 +396,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
389
396
|
requestOptions.body = request.payload ?? '';
|
|
390
397
|
return requestOptions;
|
|
391
398
|
}
|
|
392
|
-
|
|
399
|
+
encodeResponse(request, response, encoding) {
|
|
393
400
|
if (this.forceResponseEncoding) {
|
|
394
401
|
encoding = this.forceResponseEncoding;
|
|
395
402
|
}
|
|
@@ -425,7 +432,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
425
432
|
/**
|
|
426
433
|
* Checks and extends supported mime types
|
|
427
434
|
*/
|
|
428
|
-
|
|
435
|
+
extendSupportedMimeTypes(additionalMimeTypes) {
|
|
429
436
|
for (const mimeType of additionalMimeTypes) {
|
|
430
437
|
if (mimeType === '*/*') {
|
|
431
438
|
this.supportedMimeTypes.add(mimeType);
|
|
@@ -443,7 +450,7 @@ export class HttpCrawler extends BasicCrawler {
|
|
|
443
450
|
/**
|
|
444
451
|
* Handles timeout request
|
|
445
452
|
*/
|
|
446
|
-
|
|
453
|
+
handleRequestTimeout(session) {
|
|
447
454
|
session.markBad();
|
|
448
455
|
throw new Error(`request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
|
|
449
456
|
}
|
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.82",
|
|
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.0.0"
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/timeout": "^0.3.2",
|
|
51
51
|
"@apify/utilities": "^2.15.5",
|
|
52
|
-
"@crawlee/basic": "4.0.0-beta.
|
|
53
|
-
"@crawlee/core": "4.0.0-beta.
|
|
54
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
55
|
-
"@crawlee/types": "4.0.0-beta.
|
|
56
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
52
|
+
"@crawlee/basic": "4.0.0-beta.82",
|
|
53
|
+
"@crawlee/core": "4.0.0-beta.82",
|
|
54
|
+
"@crawlee/http-client": "4.0.0-beta.82",
|
|
55
|
+
"@crawlee/types": "4.0.0-beta.82",
|
|
56
|
+
"@crawlee/utils": "4.0.0-beta.82",
|
|
57
57
|
"@types/content-type": "^1.1.8",
|
|
58
58
|
"cheerio": "^1.0.0",
|
|
59
59
|
"content-type": "^1.0.5",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "eb1096f7c7743d124375ef011fbbadb19476822e"
|
|
74
74
|
}
|