@crawlee/basic 4.0.0-beta.158 → 4.0.0-beta.159
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 +7 -13
- package/internals/basic-crawler.js +29 -42
- package/package.json +7 -7
|
@@ -434,7 +434,8 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
434
434
|
}, CrawlingContext>;
|
|
435
435
|
get contextPipeline(): ContextPipeline<CrawlingContext, ExtendedContext>;
|
|
436
436
|
running: boolean;
|
|
437
|
-
|
|
437
|
+
/** Whether a `run()` on this instance has already finished - a repeated one continues where it left off. */
|
|
438
|
+
get hasFinishedBefore(): boolean;
|
|
438
439
|
get log(): CrawleeLogger;
|
|
439
440
|
protected readonly requestHandler: RequestHandler<ExtendedContext>;
|
|
440
441
|
protected readonly internalTimeoutMillis: number;
|
|
@@ -553,8 +554,12 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
553
554
|
* We can use the `requests` parameter to enqueue the initial requests — it is a shortcut for
|
|
554
555
|
* running {@link BasicCrawler.addRequests|`crawler.addRequests()`} before {@link BasicCrawler.run|`crawler.run()`}.
|
|
555
556
|
*
|
|
557
|
+
* Calling `run()` again on the same instance keeps crawling the same request manager - requests the previous
|
|
558
|
+
* run handled (a failed one counts as handled) are not processed again. Purge the queue or open a fresh one
|
|
559
|
+
* if that is what you want.
|
|
560
|
+
*
|
|
556
561
|
* @param [requests] The requests to add.
|
|
557
|
-
* @param [options] Options for the
|
|
562
|
+
* @param [options] Options for adding the initial requests.
|
|
558
563
|
*/
|
|
559
564
|
run(requests?: TypedRequestsLike<Routes>, options?: CrawlerRunOptions): Promise<FinalStatistics>;
|
|
560
565
|
/**
|
|
@@ -766,17 +771,6 @@ export interface CrawlerAddRequestsOptions extends AddRequestsBatchedOptions, En
|
|
|
766
771
|
export interface CrawlerAddRequestsResult extends AddRequestsBatchedResult {
|
|
767
772
|
}
|
|
768
773
|
export interface CrawlerRunOptions extends CrawlerAddRequestsOptions {
|
|
769
|
-
/**
|
|
770
|
-
* Controls whether the request queue is purged between repeated `run()` calls on the same crawler instance.
|
|
771
|
-
* Purging clears all requests and resets internal counters, allowing the same URLs to be processed again.
|
|
772
|
-
*
|
|
773
|
-
* - **`undefined`** (default) — only the crawler's own (auto-created) queue is purged.
|
|
774
|
-
* A user-supplied `requestQueue` is left untouched.
|
|
775
|
-
* - **`true`** — the queue is always purged, even if it was supplied by the user.
|
|
776
|
-
* - **`false`** — nothing is purged. Only genuinely new requests will be processed;
|
|
777
|
-
* note that even a failed request is considered handled.
|
|
778
|
-
*/
|
|
779
|
-
purgeRequestQueue?: boolean;
|
|
780
774
|
}
|
|
781
775
|
/**
|
|
782
776
|
* Creates new {@link Router} instance that works based on request labels.
|
|
@@ -182,8 +182,12 @@ export class BasicCrawler {
|
|
|
182
182
|
return this.#contextPipeline;
|
|
183
183
|
}
|
|
184
184
|
running = false;
|
|
185
|
-
hasFinishedBefore = false;
|
|
185
|
+
#hasFinishedBefore = false;
|
|
186
186
|
#unexpectedStop = false;
|
|
187
|
+
/** Whether a `run()` on this instance has already finished - a repeated one continues where it left off. */
|
|
188
|
+
get hasFinishedBefore() {
|
|
189
|
+
return this.#hasFinishedBefore;
|
|
190
|
+
}
|
|
187
191
|
#log;
|
|
188
192
|
get log() {
|
|
189
193
|
return this.#log;
|
|
@@ -195,16 +199,6 @@ export class BasicCrawler {
|
|
|
195
199
|
internalTimeoutMillis;
|
|
196
200
|
#maxRequestRetries;
|
|
197
201
|
#maxCrawlDepth;
|
|
198
|
-
/**
|
|
199
|
-
* How much of {@link BasicCrawler.requestManager} the crawler may empty between repeated `run()` calls.
|
|
200
|
-
*
|
|
201
|
-
* - `all` — nothing under it came from the caller, so one `purge()` on the outside covers everything.
|
|
202
|
-
* - `none` — the caller supplied it and the crawler put nothing of its own inside.
|
|
203
|
-
* - `ambiguous` — the caller supplied it, but `sameDomainDelaySecs` put the crawler's own per-domain queues
|
|
204
|
-
* underneath: purging empties the caller's storage too, skipping leaves ours stale. A repeated `run()` asks
|
|
205
|
-
* rather than guessing.
|
|
206
|
-
*/
|
|
207
|
-
#purgeableExtent;
|
|
208
202
|
#maxRequestsPerCrawl;
|
|
209
203
|
get handledRequestsCount() {
|
|
210
204
|
return this.statistics.state.requestsFinished + this.statistics.state.requestsFailed;
|
|
@@ -357,14 +351,6 @@ export class BasicCrawler {
|
|
|
357
351
|
}) ??
|
|
358
352
|
false);
|
|
359
353
|
const pacerNeeded = sameDomainDelaySecs > 0 && !floorTaken;
|
|
360
|
-
// Our per-domain queues under a manager the caller owns is the case with no right answer; a floor
|
|
361
|
-
// it took leaves nothing of ours behind. See `#purgeableExtent`.
|
|
362
|
-
if (suppliedManager === undefined) {
|
|
363
|
-
this.#purgeableExtent = 'all';
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
this.#purgeableExtent = pacerNeeded ? 'ambiguous' : 'none';
|
|
367
|
-
}
|
|
368
354
|
// Built here rather than at first use so it can sit *inside* the tandem below, which is where a
|
|
369
355
|
// loader's transferred requests pass through it.
|
|
370
356
|
const writableManager = pacerNeeded
|
|
@@ -793,8 +779,12 @@ export class BasicCrawler {
|
|
|
793
779
|
* We can use the `requests` parameter to enqueue the initial requests — it is a shortcut for
|
|
794
780
|
* running {@link BasicCrawler.addRequests|`crawler.addRequests()`} before {@link BasicCrawler.run|`crawler.run()`}.
|
|
795
781
|
*
|
|
782
|
+
* Calling `run()` again on the same instance keeps crawling the same request manager - requests the previous
|
|
783
|
+
* run handled (a failed one counts as handled) are not processed again. Purge the queue or open a fresh one
|
|
784
|
+
* if that is what you want.
|
|
785
|
+
*
|
|
796
786
|
* @param [requests] The requests to add.
|
|
797
|
-
* @param [options] Options for the
|
|
787
|
+
* @param [options] Options for adding the initial requests.
|
|
798
788
|
*/
|
|
799
789
|
async run(requests, options) {
|
|
800
790
|
// A crawl is the top level of its own transaction and timeout scope, not a participant in the caller's.
|
|
@@ -802,26 +792,7 @@ export class BasicCrawler {
|
|
|
802
792
|
if (this.running) {
|
|
803
793
|
throw new Error('This crawler instance is already running, you can add more requests to it via `crawler.addRequests()`.');
|
|
804
794
|
}
|
|
805
|
-
|
|
806
|
-
if (this.hasFinishedBefore) {
|
|
807
|
-
// When executing the run method for the second time explicitly,
|
|
808
|
-
// we need to purge the RQ to allow processing the same requests again — this is important so users can
|
|
809
|
-
// pass in failed requests back to the `crawler.run()`, otherwise they would be considered as handled and
|
|
810
|
-
// ignored — as a failed request is still handled.
|
|
811
|
-
// `purgeRequestQueue` unset purges only storage the crawler opened itself (see `#purgeableExtent`);
|
|
812
|
-
// `true` also purges a caller-supplied manager, `false` purges nothing.
|
|
813
|
-
if (purgeRequestQueue === undefined && this.#purgeableExtent === 'ambiguous') {
|
|
814
|
-
throw new Error('Cannot decide what to purge before running again: `sameDomainDelaySecs` paces the request ' +
|
|
815
|
-
'manager you supplied, so the per-domain queues that have to be emptied are the ' +
|
|
816
|
-
"crawler's while the manager underneath them is yours. Say which you want: " +
|
|
817
|
-
'`run(requests, { purgeRequestQueue: true })` empties both, `false` empties neither.');
|
|
818
|
-
}
|
|
819
|
-
if (purgeRequestQueue !== false &&
|
|
820
|
-
(this.#purgeableExtent === 'all' || purgeRequestQueue === true)) {
|
|
821
|
-
// One call from the outside in reaches everything the manager wraps, a pacer's per-domain queues
|
|
822
|
-
// included.
|
|
823
|
-
await this.requestManager?.purge?.();
|
|
824
|
-
}
|
|
795
|
+
if (this.#hasFinishedBefore) {
|
|
825
796
|
// A supplied statistics instance keeps whatever state it was handed - only wipe a default we built.
|
|
826
797
|
await this.#statisticsDep.ifOwned(async (stats) => {
|
|
827
798
|
stats.reset();
|
|
@@ -838,7 +809,7 @@ export class BasicCrawler {
|
|
|
838
809
|
configuration: serviceLocator.getConfiguration(),
|
|
839
810
|
});
|
|
840
811
|
if (requests) {
|
|
841
|
-
await this.addRequests(requests,
|
|
812
|
+
await this.addRequests(requests, options);
|
|
842
813
|
}
|
|
843
814
|
try {
|
|
844
815
|
await this.init();
|
|
@@ -884,6 +855,22 @@ export class BasicCrawler {
|
|
|
884
855
|
...finalStats,
|
|
885
856
|
};
|
|
886
857
|
this.log.info('Final request statistics:', stats);
|
|
858
|
+
// A crawl that did nothing while the manager holds only handled requests is a mistake whoever
|
|
859
|
+
// handled them - this run, another crawler on the same queue, or a previous process. Starting
|
|
860
|
+
// against handled requests is not: that is what resuming a crawl looks like.
|
|
861
|
+
if (stats.requestsFinished + stats.requestsFailed === 0) {
|
|
862
|
+
// Never let the diagnostic itself break the run.
|
|
863
|
+
const alreadyHandled = (await this.requestManager?.getHandledCount().catch(() => 0)) ?? 0;
|
|
864
|
+
if (alreadyHandled > 0) {
|
|
865
|
+
this.log.warningOnce('This crawl processed no requests - the request manager holds ' +
|
|
866
|
+
`${alreadyHandled} request${alreadyHandled === 1 ? '' : 's'}, all of them ` +
|
|
867
|
+
'already handled, and a failed request counts as handled too. Nothing ' +
|
|
868
|
+
'empties a queue between runs, so to crawl them again, purge it ' +
|
|
869
|
+
'(`await queue.purge()`) or use a fresh one (e.g. ' +
|
|
870
|
+
'`RequestQueue.open({ alias: "second-run" })`) with a freshly created ' +
|
|
871
|
+
'crawler instance.');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
887
874
|
if (this.statistics.errorTracker.total !== 0) {
|
|
888
875
|
const prettify = ([count, info]) => `${count}x: ${info.at(-1).trim()} (${info[0]})`;
|
|
889
876
|
this.log.info(`Error analysis:`, {
|
|
@@ -906,7 +893,7 @@ export class BasicCrawler {
|
|
|
906
893
|
periodicLogger.stop();
|
|
907
894
|
this.setStatusMessage(`Finished! Total ${this.statistics.state.requestsFinished + this.statistics.state.requestsFailed} requests: ${this.statistics.state.requestsFinished} succeeded, ${this.statistics.state.requestsFailed} failed.`, { isStatusMessageTerminal: true, level: 'INFO' });
|
|
908
895
|
this.running = false;
|
|
909
|
-
this
|
|
896
|
+
this.#hasFinishedBefore = true;
|
|
910
897
|
}
|
|
911
898
|
return stats;
|
|
912
899
|
}));
|
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.159",
|
|
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.159",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.159",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.159",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.159",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
50
|
"tldts": "^7.0.6",
|
|
51
51
|
"tslib": "^2.8.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"zod": "^4.4.3"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@crawlee/impit-client": "4.0.0-beta.
|
|
56
|
+
"@crawlee/impit-client": "4.0.0-beta.159"
|
|
57
57
|
},
|
|
58
58
|
"lerna": {
|
|
59
59
|
"command": {
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "9f91c03967915be4e9b077a717e0e3494ad0a101"
|
|
66
66
|
}
|