@crawlee/basic 4.0.0-beta.105 → 4.0.0-beta.107
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 +4 -58
- package/internals/basic-crawler.js +112 -109
- package/package.json +7 -7
|
@@ -433,13 +433,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
433
433
|
* collide.
|
|
434
434
|
*/
|
|
435
435
|
private static instanceCount;
|
|
436
|
-
/**
|
|
437
|
-
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
438
|
-
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
439
|
-
*/
|
|
440
|
-
private static useStateAnonymousIndices;
|
|
441
|
-
/** Backs the {@link BasicCrawler.stats|`stats`} getter. */
|
|
442
|
-
private statsDep;
|
|
443
436
|
/**
|
|
444
437
|
* The statistics instance collecting the crawler's run statistics - either the injected `statistics` option or a
|
|
445
438
|
* crawler-built default. Typed as {@link IStatistics} so custom implementations can be plugged in.
|
|
@@ -451,33 +444,11 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
451
444
|
* via {@link BasicCrawler.getRequestManager|`getRequestManager()`}.
|
|
452
445
|
*/
|
|
453
446
|
protected requestManager?: IRequestManager;
|
|
454
|
-
/** Backs the {@link BasicCrawler.sessionPool|`sessionPool`} getter. */
|
|
455
|
-
private sessionPoolDep;
|
|
456
447
|
/**
|
|
457
448
|
* A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
|
|
458
449
|
* {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
|
|
459
450
|
*/
|
|
460
451
|
get sessionPool(): ISessionPool;
|
|
461
|
-
/**
|
|
462
|
-
* Tracks **only** the queue the crawler opens for itself — not the {@link RequestManagerTandem} that may wrap it
|
|
463
|
-
* around a user-supplied `requestList` — so the owned-only purge between repeated `run()` calls never reaches
|
|
464
|
-
* through to a borrowed loader. Filled lazily in {@link BasicCrawler.openOwnedRequestQueue|`openOwnedRequestQueue()`}.
|
|
465
|
-
*/
|
|
466
|
-
private ownedRequestQueue;
|
|
467
|
-
/**
|
|
468
|
-
* Whether the request-processing-time hint has already been forwarded to the request manager. The hint
|
|
469
|
-
* derives only from `requestHandlerTimeoutMillis` (constant for the crawler's lifetime) and is raise-only,
|
|
470
|
-
* so it only needs to be applied once, at the first async access of the manager.
|
|
471
|
-
*/
|
|
472
|
-
private requestManagerTimeoutsApplied;
|
|
473
|
-
/**
|
|
474
|
-
* Resolves the governor for one run: either the injected
|
|
475
|
-
* {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} (borrowed) or a freshly built default with
|
|
476
|
-
* the concurrency shortcuts folded in (owned, so the crawler starts and stops it).
|
|
477
|
-
*/
|
|
478
|
-
private readonly resolveConcurrencySystem;
|
|
479
|
-
/** As resolved by `_init()`. Absent until the first run, so a `teardown()` before it is a no-op. */
|
|
480
|
-
private concurrencySystemDep?;
|
|
481
452
|
/**
|
|
482
453
|
* The concurrency governor this run is booking its requests against — either the
|
|
483
454
|
* {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} that was injected, or the default the
|
|
@@ -491,14 +462,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
491
462
|
* `minConcurrency`/`maxConcurrency`/`desiredConcurrency` on your own reference.
|
|
492
463
|
*/
|
|
493
464
|
get concurrencySystem(): IConcurrencySystem | undefined;
|
|
494
|
-
/**
|
|
495
|
-
* The task loop that dispatches this run's requests. Private on purpose — it is a bare parallel task runner with
|
|
496
|
-
* no configuration left of its own (see {@link ConcurrencySystem}), and everything a caller legitimately did
|
|
497
|
-
* with it now has a crawler-level counterpart: {@link BasicCrawler.pause|`pause()`},
|
|
498
|
-
* {@link BasicCrawler.resume|`resume()`}, {@link BasicCrawler.teardown|`teardown()`} and
|
|
499
|
-
* {@link BasicCrawler.concurrencySystem|`concurrencySystem`}.
|
|
500
|
-
*/
|
|
501
|
-
private autoscaledPool?;
|
|
502
465
|
/**
|
|
503
466
|
* A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
|
|
504
467
|
* Only available if used by the crawler.
|
|
@@ -509,7 +472,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
509
472
|
* See {@link Router.addHandler|`router.addHandler()`} and {@link Router.addDefaultHandler|`router.addDefaultHandler()`}.
|
|
510
473
|
*/
|
|
511
474
|
readonly router: RouterHandler<Context, Routes>;
|
|
512
|
-
private _basicContextPipeline?;
|
|
513
475
|
/**
|
|
514
476
|
* The basic part of the context pipeline. Unlike the subclass pipeline, this
|
|
515
477
|
* part has no major side effects (e.g. launching a browser). It also makes typing more explicit, as subclass
|
|
@@ -521,11 +483,9 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
521
483
|
get basicContextPipeline(): ContextPipeline<{
|
|
522
484
|
request: Request;
|
|
523
485
|
}, CrawlingContext>;
|
|
524
|
-
private _contextPipeline?;
|
|
525
486
|
get contextPipeline(): ContextPipeline<CrawlingContext, ExtendedContext>;
|
|
526
487
|
running: boolean;
|
|
527
488
|
hasFinishedBefore: boolean;
|
|
528
|
-
private unexpectedStop;
|
|
529
489
|
get log(): CrawleeLogger;
|
|
530
490
|
protected readonly requestHandler: RequestHandler<ExtendedContext>;
|
|
531
491
|
protected readonly errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
|
|
@@ -534,15 +494,10 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
534
494
|
protected readonly internalTimeoutMillis: number;
|
|
535
495
|
protected readonly maxRequestRetries: number;
|
|
536
496
|
protected readonly maxCrawlDepth?: number;
|
|
537
|
-
private sameDomainDelayMillis;
|
|
538
|
-
private domainAccessedTime;
|
|
539
497
|
protected readonly maxRequestsPerCrawl?: number;
|
|
540
498
|
private get handledRequestsCount();
|
|
541
|
-
private statusMessageLoggingInterval;
|
|
542
|
-
private statusMessageCallback?;
|
|
543
499
|
protected blockedStatusCodes: Set<number>;
|
|
544
500
|
protected readonly additionalHttpErrorStatusCodes: Set<number>;
|
|
545
|
-
private ignoreHttpErrorStatusCodes;
|
|
546
501
|
/**
|
|
547
502
|
* The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
|
|
548
503
|
* user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
|
|
@@ -552,17 +507,8 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
552
507
|
private taskLoopOptions;
|
|
553
508
|
protected readonly httpClient: BaseHttpClient;
|
|
554
509
|
protected readonly retryOnBlocked: boolean;
|
|
555
|
-
private respectRobotsTxtFile;
|
|
556
|
-
/** Whether `runInStorageTransaction()` opens a transaction at all. */
|
|
557
|
-
private readonly transactionalStorageEnabled;
|
|
558
|
-
/** The resolved per-storage-type write policy overrides forwarded to each request's transaction. */
|
|
559
|
-
private readonly storageWritePolicy;
|
|
560
510
|
protected readonly onSkippedRequest?: SkippedRequestCallback;
|
|
561
|
-
private _closeEvents?;
|
|
562
|
-
private loggedPerRun;
|
|
563
|
-
private readonly robotsTxtFileCache;
|
|
564
511
|
protected readonly identity: CrawlerIdentity;
|
|
565
|
-
private readonly contextPipelineOptions;
|
|
566
512
|
protected static optionsShape: {
|
|
567
513
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
568
514
|
contextPipelineBuilder: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
@@ -793,7 +739,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
793
739
|
/**
|
|
794
740
|
* Initializes the crawler.
|
|
795
741
|
*/
|
|
796
|
-
protected
|
|
742
|
+
protected init(): Promise<void>;
|
|
797
743
|
/**
|
|
798
744
|
* The navigation timeout (pre-navigation hooks, navigation, and post-navigation hooks) in milliseconds, used
|
|
799
745
|
* to size the internal request timeout. `BasicCrawler` has no navigation phase, so this is 0; the HTTP and
|
|
@@ -830,7 +776,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
830
776
|
/**
|
|
831
777
|
* Handles blocked request
|
|
832
778
|
*/
|
|
833
|
-
protected
|
|
779
|
+
protected throwOnBlockedRequest(statusCode: number): void;
|
|
834
780
|
private isAllowedBasedOnRobotsTxtFile;
|
|
835
781
|
protected getRobotsTxtFileForUrl(url: string): Promise<RobotsTxtFile | undefined>;
|
|
836
782
|
private pauseOnMigration;
|
|
@@ -889,7 +835,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
889
835
|
* @param error The error received
|
|
890
836
|
* @returns The message to be logged
|
|
891
837
|
*/
|
|
892
|
-
protected
|
|
838
|
+
protected getMessageFromError(error: Error, forceStack?: boolean): string | TimeoutError | undefined;
|
|
893
839
|
private canRequestBeRetried;
|
|
894
840
|
/**
|
|
895
841
|
* Stops the crawler immediately.
|
|
@@ -899,7 +845,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
899
845
|
* To stop the crawler gracefully (waiting for all running requests to finish), use {@link BasicCrawler.stop|`crawler.stop()`} instead.
|
|
900
846
|
*/
|
|
901
847
|
teardown(): Promise<void>;
|
|
902
|
-
protected
|
|
848
|
+
protected getCookieHeaderFromRequest(request: Request): string;
|
|
903
849
|
private requestMatchesEnqueueStrategy;
|
|
904
850
|
}
|
|
905
851
|
export interface CreateContextOptions {
|
|
@@ -12,9 +12,9 @@ import { cryptoRandomObjectId } from '@apify/utilities';
|
|
|
12
12
|
import { extendTimeoutKey, navigationDeadlineKey, raceWithTimeout, timeoutExpiredKey, } from './request-timeout.js';
|
|
13
13
|
import { createSendRequest } from './send-request.js';
|
|
14
14
|
class LazyDefaultHttpClient {
|
|
15
|
-
|
|
15
|
+
#delegatePromise;
|
|
16
16
|
constructor(options) {
|
|
17
|
-
this
|
|
17
|
+
this.#delegatePromise = import('@crawlee/impit-client')
|
|
18
18
|
.then(({ ImpitHttpClient }) => new ImpitHttpClient(options))
|
|
19
19
|
.catch(() => {
|
|
20
20
|
(options?.logger ?? log).warning('Optional dependency @crawlee/impit-client is not installed. ' +
|
|
@@ -23,7 +23,7 @@ class LazyDefaultHttpClient {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
async sendRequest(...args) {
|
|
26
|
-
return (await this
|
|
26
|
+
return (await this.#delegatePromise).sendRequest(...args);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
@@ -46,20 +46,21 @@ export class BasicCrawler {
|
|
|
46
46
|
* request queue; subsequent ones get their own queue via a unique alias so they don't
|
|
47
47
|
* collide.
|
|
48
48
|
*/
|
|
49
|
+
// kept as TS-private: tests reset the counter at runtime
|
|
49
50
|
static instanceCount = 0;
|
|
50
51
|
/**
|
|
51
52
|
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
52
53
|
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
53
54
|
*/
|
|
54
|
-
static useStateAnonymousIndices = new Set();
|
|
55
|
+
static #useStateAnonymousIndices = new Set();
|
|
55
56
|
/** Backs the {@link BasicCrawler.stats|`stats`} getter. */
|
|
56
|
-
statsDep;
|
|
57
|
+
#statsDep;
|
|
57
58
|
/**
|
|
58
59
|
* The statistics instance collecting the crawler's run statistics - either the injected `statistics` option or a
|
|
59
60
|
* crawler-built default. Typed as {@link IStatistics} so custom implementations can be plugged in.
|
|
60
61
|
*/
|
|
61
62
|
get stats() {
|
|
62
|
-
return this
|
|
63
|
+
return this.#statsDep.value;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
66
|
* The main request-handling component of the crawler. It manages the requests that the crawler processes,
|
|
@@ -68,34 +69,34 @@ export class BasicCrawler {
|
|
|
68
69
|
*/
|
|
69
70
|
requestManager;
|
|
70
71
|
/** Backs the {@link BasicCrawler.sessionPool|`sessionPool`} getter. */
|
|
71
|
-
sessionPoolDep;
|
|
72
|
+
#sessionPoolDep;
|
|
72
73
|
/**
|
|
73
74
|
* A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
|
|
74
75
|
* {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
|
|
75
76
|
*/
|
|
76
77
|
get sessionPool() {
|
|
77
|
-
return this
|
|
78
|
+
return this.#sessionPoolDep.value;
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
80
81
|
* Tracks **only** the queue the crawler opens for itself — not the {@link RequestManagerTandem} that may wrap it
|
|
81
82
|
* around a user-supplied `requestList` — so the owned-only purge between repeated `run()` calls never reaches
|
|
82
83
|
* through to a borrowed loader. Filled lazily in {@link BasicCrawler.openOwnedRequestQueue|`openOwnedRequestQueue()`}.
|
|
83
84
|
*/
|
|
84
|
-
ownedRequestQueue = OwnedOrInjected.resolve();
|
|
85
|
+
#ownedRequestQueue = OwnedOrInjected.resolve();
|
|
85
86
|
/**
|
|
86
87
|
* Whether the request-processing-time hint has already been forwarded to the request manager. The hint
|
|
87
88
|
* derives only from `requestHandlerTimeoutMillis` (constant for the crawler's lifetime) and is raise-only,
|
|
88
89
|
* so it only needs to be applied once, at the first async access of the manager.
|
|
89
90
|
*/
|
|
90
|
-
requestManagerTimeoutsApplied = false;
|
|
91
|
+
#requestManagerTimeoutsApplied = false;
|
|
91
92
|
/**
|
|
92
93
|
* Resolves the governor for one run: either the injected
|
|
93
94
|
* {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} (borrowed) or a freshly built default with
|
|
94
95
|
* the concurrency shortcuts folded in (owned, so the crawler starts and stops it).
|
|
95
96
|
*/
|
|
96
|
-
resolveConcurrencySystem;
|
|
97
|
-
/** As resolved by `
|
|
98
|
-
concurrencySystemDep;
|
|
97
|
+
#resolveConcurrencySystem;
|
|
98
|
+
/** As resolved by `init()`. Absent until the first run, so a `teardown()` before it is a no-op. */
|
|
99
|
+
#concurrencySystemDep;
|
|
99
100
|
/**
|
|
100
101
|
* The concurrency governor this run is booking its requests against — either the
|
|
101
102
|
* {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} that was injected, or the default the
|
|
@@ -109,7 +110,7 @@ export class BasicCrawler {
|
|
|
109
110
|
* `minConcurrency`/`maxConcurrency`/`desiredConcurrency` on your own reference.
|
|
110
111
|
*/
|
|
111
112
|
get concurrencySystem() {
|
|
112
|
-
return this
|
|
113
|
+
return this.#concurrencySystemDep?.maybeValue;
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
115
116
|
* The task loop that dispatches this run's requests. Private on purpose — it is a bare parallel task runner with
|
|
@@ -118,7 +119,7 @@ export class BasicCrawler {
|
|
|
118
119
|
* {@link BasicCrawler.resume|`resume()`}, {@link BasicCrawler.teardown|`teardown()`} and
|
|
119
120
|
* {@link BasicCrawler.concurrencySystem|`concurrencySystem`}.
|
|
120
121
|
*/
|
|
121
|
-
autoscaledPool;
|
|
122
|
+
#autoscaledPool;
|
|
122
123
|
/**
|
|
123
124
|
* A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
|
|
124
125
|
* Only available if used by the crawler.
|
|
@@ -129,7 +130,7 @@ export class BasicCrawler {
|
|
|
129
130
|
* See {@link Router.addHandler|`router.addHandler()`} and {@link Router.addDefaultHandler|`router.addDefaultHandler()`}.
|
|
130
131
|
*/
|
|
131
132
|
router = Router.create();
|
|
132
|
-
|
|
133
|
+
#basicContextPipeline;
|
|
133
134
|
/**
|
|
134
135
|
* The basic part of the context pipeline. Unlike the subclass pipeline, this
|
|
135
136
|
* part has no major side effects (e.g. launching a browser). It also makes typing more explicit, as subclass
|
|
@@ -139,21 +140,21 @@ export class BasicCrawler {
|
|
|
139
140
|
* This is used e.g. in the {@link AdaptivePlaywrightCrawler|`AdaptivePlaywrightCrawler`}.
|
|
140
141
|
*/
|
|
141
142
|
get basicContextPipeline() {
|
|
142
|
-
if (this
|
|
143
|
-
this
|
|
143
|
+
if (this.#basicContextPipeline === undefined) {
|
|
144
|
+
this.#basicContextPipeline = this.buildBasicContextPipeline();
|
|
144
145
|
}
|
|
145
|
-
return this
|
|
146
|
+
return this.#basicContextPipeline;
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
+
#contextPipeline;
|
|
148
149
|
get contextPipeline() {
|
|
149
|
-
if (this
|
|
150
|
-
this
|
|
150
|
+
if (this.#contextPipeline === undefined) {
|
|
151
|
+
this.#contextPipeline = this.buildFinalContextPipeline();
|
|
151
152
|
}
|
|
152
|
-
return this
|
|
153
|
+
return this.#contextPipeline;
|
|
153
154
|
}
|
|
154
155
|
running = false;
|
|
155
156
|
hasFinishedBefore = false;
|
|
156
|
-
unexpectedStop = false;
|
|
157
|
+
#unexpectedStop = false;
|
|
157
158
|
#log;
|
|
158
159
|
get log() {
|
|
159
160
|
return this.#log;
|
|
@@ -161,41 +162,43 @@ export class BasicCrawler {
|
|
|
161
162
|
requestHandler;
|
|
162
163
|
errorHandler;
|
|
163
164
|
failedRequestHandler;
|
|
165
|
+
// kept as TS-private: tests read it at runtime
|
|
164
166
|
requestHandlerTimeoutMillis;
|
|
165
167
|
internalTimeoutMillis;
|
|
166
168
|
maxRequestRetries;
|
|
167
169
|
maxCrawlDepth;
|
|
168
|
-
sameDomainDelayMillis;
|
|
169
|
-
domainAccessedTime;
|
|
170
|
+
#sameDomainDelayMillis;
|
|
171
|
+
#domainAccessedTime;
|
|
170
172
|
maxRequestsPerCrawl;
|
|
171
173
|
get handledRequestsCount() {
|
|
172
174
|
return this.stats.state.requestsFinished + this.stats.state.requestsFailed;
|
|
173
175
|
}
|
|
174
|
-
statusMessageLoggingInterval;
|
|
175
|
-
statusMessageCallback;
|
|
176
|
+
#statusMessageLoggingInterval;
|
|
177
|
+
#statusMessageCallback;
|
|
176
178
|
blockedStatusCodes = new Set();
|
|
177
179
|
additionalHttpErrorStatusCodes;
|
|
178
|
-
ignoreHttpErrorStatusCodes;
|
|
180
|
+
#ignoreHttpErrorStatusCodes;
|
|
179
181
|
/**
|
|
180
182
|
* The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
|
|
181
183
|
* user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
|
|
182
184
|
* {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
|
|
183
185
|
* settable here.
|
|
184
186
|
*/
|
|
187
|
+
// kept as TS-private: tests mutate it at runtime
|
|
185
188
|
taskLoopOptions;
|
|
186
189
|
httpClient;
|
|
187
190
|
retryOnBlocked;
|
|
188
|
-
respectRobotsTxtFile;
|
|
191
|
+
#respectRobotsTxtFile;
|
|
189
192
|
/** Whether `runInStorageTransaction()` opens a transaction at all. */
|
|
190
|
-
transactionalStorageEnabled;
|
|
193
|
+
#transactionalStorageEnabled;
|
|
191
194
|
/** The resolved per-storage-type write policy overrides forwarded to each request's transaction. */
|
|
192
|
-
storageWritePolicy;
|
|
195
|
+
#storageWritePolicy;
|
|
193
196
|
onSkippedRequest;
|
|
194
|
-
|
|
195
|
-
loggedPerRun = new Set();
|
|
196
|
-
robotsTxtFileCache;
|
|
197
|
+
#closeEvents;
|
|
198
|
+
#loggedPerRun = new Set();
|
|
199
|
+
#robotsTxtFileCache;
|
|
197
200
|
identity;
|
|
198
|
-
contextPipelineOptions;
|
|
201
|
+
#contextPipelineOptions;
|
|
199
202
|
static optionsShape = {
|
|
200
203
|
contextPipelineBuilder: ow.optional.object,
|
|
201
204
|
extendContext: ow.optional.function,
|
|
@@ -278,7 +281,7 @@ export class BasicCrawler {
|
|
|
278
281
|
}
|
|
279
282
|
try {
|
|
280
283
|
serviceLocatorScope.enterScope();
|
|
281
|
-
this
|
|
284
|
+
this.#contextPipelineOptions = {
|
|
282
285
|
contextPipelineBuilder: options.contextPipelineBuilder,
|
|
283
286
|
extendContext: options.extendContext,
|
|
284
287
|
};
|
|
@@ -309,13 +312,13 @@ export class BasicCrawler {
|
|
|
309
312
|
}
|
|
310
313
|
this.httpClient = httpClient ?? new LazyDefaultHttpClient({ logger: this.log });
|
|
311
314
|
this.proxyConfiguration = proxyConfiguration;
|
|
312
|
-
this
|
|
313
|
-
this
|
|
314
|
-
this
|
|
315
|
-
this
|
|
315
|
+
this.#statusMessageLoggingInterval = statusMessageLoggingInterval;
|
|
316
|
+
this.#statusMessageCallback = statusMessageCallback;
|
|
317
|
+
this.#domainAccessedTime = new Map();
|
|
318
|
+
this.#robotsTxtFileCache = new LruCache({ maxLength: 1000 });
|
|
316
319
|
this.handleSkippedRequest = this.handleSkippedRequest.bind(this);
|
|
317
320
|
this.additionalHttpErrorStatusCodes = new Set([...additionalHttpErrorStatusCodes]);
|
|
318
|
-
this
|
|
321
|
+
this.#ignoreHttpErrorStatusCodes = new Set([...ignoreHttpErrorStatusCodes]);
|
|
319
322
|
this.requestHandler = requestHandler ?? this.router;
|
|
320
323
|
this.failedRequestHandler = failedRequestHandler;
|
|
321
324
|
this.errorHandler = errorHandler;
|
|
@@ -326,11 +329,11 @@ export class BasicCrawler {
|
|
|
326
329
|
this.requestHandlerTimeoutMillis = 60_000;
|
|
327
330
|
}
|
|
328
331
|
this.retryOnBlocked = retryOnBlocked;
|
|
329
|
-
this
|
|
332
|
+
this.#respectRobotsTxtFile = respectRobotsTxtFile;
|
|
330
333
|
// The cast undoes ow's assertion signature, which mangles `boolean | object` unions.
|
|
331
334
|
const transactionalStorageOption = transactionalStorage;
|
|
332
|
-
this
|
|
333
|
-
this
|
|
335
|
+
this.#transactionalStorageEnabled = transactionalStorageOption !== false;
|
|
336
|
+
this.#storageWritePolicy = typeof transactionalStorageOption === 'object' ? transactionalStorageOption : {};
|
|
334
337
|
this.onSkippedRequest = onSkippedRequest;
|
|
335
338
|
// allow at least 5min for internal timeouts
|
|
336
339
|
this.internalTimeoutMillis =
|
|
@@ -338,8 +341,8 @@ export class BasicCrawler {
|
|
|
338
341
|
Math.max(this.requestHandlerTimeoutMillis * 2, 300e3);
|
|
339
342
|
this.maxRequestRetries = maxRequestRetries;
|
|
340
343
|
this.maxCrawlDepth = maxCrawlDepth;
|
|
341
|
-
this
|
|
342
|
-
this
|
|
344
|
+
this.#sameDomainDelayMillis = sameDomainDelaySecs * 1000;
|
|
345
|
+
this.#statsDep = OwnedOrInjected.resolve(statistics, () => new Statistics({
|
|
343
346
|
logMessage: `${this.constructor.name} request statistics:`,
|
|
344
347
|
log: this.log,
|
|
345
348
|
id: this.identity.id,
|
|
@@ -350,7 +353,7 @@ export class BasicCrawler {
|
|
|
350
353
|
'`proxyInfo` they were created with. Configure proxies on the pool instead, ' +
|
|
351
354
|
'e.g. via `addSession({ proxyInfo })` or a custom `createSessionFunction`.');
|
|
352
355
|
}
|
|
353
|
-
this
|
|
356
|
+
this.#sessionPoolDep = OwnedOrInjected.resolve(sessionPool, () => new SessionPool({
|
|
354
357
|
createSessionFunction: async (opts) => new Session({
|
|
355
358
|
...opts?.sessionOptions,
|
|
356
359
|
proxyInfo: opts?.sessionOptions?.proxyInfo ?? (await this.proxyConfiguration?.newProxyInfo()),
|
|
@@ -435,7 +438,7 @@ export class BasicCrawler {
|
|
|
435
438
|
`${this.maxRequestsPerCrawl} requests and will shut down soon. Requests that are in progress will be allowed to finish.`);
|
|
436
439
|
return false;
|
|
437
440
|
}
|
|
438
|
-
if (this
|
|
441
|
+
if (this.#unexpectedStop) {
|
|
439
442
|
this.logOncePerRun('shuttingDown', 'No new requests are allowed because the `stop()` method has been called. ' +
|
|
440
443
|
'Ongoing requests will be allowed to complete.');
|
|
441
444
|
return false;
|
|
@@ -449,7 +452,7 @@ export class BasicCrawler {
|
|
|
449
452
|
`In total, the crawler processed ${this.handledRequestsCount} requests and will shut down.`);
|
|
450
453
|
return true;
|
|
451
454
|
}
|
|
452
|
-
if (this
|
|
455
|
+
if (this.#unexpectedStop) {
|
|
453
456
|
this.log.info('The crawler has finished all the remaining ongoing requests and will shut down now.');
|
|
454
457
|
return true;
|
|
455
458
|
}
|
|
@@ -467,7 +470,7 @@ export class BasicCrawler {
|
|
|
467
470
|
log: this.log,
|
|
468
471
|
};
|
|
469
472
|
this.taskLoopOptions = { ...taskLoopOptions, ...crawlerOwnedTaskLoopConfiguration };
|
|
470
|
-
this
|
|
473
|
+
this.#resolveConcurrencySystem = () => OwnedOrInjected.resolve(concurrencySystem, () => this.createDefaultConcurrencySystem({
|
|
471
474
|
minConcurrency,
|
|
472
475
|
maxConcurrency,
|
|
473
476
|
maxTasksPerMinute: maxRequestsPerMinute,
|
|
@@ -496,7 +499,7 @@ export class BasicCrawler {
|
|
|
496
499
|
* @returns `true` if the status code is considered an error, `false` otherwise
|
|
497
500
|
*/
|
|
498
501
|
isErrorStatusCode(status) {
|
|
499
|
-
const excludeError = this
|
|
502
|
+
const excludeError = this.#ignoreHttpErrorStatusCodes.has(status);
|
|
500
503
|
const includeError = this.additionalHttpErrorStatusCodes.has(status);
|
|
501
504
|
return (status >= 500 && !excludeError) || includeError;
|
|
502
505
|
}
|
|
@@ -589,7 +592,7 @@ export class BasicCrawler {
|
|
|
589
592
|
return { enqueueLinks: enqueueLinksWrapper, addRequests, sendRequest };
|
|
590
593
|
}
|
|
591
594
|
buildFinalContextPipeline() {
|
|
592
|
-
const subclassPipeline = (this
|
|
595
|
+
const subclassPipeline = (this.#contextPipelineOptions.contextPipelineBuilder?.() ??
|
|
593
596
|
this.buildContextPipeline());
|
|
594
597
|
// `extendContext` runs *before* the subclass navigation pipeline (which includes the
|
|
595
598
|
// pre/post-navigation hooks). This makes the extension visible to those hooks and to the
|
|
@@ -601,7 +604,7 @@ export class BasicCrawler {
|
|
|
601
604
|
// TypeScript cannot express that `Context` transitively includes `ContextExtension` here. The
|
|
602
605
|
// casts below are sound because `buildFinalContextPipeline` is declared to return the fully
|
|
603
606
|
// resolved `ExtendedContext` (= `Context & ContextExtension`).
|
|
604
|
-
const { extendContext } = this
|
|
607
|
+
const { extendContext } = this.#contextPipelineOptions;
|
|
605
608
|
let contextPipeline;
|
|
606
609
|
if (extendContext !== undefined) {
|
|
607
610
|
contextPipeline = ContextPipeline.create()
|
|
@@ -635,7 +638,7 @@ export class BasicCrawler {
|
|
|
635
638
|
* @param error The error to check.
|
|
636
639
|
*/
|
|
637
640
|
isProxyError(error) {
|
|
638
|
-
return ROTATE_PROXY_ERRORS.some((x) => this.
|
|
641
|
+
return ROTATE_PROXY_ERRORS.some((x) => this.getMessageFromError(error)?.includes(x));
|
|
639
642
|
}
|
|
640
643
|
/**
|
|
641
644
|
* Sets the status message for the current crawler run.
|
|
@@ -676,14 +679,14 @@ export class BasicCrawler {
|
|
|
676
679
|
const { mode: operationMode, failedDelta } = getOperationMode();
|
|
677
680
|
let message;
|
|
678
681
|
if (operationMode === 'ERROR') {
|
|
679
|
-
message = `Experiencing problems, ${failedDelta} failed requests in the past ${this
|
|
682
|
+
message = `Experiencing problems, ${failedDelta} failed requests in the past ${this.#statusMessageLoggingInterval} seconds.`;
|
|
680
683
|
}
|
|
681
684
|
else {
|
|
682
685
|
const total = await this.requestManager?.getTotalCount();
|
|
683
686
|
message = `Crawled ${this.stats.state.requestsFinished}${total ? `/${total}` : ''} pages, ${this.stats.state.requestsFailed} failed requests, desired concurrency ${this.concurrencySystem?.desiredConcurrency ?? 0}.`;
|
|
684
687
|
}
|
|
685
|
-
if (this
|
|
686
|
-
await this
|
|
688
|
+
if (this.#statusMessageCallback) {
|
|
689
|
+
await this.#statusMessageCallback({
|
|
687
690
|
crawler: this,
|
|
688
691
|
state: this.stats.state,
|
|
689
692
|
previousState,
|
|
@@ -693,7 +696,7 @@ export class BasicCrawler {
|
|
|
693
696
|
}
|
|
694
697
|
this.setStatusMessage(message);
|
|
695
698
|
};
|
|
696
|
-
const interval = setInterval(log, this
|
|
699
|
+
const interval = setInterval(log, this.#statusMessageLoggingInterval * 1e3);
|
|
697
700
|
return { log, stop: () => clearInterval(interval) };
|
|
698
701
|
}
|
|
699
702
|
/**
|
|
@@ -721,20 +724,20 @@ export class BasicCrawler {
|
|
|
721
724
|
// When `purgeRequestQueue` is explicitly `true`, we also purge a user-supplied manager.
|
|
722
725
|
// When `purgeRequestQueue` is explicitly `false`, nothing is purged.
|
|
723
726
|
const shouldPurge = purgeRequestQueue !== false;
|
|
724
|
-
const managerToPurge = this
|
|
727
|
+
const managerToPurge = this.#ownedRequestQueue.maybeValue ?? (purgeRequestQueue === true ? this.requestManager : undefined);
|
|
725
728
|
if (managerToPurge?.purge && shouldPurge) {
|
|
726
729
|
await managerToPurge.purge();
|
|
727
730
|
}
|
|
728
731
|
// A supplied statistics instance keeps whatever state it was handed - only wipe a default we built.
|
|
729
|
-
await this
|
|
732
|
+
await this.#statsDep.ifOwned(async (stats) => {
|
|
730
733
|
stats.reset();
|
|
731
734
|
await stats.resetStore();
|
|
732
735
|
});
|
|
733
|
-
await this
|
|
736
|
+
await this.#sessionPoolDep.ifOwned((pool) => pool.resetStore());
|
|
734
737
|
}
|
|
735
|
-
this
|
|
738
|
+
this.#unexpectedStop = false;
|
|
736
739
|
this.running = true;
|
|
737
|
-
this
|
|
740
|
+
this.#loggedPerRun.clear();
|
|
738
741
|
await purgeDefaultStorages({
|
|
739
742
|
onlyPurgeOnce: true,
|
|
740
743
|
storageBackend: serviceLocator.getStorageBackend(),
|
|
@@ -744,7 +747,7 @@ export class BasicCrawler {
|
|
|
744
747
|
await this.addRequests(requests, addRequestsOptions);
|
|
745
748
|
}
|
|
746
749
|
try {
|
|
747
|
-
await this.
|
|
750
|
+
await this.init();
|
|
748
751
|
await this.stats.startCapturing();
|
|
749
752
|
}
|
|
750
753
|
catch (error) {
|
|
@@ -761,7 +764,7 @@ export class BasicCrawler {
|
|
|
761
764
|
const sigintHandler = async () => {
|
|
762
765
|
this.log.warning('Pausing... Press CTRL+C again to force exit. To resume, do: CRAWLEE_PURGE_ON_START=0 npm start');
|
|
763
766
|
await this.pauseOnMigration();
|
|
764
|
-
await this
|
|
767
|
+
await this.#autoscaledPool.abort();
|
|
765
768
|
};
|
|
766
769
|
// Attach a listener to handle migration and aborting events gracefully.
|
|
767
770
|
const boundPauseOnMigration = this.pauseOnMigration.bind(this);
|
|
@@ -771,7 +774,7 @@ export class BasicCrawler {
|
|
|
771
774
|
eventManager.on("aborting" /* EventType.ABORTING */, boundPauseOnMigration);
|
|
772
775
|
let stats = {};
|
|
773
776
|
try {
|
|
774
|
-
await this
|
|
777
|
+
await this.#autoscaledPool.run();
|
|
775
778
|
}
|
|
776
779
|
finally {
|
|
777
780
|
await this.teardown();
|
|
@@ -821,11 +824,11 @@ export class BasicCrawler {
|
|
|
821
824
|
* To stop the crawler immediately, use {@link BasicCrawler.teardown|`crawler.teardown()`} instead.
|
|
822
825
|
*/
|
|
823
826
|
stop(reason = 'The crawler has been gracefully stopped.') {
|
|
824
|
-
if (this
|
|
827
|
+
if (this.#unexpectedStop) {
|
|
825
828
|
return;
|
|
826
829
|
}
|
|
827
830
|
this.log.info(reason);
|
|
828
|
-
this
|
|
831
|
+
this.#unexpectedStop = true;
|
|
829
832
|
}
|
|
830
833
|
/**
|
|
831
834
|
* Stops dispatching new requests, letting the in-progress ones finish. Resolves once they have settled, or rejects
|
|
@@ -836,22 +839,22 @@ export class BasicCrawler {
|
|
|
836
839
|
* throughout, since a shared one may still be serving other crawlers.
|
|
837
840
|
*/
|
|
838
841
|
async pause(timeoutSecs) {
|
|
839
|
-
if (!this
|
|
842
|
+
if (!this.#autoscaledPool) {
|
|
840
843
|
this.log.warning('Cannot pause a crawler that is not running.');
|
|
841
844
|
return;
|
|
842
845
|
}
|
|
843
|
-
await this
|
|
846
|
+
await this.#autoscaledPool.pause(timeoutSecs);
|
|
844
847
|
}
|
|
845
848
|
/**
|
|
846
849
|
* Resumes a run suspended with {@link BasicCrawler.pause|`pause()`}, letting the crawler dispatch requests
|
|
847
850
|
* again. A no-op on a crawler that is not paused.
|
|
848
851
|
*/
|
|
849
852
|
resume() {
|
|
850
|
-
if (!this
|
|
853
|
+
if (!this.#autoscaledPool) {
|
|
851
854
|
this.log.warning('Cannot resume a crawler that is not running.');
|
|
852
855
|
return;
|
|
853
856
|
}
|
|
854
|
-
this
|
|
857
|
+
this.#autoscaledPool.resume();
|
|
855
858
|
}
|
|
856
859
|
/**
|
|
857
860
|
* Returns the crawler's {@link IRequestManager|request manager}, opening the default {@link RequestQueue}
|
|
@@ -864,8 +867,8 @@ export class BasicCrawler {
|
|
|
864
867
|
// Apply the processing-time hint here (an async lifecycle point) rather than in the constructor,
|
|
865
868
|
// now that `setExpectedRequestProcessingTimeSecs` is async. The hint is raise-only and idempotent,
|
|
866
869
|
// but guard so we do not re-issue it on every call.
|
|
867
|
-
if (!this
|
|
868
|
-
this
|
|
870
|
+
if (!this.#requestManagerTimeoutsApplied) {
|
|
871
|
+
this.#requestManagerTimeoutsApplied = true;
|
|
869
872
|
await this.applyRequestManagerTimeouts(this.requestManager);
|
|
870
873
|
}
|
|
871
874
|
return this.requestManager;
|
|
@@ -887,7 +890,7 @@ export class BasicCrawler {
|
|
|
887
890
|
// subsequent instances get their own queue via a unique alias so they don't collide.
|
|
888
891
|
const identifier = this.identity.instanceIndex === 0 ? null : { alias: `__default_${this.identity.id}__` };
|
|
889
892
|
const requestQueue = await RequestQueue.open(identifier, { configuration: serviceLocator.getConfiguration() });
|
|
890
|
-
return this
|
|
893
|
+
return this.#ownedRequestQueue.set(requestQueue);
|
|
891
894
|
}
|
|
892
895
|
/**
|
|
893
896
|
* Tells a request manager how long we expect to hold a fetched request, so that one backed by a
|
|
@@ -936,8 +939,8 @@ export class BasicCrawler {
|
|
|
936
939
|
const stateKey = `${BasicCrawler.CRAWLEE_STATE_KEY}_${this.identity.id}`;
|
|
937
940
|
return kvs.getAutoSavedValue(stateKey, defaultValue);
|
|
938
941
|
}
|
|
939
|
-
BasicCrawler
|
|
940
|
-
if (BasicCrawler
|
|
942
|
+
BasicCrawler.#useStateAnonymousIndices.add(this.identity.instanceIndex);
|
|
943
|
+
if (BasicCrawler.#useStateAnonymousIndices.size > 1) {
|
|
941
944
|
serviceLocator
|
|
942
945
|
.getLogger()
|
|
943
946
|
.warningOnce('Multiple crawler instances are calling useState() without an explicit `id` option. \n' +
|
|
@@ -972,9 +975,9 @@ export class BasicCrawler {
|
|
|
972
975
|
});
|
|
973
976
|
}
|
|
974
977
|
logOncePerRun(key, message) {
|
|
975
|
-
if (!this
|
|
978
|
+
if (!this.#loggedPerRun.has(key)) {
|
|
976
979
|
this.log.info(message);
|
|
977
|
-
this
|
|
980
|
+
this.#loggedPerRun.add(key);
|
|
978
981
|
}
|
|
979
982
|
}
|
|
980
983
|
/**
|
|
@@ -1112,11 +1115,11 @@ export class BasicCrawler {
|
|
|
1112
1115
|
/**
|
|
1113
1116
|
* Initializes the crawler.
|
|
1114
1117
|
*/
|
|
1115
|
-
async
|
|
1118
|
+
async init() {
|
|
1116
1119
|
const eventManager = serviceLocator.getEventManager();
|
|
1117
1120
|
if (!eventManager.isInitialized()) {
|
|
1118
1121
|
await eventManager.init();
|
|
1119
|
-
this
|
|
1122
|
+
this.#closeEvents = true;
|
|
1120
1123
|
}
|
|
1121
1124
|
// Warn once at startup if the internal timeout is shorter than the phases it is meant to outlast. It is
|
|
1122
1125
|
// floored per request so it will not actually cut them short, but the configured value is then effectively
|
|
@@ -1131,11 +1134,11 @@ export class BasicCrawler {
|
|
|
1131
1134
|
// An owned governor is rebuilt (and started) for every run, so it always starts from a clean slate — stale
|
|
1132
1135
|
// resource snapshots or a previous run's scaled desired concurrency would otherwise distort this run's
|
|
1133
1136
|
// scaling. An injected one is long-lived and its lifecycle belongs to the caller.
|
|
1134
|
-
this
|
|
1135
|
-
await this
|
|
1136
|
-
this
|
|
1137
|
+
this.#concurrencySystemDep = this.#resolveConcurrencySystem();
|
|
1138
|
+
await this.#concurrencySystemDep.ifOwned((system) => system.start());
|
|
1139
|
+
this.#autoscaledPool = new AutoscaledPool({
|
|
1137
1140
|
...this.taskLoopOptions,
|
|
1138
|
-
concurrencySystem: this
|
|
1141
|
+
concurrencySystem: this.#concurrencySystemDep.value,
|
|
1139
1142
|
consumer: this.identity,
|
|
1140
1143
|
});
|
|
1141
1144
|
await this.getRequestManager();
|
|
@@ -1192,11 +1195,11 @@ export class BasicCrawler {
|
|
|
1192
1195
|
* a normal return says nothing about success. `handleRequest` owns the outcome.
|
|
1193
1196
|
*/
|
|
1194
1197
|
async runInStorageTransaction(callback) {
|
|
1195
|
-
if (!this
|
|
1198
|
+
if (!this.#transactionalStorageEnabled) {
|
|
1196
1199
|
return callback();
|
|
1197
1200
|
}
|
|
1198
1201
|
const transaction = createStorageTransaction({
|
|
1199
|
-
policy: this
|
|
1202
|
+
policy: this.#storageWritePolicy,
|
|
1200
1203
|
commitTimeoutMillis: this.internalTimeoutMillis,
|
|
1201
1204
|
});
|
|
1202
1205
|
let threw = true;
|
|
@@ -1223,7 +1226,7 @@ export class BasicCrawler {
|
|
|
1223
1226
|
/**
|
|
1224
1227
|
* Handles blocked request
|
|
1225
1228
|
*/
|
|
1226
|
-
|
|
1229
|
+
throwOnBlockedRequest(statusCode) {
|
|
1227
1230
|
if (this.retryOnBlocked)
|
|
1228
1231
|
return;
|
|
1229
1232
|
if (this.blockedStatusCodes.has(statusCode)) {
|
|
@@ -1231,25 +1234,25 @@ export class BasicCrawler {
|
|
|
1231
1234
|
}
|
|
1232
1235
|
}
|
|
1233
1236
|
async isAllowedBasedOnRobotsTxtFile(url) {
|
|
1234
|
-
if (!this
|
|
1237
|
+
if (!this.#respectRobotsTxtFile) {
|
|
1235
1238
|
return true;
|
|
1236
1239
|
}
|
|
1237
1240
|
const robotsTxtFile = await this.getRobotsTxtFileForUrl(url);
|
|
1238
|
-
const userAgent = typeof this
|
|
1241
|
+
const userAgent = typeof this.#respectRobotsTxtFile === 'object' ? this.#respectRobotsTxtFile?.userAgent : '*';
|
|
1239
1242
|
return !robotsTxtFile || robotsTxtFile.isAllowed(url, userAgent);
|
|
1240
1243
|
}
|
|
1241
1244
|
async getRobotsTxtFileForUrl(url) {
|
|
1242
|
-
if (!this
|
|
1245
|
+
if (!this.#respectRobotsTxtFile) {
|
|
1243
1246
|
return undefined;
|
|
1244
1247
|
}
|
|
1245
1248
|
try {
|
|
1246
1249
|
const origin = new URL(url).origin;
|
|
1247
|
-
const cachedRobotsTxtFile = this
|
|
1250
|
+
const cachedRobotsTxtFile = this.#robotsTxtFileCache.get(origin);
|
|
1248
1251
|
if (cachedRobotsTxtFile) {
|
|
1249
1252
|
return cachedRobotsTxtFile;
|
|
1250
1253
|
}
|
|
1251
1254
|
const robotsTxtFile = await RobotsTxtFile.find(url, { logger: this.log });
|
|
1252
|
-
this
|
|
1255
|
+
this.#robotsTxtFileCache.add(origin, robotsTxtFile);
|
|
1253
1256
|
return robotsTxtFile;
|
|
1254
1257
|
}
|
|
1255
1258
|
catch (e) {
|
|
@@ -1258,9 +1261,9 @@ export class BasicCrawler {
|
|
|
1258
1261
|
}
|
|
1259
1262
|
}
|
|
1260
1263
|
async pauseOnMigration() {
|
|
1261
|
-
if (this
|
|
1264
|
+
if (this.#autoscaledPool) {
|
|
1262
1265
|
// if run wasn't called, this is going to crash
|
|
1263
|
-
await this
|
|
1266
|
+
await this.#autoscaledPool.pause(SAFE_MIGRATION_WAIT_MILLIS).catch((err) => {
|
|
1264
1267
|
if (err.message.includes('running tasks did not finish')) {
|
|
1265
1268
|
this.log.error('The crawler was paused due to migration to another host, ' +
|
|
1266
1269
|
"but some requests did not finish in time. Those requests' results may be duplicated.");
|
|
@@ -1311,12 +1314,12 @@ export class BasicCrawler {
|
|
|
1311
1314
|
return false;
|
|
1312
1315
|
}
|
|
1313
1316
|
const now = Date.now();
|
|
1314
|
-
const lastAccessTime = this
|
|
1315
|
-
if (!lastAccessTime || now - lastAccessTime >= this
|
|
1316
|
-
this
|
|
1317
|
+
const lastAccessTime = this.#domainAccessedTime.get(domain);
|
|
1318
|
+
if (!lastAccessTime || now - lastAccessTime >= this.#sameDomainDelayMillis) {
|
|
1319
|
+
this.#domainAccessedTime.set(domain, now);
|
|
1317
1320
|
return false;
|
|
1318
1321
|
}
|
|
1319
|
-
const delay = lastAccessTime + this
|
|
1322
|
+
const delay = lastAccessTime + this.#sameDomainDelayMillis - now;
|
|
1320
1323
|
this.log.debug(`Request ${request.url} (${request.id}) will be reclaimed after ${delay} milliseconds due to same domain delay`);
|
|
1321
1324
|
setTimeout(async () => {
|
|
1322
1325
|
this.log.debug(`Adding request ${request.url} (${request.id}) back to the queue`);
|
|
@@ -1434,7 +1437,7 @@ export class BasicCrawler {
|
|
|
1434
1437
|
return await enqueueLinks({
|
|
1435
1438
|
requestManager,
|
|
1436
1439
|
robotsTxtFile: await this.getRobotsTxtFileForUrl(request.url),
|
|
1437
|
-
respectRobotsTxtFile: this
|
|
1440
|
+
respectRobotsTxtFile: this.#respectRobotsTxtFile,
|
|
1438
1441
|
onSkippedRequest,
|
|
1439
1442
|
limit: await this.calculateEnqueuedRequestLimit(options.limit),
|
|
1440
1443
|
// Allow user options to override defaults set above ⤴
|
|
@@ -1521,7 +1524,7 @@ export class BasicCrawler {
|
|
|
1521
1524
|
const { url, retryCount, id } = request;
|
|
1522
1525
|
// We don't want to see the stack trace in the logs by default, when we are going to retry the request.
|
|
1523
1526
|
// Thus, we print the full stack trace only when CRAWLEE_VERBOSE_LOG environment variable is set to true.
|
|
1524
|
-
const message = this.
|
|
1527
|
+
const message = this.getMessageFromError(error);
|
|
1525
1528
|
this.log.warning(`Reclaiming failed request back to the list or queue. ${message}`, {
|
|
1526
1529
|
id,
|
|
1527
1530
|
url,
|
|
@@ -1554,7 +1557,7 @@ export class BasicCrawler {
|
|
|
1554
1557
|
async handleFailedRequestHandler(crawlingContext, error) {
|
|
1555
1558
|
// Always log the last error regardless if the user provided a failedRequestHandler
|
|
1556
1559
|
const { id, url, method, uniqueKey } = crawlingContext.request;
|
|
1557
|
-
const message = this.
|
|
1560
|
+
const message = this.getMessageFromError(error, true);
|
|
1558
1561
|
this.log.error(`Request failed and reached maximum retries. ${message}`, { id, url, method, uniqueKey });
|
|
1559
1562
|
if (this.failedRequestHandler) {
|
|
1560
1563
|
await this.failedRequestHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
|
|
@@ -1566,7 +1569,7 @@ export class BasicCrawler {
|
|
|
1566
1569
|
* @param error The error received
|
|
1567
1570
|
* @returns The message to be logged
|
|
1568
1571
|
*/
|
|
1569
|
-
|
|
1572
|
+
getMessageFromError(error, forceStack = false) {
|
|
1570
1573
|
if ([TypeError, SyntaxError, ReferenceError].some((type) => error instanceof type)) {
|
|
1571
1574
|
forceStack = true;
|
|
1572
1575
|
}
|
|
@@ -1602,14 +1605,14 @@ export class BasicCrawler {
|
|
|
1602
1605
|
*/
|
|
1603
1606
|
async teardown() {
|
|
1604
1607
|
serviceLocator.getEventManager().emit("persistState" /* EventType.PERSIST_STATE */, { isMigrating: false });
|
|
1605
|
-
if (this
|
|
1608
|
+
if (this.#closeEvents) {
|
|
1606
1609
|
await serviceLocator.getEventManager().close();
|
|
1607
1610
|
}
|
|
1608
|
-
await this
|
|
1609
|
-
await this
|
|
1610
|
-
await this
|
|
1611
|
+
await this.#sessionPoolDep.ifOwned((pool) => pool.teardown());
|
|
1612
|
+
await this.#autoscaledPool?.abort();
|
|
1613
|
+
await this.#concurrencySystemDep?.ifOwned((system) => system.stop());
|
|
1611
1614
|
}
|
|
1612
|
-
|
|
1615
|
+
getCookieHeaderFromRequest(request) {
|
|
1613
1616
|
if (request.headers?.Cookie && request.headers?.cookie) {
|
|
1614
1617
|
this.log.warning(`Encountered mixed casing for the cookie headers for request ${request.url} (${request.id}). Their values will be merged.`);
|
|
1615
1618
|
return mergeCookies(request.url, [request.headers.cookie, request.headers.Cookie]);
|
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.107",
|
|
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.4.4",
|
|
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.107",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.107",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.107",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.107",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
50
|
"ow": "^2.0.0",
|
|
51
51
|
"tldts": "^7.0.6",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"type-fest": "^4.41.0"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@crawlee/impit-client": "^4.0.0-beta.
|
|
56
|
+
"@crawlee/impit-client": "^4.0.0-beta.107"
|
|
57
57
|
},
|
|
58
58
|
"lerna": {
|
|
59
59
|
"command": {
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "5a3cb6242d0ae261df93c5e03cfebbb9b736e6b7"
|
|
66
66
|
}
|