@crawlee/core 4.0.0-beta.145 → 4.0.0-beta.147
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/package.json +6 -6
- package/storages/request_list.d.ts +2 -6
- package/storages/request_list.js +6 -9
- package/storages/request_loader.d.ts +47 -15
- package/storages/request_loader.js +36 -1
- package/storages/request_manager.d.ts +76 -0
- package/storages/request_manager_tandem.d.ts +12 -9
- package/storages/request_manager_tandem.js +29 -20
- package/storages/request_queue.d.ts +14 -17
- package/storages/request_queue.js +23 -28
- package/storages/sitemap_request_loader.d.ts +2 -6
- package/storages/sitemap_request_loader.js +11 -10
- package/storages/throttling_request_manager.d.ts +45 -62
- package/storages/throttling_request_manager.js +226 -92
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Dictionary } from '@crawlee/types';
|
|
2
2
|
import type { Configuration } from '../configuration.js';
|
|
3
3
|
import type { Request, Source } from '../request.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { RequestSourceStatus } from './request_loader.js';
|
|
5
|
+
import type { IRequestManager, PacingSignal, RequestsLike } from './request_manager.js';
|
|
5
6
|
import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
|
|
6
7
|
import type { StorageIdentifier } from './storage_instance_manager.js';
|
|
7
8
|
import type { StorageOpenOptions } from './utils.js';
|
|
@@ -12,30 +13,18 @@ import type { StorageOpenOptions } from './utils.js';
|
|
|
12
13
|
* {@link ThrottlingRequestManager} calls this once per configured domain, so every per-domain queue shares the
|
|
13
14
|
* concrete type and storage backend of the manager being wrapped.
|
|
14
15
|
*/
|
|
15
|
-
export type RequestManagerOpener<T extends IRequestManager = IRequestManager> = (identifier
|
|
16
|
-
/**
|
|
17
|
-
* A request manager that can pace requests per domain, as {@link ThrottlingRequestManager} does.
|
|
18
|
-
*
|
|
19
|
-
* The crawlers detect this structurally rather than by type, so a wrapper can opt in by forwarding these three
|
|
20
|
-
* methods without {@link IRequestManager} having to know that throttling exists.
|
|
21
|
-
*/
|
|
22
|
-
export interface SupportsDomainThrottling {
|
|
23
|
-
/** @see {@link ThrottlingRequestManager.recordDomainDelay} */
|
|
24
|
-
recordDomainDelay(url: string, retryAfterMs?: number | null): boolean;
|
|
25
|
-
/** @see {@link ThrottlingRequestManager.setCrawlDelay} */
|
|
26
|
-
setCrawlDelay(url: string, delaySeconds: number): boolean;
|
|
27
|
-
/** @see {@link ThrottlingRequestManager.assertNoStalledDomains} */
|
|
28
|
-
assertNoStalledDomains(): Promise<void>;
|
|
29
|
-
}
|
|
30
|
-
/** Whether `manager` can pace requests per domain. */
|
|
31
|
-
export declare function supportsDomainThrottling(manager: unknown): manager is SupportsDomainThrottling;
|
|
16
|
+
export type RequestManagerOpener<T extends IRequestManager = IRequestManager> = (identifier?: string | StorageIdentifier | null, options?: StorageOpenOptions) => Promise<T>;
|
|
32
17
|
/** Options for {@link ThrottlingRequestManager}. */
|
|
33
18
|
export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRequestManager> {
|
|
34
19
|
/**
|
|
35
20
|
* The request manager to wrap, usually a {@link RequestQueue}. Requests for domains that are not throttled
|
|
36
|
-
* are stored here.
|
|
21
|
+
* are stored here. May be a factory, so that the throttler can be constructed synchronously and the manager
|
|
22
|
+
* under it opened only on first use.
|
|
23
|
+
*
|
|
24
|
+
* Omitted, the default request queue is opened on first use through
|
|
25
|
+
* {@link ThrottlingRequestManagerOptions.requestManagerOpener|`requestManagerOpener`}.
|
|
37
26
|
*/
|
|
38
|
-
inner
|
|
27
|
+
inner?: T | (() => T | Promise<T>);
|
|
39
28
|
/**
|
|
40
29
|
* Which domains to throttle: a list of hostnames, or `'all'` for every domain the crawl encounters.
|
|
41
30
|
*
|
|
@@ -54,7 +43,8 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
|
|
|
54
43
|
/**
|
|
55
44
|
* A floor under the crawl delay of every throttled domain, in seconds - the proactive clock described on
|
|
56
45
|
* {@link ThrottlingRequestManager}. A domain whose robots.txt asks for a longer `Crawl-delay` gets the
|
|
57
|
-
* longer one; this is a minimum, not an override.
|
|
46
|
+
* longer one; this is a minimum, not an override. A `minIntervalEverywhere` {@link PacingSignal} raises
|
|
47
|
+
* this floor at runtime, and never lowers it.
|
|
58
48
|
* @default 0
|
|
59
49
|
*/
|
|
60
50
|
minCrawlDelaySecs?: number;
|
|
@@ -125,8 +115,9 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
|
|
|
125
115
|
*
|
|
126
116
|
* {@link ThrottlingRequestManager.fetchNextRequest|`fetchNextRequest()`} serves the domain that has been waiting
|
|
127
117
|
* longest and skips any that are backing off, falling back to the wrapped manager. It never blocks: while every
|
|
128
|
-
* remaining request belongs to a throttled domain it returns `null` and
|
|
129
|
-
* reports `
|
|
118
|
+
* remaining request belongs to a throttled domain it returns `null` and
|
|
119
|
+
* {@link ThrottlingRequestManager.checkReadiness|`checkReadiness()`} reports `waiting` with the moment the
|
|
120
|
+
* earliest of them comes due, so the crawler idles instead of holding a concurrency slot open.
|
|
130
121
|
*
|
|
131
122
|
* Each throttled domain runs two independent clocks, and may be dispatched to once **both** have run out:
|
|
132
123
|
* - **Backoff**, set by HTTP 429 responses - honouring `Retry-After`, and otherwise doubling from `baseDelaySecs`.
|
|
@@ -141,12 +132,19 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
|
|
|
141
132
|
* Which domains get those clocks is {@link ThrottlingRequestManagerOptions.domains|`domains`} - a list, or
|
|
142
133
|
* `'all'` for every domain the crawl encounters.
|
|
143
134
|
*
|
|
135
|
+
* Pass one as a crawler's `requestManager`; the `sameDomainDelaySecs` shorthand builds one with `domains: 'all'`
|
|
136
|
+
* and `throttleBy: 'registrableDomain'`. Construct it yourself to name the domains or tune the delays - one
|
|
137
|
+
* covering every domain also makes `sameDomainDelaySecs` land on it as a floor rather than adding a second pacer.
|
|
138
|
+
*
|
|
139
|
+
* Signals - 429s, robots.txt `Crawl-delay`, that floor - arrive through
|
|
140
|
+
* {@link IRequestManager.recordPacingSignal|`recordPacingSignal`}, which wrapping managers forward, so this
|
|
141
|
+
* works wherever it sits in a composition, including inside a {@link RequestManagerTandem}.
|
|
142
|
+
*
|
|
144
143
|
* **Example usage:**
|
|
145
144
|
*
|
|
146
145
|
* ```ts
|
|
147
146
|
* const crawler = new CheerioCrawler({
|
|
148
147
|
* requestManager: new ThrottlingRequestManager({
|
|
149
|
-
* inner: await RequestQueue.open(),
|
|
150
148
|
* domains: ['api.example.com', 'slow-site.org'],
|
|
151
149
|
* }),
|
|
152
150
|
* requestHandler: async ({ request }) => { ... },
|
|
@@ -155,39 +153,27 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
|
|
|
155
153
|
*
|
|
156
154
|
* @category Sources
|
|
157
155
|
*/
|
|
158
|
-
export declare class ThrottlingRequestManager<T extends IRequestManager = IRequestManager> implements IRequestManager
|
|
156
|
+
export declare class ThrottlingRequestManager<T extends IRequestManager = IRequestManager> implements IRequestManager {
|
|
159
157
|
#private;
|
|
160
158
|
private readonly config;
|
|
161
159
|
private readonly domainStates;
|
|
162
160
|
private readonly log;
|
|
163
161
|
constructor(options: ThrottlingRequestManagerOptions<T>, config?: Configuration);
|
|
164
|
-
/** The wrapped manager, holding every request whose domain is not throttled. */
|
|
165
|
-
get innerManager(): T;
|
|
166
|
-
/**
|
|
167
|
-
* Records a 429 response and puts the URL's domain into backoff.
|
|
168
|
-
*
|
|
169
|
-
* @returns `false` if the domain is not configured for throttling, in which case this is a no-op.
|
|
170
|
-
*/
|
|
171
|
-
recordDomainDelay(url: string, retryAfterMs?: number | null): boolean;
|
|
172
162
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* The first value wins, so a robots.txt re-fetch cannot change the cadence mid-crawl.
|
|
177
|
-
*
|
|
178
|
-
* @returns `false` if the domain is not throttled, in which case this is a no-op.
|
|
163
|
+
* The wrapped manager, holding every request whose domain is not throttled. `undefined` until an `inner`
|
|
164
|
+
* passed as a factory is resolved - reading this never forces it, because a getter should not open a queue
|
|
165
|
+
* behind a caller's back.
|
|
179
166
|
*/
|
|
180
|
-
|
|
167
|
+
get innerManager(): T | undefined;
|
|
181
168
|
/**
|
|
182
|
-
*
|
|
183
|
-
* {@link
|
|
184
|
-
* request through.
|
|
169
|
+
* Records a pacing signal: a refusal puts the URL's domain into backoff, a declared interval becomes its
|
|
170
|
+
* crawl delay, and a crawl-wide floor raises {@link recordEverywhereFloor|the floor under all of them}.
|
|
185
171
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
172
|
+
* @returns `false` if the domain the signal covers is not throttled, in which case this is a no-op.
|
|
173
|
+
* @throws If the signal's scope is one this manager cannot honour - see {@link assertScopeHonourable}.
|
|
174
|
+
* @inheritdoc
|
|
189
175
|
*/
|
|
190
|
-
|
|
176
|
+
recordPacingSignal(signal: PacingSignal): boolean;
|
|
191
177
|
addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
|
|
192
178
|
/**
|
|
193
179
|
* Adds requests in batches, routing each one to the manager that owns its domain.
|
|
@@ -203,34 +189,31 @@ export declare class ThrottlingRequestManager<T extends IRequestManager = IReque
|
|
|
203
189
|
getPendingCount(): Promise<number>;
|
|
204
190
|
getHandledCount(): Promise<number>;
|
|
205
191
|
/**
|
|
206
|
-
*
|
|
192
|
+
* Reports whether anything can be dispatched right now, and if not, when — or why never.
|
|
207
193
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
194
|
+
* One traversal of the domain clocks answers all of it: only domains whose delays have run out are probed,
|
|
195
|
+
* the rest merely contribute the moment they come due. Throttled requests count as outstanding work, so a
|
|
196
|
+
* crawler gated on this idles for the backoff instead of concluding it is done.
|
|
197
|
+
*
|
|
198
|
+
* `ready` from anywhere else outranks a stalling domain and is returned without looking at the stall clocks,
|
|
199
|
+
* so one hopeless domain never ends a crawl making progress elsewhere. It cannot outrank itself, though -
|
|
200
|
+
* see the traversal.
|
|
210
201
|
*/
|
|
211
|
-
|
|
212
|
-
/** Unlike {@link ThrottlingRequestManager.isEmpty}, throttled requests still count as outstanding work. */
|
|
213
|
-
isFinished(): Promise<boolean>;
|
|
202
|
+
checkReadiness(): Promise<RequestSourceStatus>;
|
|
214
203
|
/**
|
|
215
204
|
* Empties every manager and clears the accumulated backoff. A robots.txt `Crawl-delay` is a property of the
|
|
216
205
|
* site rather than of the run, so it survives.
|
|
217
206
|
*/
|
|
218
207
|
purge(): Promise<void>;
|
|
219
|
-
/**
|
|
220
|
-
* Empties the per-domain queues, leaving the wrapped manager alone.
|
|
221
|
-
*
|
|
222
|
-
* Those queues are this manager's own no matter who owns the one it wraps, which is what makes this safe to
|
|
223
|
-
* call where a full {@link ThrottlingRequestManager.purge|`purge()`} would not be.
|
|
224
|
-
*/
|
|
225
|
-
purgeDomainQueues(): Promise<void>;
|
|
226
208
|
setExpectedRequestProcessingTimeSecs(secs: number): Promise<void>;
|
|
227
209
|
/**
|
|
228
210
|
* Returns the next request from a domain that is not backing off, or from the inner manager.
|
|
229
211
|
*
|
|
230
212
|
* Returns `null` while every remaining request belongs to a throttled domain - it never waits the backoff
|
|
231
213
|
* out, because a consumer parked in here holds a concurrency slot, which the autoscaler reads as spare
|
|
232
|
-
* capacity and answers by scaling up. Callers poll instead, and
|
|
233
|
-
* reports `
|
|
214
|
+
* capacity and answers by scaling up. Callers poll instead, and
|
|
215
|
+
* {@link ThrottlingRequestManager.checkReadiness|`checkReadiness()`} reports `waiting` meanwhile so the
|
|
216
|
+
* crawler's task loop idles rather than spins.
|
|
234
217
|
*/
|
|
235
218
|
fetchNextRequest<R extends Dictionary = Dictionary>(): Promise<Request<R> | null>;
|
|
236
219
|
[Symbol.asyncIterator](): AsyncGenerator<Request<Dictionary>, void, unknown>;
|