@crawlee/basic 4.0.0-beta.134 → 4.0.0-beta.135

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/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from '@crawlee/core';
2
2
  export * from './internals/basic-crawler.js';
3
- export type { CheerioRoot, CheerioAPI, Cheerio, Element } from '@crawlee/utils/internal';
@@ -367,93 +367,8 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
367
367
  */
368
368
  additionalHttpErrorStatusCodes?: number[];
369
369
  }
370
- /**
371
- * Provides a simple framework for parallel crawling of web pages.
372
- * The URLs to crawl are fed either from a static list of URLs
373
- * or from a dynamic queue of URLs enabling recursive crawling of websites.
374
- *
375
- * `BasicCrawler` is a low-level tool that requires the user to implement the page
376
- * download and data extraction functionality themselves.
377
- * If we want a crawler that already facilitates this functionality,
378
- * we should consider using {@link CheerioCrawler}, {@link PuppeteerCrawler} or {@link PlaywrightCrawler}.
379
- *
380
- * `BasicCrawler` invokes the user-provided {@link BasicCrawlerOptions.requestHandler|`requestHandler`}
381
- * for each {@link Request} object, which represents a single URL to crawl.
382
- * The {@link Request} objects are fed from the {@link IRequestManager|request manager} provided via the
383
- * {@link BasicCrawlerOptions.requestManager|`requestManager`} constructor option (a {@link RequestQueue} is
384
- * itself a request manager). If no `requestManager` is provided, the crawler opens the default {@link RequestQueue}
385
- * either when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called, or if the `requests`
386
- * parameter (representing the initial requests) of the {@link BasicCrawler.run|`crawler.run()`} function is provided.
387
- *
388
- * To read requests from a read-only source such as a {@link RequestList} or {@link SitemapRequestLoader} while
389
- * still being able to enqueue new ones, combine the loader with a queue into a {@link RequestManagerTandem} using
390
- * {@link IRequestLoader.toTandem|`requestLoader.toTandem()`} and pass the result as `requestManager`. The tandem
391
- * first processes URLs from the loader and automatically enqueues them into the queue, ensuring a single URL is not
392
- * crawled multiple times.
393
- *
394
- * > The legacy {@link BasicCrawlerOptions.requestList|`requestList`} and
395
- * > {@link BasicCrawlerOptions.requestQueue|`requestQueue`} options are deprecated. They are still accepted and
396
- * > folded into a single `requestManager` (combined into a tandem when both are given), but new code should use
397
- * > `requestManager` directly.
398
- *
399
- * The crawler finishes if there are no more {@link Request} objects to crawl.
400
- *
401
- * New requests are only dispatched when there is enough free CPU and memory available, as judged by the crawler's
402
- * {@link ConcurrencySystem}.
403
- * Concurrency is tuned via the {@link BasicCrawlerOptions.minConcurrency|`minConcurrency`},
404
- * {@link BasicCrawlerOptions.maxConcurrency|`maxConcurrency`} and
405
- * {@link BasicCrawlerOptions.maxRequestsPerMinute|`maxRequestsPerMinute`} shortcuts, or, for finer control, by
406
- * injecting a pre-configured {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`}.
407
- *
408
- * **Example usage:**
409
- *
410
- * ```javascript
411
- * import { BasicCrawler, Dataset } from 'crawlee';
412
- *
413
- * // Create a crawler instance
414
- * const crawler = new BasicCrawler({
415
- * async requestHandler({ request, sendRequest }) {
416
- * // 'request' contains an instance of the Request class
417
- * // Here we simply fetch the HTML of the page and store it to a dataset
418
- * const { body } = await sendRequest({
419
- * url: request.url,
420
- * method: request.method,
421
- * body: request.payload,
422
- * headers: request.headers,
423
- * });
424
- *
425
- * await Dataset.pushData({
426
- * url: request.url,
427
- * html: body,
428
- * })
429
- * },
430
- * });
431
- *
432
- * // Enqueue the initial requests and run the crawler
433
- * await crawler.run([
434
- * 'http://www.example.com/page-1',
435
- * 'http://www.example.com/page-2',
436
- * ]);
437
- * ```
438
- * @category Crawlers
439
- */
440
- /**
441
- * Identifies a crawler instance for storage aliasing, `useState()` and status-message events.
442
- */
443
- interface CrawlerIdentity {
444
- /**
445
- * 0-based instantiation order across all crawlers in the process.
446
- * Note that the value can be subject to race conditions between different script invocations.
447
- */
448
- readonly instanceIndex: number;
449
- /** The user-supplied `id` option, or a fallback derived from `instanceIndex`. */
450
- readonly id: string;
451
- /** Whether `id` came from the user (as opposed to being derived from `instanceIndex`). */
452
- readonly hasExplicitId: boolean;
453
- }
454
370
  export declare class BasicCrawler<Context extends CrawlingContext = CrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>, StatisticStateExtension extends object = {}> {
455
371
  #private;
456
- protected static readonly CRAWLEE_STATE_KEY = "CRAWLEE_STATE";
457
372
  /**
458
373
  * Tracks the number of crawler instances created. The first crawler uses the default
459
374
  * request queue; subsequent ones get their own queue via a unique alias so they don't
@@ -515,13 +430,8 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
515
430
  hasFinishedBefore: boolean;
516
431
  get log(): CrawleeLogger;
517
432
  protected readonly requestHandler: RequestHandler<ExtendedContext>;
518
- protected readonly errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
519
- protected readonly failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
520
433
  private requestHandlerTimeoutMillis;
521
434
  protected readonly internalTimeoutMillis: number;
522
- protected readonly maxRequestRetries: number;
523
- protected readonly maxCrawlDepth?: number;
524
- protected readonly maxRequestsPerCrawl?: number;
525
435
  private get handledRequestsCount();
526
436
  protected blockedStatusCodes: Set<number>;
527
437
  protected readonly additionalHttpErrorStatusCodes: Set<number>;
@@ -534,8 +444,9 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
534
444
  private taskLoopOptions;
535
445
  protected readonly httpClient: BaseHttpClient;
536
446
  protected readonly retryOnBlocked: boolean;
537
- protected readonly onSkippedRequest?: SkippedRequestCallback;
538
- protected readonly identity: CrawlerIdentity;
447
+ /**
448
+ * @internal
449
+ */
539
450
  protected static optionsShape: {
540
451
  contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
541
452
  extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
@@ -580,50 +491,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
580
491
  statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
581
492
  id: z.ZodOptional<z.ZodString>;
582
493
  };
583
- protected static optionsSchema: z.ZodObject<{
584
- contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
585
- extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
586
- requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
587
- requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
588
- requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
589
- requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
590
- requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
591
- errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
592
- failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
593
- maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
594
- sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
595
- maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
596
- maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
597
- taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
598
- concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
599
- sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
600
- proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
601
- statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
602
- statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
603
- additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
604
- ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
605
- blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
606
- retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
607
- respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
608
- transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
609
- requestQueue: z.ZodOptional<z.ZodEnum<{
610
- deferred: "deferred";
611
- writeThrough: "writeThrough";
612
- }>>;
613
- }, z.core.$strict>]>>;
614
- onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
615
- httpClient: z.ZodOptional<z.ZodCustom<BaseHttpClient, BaseHttpClient>>;
616
- configuration: z.ZodOptional<z.ZodCustom<Configuration, Configuration>>;
617
- storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
618
- eventManager: z.ZodOptional<z.ZodCustom<EventManager, EventManager>>;
619
- logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
620
- minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
621
- maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
622
- maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
623
- keepAlive: z.ZodOptional<z.ZodBoolean>;
624
- statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
625
- id: z.ZodOptional<z.ZodString>;
626
- }, z.core.$strict>;
627
494
  /**
628
495
  * All `BasicCrawler` parameters are passed via an options object.
629
496
  */
