@crawlee/basic 4.0.0-beta.143 → 4.0.0-beta.145

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,4 +1,4 @@
1
- import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueUrlsOptions, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, IStatistics, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticState, StorageIdentifier, StorageWritePolicy, TaskLoopPredicates, TypedRequestsLike } from '@crawlee/core';
1
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueUrlsOptions, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, IStatistics, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticState, StorageIdentifier, StorageWritePolicy, TaskLoopOptions, TypedRequestsLike } from '@crawlee/core';
2
2
  import { ConcurrencySystem, Configuration, ContextPipeline, Request, Dataset, EventManager, RequestQueue } from '@crawlee/core';
3
3
  import { BaseHttpClient } from '@crawlee/http-client';
4
4
  import type { Awaitable, Dictionary, ISession, ISessionPool, ProxyInfo, SetStatusMessageOptions, StorageBackend } from '@crawlee/types';
@@ -165,7 +165,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
165
165
  * Concurrency is configured elsewhere — through the `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute`
166
166
  * shortcuts, or a {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} for finer control.
167
167
  */
168
- taskLoopOptions?: TaskLoopPredicates;
168
+ taskLoopOptions?: TaskLoopOptions;
169
169
  /**
170
170
  * A pre-configured concurrency governor — the component that decides whether there is free compute for one more
171
171
  * task. Typically a {@link ConcurrencySystem}, though any {@link IConcurrencySystem} is accepted. All
@@ -176,8 +176,8 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
176
176
  * single budget. Each crawler still builds and drives its own {@link AutoscaledPool}; only the load/scaling
177
177
  * accounting is shared.
178
178
  *
179
- * Mutually exclusive with the `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts, which configure
180
- * the default system this one replaces — combining the two throws.
179
+ * Mutually exclusive with the `minConcurrency`/`maxConcurrency`/`initialConcurrency`/`maxRequestsPerMinute`
180
+ * shortcuts, which configure the default system this one replaces — combining the two throws.
181
181
  *
182
182
  * You own a supplied system's lifecycle: `start()` it before `run()` (which throws otherwise) and `stop()` it once
183
183
  * every crawler borrowing it has finished. The crawler does neither on your behalf.
@@ -197,6 +197,12 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
197
197
  * {@link ConcurrencySystem}.
198
198
  */
199
199
  maxConcurrency?: number;
200
+ /**
201
+ * Sets the concurrency (parallelism) the crawl starts with, before any scaling happens. Shortcut for the
202
+ * {@link ConcurrencySystemOptions.desiredConcurrency|`desiredConcurrency`} option of the crawler's default
203
+ * {@link ConcurrencySystem}. Defaults to `minConcurrency`.
204
+ */
205
+ initialConcurrency?: number;
200
206
  /**
201
207
  * The maximum number of requests per minute the crawler should run.
202
208
  * By default, this is set to `Infinity`, but we can pass any positive, non-zero integer.
@@ -430,18 +436,10 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
430
436
  hasFinishedBefore: boolean;
431
437
  get log(): CrawleeLogger;
432
438
  protected readonly requestHandler: RequestHandler<ExtendedContext>;
433
- private requestHandlerTimeoutMillis;
434
439
  protected readonly internalTimeoutMillis: number;
435
440
  private get handledRequestsCount();
436
441
  protected blockedStatusCodes: Set<number>;
437
442
  protected readonly additionalHttpErrorStatusCodes: Set<number>;
438
- /**
439
- * The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
440
- * user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
441
- * {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
442
- * settable here.
443
- */
444
- private taskLoopOptions;
445
443
  protected readonly httpClient: BaseHttpClient;
446
444
  protected readonly retryOnBlocked: boolean;
