@crawlee/basic 4.0.0-beta.91 → 4.0.0-beta.93

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.
@@ -1,5 +1,5 @@
1
- import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, AutoscaledPoolPredicateOptions, ConcurrencySystemOptions, Configuration, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueLinksOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, Request, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticsOptions, StatisticState, StorageIdentifier, TypedRequestsLike } from '@crawlee/core';
2
- import { AutoscaledPool, ConcurrencySystem, ContextPipeline, Dataset, RequestQueue, Statistics } from '@crawlee/core';
1
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions, Configuration, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueLinksOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, Request, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticsOptions, StatisticState, StorageIdentifier, TaskLoopPredicates, TypedRequestsLike } from '@crawlee/core';
2
+ import { ConcurrencySystem, ContextPipeline, Dataset, RequestQueue, Statistics } from '@crawlee/core';
3
3
  import type { Awaitable, BaseHttpClient, BatchAddRequestsResult, Dictionary, ISession, ISessionPool, ProxyInfo, SetStatusMessageOptions, StorageBackend } from '@crawlee/types';
4
4
  import { RobotsTxtFile } from '@crawlee/utils';
5
5
  import type { ReadonlyDeep, SetRequired } from 'type-fest';
@@ -151,13 +151,14 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
151
151
  */
152
152
  maxCrawlDepth?: number;
153
153
  /**
154
- * Lets you override the task-loop predicates (`isFinishedFunction`, `isTaskReadyFunction`) of the crawler's
155
- * underlying {@link AutoscaledPool}. The `runTaskFunction` is owned by the crawler and cannot be overridden.
154
+ * Lets you override the predicates that steer the crawler's task loop: `isTaskReadyFunction` (may another request
155
+ * start?) and `isFinishedFunction` (is the crawl over?). The task itself fetching a request and running it
156
+ * through the pipeline — is owned by the crawler and cannot be overridden.
156
157
  *
157
158
  * Concurrency is configured elsewhere — through the `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute`
158
159
  * shortcuts, or a {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} for finer control.
159
160
  */
160
- autoscaledPoolOptions?: AutoscaledPoolPredicateOptions;
161
+ taskLoopOptions?: TaskLoopPredicates;
161
162
  /**
162
163
  * A pre-configured concurrency governor — the component that decides whether there is free compute for one more
163
164
  * task. Typically a {@link ConcurrencySystem}, though any {@link IConcurrencySystem} is accepted. All
@@ -353,8 +354,8 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
353
354
  *
354
355
  * The crawler finishes if there are no more {@link Request} objects to crawl.
355
356
  *
356
- * New requests are only dispatched when there is enough free CPU and memory available,
357
- * using the functionality provided by the {@link AutoscaledPool} class.
357
+ * New requests are only dispatched when there is enough free CPU and memory available, as judged by the crawler's
358
+ * {@link ConcurrencySystem}.
358
359
  * Concurrency is tuned via the {@link BasicCrawlerOptions.minConcurrency|`minConcurrency`},
359
360
  * {@link BasicCrawlerOptions.maxConcurrency|`maxConcurrency`} and
360
361
  * {@link BasicCrawlerOptions.maxRequestsPerMinute|`maxRequestsPerMinute`} shortcuts, or, for finer control, by
@@ -458,16 +459,26 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
458
459
  /** As resolved by `_init()`. Absent until the first run, so a `teardown()` before it is a no-op. */
459
460
  private concurrencySystemDep?;
