@crawlee/basic 4.0.0-beta.81 → 4.0.0-beta.83
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/basic-crawler.d.ts +16 -15
- package/internals/basic-crawler.js +40 -36
- package/package.json +7 -7
|
@@ -46,7 +46,13 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
|
|
|
46
46
|
*/
|
|
47
47
|
requestHandler?: RequestHandler<ExtendedContext>;
|
|
48
48
|
/**
|
|
49
|
-
* Allows the user to extend the crawling context
|
|
49
|
+
* Allows the user to extend the crawling context with custom functionality (helpers, references, etc.).
|
|
50
|
+
*
|
|
51
|
+
* `extendContext` runs *before* navigation, so the returned members are visible to the
|
|
52
|
+
* `preNavigationHooks`, `postNavigationHooks`, and the `requestHandler` alike. As a consequence,
|
|
53
|
+
* the `context` passed to `extendContext` is the pre-navigation {@link CrawlingContext} and does
|
|
54
|
+
* **not** include navigation-dependent members (e.g. `page`, `response`, `$`, `body`). If you need
|
|
55
|
+
* those, use a `postNavigationHook` or the `requestHandler` instead.
|
|
50
56
|
*
|
|
51
57
|
* **Example usage:**
|
|
52
58
|
*
|
|
@@ -66,7 +72,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
|
|
|
66
72
|
* });
|
|
67
73
|
* ```
|
|
68
74
|
*/
|
|
69
|
-
extendContext?: (context:
|
|
75
|
+
extendContext?: (context: CrawlingContext) => Awaitable<ContextExtension>;
|
|
70
76
|
/**
|
|
71
77
|
* *Intended for BasicCrawler subclasses*. Prepares a context pipeline that transforms the initial crawling context into the shape given by the `Context` type parameter.
|
|
72
78
|
*
|
|
@@ -405,24 +411,19 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
405
411
|
* via {@link BasicCrawler.getRequestManager|`getRequestManager()`}.
|
|
406
412
|
*/
|
|
407
413
|
protected requestManager?: IRequestManager;
|
|
414
|
+
/** Backs the {@link BasicCrawler.sessionPool|`sessionPool`} getter. */
|
|
415
|
+
private sessionPoolDep;
|
|
408
416
|
/**
|
|
409
417
|
* A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
|
|
410
418
|
* {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
|
|
411
419
|
*/
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Set when the crawler constructed its own {@link SessionPool} (no `sessionPool` option was provided).
|
|
415
|
-
* Holds the same instance as `sessionPool`, but typed as the concrete class so the crawler can call
|
|
416
|
-
* lifecycle methods (`resetStore`, `teardown`) that aren't part of {@link ISessionPool}. A user-supplied
|
|
417
|
-
* pool is never owned and never torn down by the crawler.
|
|
418
|
-
*/
|
|
419
|
-
private ownedSessionPool?;
|
|
420
|
+
get sessionPool(): ISessionPool;
|
|
420
421
|
/**
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
422
|
+
* Tracks **only** the queue the crawler opens for itself — not the {@link RequestManagerTandem} that may wrap it
|
|
423
|
+
* around a user-supplied `requestList` — so the owned-only purge between repeated `run()` calls never reaches
|
|
424
|
+
* through to a borrowed loader. Filled lazily in {@link BasicCrawler.openOwnedRequestQueue|`openOwnedRequestQueue()`}.
|
|
424
425
|
*/
|
|
425
|
-
private
|
|
426
|
+
private ownedRequestQueue;
|
|
426
427
|
/**
|
|
427
428
|
* Whether the request-processing-time hint has already been forwarded to the request manager. The hint
|
|
428
429
|
* derives only from `requestHandlerTimeoutMillis` (constant for the crawler's lifetime) and is raise-only,
|
|
@@ -637,7 +638,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
637
638
|
getRequestQueue(): Promise<IRequestManager>;
|
|
638
639
|
/**
|
|
639
640
|
* Opens the default {@link RequestQueue}, applies the crawler's timeouts to it and records it as the
|
|
640
|
-
* crawler-owned
|
|
641
|
+
* crawler-owned queue (so it gets purged between repeated `run()` calls).
|
|
641
642
|
* @private
|
|
642
643
|
*/
|
|
643
644
|
private openOwnedRequestQueue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, CriticalError, Dataset, enqueueLinks, EnqueueStrategy, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, purgeDefaultStorages, RequestHandlerError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, Router, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, validateUserData, validators, } from '@crawlee/core';
|
|
3
|
+
import { AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, CriticalError, Dataset, enqueueLinks, EnqueueStrategy, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, OwnedOrInjected, purgeDefaultStorages, RequestHandlerError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, Router, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, validateUserData, validators, } from '@crawlee/core';
|
|
4
4
|
import { FetchHttpClient } from '@crawlee/http-client';
|
|
5
5
|
import { getObjectType, isAsyncIterable, isIterable, RobotsTxtFile, ROTATE_PROXY_ERRORS } from '@crawlee/utils';
|
|
6
6
|
import { stringify } from 'csv-stringify/sync';
|
|
@@ -60,24 +60,21 @@ export class BasicCrawler {
|
|
|
60
60
|
* via {@link BasicCrawler.getRequestManager|`getRequestManager()`}.
|
|
61
61
|
*/
|
|
62
62
|
requestManager;
|
|
63
|
+
/** Backs the {@link BasicCrawler.sessionPool|`sessionPool`} getter. */
|
|
64
|
+
sessionPoolDep;
|
|
63
65
|
/**
|
|
64
66
|
* A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
|
|
65
67
|
* {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
|
|
66
68
|
*/
|
|
67
|
-
sessionPool
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* Holds the same instance as `sessionPool`, but typed as the concrete class so the crawler can call
|
|
71
|
-
* lifecycle methods (`resetStore`, `teardown`) that aren't part of {@link ISessionPool}. A user-supplied
|
|
72
|
-
* pool is never owned and never torn down by the crawler.
|
|
73
|
-
*/
|
|
74
|
-
ownedSessionPool;
|
|
69
|
+
get sessionPool() {
|
|
70
|
+
return this.sessionPoolDep.value;
|
|
71
|
+
}
|
|
75
72
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
73
|
+
* Tracks **only** the queue the crawler opens for itself — not the {@link RequestManagerTandem} that may wrap it
|
|
74
|
+
* around a user-supplied `requestList` — so the owned-only purge between repeated `run()` calls never reaches
|
|
75
|
+
* through to a borrowed loader. Filled lazily in {@link BasicCrawler.openOwnedRequestQueue|`openOwnedRequestQueue()`}.
|
|
79
76
|
*/
|
|
80
|
-
|
|
77
|
+
ownedRequestQueue = OwnedOrInjected.resolve();
|
|
81
78
|
/**
|
|
82
79
|
* Whether the request-processing-time hint has already been forwarded to the request manager. The hint
|
|
83
80
|
* derives only from `requestHandlerTimeoutMillis` (constant for the crawler's lifetime) and is raise-only,
|
|
@@ -297,18 +294,12 @@ export class BasicCrawler {
|
|
|
297
294
|
'`proxyInfo` they were created with. Configure proxies on the pool instead, ' +
|
|
298
295
|
'e.g. via `addSession({ proxyInfo })` or a custom `createSessionFunction`.');
|
|
299
296
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
...opts?.sessionOptions,
|
|
307
|
-
proxyInfo: opts?.sessionOptions?.proxyInfo ?? (await this.proxyConfiguration?.newProxyInfo()),
|
|
308
|
-
}),
|
|
309
|
-
});
|
|
310
|
-
this.sessionPool = this.ownedSessionPool;
|
|
311
|
-
}
|
|
297
|
+
this.sessionPoolDep = OwnedOrInjected.resolve(sessionPool, () => new SessionPool({
|
|
298
|
+
createSessionFunction: async (opts) => new Session({
|
|
299
|
+
...opts?.sessionOptions,
|
|
300
|
+
proxyInfo: opts?.sessionOptions?.proxyInfo ?? (await this.proxyConfiguration?.newProxyInfo()),
|
|
301
|
+
}),
|
|
302
|
+
}));
|
|
312
303
|
this.blockedStatusCodes = new Set(blockedStatusCodesInput ?? BLOCKED_STATUS_CODES);
|
|
313
304
|
const maxSignedInteger = 2 ** 31 - 1;
|
|
314
305
|
if (this.requestHandlerTimeoutMillis > maxSignedInteger) {
|
|
@@ -507,13 +498,27 @@ export class BasicCrawler {
|
|
|
507
498
|
return { enqueueLinks: enqueueLinksWrapper, addRequests, sendRequest };
|
|
508
499
|
}
|
|
509
500
|
buildFinalContextPipeline() {
|
|
510
|
-
|
|
501
|
+
const subclassPipeline = (this.contextPipelineOptions.contextPipelineBuilder?.() ??
|
|
511
502
|
this.buildContextPipeline());
|
|
503
|
+
// `extendContext` runs *before* the subclass navigation pipeline (which includes the
|
|
504
|
+
// pre/post-navigation hooks). This makes the extension visible to those hooks and to the
|
|
505
|
+
// request handler alike. The trade-off is that `extendContext` cannot access
|
|
506
|
+
// navigation-dependent context members (e.g. `page`, `response`, `$`, `body`), as those
|
|
507
|
+
// don't exist yet at this point in the pipeline.
|
|
508
|
+
// The `extendContext` output (`ContextExtension`) is carried through the subclass pipeline at
|
|
509
|
+
// runtime (the pipeline copies each middleware's returned members onto the shared context), but
|
|
510
|
+
// TypeScript cannot express that `Context` transitively includes `ContextExtension` here. The
|
|
511
|
+
// casts below are sound because `buildFinalContextPipeline` is declared to return the fully
|
|
512
|
+
// resolved `ExtendedContext` (= `Context & ContextExtension`).
|
|
512
513
|
const { extendContext } = this.contextPipelineOptions;
|
|
514
|
+
let contextPipeline;
|
|
513
515
|
if (extendContext !== undefined) {
|
|
514
|
-
contextPipeline =
|
|
515
|
-
action: async (context) => await extendContext(context)
|
|
516
|
-
|
|
516
|
+
contextPipeline = ContextPipeline.create()
|
|
517
|
+
.compose({ action: async (context) => await extendContext(context) })
|
|
518
|
+
.chain(subclassPipeline);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
contextPipeline = subclassPipeline;
|
|
517
522
|
}
|
|
518
523
|
contextPipeline = contextPipeline.compose({
|
|
519
524
|
action: async (context) => {
|
|
@@ -620,17 +625,17 @@ export class BasicCrawler {
|
|
|
620
625
|
// we need to purge the RQ to allow processing the same requests again — this is important so users can
|
|
621
626
|
// pass in failed requests back to the `crawler.run()`, otherwise they would be considered as handled and
|
|
622
627
|
// ignored — as a failed request is still handled.
|
|
623
|
-
// By default (purgeRequestQueue unset
|
|
628
|
+
// By default (`purgeRequestQueue` unset), only the queue we opened ourselves is purged.
|
|
624
629
|
// When `purgeRequestQueue` is explicitly `true`, we also purge a user-supplied manager.
|
|
625
630
|
// When `purgeRequestQueue` is explicitly `false`, nothing is purged.
|
|
626
631
|
const shouldPurge = purgeRequestQueue !== false;
|
|
627
|
-
const managerToPurge = this.
|
|
632
|
+
const managerToPurge = this.ownedRequestQueue.maybeValue ?? (purgeRequestQueue === true ? this.requestManager : undefined);
|
|
628
633
|
if (managerToPurge?.purge && shouldPurge) {
|
|
629
634
|
await managerToPurge.purge();
|
|
630
635
|
}
|
|
631
636
|
this.stats.reset();
|
|
632
637
|
await this.stats.resetStore();
|
|
633
|
-
await this.
|
|
638
|
+
await this.sessionPoolDep.ifOwned((pool) => pool.resetStore());
|
|
634
639
|
}
|
|
635
640
|
this.unexpectedStop = false;
|
|
636
641
|
this.running = true;
|
|
@@ -742,7 +747,7 @@ export class BasicCrawler {
|
|
|
742
747
|
}
|
|
743
748
|
/**
|
|
744
749
|
* Opens the default {@link RequestQueue}, applies the crawler's timeouts to it and records it as the
|
|
745
|
-
* crawler-owned
|
|
750
|
+
* crawler-owned queue (so it gets purged between repeated `run()` calls).
|
|
746
751
|
* @private
|
|
747
752
|
*/
|
|
748
753
|
async openOwnedRequestQueue() {
|
|
@@ -750,8 +755,7 @@ export class BasicCrawler {
|
|
|
750
755
|
// subsequent instances get their own queue via a unique alias so they don't collide.
|
|
751
756
|
const identifier = this.identity.instanceIndex === 0 ? null : { alias: `__default_${this.identity.id}__` };
|
|
752
757
|
const requestQueue = await RequestQueue.open(identifier, { config: serviceLocator.getConfiguration() });
|
|
753
|
-
this.
|
|
754
|
-
return requestQueue;
|
|
758
|
+
return this.ownedRequestQueue.set(requestQueue);
|
|
755
759
|
}
|
|
756
760
|
/**
|
|
757
761
|
* Tells a request manager how long we expect to hold a fetched request, so that one backed by a
|
|
@@ -1350,7 +1354,7 @@ export class BasicCrawler {
|
|
|
1350
1354
|
if (this._closeEvents) {
|
|
1351
1355
|
await serviceLocator.getEventManager().close();
|
|
1352
1356
|
}
|
|
1353
|
-
await this.
|
|
1357
|
+
await this.sessionPoolDep.ifOwned((pool) => pool.teardown());
|
|
1354
1358
|
await this.autoscaledPool?.abort();
|
|
1355
1359
|
}
|
|
1356
1360
|
_getCookieHeaderFromRequest(request) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/basic",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.83",
|
|
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"
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"@apify/datastructures": "^2.0.0",
|
|
43
43
|
"@apify/timeout": "^0.3.2",
|
|
44
44
|
"@apify/utilities": "^2.15.5",
|
|
45
|
-
"@crawlee/core": "4.0.0-beta.
|
|
46
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
47
|
-
"@crawlee/types": "4.0.0-beta.
|
|
48
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
45
|
+
"@crawlee/core": "4.0.0-beta.83",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.83",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.83",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.83",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
50
|
"fs-extra": "^11.3.0",
|
|
51
51
|
"ow": "^2.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"type-fest": "^4.41.0"
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
|
-
"@crawlee/impit-client": "^4.0.0-beta.
|
|
57
|
+
"@crawlee/impit-client": "^4.0.0-beta.83"
|
|
58
58
|
},
|
|
59
59
|
"lerna": {
|
|
60
60
|
"command": {
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "5f25b90c4914d7e097732cf761e440aeba799137"
|
|
67
67
|
}
|