@crawlee/basic 4.0.0-beta.12 → 4.0.0-beta.120

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,25 +1,34 @@
1
- import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, AutoscaledPoolOptions, BaseHttpClient, CrawlingContext, DatasetExportOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IRequestList, ProxyConfiguration, ProxyInfo, Request, RequestOptions, RouterHandler, RouterRoutes, Session, SessionPoolOptions, SkippedRequestCallback, Source, StatisticsOptions, StatisticState } from '@crawlee/core';
2
- import { AutoscaledPool, Configuration, ContextPipeline, Dataset, RequestProvider, SessionPool, Statistics } from '@crawlee/core';
3
- import type { Awaitable, Dictionary, SetStatusMessageOptions } from '@crawlee/types';
1
+ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions, Configuration, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueLinksOptions, EventManager, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, IStatistics, Request, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticState, StorageIdentifier, StorageWritePolicy, TaskLoopPredicates, TypedRequestsLike } from '@crawlee/core';
2
+ import { ConcurrencySystem, ContextPipeline, Dataset, RequestQueue } from '@crawlee/core';
3
+ import type { Awaitable, BaseHttpClient, BatchAddRequestsResult, Dictionary, ISession, ISessionPool, ProxyInfo, SetStatusMessageOptions, StorageBackend } from '@crawlee/types';
4
4
  import { RobotsTxtFile } from '@crawlee/utils';
5
- import type { ReadonlyDeep } from 'type-fest';
6
- import type { Log } from '@apify/log';
5
+ import { type BasePredicate } from 'ow';
6
+ import type { ReadonlyDeep, SetRequired } from 'type-fest';
7
7
  import { TimeoutError } from '@apify/timeout';
8
8
  export interface BasicCrawlingContext<UserData extends Dictionary = Dictionary> extends CrawlingContext<UserData> {
9
9
  }
10
+ export { navigationDeadlineKey, remainingNavigationWindowMillis } from './request-timeout.js';
10
11
  export type RequestHandler<Context extends CrawlingContext = CrawlingContext> = (inputs: Context) => Awaitable<void>;
