@crawlee/basic 4.0.0-beta.182 → 4.0.0-beta.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/internals/autoscaling/autoscaled_pool.d.ts +3 -1
- package/internals/autoscaling/autoscaled_pool.js +7 -4
- package/internals/autoscaling/concurrency_system.d.ts +10 -3
- package/internals/basic-crawler.d.ts +9 -3
- package/internals/basic-crawler.js +20 -8
- package/internals/crawlers/crawler_commons.d.ts +3 -2
- package/internals/crawling_request.d.ts +86 -0
- package/internals/crawling_request.js +127 -0
- package/internals/enqueue_links/shared.d.ts +2 -1
- package/internals/enqueue_links/shared.js +1 -2
- package/internals/router.d.ts +4 -4
- package/internals/throttling_request_manager.js +1 -1
- package/package.json +7 -7
package/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './internals/autoscaling/index.js';
|
|
|
3
3
|
export * from './internals/basic-crawler.js';
|
|
4
4
|
export * from './internals/cookie_utils.js';
|
|
5
5
|
export * from './internals/crawlers/index.js';
|
|
6
|
+
export * from './internals/crawling_request.js';
|
|
6
7
|
export * from './internals/enqueue_links/index.js';
|
|
7
8
|
export * from './internals/errors.js';
|
|
8
9
|
export * from './internals/http.js';
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './internals/autoscaling/index.js';
|
|
|
3
3
|
export * from './internals/basic-crawler.js';
|
|
4
4
|
export * from './internals/cookie_utils.js';
|
|
5
5
|
export * from './internals/crawlers/index.js';
|
|
6
|
+
export * from './internals/crawling_request.js';
|
|
6
7
|
export * from './internals/enqueue_links/index.js';
|
|
7
8
|
export * from './internals/errors.js';
|
|
8
9
|
export * from './internals/http.js';
|
|
@@ -46,8 +46,10 @@ export interface AutoscaledPoolOptions extends TaskLoopOptions {
|
|
|
46
46
|
/**
|
|
47
47
|
* A function that performs an asynchronous resource-intensive task.
|
|
48
48
|
* The function must either be labeled `async` or return a promise.
|
|
49
|
+
* @param taskId The id the task is {@link IConcurrencySystem.tryRegisterTaskStart|booked} under, so work done
|
|
50
|
+
* inside can be correlated with what the governor sees.
|
|
49
51
|
*/
|
|
50
|
-
runTaskFunction?: () => Promise<unknown>;
|
|
52
|
+
runTaskFunction?: (taskId: string) => Promise<unknown>;
|
|
51
53
|
/**
|
|
52
54
|
* Timeout in which the `runTaskFunction` needs to finish, given in seconds.
|
|
53
55
|
* @default 0
|
|
@@ -94,6 +94,8 @@ export class AutoscaledPool {
|
|
|
94
94
|
* (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
|
|
95
95
|
*/
|
|
96
96
|
#ownConcurrency = 0;
|
|
97
|
+
/** Source of the ids that pair a booking with the release of the same task. */
|
|
98
|
+
#nextTaskId = 0;
|
|
97
99
|
constructor(options) {
|
|
98
100
|
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs, taskTimeoutSecs, log, concurrencySystem, consumer, } = parseArgument(options, autoscaledPoolOptionsSchema, 'AutoscaledPoolOptions');
|
|
99
101
|
this.#log = log.child({ prefix: 'AutoscaledPool' });
|
|
@@ -299,9 +301,10 @@ export class AutoscaledPool {
|
|
|
299
301
|
// No tasks could mean that we're finished with all tasks.
|
|
300
302
|
return this.maybeFinish();
|
|
301
303
|
}
|
|
304
|
+
const taskId = String(this.#nextTaskId++);
|
|
302
305
|
// - the budget still has room. Re-checked atomically, because another pool sharing the governor may have taken
|
|
303
306
|
// the last free slot while we awaited `isTaskReadyFunction` above.
|
|
304
|
-
if (!this.#concurrencySystem.tryRegisterTaskStart(this.#consumer)) {
|
|
307
|
+
if (!this.#concurrencySystem.tryRegisterTaskStart(this.#consumer, taskId)) {
|
|
305
308
|
return done();
|
|
306
309
|
}
|
|
307
310
|
this.#ownConcurrency++;
|
|
@@ -315,10 +318,10 @@ export class AutoscaledPool {
|
|
|
315
318
|
// Execute the current task.
|
|
316
319
|
this.#log.perf('Running a task.');
|
|
317
320
|
if (this.#taskTimeoutMillis > 0) {
|
|
318
|
-
await addTimeoutToPromise(async () => this.#runTaskFunction(), this.#taskTimeoutMillis, `runTaskFunction timed out after ${this.#taskTimeoutMillis / 1000} seconds.`);
|
|
321
|
+
await addTimeoutToPromise(async () => this.#runTaskFunction(taskId), this.#taskTimeoutMillis, `runTaskFunction timed out after ${this.#taskTimeoutMillis / 1000} seconds.`);
|
|
319
322
|
}
|
|
320
323
|
else {
|
|
321
|
-
await this.#runTaskFunction();
|
|
324
|
+
await this.#runTaskFunction(taskId);
|
|
322
325
|
}
|
|
323
326
|
this.#log.perf('Task finished.');
|
|
324
327
|
// Run task after the previous one finished. Only on success: a failed task rejects the pool, and
|
|
@@ -340,7 +343,7 @@ export class AutoscaledPool {
|
|
|
340
343
|
}
|
|
341
344
|
}
|
|
342
345
|
finally {
|
|
343
|
-
this.#concurrencySystem.registerTaskEnd(this.#consumer);
|
|
346
|
+
this.#concurrencySystem.registerTaskEnd(this.#consumer, taskId);
|
|
344
347
|
this.#ownConcurrency--;
|
|
345
348
|
}
|
|
346
349
|
return undefined;
|
|
@@ -131,10 +131,17 @@ export interface IConcurrencySystem {
|
|
|
131
131
|
*
|
|
132
132
|
* Must be an *atomic* (synchronous) check-and-book: several pools may share one governor, and a check separated
|
|
133
133
|
* from the booking by an `await` lets two of them claim the last free slot at once.
|
|
134
|
+
* @param taskId Identifies the booked task among the ones `consumer` has in flight, and is repeated by the
|
|
135
|
+
* {@link IConcurrencySystem.registerTaskEnd|release} — pairing the two is what lets an implementation time a
|
|
136
|
+
* task or tell which ones are still running. Nothing beyond that is promised: another consumer may be using the
|
|
137
|
+
* same id at the same time, and an id may come back around once its task has ended.
|
|
134
138
|
*/
|
|
135
|
-
tryRegisterTaskStart(consumer: ConcurrencyConsumer): boolean;
|
|
136
|
-
/**
|
|
137
|
-
|
|
139
|
+
tryRegisterTaskStart(consumer: ConcurrencyConsumer, taskId: string): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Returns a task's slot to `consumer`'s budget. Called once the task settles (resolve or reject).
|
|
142
|
+
* @param taskId The id the task was {@link IConcurrencySystem.tryRegisterTaskStart|booked} under.
|
|
143
|
+
*/
|
|
144
|
+
registerTaskEnd(consumer: ConcurrencyConsumer, taskId: string): void;
|
|
138
145
|
}
|
|
139
146
|
/**
|
|
140
147
|
* The shareable "governor" behind an {@link AutoscaledPool}: it decides whether there is free compute for one more
|
|
@@ -12,6 +12,7 @@ import { ConcurrencySystem } from './autoscaling/concurrency_system.js';
|
|
|
12
12
|
import type { FinalStatistics } from './autoscaling/system_status.js';
|
|
13
13
|
import { ContextPipeline } from './crawlers/context_pipeline.js';
|
|
14
14
|
import type { CrawlingContext, TypedRequestsLike } from './crawlers/crawler_commons.js';
|
|
15
|
+
import { CrawlingRequest } from './crawling_request.js';
|
|
15
16
|
import type { IStatistics, StatisticState } from './crawlers/statistics.js';
|
|
16
17
|
import type { EnqueueUrlsOptions, SkippedRequestCallback } from './enqueue_links/index.js';
|
|
17
18
|
import type { GetUserDataFromRequest, RouterHandler, RouterRoutes } from './router.js';
|
|
@@ -54,7 +55,7 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
|
|
|
54
55
|
* To make this work, we should **always**
|
|
55
56
|
* let our function throw exceptions rather than catch them.
|
|
56
57
|
* The exceptions are logged to the request using the
|
|
57
|
-
* {@link
|
|
58
|
+
* {@link CrawlingRequest.pushErrorMessage|`request.pushErrorMessage()`} function.
|
|
58
59
|
*/
|
|
59
60
|
requestHandler?: RouterHandler<ExtendedContext, Routes> | RequestHandler<ExtendedContext>;
|
|
60
61
|
/**
|
|
@@ -441,7 +442,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
441
442
|
* This is used e.g. in the {@link AdaptivePlaywrightCrawler|`AdaptivePlaywrightCrawler`}.
|
|
442
443
|
*/
|
|
443
444
|
get basicContextPipeline(): ContextPipeline<{
|
|
444
|
-
request:
|
|
445
|
+
request: CrawlingRequest;
|
|
445
446
|
}, CrawlingContext>;
|
|
446
447
|
get contextPipeline(): ContextPipeline<CrawlingContext, ExtendedContext>;
|
|
447
448
|
/** Whether a `run()` on this instance has already finished - a repeated one continues where it left off. */
|
|
@@ -534,6 +535,11 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
534
535
|
*/
|
|
535
536
|
protected buildContextPipeline(): ContextPipeline<CrawlingContext, CrawlingContext>;
|
|
536
537
|
private createBaseContext;
|
|
538
|
+
/**
|
|
539
|
+
* Fetches the next request and rebuilds it as a {@link CrawlingRequest}. A copy rather than the manager's
|
|
540
|
+
* instance: managers key on `uniqueKey`/`id`, and it is this copy that flows back into `reclaimRequest` /
|
|
541
|
+
* `markRequestAsHandled`, so nothing observes the difference.
|
|
542
|
+
*/
|
|
537
543
|
private resolveRequest;
|
|
538
544
|
private resolveSession;
|
|
539
545
|
private createContextHelpers;
|
|
@@ -780,7 +786,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
780
786
|
private requestMatchesEnqueueStrategy;
|
|
781
787
|
}
|
|
782
788
|
export interface CreateContextOptions {
|
|
783
|
-
request:
|
|
789
|
+
request: CrawlingRequest;
|
|
784
790
|
session: ISession;
|
|
785
791
|
proxyInfo?: ProxyInfo;
|
|
786
792
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { bindMethodsToServiceLocator, Configuration, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, getObjectType, KeyValueStore, log, NonRetryableError, OwnedOrInjected, purgeDefaultStorages, RequestManagerTandem, RequestQueue,
|
|
3
|
+
import { bindMethodsToServiceLocator, Configuration, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, getObjectType, KeyValueStore, log, NonRetryableError, OwnedOrInjected, purgeDefaultStorages, RequestManagerTandem, RequestQueue, ServiceLocator, serviceLocator, SessionError, validators, withDirectStorageAccess, } from '@crawlee/core';
|
|
4
4
|
import { BaseHttpClient, FetchHttpClient } from '@crawlee/http-client';
|
|
5
5
|
import { isAsyncIterable, isIterable, parseArgument, ROTATE_PROXY_ERRORS, schemas } from '@crawlee/utils/internal';
|
|
6
6
|
import { RobotsTxtFile } from '@crawlee/utils';
|
|
@@ -12,6 +12,7 @@ import { cryptoRandomObjectId } from '@apify/utilities';
|
|
|
12
12
|
import { ConcurrencySystem } from './autoscaling/concurrency_system.js';
|
|
13
13
|
import { mergeCookies } from './cookie_utils.js';
|
|
14
14
|
import { ContextPipeline } from './crawlers/context_pipeline.js';
|
|
15
|
+
import { CrawlingRequest, RequestState } from './crawling_request.js';
|
|
15
16
|
import { ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, MissingSessionError, NavigationSkippedError, PersistentRateLimitError, RequestHandlerError, RequestThrottledError, RetryRequestError, } from './errors.js';
|
|
16
17
|
import { Statistics } from './crawlers/statistics.js';
|
|
17
18
|
import { applyRequestTransform, buildEnqueueStrategyPatterns, constructUrlPatternObjects, createRequestOptions, createSkippedRequestArgs, filterRequestOptionsByPatterns, } from './enqueue_links/index.js';
|
|
@@ -653,12 +654,19 @@ export class BasicCrawler {
|
|
|
653
654
|
[deferredCleanupKey]: deferredCleanup,
|
|
654
655
|
};
|
|
655
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Fetches the next request and rebuilds it as a {@link CrawlingRequest}. A copy rather than the manager's
|
|
659
|
+
* instance: managers key on `uniqueKey`/`id`, and it is this copy that flows back into `reclaimRequest` /
|
|
660
|
+
* `markRequestAsHandled`, so nothing observes the difference.
|
|
661
|
+
*/
|
|
656
662
|
async resolveRequest() {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
request.loadedUrl = undefined;
|
|
663
|
+
const fetched = await this.timeoutAndRetry(this.fetchNextRequest.bind(this), this.internalTimeoutMillis, `Fetching next request timed out after ${this.internalTimeoutMillis / 1e3} seconds.`);
|
|
664
|
+
if (!fetched) {
|
|
665
|
+
return null;
|
|
661
666
|
}
|
|
667
|
+
const request = CrawlingRequest.fromSchema(fetched);
|
|
668
|
+
// Reset loadedUrl so an old one is not carried over to retries.
|
|
669
|
+
request.loadedUrl = undefined;
|
|
662
670
|
return request;
|
|
663
671
|
}
|
|
664
672
|
async resolveSession({ request }) {
|
|
@@ -1194,7 +1202,7 @@ export class BasicCrawler {
|
|
|
1194
1202
|
}
|
|
1195
1203
|
let [finalOptions] = filtered;
|
|
1196
1204
|
if (options.transformRequestFunction) {
|
|
1197
|
-
const transformed = applyRequestTransform([finalOptions], options.transformRequestFunction, (r) => allSkipped.push({ source: keepSkippedSource(r), reason:
|
|
1205
|
+
const transformed = applyRequestTransform([finalOptions], options.transformRequestFunction, (r) => allSkipped.push({ source: keepSkippedSource(r), reason: 'transform' }));
|
|
1198
1206
|
if (transformed.length === 0) {
|
|
1199
1207
|
continue;
|
|
1200
1208
|
}
|
|
@@ -1590,6 +1598,11 @@ export class BasicCrawler {
|
|
|
1590
1598
|
if (typeof request === 'string') {
|
|
1591
1599
|
yield { url: request, crawlDepth: newRequestDepth };
|
|
1592
1600
|
}
|
|
1601
|
+
else if (request instanceof Request) {
|
|
1602
|
+
const crawlee = (request.userData.__crawlee ??= {});
|
|
1603
|
+
crawlee.crawlDepth ??= newRequestDepth;
|
|
1604
|
+
yield request;
|
|
1605
|
+
}
|
|
1593
1606
|
else {
|
|
1594
1607
|
request.crawlDepth ??= newRequestDepth;
|
|
1595
1608
|
yield request;
|
|
@@ -1809,8 +1822,7 @@ export class BasicCrawler {
|
|
|
1809
1822
|
throw err;
|
|
1810
1823
|
}
|
|
1811
1824
|
const { url, loadedUrl } = request;
|
|
1812
|
-
|
|
1813
|
-
const strategy = request['enqueueStrategy'];
|
|
1825
|
+
const strategy = request.enqueueStrategy;
|
|
1814
1826
|
// No strategy set, so we assume it matches, or it was added outside of enqueueLinks
|
|
1815
1827
|
if (!strategy) {
|
|
1816
1828
|
return true;
|
|
@@ -2,6 +2,7 @@ import type { Awaitable, Dictionary, HttpRequestOptions, ISession, ProxyInfo, Se
|
|
|
2
2
|
import type { ReadonlyDeep } from 'type-fest';
|
|
3
3
|
import type { AddRequestsBatchedResult, CrawleeLogger, Dataset, KeyValueStore, Request, RequestOptions, Source, StorageIdentifier } from '@crawlee/core';
|
|
4
4
|
import type { EnqueueUrlsOptions } from '../enqueue_links/index.js';
|
|
5
|
+
import type { CrawlingRequest } from '../crawling_request.js';
|
|
5
6
|
/** @internal */
|
|
6
7
|
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
7
8
|
/**
|
|
@@ -69,9 +70,9 @@ export interface RestrictedCrawlingContext<UserData extends Dictionary = Diction
|
|
|
69
70
|
*/
|
|
70
71
|
proxyInfo?: ProxyInfo;
|
|
71
72
|
/**
|
|
72
|
-
* The
|
|
73
|
+
* The request being processed.
|
|
73
74
|
*/
|
|
74
|
-
request:
|
|
75
|
+
request: CrawlingRequest<UserData>;
|
|
75
76
|
/**
|
|
76
77
|
* This function allows you to push data to a {@link Dataset} specified by name, or the one currently used by the crawler.
|
|
77
78
|
*
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { EnqueueStrategyOption } from '@crawlee/core';
|
|
2
|
+
import { Request } from '@crawlee/core';
|
|
3
|
+
import type { Dictionary, RequestSchema } from '@crawlee/types';
|
|
4
|
+
export type SkippedRequestReason = 'robotsTxt' | 'limit' | 'enqueueLimit' | 'filters' | 'transform' | 'redirect' | 'depth';
|
|
5
|
+
export declare enum RequestState {
|
|
6
|
+
UNPROCESSED = 0,
|
|
7
|
+
BEFORE_NAV = 1,
|
|
8
|
+
AFTER_NAV = 2,
|
|
9
|
+
REQUEST_HANDLER = 3,
|
|
10
|
+
DONE = 4,
|
|
11
|
+
ERROR_HANDLER = 5,
|
|
12
|
+
ERROR = 6,
|
|
13
|
+
SKIPPED = 7
|
|
14
|
+
}
|
|
15
|
+
export interface PushErrorMessageOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Only push the error message without stack trace when true.
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
omitStack?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The crawler's per-request state, persisted inside `userData.__crawlee` so every storage carries it as plain user
|
|
24
|
+
* data. The settings are written by the `Request` constructor from `RequestOptions`; `state` only by a running crawler.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export interface CrawlingRequestData {
|
|
28
|
+
skipNavigation?: boolean;
|
|
29
|
+
crawlDepth?: number;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
maxRetries?: number;
|
|
32
|
+
enqueueStrategy?: EnqueueStrategyOption;
|
|
33
|
+
state?: RequestState;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A {@link Request} as seen by a crawler: the same stored record, with the crawler's own per-request state
|
|
37
|
+
* (`userData.__crawlee`) exposed as properties. This is what `request` is in every request handler.
|
|
38
|
+
*
|
|
39
|
+
* Requests fetched from a request manager are plain {@link Request}s; {@link BasicCrawler} rebuilds them
|
|
40
|
+
* as `CrawlingRequest`s before handing them to the pipeline.
|
|
41
|
+
* @category Sources
|
|
42
|
+
*/
|
|
43
|
+
export declare class CrawlingRequest<UserData extends Dictionary = Dictionary> extends Request<UserData> {
|
|
44
|
+
static fromSchema<UserData extends Dictionary = Dictionary>(schema: RequestSchema): CrawlingRequest<UserData>;
|
|
45
|
+
private get crawleeData();
|
|
46
|
+
/**
|
|
47
|
+
* Tells the crawler processing this request to skip the navigation and process the request directly.
|
|
48
|
+
*
|
|
49
|
+
* When this is set to `true`, the crawling context will not contain the results of the navigation
|
|
50
|
+
* (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
|
|
51
|
+
* Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
|
|
52
|
+
*/
|
|
53
|
+
get skipNavigation(): boolean;
|
|
54
|
+
set skipNavigation(value: boolean);
|
|
55
|
+
/**
|
|
56
|
+
* Depth of the request in the current crawl tree.
|
|
57
|
+
* Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
|
|
58
|
+
*/
|
|
59
|
+
get crawlDepth(): number;
|
|
60
|
+
set crawlDepth(value: number);
|
|
61
|
+
/** ID of a session to use for this request. When set, the crawler will fetch this session from the session pool instead of creating a new one. */
|
|
62
|
+
get sessionId(): string | undefined;
|
|
63
|
+
set sessionId(value: string | undefined);
|
|
64
|
+
/** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
|
|
65
|
+
get maxRetries(): number | undefined;
|
|
66
|
+
set maxRetries(value: number | undefined);
|
|
67
|
+
/** Describes the request's current lifecycle state. */
|
|
68
|
+
get state(): RequestState;
|
|
69
|
+
set state(value: RequestState);
|
|
70
|
+
/** The strategy the request was enqueued under, if it came from `enqueueLinks`. @internal */
|
|
71
|
+
get enqueueStrategy(): EnqueueStrategyOption | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Stores information about an error that occurred during processing of this request.
|
|
74
|
+
*
|
|
75
|
+
* You should always use Error instances when throwing errors in JavaScript.
|
|
76
|
+
*
|
|
77
|
+
* Nevertheless, to improve the debugging experience when using third party libraries
|
|
78
|
+
* that may not always throw an Error instance, the function performs a type
|
|
79
|
+
* inspection of the passed argument and attempts to extract as much information
|
|
80
|
+
* as possible, since just throwing a bad type error makes any debugging rather difficult.
|
|
81
|
+
*
|
|
82
|
+
* @param errorOrMessage Error object or error message to be stored in the request.
|
|
83
|
+
* @param [options]
|
|
84
|
+
*/
|
|
85
|
+
pushErrorMessage(errorOrMessage: unknown, options?: PushErrorMessageOptions): void;
|
|
86
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import util from 'node:util';
|
|
2
|
+
import { Request } from '@crawlee/core';
|
|
3
|
+
export var RequestState;
|
|
4
|
+
(function (RequestState) {
|
|
5
|
+
RequestState[RequestState["UNPROCESSED"] = 0] = "UNPROCESSED";
|
|
6
|
+
RequestState[RequestState["BEFORE_NAV"] = 1] = "BEFORE_NAV";
|
|
7
|
+
RequestState[RequestState["AFTER_NAV"] = 2] = "AFTER_NAV";
|
|
8
|
+
RequestState[RequestState["REQUEST_HANDLER"] = 3] = "REQUEST_HANDLER";
|
|
9
|
+
RequestState[RequestState["DONE"] = 4] = "DONE";
|
|
10
|
+
RequestState[RequestState["ERROR_HANDLER"] = 5] = "ERROR_HANDLER";
|
|
11
|
+
RequestState[RequestState["ERROR"] = 6] = "ERROR";
|
|
12
|
+
RequestState[RequestState["SKIPPED"] = 7] = "SKIPPED";
|
|
13
|
+
})(RequestState || (RequestState = {}));
|
|
14
|
+
/**
|
|
15
|
+
* A {@link Request} as seen by a crawler: the same stored record, with the crawler's own per-request state
|
|
16
|
+
* (`userData.__crawlee`) exposed as properties. This is what `request` is in every request handler.
|
|
17
|
+
*
|
|
18
|
+
* Requests fetched from a request manager are plain {@link Request}s; {@link BasicCrawler} rebuilds them
|
|
19
|
+
* as `CrawlingRequest`s before handing them to the pipeline.
|
|
20
|
+
* @category Sources
|
|
21
|
+
*/
|
|
22
|
+
export class CrawlingRequest extends Request {
|
|
23
|
+
static fromSchema(schema) {
|
|
24
|
+
return super.fromSchema(schema);
|
|
25
|
+
}
|
|
26
|
+
get crawleeData() {
|
|
27
|
+
return (this.userData.__crawlee ??= {});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Tells the crawler processing this request to skip the navigation and process the request directly.
|
|
31
|
+
*
|
|
32
|
+
* When this is set to `true`, the crawling context will not contain the results of the navigation
|
|
33
|
+
* (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
|
|
34
|
+
* Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
|
|
35
|
+
*/
|
|
36
|
+
get skipNavigation() {
|
|
37
|
+
return this.crawleeData.skipNavigation ?? false;
|
|
38
|
+
}
|
|
39
|
+
set skipNavigation(value) {
|
|
40
|
+
this.crawleeData.skipNavigation = value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Depth of the request in the current crawl tree.
|
|
44
|
+
* Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
|
|
45
|
+
*/
|
|
46
|
+
get crawlDepth() {
|
|
47
|
+
return this.crawleeData.crawlDepth ?? 0;
|
|
48
|
+
}
|
|
49
|
+
set crawlDepth(value) {
|
|
50
|
+
this.crawleeData.crawlDepth = value;
|
|
51
|
+
}
|
|
52
|
+
/** ID of a session to use for this request. When set, the crawler will fetch this session from the session pool instead of creating a new one. */
|
|
53
|
+
get sessionId() {
|
|
54
|
+
return this.crawleeData.sessionId;
|
|
55
|
+
}
|
|
56
|
+
set sessionId(value) {
|
|
57
|
+
this.crawleeData.sessionId = value;
|
|
58
|
+
}
|
|
59
|
+
/** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
|
|
60
|
+
get maxRetries() {
|
|
61
|
+
return this.crawleeData.maxRetries;
|
|
62
|
+
}
|
|
63
|
+
set maxRetries(value) {
|
|
64
|
+
this.crawleeData.maxRetries = value;
|
|
65
|
+
}
|
|
66
|
+
/** Describes the request's current lifecycle state. */
|
|
67
|
+
get state() {
|
|
68
|
+
return this.crawleeData.state ?? RequestState.UNPROCESSED;
|
|
69
|
+
}
|
|
70
|
+
set state(value) {
|
|
71
|
+
this.crawleeData.state = value;
|
|
72
|
+
}
|
|
73
|
+
/** The strategy the request was enqueued under, if it came from `enqueueLinks`. @internal */
|
|
74
|
+
get enqueueStrategy() {
|
|
75
|
+
return this.crawleeData.enqueueStrategy;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Stores information about an error that occurred during processing of this request.
|
|
79
|
+
*
|
|
80
|
+
* You should always use Error instances when throwing errors in JavaScript.
|
|
81
|
+
*
|
|
82
|
+
* Nevertheless, to improve the debugging experience when using third party libraries
|
|
83
|
+
* that may not always throw an Error instance, the function performs a type
|
|
84
|
+
* inspection of the passed argument and attempts to extract as much information
|
|
85
|
+
* as possible, since just throwing a bad type error makes any debugging rather difficult.
|
|
86
|
+
*
|
|
87
|
+
* @param errorOrMessage Error object or error message to be stored in the request.
|
|
88
|
+
* @param [options]
|
|
89
|
+
*/
|
|
90
|
+
pushErrorMessage(errorOrMessage, options = {}) {
|
|
91
|
+
const { omitStack } = options;
|
|
92
|
+
let message;
|
|
93
|
+
const type = typeof errorOrMessage;
|
|
94
|
+
if (type === 'object') {
|
|
95
|
+
if (!errorOrMessage) {
|
|
96
|
+
message = 'null';
|
|
97
|
+
}
|
|
98
|
+
else if (errorOrMessage instanceof Error) {
|
|
99
|
+
message = omitStack
|
|
100
|
+
? errorOrMessage.message
|
|
101
|
+
: // .stack includes the message
|
|
102
|
+
errorOrMessage.stack;
|
|
103
|
+
}
|
|
104
|
+
else if (Reflect.has(Object(errorOrMessage), 'message')) {
|
|
105
|
+
message = Reflect.get(Object(errorOrMessage), 'message');
|
|
106
|
+
}
|
|
107
|
+
else if (errorOrMessage.toString() !== '[object Object]') {
|
|
108
|
+
message = errorOrMessage.toString();
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
try {
|
|
112
|
+
message = util.inspect(errorOrMessage);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
message = 'Unable to extract any message from the received object.';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (type === 'undefined') {
|
|
120
|
+
message = 'undefined';
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
message = errorOrMessage.toString();
|
|
124
|
+
}
|
|
125
|
+
this.errorMessages.push(message);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import type { EnqueueStrategyOption, RequestOptions,
|
|
3
|
+
import type { EnqueueStrategyOption, RequestOptions, Source } from '@crawlee/core';
|
|
4
4
|
import { Request } from '@crawlee/core';
|
|
5
|
+
import type { SkippedRequestReason } from '../crawling_request.js';
|
|
5
6
|
export interface UrlPatternObject {
|
|
6
7
|
glob?: string;
|
|
7
8
|
regexp?: RegExp;
|
|
@@ -27,10 +27,9 @@ export const urlPatternSchema = z.union([
|
|
|
27
27
|
* @ignore
|
|
28
28
|
*/
|
|
29
29
|
export function createSkippedRequestArgs(source, reason) {
|
|
30
|
-
const sourceReason = typeof source === 'string' ? undefined : source.skippedReason;
|
|
31
30
|
let request;
|
|
32
31
|
return {
|
|
33
|
-
reason
|
|
32
|
+
reason,
|
|
34
33
|
get request() {
|
|
35
34
|
request ??=
|
|
36
35
|
source instanceof Request
|
package/internals/router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CrawlingRequest } from './crawling_request.js';
|
|
2
2
|
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
3
3
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import type { CrawlingContext, LoadedRequest, RestrictedCrawlingContext, TypedContextAddRequests, TypedContextEnqueueLinks } from './crawlers/crawler_commons.js';
|
|
@@ -14,7 +14,7 @@ export declare const defaultRoute: unique symbol;
|
|
|
14
14
|
* request under a declared label requires the matching `userData` shape.
|
|
15
15
|
*/
|
|
16
16
|
export type RouterHandlerContext<Context, UserData extends Dictionary, Routes extends Record<keyof Routes, Dictionary>> = Omit<Context, 'request' | 'addRequests' | 'enqueueLinks'> & {
|
|
17
|
-
request: LoadedRequest<
|
|
17
|
+
request: LoadedRequest<CrawlingRequest<UserData>>;
|
|
18
18
|
addRequests: TypedContextAddRequests<Routes>;
|
|
19
19
|
} & (Context extends {
|
|
20
20
|
enqueueLinks: infer EnqueueLinks;
|
|
@@ -68,7 +68,7 @@ export type RouterLabel<Routes extends Record<keyof Routes, Dictionary>> = strin
|
|
|
68
68
|
export interface RouterHandler<Context extends RestrictedCrawlingContext = CrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends Router<Context, Routes> {
|
|
69
69
|
(ctx: Context): Awaitable<void>;
|
|
70
70
|
}
|
|
71
|
-
export type GetUserDataFromRequest<T> = T extends
|
|
71
|
+
export type GetUserDataFromRequest<T> = T extends CrawlingRequest<infer Y> ? Y : never;
|
|
72
72
|
/**
|
|
73
73
|
* Per-route overrides, passed as the last argument of {@link Router.addHandler|`addHandler`} and
|
|
74
74
|
* {@link Router.addDefaultHandler|`addDefaultHandler`}.
|
|
@@ -85,7 +85,7 @@ export interface RouteOptions {
|
|
|
85
85
|
}
|
|
86
86
|
export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary>> = {
|
|
87
87
|
[Label in keyof Routes]: (ctx: Omit<Context, 'request'> & {
|
|
88
|
-
request:
|
|
88
|
+
request: CrawlingRequest<Routes[Label]>;
|
|
89
89
|
}) => Awaitable<void>;
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
@@ -799,7 +799,7 @@ export class ThrottlingRequestManager {
|
|
|
799
799
|
async #migrateToSubQueue(request, inner) {
|
|
800
800
|
try {
|
|
801
801
|
const subManager = await this.#selectManagerOrThrow(request.url);
|
|
802
|
-
await subManager.addRequest(
|
|
802
|
+
await subManager.addRequest(Request.fromSchema({ ...request, id: undefined }));
|
|
803
803
|
}
|
|
804
804
|
catch (error) {
|
|
805
805
|
// Whatever the reason, the request is out of the wrapped manager and in nobody's hands, so it
|
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.184",
|
|
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": "^3.0.1",
|
|
43
43
|
"@apify/timeout": "^1.0.1",
|
|
44
44
|
"@apify/utilities": "^3.0.1",
|
|
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.184",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.184",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.184",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.184",
|
|
49
49
|
"@sapphire/async-queue": "^1.5.5",
|
|
50
50
|
"@standard-schema/spec": "^1.0.0",
|
|
51
51
|
"csv-stringify": "^6.5.2",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"zod": "^4.5.4"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
|
-
"@crawlee/impit-client": "4.0.0-beta.
|
|
60
|
+
"@crawlee/impit-client": "4.0.0-beta.184"
|
|
61
61
|
},
|
|
62
62
|
"lerna": {
|
|
63
63
|
"command": {
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "19ebeeb9bb90e38496cbe90565c8ebb02e102193"
|
|
70
70
|
}
|