@crawlee/core 4.0.0-beta.123 → 4.0.0-beta.125

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.
@@ -37,15 +37,58 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
37
37
  */
38
38
  inner: T;
39
39
  /**
40
- * Hostnames to throttle. Matching is case-insensitive and exact - wildcards such as `*.example.com` are not
41
- * supported, so list each subdomain you care about. Requests for any other domain bypass throttling entirely.
40
+ * Which domains to throttle: a list of hostnames, or `'all'` for every domain the crawl encounters.
42
41
  *
43
- * An internationalized domain may be given in either its unicode or its punycode form, and an IPv6 address
44
- * has to be bracketed (`[::1]`).
42
+ * Matching a listed hostname is case-insensitive and exact - wildcards such as `*.example.com` are not
43
+ * supported, so list each subdomain you care about (or set
44
+ * {@link ThrottlingRequestManagerOptions.throttleBy|`throttleBy: 'registrableDomain'`}). An
45
+ * internationalized domain may be given in either its unicode or its punycode form, and an IPv6 address has
46
+ * to be bracketed (`[::1]`). Requests for any other domain bypass throttling entirely.
47
+ *
48
+ * `'all'` gives each domain a queue of its own the first time it is seen, so that it can be held back
49
+ * without its requests being repeatedly popped and re-enqueued. One request queue per domain is not free,
50
+ * which is what {@link ThrottlingRequestManagerOptions.maxThrottledDomains|`maxThrottledDomains`} is
51
+ * there to bound.
52
+ */
53
+ domains: string[] | 'all';
54
+ /**
55
+ * A floor under the crawl delay of every throttled domain, in seconds - the proactive clock described on
56
+ * {@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.
58
+ * @default 0
59
+ */
60
+ minCrawlDelaySecs?: number;
61
+ /**
62
+ * What counts as "the same domain": the exact hostname, or the registrable domain it belongs to
63
+ * (`example.com` for `www.example.com`, `a.example.co.uk` and so on). Hosts with no registrable domain -
64
+ * IP addresses, `localhost` - are always throttled per hostname.
65
+ *
66
+ * Grouping by registrable domain gives subdomains a single pair of clocks and a single queue, which is what
67
+ * you want when the pacing is there to be polite to one server rather than to satisfy a specific host's
68
+ * rate limit.
69
+ * @default 'hostname'
70
+ */
71
+ throttleBy?: 'hostname' | 'registrableDomain';
72
+ /**
73
+ * The most domains a run may throttle at once. Exceeding it throws, rather than silently letting the
74
+ * throttling lapse - one request queue per domain is not free, and a crawl that discovers domains without
75
+ * bound would drown the storage backend in them.
76
+ *
77
+ * Only domains discovered under `domains: 'all'` count against this; an explicit list is taken at face value.
78
+ * @default 100
45
79
  */
46
- domains: string[];
80
+ maxThrottledDomains?: number;
47
81
  /**
48
- * Opens the per-domain queues, one per entry in `domains`, each under the alias `throttled-<domain>`.
82
+ * The key under which the discovered domain list is kept in the default key-value store, so that a restart
83
+ * with `purgeOnStart` disabled reopens their queues instead of stranding whatever they still hold. Only
84
+ * written under `domains: 'all'`.
85
+ *
86
+ * Give each manager its own key when running several of them against the same storage.
87
+ * @default 'CRAWLEE_THROTTLED_DOMAINS'
88
+ */
89
+ persistStateKey?: string;
90
+ /**
91
+ * Opens the per-domain queues, one per throttled domain, each under the alias `throttled-<domain>`.
49
92
  * @default RequestQueue.open
50
93
  */
51
94
  requestManagerOpener?: RequestManagerOpener<T>;
@@ -77,22 +120,26 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
77
120
  /**
78
121
  * A request manager that wraps another one and paces requests per domain.
79
122
  *
80
- * Requests for the configured {@link ThrottlingRequestManagerOptions.domains|`domains`} are routed into their own
81
- * queue when they are added, so each request lives in exactly one place and deduplication keeps working. Everything
82
- * else goes to the wrapped manager untouched.
123
+ * Requests for a throttled domain are routed into their own queue when they are added, so each request lives in
124
+ * exactly one place and deduplication keeps working. Everything else goes to the wrapped manager untouched.
83
125
  *
84
126
  * {@link ThrottlingRequestManager.fetchNextRequest|`fetchNextRequest()`} serves the domain that has been waiting
85
127
  * longest and skips any that are backing off, falling back to the wrapped manager. It never blocks: while every
86
128
  * remaining request belongs to a throttled domain it returns `null` and {@link ThrottlingRequestManager.isEmpty}
87
129
  * reports `true`, so the crawler idles instead of holding a concurrency slot open.
88
130
  *
89
- * Delays come from two places:
90
- * - HTTP 429 responses, honouring `Retry-After` and otherwise backing off exponentially. The crawlers report these
91
- * automatically; a request that is throttled is retried later without counting against `maxRequestRetries` and
131
+ * Each throttled domain runs two independent clocks, and may be dispatched to once **both** have run out:
132
+ * - **Backoff**, set by HTTP 429 responses - honouring `Retry-After`, and otherwise doubling from `baseDelaySecs`.
133
+ * Reactive and temporary: it decays once the domain stops turning us away. The crawlers report the 429s
134
+ * themselves; a request held back this way is retried later without counting against `maxRequestRetries` and
92
135
  * without penalising its session.
93
- * - robots.txt `Crawl-delay` directives, when `respectRobotsTxtFile` is enabled.
136
+ * - **Crawl delay**, the minimum interval between two dispatches to the domain, armed after each one. Proactive
137
+ * and constant: whatever the domain's robots.txt asks for, floored by
138
+ * {@link ThrottlingRequestManagerOptions.minCrawlDelaySecs|`minCrawlDelaySecs`}. Either may be absent, in
139
+ * which case the other one is the delay.
94
140
  *
95
- * This is opt-in: throttling only happens for a domain you list explicitly.
141
+ * Which domains get those clocks is {@link ThrottlingRequestManagerOptions.domains|`domains`} - a list, or
142
+ * `'all'` for every domain the crawl encounters.
96
143
  *
97
144
  * **Example usage:**
98
145
  *
@@ -109,40 +156,13 @@ export interface ThrottlingRequestManagerOptions<T extends IRequestManager = IRe
109
156
  * @category Sources
110
157
  */