460
461
  /**
461
- * A reference to the underlying {@link AutoscaledPool} class that runs the crawler's task loop.
462
- * > *NOTE:* This property is only initialized after calling the {@link BasicCrawler.run|`crawler.run()`} function.
463
- * We can use it to pause the crawler by calling {@link AutoscaledPool.pause|`autoscaledPool.pause()`}
464
- * or to abort it by calling {@link AutoscaledPool.abort|`autoscaledPool.abort()`}.
462
+ * The concurrency governor this run is booking its requests against either the
463
+ * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} that was injected, or the default the
464
+ * crawler built for itself. Read it for telemetry: `desiredConcurrency`, `currentConcurrency`, `isRunning`.
465
465
  *
466
- * The pool only exposes read-only concurrency telemetry. To tune concurrency at runtime, keep a reference to a
467
- * {@link ConcurrencySystem} and inject it via
468
- * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`}.
466
+ * > *NOTE:* `undefined` until {@link BasicCrawler.run|`crawler.run()`} has resolved it. A crawler-owned default
467
+ * is also rebuilt for every run, so the instance is not stable across runs.
468
+ *
469
+ * {@link IConcurrencySystem} is deliberately read-only. Tuning concurrency *while a crawl is running* means
470
+ * owning the instance: build a {@link ConcurrencySystem} yourself and inject it, then set
471
+ * `minConcurrency`/`maxConcurrency`/`desiredConcurrency` on your own reference.
472
+ */
473
+ get concurrencySystem(): IConcurrencySystem | undefined;
474
+ /**
475
+ * The task loop that dispatches this run's requests. Private on purpose — it is a bare parallel task runner with
476
+ * no configuration left of its own (see {@link ConcurrencySystem}), and everything a caller legitimately did
477
+ * with it now has a crawler-level counterpart: {@link BasicCrawler.pause|`pause()`},
478
+ * {@link BasicCrawler.resume|`resume()`}, {@link BasicCrawler.teardown|`teardown()`} and
479
+ * {@link BasicCrawler.concurrencySystem|`concurrencySystem`}.
469
480
  */
470
- autoscaledPool?: AutoscaledPool;
481
+ private autoscaledPool?;
471
482
  /**
472
483
  * A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
473
484
  * Only available if used by the crawler.
@@ -513,12 +524,12 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
513
524
  protected readonly additionalHttpErrorStatusCodes: Set<number>;
514
525
  private ignoreHttpErrorStatusCodes;
515
526
  /**
516
- * The resolved task-loop options for the crawler's own {@link AutoscaledPool} — the crawler-owned
517
- * `runTaskFunction`, the (possibly user-overridden) ready/finished predicates and cadence/logging. Concurrency
518
- * configuration lives on the {@link ConcurrencySystem} instead, and the pool's `consumer` identity is the
519
- * crawler's own, so neither is settable here.
527
+ * The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
528
+ * user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
529
+ * {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
530
+ * settable here.
520
531
  */
521
- private autoscaledPoolOptions;
532
+ private taskLoopOptions;
522
533
  protected readonly httpClient: BaseHttpClient;
523
534
  protected readonly retryOnBlocked: boolean;
524
535
  private respectRobotsTxtFile;
@@ -554,7 +565,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
554
565
  // @ts-ignore optional peer dependency or compatibility with es2022
555
566
  maxCrawlDepth: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
556
567
  // @ts-ignore optional peer dependency or compatibility with es2022
557
- autoscaledPoolOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
568
+ taskLoopOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
558
569
  // @ts-ignore optional peer dependency or compatibility with es2022
559
570
  concurrencySystem: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
560
571
  // @ts-ignore optional peer dependency or compatibility with es2022
@@ -655,8 +666,9 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
655
666
  setStatusMessage(message: string, options?: SetStatusMessageOptions): void;
656
667
  private getPeriodicLogger;
657
668
  /**
658
- * Runs the crawler. Returns a promise that resolves once all the requests are processed
659
- * and `autoscaledPool.isFinished` returns `true`.
669
+ * Runs the crawler. Returns a promise that resolves once every request has been processed and the crawler's
670
+ * finished-check ({@link BasicCrawlerOptions.taskLoopOptions|`taskLoopOptions.isFinishedFunction`}, or the
671
+ * default "the request manager is empty") reports that the crawl is over.
660
672
  *
661
673
  * We can use the `requests` parameter to enqueue the initial requests — it is a shortcut for
662
674
  * running {@link BasicCrawler.addRequests|`crawler.addRequests()`} before {@link BasicCrawler.run|`crawler.run()`}.
@@ -673,6 +685,20 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
673
685
  * To stop the crawler immediately, use {@link BasicCrawler.teardown|`crawler.teardown()`} instead.
674
686
  */
675
687
  stop(reason?: string): void;