447
445
  /**
@@ -486,6 +484,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
486
484
  logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
487
485
  minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
488
486
  maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
487
+ initialConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
489
488
  maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
490
489
  keepAlive: z.ZodOptional<z.ZodBoolean>;
491
490
  statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
@@ -497,7 +496,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
497
496
  constructor(options?: BasicCrawlerOptions<Context, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> & RequireContextPipeline<CrawlingContext, Context>);
498
497
  /**
499
498
  * Builds the crawler-owned default {@link ConcurrencySystem} from the resolved
500
- * `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts. Not called when a
499
+ * `minConcurrency`/`maxConcurrency`/`initialConcurrency`/`maxRequestsPerMinute` shortcuts. Not called when a
501
500
  * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} was injected.
502
501
  *
503
502
  * Subclasses may override this to tune the default system (e.g. {@link HttpCrawler} raises the starting
@@ -7,7 +7,7 @@ import { RobotsTxtFile } from '@crawlee/utils';
7
7
  import { getDomain } from 'tldts';
8
8
  import { z } from 'zod';
9
9
  import { LruCache } from '@apify/datastructures';
10
- import { addTimeoutToPromise, extendTimeout, TimeoutError } from '@apify/timeout';
10
+ import { addTimeoutToPromise, extendTimeout, TimeoutError, tryCancel } from '@apify/timeout';
11
11
  import { cryptoRandomObjectId } from '@apify/utilities';
12
12
  import { extendTimeoutKey, navigationDeadlineKey, raceWithTimeout, timeoutExpiredKey, } from './request-timeout.js';
13
13
  import { createSendRequest } from './send-request.js';
@@ -193,8 +193,7 @@ export class BasicCrawler {
193
193
  requestHandler;
194
194
  #errorHandler;
195
195
  #failedRequestHandler;
196
- // kept as TS-private: tests read it at runtime
197
- requestHandlerTimeoutMillis;
196
+ #requestHandlerTimeoutMillis;
198
197
  internalTimeoutMillis;
199
198
  #maxRequestRetries;
200
199
  #maxCrawlDepth;
@@ -214,8 +213,7 @@ export class BasicCrawler {
214
213
  * {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
215
214
  * settable here.
216
215
  */
217
- // kept as TS-private: tests mutate it at runtime
218
- taskLoopOptions;
216
+ #taskLoopOptions;
219
217
  httpClient;
220
218
  retryOnBlocked;
221
219
  #respectRobotsTxtFile;
@@ -273,6 +271,7 @@ export class BasicCrawler {
273
271
  // AutoscaledPool shorthands
274
272
  minConcurrency: schemas.anyNumber.optional(),
275
273
  maxConcurrency: schemas.anyNumber.optional(),
274
+ initialConcurrency: schemas.anyNumber.optional(),
276
275
  maxRequestsPerMinute: schemas.anyNumber
277
276
  .refine((value) => Number.isInteger(value) || value === Infinity, 'Expected an integer or infinite number')
278
277
  .refine((value) => value >= 1, 'Expected a number greater than or equal to 1')
@@ -295,15 +294,18 @@ export class BasicCrawler {
295
294
  // Service locator options
296
295
  configuration, storageBackend, eventManager, logger,
297
296
  // AutoscaledPool shorthands
298
- minConcurrency, maxConcurrency, maxRequestsPerMinute, blockedStatusCodes: blockedStatusCodesInput, retryOnBlocked, respectRobotsTxtFile, transactionalStorage, onSkippedRequest, requestHandler, requestHandlerTimeoutSecs, errorHandler, failedRequestHandler, statusMessageLoggingInterval, statusMessageCallback, statistics, httpClient, id, } = parsedOptions;
297
+ minConcurrency, maxConcurrency, initialConcurrency, maxRequestsPerMinute, blockedStatusCodes: blockedStatusCodesInput, retryOnBlocked, respectRobotsTxtFile, transactionalStorage, onSkippedRequest, requestHandler, requestHandlerTimeoutSecs, errorHandler, failedRequestHandler, statusMessageLoggingInterval, statusMessageCallback, statistics, httpClient, id, } = parsedOptions;
299
298
  // All concurrency configuration lives on the `ConcurrencySystem`, so the shortcuts have nowhere to go once
300
299
  // one is supplied - and silently dropping a `maxConcurrency` the user asked for is how crawls end up
301
300
  // hammering a site.
