@crawlee/basic 4.0.0-beta.80 → 4.0.0-beta.82

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, AutoscaledPoolOptions, Configuration, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueLinksOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IRequestLoader, IRequestManager, ProxyConfiguration, Request, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticsOptions, StatisticState, StorageIdentifier } from '@crawlee/core';
1
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, AutoscaledPoolOptions, Configuration, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueLinksOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IProxyConfiguration, IRequestLoader, IRequestManager, Request, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticsOptions, StatisticState, StorageIdentifier } from '@crawlee/core';
2
2
  import { AutoscaledPool, 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';
@@ -46,7 +46,13 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
46
46
  */
47
47
  requestHandler?: RequestHandler<ExtendedContext>;
48
48
  /**
49
- * Allows the user to extend the crawling context passed to the request handler with custom functionality.
49
+ * Allows the user to extend the crawling context with custom functionality (helpers, references, etc.).
50
+ *
51
+ * `extendContext` runs *before* navigation, so the returned members are visible to the
52
+ * `preNavigationHooks`, `postNavigationHooks`, and the `requestHandler` alike. As a consequence,
53
+ * the `context` passed to `extendContext` is the pre-navigation {@link CrawlingContext} and does
54
+ * **not** include navigation-dependent members (e.g. `page`, `response`, `$`, `body`). If you need
55
+ * those, use a `postNavigationHook` or the `requestHandler` instead.
50
56
  *
51
57
  * **Example usage:**
52
58
  *
@@ -66,7 +72,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
66
72
  * });
67
73
  * ```
68
74
  */
69
- extendContext?: (context: Context) => Awaitable<ContextExtension>;
75
+ extendContext?: (context: CrawlingContext) => Awaitable<ContextExtension>;
70
76
  /**
71
77
  * *Intended for BasicCrawler subclasses*. Prepares a context pipeline that transforms the initial crawling context into the shape given by the `Context` type parameter.
72
78
  *
@@ -252,7 +258,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
252
258
  * If set, the crawler will be configured for all connections to use
253
259
  * the Proxy URLs provided and rotated according to the configuration.
254
260
  */
255
- proxyConfiguration?: ProxyConfiguration;
261
+ proxyConfiguration?: IProxyConfiguration;
256
262
  /**
257
263
  * Custom configuration to use for this crawler.
258
264
  * If provided, the crawler will use its own ServiceLocator instance instead of the global one.
@@ -409,7 +415,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
409
415
  * A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
410
416
  * {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
411
417
  */
412
- sessionPool: ISessionPool;
418
+ readonly sessionPool: ISessionPool;
413
419
  /**
414
420
  * Set when the crawler constructed its own {@link SessionPool} (no `sessionPool` option was provided).
415
421
  * Holds the same instance as `sessionPool`, but typed as the concrete class so the crawler can call
@@ -438,10 +444,10 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
438
444
  */
439
445
  autoscaledPool?: AutoscaledPool;
440
446
  /**
441
- * A reference to the underlying {@link ProxyConfiguration} class that manages the crawler's proxies.
447
+ * A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
442
448
  * Only available if used by the crawler.
443
449
  */
444
- proxyConfiguration?: ProxyConfiguration;
450
+ readonly proxyConfiguration?: IProxyConfiguration;
445
451
  /**
446
452
  * Default {@link Router} instance that will be used if we don't specify any {@link BasicCrawlerOptions.requestHandler|`requestHandler`}.
447
453
  * See {@link Router.addHandler|`router.addHandler()`} and {@link Router.addDefaultHandler|`router.addDefaultHandler()`}.
@@ -463,33 +469,29 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
463
469
  get contextPipeline(): ContextPipeline<CrawlingContext, ExtendedContext>;
464
470
  running: boolean;
465
471
  hasFinishedBefore: boolean;
466
- protected unexpectedStop: boolean;
472
+ private unexpectedStop;
467
473
  get log(): CrawleeLogger;
468
- protected requestHandler: RequestHandler<ExtendedContext>;
469
- protected errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
470
- protected failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
471
- protected requestHandlerTimeoutMillis: number;
472
- protected internalTimeoutMillis: number;
473
- protected maxRequestRetries: number;
474
- protected maxCrawlDepth?: number;
475
- protected sameDomainDelayMillis: number;
476
- protected domainAccessedTime: Map<string, number>;
477
- protected maxRequestsPerCrawl?: number;
478
- protected get handledRequestsCount(): number;
479
- /** @deprecated Setting `handledRequestsCount` directly is no longer supported. The count is now derived from `this.stats`. */
480
- protected set handledRequestsCount(_value: number);
481
- protected statusMessageLoggingInterval: number;
482
- protected statusMessageCallback?: StatusMessageCallback;
474
+ protected readonly requestHandler: RequestHandler<ExtendedContext>;
475
+ protected readonly errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
476
+ protected readonly failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
477
+ private requestHandlerTimeoutMillis;
478
+ protected readonly internalTimeoutMillis: number;
479
+ protected readonly maxRequestRetries: number;
480
+ protected readonly maxCrawlDepth?: number;
481
+ private sameDomainDelayMillis;
482
+ private domainAccessedTime;
483
+ protected readonly maxRequestsPerCrawl?: number;
484
+ private get handledRequestsCount();
485
+ private statusMessageLoggingInterval;
486
+ private statusMessageCallback?;
483
487
  protected blockedStatusCodes: Set<number>;
484
- protected additionalHttpErrorStatusCodes: Set<number>;
485
- protected ignoreHttpErrorStatusCodes: Set<number>;
486
- protected autoscaledPoolOptions: AutoscaledPoolOptions;
487
- protected httpClient: BaseHttpClient;
488
- protected retryOnBlocked: boolean;
489
- protected respectRobotsTxtFile: boolean | {
490
- userAgent?: string;
491
- };
492
- protected onSkippedRequest?: SkippedRequestCallback;
488
+ protected readonly additionalHttpErrorStatusCodes: Set<number>;
489
+ private ignoreHttpErrorStatusCodes;
490
+ private autoscaledPoolOptions;
491
+ protected readonly httpClient: BaseHttpClient;
492
+ protected readonly retryOnBlocked: boolean;
493
+ private respectRobotsTxtFile;
494
+ protected readonly onSkippedRequest?: SkippedRequestCallback;
493
495
  private _closeEvents?;
494
496
  private loggedPerRun;
495
497
  private readonly robotsTxtFileCache;
@@ -580,9 +582,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
580
582
  * Builds the basic context pipeline that transforms `{ request }` into a full `CrawlingContext`.
581
583
  * This handles base context creation, session resolution, and context helpers.
582
584
  */
583
- protected buildBasicContextPipeline(): ContextPipeline<{
584
- request: Request;
585
- }, CrawlingContext>;
585
+ private buildBasicContextPipeline;
586
586
  private checkRobotsTxt;
587
587
  /**
588
588
  * Builds the subclass-specific context pipeline that transforms a `CrawlingContext` into the crawler's target context type.
@@ -662,7 +662,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
662
662
  * the request's label. Applied by the crawler on the add paths it owns — `crawler.addRequests`,
663
663
  * `crawler.run`, `context.addRequests` and `context.enqueueLinks`.
664
664
  */
665
- protected validateRequestUserData(source: Source | string): Promise<void>;
665
+ private validateRequestUserData;
666
666
  useState<State extends Dictionary = Dictionary>(defaultValue?: State): Promise<State>;
667
667
  protected getPendingRequestCountApproximation(): Promise<number>;
668
668
  protected calculateEnqueuedRequestLimit(explicitLimit?: number): Promise<number | undefined>;
@@ -708,19 +708,19 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
708
708
  protected _throwOnBlockedRequest(statusCode: number): void;
709
709
  private isAllowedBasedOnRobotsTxtFile;
710
710
  protected getRobotsTxtFileForUrl(url: string): Promise<RobotsTxtFile | undefined>;
711
- protected _pauseOnMigration(): Promise<void>;
711
+ private pauseOnMigration;
712
712
  /**
713
713
  * Fetches the next request to process from the underlying request provider.
714
714
  */
715
- protected _fetchNextRequest(): Promise<Request<Dictionary> | null>;
715
+ private fetchNextRequest;
716
716
  /**
717
717
  * Delays processing of the request based on the `sameDomainDelaySecs` option,
718
718
  * adding it back to the queue after the timeout passes. Returns `true` if the request
719
719
  * should be ignored and will be reclaimed to the queue once ready.
720
720
  */
721
- protected delayRequest(request: Request, source: IRequestManager): boolean;
721
+ private delayRequest;
722
722
  /** Handles a single request - runs the request handler with retries, error handling, and lifecycle management. */
723
- protected handleRequest(crawlingContext: ExtendedContext, requestSource: IRequestManager, request: Request): Promise<void>;
723
+ private handleRequest;
724
724
  /**
725
725
  * Wrapper around the crawling context's `enqueueLinks` method:
726
726
  * - Injects `crawlDepth` to each request being added based on the crawling context request.
@@ -738,15 +738,15 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
738
738
  * Run async callback with given timeout and retry. Returns the result of the callback.
739
739
  * @ignore
740
740
  */
741
- protected _timeoutAndRetry<T>(handler: () => Promise<T>, timeout: number, error: Error | string, maxRetries?: number, retried?: number): Promise<T>;
741
+ private timeoutAndRetry;
742
742
  /**
743
743
  * Returns true if either RequestList or RequestQueue have a request ready for processing.
744
744
  */
745
- protected _isTaskReadyFunction(): Promise<boolean>;
745
+ private isTaskReadyFunction;
746
746
  /**
747
747
  * Returns true if both RequestList and RequestQueue have all requests finished.
748
748
  */
749
- protected _defaultIsFinishedFunction(): Promise<boolean>;
749
+ private defaultIsFinishedFunction;
750
750
  /**
751
751
  * Unwraps errors thrown by the context pipeline to get the actual user error.
752
752
  * RequestHandlerError and ContextPipelineInitializationError wrap the actual error.
@@ -757,16 +757,15 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
757
757
  *
758
758
  * @param request The request object, passed separately to circumvent potential dynamic logic in crawlingContext.request
759
759
  */
760
- protected _requestFunctionErrorHandler(error: Error, crawlingContext: CrawlingContext, request: Request, source: IRequestManager): Promise<void>;
761
- protected _tagUserHandlerError<T>(cb: () => unknown): Promise<T>;
762
- protected _handleFailedRequestHandler(crawlingContext: CrawlingContext, error: Error): Promise<void>;
760
+ private requestFunctionErrorHandler;
761
+ private handleFailedRequestHandler;
763
762
  /**
764
763
  * Resolves the most verbose error message from a thrown error
765
764
  * @param error The error received
766
765
  * @returns The message to be logged
767
766
  */
768
767
  protected _getMessageFromError(error: Error, forceStack?: boolean): string | TimeoutError | undefined;
769
- protected _canRequestBeRetried(request: Request, error: Error): boolean;
768
+ private canRequestBeRetried;
770
769
  /**
771
770
  * Stops the crawler immediately.
772
771
  *
@@ -93,7 +93,7 @@ export class BasicCrawler {
93
93
  */
94
94
  autoscaledPool;
95
95
  /**
96
- * A reference to the underlying {@link ProxyConfiguration} class that manages the crawler's proxies.
96
+ * A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
97
97
  * Only available if used by the crawler.
98
98
  */
99
99
  proxyConfiguration;
@@ -144,11 +144,6 @@ export class BasicCrawler {
144
144
  get handledRequestsCount() {
145
145
  return this.stats.state.requestsFinished + this.stats.state.requestsFailed;
146
146
  }
147
- /** @deprecated Setting `handledRequestsCount` directly is no longer supported. The count is now derived from `this.stats`. */
148
- set handledRequestsCount(_value) {
149
- throw new Error('Setting `handledRequestsCount` directly is no longer supported. ' +
150
- 'The count is now derived from `this.stats.state.requestsFinished` and `this.stats.state.requestsFailed`.');
151
- }
152
147
  statusMessageLoggingInterval;
153
148
  statusMessageCallback;
154
149
  blockedStatusCodes = new Set();
@@ -357,7 +352,7 @@ export class BasicCrawler {
357
352
  // (e.g., doesn't match enqueue strategy after redirect). Just return gracefully.
358
353
  if (error instanceof ContextPipelineInterruptedError) {
359
354
  this.stats.discardJob(request.id || request.uniqueKey);
360
- await this._timeoutAndRetry(async () => this.requestManager?.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${crawlingContext.request.url} (${crawlingContext.request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
355
+ await this.timeoutAndRetry(async () => this.requestManager?.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${crawlingContext.request.url} (${crawlingContext.request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
361
356
  return;
362
357
  }
363
358
  // If the error happened during pipeline initialization (e.g., navigation timeout, session/proxy error,
@@ -365,8 +360,8 @@ export class BasicCrawler {
365
360
  const isPipelineError = error instanceof ContextPipelineInitializationError || error instanceof SessionError;
366
361
  if (isPipelineError) {
367
362
  const unwrappedError = this.unwrapError(error);
368
- await this._requestFunctionErrorHandler(unwrappedError, crawlingContext, request, this.requestManager);
369
- // SessionError already retired the session in `_requestFunctionErrorHandler`;
363
+ await this.requestFunctionErrorHandler(unwrappedError, crawlingContext, request, this.requestManager);
364
+ // SessionError already retired the session in `requestFunctionErrorHandler`;
370
365
  // skip `markBad` to avoid double-counting usage/error score.
371
366
  if (!(unwrappedError instanceof SessionError)) {
372
367
  crawlingContext.session?.markBad();
@@ -392,7 +387,7 @@ export class BasicCrawler {
392
387
  'Ongoing requests will be allowed to complete.');
393
388
  return false;
394
389
  }
395
- return isTaskReadyFunction ? await isTaskReadyFunction() : await this._isTaskReadyFunction();
390
+ return isTaskReadyFunction ? await isTaskReadyFunction() : await this.isTaskReadyFunction();
396
391
  },
397
392
  isFinishedFunction: async () => {
398
393
  if (isMaxPagesExceeded()) {
@@ -407,7 +402,7 @@ export class BasicCrawler {
407
402
  }
408
403
  const isFinished = isFinishedFunction
409
404
  ? await isFinishedFunction()
410
- : await this._defaultIsFinishedFunction();
405
+ : await this.defaultIsFinishedFunction();
411
406
  if (isFinished) {
412
407
  const reason = isFinishedFunction
413
408
  ? "Crawler's custom isFinishedFunction() returned true, the crawler will shut down."
@@ -481,7 +476,7 @@ export class BasicCrawler {
481
476
  };
482
477
  }
483
478
  async resolveRequest() {
484
- const request = await this._timeoutAndRetry(this._fetchNextRequest.bind(this), this.internalTimeoutMillis, `Fetching next request timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
479
+ const request = await this.timeoutAndRetry(this.fetchNextRequest.bind(this), this.internalTimeoutMillis, `Fetching next request timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
485
480
  // Reset loadedUrl so an old one is not carried over to retries.
486
481
  if (request) {
487
482
  request.loadedUrl = undefined;
@@ -489,7 +484,7 @@ export class BasicCrawler {
489
484
  return request;
490
485
  }
491
486
  async resolveSession({ request }) {
492
- const session = await this._timeoutAndRetry(async () => {
487
+ const session = await this.timeoutAndRetry(async () => {
493
488
  const existingSession = await this.sessionPool.getSession(request.sessionId);
494
489
  if (!existingSession) {
495
490
  throw new ContextPipelineInitializationError(new MissingSessionError(request.sessionId));
@@ -512,13 +507,27 @@ export class BasicCrawler {
512
507
  return { enqueueLinks: enqueueLinksWrapper, addRequests, sendRequest };
513
508
  }
514
509
  buildFinalContextPipeline() {
515
- let contextPipeline = (this.contextPipelineOptions.contextPipelineBuilder?.() ??
510
+ const subclassPipeline = (this.contextPipelineOptions.contextPipelineBuilder?.() ??
516
511
  this.buildContextPipeline());
512
+ // `extendContext` runs *before* the subclass navigation pipeline (which includes the
513
+ // pre/post-navigation hooks). This makes the extension visible to those hooks and to the
514
+ // request handler alike. The trade-off is that `extendContext` cannot access
515
+ // navigation-dependent context members (e.g. `page`, `response`, `$`, `body`), as those
516
+ // don't exist yet at this point in the pipeline.
517
+ // The `extendContext` output (`ContextExtension`) is carried through the subclass pipeline at
518
+ // runtime (the pipeline copies each middleware's returned members onto the shared context), but
519
+ // TypeScript cannot express that `Context` transitively includes `ContextExtension` here. The
520
+ // casts below are sound because `buildFinalContextPipeline` is declared to return the fully
521
+ // resolved `ExtendedContext` (= `Context & ContextExtension`).
517
522
  const { extendContext } = this.contextPipelineOptions;
523
+ let contextPipeline;
518
524
  if (extendContext !== undefined) {
519
- contextPipeline = contextPipeline.compose({
520
- action: async (context) => await extendContext(context),
521
- });
525
+ contextPipeline = ContextPipeline.create()
526
+ .compose({ action: async (context) => await extendContext(context) })
527
+ .chain(subclassPipeline);
528
+ }
529
+ else {
530
+ contextPipeline = subclassPipeline;
522
531
  }
523
532
  contextPipeline = contextPipeline.compose({
524
533
  action: async (context) => {
@@ -654,11 +663,11 @@ export class BasicCrawler {
654
663
  this.setStatusMessage('Starting the crawler.', { level: 'INFO' });
655
664
  const sigintHandler = async () => {
656
665
  this.log.warning('Pausing... Press CTRL+C again to force exit. To resume, do: CRAWLEE_PURGE_ON_START=0 npm start');
657
- await this._pauseOnMigration();
666
+ await this.pauseOnMigration();
658
667
  await this.autoscaledPool.abort();
659
668
  };
660
669
  // Attach a listener to handle migration and aborting events gracefully.
661
- const boundPauseOnMigration = this._pauseOnMigration.bind(this);
670
+ const boundPauseOnMigration = this.pauseOnMigration.bind(this);
662
671
  process.once('SIGINT', sigintHandler);
663
672
  const eventManager = serviceLocator.getEventManager();
664
673
  eventManager.on("migrating" /* EventType.MIGRATING */, boundPauseOnMigration);
@@ -1021,7 +1030,7 @@ export class BasicCrawler {
1021
1030
  return undefined;
1022
1031
  }
1023
1032
  }
1024
- async _pauseOnMigration() {
1033
+ async pauseOnMigration() {
1025
1034
  if (this.autoscaledPool) {
1026
1035
  // if run wasn't called, this is going to crash
1027
1036
  await this.autoscaledPool.pause(SAFE_MIGRATION_WAIT_MILLIS).catch((err) => {
@@ -1058,9 +1067,9 @@ export class BasicCrawler {
1058
1067
  /**
1059
1068
  * Fetches the next request to process from the underlying request provider.
1060
1069
  */
1061
- async _fetchNextRequest() {
1070
+ async fetchNextRequest() {
1062
1071
  if (this.requestManager === undefined) {
1063
- throw new Error(`_fetchNextRequest called on an uninitialized crawler`);
1072
+ throw new Error(`fetchNextRequest called on an uninitialized crawler`);
1064
1073
  }
1065
1074
  return this.requestManager.fetchNextRequest();
1066
1075
  }
@@ -1095,7 +1104,7 @@ export class BasicCrawler {
1095
1104
  try {
1096
1105
  request.state = RequestState.REQUEST_HANDLER;
1097
1106
  await this.runRequestHandler(crawlingContext);
1098
- await this._timeoutAndRetry(async () => requestSource.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${request.url} (${request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
1107
+ await this.timeoutAndRetry(async () => requestSource.markRequestAsHandled(request), this.internalTimeoutMillis, `Marking request ${request.url} (${request.id}) as handled timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
1099
1108
  isRequestLocked = false; // markRequestAsHandled succeeded and unlocked the request
1100
1109
  this.stats.finishJob(statisticsId, request.retryCount);
1101
1110
  // reclaim session if request finishes successfully
@@ -1106,9 +1115,9 @@ export class BasicCrawler {
1106
1115
  const err = this.unwrapError(rawError);
1107
1116
  try {
1108
1117
  request.state = RequestState.ERROR_HANDLER;
1109
- await addTimeoutToPromise(async () => this._requestFunctionErrorHandler(err, crawlingContext, request, requestSource), this.internalTimeoutMillis, `Handling request failure of ${request.url} (${request.id}) timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
1118
+ await addTimeoutToPromise(async () => this.requestFunctionErrorHandler(err, crawlingContext, request, requestSource), this.internalTimeoutMillis, `Handling request failure of ${request.url} (${request.id}) timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
1110
1119
  if (!(err instanceof CriticalError)) {
1111
- isRequestLocked = false; // _requestFunctionErrorHandler calls either markRequestAsHandled or reclaimRequest
1120
+ isRequestLocked = false; // requestFunctionErrorHandler calls either markRequestAsHandled or reclaimRequest
1112
1121
  }
1113
1122
  request.state = RequestState.DONE;
1114
1123
  }
@@ -1211,7 +1220,7 @@ export class BasicCrawler {
1211
1220
  * Run async callback with given timeout and retry. Returns the result of the callback.
1212
1221
  * @ignore
1213
1222
  */
1214
- async _timeoutAndRetry(handler, timeout, error, maxRetries = 3, retried = 1) {
1223
+ async timeoutAndRetry(handler, timeout, error, maxRetries = 3, retried = 1) {
1215
1224
  try {
1216
1225
  return await addTimeoutToPromise(handler, timeout, error);
1217
1226
  }
@@ -1219,7 +1228,7 @@ export class BasicCrawler {
1219
1228
  if (retried <= maxRetries) {
1220
1229
  // we retry on any error, not just timeout
1221
1230
  this.log.warning(`${e.message} (retrying ${retried}/${maxRetries})`);
1222
- return this._timeoutAndRetry(handler, timeout, error, maxRetries, retried + 1);
1231
+ return this.timeoutAndRetry(handler, timeout, error, maxRetries, retried + 1);
1223
1232
  }
1224
1233
  throw e;
1225
1234
  }
@@ -1227,13 +1236,13 @@ export class BasicCrawler {
1227
1236
  /**
1228
1237
  * Returns true if either RequestList or RequestQueue have a request ready for processing.
1229
1238
  */
1230
- async _isTaskReadyFunction() {
1239
+ async isTaskReadyFunction() {
1231
1240
  return this.requestManager !== undefined && !(await this.requestManager.isEmpty());
1232
1241
  }
1233
1242
  /**
1234
1243
  * Returns true if both RequestList and RequestQueue have all requests finished.
1235
1244
  */
1236
- async _defaultIsFinishedFunction() {
1245
+ async defaultIsFinishedFunction() {
1237
1246
  return !this.requestManager || (await this.requestManager.isFinished());
1238
1247
  }
1239
1248
  /**
@@ -1253,12 +1262,12 @@ export class BasicCrawler {
1253
1262
  *
1254
1263
  * @param request The request object, passed separately to circumvent potential dynamic logic in crawlingContext.request
1255
1264
  */
1256
- async _requestFunctionErrorHandler(error, crawlingContext, request, source) {
1265
+ async requestFunctionErrorHandler(error, crawlingContext, request, source) {
1257
1266
  request.pushErrorMessage(error);
1258
1267
  if (error instanceof CriticalError) {
1259
1268
  throw error;
1260
1269
  }
1261
- const shouldRetryRequest = this._canRequestBeRetried(request, error);
1270
+ const shouldRetryRequest = this.canRequestBeRetried(request, error);
1262
1271
  if (shouldRetryRequest) {
1263
1272
  await this.stats.errorTrackerRetry.addAsync(error, crawlingContext);
1264
1273
  await this.errorHandler?.(crawlingContext, // valid cast - ExtendedContext transitively extends CrawlingContext
@@ -1299,18 +1308,9 @@ export class BasicCrawler {
1299
1308
  // Mark the request as failed and do not retry.
1300
1309
  await source.markRequestAsHandled(request);
1301
1310
  this.stats.failJob(request.id || request.uniqueKey, request.retryCount);
1302
- await this._handleFailedRequestHandler(crawlingContext, error); // This function prints an error message.
1303
- }
1304
- async _tagUserHandlerError(cb) {
1305
- try {
1306
- return (await cb());
1307
- }
1308
- catch (e) {
1309
- Object.defineProperty(e, 'triggeredFromUserHandler', { value: true });
1310
- throw e;
1311
- }
1311
+ await this.handleFailedRequestHandler(crawlingContext, error); // This function prints an error message.
1312
1312
  }
1313
- async _handleFailedRequestHandler(crawlingContext, error) {
1313
+ async handleFailedRequestHandler(crawlingContext, error) {
1314
1314
  // Always log the last error regardless if the user provided a failedRequestHandler
1315
1315
  const { id, url, method, uniqueKey } = crawlingContext.request;
1316
1316
  const message = this._getMessageFromError(error, true);
@@ -1339,7 +1339,7 @@ export class BasicCrawler {
1339
1339
  ? (error.stack ?? [error.message || error, ...stackLines].join('\n'))
1340
1340
  : [error.message || error, userLine].join('\n');
1341
1341
  }
1342
- _canRequestBeRetried(request, error) {
1342
+ canRequestBeRetried(request, error) {
1343
1343
  // Request should never be retried, or the error encountered makes it not able to be retried.
1344
1344
  if (request.noRetry || error instanceof NonRetryableError) {
1345
1345
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/basic",
3
- "version": "4.0.0-beta.80",
3
+ "version": "4.0.0-beta.82",
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.80",
46
- "@crawlee/http-client": "4.0.0-beta.80",
47
- "@crawlee/types": "4.0.0-beta.80",
48
- "@crawlee/utils": "4.0.0-beta.80",
45
+ "@crawlee/core": "4.0.0-beta.82",
46
+ "@crawlee/http-client": "4.0.0-beta.82",
47
+ "@crawlee/types": "4.0.0-beta.82",
48
+ "@crawlee/utils": "4.0.0-beta.82",
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.80"
57
+ "@crawlee/impit-client": "^4.0.0-beta.82"
58
58
  },
59
59
  "lerna": {
60
60
  "command": {
@@ -63,5 +63,5 @@
63
63
  }
64
64
  }
65
65
  },
66
- "gitHead": "96c57b4a0c999e4b2bd198792490af28db7aa42d"
66
+ "gitHead": "eb1096f7c7743d124375ef011fbbadb19476822e"
67
67
  }