688
+ /**
689
+ * Stops dispatching new requests, letting the in-progress ones finish. Resolves once they have settled, or rejects
690
+ * after `timeoutSecs` if they take too long. Unlike {@link BasicCrawler.stop|`stop()`}, this does not end the
691
+ * run — {@link BasicCrawler.run|`run()`} stays pending until {@link BasicCrawler.resume|`resume()`}.
692
+ *
693
+ * > *NOTE:* The {@link BasicCrawler.concurrencySystem|concurrency system} keeps monitoring and autoscaling
694
+ * throughout, since a shared one may still be serving other crawlers.
695
+ */
696
+ pause(timeoutSecs?: number): Promise<void>;
697
+ /**
698
+ * Resumes a run suspended with {@link BasicCrawler.pause|`pause()`}, letting the crawler dispatch requests
699
+ * again. A no-op on a crawler that is not paused.
700
+ */
701
+ resume(): void;
676
702
  /**
677
703
  * Returns the crawler's {@link IRequestManager|request manager}, opening the default {@link RequestQueue}
678
704
  * if none has been configured or opened yet.
@@ -90,14 +90,26 @@ export class BasicCrawler {
90
90
  /** As resolved by `_init()`. Absent until the first run, so a `teardown()` before it is a no-op. */
91
91
  concurrencySystemDep;
92
92
  /**
93
- * A reference to the underlying {@link AutoscaledPool} class that runs the crawler's task loop.
94
- * > *NOTE:* This property is only initialized after calling the {@link BasicCrawler.run|`crawler.run()`} function.
95
- * We can use it to pause the crawler by calling {@link AutoscaledPool.pause|`autoscaledPool.pause()`}
96
- * or to abort it by calling {@link AutoscaledPool.abort|`autoscaledPool.abort()`}.
93
+ * The concurrency governor this run is booking its requests against either the
94
+ * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} that was injected, or the default the
95
+ * crawler built for itself. Read it for telemetry: `desiredConcurrency`, `currentConcurrency`, `isRunning`.
97
96
  *
98
- * The pool only exposes read-only concurrency telemetry. To tune concurrency at runtime, keep a reference to a
99
- * {@link ConcurrencySystem} and inject it via
100
- * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`}.
97
+ * > *NOTE:* `undefined` until {@link BasicCrawler.run|`crawler.run()`} has resolved it. A crawler-owned default
98
+ * is also rebuilt for every run, so the instance is not stable across runs.
99
+ *
100
+ * {@link IConcurrencySystem} is deliberately read-only. Tuning concurrency *while a crawl is running* means
101
+ * owning the instance: build a {@link ConcurrencySystem} yourself and inject it, then set
102
+ * `minConcurrency`/`maxConcurrency`/`desiredConcurrency` on your own reference.
103
+ */
104
+ get concurrencySystem() {
105
+ return this.concurrencySystemDep?.maybeValue;
106
+ }
107
+ /**
108
+ * The task loop that dispatches this run's requests. Private on purpose — it is a bare parallel task runner with
109
+ * no configuration left of its own (see {@link ConcurrencySystem}), and everything a caller legitimately did
110
+ * with it now has a crawler-level counterpart: {@link BasicCrawler.pause|`pause()`},
111
+ * {@link BasicCrawler.resume|`resume()`}, {@link BasicCrawler.teardown|`teardown()`} and
112
+ * {@link BasicCrawler.concurrencySystem|`concurrencySystem`}.
101
113
  */
102
114
  autoscaledPool;
103
115
  /**
@@ -158,12 +170,12 @@ export class BasicCrawler {
158
170
  additionalHttpErrorStatusCodes;
159
171
  ignoreHttpErrorStatusCodes;
160
172
  /**
161
- * The resolved task-loop options for the crawler's own {@link AutoscaledPool} — the crawler-owned
162
- * `runTaskFunction`, the (possibly user-overridden) ready/finished predicates and cadence/logging. Concurrency
163
- * configuration lives on the {@link ConcurrencySystem} instead, and the pool's `consumer` identity is the
164
- * crawler's own, so neither is settable here.
173
+ * The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
174
+ * user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
175
+ * {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
176
+ * settable here.
165
177
  */
166
- autoscaledPoolOptions;
178
+ taskLoopOptions;
167
179
  httpClient;
168
180
  retryOnBlocked;
169
181
  respectRobotsTxtFile;