302
301
  if (concurrencySystem !== undefined &&
303
- (minConcurrency !== undefined || maxConcurrency !== undefined || maxRequestsPerMinute !== undefined)) {
304
- throw new Error('The `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts cannot be combined with ' +
305
- '`concurrencySystem` - they configure the default `ConcurrencySystem` that a supplied one ' +
306
- 'replaces. Pass them to the `ConcurrencySystem` constructor instead.');
302
+ (minConcurrency !== undefined ||
303
+ maxConcurrency !== undefined ||
304
+ initialConcurrency !== undefined ||
305
+ maxRequestsPerMinute !== undefined)) {
306
+ throw new Error('The `minConcurrency`/`maxConcurrency`/`initialConcurrency`/`maxRequestsPerMinute` shortcuts ' +
307
+ 'cannot be combined with `concurrencySystem` - they configure the default `ConcurrencySystem` ' +
308
+ 'that a supplied one replaces. Pass them to the `ConcurrencySystem` constructor instead.');
307
309
  }
308
310
  // Create per-crawler service locator if custom services were provided.
309
311
  // This wraps every method on the crawler instance so that calls to the global `serviceLocator`
@@ -370,10 +372,10 @@ export class BasicCrawler {
370
372
  this.#failedRequestHandler = failedRequestHandler;
371
373
  this.#errorHandler = errorHandler;
372
374
  if (requestHandlerTimeoutSecs) {
373
- this.requestHandlerTimeoutMillis = requestHandlerTimeoutSecs * 1000;
375
+ this.#requestHandlerTimeoutMillis = requestHandlerTimeoutSecs * 1000;
374
376
  }
375
377
  else {
376
- this.requestHandlerTimeoutMillis = 60_000;
378
+ this.#requestHandlerTimeoutMillis = 60_000;
377
379
  }
378
380
  this.retryOnBlocked = retryOnBlocked;
379
381
  this.#respectRobotsTxtFile = respectRobotsTxtFile;
@@ -385,7 +387,7 @@ export class BasicCrawler {
385
387
  // allow at least 5min for internal timeouts
386
388
  this.internalTimeoutMillis =
387
389
  serviceLocator.getConfiguration().internalTimeoutMillis ??
388
- Math.max(this.requestHandlerTimeoutMillis * 2, 300e3);
390
+ Math.max(this.#requestHandlerTimeoutMillis * 2, 300e3);
389
391
  this.#maxRequestRetries = maxRequestRetries;
390
392
  this.#maxCrawlDepth = maxCrawlDepth;
391
393
  this.#sameDomainDelaySecs = sameDomainDelaySecs;
@@ -412,10 +414,10 @@ export class BasicCrawler {
412
414
  }));
413
415
  this.blockedStatusCodes = new Set(blockedStatusCodesInput ?? BLOCKED_STATUS_CODES);
414
416
  const maxSignedInteger = 2 ** 31 - 1;
415
- if (this.requestHandlerTimeoutMillis > maxSignedInteger) {
416
- this.log.warning(`requestHandlerTimeoutMillis ${this.requestHandlerTimeoutMillis}` +
417
+ if (this.#requestHandlerTimeoutMillis > maxSignedInteger) {
418
+ this.log.warning(`requestHandlerTimeoutMillis ${this.#requestHandlerTimeoutMillis}` +
417
419
  ` does not fit a signed 32-bit integer. Limiting the value to ${maxSignedInteger}`);
418
- this.requestHandlerTimeoutMillis = maxSignedInteger;
420
+ this.#requestHandlerTimeoutMillis = maxSignedInteger;
419
421
  }
420
422
  this.internalTimeoutMillis = Math.min(this.internalTimeoutMillis, maxSignedInteger);
421
423
  this.#maxRequestsPerCrawl = maxRequestsPerCrawl;
@@ -525,11 +527,14 @@ export class BasicCrawler {
525
527
  },
526
528
  log: this.log,
527
529
  };
528
- this.taskLoopOptions = { ...taskLoopOptions, ...crawlerOwnedTaskLoopConfiguration };
530
+ this.#taskLoopOptions = { ...taskLoopOptions, ...crawlerOwnedTaskLoopConfiguration };
529
531
  this.#resolveConcurrencySystem = () => OwnedOrInjected.resolve(concurrencySystem, () => this.createDefaultConcurrencySystem({
530
532
  minConcurrency,
531
533
  maxConcurrency,
532
534
  maxTasksPerMinute: maxRequestsPerMinute,
535
+ // Spread conditionally - an explicit `undefined` would clobber a subclass default, see
536
+ // `HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS`.
537
+ ...(initialConcurrency !== undefined && { desiredConcurrency: initialConcurrency }),
533
538
  log: this.log,
534
539
  }));