@@ -745,9 +612,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
745
612
  */
746
613
  private validateRequestUserData;
747
614
  useState<State extends Dictionary = Dictionary>(defaultValue?: State): Promise<State>;
748
- protected getPendingRequestCountApproximation(): Promise<number>;
749
- protected calculateEnqueuedRequestLimit(explicitLimit?: number): Promise<number | undefined>;
750
- protected handleSkippedRequest(options: Parameters<SkippedRequestCallback>[0]): Promise<void>;
751
615
  private logOncePerRun;
752
616
  /**
753
617
  * Adds requests to the queue in batches. By default, it will resolve after the initial batch is added, and continue
@@ -1,8 +1,8 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { dirname } from 'node:path';
3
- import { applyRequestTransform, AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, buildEnqueueStrategyPatterns, ConcurrencySystem, Configuration, constructUrlPatternObjects, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, createRequestOptions, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, filterRequestOptionsByPatterns, getObjectType, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, OwnedOrInjected, parseArgument, purgeDefaultStorages, RequestHandlerError, parseRetryAfterHeader, RequestThrottledError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, supportsDomainThrottling, Router, schemas, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, ThrottlingRequestManager, validateUserData, validators, withDirectStorageAccess, } from '@crawlee/core';
3
+ import { applyRequestTransform, AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, buildEnqueueStrategyPatterns, ConcurrencySystem, Configuration, constructUrlPatternObjects, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, createRequestOptions, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, filterRequestOptionsByPatterns, getObjectType, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, OwnedOrInjected, purgeDefaultStorages, RequestHandlerError, parseRetryAfterHeader, RequestThrottledError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, supportsDomainThrottling, Router, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, ThrottlingRequestManager, validateUserData, validators, withDirectStorageAccess, } from '@crawlee/core';
4
4
  import { BaseHttpClient, FetchHttpClient } from '@crawlee/http-client';
5
- import { isAsyncIterable, isIterable, ROTATE_PROXY_ERRORS } from '@crawlee/utils/internal';
5
+ import { isAsyncIterable, isIterable, parseArgument, ROTATE_PROXY_ERRORS, schemas } from '@crawlee/utils/internal';
6
6
  import { RobotsTxtFile } from '@crawlee/utils';
7
7
  import { getDomain } from 'tldts';
8
8
  import { z } from 'zod';
@@ -71,7 +71,7 @@ const addRequestsOptionsSchema = z.looseObject({
71
71
  onSkippedRequest: schemas.anyFunction.optional(),
72
72
  });
73
73
  export class BasicCrawler {
74
- static CRAWLEE_STATE_KEY = 'CRAWLEE_STATE';
74
+ static #CRAWLEE_STATE_KEY = 'CRAWLEE_STATE';
75
75
  /**
76
76
  * Tracks the number of crawler instances created. The first crawler uses the default
77
77
  * request queue; subsequent ones get their own queue via a unique alias so they don't
@@ -191,15 +191,15 @@ export class BasicCrawler {
191
191
  return this.#log;
192
192
  }
193
193
  requestHandler;
194
- errorHandler;
195
- failedRequestHandler;
194
+ #errorHandler;
195
+ #failedRequestHandler;
196
196
  // kept as TS-private: tests read it at runtime
197
197
  requestHandlerTimeoutMillis;
198
198
  internalTimeoutMillis;
199
- maxRequestRetries;
200
- maxCrawlDepth;
199
+ #maxRequestRetries;
200
+ #maxCrawlDepth;
201
201
  #sameDomainDelaySecs;
202
- maxRequestsPerCrawl;
202
+ #maxRequestsPerCrawl;
203
203
  get handledRequestsCount() {
204
204
  return this.statistics.state.requestsFinished + this.statistics.state.requestsFailed;
205
205
  }
@@ -223,12 +223,15 @@ export class BasicCrawler {
223
223
  #transactionalStorageEnabled;
224
224
  /** The resolved per-storage-type write policy overrides forwarded to each request's transaction. */
