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