111
158
  export declare class ThrottlingRequestManager<T extends IRequestManager = IRequestManager> implements IRequestManager, SupportsDomainThrottling {
159
+ #private;
112
160
  private readonly config;
113
- private readonly inner;
114
- private readonly requestManagerOpener;
115
- private readonly baseDelayMs;
116
- private readonly maxDelayMs;
117
- private readonly maxDomainStallMs;
118
161
  private readonly domainStates;
119
- private readonly subManagers;
120
162
  private readonly log;
121
- /**
122
- * Sub-managers are keyed by a stable alias, so with `purgeOnStart` disabled they outlive the process. They
123
- * must therefore be reopened for every configured domain rather than created on first insert - otherwise a
124
- * restart sees an empty map, reports the crawl finished, and strands whatever the previous run left in them.
125
- */
126
- private subManagersReady?;
127
- /** Batches still being added in the background; keeps {@link ThrottlingRequestManager.isFinished} honest. */
128
- private inProgressBatchCount;
129
- private readonly warnedAbout;
130
- private get hasThrottledDomains();
131
163
  constructor(options: ThrottlingRequestManagerOptions<T>, config?: Configuration);
132
164
  /** The wrapped manager, holding every request whose domain is not throttled. */
133
165
  get innerManager(): T;
134
- /** Warns once about sources that cannot be routed by domain, because their URLs are not known yet. */
135
- private warnIfNotRoutable;
136
- private warnOnce;
137
- private extractDomain;
138
- private getDomainState;
139
- private selectManager;
140
- /** Only valid once {@link ThrottlingRequestManager.ensureSubManagers} has resolved. */
141
- private managerForUrl;
142
- private ensureSubManagers;
143
- private getSubManagers;
144
- /** Configured domains that are not currently backing off, longest-overdue first. */
145
- private fetchableDomains;
146
166
  /**
147
167
  * Records a 429 response and puts the URL's domain into backoff.
148
168
  *
@@ -150,11 +170,12 @@ export declare class ThrottlingRequestManager<T extends IRequestManager = IReque
150
170
  */
151
171
  recordDomainDelay(url: string, retryAfterMs?: number | null): boolean;
152
172
  /**
153
- * Applies a robots.txt `Crawl-delay` to the URL's domain, as a minimum interval between dispatches.
173
+ * Records the `Crawl-delay` a domain's robots.txt asked for, which becomes its crawl delay unless
174
+ * {@link ThrottlingRequestManagerOptions.minCrawlDelaySecs|`minCrawlDelaySecs`} asks for longer.
154
175
  *
155
176
  * The first value wins, so a robots.txt re-fetch cannot change the cadence mid-crawl.
156
177
  *
157
- * @returns `false` if the domain is not configured for throttling, in which case this is a no-op.
178
+ * @returns `false` if the domain is not throttled, in which case this is a no-op.
158
179
  */
159
180
  setCrawlDelay(url: string, delaySeconds: number): boolean;
160
181
  /**
@@ -167,8 +188,6 @@ export declare class ThrottlingRequestManager<T extends IRequestManager = IReque
167
188
  * `Crawl-delay` is being obeyed, not stonewalled.
168
189
  */
169
190
  assertNoStalledDomains(): Promise<void>;
170
- /** Records that a domain let a request through, which ends any rate-limit run stall detection was timing. */
171
- private recordProgress;
172
191
  addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
173
192
  /**
174
193
  * Adds requests in batches, routing each one to the manager that owns its domain.
@@ -197,10 +216,14 @@ export declare class ThrottlingRequestManager<T extends IRequestManager = IReque
197
216
  * site rather than of the run, so it survives.
198
217
  */
199
218
  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>;
200
226
  setExpectedRequestProcessingTimeSecs(secs: number): Promise<void>;
201
- private forEachManager;
202
- private sumOverManagers;
203
- private everyManager;
204
227
  /**
205
228
  * Returns the next request from a domain that is not backing off, or from the inner manager.
206
229
  *