535
540
  }
@@ -539,7 +544,7 @@ export class BasicCrawler {
539
544
  }
540
545
  /**
541
546
  * Builds the crawler-owned default {@link ConcurrencySystem} from the resolved
542
- * `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts. Not called when a
547
+ * `minConcurrency`/`maxConcurrency`/`initialConcurrency`/`maxRequestsPerMinute` shortcuts. Not called when a
543
548
  * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} was injected.
544
549
  *
545
550
  * Subclasses may override this to tune the default system (e.g. {@link HttpCrawler} raises the starting
@@ -975,7 +980,7 @@ export class BasicCrawler {
975
980
  // which routes a run will hit, so reserve for the longest one any route asked for. The hint is
976
981
  // raise-only, so erring high here is safe.
977
982
  const maxRouteTimeoutSecs = this.requestHandler.getMaxTimeoutSecs?.() ?? 0;
978
- const handlerTimeoutSecs = Math.max(this.requestHandlerTimeoutMillis / 1000, maxRouteTimeoutSecs);
983
+ const handlerTimeoutSecs = Math.max(this.#requestHandlerTimeoutMillis / 1000, maxRouteTimeoutSecs);
979
984
  await requestManager.setExpectedRequestProcessingTimeSecs?.(Math.max(handlerTimeoutSecs + 5, 60));
980
985
  }
981
986
  /**
@@ -1167,6 +1172,7 @@ export class BasicCrawler {
1167
1172
  * Pushes data to the specified {@link Dataset}, or the default crawler {@link Dataset} by calling {@link Dataset.pushData}.
1168
1173
  */
1169
1174
  async pushData(data, datasetIdentifier) {
1175
+ tryCancel();
1170
1176
  const dataset = await this.getDataset(datasetIdentifier);
1171
1177
  return dataset.pushData(data);
1172
1178
  }
@@ -1256,7 +1262,7 @@ export class BasicCrawler {
1256
1262
  this.#concurrencySystemDep = this.#resolveConcurrencySystem();
1257
1263
  await this.#concurrencySystemDep.ifOwned((system) => system.start());
1258
1264
  this.#autoscaledPool = new AutoscaledPool({
1259
- ...this.taskLoopOptions,
1265
+ ...this.#taskLoopOptions,
1260
1266
  concurrencySystem: this.#concurrencySystemDep.value,
1261
1267
  consumer: this.#identity,
1262
1268
  });
@@ -1289,7 +1295,7 @@ export class BasicCrawler {
1289
1295
  * @param label The request's route label, or `undefined` for the default route / no specific request.
1290
1296
  * @param fallbackMillis Timeout to use when no route overrides it.
1291
1297
  */
1292
- resolveRequestHandlerTimeoutMillis(label, fallbackMillis = this.requestHandlerTimeoutMillis) {
1298
+ resolveRequestHandlerTimeoutMillis(label, fallbackMillis = this.#requestHandlerTimeoutMillis) {
1293
1299
  return this.getRouteTimeoutMillis(label) ?? fallbackMillis;
1294
1300
  }
1295
1301
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/basic",
3
- "version": "4.0.0-beta.143",
3
+ "version": "4.0.0-beta.145",
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.143",
46
- "@crawlee/http-client": "4.0.0-beta.143",
47
- "@crawlee/types": "4.0.0-beta.143",
48
- "@crawlee/utils": "4.0.0-beta.143",
45
+ "@crawlee/core": "4.0.0-beta.145",
46
+ "@crawlee/http-client": "4.0.0-beta.145",
47
+ "@crawlee/types": "4.0.0-beta.145",
48
+ "@crawlee/utils": "4.0.0-beta.145",
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.143"
56
+ "@crawlee/impit-client": "^4.0.0-beta.145"
57
57
  },
58
58
  "lerna": {
59
59
  "command": {
@@ -62,5 +62,5 @@
62
62
  }
63
63
  }
64
64
  },
65
- "gitHead": "d44b4c37b5acd824c2093542f878266feb6214ea"
65
+ "gitHead": "e8b4d50265b9e433ae61c69d16cc1fee00a4d7e9"
66
66
  }