225
225
  #storageWritePolicy;
226
- onSkippedRequest;
226
+ #onSkippedRequest;
227
227
  #closeEvents;
228
228
  #loggedPerRun = new Set();
229
229
  #robotsTxtFileCache;
230
- identity;
230
+ #identity;
231
231
  #contextPipelineOptions;
232
+ /**
233
+ * @internal
234
+ */
232
235
  static optionsShape = {
233
236
  contextPipelineBuilder: schemas.anyObject.optional(),
234
237
  extendContext: schemas.anyFunction.optional(),
@@ -278,12 +281,12 @@ export class BasicCrawler {
278
281
  statistics: schemas.anyObject.optional(),
279
282
  id: z.string().optional(),
280
283
  };
281
- static optionsSchema = z.strictObject(BasicCrawler.optionsShape);
284
+ static #optionsSchema = z.strictObject(BasicCrawler.optionsShape);
282
285
  /**
283
286
  * All `BasicCrawler` parameters are passed via an options object.
284
287
  */
285
288
  constructor(options = {}) {
286
- const parsedOptions = parseArgument(options, BasicCrawler.optionsSchema, 'BasicCrawlerOptions');
289
+ const parsedOptions = parseArgument(options, BasicCrawler.#optionsSchema, 'BasicCrawlerOptions');
287
290
  const {
288
291
  // oxlint-disable-next-line typescript/no-deprecated -- still accepted and folded into `requestManager` for back-compat
289
292
  requestList,
@@ -329,7 +332,7 @@ export class BasicCrawler {
329
332
  // Initialize the Configuration instance to avoid lazy loading in the components
330
333
  serviceLocator.getConfiguration();
331
334
  const instanceIndex = BasicCrawler.instanceCount++;
332
- this.identity = { instanceIndex, hasExplicitId: id !== undefined, id: id ?? String(instanceIndex) };
335
+ this.#identity = { instanceIndex, hasExplicitId: id !== undefined, id: id ?? String(instanceIndex) };
333
336
  if (requestManager !== undefined) {
334
337
  if (requestList !== undefined || requestQueue !== undefined) {
335
338
  throw new Error('The `requestManager` option cannot be used in conjunction with `requestList` and/or `requestQueue`');
@@ -361,12 +364,11 @@ export class BasicCrawler {
361
364
  this.#statusMessageLoggingInterval = statusMessageLoggingInterval;
362
365
  this.#statusMessageCallback = statusMessageCallback;
363
366
  this.#robotsTxtFileCache = new LruCache({ maxLength: 1000 });
364
- this.handleSkippedRequest = this.handleSkippedRequest.bind(this);
365
367
  this.additionalHttpErrorStatusCodes = new Set([...additionalHttpErrorStatusCodes]);
366
368
  this.#ignoreHttpErrorStatusCodes = new Set([...ignoreHttpErrorStatusCodes]);
367
369
  this.requestHandler = requestHandler ?? this.router;
368
- this.failedRequestHandler = failedRequestHandler;
369
- this.errorHandler = errorHandler;
370
+ this.#failedRequestHandler = failedRequestHandler;
371
+ this.#errorHandler = errorHandler;
370
372
  if (requestHandlerTimeoutSecs) {
371
373
  this.requestHandlerTimeoutMillis = requestHandlerTimeoutSecs * 1000;
372
374
  }
@@ -379,13 +381,13 @@ export class BasicCrawler {
379
381
  const transactionalStorageOption = transactionalStorage;
380
382
  this.#transactionalStorageEnabled = transactionalStorageOption !== false;
381
383
  this.#storageWritePolicy = typeof transactionalStorageOption === 'object' ? transactionalStorageOption : {};
382
- this.onSkippedRequest = onSkippedRequest;
384
+ this.#onSkippedRequest = onSkippedRequest;
383
385
  // allow at least 5min for internal timeouts
384
386
  this.internalTimeoutMillis =
385
387
  serviceLocator.getConfiguration().internalTimeoutMillis ??
386
388
  Math.max(this.requestHandlerTimeoutMillis * 2, 300e3);
387
- this.maxRequestRetries = maxRequestRetries;
388
- this.maxCrawlDepth = maxCrawlDepth;
389
+ this.#maxRequestRetries = maxRequestRetries;
390
+ this.#maxCrawlDepth = maxCrawlDepth;
389
391
  this.#sameDomainDelaySecs = sameDomainDelaySecs;
390
392
  this.#statisticsDep = OwnedOrInjected.resolve(statistics,
391
393
  // A crawler-built default tracks the built-in fields only. A non-empty `StatisticStateExtension` can
@@ -394,7 +396,7 @@ export class BasicCrawler {
394
396
  () => new Statistics({
395
397
  logMessage: `${this.constructor.name} request statistics:`,
396
398
  log: this.log,
397
- id: this.identity.id,
399
+ id: this.#identity.id,
398
400
  }));
399
401
  if (sessionPool && proxyConfiguration) {
400
402
  this.log.warning('Both `sessionPool` and `proxyConfiguration` were provided to the crawler. ' +
@@ -416,8 +418,8 @@ export class BasicCrawler {
416
418
  this.requestHandlerTimeoutMillis = maxSignedInteger;
417
419
  }
418
420
  this.internalTimeoutMillis = Math.min(this.internalTimeoutMillis, maxSignedInteger);
419
- this.maxRequestsPerCrawl = maxRequestsPerCrawl;
420
- const isMaxPagesExceeded = () => this.maxRequestsPerCrawl && this.maxRequestsPerCrawl <= this.handledRequestsCount;
421
+ this.#maxRequestsPerCrawl = maxRequestsPerCrawl;
422
+ const isMaxPagesExceeded = () => this.#maxRequestsPerCrawl && this.#maxRequestsPerCrawl <= this.handledRequestsCount;
421
423
  // eslint-disable-next-line prefer-const
422
424
  let { isFinishedFunction, isTaskReadyFunction } = taskLoopOptions;
423
425
  // override even if `isFinishedFunction` provided by user - `keepAlive` has higher priority
@@ -484,7 +486,7 @@ export class BasicCrawler {
484
486
  isTaskReadyFunction: async () => {
485
487
  if (isMaxPagesExceeded()) {
486
488
  this.logOncePerRun('shuttingDown', 'Crawler reached the maxRequestsPerCrawl limit of ' +
487
- `${this.maxRequestsPerCrawl} requests and will shut down soon. Requests that are in progress will be allowed to finish.`);
489
+ `${this.#maxRequestsPerCrawl} requests and will shut down soon. Requests that are in progress will be allowed to finish.`);
488
490
  return false;
489
491
  }
490
492
  if (this.#unexpectedStop) {
@@ -496,7 +498,7 @@ export class BasicCrawler {
496
498
  },
497
499
  isFinishedFunction: async () => {
498
500
  if (isMaxPagesExceeded()) {
499
- this.log.info(`Earlier, the crawler reached the maxRequestsPerCrawl limit of ${this.maxRequestsPerCrawl} requests ` +
501
+ this.log.info(`Earlier, the crawler reached the maxRequestsPerCrawl limit of ${this.#maxRequestsPerCrawl} requests ` +
500
502
  'and all requests that were in progress at that time have now finished. ' +
501
503
  `In total, the crawler processed ${this.handledRequestsCount} requests and will shut down.`);
502
504
  return true;
@@ -573,7 +575,7 @@ export class BasicCrawler {
573
575
  this.log.warning(`Skipping request ${request.url} (${request.id}) because it is disallowed based on robots.txt`);
574
576
  request.state = RequestState.SKIPPED;
575
577
  request.noRetry = true;
576
- await this.handleSkippedRequest({
578
+ await this.#handleSkippedRequest({
577
579
  url: request.url,
578
580
  reason: 'robotsTxt',
579
581
  });
@@ -673,7 +675,7 @@ export class BasicCrawler {
673
675
  this.log.debug(message);
674
676
  request.noRetry = true;
675
677
  request.state = RequestState.SKIPPED;
676
- await this.handleSkippedRequest({ url: request.url, reason: 'redirect' });
678
+ await this.#handleSkippedRequest({ url: request.url, reason: 'redirect' });
677
679
  throw new ContextPipelineInterruptedError(message);
678
680
  }
679
681
  return context;
@@ -707,7 +709,7 @@ export class BasicCrawler {
707
709
  // Setting the status message is not a storage concern, so we intentionally don't route it
708
710
  // through the storage client anymore.
709
711
  serviceLocator.getEventManager().emit(EventType.STATUS_MESSAGE, {
710
- crawlerId: this.identity.id,
712
+ crawlerId: this.#identity.id,
711
713
  message,
712
714
  isStatusMessageTerminal: options.isStatusMessageTerminal,
713
715
  level: options.level,
@@ -930,7 +932,7 @@ export class BasicCrawler {
930
932
  minCrawlDelaySecs: this.#sameDomainDelaySecs,
931
933
  // What `sameDomainDelaySecs` has always meant: one clock for a site, subdomains included.
932
934
  throttleBy: 'registrableDomain',
933
- persistStateKey: `CRAWLEE_THROTTLED_DOMAINS_${this.identity.id}`,
935
+ persistStateKey: `CRAWLEE_THROTTLED_DOMAINS_${this.#identity.id}`,
934
936
  });
935
937
  }
936
938
  // Apply the processing-time hint here (an async lifecycle point) rather than in the constructor,
@@ -957,7 +959,7 @@ export class BasicCrawler {
957
959
  async openOwnedRequestQueue() {
958
960
  // The first crawler instance uses the default queue (null identifier);
959
961
  // subsequent instances get their own queue via a unique alias so they don't collide.
960
- const identifier = this.identity.instanceIndex === 0 ? null : { alias: `__default_${this.identity.id}__` };
962
+ const identifier = this.#identity.instanceIndex === 0 ? null : { alias: `__default_${this.#identity.id}__` };
961
963
  const requestQueue = await RequestQueue.open(identifier, { configuration: serviceLocator.getConfiguration() });
962
964
  return this.#ownedRequestQueue.set(requestQueue);
963
965
  }
@@ -1004,11 +1006,11 @@ export class BasicCrawler {
1004
1006
  }
1005
1007
  async useState(defaultValue = {}) {
1006
1008
  const kvs = await KeyValueStore.open(null, { configuration: serviceLocator.getConfiguration() });
1007
- if (this.identity.hasExplicitId) {
1008
- const stateKey = `${BasicCrawler.CRAWLEE_STATE_KEY}_${this.identity.id}`;
1009
+ if (this.#identity.hasExplicitId) {
1010
+ const stateKey = `${BasicCrawler.#CRAWLEE_STATE_KEY}_${this.#identity.id}`;
1009
1011
  return kvs.getAutoSavedValue(stateKey, defaultValue);
1010
1012
  }
1011
- BasicCrawler.#useStateAnonymousIndices.add(this.identity.instanceIndex);
1013
+ BasicCrawler.#useStateAnonymousIndices.add(this.#identity.instanceIndex);
1012
1014
  if (BasicCrawler.#useStateAnonymousIndices.size > 1) {
1013
1015
  serviceLocator
1014
1016
  .getLogger()
@@ -1017,30 +1019,30 @@ export class BasicCrawler {
1017
1019
  'To fix this, provide a unique `id` option to each crawler instance. \n' +
1018
1020
  'Example: new BasicCrawler({ id: "my-crawler-1", ... })');
1019
1021
  }
1020
- return kvs.getAutoSavedValue(BasicCrawler.CRAWLEE_STATE_KEY, defaultValue);
1022
+ return kvs.getAutoSavedValue(BasicCrawler.#CRAWLEE_STATE_KEY, defaultValue);
1021
1023
  }
1022
- async getPendingRequestCountApproximation() {
1024
+ async #getPendingRequestCountApproximation() {
1023
1025
  return (await this.requestManager?.getPendingCount()) ?? 0;
1024
1026
  }
1025
- async calculateEnqueuedRequestLimit(explicitLimit) {
1026
- if (this.maxRequestsPerCrawl === undefined) {
1027
+ async #calculateEnqueuedRequestLimit(explicitLimit) {
1028
+ if (this.#maxRequestsPerCrawl === undefined) {
1027
1029
  return explicitLimit;
1028
1030
  }
1029
- const limit = Math.max(0, this.maxRequestsPerCrawl - this.handledRequestsCount - (await this.getPendingRequestCountApproximation()));
1031
+ const limit = Math.max(0, this.#maxRequestsPerCrawl - this.handledRequestsCount - (await this.#getPendingRequestCountApproximation()));
1030
1032
  return Math.min(limit, explicitLimit ?? Infinity);
1031
1033
  }
1032
- async handleSkippedRequest(options) {
1034
+ async #handleSkippedRequest(options) {
1033
1035
  // A skipped request is a *successful* outcome, but the interrupt still unwinds through the
1034
1036
  // transaction scope, which rolls back - so the skip bookkeeping must write directly.
1035
1037
  await withDirectStorageAccess(async () => {
1036
1038
  if (options.reason === 'limit') {
1037
1039
  this.logOncePerRun('maxRequestsPerCrawl', 'The number of requests enqueued by the crawler reached the maxRequestsPerCrawl limit of ' +
1038
- `${this.maxRequestsPerCrawl} requests and no further requests will be added.`);
1040
+ `${this.#maxRequestsPerCrawl} requests and no further requests will be added.`);
1039
1041
  }
1040
1042
  if (options.reason === 'depth') {
1041
- this.logOncePerRun('maxCrawlDepth', `The crawler reached the maxCrawlDepth limit of ${this.maxCrawlDepth} and no further requests will be enqueued.`);
1043
+ this.logOncePerRun('maxCrawlDepth', `The crawler reached the maxCrawlDepth limit of ${this.#maxCrawlDepth} and no further requests will be enqueued.`);
1042
1044
  }
1043
- await this.onSkippedRequest?.(options);
1045
+ await this.#onSkippedRequest?.(options);
1044
1046
  });
1045
1047
  }
1046
1048
  logOncePerRun(key, message, level = 'info') {
@@ -1079,7 +1081,7 @@ export class BasicCrawler {
1079
1081
  if (options.label !== undefined || options.userData !== undefined) {
1080
1082
  await this.validateRequestUserData({ label: options.label, userData: options.userData });
1081
1083
  }
1082
- const requestLimit = await this.calculateEnqueuedRequestLimit(options.limit);
1084
+ const requestLimit = await this.#calculateEnqueuedRequestLimit(options.limit);
1083
1085
  const strategy = options.strategy ?? EnqueueStrategy.All;
1084
1086
  const urlExcludePatternObjects = options.exclude?.length
1085
1087
  ? constructUrlPatternObjects(options.exclude)
@@ -1093,7 +1095,7 @@ export class BasicCrawler {
1093
1095
  ? buildEnqueueStrategyPatterns(options.baseUrl, strategy)
1094
1096
  : [];
1095
1097
  const isAllowedBasedOnRobotsTxtFile = this.isAllowedBasedOnRobotsTxtFile.bind(this);
1096
- const maxCrawlDepth = this.maxCrawlDepth;
1098
+ const maxCrawlDepth = this.#maxCrawlDepth;
1097
1099
  const validateRequestUserData = this.validateRequestUserData.bind(this);
1098
1100
  const allSkipped = [];
1099
1101
  async function* filteredRequests() {
@@ -1155,7 +1157,7 @@ export class BasicCrawler {
1155
1157
  : `Skipping requests in this call due to the remaining maxRequestsPerCrawl budget of ${requestLimit}, which is lower than the enqueueLinks limit of ${options.limit}.`);
1156
1158
  }
1157
1159
  await Promise.all(allSkipped.map(async ({ url, reason }) => {
1158
- await this.handleSkippedRequest({ url, reason });
1160
+ await this.#handleSkippedRequest({ url, reason });
1159
1161
  await options.onSkippedRequest?.({ url, reason });
1160
1162
  }));
1161
1163
  }
@@ -1256,7 +1258,7 @@ export class BasicCrawler {
1256
1258
  this.#autoscaledPool = new AutoscaledPool({
1257
1259
  ...this.taskLoopOptions,
1258
1260
  concurrencySystem: this.#concurrencySystemDep.value,
1259
- consumer: this.identity,
1261
+ consumer: this.#identity,
1260
1262
  });
1261
1263
  await this.getRequestManager();
1262
1264
  }
@@ -1611,7 +1613,7 @@ export class BasicCrawler {
1611
1613
  const shouldRetryRequest = this.canRequestBeRetried(request, error);
1612
1614
  if (shouldRetryRequest) {
1613
1615
  await this.statistics.errorTrackerRetry.addAsync(error, crawlingContext);
1614
- await this.errorHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
1616
+ await this.#errorHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
1615
1617
  error);
1616
1618
  if (error instanceof SessionError) {
1617
1619
  crawlingContext.session?.retire();
@@ -1656,8 +1658,8 @@ export class BasicCrawler {
1656
1658
  const { id, url, method, uniqueKey } = crawlingContext.request;
1657
1659
  const message = this.getMessageFromError(error, true);
1658
1660
  this.log.error(`Request failed and reached maximum retries. ${message}`, { id, url, method, uniqueKey });
1659
- if (this.failedRequestHandler) {
1660
- await this.failedRequestHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
1661
+ if (this.#failedRequestHandler) {
1662
+ await this.#failedRequestHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
1661
1663
  error);
1662
1664
  }
1663
1665
  }
@@ -1697,7 +1699,7 @@ export class BasicCrawler {
1697
1699
  return true;
1698
1700
  }
1699
1701
  // Ensure there are more retries available for the request
1700
- const maxRequestRetries = request.maxRetries ?? this.maxRequestRetries;
1702
+ const maxRequestRetries = request.maxRetries ?? this.#maxRequestRetries;
1701
1703
  return request.retryCount < maxRequestRetries;
1702
1704
  }
1703
1705
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/basic",
3
- "version": "4.0.0-beta.134",
3
+ "version": "4.0.0-beta.135",
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.134",
46
- "@crawlee/http-client": "4.0.0-beta.134",
47
- "@crawlee/types": "4.0.0-beta.134",
48
- "@crawlee/utils": "4.0.0-beta.134",
45
+ "@crawlee/core": "4.0.0-beta.135",
46
+ "@crawlee/http-client": "4.0.0-beta.135",
47
+ "@crawlee/types": "4.0.0-beta.135",
48
+ "@crawlee/utils": "4.0.0-beta.135",
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.134"
56
+ "@crawlee/impit-client": "^4.0.0-beta.135"
57
57
  },
58
58
  "lerna": {
59
59
  "command": {
@@ -62,5 +62,5 @@
62
62
  }
63
63
  }
64
64
  },
65
- "gitHead": "0f269d5b24ec2829f3d44c19a35973fa20d95999"
65
+ "gitHead": "ca0325880bbcc753d97506796e9bda3b35bca201"
66
66
  }