11
- export type ErrorHandler<Context extends CrawlingContext = CrawlingContext, ExtendedContext extends Context = Context> = (inputs: Context & Partial<ExtendedContext>, error: Error) => Awaitable<void>;
12
- export interface StatusMessageCallbackParams<Context extends CrawlingContext = BasicCrawlingContext, Crawler extends BasicCrawler<any> = BasicCrawler<Context>> {
12
+ /**
13
+ * An error handler receives the crawling context and the error that was thrown while processing the request.
14
+ *
15
+ * Unlike the {@link RequestHandler}, an error handler may run before the context pipeline has finished
16
+ * building the full context (e.g. when navigation or session setup fails). Therefore only `BaseContext` is
17
+ * guaranteed to be present, while the extra properties added by the pipeline and `extendContext` (the
18
+ * difference between `BaseContext` and `ExtendedContext`) are only available as a `Partial`.
19
+ */
20
+ export type ErrorHandler<BaseContext extends CrawlingContext = CrawlingContext, ExtendedContext extends BaseContext = BaseContext> = (inputs: BaseContext & Partial<ExtendedContext>, error: Error) => Awaitable<void>;
21
+ export interface StatusMessageCallbackParams<Context extends CrawlingContext = BasicCrawlingContext, Crawler extends BasicCrawler<any, any, any, any> = BasicCrawler<Context>> {
13
22
  state: StatisticState;
14
23
  crawler: Crawler;
15
24
  previousState: StatisticState;
16
25
  message: string;
17
26
  }
18
- export type StatusMessageCallback<Context extends CrawlingContext = BasicCrawlingContext, Crawler extends BasicCrawler<any> = BasicCrawler<Context>> = (params: StatusMessageCallbackParams<Context, Crawler>) => Awaitable<void>;
27
+ export type StatusMessageCallback<Context extends CrawlingContext = BasicCrawlingContext, Crawler extends BasicCrawler<any, any, any, any> = BasicCrawler<Context>> = (params: StatusMessageCallbackParams<Context, Crawler>) => Awaitable<void>;
19
28
  export type RequireContextPipeline<DefaultContextType extends CrawlingContext, FinalContextType extends DefaultContextType> = DefaultContextType extends FinalContextType ? {} : {
20
29
  contextPipelineBuilder: () => ContextPipeline<CrawlingContext, FinalContextType>;
21
30
  };
22
- export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingContext, ContextExtension = {}, ExtendedContext extends Context = Context & ContextExtension> {
31
+ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> {
23
32
  /**
24
33
  * User-provided function that performs the logic of the crawler. It is called for each URL to crawl.
25
34
  *
@@ -37,9 +46,15 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
37
46
  * The exceptions are logged to the request using the
38
47
  * {@link Request.pushErrorMessage|`Request.pushErrorMessage()`} function.
39
48
  */
40
- requestHandler?: RequestHandler<ExtendedContext>;
49
+ requestHandler?: RouterHandler<ExtendedContext, Routes> | RequestHandler<ExtendedContext>;
41
50
  /**
42
- * Allows the user to extend the crawling context passed to the request handler with custom functionality.
51
+ * Allows the user to extend the crawling context with custom functionality (helpers, references, etc.).
52
+ *
53
+ * `extendContext` runs *before* navigation, so the returned members are visible to the
54
+ * `preNavigationHooks`, `postNavigationHooks`, and the `requestHandler` alike. As a consequence,
55
+ * the `context` passed to `extendContext` is the pre-navigation {@link CrawlingContext} and does
56
+ * **not** include navigation-dependent members (e.g. `page`, `response`, `$`, `body`). If you need
57
+ * those, use a `postNavigationHook` or the `requestHandler` instead.
43
58
  *
44
59
  * **Example usage:**
45
60
  *
@@ -59,7 +74,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
59
74
  * });
60
75
  * ```
61
76
  */
62
- extendContext?: (context: Context) => Awaitable<ContextExtension>;
77
+ extendContext?: (context: CrawlingContext) => Awaitable<ContextExtension>;
63
78
  /**
64
79
  * *Intended for BasicCrawler subclasses*. Prepares a context pipeline that transforms the initial crawling context into the shape given by the `Context` type parameter.
65
80
  *
@@ -68,18 +83,27 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
68
83
  contextPipelineBuilder?: () => ContextPipeline<CrawlingContext, Context>;
69
84
  /**
70
85
  * Static list of URLs to be processed.
71
- * If not provided, the crawler will open the default request queue when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called.
72
- * > Alternatively, `requests` parameter of {@link BasicCrawler.run|`crawler.run()`} could be used to enqueue the initial requests -
73
- * it is a shortcut for running `crawler.addRequests()` before the `crawler.run()`.
86
+ *
87
+ * @deprecated Use the `requestManager` option instead. To combine a read-only loader (such as a `RequestList`)
88
+ * with a writable queue, build a tandem with {@link IRequestLoader.toTandem|`requestList.toTandem(requestQueue)`}
89
+ * and pass the result as `requestManager`. When both `requestList` and `requestQueue` are provided, they are
90
+ * combined into a tandem automatically.
74
91
  */
75
- requestList?: IRequestList;
92
+ requestList?: IRequestLoader;
76
93
  /**
77
94
  * Dynamic queue of URLs to be processed. This is useful for recursive crawling of websites.
78
- * If not provided, the crawler will open the default request queue when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called.
79
- * > Alternatively, `requests` parameter of {@link BasicCrawler.run|`crawler.run()`} could be used to enqueue the initial requests -
80
- * it is a shortcut for running `crawler.addRequests()` before the `crawler.run()`.
95
+ *
96
+ * @deprecated Use the `requestManager` option instead. A `RequestQueue` is itself a request manager, so you can
97
+ * pass it directly as `requestManager`.
98
+ */
99
+ requestQueue?: RequestQueue;
100
+ /**
101
+ * Manager of requests that should be processed by the crawler. Mutually exclusive with the deprecated
102
+ * `requestQueue` and `requestList` options.
103
+ *
104
+ * If not provided, the crawler will open the default {@link RequestQueue} when it is first needed.
81
105
  */
82
- requestQueue?: RequestProvider;
106
+ requestManager?: IRequestManager;
83
107
  /**
84
108
  * Timeout in which the function passed as {@link BasicCrawlerOptions.requestHandler|`requestHandler`} needs to finish, in seconds.
85
109
  * @default 60
@@ -106,11 +130,8 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
106
130
  failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
107
131
  /**
108
132
  * Specifies the maximum number of retries allowed for a request if its processing fails.
109
- * This includes retries due to navigation errors or errors thrown from user-supplied functions
110
- * (`requestHandler`, `preNavigationHooks`, `postNavigationHooks`).
111
- *
112
- * This limit does not apply to retries triggered by session rotation
113
- * (see {@link BasicCrawlerOptions.maxSessionRotations|`maxSessionRotations`}).
133
+ * This includes retries due to navigation errors, session/proxy errors, or errors thrown from user-supplied
134
+ * functions (`requestHandler`, `preNavigationHooks`, `postNavigationHooks`).
114
135
  * @default 3
115
136
  */
116
137
  maxRequestRetries?: number;
@@ -119,14 +140,6 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
119
140
  * @default 0
120
141
  */
121
142
  sameDomainDelaySecs?: number;
122
- /**
123
- * Maximum number of session rotations per request.
124
- * The crawler will automatically rotate the session in case of a proxy error or if it gets blocked by the website.
125
- *
126
- * The session rotations are not counted towards the {@link BasicCrawlerOptions.maxRequestRetries|`maxRequestRetries`} limit.
127
- * @default 10
128
- */
129
- maxSessionRotations?: number;
130
143
  /**
131
144
  * Maximum number of pages that the crawler will open. The crawl will stop when this limit is reached.
132
145
  * This value should always be set in order to prevent infinite loops in misconfigured crawlers.
@@ -134,29 +147,56 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
134
147
  */
135
148
  maxRequestsPerCrawl?: number;
136
149
  /**
137
- * Custom options passed to the underlying {@link AutoscaledPool} constructor.
138
- * > *NOTE:* The {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`}
139
- * and {@link AutoscaledPoolOptions.isTaskReadyFunction|`isTaskReadyFunction`} options
140
- * are provided by the crawler and cannot be overridden.
141
- * However, we can provide a custom implementation of {@link AutoscaledPoolOptions.isFinishedFunction|`isFinishedFunction`}.
150
+ * Maximum depth of the crawl. If not set, the crawl will continue until all requests are processed.
151
+ * Setting this to `0` will only process the initial requests, skipping all links enqueued by `crawlingContext.enqueueLinks` and `crawlingContext.addRequests`.
152
+ * Passing `1` will process the initial requests and all links enqueued by `crawlingContext.enqueueLinks` and `crawlingContext.addRequests` in the handler for initial requests.
142
153
  */
143
- autoscaledPoolOptions?: AutoscaledPoolOptions;
154
+ maxCrawlDepth?: number;
155
+ /**
156
+ * Lets you override the predicates that steer the crawler's task loop: `isTaskReadyFunction` (may another request
157
+ * start?) and `isFinishedFunction` (is the crawl over?). The task itself — fetching a request and running it
158
+ * through the pipeline — is owned by the crawler and cannot be overridden.
159
+ *
160
+ * Concurrency is configured elsewhere — through the `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute`
161
+ * shortcuts, or a {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} for finer control.
162
+ */
163
+ taskLoopOptions?: TaskLoopPredicates;
164
+ /**
165
+ * A pre-configured concurrency governor — the component that decides whether there is free compute for one more
166
+ * task. Typically a {@link ConcurrencySystem}, though any {@link IConcurrencySystem} is accepted. All
167
+ * scaling configuration (min/max/desired concurrency, scaling ratios, `maxTasksPerMinute`, snapshotter tuning)
168
+ * lives on the instance itself.
169
+ *
170
+ * Inject the *same* instance into several concurrent crawlers to cap their **combined** concurrency against a
171
+ * single budget. Each crawler still builds and drives its own {@link AutoscaledPool}; only the load/scaling
172
+ * accounting is shared.
173
+ *
174
+ * Mutually exclusive with the `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts, which configure
175
+ * the default system this one replaces — combining the two throws.
176
+ *
177
+ * You own a supplied system's lifecycle: `start()` it before `run()` (which throws otherwise) and `stop()` it once
178
+ * every crawler borrowing it has finished. The crawler does neither on your behalf.
179
+ */
180
+ concurrencySystem?: IConcurrencySystem;
144
181
  /**
145
182
  * Sets the minimum concurrency (parallelism) for the crawl. Shortcut for the
146
- * AutoscaledPool {@link AutoscaledPoolOptions.minConcurrency|`minConcurrency`} option.
183
+ * {@link ConcurrencySystemOptions.minConcurrency|`minConcurrency`} option of the crawler's default
184
+ * {@link ConcurrencySystem}.
147
185
  * > *WARNING:* If we set this value too high with respect to the available system memory and CPU, our crawler will run extremely slow or crash.
148
186
  * If not sure, it's better to keep the default value and the concurrency will scale up automatically.
149
187
  */
150
188
  minConcurrency?: number;
151
189
  /**
152
190
  * Sets the maximum concurrency (parallelism) for the crawl. Shortcut for the
153
- * AutoscaledPool {@link AutoscaledPoolOptions.maxConcurrency|`maxConcurrency`} option.
191
+ * {@link ConcurrencySystemOptions.maxConcurrency|`maxConcurrency`} option of the crawler's default
192
+ * {@link ConcurrencySystem}.
154
193
  */
155
194
  maxConcurrency?: number;
156
195
  /**
157
196
  * The maximum number of requests per minute the crawler should run.
158
197
  * By default, this is set to `Infinity`, but we can pass any positive, non-zero integer.
159
- * Shortcut for the AutoscaledPool {@link AutoscaledPoolOptions.maxTasksPerMinute|`maxTasksPerMinute`} option.
198
+ * Shortcut for the {@link ConcurrencySystemOptions.maxTasksPerMinute|`maxTasksPerMinute`} option of the
199
+ * crawler's default {@link ConcurrencySystem}.
160
200
  */
161
201
  maxRequestsPerMinute?: number;
162
202
  /**
@@ -166,14 +206,14 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
166
206
  */
167
207
  keepAlive?: boolean;
168
208
  /**
169
- * Basic crawler will initialize the {@link SessionPool} with the corresponding {@link SessionPoolOptions|`sessionPoolOptions`}.
170
- * The session instance will be than available in the {@link BasicCrawlerOptions.requestHandler|`requestHandler`}.
171
- */
172
- useSessionPool?: boolean;
173
- /**
174
- * The configuration options for {@link SessionPool} to use.
209
+ * An existing session pool instance to use. When provided, the crawler will use this pool directly instead of
210
+ * creating a new one, enabling session sharing across multiple crawlers. The crawler will not tear down a shared
211
+ * pool — the caller is responsible for its lifecycle.
212
+ *
213
+ * Accepts the built-in {@link SessionPool} or any object implementing the {@link ISessionPool} interface,
214
+ * so custom session-management strategies can be plugged in.
175
215
  */
176
- sessionPoolOptions?: SessionPoolOptions;
216
+ sessionPool?: ISessionPool;
177
217
  /**
178
218
  * Defines the length of the interval for calling the `setStatusMessage` in seconds.
179
219
  */
@@ -195,6 +235,15 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
195
235
  * ```
196
236
  */
197
237
  statusMessageCallback?: StatusMessageCallback;
238
+ /**
239
+ * HTTP status codes that indicate the session should be retired.
240
+ *
241
+ * A 429 from a domain covered by a {@link ThrottlingRequestManager} is handled as a rate limit before
242
+ * this is consulted, so removing 429 here only affects domains that manager does not cover.
243
+ *
244
+ * @default [401, 403, 429]
245
+ */
246
+ blockedStatusCodes?: number[];
198
247
  /**
199
248
  * If set to `true`, the crawler will automatically try to bypass any detected bot protection.
200
249
  *
@@ -206,50 +255,94 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
206
255
  /**
207
256
  * If set to `true`, the crawler will automatically try to fetch the robots.txt file for each domain,
208
257
  * and skip those that are not allowed. This also prevents disallowed URLs to be added via `enqueueLinks`.
258
+ *
259
+ * If an object is provided, it may contain a `userAgent` property to specify which user-agent
260
+ * should be used when checking the robots.txt file. If not provided, the default user-agent `*` will be used.
209
261
  */
210
- respectRobotsTxtFile?: boolean;
262
+ respectRobotsTxtFile?: boolean | {
263
+ userAgent?: string;
264
+ };
211
265
  /**
212
266
  * When a request is skipped for some reason, you can use this callback to act on it.
213
- * This is currently fired only for requests skipped based on robots.txt file.
267
+ * This is currently fired for requests skipped
268
+ * 1. based on robots.txt file,
269
+ * 2. because they don't match enqueueLinks filters,
270
+ * 3. because they are redirected to a URL that doesn't match the enqueueLinks strategy,
271
+ * 4. or because the {@link BasicCrawlerOptions.maxRequestsPerCrawl|`maxRequestsPerCrawl`} limit has been reached
214
272
  */
215
273
  onSkippedRequest?: SkippedRequestCallback;
216
- /** @internal */
217
- log?: Log;
218
- /**
219
- * Enables experimental features of Crawlee, which can alter the behavior of the crawler.
220
- * WARNING: these options are not guaranteed to be stable and may change or be removed at any time.
221
- */
222
- experiments?: CrawlerExperiments;
223
274
  /**
224
- * Customize the way statistics collecting works, such as logging interval or
225
- * whether to output them to the Key-Value store.
275
+ * A preconfigured statistics instance. When provided, the crawler records into it instead of building its own and
276
+ * will not `reset()` it between `run()` calls. Accepts the built-in {@link Statistics} (subclass it to track
277
+ * extra fields) or any object implementing {@link IStatistics}.
226
278
  */
227
- statisticsOptions?: StatisticsOptions;
279
+ statistics?: IStatistics;
228
280
  /**
229
281
  * HTTP client implementation for the `sendRequest` context helper and for plain HTTP crawling.
230
- * Defaults to a new instance of {@link GotScrapingHttpClient}
282
+ * Defaults to {@link ImpitHttpClient} when `@crawlee/impit-client` is installed, otherwise {@link FetchHttpClient}.
231
283
  */
232
284
  httpClient?: BaseHttpClient;
233
285
  /**
234
286
  * If set, the crawler will be configured for all connections to use
235
287
  * the Proxy URLs provided and rotated according to the configuration.
236
288
  */
237
- proxyConfiguration?: ProxyConfiguration;
238
- }
239
- /**
240
- * A set of options that you can toggle to enable experimental features in Crawlee.
241
- *
242
- * NOTE: These options will not respect semantic versioning and may be removed or changed at any time. Use at your own risk.
243
- * If you do use these and encounter issues, please report them to us.
244
- */
245
- export interface CrawlerExperiments {
289
+ proxyConfiguration?: IProxyConfiguration;
290
+ /**
291
+ * Custom configuration to use for this crawler.
292
+ * If provided, the crawler will use its own ServiceLocator instance instead of the global one.
293
+ */
294
+ configuration?: Configuration;
295
+ /**
296
+ * Custom storage backend to use for this crawler.
297
+ * If provided, the crawler will use its own ServiceLocator instance instead of the global one.
298
+ */
299
+ storageBackend?: StorageBackend;
300
+ /**
301
+ * Custom event manager to use for this crawler.
302
+ * If provided, the crawler will use its own ServiceLocator instance instead of the global one.
303
+ */
304
+ eventManager?: EventManager;
246
305
  /**
247
- * @deprecated This experiment is now enabled by default, and this flag will be removed in a future release.
248
- * If you encounter issues due to this change, please:
249
- * - report it to us: https://github.com/apify/crawlee
250
- * - set `requestLocking` to `false` in the `experiments` option of the crawler
306
+ * Custom logger to use for this crawler.
307
+ * If provided, the crawler will use its own ServiceLocator instance instead of the global one.
251
308
  */
252
- requestLocking?: boolean;
309
+ logger?: CrawleeLogger;
310
+ /**
311
+ * A unique identifier for the crawler instance. This ID is used to isolate the state returned by
312
+ * {@link BasicCrawler.useState|`crawler.useState()`} from other crawler instances.
313
+ *
314
+ * When multiple crawler instances use `useState()` without an explicit `id`, they will share the same
315
+ * state object for backward compatibility. A warning will be logged in this case.
316
+ *
317
+ * To ensure each crawler has its own isolated state that also persists across script restarts
318
+ * (e.g., during Apify migrations), provide a stable, unique ID for each crawler instance.
319
+ *
320
+ */
321
+ id?: string;
322
+ /**
323
+ * Makes the storage writes performed while handling a request atomic with respect to the request
324
+ * succeeding: they are recorded in a {@link StorageTransaction} spanning the whole request
325
+ * lifecycle and only applied when the request handler succeeds, so a thrown handler leaves no partial
326
+ * writes behind and a retry does not double-write. Reads within the handler see its own writes.
327
+ *
328
+ * `false` disables the mechanism entirely; an object overrides the per-storage-type
329
+ * {@link StorageWritePolicy} (e.g. `{ requestQueue: 'deferred' }` for all-or-nothing enqueues).
330
+ * {@link withDirectStorageAccess} is the per-call-site escape hatch; `useState()` is deliberately
331
+ * *not* transactional.
332
+ *
333
+ * @default true
334
+ */
335
+ transactionalStorage?: boolean | Partial<StorageWritePolicy>;
336
+ /**
337
+ * An array of HTTP response [Status Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) to be excluded from error consideration.
338
+ * By default, status codes >= 500 trigger errors.
339
+ */
340
+ ignoreHttpErrorStatusCodes?: number[];
341
+ /**
342
+ * An array of additional HTTP response [Status Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) to be treated as errors.
343
+ * By default, status codes >= 500 trigger errors.
344
+ */
345
+ additionalHttpErrorStatusCodes?: number[];
253
346
  }
254
347
  /**
255
348
  * Provides a simple framework for parallel crawling of web pages.
@@ -263,25 +356,31 @@ export interface CrawlerExperiments {
263
356
  *
264
357
  * `BasicCrawler` invokes the user-provided {@link BasicCrawlerOptions.requestHandler|`requestHandler`}
265
358
  * for each {@link Request} object, which represents a single URL to crawl.
266
- * The {@link Request} objects are fed from the {@link RequestList} or {@link RequestQueue}
267
- * instances provided by the {@link BasicCrawlerOptions.requestList|`requestList`} or {@link BasicCrawlerOptions.requestQueue|`requestQueue`}
268
- * constructor options, respectively. If neither `requestList` nor `requestQueue` options are provided,
269
- * the crawler will open the default request queue either when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called,
270
- * or if `requests` parameter (representing the initial requests) of the {@link BasicCrawler.run|`crawler.run()`} function is provided.
359
+ * The {@link Request} objects are fed from the {@link IRequestManager|request manager} provided via the
360
+ * {@link BasicCrawlerOptions.requestManager|`requestManager`} constructor option (a {@link RequestQueue} is
361
+ * itself a request manager). If no `requestManager` is provided, the crawler opens the default {@link RequestQueue}
362
+ * either when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called, or if the `requests`
363
+ * parameter (representing the initial requests) of the {@link BasicCrawler.run|`crawler.run()`} function is provided.
271
364
  *
272
- * If both {@link BasicCrawlerOptions.requestList|`requestList`} and {@link BasicCrawlerOptions.requestQueue|`requestQueue`} options are used,
273
- * the instance first processes URLs from the {@link RequestList} and automatically enqueues all of them
274
- * to the {@link RequestQueue} before it starts their processing. This ensures that a single URL is not crawled multiple times.
365
+ * To read requests from a read-only source such as a {@link RequestList} or {@link SitemapRequestLoader} while
366
+ * still being able to enqueue new ones, combine the loader with a queue into a {@link RequestManagerTandem} using
367
+ * {@link IRequestLoader.toTandem|`requestLoader.toTandem()`} and pass the result as `requestManager`. The tandem
368
+ * first processes URLs from the loader and automatically enqueues them into the queue, ensuring a single URL is not
369
+ * crawled multiple times.
370
+ *
371
+ * > The legacy {@link BasicCrawlerOptions.requestList|`requestList`} and
372
+ * > {@link BasicCrawlerOptions.requestQueue|`requestQueue`} options are deprecated. They are still accepted and
373
+ * > folded into a single `requestManager` (combined into a tandem when both are given), but new code should use
374
+ * > `requestManager` directly.
275
375
  *
276
376
  * The crawler finishes if there are no more {@link Request} objects to crawl.
277
377
  *
278
- * New requests are only dispatched when there is enough free CPU and memory available,
279
- * using the functionality provided by the {@link AutoscaledPool} class.
280
- * All {@link AutoscaledPool} configuration options can be passed to the {@link BasicCrawlerOptions.autoscaledPoolOptions|`autoscaledPoolOptions`}
281
- * parameter of the `BasicCrawler` constructor.
282
- * For user convenience, the {@link AutoscaledPoolOptions.minConcurrency|`minConcurrency`} and
283
- * {@link AutoscaledPoolOptions.maxConcurrency|`maxConcurrency`} options of the
284
- * underlying {@link AutoscaledPool} constructor are available directly in the `BasicCrawler` constructor.
378
+ * New requests are only dispatched when there is enough free CPU and memory available, as judged by the crawler's
379
+ * {@link ConcurrencySystem}.
380
+ * Concurrency is tuned via the {@link BasicCrawlerOptions.minConcurrency|`minConcurrency`},
381
+ * {@link BasicCrawlerOptions.maxConcurrency|`maxConcurrency`} and
382
+ * {@link BasicCrawlerOptions.maxRequestsPerMinute|`maxRequestsPerMinute`} shortcuts, or, for finer control, by
383
+ * injecting a pre-configured {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`}.
285
384
  *
286
385
  * **Example usage:**
287
386
  *
@@ -315,142 +414,216 @@ export interface CrawlerExperiments {
315
414
  * ```
316
415
  * @category Crawlers
317
416
  */
318
- export declare class BasicCrawler<Context extends CrawlingContext = CrawlingContext, ContextExtension = {}, ExtendedContext extends Context = Context & ContextExtension> {
319
- readonly config: Configuration;
417
+ /**
418
+ * Identifies a crawler instance for storage aliasing, `useState()` and status-message events.
419
+ */
420
+ interface CrawlerIdentity {
421
+ /**
422
+ * 0-based instantiation order across all crawlers in the process.
423
+ * Note that the value can be subject to race conditions between different script invocations.
424
+ */
425
+ readonly instanceIndex: number;
426
+ /** The user-supplied `id` option, or a fallback derived from `instanceIndex`. */
427
+ readonly id: string;
428
+ /** Whether `id` came from the user (as opposed to being derived from `instanceIndex`). */
429
+ readonly hasExplicitId: boolean;
430
+ }
431
+ 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']>>> {
432
+ #private;
320
433
  protected static readonly CRAWLEE_STATE_KEY = "CRAWLEE_STATE";
321
434
  /**
322
- * A reference to the underlying {@link Statistics} class that collects and logs run statistics for requests.
435
+ * Tracks the number of crawler instances created. The first crawler uses the default
436
+ * request queue; subsequent ones get their own queue via a unique alias so they don't
437
+ * collide.
323
438
  */
324
- readonly stats: Statistics;
439
+ private static instanceCount;
325
440
  /**
326
- * A reference to the underlying {@link RequestList} class that manages the crawler's {@link Request|requests}.
327
- * Only available if used by the crawler.
441
+ * The statistics instance collecting the crawler's run statistics - either the injected `statistics` option or a
442
+ * crawler-built default. Typed as {@link IStatistics} so custom implementations can be plugged in.
328
443
  */
329
- requestList?: IRequestList;
444
+ get stats(): IStatistics;
330
445
  /**
331
- * Dynamic queue of URLs to be processed. This is useful for recursive crawling of websites.
332
- * A reference to the underlying {@link RequestQueue} class that manages the crawler's {@link Request|requests}.
333
- * Only available if used by the crawler.
446
+ * The main request-handling component of the crawler. It manages the requests that the crawler processes,
447
+ * combining any provided request loader and/or queue. It's initialized during the crawler startup or lazily
448
+ * via {@link BasicCrawler.getRequestManager|`getRequestManager()`}.
334
449
  */
335
- requestQueue?: RequestProvider;
450
+ protected requestManager?: IRequestManager;
336
451
  /**
337
- * A reference to the underlying {@link SessionPool} class that manages the crawler's {@link Session|sessions}.
338
- * Only available if used by the crawler.
452
+ * A reference to the underlying session pool that manages the crawler's {@link Session|sessions}. Typed as
453
+ * {@link ISessionPool} so custom implementations can be plugged in via the `sessionPool` constructor option.
339
454
  */
340
- sessionPool?: SessionPool;
455
+ get sessionPool(): ISessionPool;
341
456
  /**
342
- * A reference to the underlying {@link AutoscaledPool} class that manages the concurrency of the crawler.
343
- * > *NOTE:* This property is only initialized after calling the {@link BasicCrawler.run|`crawler.run()`} function.
344
- * We can use it to change the concurrency settings on the fly,
345
- * to pause the crawler by calling {@link AutoscaledPool.pause|`autoscaledPool.pause()`}
346
- * or to abort it by calling {@link AutoscaledPool.abort|`autoscaledPool.abort()`}.
457
+ * The concurrency governor this run is booking its requests against either the
458
+ * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} that was injected, or the default the
459
+ * crawler built for itself. Read it for telemetry: `desiredConcurrency`, `currentConcurrency`, `isRunning`.
460
+ *
461
+ * > *NOTE:* `undefined` until {@link BasicCrawler.run|`crawler.run()`} has resolved it. A crawler-owned default
462
+ * is also rebuilt for every run, so the instance is not stable across runs.
463
+ *
464
+ * {@link IConcurrencySystem} is deliberately read-only. Tuning concurrency *while a crawl is running* means
465
+ * owning the instance: build a {@link ConcurrencySystem} yourself and inject it, then set
466
+ * `minConcurrency`/`maxConcurrency`/`desiredConcurrency` on your own reference.
347
467
  */
348
- autoscaledPool?: AutoscaledPool;
468
+ get concurrencySystem(): IConcurrencySystem | undefined;
349
469
  /**
350
- * A reference to the underlying {@link ProxyConfiguration} class that manages the crawler's proxies.
470
+ * A reference to the underlying {@link IProxyConfiguration} instance that manages the crawler's proxies.
351
471
  * Only available if used by the crawler.
352
472
  */
353
- proxyConfiguration?: ProxyConfiguration;
473
+ readonly proxyConfiguration?: IProxyConfiguration;
354
474
  /**
355
475
  * Default {@link Router} instance that will be used if we don't specify any {@link BasicCrawlerOptions.requestHandler|`requestHandler`}.
356
476
  * See {@link Router.addHandler|`router.addHandler()`} and {@link Router.addDefaultHandler|`router.addDefaultHandler()`}.
357
477
  */
358
- readonly router: RouterHandler<Context>;
359
- private contextPipelineBuilder;
360
- private _contextPipeline?;
478
+ readonly router: RouterHandler<Context, Routes>;
479
+ /**
480
+ * The basic part of the context pipeline. Unlike the subclass pipeline, this
481
+ * part has no major side effects (e.g. launching a browser). It also makes typing more explicit, as subclass
482
+ * pipelines expect the basic crawler fields to already be present in the context at runtime.
483
+ *
484
+ * Context built with this pipeline can be passed into multiple crawler pipelines at once.
485
+ * This is used e.g. in the {@link AdaptivePlaywrightCrawler|`AdaptivePlaywrightCrawler`}.
486
+ */
487
+ get basicContextPipeline(): ContextPipeline<{
488
+ request: Request;
489
+ }, CrawlingContext>;
361
490
  get contextPipeline(): ContextPipeline<CrawlingContext, ExtendedContext>;
362
491
  running: boolean;
363
492
  hasFinishedBefore: boolean;
364
- readonly log: Log;
365
- protected requestHandler: RequestHandler<ExtendedContext>;
366
- protected errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
367
- protected failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
368
- protected requestHandlerTimeoutMillis: number;
369
- protected internalTimeoutMillis: number;
370
- protected maxRequestRetries: number;
371
- protected sameDomainDelayMillis: number;
372
- protected domainAccessedTime: Map<string, number>;
373
- protected maxSessionRotations: number;
374
- protected handledRequestsCount: number;
375
- protected statusMessageLoggingInterval: number;
376
- protected statusMessageCallback?: StatusMessageCallback;
377
- protected sessionPoolOptions: SessionPoolOptions;
378
- protected useSessionPool: boolean;
379
- protected autoscaledPoolOptions: AutoscaledPoolOptions;
380
- protected events: EventManager;
381
- protected httpClient: BaseHttpClient;
382
- protected retryOnBlocked: boolean;
383
- protected respectRobotsTxtFile: boolean;
384
- protected onSkippedRequest?: SkippedRequestCallback;
385
- private _closeEvents?;
386
- private experiments;
387
- private readonly robotsTxtFileCache;
388
- private _experimentWarnings;
493
+ get log(): CrawleeLogger;
494
+ protected readonly requestHandler: RequestHandler<ExtendedContext>;
495
+ protected readonly errorHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
496
+ protected readonly failedRequestHandler?: ErrorHandler<CrawlingContext, ExtendedContext>;
497
+ private requestHandlerTimeoutMillis;
498
+ protected readonly internalTimeoutMillis: number;
499
+ protected readonly maxRequestRetries: number;
500
+ protected readonly maxCrawlDepth?: number;
501
+ protected readonly maxRequestsPerCrawl?: number;
502
+ private get handledRequestsCount();
503
+ protected blockedStatusCodes: Set<number>;
504
+ protected readonly additionalHttpErrorStatusCodes: Set<number>;
505
+ /**
506
+ * The resolved options for the crawler's own task loop — the crawler-owned `runTaskFunction`, the (possibly
507
+ * user-overridden) ready/finished predicates and cadence/logging. Concurrency configuration lives on the
508
+ * {@link ConcurrencySystem} instead, and the loop's `consumer` identity is the crawler's own, so neither is
509
+ * settable here.
510
+ */
511
+ private taskLoopOptions;
512
+ protected readonly httpClient: BaseHttpClient;
513
+ protected readonly retryOnBlocked: boolean;
514
+ protected readonly onSkippedRequest?: SkippedRequestCallback;
515
+ protected readonly identity: CrawlerIdentity;
389
516
  protected static optionsShape: {
390
517
  // @ts-ignore optional peer dependency or compatibility with es2022
391
- contextPipelineBuilder: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
518
+ contextPipelineBuilder: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
519
+ // @ts-ignore optional peer dependency or compatibility with es2022
520
+ extendContext: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
521
+ // @ts-ignore optional peer dependency or compatibility with es2022
522
+ requestList: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
523
+ // @ts-ignore optional peer dependency or compatibility with es2022
524
+ requestQueue: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
525
+ // @ts-ignore optional peer dependency or compatibility with es2022
526
+ requestManager: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
392
527
  // @ts-ignore optional peer dependency or compatibility with es2022
393
- extendContext: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
528
+ requestHandler: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
394
529
  // @ts-ignore optional peer dependency or compatibility with es2022
395
- requestList: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
530
+ requestHandlerTimeoutSecs: import("ow").NumberPredicate & BasePredicate<number | undefined>;
396
531
  // @ts-ignore optional peer dependency or compatibility with es2022
397
- requestQueue: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
532
+ errorHandler: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
398
533
  // @ts-ignore optional peer dependency or compatibility with es2022
399
- requestHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
534
+ failedRequestHandler: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
400
535
  // @ts-ignore optional peer dependency or compatibility with es2022
401
- requestHandlerTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
536
+ maxRequestRetries: import("ow").NumberPredicate & BasePredicate<number | undefined>;
402
537
  // @ts-ignore optional peer dependency or compatibility with es2022
403
- errorHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
538
+ sameDomainDelaySecs: import("ow").NumberPredicate & BasePredicate<number | undefined>;
404
539
  // @ts-ignore optional peer dependency or compatibility with es2022
405
- failedRequestHandler: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
540
+ maxRequestsPerCrawl: import("ow").NumberPredicate & BasePredicate<number | undefined>;
406
541
  // @ts-ignore optional peer dependency or compatibility with es2022
407
- maxRequestRetries: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
542
+ maxCrawlDepth: import("ow").NumberPredicate & BasePredicate<number | undefined>;
408
543
  // @ts-ignore optional peer dependency or compatibility with es2022
409
- sameDomainDelaySecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
544
+ taskLoopOptions: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
410
545
  // @ts-ignore optional peer dependency or compatibility with es2022
411
- maxSessionRotations: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
546
+ concurrencySystem: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
412
547
  // @ts-ignore optional peer dependency or compatibility with es2022
413
- maxRequestsPerCrawl: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
548
+ sessionPool: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
414
549
  // @ts-ignore optional peer dependency or compatibility with es2022
415
- autoscaledPoolOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
550
+ proxyConfiguration: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
416
551
  // @ts-ignore optional peer dependency or compatibility with es2022
417
- sessionPoolOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
552
+ statusMessageLoggingInterval: import("ow").NumberPredicate & BasePredicate<number | undefined>;
418
553
  // @ts-ignore optional peer dependency or compatibility with es2022
419
- useSessionPool: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
554
+ statusMessageCallback: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
420
555
  // @ts-ignore optional peer dependency or compatibility with es2022
421
- proxyConfiguration: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
556
+ additionalHttpErrorStatusCodes: import("ow").ArrayPredicate<number>;
422
557
  // @ts-ignore optional peer dependency or compatibility with es2022
423
- statusMessageLoggingInterval: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
558
+ ignoreHttpErrorStatusCodes: import("ow").ArrayPredicate<number>;
424
559
  // @ts-ignore optional peer dependency or compatibility with es2022
425
- statusMessageCallback: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
560
+ blockedStatusCodes: import("ow").ArrayPredicate<number>;
426
561
  // @ts-ignore optional peer dependency or compatibility with es2022
427
- retryOnBlocked: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
562
+ retryOnBlocked: import("ow").BooleanPredicate & BasePredicate<boolean | undefined>;
428
563
  // @ts-ignore optional peer dependency or compatibility with es2022
429
- respectRobotsTxtFile: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
564
+ respectRobotsTxtFile: import("ow").AnyPredicate<boolean | object>;
565
+ transactionalStorage: BasePredicate<boolean | Partial<StorageWritePolicy> | undefined>;
430
566
  // @ts-ignore optional peer dependency or compatibility with es2022
431
- onSkippedRequest: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
567
+ onSkippedRequest: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
432
568
  // @ts-ignore optional peer dependency or compatibility with es2022
433
- httpClient: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
569
+ httpClient: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
434
570
  // @ts-ignore optional peer dependency or compatibility with es2022
435
- minConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
571
+ configuration: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
436
572
  // @ts-ignore optional peer dependency or compatibility with es2022
437
- maxConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
573
+ storageBackend: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
438
574
  // @ts-ignore optional peer dependency or compatibility with es2022
439
- maxRequestsPerMinute: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
575
+ eventManager: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
440
576
  // @ts-ignore optional peer dependency or compatibility with es2022
441
- keepAlive: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
577
+ logger: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
442
578
  // @ts-ignore optional peer dependency or compatibility with es2022
443
- log: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
579
+ minConcurrency: import("ow").NumberPredicate & BasePredicate<number | undefined>;
444
580
  // @ts-ignore optional peer dependency or compatibility with es2022
445
- experiments: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
581
+ maxConcurrency: import("ow").NumberPredicate & BasePredicate<number | undefined>;
446
582
  // @ts-ignore optional peer dependency or compatibility with es2022
447
- statisticsOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
583
+ maxRequestsPerMinute: import("ow").NumberPredicate & BasePredicate<number | undefined>;
584
+ // @ts-ignore optional peer dependency or compatibility with es2022
585
+ keepAlive: import("ow").BooleanPredicate & BasePredicate<boolean | undefined>;
586
+ // @ts-ignore optional peer dependency or compatibility with es2022
587
+ statistics: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
588
+ // @ts-ignore optional peer dependency or compatibility with es2022
589
+ id: import("ow").StringPredicate & BasePredicate<string | undefined>;
448
590
  };
449
591
  /**
450
592
  * All `BasicCrawler` parameters are passed via an options object.
451
593
  */
452
- constructor(options?: BasicCrawlerOptions<Context, ContextExtension, ExtendedContext> & RequireContextPipeline<CrawlingContext, Context>, // cast because the constructor logic handles missing `contextPipelineBuilder` - the type is just for DX
453
- config?: Configuration);
594
+ constructor(options?: BasicCrawlerOptions<Context, ContextExtension, ExtendedContext, Routes> & RequireContextPipeline<CrawlingContext, Context>);
595
+ /**
596
+ * Builds the crawler-owned default {@link ConcurrencySystem} from the resolved
597
+ * `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts. Not called when a
598
+ * {@link BasicCrawlerOptions.concurrencySystem|`concurrencySystem`} was injected.
599
+ *
600
+ * Subclasses may override this to tune the default system (e.g. {@link HttpCrawler} raises the starting
601
+ * concurrency and relaxes the event loop signal) while still honouring the user's shortcuts.
602
+ */
603
+ protected createDefaultConcurrencySystem(options: ConcurrencySystemOptions): ConcurrencySystem;
604
+ /**
605
+ * Determines if the given HTTP status code is an error status code given
606
+ * the default behaviour and user-set preferences.
607
+ * @param status
608
+ * @returns `true` if the status code is considered an error, `false` otherwise
609
+ */
610
+ protected isErrorStatusCode(status: number): boolean;
611
+ /**
612
+ * Builds the basic context pipeline that transforms `{ request }` into a full `CrawlingContext`.
613
+ * This handles base context creation, session resolution, and context helpers.
614
+ */
615
+ private buildBasicContextPipeline;
616
+ private checkRobotsTxt;
617
+ /**
618
+ * Builds the subclass-specific context pipeline that transforms a `CrawlingContext` into the crawler's target context type.
619
+ * Subclasses should override this to add their own pipeline stages.
620
+ */
621
+ protected buildContextPipeline(): ContextPipeline<CrawlingContext, CrawlingContext>;
622
+ private createBaseContext;
623
+ private resolveRequest;
624
+ private resolveSession;
625
+ private createContextHelpers;
626
+ private buildFinalContextPipeline;
454
627
  /**
455
628
  * Checks if the given error is a proxy error by comparing its message to a list of known proxy error messages.
456
629
  * Used for retrying requests that failed due to proxy errors.
@@ -459,13 +632,20 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
459
632
  */
460
633
  protected isProxyError(error: Error): boolean;
461
634
  /**
635
+ * Sets the status message for the current crawler run.
636
+ *
462
637
  * This method is periodically called by the crawler, every `statusMessageLoggingInterval` seconds.
638
+ *
639
+ * The message is logged and broadcast via the {@link EventType.STATUS_MESSAGE|`statusMessage`}
640
+ * event. Integrations such as the Apify SDK subscribe to that event and forward the message to
641
+ * their status-reporting backend (e.g. the Apify platform).
463
642
  */
464
- setStatusMessage(message: string, options?: SetStatusMessageOptions): Promise<void>;
643
+ setStatusMessage(message: string, options?: SetStatusMessageOptions): void;
465
644
  private getPeriodicLogger;
466
645
  /**
467
- * Runs the crawler. Returns a promise that resolves once all the requests are processed
468
- * and `autoscaledPool.isFinished` returns `true`.
646
+ * Runs the crawler. Returns a promise that resolves once every request has been processed and the crawler's
647
+ * finished-check ({@link BasicCrawlerOptions.taskLoopOptions|`taskLoopOptions.isFinishedFunction`}, or the
648
+ * default "the request manager is empty") reports that the crawl is over.
469
649
  *
470
650
  * We can use the `requests` parameter to enqueue the initial requests — it is a shortcut for
471
651
  * running {@link BasicCrawler.addRequests|`crawler.addRequests()`} before {@link BasicCrawler.run|`crawler.run()`}.
@@ -473,15 +653,66 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
473
653
  * @param [requests] The requests to add.
474
654
  * @param [options] Options for the request queue.
475
655
  */
476
- run(requests?: (string | Request | RequestOptions)[], options?: CrawlerRunOptions): Promise<FinalStatistics>;
656
+ run(requests?: TypedRequestsLike<Routes>, options?: CrawlerRunOptions): Promise<FinalStatistics>;
477
657
  /**
478
658
  * Gracefully stops the current run of the crawler.
479
659
  *
480
660
  * All the tasks active at the time of calling this method will be allowed to finish.
661
+ *
662
+ * To stop the crawler immediately, use {@link BasicCrawler.teardown|`crawler.teardown()`} instead.
663
+ */
664
+ stop(reason?: string): void;
665
+ /**
666
+ * Stops dispatching new requests, letting the in-progress ones finish. Resolves once they have settled, or rejects
667
+ * after `timeoutSecs` if they take too long. Unlike {@link BasicCrawler.stop|`stop()`}, this does not end the
668
+ * run — {@link BasicCrawler.run|`run()`} stays pending until {@link BasicCrawler.resume|`resume()`}.
669
+ *
670
+ * > *NOTE:* The {@link BasicCrawler.concurrencySystem|concurrency system} keeps monitoring and autoscaling
671
+ * throughout, since a shared one may still be serving other crawlers.
672
+ */
673
+ pause(timeoutSecs?: number): Promise<void>;
674
+ /**
675
+ * Resumes a run suspended with {@link BasicCrawler.pause|`pause()`}, letting the crawler dispatch requests
676
+ * again. A no-op on a crawler that is not paused.
677
+ */
678
+ resume(): void;
679
+ /**
680
+ * Returns the crawler's {@link IRequestManager|request manager}, opening the default {@link RequestQueue}
681
+ * if none has been configured or opened yet.
682
+ */
683
+ getRequestManager(): Promise<IRequestManager>;
684
+ /**
685
+ * @deprecated Use {@link BasicCrawler.getRequestManager|`getRequestManager()`} instead. This returns the
686
+ * crawler's request manager, which is no longer guaranteed to be a {@link RequestQueue}.
481
687
  */
482
- stop(message?: string): void;
483
- getRequestQueue(): Promise<RequestProvider>;
688
+ getRequestQueue(): Promise<IRequestManager>;
689
+ /**
690
+ * Opens the default {@link RequestQueue}, applies the crawler's timeouts to it and records it as the
691
+ * crawler-owned queue (so it gets purged between repeated `run()` calls).
692
+ * @private
693
+ */
694
+ private openOwnedRequestQueue;
695
+ /**
696
+ * Tells a request manager how long we expect to hold a fetched request, so that one backed by a
697
+ * locking storage backend keeps it reserved for slightly longer than the request handler timeout
698
+ * (with some padding for overhead), but never for less than a minute. This prevents a long-running
699
+ * request from being handed out a second time while it is still being processed — and it works
700
+ * regardless of whether the manager is a plain {@link RequestQueue} or a `RequestManagerTandem`.
701
+ */
702
+ private applyRequestManagerTimeouts;
703
+ /**
704
+ * Validates a request source's `userData` against the {@link RouteSchemas|Standard Schema} registered
705
+ * for its label on the crawler's schema-router (if any), throwing a {@link RequestValidationError} on
706
+ * mismatch. A no-op when the user's request handler is not a schema-router, or no schema is registered for
707
+ * the request's label. Applied by the crawler on the add paths it owns — `crawler.addRequests`,
708
+ * `crawler.run`, `context.addRequests` and `context.enqueueLinks`.
709
+ */
710
+ private validateRequestUserData;
484
711
  useState<State extends Dictionary = Dictionary>(defaultValue?: State): Promise<State>;
712
+ protected getPendingRequestCountApproximation(): Promise<number>;
713
+ protected calculateEnqueuedRequestLimit(explicitLimit?: number): Promise<number | undefined>;
714
+ protected handleSkippedRequest(options: Parameters<SkippedRequestCallback>[0]): Promise<void>;
715
+ private logOncePerRun;
485
716
  /**
486
717
  * Adds requests to the queue in batches. By default, it will resolve after the initial batch is added, and continue
487
718
  * adding the rest in background. You can configure the batch size via `batchSize` option and the sleep time in between
@@ -493,15 +724,15 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
493
724
  * @param requests The requests to add
494
725
  * @param options Options for the request queue
495
726
  */
496
- addRequests(requests: ReadonlyDeep<(string | Source)[]>, options?: CrawlerAddRequestsOptions): Promise<CrawlerAddRequestsResult>;
727
+ addRequests(requests: ReadonlyDeep<TypedRequestsLike<Routes>>, options?: CrawlerAddRequestsOptions): Promise<CrawlerAddRequestsResult>;
497
728
  /**
498
729
  * Pushes data to the specified {@link Dataset}, or the default crawler {@link Dataset} by calling {@link Dataset.pushData}.
499
730
  */
500
- pushData(data: Parameters<Dataset['pushData']>[0], datasetIdOrName?: string): Promise<void>;
731
+ pushData(data: Parameters<Dataset['pushData']>[0], datasetIdentifier?: string | StorageIdentifier): Promise<void>;
501
732
  /**
502
733
  * Retrieves the specified {@link Dataset}, or the default crawler {@link Dataset}.
503
734
  */
504
- getDataset(idOrName?: string): Promise<Dataset>;
735
+ getDataset(identifier?: string | StorageIdentifier): Promise<Dataset>;
505
736
  /**
506
737
  * Retrieves data from the default crawler {@link Dataset} by calling {@link Dataset.getData}.
507
738
  */
@@ -511,45 +742,103 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
511
742
  * Supported formats are currently 'json' and 'csv', and will be inferred from the `path` automatically.
512
743
  */
513
744
  exportData<Data>(path: string, format?: 'json' | 'csv', options?: DatasetExportOptions): Promise<Data[]>;
514
- protected _init(): Promise<void>;
515
- protected runRequestHandler(crawlingContext: CrawlingContext): Promise<void>;
745
+ /**
746
+ * Initializes the crawler.
747
+ */
748
+ protected init(): Promise<void>;
749
+ /**
750
+ * The navigation timeout (pre-navigation hooks, navigation, and post-navigation hooks) in milliseconds, used
751
+ * to size the internal request timeout. `BasicCrawler` has no navigation phase, so this is 0; the HTTP and
752
+ * browser crawlers override it with their `navigationTimeoutSecs`.
753
+ */
754
+ protected getNavigationTimeoutMillis(): number;
755
+ /**
756
+ * Races the request against the internal timeout (see {@link raceWithTimeout}), sized to outlast the phases
757
+ * that have their own timeout - the navigation, its hooks, and the request handler - so a legitimately slow
758
+ * request, a per-route override, or a low `CRAWLEE_INTERNAL_TIMEOUT` is not cut short mid-phase. It takes
759
+ * whichever is larger: the configured internal timeout, or this request's combined phase budget.
760
+ */
761
+ private withRequestTimeout;
762
+ /**
763
+ * The request handler timeout for a request with the given route label. A router route may override the
764
+ * crawler's own `requestHandlerTimeoutSecs`; anything else falls back to `fallbackMillis`.
765
+ *
766
+ * @param label The request's route label, or `undefined` for the default route / no specific request.
767
+ * @param fallbackMillis Timeout to use when no route overrides it.
768
+ */
769
+ private resolveRequestHandlerTimeoutMillis;
770
+ /**
771
+ * The timeout the router route with the given label asked for, or `undefined` when it did not override one
772
+ * (or the request handler is not a router at all).
773
+ */
774
+ private getRouteTimeoutMillis;
775
+ protected runRequestHandler(crawlingContext: ExtendedContext): Promise<void>;
776
+ /**
777
+ * Runs `callback` inside a {@link StorageTransaction}, unless transactional storage is disabled.
778
+ * Deliberately does **not** commit on return - `handleRequest` swallows request handler failures, so
779
+ * a normal return says nothing about success. `handleRequest` owns the outcome.
780
+ */
781
+ private runInStorageTransaction;
516
782
  /**
517
783
  * Handles blocked request
518
784
  */
519
- protected _throwOnBlockedRequest(session: Session, statusCode: number): void;
785
+ protected throwOnBlockedRequest(statusCode: number): void;
520
786
  private isAllowedBasedOnRobotsTxtFile;
787
+ /**
788
+ * Records an HTTP 429 against the URL's domain so the request manager can pace the retry.
789
+ *
790
+ * @param retryAfterHeader The raw `Retry-After` response header, if the server sent one.
791
+ * @returns `true` if a manager took responsibility for the delay, in which case the caller should throw
792
+ * {@link RequestThrottledError} rather than treating the response as a blocked session.
793
+ */
794
+ protected recordDomainRateLimit(url: string, retryAfterHeader?: string | null): boolean;
795
+ /**
796
+ * Hands a robots.txt `Crawl-delay` to the request manager, warning if nothing is able to honour it.
797
+ *
798
+ * The warning is driven by whether the delay was actually accepted rather than by the type of the manager,
799
+ * because a manager that does throttle still drops the delay for a domain missing from its `domains` list.
800
+ */
801
+ private applyCrawlDelay;
521
802
  protected getRobotsTxtFileForUrl(url: string): Promise<RobotsTxtFile | undefined>;
522
- protected _pauseOnMigration(): Promise<void>;
803
+ private pauseOnMigration;
523
804
  /**
524
- * Fetches request from either RequestList or RequestQueue. If request comes from a RequestList
525
- * and RequestQueue is present then enqueues it to the queue first.
805
+ * Fetches the next request to process from the underlying request provider.
526
806
  */
527
- protected _fetchNextRequest(): Promise<Request<Dictionary> | null | undefined>;
807
+ private fetchNextRequest;
528
808
  /**
529
809
  * Delays processing of the request based on the `sameDomainDelaySecs` option,
530
810
  * adding it back to the queue after the timeout passes. Returns `true` if the request
531
811
  * should be ignored and will be reclaimed to the queue once ready.
532
812
  */
533
- protected delayRequest(request: Request, source: IRequestList | RequestProvider): boolean;
813
+ private delayRequest;
814
+ /** Handles a single request - runs the request handler with retries, error handling, and lifecycle management. */
815
+ private handleRequest;
534
816
  /**
535
- * Wrapper around requestHandler that fetches requests from RequestList/RequestQueue
536
- * then retries them in a case of an error, etc.
817
+ * Wrapper around the crawling context's `enqueueLinks` method:
818
+ * - Injects `crawlDepth` to each request being added based on the crawling context request.
819
+ * - Provides defaults for the `enqueueLinks` options based on the crawler configuration.
820
+ * - These options can be overridden by the user.
821
+ * @internal
537
822
  */
538
- protected _runTaskFunction(): Promise<void>;
823
+ protected enqueueLinksWithCrawlDepth(options: SetRequired<EnqueueLinksOptions, 'urls'>, request: Request<Dictionary>, requestManager: IRequestManager): Promise<BatchAddRequestsResult>;
539
824
  /**
540
- * Run async callback with given timeout and retry.
825
+ * Generator function that yields requests injected with the given crawl depth.
826
+ * @internal
827
+ */
828
+ protected addCrawlDepthRequestGenerator(requests: RequestsLike, newRequestDepth: number): AsyncGenerator<Source, void, undefined>;
829
+ /**
830
+ * Run async callback with given timeout and retry. Returns the result of the callback.
541
831
  * @ignore
542
832
  */
543
- protected _timeoutAndRetry(handler: () => Promise<unknown>, timeout: number, error: Error | string, maxRetries?: number, retried?: number): Promise<void>;
833
+ private timeoutAndRetry;
544
834
  /**
545
835
  * Returns true if either RequestList or RequestQueue have a request ready for processing.
546
836
  */
547
- protected _isTaskReadyFunction(): Promise<boolean>;
837
+ private isTaskReadyFunction;
548
838
  /**
549
839
  * Returns true if both RequestList and RequestQueue have all requests finished.
550
840
  */
551
- protected _defaultIsFinishedFunction(): Promise<boolean>;
552
- private _rotateSession;
841
+ private defaultIsFinishedFunction;
553
842
  /**
554
843
  * Unwraps errors thrown by the context pipeline to get the actual user error.
555
844
  * RequestHandlerError and ContextPipelineInitializationError wrap the actual error.
@@ -557,39 +846,37 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
557
846
  private unwrapError;
558
847
  /**
559
848
  * Handles errors thrown by user provided requestHandler()
849
+ *
850
+ * @param request The request object, passed separately to circumvent potential dynamic logic in crawlingContext.request
560
851
  */
561
- protected _requestFunctionErrorHandler(error: Error, crawlingContext: CrawlingContext, source: IRequestList | RequestProvider): Promise<void>;
562
- protected _tagUserHandlerError<T>(cb: () => unknown): Promise<T>;
563
- protected _handleFailedRequestHandler(crawlingContext: CrawlingContext, error: Error): Promise<void>;
852
+ private requestFunctionErrorHandler;
853
+ private handleFailedRequestHandler;
564
854
  /**
565
855
  * Resolves the most verbose error message from a thrown error
566
856
  * @param error The error received
567
857
  * @returns The message to be logged
568
858
  */
569
- protected _getMessageFromError(error: Error, forceStack?: boolean): string | TimeoutError | undefined;
570
- protected _canRequestBeRetried(request: Request, error: Error): boolean;
859
+ protected getMessageFromError(error: Error, forceStack?: boolean): string | TimeoutError | undefined;
571
860
  /**
572
- * Updates handledRequestsCount from possibly stored counts,
573
- * usually after worker migration. Since one of the stores
574
- * needs to have priority when both are present,
575
- * it is the request queue, because generally, the request
576
- * list will first be dumped into the queue and then left
577
- * empty.
861
+ * Whether the session should be spared for this error - either because it was already retired, or because the
862
+ * failure says nothing about the session (a rate limit is a property of the domain).
578
863
  */
579
- protected _loadHandledRequestCount(): Promise<void>;
580
- protected _executeHooks<HookLike extends (...args: any[]) => Awaitable<void>>(hooks: HookLike[], ...args: Parameters<HookLike>): Promise<void>;
864
+ private errorAbsolvesSession;
865
+ private canRequestBeRetried;
581
866
  /**
582
- * Function for cleaning up after all request are processed.
583
- * @ignore
867
+ * Stops the crawler immediately.
868
+ *
869
+ * This method doesn't wait for currently active requests to finish.
870
+ *
871
+ * To stop the crawler gracefully (waiting for all running requests to finish), use {@link BasicCrawler.stop|`crawler.stop()`} instead.
584
872
  */
585
873
  teardown(): Promise<void>;
586
- protected _getCookieHeaderFromRequest(request: Request): string;
587
- private _getRequestQueue;
588
- protected requestMatchesEnqueueStrategy(request: Request): boolean;
874
+ protected getCookieHeaderFromRequest(request: Request): string;
875
+ private requestMatchesEnqueueStrategy;
589
876
  }
590
877
  export interface CreateContextOptions {
591
878
  request: Request;
592
- session?: Session;
879
+ session: ISession;
593
880
  proxyInfo?: ProxyInfo;
594
881
  }
595
882
  export interface CrawlerAddRequestsOptions extends AddRequestsBatchedOptions {
@@ -598,9 +885,14 @@ export interface CrawlerAddRequestsResult extends AddRequestsBatchedResult {
598
885
  }
599
886
  export interface CrawlerRunOptions extends CrawlerAddRequestsOptions {
600
887
  /**
601
- * Whether to purge the RequestQueue before running the crawler again. Defaults to true, so it is possible to reprocess failed requests.
602
- * When disabled, only new requests will be considered. Note that even a failed request is considered as handled.
603
- * @default true
888
+ * Controls whether the request queue is purged between repeated `run()` calls on the same crawler instance.
889
+ * Purging clears all requests and resets internal counters, allowing the same URLs to be processed again.
890
+ *
891
+ * - **`undefined`** (default) — only the crawler's own (auto-created) queue is purged.
892
+ * A user-supplied `requestQueue` is left untouched.
893
+ * - **`true`** — the queue is always purged, even if it was supplied by the user.
894
+ * - **`false`** — nothing is purged. Only genuinely new requests will be processed;
895
+ * note that even a failed request is considered handled.
604
896
  */
605
897
  purgeRequestQueue?: boolean;
606
898
  }
@@ -628,5 +920,5 @@ export interface CrawlerRunOptions extends CrawlerAddRequestsOptions {
628
920
  * await crawler.run();
629
921
  * ```
630
922
  */
631
- export declare function createBasicRouter<Context extends BasicCrawlingContext = BasicCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, UserData>): RouterHandler<Context>;
632
- //# sourceMappingURL=basic-crawler.d.ts.map
923
+ export declare function createBasicRouter<Context extends BasicCrawlingContext = BasicCrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
924
+ export declare function createBasicRouter<Context extends BasicCrawlingContext = BasicCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;