@@ -189,7 +201,7 @@ export class BasicCrawler {
189
201
  sameDomainDelaySecs: ow.optional.number,
190
202
  maxRequestsPerCrawl: ow.optional.number,
191
203
  maxCrawlDepth: ow.optional.number,
192
- autoscaledPoolOptions: ow.optional.object,
204
+ taskLoopOptions: ow.optional.object,
193
205
  concurrencySystem: ow.optional.object,
194
206
  sessionPool: ow.optional.object.validate(validators.sessionPool),
195
207
  proxyConfiguration: ow.optional.object.validate(validators.proxyConfiguration),
@@ -223,7 +235,7 @@ export class BasicCrawler {
223
235
  // oxlint-disable-next-line typescript/no-deprecated -- still accepted and folded into `requestManager` for back-compat
224
236
  requestList,
225
237
  // oxlint-disable-next-line typescript/no-deprecated -- still accepted and folded into `requestManager` for back-compat
226
- requestQueue, requestManager, maxRequestRetries = 3, sameDomainDelaySecs = 0, maxRequestsPerCrawl, maxCrawlDepth, autoscaledPoolOptions = {}, concurrencySystem, keepAlive, sessionPool, proxyConfiguration, additionalHttpErrorStatusCodes = [], ignoreHttpErrorStatusCodes = [],
238
+ requestQueue, requestManager, maxRequestRetries = 3, sameDomainDelaySecs = 0, maxRequestsPerCrawl, maxCrawlDepth, taskLoopOptions = {}, concurrencySystem, keepAlive, sessionPool, proxyConfiguration, additionalHttpErrorStatusCodes = [], ignoreHttpErrorStatusCodes = [],
227
239
  // Service locator options
228
240
  configuration, storageBackend, eventManager, logger,
229
241
  // AutoscaledPool shorthands
@@ -338,12 +350,12 @@ export class BasicCrawler {
338
350
  this.maxRequestsPerCrawl = maxRequestsPerCrawl;
339
351
  const isMaxPagesExceeded = () => this.maxRequestsPerCrawl && this.maxRequestsPerCrawl <= this.handledRequestsCount;
340
352
  // eslint-disable-next-line prefer-const
341
- let { isFinishedFunction, isTaskReadyFunction } = autoscaledPoolOptions;
353
+ let { isFinishedFunction, isTaskReadyFunction } = taskLoopOptions;
342
354
  // override even if `isFinishedFunction` provided by user - `keepAlive` has higher priority
343
355
  if (keepAlive) {
344
356
  isFinishedFunction = async () => false;
345
357
  }
346
- const basicCrawlerAutoscaledPoolConfiguration = {
358
+ const crawlerOwnedTaskLoopConfiguration = {
347
359
  runTaskFunction: async () => {
348
360
  const source = this.requestManager;
349
361
  if (!source)
@@ -428,7 +440,7 @@ export class BasicCrawler {
428
440
  },
429
441
  log: this.log,
430
442
  };
431
- this.autoscaledPoolOptions = { ...autoscaledPoolOptions, ...basicCrawlerAutoscaledPoolConfiguration };
443
+ this.taskLoopOptions = { ...taskLoopOptions, ...crawlerOwnedTaskLoopConfiguration };
432
444
  this.resolveConcurrencySystem = () => OwnedOrInjected.resolve(concurrencySystem, () => this.createDefaultConcurrencySystem({
433
445
  minConcurrency,
434
446
  maxConcurrency,
@@ -630,7 +642,7 @@ export class BasicCrawler {
630
642
  }
631
643
  else {
632
644
  const total = await this.requestManager?.getTotalCount();
633
- message = `Crawled ${this.stats.state.requestsFinished}${total ? `/${total}` : ''} pages, ${this.stats.state.requestsFailed} failed requests, desired concurrency ${this.autoscaledPool?.desiredConcurrency ?? 0}.`;
645
+ message = `Crawled ${this.stats.state.requestsFinished}${total ? `/${total}` : ''} pages, ${this.stats.state.requestsFailed} failed requests, desired concurrency ${this.concurrencySystem?.desiredConcurrency ?? 0}.`;
634
646
  }
635
647
  if (this.statusMessageCallback) {
636
648
  await this.statusMessageCallback({
@@ -647,8 +659,9 @@ export class BasicCrawler {
647
659
  return { log, stop: () => clearInterval(interval) };
648
660
  }
649
661
  /**
650
- * Runs the crawler. Returns a promise that resolves once all the requests are processed
651
- * and `autoscaledPool.isFinished` returns `true`.
662
+ * Runs the crawler. Returns a promise that resolves once every request has been processed and the crawler's
663
+ * finished-check ({@link BasicCrawlerOptions.taskLoopOptions|`taskLoopOptions.isFinishedFunction`}, or the
664
+ * default "the request manager is empty") reports that the crawl is over.
652
665
  *
653
666
  * We can use the `requests` parameter to enqueue the initial requests — it is a shortcut for
654
667
  * running {@link BasicCrawler.addRequests|`crawler.addRequests()`} before {@link BasicCrawler.run|`crawler.run()`}.
@@ -773,6 +786,32 @@ export class BasicCrawler {
773
786
  this.log.info(reason);
774
787
  this.unexpectedStop = true;
775
788
  }
789
+ /**
790
+ * Stops dispatching new requests, letting the in-progress ones finish. Resolves once they have settled, or rejects
791
+ * after `timeoutSecs` if they take too long. Unlike {@link BasicCrawler.stop|`stop()`}, this does not end the
792
+ * run — {@link BasicCrawler.run|`run()`} stays pending until {@link BasicCrawler.resume|`resume()`}.
793
+ *
794
+ * > *NOTE:* The {@link BasicCrawler.concurrencySystem|concurrency system} keeps monitoring and autoscaling
795
+ * throughout, since a shared one may still be serving other crawlers.
796
+ */
797
+ async pause(timeoutSecs) {
798
+ if (!this.autoscaledPool) {
799
+ this.log.warning('Cannot pause a crawler that is not running.');
800
+ return;
801
+ }
802
+ await this.autoscaledPool.pause(timeoutSecs);
803
+ }
804
+ /**
805
+ * Resumes a run suspended with {@link BasicCrawler.pause|`pause()`}, letting the crawler dispatch requests
806
+ * again. A no-op on a crawler that is not paused.
807
+ */
808
+ resume() {
809
+ if (!this.autoscaledPool) {
810
+ this.log.warning('Cannot resume a crawler that is not running.');
811
+ return;
812
+ }
813
+ this.autoscaledPool.resume();
814
+ }
776
815
  /**
777
816
  * Returns the crawler's {@link IRequestManager|request manager}, opening the default {@link RequestQueue}
778
817
  * if none has been configured or opened yet.
@@ -1035,7 +1074,7 @@ export class BasicCrawler {
1035
1074
  this.concurrencySystemDep = this.resolveConcurrencySystem();
1036
1075
  await this.concurrencySystemDep.ifOwned((system) => system.start());
1037
1076
  this.autoscaledPool = new AutoscaledPool({
1038
- ...this.autoscaledPoolOptions,
1077
+ ...this.taskLoopOptions,
1039
1078
  concurrencySystem: this.concurrencySystemDep.value,
1040
1079
  consumer: this.identity,
1041
1080
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/basic",
3
- "version": "4.0.0-beta.91",
3
+ "version": "4.0.0-beta.93",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -42,10 +42,10 @@
42
42
  "@apify/datastructures": "^2.0.0",
43
43
  "@apify/timeout": "^0.3.2",
44
44
  "@apify/utilities": "^2.15.5",
45
- "@crawlee/core": "4.0.0-beta.91",
46
- "@crawlee/http-client": "4.0.0-beta.91",
47
- "@crawlee/types": "4.0.0-beta.91",
48
- "@crawlee/utils": "4.0.0-beta.91",
45
+ "@crawlee/core": "4.0.0-beta.93",
46
+ "@crawlee/http-client": "4.0.0-beta.93",
47
+ "@crawlee/types": "4.0.0-beta.93",
48
+ "@crawlee/utils": "4.0.0-beta.93",
49
49
  "csv-stringify": "^6.5.2",
50
50
  "fs-extra": "^11.3.0",
51
51
  "ow": "^2.0.0",
@@ -54,7 +54,7 @@
54
54
  "type-fest": "^4.41.0"
55
55
  },
56
56
  "optionalDependencies": {
57
- "@crawlee/impit-client": "^4.0.0-beta.91"
57
+ "@crawlee/impit-client": "^4.0.0-beta.93"
58
58
  },
59
59
  "lerna": {
60
60
  "command": {
@@ -63,5 +63,5 @@
63
63
  }
64
64
  }
65
65
  },
66
- "gitHead": "1e3e1ca10f24be53e1d527b3ea25f456baebecbf"
66
+ "gitHead": "b9d21e80d94d01e21d4e1c19191610d0157dd172"
67
67
  }