@crawlee/basic 4.0.0-beta.211 → 4.0.0-beta.213
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/autoscaling/autoscaled_pool.d.ts +0 -20
- package/internals/autoscaling/autoscaled_pool.js +11 -12
- package/internals/autoscaling/concurrency_system.d.ts +0 -38
- package/internals/autoscaling/concurrency_system.js +51 -53
- package/internals/autoscaling/memory_load_signal.d.ts +0 -2
- package/internals/autoscaling/memory_load_signal.js +4 -4
- package/internals/autoscaling/system_status.d.ts +0 -10
- package/internals/autoscaling/system_status.js +5 -5
- package/internals/basic-crawler.d.ts +0 -88
- package/internals/basic-crawler.js +73 -68
- package/internals/crawlers/context_pipeline.js +3 -3
- package/internals/crawlers/error_tracker.d.ts +0 -1
- package/internals/crawlers/error_tracker.js +3 -3
- package/internals/crawlers/statistics.d.ts +0 -2
- package/internals/crawlers/statistics.js +11 -11
- package/internals/crawling_request.d.ts +1 -1
- package/internals/crawling_request.js +12 -12
- package/internals/router.d.ts +0 -9
- package/internals/router.js +5 -5
- package/internals/session_pool/session.d.ts +0 -5
- package/internals/session_pool/session.js +3 -3
- package/internals/session_pool/session_pool.d.ts +0 -51
- package/internals/session_pool/session_pool.js +36 -36
- package/internals/sitemap_request_loader.d.ts +0 -39
- package/internals/sitemap_request_loader.js +18 -18
- package/internals/throttling_request_manager.d.ts +0 -1
- package/internals/throttling_request_manager.js +5 -5
- package/package.json +7 -7
|
@@ -441,7 +441,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
441
441
|
get log(): CrawleeLogger;
|
|
442
442
|
protected readonly requestHandler: RequestHandler<ExtendedContext>;
|
|
443
443
|
protected readonly internalTimeoutMillis: number;
|
|
444
|
-
private get handledRequestsCount();
|
|
445
444
|
protected blockedStatusCodes: Set<number>;
|
|
446
445
|
protected readonly additionalHttpErrorStatusCodes: Set<number>;
|
|
447
446
|
protected readonly httpClient: BaseHttpClient;
|
|
@@ -514,22 +513,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
514
513
|
* @returns `true` if the status code is considered an error, `false` otherwise
|
|
515
514
|
*/
|
|
516
515
|
protected isErrorStatusCode(status: number): boolean;
|
|
517
|
-
/**
|
|
518
|
-
* Builds the basic context pipeline that transforms `{ request }` into a full `CrawlingContext`.
|
|
519
|
-
* This handles base context creation, session resolution, and context helpers.
|
|
520
|
-
*/
|
|
521
|
-
private buildBasicContextPipeline;
|
|
522
|
-
private checkRobotsTxt;
|
|
523
|
-
private createBaseContext;
|
|
524
|
-
/**
|
|
525
|
-
* Fetches the next request and rebuilds it as a {@link CrawlingRequest}. A copy rather than the manager's
|
|
526
|
-
* instance: managers key on `uniqueKey`/`id`, and it is this copy that flows back into `reclaimRequest` /
|
|
527
|
-
* `markRequestAsHandled`, so nothing observes the difference.
|
|
528
|
-
*/
|
|
529
|
-
private resolveRequest;
|
|
530
|
-
private resolveSession;
|
|
531
|
-
private createContextHelpers;
|
|
532
|
-
private buildFinalContextPipeline;
|
|
533
516
|
/**
|
|
534
517
|
* Checks if the given error is a proxy error by comparing its message to a list of known proxy error messages.
|
|
535
518
|
* Used for retrying requests that failed due to proxy errors.
|
|
@@ -547,7 +530,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
547
530
|
* their status-reporting backend (e.g. the Apify platform).
|
|
548
531
|
*/
|
|
549
532
|
setStatusMessage(message: string, options?: SetStatusMessageOptions): void;
|
|
550
|
-
private getPeriodicLogger;
|
|
551
533
|
/**
|
|
552
534
|
* Runs the crawler. Returns a promise that resolves once every request has been processed and the crawler's
|
|
553
535
|
* finished-check ({@link BasicCrawlerOptions.taskLoopOptions|`taskLoopOptions.isFinishedFunction`}, or the
|
|
@@ -596,26 +578,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
596
578
|
* crawler's request manager, which is no longer guaranteed to be a {@link RequestQueue}.
|
|
597
579
|
*/
|
|
598
580
|
getRequestQueue(): Promise<IRequestManager>;
|
|
599
|
-
/**
|
|
600
|
-
* Opens the default {@link RequestQueue} — the crawler's own, read from when the caller supplied nothing.
|
|
601
|
-
*/
|
|
602
|
-
private openOwnedRequestQueue;
|
|
603
|
-
/**
|
|
604
|
-
* Tells a request manager how long we expect to hold a fetched request, so that one backed by a
|
|
605
|
-
* locking storage backend keeps it reserved for slightly longer than the request handler timeout
|
|
606
|
-
* (with some padding for overhead), but never for less than a minute. This prevents a long-running
|
|
607
|
-
* request from being handed out a second time while it is still being processed — and it works
|
|
608
|
-
* regardless of whether the manager is a plain {@link RequestQueue} or a `RequestManagerTandem`.
|
|
609
|
-
*/
|
|
610
|
-
private applyRequestManagerTimeouts;
|
|
611
|
-
/**
|
|
612
|
-
* Validates a request source's `userData` against the {@link RouteSchemas|Standard Schema} registered
|
|
613
|
-
* for its label on the crawler's schema-router (if any), throwing a {@link RequestValidationError} on
|
|
614
|
-
* mismatch. A no-op when the user's request handler is not a schema-router, or no schema is registered for
|
|
615
|
-
* the request's label. Applied by the crawler on the add paths it owns — `crawler.addRequests`,
|
|
616
|
-
* `crawler.run`, `context.addRequests` and `context.enqueueLinks`.
|
|
617
|
-
*/
|
|
618
|
-
private validateRequestUserData;
|
|
619
581
|
useState<State extends Dictionary = Dictionary>(defaultValue?: State): Promise<State>;
|
|
620
582
|
/**
|
|
621
583
|
* Adds requests to the queue in batches. By default, it will resolve after the initial batch is added, and continue
|
|
@@ -661,26 +623,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
661
623
|
* browser crawlers override it with their `navigationTimeoutSecs`.
|
|
662
624
|
*/
|
|
663
625
|
protected getNavigationTimeoutMillis(): number;
|
|
664
|
-
/**
|
|
665
|
-
* Races the request against the internal timeout (see {@link raceWithTimeout}), sized to outlast the phases
|
|
666
|
-
* that have their own timeout - the navigation, its hooks, and the request handler - so a legitimately slow
|
|
667
|
-
* request, a per-route override, or a low `CRAWLEE_INTERNAL_TIMEOUT` is not cut short mid-phase. It takes
|
|
668
|
-
* whichever is larger: the configured internal timeout, or this request's combined phase budget.
|
|
669
|
-
*/
|
|
670
|
-
private withRequestTimeout;
|
|
671
|
-
/**
|
|
672
|
-
* The request handler timeout for a request with the given route label. A router route may override the
|
|
673
|
-
* crawler's own `requestHandlerTimeoutSecs`; anything else falls back to `fallbackMillis`.
|
|
674
|
-
*
|
|
675
|
-
* @param label The request's route label, or `undefined` for the default route / no specific request.
|
|
676
|
-
* @param fallbackMillis Timeout to use when no route overrides it.
|
|
677
|
-
*/
|
|
678
|
-
private resolveRequestHandlerTimeoutMillis;
|
|
679
|
-
/**
|
|
680
|
-
* The timeout the router route with the given label asked for, or `undefined` when it did not override one
|
|
681
|
-
* (or the request handler is not a router at all).
|
|
682
|
-
*/
|
|
683
|
-
private getRouteTimeoutMillis;
|
|
684
626
|
protected runRequestHandler(crawlingContext: ExtendedContext): Promise<void>;
|
|
685
627
|
/**
|
|
686
628
|
* Runs `callback` inside a {@link StorageTransaction}, unless transactional storage is disabled.
|
|
@@ -692,7 +634,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
692
634
|
* Handles blocked request
|
|
693
635
|
*/
|
|
694
636
|
protected throwOnBlockedRequest(statusCode: number): void;
|
|
695
|
-
private isAllowedBasedOnRobotsTxtFile;
|
|
696
637
|
/**
|
|
697
638
|
* Records an HTTP 429 against the URL's domain so the request manager can hold the retry back.
|
|
698
639
|
*
|
|
@@ -701,15 +642,8 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
701
642
|
* {@link RequestThrottledError} rather than treating the response as a blocked session.
|
|
702
643
|
*/
|
|
703
644
|
protected recordDomainRateLimit(url: string, retryAfterHeader?: string | null): boolean;
|
|
704
|
-
/** Hands a robots.txt `Crawl-delay` to the request manager, warning if it will not be honoured. */
|
|
705
|
-
private applyCrawlDelay;
|
|
706
645
|
/** @internal */
|
|
707
646
|
protected getRobotsTxtFileForUrl(url: string): Promise<RobotsTxtFile | undefined>;
|
|
708
|
-
private pauseOnMigration;
|
|
709
|
-
/**
|
|
710
|
-
* Fetches the next request to process from the underlying request provider.
|
|
711
|
-
*/
|
|
712
|
-
private fetchNextRequest;
|
|
713
647
|
/** Runs the request handler and marks the request as handled. Failures are left to `handleRequestFailure`. */
|
|
714
648
|
private handleRequest;
|
|
715
649
|
/**
|
|
@@ -717,21 +651,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
717
651
|
* @internal
|
|
718
652
|
*/
|
|
719
653
|
protected addCrawlDepthRequestGenerator(requests: RequestsLike, newRequestDepth: number): AsyncGenerator<Source, void, undefined>;
|
|
720
|
-
/**
|
|
721
|
-
* Run async callback with given timeout and retry. Returns the result of the callback.
|
|
722
|
-
* @ignore
|
|
723
|
-
*/
|
|
724
|
-
private timeoutAndRetry;
|
|
725
|
-
/**
|
|
726
|
-
* Whether the request manager has a request ready for processing. A manager that is only `waiting` also gets a
|
|
727
|
-
* wake-up scheduled, so a paced crawl resumes on its clock rather than on the task loop's polling interval.
|
|
728
|
-
*/
|
|
729
|
-
private isTaskReadyFunction;
|
|
730
|
-
/**
|
|
731
|
-
* Unwraps errors thrown by the context pipeline to get the actual user error.
|
|
732
|
-
* RequestHandlerError, ContextPipelineInitializationError and ContextPipelineCleanupError wrap the actual error.
|
|
733
|
-
*/
|
|
734
|
-
private unwrapError;
|
|
735
654
|
/**
|
|
736
655
|
* Handles errors thrown by user provided requestHandler()
|
|
737
656
|
*
|
|
@@ -745,12 +664,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
745
664
|
* @returns The message to be logged
|
|
746
665
|
*/
|
|
747
666
|
protected getMessageFromError(error: Error, forceStack?: boolean): string;
|
|
748
|
-
/**
|
|
749
|
-
* Whether the session should be spared for this error - either because it was already retired, or because the
|
|
750
|
-
* failure says nothing about the session (a rate limit is a property of the domain).
|
|
751
|
-
*/
|
|
752
|
-
private errorAbsolvesSession;
|
|
753
|
-
private canRequestBeRetried;
|
|
754
667
|
/**
|
|
755
668
|
* Ends the run in progress immediately, without waiting for the requests already in flight.
|
|
756
669
|
*
|
|
@@ -769,7 +682,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
769
682
|
destroy(): Promise<void>;
|
|
770
683
|
[Symbol.asyncDispose](): Promise<void>;
|
|
771
684
|
protected getCookieHeaderFromRequest(request: Request): string;
|
|
772
|
-
private requestMatchesEnqueueStrategy;
|
|
773
685
|
}
|
|
774
686
|
export interface CrawlerAddRequestsOptions extends AddRequestsBatchedOptions, EnqueueUrlsOptions {
|
|
775
687
|
}
|
|
@@ -184,14 +184,14 @@ export class BasicCrawler {
|
|
|
184
184
|
* Context built with this pipeline can be passed into multiple crawler pipelines at once.
|
|
185
185
|
*/
|
|
186
186
|
get #basicPipeline() {
|
|
187
|
-
this.#basicContextPipeline ??= this
|
|
187
|
+
this.#basicContextPipeline ??= this.#buildBasicContextPipeline();
|
|
188
188
|
return this.#basicContextPipeline;
|
|
189
189
|
}
|
|
190
190
|
#contextPipeline;
|
|
191
191
|
/** @internal */
|
|
192
192
|
get contextPipeline() {
|
|
193
193
|
if (this.#contextPipeline === undefined) {
|
|
194
|
-
this.#contextPipeline = this
|
|
194
|
+
this.#contextPipeline = this.#buildFinalContextPipeline();
|
|
195
195
|
}
|
|
196
196
|
return this.#contextPipeline;
|
|
197
197
|
}
|
|
@@ -212,7 +212,7 @@ export class BasicCrawler {
|
|
|
212
212
|
#maxRequestRetries;
|
|
213
213
|
#maxCrawlDepth;
|
|
214
214
|
#maxRequestsPerCrawl;
|
|
215
|
-
get handledRequestsCount() {
|
|
215
|
+
get #handledRequestsCount() {
|
|
216
216
|
return this.statistics.state.requestsSucceeded + this.statistics.state.requestsFailed;
|
|
217
217
|
}
|
|
218
218
|
#statusMessageLoggingInterval;
|
|
@@ -370,12 +370,12 @@ export class BasicCrawler {
|
|
|
370
370
|
throttleBy: 'registrableDomain',
|
|
371
371
|
persistStateKey: `CRAWLEE_THROTTLED_DOMAINS_${this.#identity.id}`,
|
|
372
372
|
// A factory, because the default queue is only opened on first use.
|
|
373
|
-
inner: suppliedManager ?? (() => this
|
|
373
|
+
inner: suppliedManager ?? (() => this.#openOwnedRequestQueue()),
|
|
374
374
|
})
|
|
375
375
|
: suppliedManager;
|
|
376
376
|
if (requestList !== undefined) {
|
|
377
377
|
// The list is read first, while new requests still have somewhere writable to go.
|
|
378
|
-
this.#requestManager = new RequestManagerTandem(requestList, writableManager ?? (() => this
|
|
378
|
+
this.#requestManager = new RequestManagerTandem(requestList, writableManager ?? (() => this.#openOwnedRequestQueue()));
|
|
379
379
|
}
|
|
380
380
|
else if (writableManager !== undefined) {
|
|
381
381
|
// A RequestQueue is itself a request manager.
|
|
@@ -428,7 +428,9 @@ export class BasicCrawler {
|
|
|
428
428
|
this.#sessionPoolDep = OwnedOrInjected.resolve(sessionPool, () => new SessionPool({
|
|
429
429
|
createSessionFunction: async (opts) => new Session({
|
|
430
430
|
...opts?.sessionOptions,
|
|
431
|
-
proxyInfo:
|
|
431
|
+
proxyInfo: this.proxyConfiguration
|
|
432
|
+
? await this.proxyConfiguration.newProxyInfo(opts?.sessionOptions?.proxyInfo)
|
|
433
|
+
: opts?.sessionOptions?.proxyInfo,
|
|
432
434
|
}),
|
|
433
435
|
}));
|
|
434
436
|
this.blockedStatusCodes = new Set(blockedStatusCodesInput ?? BLOCKED_STATUS_CODES);
|
|
@@ -440,7 +442,7 @@ export class BasicCrawler {
|
|
|
440
442
|
}
|
|
441
443
|
this.internalTimeoutMillis = Math.min(this.internalTimeoutMillis, maxSignedInteger);
|
|
442
444
|
this.#maxRequestsPerCrawl = maxRequestsPerCrawl;
|
|
443
|
-
const isMaxPagesExceeded = () => this.#maxRequestsPerCrawl && this.#maxRequestsPerCrawl <= this
|
|
445
|
+
const isMaxPagesExceeded = () => this.#maxRequestsPerCrawl && this.#maxRequestsPerCrawl <= this.#handledRequestsCount;
|
|
444
446
|
// eslint-disable-next-line prefer-const
|
|
445
447
|
let { isFinishedFunction, isTaskReadyFunction } = taskLoopOptions;
|
|
446
448
|
// override even if `isFinishedFunction` provided by user - `keepAlive` has higher priority
|
|
@@ -452,7 +454,7 @@ export class BasicCrawler {
|
|
|
452
454
|
const source = this.requestManager;
|
|
453
455
|
if (!source)
|
|
454
456
|
throw new Error('Request provider is not initialized!');
|
|
455
|
-
const request = await this
|
|
457
|
+
const request = await this.#resolveRequest();
|
|
456
458
|
if (!request) {
|
|
457
459
|
return;
|
|
458
460
|
}
|
|
@@ -469,20 +471,20 @@ export class BasicCrawler {
|
|
|
469
471
|
// Navigation, the navigation hooks and the request handler are timed individually, but the
|
|
470
472
|
// phases between them are not, so a request could still get stuck indefinitely. This is the
|
|
471
473
|
// catch-all for that - see `raceWithTimeout` for why it is a bare timer, not a timeout frame.
|
|
472
|
-
await this
|
|
474
|
+
await this.#withRequestTimeout(crawlingContext, this.#basicPipeline.chain(this.contextPipeline).call(crawlingContext, (ctx) => this.handleRequest(ctx, source, request), (error) => this.#handleRequestFailure(error, crawlingContext, source, request))));
|
|
473
475
|
}
|
|
474
476
|
catch (error) {
|
|
475
477
|
// The internal timeout above fired, or `handleRequestFailure` threw a `SessionError` - both
|
|
476
478
|
// take the normal error flow. Anything else is fatal.
|
|
477
479
|
if (error instanceof SessionError || crawlingContext[timeoutExpiredKey]?.()) {
|
|
478
|
-
const unwrappedError = this
|
|
480
|
+
const unwrappedError = this.#unwrapError(error);
|
|
479
481
|
await this.requestFunctionErrorHandler(unwrappedError, crawlingContext, request, this.requestManager);
|
|
480
|
-
if (!this
|
|
482
|
+
if (!this.#errorAbsolvesSession(unwrappedError)) {
|
|
481
483
|
crawlingContext.session?.markBad();
|
|
482
484
|
}
|
|
483
485
|
return;
|
|
484
486
|
}
|
|
485
|
-
throw this
|
|
487
|
+
throw this.#unwrapError(error);
|
|
486
488
|
}
|
|
487
489
|
},
|
|
488
490
|
isTaskReadyFunction: async () => {
|
|
@@ -496,13 +498,13 @@ export class BasicCrawler {
|
|
|
496
498
|
'Ongoing requests will be allowed to complete.');
|
|
497
499
|
return false;
|
|
498
500
|
}
|
|
499
|
-
return isTaskReadyFunction ? await isTaskReadyFunction() : await this
|
|
501
|
+
return isTaskReadyFunction ? await isTaskReadyFunction() : await this.#isTaskReadyFunction();
|
|
500
502
|
},
|
|
501
503
|
isFinishedFunction: async () => {
|
|
502
504
|
if (isMaxPagesExceeded()) {
|
|
503
505
|
this.log.info(`Earlier, the crawler reached the maxRequestsPerCrawl limit of ${this.#maxRequestsPerCrawl} requests ` +
|
|
504
506
|
'and all requests that were in progress at that time have now finished. ' +
|
|
505
|
-
`In total, the crawler processed ${this
|
|
507
|
+
`In total, the crawler processed ${this.#handledRequestsCount} requests and will shut down.`);
|
|
506
508
|
return true;
|
|
507
509
|
}
|
|
508
510
|
if (this.#run?.stopRequested) {
|
|
@@ -571,15 +573,15 @@ export class BasicCrawler {
|
|
|
571
573
|
* Builds the basic context pipeline that transforms `{ request }` into a full `CrawlingContext`.
|
|
572
574
|
* This handles base context creation, session resolution, and context helpers.
|
|
573
575
|
*/
|
|
574
|
-
buildBasicContextPipeline() {
|
|
576
|
+
#buildBasicContextPipeline() {
|
|
575
577
|
return ContextPipeline.create()
|
|
576
|
-
.compose(this
|
|
577
|
-
.compose(this
|
|
578
|
-
.compose(this
|
|
579
|
-
.compose(this
|
|
578
|
+
.compose(this.#checkRobotsTxt.bind(this))
|
|
579
|
+
.compose(this.#createBaseContext.bind(this))
|
|
580
|
+
.compose(this.#resolveSession.bind(this))
|
|
581
|
+
.compose(this.#createContextHelpers.bind(this));
|
|
580
582
|
}
|
|
581
|
-
async checkRobotsTxt({ request }) {
|
|
582
|
-
if (!(await this
|
|
583
|
+
async #checkRobotsTxt({ request }) {
|
|
584
|
+
if (!(await this.#isAllowedBasedOnRobotsTxtFile(request.url))) {
|
|
583
585
|
this.log.warning(`Skipping request ${request.url} (${request.id}) because it is disallowed based on robots.txt`);
|
|
584
586
|
request.state = RequestState.SKIPPED;
|
|
585
587
|
request.noRetry = true;
|
|
@@ -591,7 +593,7 @@ export class BasicCrawler {
|
|
|
591
593
|
}
|
|
592
594
|
return {};
|
|
593
595
|
}
|
|
594
|
-
createBaseContext(context, onCleanup) {
|
|
596
|
+
#createBaseContext(context, onCleanup) {
|
|
595
597
|
return {
|
|
596
598
|
id: cryptoRandomObjectId(10),
|
|
597
599
|
log: this.log,
|
|
@@ -640,8 +642,8 @@ export class BasicCrawler {
|
|
|
640
642
|
* instance: managers key on `uniqueKey`/`id`, and it is this copy that flows back into `reclaimRequest` /
|
|
641
643
|
* `markRequestAsHandled`, so nothing observes the difference.
|
|
642
644
|
*/
|
|
643
|
-
async resolveRequest() {
|
|
644
|
-
const fetched = await this
|
|
645
|
+
async #resolveRequest() {
|
|
646
|
+
const fetched = await this.#timeoutAndRetry(this.#fetchNextRequest.bind(this), this.internalTimeoutMillis, `Fetching next request timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
|
|
645
647
|
if (!fetched) {
|
|
646
648
|
return null;
|
|
647
649
|
}
|
|
@@ -650,8 +652,8 @@ export class BasicCrawler {
|
|
|
650
652
|
request.loadedUrl = undefined;
|
|
651
653
|
return request;
|
|
652
654
|
}
|
|
653
|
-
async resolveSession({ request }) {
|
|
654
|
-
const session = await this
|
|
655
|
+
async #resolveSession({ request }) {
|
|
656
|
+
const session = await this.#timeoutAndRetry(async () => {
|
|
655
657
|
const existingSession = await this.sessionPool.getSession(request.sessionId);
|
|
656
658
|
if (!existingSession) {
|
|
657
659
|
throw new MissingSessionError(request.sessionId);
|
|
@@ -660,7 +662,7 @@ export class BasicCrawler {
|
|
|
660
662
|
}, this.internalTimeoutMillis, `Fetching session timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
|
|
661
663
|
return { session, proxyInfo: session?.proxyInfo };
|
|
662
664
|
}
|
|
663
|
-
async createContextHelpers({ request, session }) {
|
|
665
|
+
async #createContextHelpers({ request, session }) {
|
|
664
666
|
const addRequests = async (requests, options = {}) => {
|
|
665
667
|
const newCrawlDepth = request.crawlDepth + 1;
|
|
666
668
|
const requestsGenerator = this.addCrawlDepthRequestGenerator(requests, newCrawlDepth);
|
|
@@ -669,7 +671,7 @@ export class BasicCrawler {
|
|
|
669
671
|
const sendRequest = createSendRequest(this.httpClient, request, session);
|
|
670
672
|
return { addRequests, sendRequest };
|
|
671
673
|
}
|
|
672
|
-
buildFinalContextPipeline() {
|
|
674
|
+
#buildFinalContextPipeline() {
|
|
673
675
|
const subclassPipeline = (this.#contextPipelineOptions.contextPipelineBuilder?.() ??
|
|
674
676
|
ContextPipeline.create());
|
|
675
677
|
// `extendContext` runs *before* the subclass navigation pipeline (which includes the
|
|
@@ -694,7 +696,7 @@ export class BasicCrawler {
|
|
|
694
696
|
}
|
|
695
697
|
contextPipeline = contextPipeline.compose(async (context) => {
|
|
696
698
|
const { request } = context;
|
|
697
|
-
if (request && !this
|
|
699
|
+
if (request && !this.#requestMatchesEnqueueStrategy(request)) {
|
|
698
700
|
// eslint-disable-next-line dot-notation
|
|
699
701
|
const message = `Skipping request ${request.id} (starting url: ${request.url} -> loaded url: ${request.loadedUrl}) because it does not match the enqueue strategy (${request['enqueueStrategy']}).`;
|
|
700
702
|
this.log.debug(message);
|
|
@@ -741,7 +743,7 @@ export class BasicCrawler {
|
|
|
741
743
|
level: options.level,
|
|
742
744
|
});
|
|
743
745
|
}
|
|
744
|
-
getPeriodicLogger() {
|
|
746
|
+
#getPeriodicLogger() {
|
|
745
747
|
let previousState = { ...this.statistics.state };
|
|
746
748
|
const getOperationMode = () => {
|
|
747
749
|
const { requestsFailed } = this.statistics.state;
|
|
@@ -846,15 +848,15 @@ export class BasicCrawler {
|
|
|
846
848
|
await this.#concurrencySystem.ifOwned((system) => system.stop());
|
|
847
849
|
throw error;
|
|
848
850
|
}
|
|
849
|
-
const periodicLogger = this
|
|
851
|
+
const periodicLogger = this.#getPeriodicLogger();
|
|
850
852
|
this.setStatusMessage('Starting the crawler.', { level: 'INFO' });
|
|
851
853
|
const sigintHandler = async () => {
|
|
852
854
|
this.log.warning('Pausing... Press CTRL+C again to force exit. To resume, do: CRAWLEE_PURGE_ON_START=0 npm start');
|
|
853
|
-
await this
|
|
855
|
+
await this.#pauseOnMigration();
|
|
854
856
|
await run.abort();
|
|
855
857
|
};
|
|
856
858
|
// Attach a listener to handle migration and aborting events gracefully.
|
|
857
|
-
const boundPauseOnMigration = this
|
|
859
|
+
const boundPauseOnMigration = this.#pauseOnMigration.bind(this);
|
|
858
860
|
process.once('SIGINT', sigintHandler);
|
|
859
861
|
const eventManager = serviceLocator.getEventManager();
|
|
860
862
|
eventManager.on(EventType.MIGRATING, boundPauseOnMigration);
|
|
@@ -970,14 +972,14 @@ export class BasicCrawler {
|
|
|
970
972
|
*/
|
|
971
973
|
async getRequestManager() {
|
|
972
974
|
if (!this.#requestManager) {
|
|
973
|
-
this.#requestManager = await this
|
|
975
|
+
this.#requestManager = await this.#openOwnedRequestQueue();
|
|
974
976
|
}
|
|
975
977
|
// Apply the processing-time hint here (an async lifecycle point) rather than in the constructor,
|
|
976
978
|
// now that `setExpectedRequestProcessingTimeSecs` is async. The hint is raise-only and idempotent,
|
|
977
979
|
// but guard so we do not re-issue it on every call.
|
|
978
980
|
if (!this.#requestManagerTimeoutsApplied) {
|
|
979
981
|
this.#requestManagerTimeoutsApplied = true;
|
|
980
|
-
await this
|
|
982
|
+
await this.#applyRequestManagerTimeouts(this.#requestManager);
|
|
981
983
|
}
|
|
982
984
|
return this.#requestManager;
|
|
983
985
|
}
|
|
@@ -991,7 +993,7 @@ export class BasicCrawler {
|
|
|
991
993
|
/**
|
|
992
994
|
* Opens the default {@link RequestQueue} — the crawler's own, read from when the caller supplied nothing.
|
|
993
995
|
*/
|
|
994
|
-
async openOwnedRequestQueue() {
|
|
996
|
+
async #openOwnedRequestQueue() {
|
|
995
997
|
// The first crawler instance uses the default queue (null identifier);
|
|
996
998
|
// subsequent instances get their own queue via a unique alias so they don't collide.
|
|
997
999
|
const identifier = this.#identity.instanceIndex === 0 ? null : { alias: `__default_${this.#identity.id}__` };
|
|
@@ -1004,7 +1006,7 @@ export class BasicCrawler {
|
|
|
1004
1006
|
* request from being handed out a second time while it is still being processed — and it works
|
|
1005
1007
|
* regardless of whether the manager is a plain {@link RequestQueue} or a `RequestManagerTandem`.
|
|
1006
1008
|
*/
|
|
1007
|
-
async applyRequestManagerTimeouts(requestManager) {
|
|
1009
|
+
async #applyRequestManagerTimeouts(requestManager) {
|
|
1008
1010
|
// A router route may hold a request for longer than the crawler's own timeout, and we cannot know
|
|
1009
1011
|
// which routes a run will hit, so reserve for the longest one any route asked for. The hint is
|
|
1010
1012
|
// raise-only, so erring high here is safe.
|
|
@@ -1019,7 +1021,7 @@ export class BasicCrawler {
|
|
|
1019
1021
|
* the request's label. Applied by the crawler on the add paths it owns — `crawler.addRequests`,
|
|
1020
1022
|
* `crawler.run`, `context.addRequests` and `context.enqueueLinks`.
|
|
1021
1023
|
*/
|
|
1022
|
-
async validateRequestUserData(source) {
|
|
1024
|
+
async #validateRequestUserData(source) {
|
|
1023
1025
|
if (typeof source === 'string') {
|
|
1024
1026
|
return;
|
|
1025
1027
|
}
|
|
@@ -1062,7 +1064,9 @@ export class BasicCrawler {
|
|
|
1062
1064
|
if (this.#maxRequestsPerCrawl === undefined) {
|
|
1063
1065
|
return explicitLimit;
|
|
1064
1066
|
}
|
|
1065
|
-
const limit = Math.max(0, this.#maxRequestsPerCrawl -
|
|
1067
|
+
const limit = Math.max(0, this.#maxRequestsPerCrawl -
|
|
1068
|
+
this.#handledRequestsCount -
|
|
1069
|
+
(await this.#getPendingRequestCountApproximation()));
|
|
1066
1070
|
return Math.min(limit, explicitLimit ?? Infinity);
|
|
1067
1071
|
}
|
|
1068
1072
|
async #handleSkippedRequest(options) {
|
|
@@ -1109,7 +1113,7 @@ export class BasicCrawler {
|
|
|
1109
1113
|
// its own per-item validation below, and validating an absent label/userData here would spuriously
|
|
1110
1114
|
// check them against a registered default-route schema.
|
|
1111
1115
|
if (options.label !== undefined || options.userData !== undefined) {
|
|
1112
|
-
await this
|
|
1116
|
+
await this.#validateRequestUserData({ label: options.label, userData: options.userData });
|
|
1113
1117
|
}
|
|
1114
1118
|
const requestLimit = await this.#calculateEnqueuedRequestLimit(options.limit);
|
|
1115
1119
|
const strategy = options.strategy ?? EnqueueStrategy.All;
|
|
@@ -1118,9 +1122,9 @@ export class BasicCrawler {
|
|
|
1118
1122
|
// The strategy always applies, even when `include` patterns are provided - the two are AND-ed together
|
|
1119
1123
|
// (a URL must match an `include` pattern *and* satisfy the strategy). This mirrors crawlee-python.
|
|
1120
1124
|
const enqueueStrategyPatterns = options.baseUrl ? buildEnqueueStrategyPatterns(options.baseUrl, strategy) : [];
|
|
1121
|
-
const isAllowedBasedOnRobotsTxtFile = this
|
|
1125
|
+
const isAllowedBasedOnRobotsTxtFile = this.#isAllowedBasedOnRobotsTxtFile.bind(this);
|
|
1122
1126
|
const maxCrawlDepth = this.#maxCrawlDepth;
|
|
1123
|
-
const validateRequestUserData = this
|
|
1127
|
+
const validateRequestUserData = this.#validateRequestUserData.bind(this);
|
|
1124
1128
|
const allSkipped = [];
|
|
1125
1129
|
// A skipped source (which can carry arbitrary userData) is only retained if something reads it -
|
|
1126
1130
|
// otherwise the URL alone is enough to build the callback argument and to log with.
|
|
@@ -1292,7 +1296,7 @@ export class BasicCrawler {
|
|
|
1292
1296
|
// floored per request so it will not actually cut them short, but the configured value is then effectively
|
|
1293
1297
|
// ignored, which is worth flagging. Checked here (not in the constructor) because a subclass sets its
|
|
1294
1298
|
// navigation timeout only after `super()`.
|
|
1295
|
-
const phasesMillis = this.getNavigationTimeoutMillis() + this
|
|
1299
|
+
const phasesMillis = this.getNavigationTimeoutMillis() + this.#resolveRequestHandlerTimeoutMillis(undefined);
|
|
1296
1300
|
if (this.internalTimeoutMillis < phasesMillis) {
|
|
1297
1301
|
this.log.warning(`CRAWLEE_INTERNAL_TIMEOUT (${this.internalTimeoutMillis / 1000}s) is shorter than the navigation ` +
|
|
1298
1302
|
`and request handler timeouts combined (${phasesMillis / 1000}s); it will be raised per request ` +
|
|
@@ -1314,9 +1318,9 @@ export class BasicCrawler {
|
|
|
1314
1318
|
* request, a per-route override, or a low `CRAWLEE_INTERNAL_TIMEOUT` is not cut short mid-phase. It takes
|
|
1315
1319
|
* whichever is larger: the configured internal timeout, or this request's combined phase budget.
|
|
1316
1320
|
*/
|
|
1317
|
-
async withRequestTimeout(crawlingContext, work) {
|
|
1321
|
+
async #withRequestTimeout(crawlingContext, work) {
|
|
1318
1322
|
const { request } = crawlingContext;
|
|
1319
|
-
const phasesMillis = this.getNavigationTimeoutMillis() + this
|
|
1323
|
+
const phasesMillis = this.getNavigationTimeoutMillis() + this.#resolveRequestHandlerTimeoutMillis(request.label);
|
|
1320
1324
|
const timeoutMillis = Math.max(this.internalTimeoutMillis, phasesMillis);
|
|
1321
1325
|
await raceWithTimeout(crawlingContext, work, { timeoutMillis, requestId: request.id });
|
|
1322
1326
|
}
|
|
@@ -1327,14 +1331,14 @@ export class BasicCrawler {
|
|
|
1327
1331
|
* @param label The request's route label, or `undefined` for the default route / no specific request.
|
|
1328
1332
|
* @param fallbackMillis Timeout to use when no route overrides it.
|
|
1329
1333
|
*/
|
|
1330
|
-
resolveRequestHandlerTimeoutMillis(label, fallbackMillis = this.#requestHandlerTimeoutMillis) {
|
|
1331
|
-
return this
|
|
1334
|
+
#resolveRequestHandlerTimeoutMillis(label, fallbackMillis = this.#requestHandlerTimeoutMillis) {
|
|
1335
|
+
return this.#getRouteTimeoutMillis(label) ?? fallbackMillis;
|
|
1332
1336
|
}
|
|
1333
1337
|
/**
|
|
1334
1338
|
* The timeout the router route with the given label asked for, or `undefined` when it did not override one
|
|
1335
1339
|
* (or the request handler is not a router at all).
|
|
1336
1340
|
*/
|
|
1337
|
-
getRouteTimeoutMillis(label) {
|
|
1341
|
+
#getRouteTimeoutMillis(label) {
|
|
1338
1342
|
const getTimeoutSecs = this.requestHandler.getTimeoutSecs;
|
|
1339
1343
|
if (typeof getTimeoutSecs !== 'function') {
|
|
1340
1344
|
return undefined;
|
|
@@ -1343,7 +1347,7 @@ export class BasicCrawler {
|
|
|
1343
1347
|
return timeoutSecs === undefined ? undefined : timeoutSecs * 1000;
|
|
1344
1348
|
}
|
|
1345
1349
|
async runRequestHandler(crawlingContext) {
|
|
1346
|
-
const timeoutMillis = this
|
|
1350
|
+
const timeoutMillis = this.#resolveRequestHandlerTimeoutMillis(crawlingContext.request.label);
|
|
1347
1351
|
await addTimeoutToPromise(async () => this.requestHandler(crawlingContext), timeoutMillis, `requestHandler timed out after ${timeoutMillis / 1000} seconds (${crawlingContext.request.id}).`);
|
|
1348
1352
|
}
|
|
1349
1353
|
/**
|
|
@@ -1351,6 +1355,7 @@ export class BasicCrawler {
|
|
|
1351
1355
|
* Deliberately does **not** commit on return - the pipeline hands failures to `handleRequestFailure`, so a
|
|
1352
1356
|
* normal return says nothing about success. `handleRequest` commits on success.
|
|
1353
1357
|
*/
|
|
1358
|
+
// oxlint-disable-next-line crawlee/prefer-private-fields -- accessed by tests
|
|
1354
1359
|
async runInStorageTransaction(callback) {
|
|
1355
1360
|
if (!this.#transactionalStorageEnabled) {
|
|
1356
1361
|
return callback();
|
|
@@ -1390,7 +1395,7 @@ export class BasicCrawler {
|
|
|
1390
1395
|
throw new SessionError(`Request blocked - received ${statusCode} status code.`);
|
|
1391
1396
|
}
|
|
1392
1397
|
}
|
|
1393
|
-
async isAllowedBasedOnRobotsTxtFile(url) {
|
|
1398
|
+
async #isAllowedBasedOnRobotsTxtFile(url) {
|
|
1394
1399
|
if (!this.#respectRobotsTxtFile) {
|
|
1395
1400
|
return true;
|
|
1396
1401
|
}
|
|
@@ -1399,7 +1404,7 @@ export class BasicCrawler {
|
|
|
1399
1404
|
if (robotsTxtFile) {
|
|
1400
1405
|
const crawlDelay = robotsTxtFile.getCrawlDelay(userAgent);
|
|
1401
1406
|
if (crawlDelay !== undefined) {
|
|
1402
|
-
this
|
|
1407
|
+
this.#applyCrawlDelay(url, crawlDelay);
|
|
1403
1408
|
}
|
|
1404
1409
|
}
|
|
1405
1410
|
return !robotsTxtFile || robotsTxtFile.isAllowed(url, userAgent);
|
|
@@ -1427,7 +1432,7 @@ export class BasicCrawler {
|
|
|
1427
1432
|
return false;
|
|
1428
1433
|
}
|
|
1429
1434
|
/** Hands a robots.txt `Crawl-delay` to the request manager, warning if it will not be honoured. */
|
|
1430
|
-
applyCrawlDelay(url, delaySeconds) {
|
|
1435
|
+
#applyCrawlDelay(url, delaySeconds) {
|
|
1431
1436
|
// robots.txt is per-origin; `hostname` is the closest pacing scope and errs wide (http and https to one
|
|
1432
1437
|
// host share a clock).
|
|
1433
1438
|
if (this.requestManager?.recordPacingSignal({
|
|
@@ -1464,7 +1469,7 @@ export class BasicCrawler {
|
|
|
1464
1469
|
return undefined;
|
|
1465
1470
|
}
|
|
1466
1471
|
}
|
|
1467
|
-
async pauseOnMigration() {
|
|
1472
|
+
async #pauseOnMigration() {
|
|
1468
1473
|
const run = this.#dispatchingRun;
|
|
1469
1474
|
if (run) {
|
|
1470
1475
|
await run.pause(SAFE_MIGRATION_WAIT_MILLIS).catch((err) => {
|
|
@@ -1484,7 +1489,7 @@ export class BasicCrawler {
|
|
|
1484
1489
|
/**
|
|
1485
1490
|
* Fetches the next request to process from the underlying request provider.
|
|
1486
1491
|
*/
|
|
1487
|
-
async fetchNextRequest() {
|
|
1492
|
+
async #fetchNextRequest() {
|
|
1488
1493
|
if (this.requestManager === undefined) {
|
|
1489
1494
|
throw new Error(`fetchNextRequest called on an uninitialized crawler`);
|
|
1490
1495
|
}
|
|
@@ -1505,7 +1510,7 @@ export class BasicCrawler {
|
|
|
1505
1510
|
// it is retried. This also closes the transaction, so everything below passes through.
|
|
1506
1511
|
// Opened by `runInStorageTransaction`; absent when disabled or when the subclass opens its own.
|
|
1507
1512
|
await currentStorageTransaction()?.commit();
|
|
1508
|
-
await this
|
|
1513
|
+
await this.#timeoutAndRetry(async () => requestSource.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${request.url} (${request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
|
|
1509
1514
|
this.statistics.recordRequestSuccess(request.id || request.uniqueKey, request.retryCount);
|
|
1510
1515
|
// reclaim session if request finishes successfully
|
|
1511
1516
|
request.state = RequestState.DONE;
|
|
@@ -1522,7 +1527,7 @@ export class BasicCrawler {
|
|
|
1522
1527
|
currentStorageTransaction()?.rollback();
|
|
1523
1528
|
const skipped = rawError instanceof ContextPipelineInitializationError &&
|
|
1524
1529
|
rawError.cause instanceof ContextPipelineInterruptedError;
|
|
1525
|
-
const err = this
|
|
1530
|
+
const err = this.#unwrapError(rawError);
|
|
1526
1531
|
const context = crawlingContext;
|
|
1527
1532
|
const retryCountBefore = request.retryCount;
|
|
1528
1533
|
const resumeRequestTimeout = crawlingContext[suspendTimeoutKey]?.();
|
|
@@ -1531,7 +1536,7 @@ export class BasicCrawler {
|
|
|
1531
1536
|
// (e.g., doesn't match enqueue strategy after redirect). Just return gracefully.
|
|
1532
1537
|
if (skipped) {
|
|
1533
1538
|
this.statistics.discardRequestRecord(request.id || request.uniqueKey);
|
|
1534
|
-
await this
|
|
1539
|
+
await this.#timeoutAndRetry(async () => requestSource.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${request.url} (${request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
|
|
1535
1540
|
return;
|
|
1536
1541
|
}
|
|
1537
1542
|
request.state = RequestState.ERROR_HANDLER;
|
|
@@ -1539,7 +1544,7 @@ export class BasicCrawler {
|
|
|
1539
1544
|
request.state = RequestState.DONE;
|
|
1540
1545
|
}
|
|
1541
1546
|
catch (secondaryError) {
|
|
1542
|
-
const unwrappedSecondaryError = this
|
|
1547
|
+
const unwrappedSecondaryError = this.#unwrapError(secondaryError);
|
|
1543
1548
|
const timedOut = unwrappedSecondaryError instanceof TimeoutError;
|
|
1544
1549
|
request.state = RequestState.ERROR;
|
|
1545
1550
|
if (timedOut) {
|
|
@@ -1575,7 +1580,7 @@ export class BasicCrawler {
|
|
|
1575
1580
|
}
|
|
1576
1581
|
// decrease the session score if the request fails (but the error handler did not throw);
|
|
1577
1582
|
// skip when the error is a SessionError, which already retired the session
|
|
1578
|
-
if (!skipped && !this
|
|
1583
|
+
if (!skipped && !this.#errorAbsolvesSession(err)) {
|
|
1579
1584
|
context.session?.markBad();
|
|
1580
1585
|
}
|
|
1581
1586
|
}
|
|
@@ -1603,7 +1608,7 @@ export class BasicCrawler {
|
|
|
1603
1608
|
* Run async callback with given timeout and retry. Returns the result of the callback.
|
|
1604
1609
|
* @ignore
|
|
1605
1610
|
*/
|
|
1606
|
-
async timeoutAndRetry(handler, timeout, error, maxRetries = 3, retried = 1) {
|
|
1611
|
+
async #timeoutAndRetry(handler, timeout, error, maxRetries = 3, retried = 1) {
|
|
1607
1612
|
try {
|
|
1608
1613
|
return await addTimeoutToPromise(handler, timeout, error);
|
|
1609
1614
|
}
|
|
@@ -1611,7 +1616,7 @@ export class BasicCrawler {
|
|
|
1611
1616
|
if (retried <= maxRetries) {
|
|
1612
1617
|
// we retry on any error, not just timeout
|
|
1613
1618
|
this.log.warning(`${e.message} (retrying ${retried}/${maxRetries})`);
|
|
1614
|
-
return this
|
|
1619
|
+
return this.#timeoutAndRetry(handler, timeout, error, maxRetries, retried + 1);
|
|
1615
1620
|
}
|
|
1616
1621
|
throw e;
|
|
1617
1622
|
}
|
|
@@ -1620,7 +1625,7 @@ export class BasicCrawler {
|
|
|
1620
1625
|
* Whether the request manager has a request ready for processing. A manager that is only `waiting` also gets a
|
|
1621
1626
|
* wake-up scheduled, so a paced crawl resumes on its clock rather than on the task loop's polling interval.
|
|
1622
1627
|
*/
|
|
1623
|
-
async isTaskReadyFunction() {
|
|
1628
|
+
async #isTaskReadyFunction() {
|
|
1624
1629
|
if (this.requestManager === undefined) {
|
|
1625
1630
|
return false;
|
|
1626
1631
|
}
|
|
@@ -1634,11 +1639,11 @@ export class BasicCrawler {
|
|
|
1634
1639
|
* Unwraps errors thrown by the context pipeline to get the actual user error.
|
|
1635
1640
|
* RequestHandlerError, ContextPipelineInitializationError and ContextPipelineCleanupError wrap the actual error.
|
|
1636
1641
|
*/
|
|
1637
|
-
unwrapError(error) {
|
|
1642
|
+
#unwrapError(error) {
|
|
1638
1643
|
if (error instanceof RequestHandlerError ||
|
|
1639
1644
|
error instanceof ContextPipelineInitializationError ||
|
|
1640
1645
|
error instanceof ContextPipelineCleanupError) {
|
|
1641
|
-
return this
|
|
1646
|
+
return this.#unwrapError(error.cause);
|
|
1642
1647
|
}
|
|
1643
1648
|
return error;
|
|
1644
1649
|
}
|
|
@@ -1663,7 +1668,7 @@ export class BasicCrawler {
|
|
|
1663
1668
|
if (error instanceof CriticalError) {
|
|
1664
1669
|
throw error;
|
|
1665
1670
|
}
|
|
1666
|
-
const shouldRetryRequest = this
|
|
1671
|
+
const shouldRetryRequest = this.#canRequestBeRetried(request, error);
|
|
1667
1672
|
if (shouldRetryRequest) {
|
|
1668
1673
|
await this.statistics.errorTrackerRetry.addAsync(error, crawlingContext);
|
|
1669
1674
|
await this.#errorHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
|
|
@@ -1741,10 +1746,10 @@ export class BasicCrawler {
|
|
|
1741
1746
|
* Whether the session should be spared for this error - either because it was already retired, or because the
|
|
1742
1747
|
* failure says nothing about the session (a rate limit is a property of the domain).
|
|
1743
1748
|
*/
|
|
1744
|
-
errorAbsolvesSession(error) {
|
|
1749
|
+
#errorAbsolvesSession(error) {
|
|
1745
1750
|
return error instanceof SessionError || error instanceof RequestThrottledError;
|
|
1746
1751
|
}
|
|
1747
|
-
canRequestBeRetried(request, error) {
|
|
1752
|
+
#canRequestBeRetried(request, error) {
|
|
1748
1753
|
// Request should never be retried, or the error encountered makes it not able to be retried.
|
|
1749
1754
|
if (request.noRetry || error instanceof NonRetryableError) {
|
|
1750
1755
|
return false;
|
|
@@ -1802,7 +1807,7 @@ export class BasicCrawler {
|
|
|
1802
1807
|
}
|
|
1803
1808
|
return request.headers?.Cookie || request.headers?.cookie || '';
|
|
1804
1809
|
}
|
|
1805
|
-
requestMatchesEnqueueStrategy(request) {
|
|
1810
|
+
#requestMatchesEnqueueStrategy(request) {
|
|
1806
1811
|
// If `skipNavigation` was used, just return `true`
|
|
1807
1812
|
try {
|
|
1808
1813
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|