@crawlee/core 4.0.0-beta.98 → 4.0.0-rc.0
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/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +105 -97
- package/autoscaling/concurrency_system.d.ts +7 -22
- package/autoscaling/concurrency_system.js +112 -100
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +11 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/index.d.ts +1 -1
- package/autoscaling/index.js +1 -1
- package/autoscaling/load_signal.d.ts +8 -7
- package/autoscaling/load_signal.js +14 -12
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +41 -41
- package/autoscaling/snapshotter.d.ts +7 -10
- package/autoscaling/snapshotter.js +18 -18
- package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
- package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
- package/autoscaling/system_status.d.ts +9 -11
- package/autoscaling/system_status.js +13 -13
- package/configuration.d.ts +16 -16
- package/configuration.js +6 -6
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +14 -116
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/error_snapshotter.d.ts +2 -4
- package/crawlers/error_snapshotter.js +5 -5
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +164 -45
- package/crawlers/statistics.js +339 -153
- package/debug.js +4 -4
- package/enqueue_links/enqueue_links.d.ts +51 -159
- package/enqueue_links/enqueue_links.js +38 -231
- package/enqueue_links/shared.d.ts +34 -24
- package/enqueue_links/shared.js +54 -36
- package/errors.d.ts +43 -12
- package/errors.js +48 -9
- package/events/event_manager.d.ts +2 -2
- package/events/event_manager.js +7 -6
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +5 -5
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +3 -11
- package/memory-storage/memory-storage.js +42 -48
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +8 -13
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +36 -38
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +68 -80
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +8 -9
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +18 -14
- package/recoverable_state.d.ts +80 -45
- package/recoverable_state.js +163 -75
- package/request.d.ts +3 -4
- package/request.js +57 -44
- package/router.d.ts +6 -10
- package/router.js +23 -23
- package/serialization.js +14 -13
- package/service_locator.d.ts +19 -10
- package/service_locator.js +57 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +76 -74
- package/session_pool/session_pool.d.ts +6 -12
- package/session_pool/session_pool.js +85 -77
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +133 -32
- package/storages/index.d.ts +2 -1
- package/storages/index.js +2 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +204 -75
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +4 -23
- package/storages/request_list.js +103 -96
- package/storages/request_loader.d.ts +5 -0
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -21
- package/storages/request_queue.js +318 -177
- package/storages/sitemap_request_loader.d.ts +22 -70
- package/storages/sitemap_request_loader.js +122 -116
- package/storages/storage_instance_manager.d.ts +1 -4
- package/storages/storage_instance_manager.js +17 -19
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/throttling_request_manager.d.ts +239 -0
- package/storages/throttling_request_manager.js +646 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +6 -3
- package/storages/utils.js +11 -2
- package/system-info/runtime.js +7 -7
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -25
- package/validators.js +13 -25
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import type { Awaitable } from '@crawlee/types';
|
|
1
|
+
import type { Awaitable, Dictionary } from '@crawlee/types';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import type { RequestOptions } from '../request.js';
|
|
3
|
-
import type {
|
|
4
|
-
export { tryAbsoluteURL } from '@crawlee/utils';
|
|
5
|
-
export
|
|
4
|
+
import type { EnqueueStrategyOption } from './enqueue_links.js';
|
|
5
|
+
export { tryAbsoluteURL } from '@crawlee/utils/internal';
|
|
6
|
+
export interface UrlPatternObject {
|
|
6
7
|
glob?: string;
|
|
7
8
|
regexp?: RegExp;
|
|
8
|
-
}
|
|
9
|
-
export
|
|
10
|
-
purl: string;
|
|
11
|
-
} & Pick<RequestOptions, 'method' | 'payload' | 'label' | 'userData' | 'headers'>;
|
|
12
|
-
export type PseudoUrlInput = string | PseudoUrlObject;
|
|
13
|
-
export type GlobObject = {
|
|
9
|
+
}
|
|
10
|
+
export interface GlobObject {
|
|
14
11
|
glob: string;
|
|
15
|
-
}
|
|
12
|
+
}
|
|
16
13
|
export type GlobInput = string | GlobObject;
|
|
17
|
-
export
|
|
14
|
+
export interface RegExpObject {
|
|
18
15
|
regexp: RegExp;
|
|
19
|
-
}
|
|
16
|
+
}
|
|
20
17
|
export type RegExpInput = RegExp | RegExpObject;
|
|
18
|
+
/** Unified URL pattern input — accepts glob strings, glob objects, RegExp instances, or regexp objects. */
|
|
19
|
+
export type UrlPatternInput = GlobInput | RegExpInput;
|
|
20
|
+
/**
|
|
21
|
+
* Accepts one {@link UrlPatternInput} — a glob string, a RegExp instance, or a `{ glob }` / `{ regexp }` object.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare const urlPatternSchema: z.ZodType<UrlPatternInput>;
|
|
21
25
|
export type SkippedRequestReason = 'robotsTxt' | 'limit' | 'enqueueLimit' | 'filters' | 'transform' | 'redirect' | 'depth';
|
|
22
26
|
export type SkippedRequestCallback = (args: {
|
|
23
27
|
url: string;
|
|
@@ -26,13 +30,7 @@ export type SkippedRequestCallback = (args: {
|
|
|
26
30
|
/**
|
|
27
31
|
* @ignore
|
|
28
32
|
*/
|
|
29
|
-
export declare function updateEnqueueLinksPatternCache(item: GlobInput | RegExpInput
|
|
30
|
-
/**
|
|
31
|
-
* Helper factory used in the `enqueueLinks()` and enqueueLinksByClickingElements() function
|
|
32
|
-
* to construct RegExps from PseudoUrl strings.
|
|
33
|
-
* @ignore
|
|
34
|
-
*/
|
|
35
|
-
export declare function constructRegExpObjectsFromPseudoUrls(pseudoUrls: readonly PseudoUrlInput[]): RegExpObject[];
|
|
33
|
+
export declare function updateEnqueueLinksPatternCache(item: GlobInput | RegExpInput, pattern: RegExpObject | GlobObject): void;
|
|
36
34
|
/**
|
|
37
35
|
* Helper factory used in the `enqueueLinks()` and enqueueLinksByClickingElements() function
|
|
38
36
|
* to construct Glob objects from Glob pattern strings.
|
|
@@ -50,17 +48,29 @@ export declare function validateGlobPattern(glob: string): string;
|
|
|
50
48
|
*/
|
|
51
49
|
export declare function constructRegExpObjectsFromRegExps(regexps: readonly RegExpInput[]): RegExpObject[];
|
|
52
50
|
/**
|
|
53
|
-
*
|
|
54
|
-
* from
|
|
51
|
+
* Helper factory used in the `enqueueLinks()` function to construct UrlPatternObjects
|
|
52
|
+
* from a mixed array of glob strings, glob objects, RegExp instances, and regexp objects.
|
|
53
|
+
* @ignore
|
|
54
|
+
*/
|
|
55
|
+
export declare function constructUrlPatternObjects(patterns: readonly UrlPatternInput[]): UrlPatternObject[];
|
|
56
|
+
/**
|
|
57
|
+
* Filters request options by URL patterns.
|
|
55
58
|
*
|
|
56
59
|
* When `includePatterns` is empty/undefined, all options pass through (only exclude filtering applies).
|
|
57
60
|
* @ignore
|
|
58
61
|
*/
|
|
59
|
-
export declare function filterRequestOptionsByPatterns(requestOptions: RequestOptions[], includePatterns: UrlPatternObject[] | undefined, excludePatterns?: UrlPatternObject[], strategy?:
|
|
62
|
+
export declare function filterRequestOptionsByPatterns(requestOptions: RequestOptions[], includePatterns: UrlPatternObject[] | undefined, excludePatterns?: UrlPatternObject[], strategy?: EnqueueStrategyOption, onSkippedUrl?: (url: string) => void): RequestOptions[];
|
|
60
63
|
/**
|
|
61
64
|
* @ignore
|
|
62
65
|
*/
|
|
63
|
-
export declare function createRequestOptions(sources: readonly (string | Record<string, unknown>)[], options?:
|
|
66
|
+
export declare function createRequestOptions(sources: readonly (string | Record<string, unknown>)[], options?: {
|
|
67
|
+
label?: string;
|
|
68
|
+
userData?: Dictionary;
|
|
69
|
+
baseUrl?: string;
|
|
70
|
+
skipNavigation?: boolean;
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
strategy?: EnqueueStrategyOption;
|
|
73
|
+
}): RequestOptions[];
|
|
64
74
|
/**
|
|
65
75
|
* Takes a {@link RequestOptions} object and changes its attributes in a desired way. This user-function is used
|
|
66
76
|
* by {@link enqueueLinks} to modify request options before they are converted to {@link Request} instances.
|
package/enqueue_links/shared.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { URL } from 'node:url';
|
|
2
2
|
import { Minimatch } from 'minimatch';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { schemas } from '../validators.js';
|
|
5
|
+
export { tryAbsoluteURL } from '@crawlee/utils/internal';
|
|
5
6
|
const MAX_ENQUEUE_LINKS_CACHE_SIZE = 1000;
|
|
6
7
|
/**
|
|
7
|
-
* To
|
|
8
|
-
*
|
|
9
|
-
* by the `enqueueLinks()` function.
|
|
8
|
+
* To keep high performance when the same patterns are passed on every `enqueueLinks()` call,
|
|
9
|
+
* each glob/regexp is only compiled once and then kept in a cache.
|
|
10
10
|
* @ignore
|
|
11
11
|
*/
|
|
12
12
|
const enqueueLinksPatternCache = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Accepts one {@link UrlPatternInput} — a glob string, a RegExp instance, or a `{ glob }` / `{ regexp }` object.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export const urlPatternSchema = z.union([
|
|
18
|
+
z.string(),
|
|
19
|
+
z.instanceof(RegExp),
|
|
20
|
+
schemas.objectWithKeys(['glob']),
|
|
21
|
+
schemas.objectWithKeys(['regexp']),
|
|
22
|
+
]);
|
|
13
23
|
/**
|
|
14
24
|
* @ignore
|
|
15
25
|
*/
|
|
@@ -20,28 +30,6 @@ export function updateEnqueueLinksPatternCache(item, pattern) {
|
|
|
20
30
|
enqueueLinksPatternCache.delete(key);
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Helper factory used in the `enqueueLinks()` and enqueueLinksByClickingElements() function
|
|
25
|
-
* to construct RegExps from PseudoUrl strings.
|
|
26
|
-
* @ignore
|
|
27
|
-
*/
|
|
28
|
-
export function constructRegExpObjectsFromPseudoUrls(pseudoUrls) {
|
|
29
|
-
return pseudoUrls.map((item) => {
|
|
30
|
-
// Get pseudoUrl object from cache.
|
|
31
|
-
let regexpObject = enqueueLinksPatternCache.get(item);
|
|
32
|
-
if (regexpObject)
|
|
33
|
-
return regexpObject;
|
|
34
|
-
if (typeof item === 'string') {
|
|
35
|
-
regexpObject = { regexp: purlToRegExp(item) };
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const { purl, ...requestOptions } = item;
|
|
39
|
-
regexpObject = { regexp: purlToRegExp(purl), ...requestOptions };
|
|
40
|
-
}
|
|
41
|
-
updateEnqueueLinksPatternCache(item, regexpObject);
|
|
42
|
-
return regexpObject;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
33
|
/**
|
|
46
34
|
* Helper factory used in the `enqueueLinks()` and enqueueLinksByClickingElements() function
|
|
47
35
|
* to construct Glob objects from Glob pattern strings.
|
|
@@ -71,8 +59,7 @@ export function constructGlobObjectsFromGlobs(globs) {
|
|
|
71
59
|
globObject = { glob: validateGlobPattern(item) };
|
|
72
60
|
}
|
|
73
61
|
else {
|
|
74
|
-
|
|
75
|
-
globObject = { glob: validateGlobPattern(glob), ...requestOptions };
|
|
62
|
+
globObject = { glob: validateGlobPattern(item.glob) };
|
|
76
63
|
}
|
|
77
64
|
updateEnqueueLinksPatternCache(item, globObject);
|
|
78
65
|
return globObject;
|
|
@@ -102,15 +89,31 @@ export function constructRegExpObjectsFromRegExps(regexps) {
|
|
|
102
89
|
regexpObject = { regexp: item };
|
|
103
90
|
}
|
|
104
91
|
else {
|
|
105
|
-
regexpObject = item;
|
|
92
|
+
regexpObject = { regexp: item.regexp };
|
|
106
93
|
}
|
|
107
94
|
updateEnqueueLinksPatternCache(item, regexpObject);
|
|
108
95
|
return regexpObject;
|
|
109
96
|
});
|
|
110
97
|
}
|
|
111
98
|
/**
|
|
112
|
-
*
|
|
113
|
-
* from
|
|
99
|
+
* Helper factory used in the `enqueueLinks()` function to construct UrlPatternObjects
|
|
100
|
+
* from a mixed array of glob strings, glob objects, RegExp instances, and regexp objects.
|
|
101
|
+
* @ignore
|
|
102
|
+
*/
|
|
103
|
+
export function constructUrlPatternObjects(patterns) {
|
|
104
|
+
const result = [];
|
|
105
|
+
for (const item of patterns) {
|
|
106
|
+
if (typeof item === 'string' || 'glob' in item) {
|
|
107
|
+
result.push(...constructGlobObjectsFromGlobs([item]));
|
|
108
|
+
}
|
|
109
|
+
else if (item instanceof RegExp || 'regexp' in item) {
|
|
110
|
+
result.push(...constructRegExpObjectsFromRegExps([item]));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Filters request options by URL patterns.
|
|
114
117
|
*
|
|
115
118
|
* When `includePatterns` is empty/undefined, all options pass through (only exclude filtering applies).
|
|
116
119
|
* @ignore
|
|
@@ -130,9 +133,9 @@ export function filterRequestOptionsByPatterns(requestOptions, includePatterns,
|
|
|
130
133
|
if (!includeMatchers) {
|
|
131
134
|
return { ...opts, enqueueStrategy: strategy };
|
|
132
135
|
}
|
|
133
|
-
for (const { match
|
|
136
|
+
for (const { match } of includeMatchers) {
|
|
134
137
|
if (match(opts.url)) {
|
|
135
|
-
return { ...opts,
|
|
138
|
+
return { ...opts, enqueueStrategy: strategy };
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
// didn't match any positive pattern
|
|
@@ -141,6 +144,16 @@ export function filterRequestOptionsByPatterns(requestOptions, includePatterns,
|
|
|
141
144
|
})
|
|
142
145
|
.filter((opts) => opts !== null);
|
|
143
146
|
}
|
|
147
|
+
function isAbsoluteUrl(url) {
|
|
148
|
+
try {
|
|
149
|
+
// eslint-disable-next-line no-new
|
|
150
|
+
new URL(url);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
144
157
|
/**
|
|
145
158
|
* @ignore
|
|
146
159
|
*/
|
|
@@ -158,7 +171,12 @@ export function createRequestOptions(sources, options = {}) {
|
|
|
158
171
|
}
|
|
159
172
|
})
|
|
160
173
|
.map((requestOptions) => {
|
|
161
|
-
|
|
174
|
+
// Leave already-absolute URLs untouched - re-deriving them via `new URL()` would normalize them
|
|
175
|
+
// (e.g. adding a trailing slash to a bare domain), which is surprising for URLs that didn't need
|
|
176
|
+
// resolving against `baseUrl` in the first place.
|
|
177
|
+
if (!isAbsoluteUrl(requestOptions.url)) {
|
|
178
|
+
requestOptions.url = new URL(requestOptions.url, options.baseUrl).href;
|
|
179
|
+
}
|
|
162
180
|
requestOptions.userData ??= options.userData ?? {};
|
|
163
181
|
if (typeof options.label === 'string') {
|
|
164
182
|
requestOptions.userData = {
|
|
@@ -191,7 +209,7 @@ function createPatternObjectMatcher(urlPatternObject) {
|
|
|
191
209
|
else {
|
|
192
210
|
match = () => false;
|
|
193
211
|
}
|
|
194
|
-
return {
|
|
212
|
+
return { match };
|
|
195
213
|
}
|
|
196
214
|
/**
|
|
197
215
|
* Applies a {@link RequestTransform} function to a list of request options.
|
package/errors.d.ts
CHANGED
|
@@ -14,6 +14,16 @@ export declare class CriticalError extends NonRetryableError {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class MissingRouteError extends CriticalError {
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A schema validation issue, structurally compatible with `StandardSchemaV1.Issue`. Declared here so that
|
|
19
|
+
* error types do not have to depend on `@standard-schema/spec`.
|
|
20
|
+
*/
|
|
21
|
+
export interface SchemaIssue {
|
|
22
|
+
readonly message: string;
|
|
23
|
+
readonly path?: readonly (PropertyKey | {
|
|
24
|
+
key: PropertyKey;
|
|
25
|
+
})[];
|
|
26
|
+
}
|
|
17
27
|
/**
|
|
18
28
|
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
19
29
|
*
|
|
@@ -21,18 +31,19 @@ export declare class MissingRouteError extends CriticalError {
|
|
|
21
31
|
*/
|
|
22
32
|
export declare class RequestValidationError extends NonRetryableError {
|
|
23
33
|
readonly label: string | symbol;
|
|
24
|
-
readonly issues: readonly
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
readonly issues: readonly SchemaIssue[];
|
|
35
|
+
constructor(label: string | symbol, issues: readonly SchemaIssue[]);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown by {@link RecoverableState} when a persisted state record does not match its `stateSchema`.
|
|
39
|
+
*
|
|
40
|
+
* Whether a corrupt record should abort the run or be discarded in favour of the defaults depends on what the
|
|
41
|
+
* state is for, so {@link RecoverableState.initialize} always throws and leaves the choice to the caller.
|
|
42
|
+
*/
|
|
43
|
+
export declare class StateValidationError extends Error {
|
|
44
|
+
readonly persistStateKey: string;
|
|
45
|
+
readonly issues: readonly SchemaIssue[];
|
|
46
|
+
constructor(persistStateKey: string, issues: readonly SchemaIssue[]);
|
|
36
47
|
}
|
|
37
48
|
/**
|
|
38
49
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
@@ -42,6 +53,26 @@ export declare class RequestValidationError extends NonRetryableError {
|
|
|
42
53
|
export declare class RetryRequestError extends Error {
|
|
43
54
|
constructor(message?: string);
|
|
44
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when a domain has rate-limited us and the request should simply be attempted again later.
|
|
58
|
+
*
|
|
59
|
+
* The request is reclaimed without recording a failure: it costs neither a retry nor session reputation, because
|
|
60
|
+
* nothing about the request or the session was at fault. A {@link ThrottlingRequestManager} holds it back until
|
|
61
|
+
* the domain's backoff expires, so retries are paced rather than immediate.
|
|
62
|
+
*/
|
|
63
|
+
export declare class RequestThrottledError extends RetryRequestError {
|
|
64
|
+
constructor(message?: string);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Thrown when a domain has rate-limited us for so long that no request has got through, and the crawl is
|
|
68
|
+
* abandoned rather than kept waiting.
|
|
69
|
+
*
|
|
70
|
+
* Waiting longer will not help: at this point the concurrency is too high for the domain, or it has blocked us.
|
|
71
|
+
* The affected requests are deliberately left in their queue, so re-running the crawl without purging storages
|
|
72
|
+
* resumes them once the domain recovers.
|
|
73
|
+
*/
|
|
74
|
+
export declare class PersistentRateLimitError extends CriticalError {
|
|
75
|
+
}
|
|
45
76
|
/**
|
|
46
77
|
* Errors of `SessionError` type retire the session associated with the request and trigger a regular retry.
|
|
47
78
|
*
|
package/errors.js
CHANGED
|
@@ -15,6 +15,16 @@ export class CriticalError extends NonRetryableError {
|
|
|
15
15
|
*/
|
|
16
16
|
export class MissingRouteError extends CriticalError {
|
|
17
17
|
}
|
|
18
|
+
function formatIssues(issues) {
|
|
19
|
+
return issues
|
|
20
|
+
.map((issue) => {
|
|
21
|
+
const path = (issue.path ?? [])
|
|
22
|
+
.map((segment) => (typeof segment === 'object' ? segment.key : segment))
|
|
23
|
+
.join('.');
|
|
24
|
+
return `- ${path ? `${path}: ` : ''}${issue.message}`;
|
|
25
|
+
})
|
|
26
|
+
.join('\n');
|
|
27
|
+
}
|
|
18
28
|
/**
|
|
19
29
|
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
20
30
|
*
|
|
@@ -24,19 +34,26 @@ export class RequestValidationError extends NonRetryableError {
|
|
|
24
34
|
label;
|
|
25
35
|
issues;
|
|
26
36
|
constructor(label, issues) {
|
|
27
|
-
|
|
28
|
-
.map((issue) => {
|
|
29
|
-
const path = (issue.path ?? [])
|
|
30
|
-
.map((segment) => (typeof segment === 'object' ? segment.key : segment))
|
|
31
|
-
.join('.');
|
|
32
|
-
return `- ${path ? `${path}: ` : ''}${issue.message}`;
|
|
33
|
-
})
|
|
34
|
-
.join('\n');
|
|
35
|
-
super(`Request userData for label '${String(label)}' failed schema validation:\n${details}`);
|
|
37
|
+
super(`Request userData for label '${String(label)}' failed schema validation:\n${formatIssues(issues)}`);
|
|
36
38
|
this.label = label;
|
|
37
39
|
this.issues = issues;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by {@link RecoverableState} when a persisted state record does not match its `stateSchema`.
|
|
44
|
+
*
|
|
45
|
+
* Whether a corrupt record should abort the run or be discarded in favour of the defaults depends on what the
|
|
46
|
+
* state is for, so {@link RecoverableState.initialize} always throws and leaves the choice to the caller.
|
|
47
|
+
*/
|
|
48
|
+
export class StateValidationError extends Error {
|
|
49
|
+
persistStateKey;
|
|
50
|
+
issues;
|
|
51
|
+
constructor(persistStateKey, issues) {
|
|
52
|
+
super(`State persisted under key '${persistStateKey}' failed schema validation:\n${formatIssues(issues)}`);
|
|
53
|
+
this.persistStateKey = persistStateKey;
|
|
54
|
+
this.issues = issues;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
/**
|
|
41
58
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
42
59
|
*
|
|
@@ -47,6 +64,28 @@ export class RetryRequestError extends Error {
|
|
|
47
64
|
super(message ?? "Request is being retried at the user's request");
|
|
48
65
|
}
|
|
49
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Thrown when a domain has rate-limited us and the request should simply be attempted again later.
|
|
69
|
+
*
|
|
70
|
+
* The request is reclaimed without recording a failure: it costs neither a retry nor session reputation, because
|
|
71
|
+
* nothing about the request or the session was at fault. A {@link ThrottlingRequestManager} holds it back until
|
|
72
|
+
* the domain's backoff expires, so retries are paced rather than immediate.
|
|
73
|
+
*/
|
|
74
|
+
export class RequestThrottledError extends RetryRequestError {
|
|
75
|
+
constructor(message) {
|
|
76
|
+
super(message ?? 'Request is being retried later because its domain is rate-limiting us');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Thrown when a domain has rate-limited us for so long that no request has got through, and the crawl is
|
|
81
|
+
* abandoned rather than kept waiting.
|
|
82
|
+
*
|
|
83
|
+
* Waiting longer will not help: at this point the concurrency is too high for the domain, or it has blocked us.
|
|
84
|
+
* The affected requests are deliberately left in their queue, so re-running the crawl without purging storages
|
|
85
|
+
* resumes them once the domain recovers.
|
|
86
|
+
*/
|
|
87
|
+
export class PersistentRateLimitError extends CriticalError {
|
|
88
|
+
}
|
|
50
89
|
/**
|
|
51
90
|
* Errors of `SessionError` type retire the session associated with the request and trigger a regular retry.
|
|
52
91
|
*
|
|
@@ -4,7 +4,7 @@ export interface EventManagerOptions {
|
|
|
4
4
|
/** Interval between emitted `persistState` events in milliseconds. */
|
|
5
5
|
persistStateIntervalMillis: number;
|
|
6
6
|
}
|
|
7
|
-
export declare
|
|
7
|
+
export declare enum EventType {
|
|
8
8
|
PERSIST_STATE = "persistState",
|
|
9
9
|
SYSTEM_INFO = "systemInfo",
|
|
10
10
|
MIGRATING = "migrating",
|
|
@@ -41,12 +41,12 @@ interface Intervals {
|
|
|
41
41
|
systemInfo?: BetterIntervalID;
|
|
42
42
|
}
|
|
43
43
|
export declare abstract class EventManager {
|
|
44
|
+
#private;
|
|
44
45
|
protected events: AsyncEventEmitter<{}>;
|
|
45
46
|
protected initialized: boolean;
|
|
46
47
|
protected intervals: Intervals;
|
|
47
48
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
48
49
|
protected log: import("@crawlee/types").CrawleeLogger;
|
|
49
|
-
private persistStateIntervalMillis;
|
|
50
50
|
constructor(options: EventManagerOptions);
|
|
51
51
|
/**
|
|
52
52
|
* Initializes the event manager by starting the `persistState` event interval.
|
package/events/event_manager.js
CHANGED
|
@@ -15,10 +15,11 @@ export class EventManager {
|
|
|
15
15
|
initialized = false;
|
|
16
16
|
intervals = {};
|
|
17
17
|
log = serviceLocator.getLogger().child({ prefix: 'Events' });
|
|
18
|
-
persistStateIntervalMillis;
|
|
18
|
+
#persistStateIntervalMillis;
|
|
19
19
|
constructor(options) {
|
|
20
|
-
this
|
|
21
|
-
|
|
20
|
+
this.#persistStateIntervalMillis = options.persistStateIntervalMillis;
|
|
21
|
+
// One MIGRATING listener per RequestQueue, and ThrottlingRequestManager opens one per domain.
|
|
22
|
+
this.events.setMaxListeners(150);
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Initializes the event manager by starting the `persistState` event interval.
|
|
@@ -29,9 +30,9 @@ export class EventManager {
|
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
this.intervals.persistState = betterSetInterval((intervalCallback) => {
|
|
32
|
-
this.emit(
|
|
33
|
+
this.emit(EventType.PERSIST_STATE, { isMigrating: false });
|
|
33
34
|
intervalCallback();
|
|
34
|
-
}, this
|
|
35
|
+
}, this.#persistStateIntervalMillis);
|
|
35
36
|
this.initialized = true;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
@@ -45,7 +46,7 @@ export class EventManager {
|
|
|
45
46
|
betterClearInterval(this.intervals.persistState);
|
|
46
47
|
this.initialized = false;
|
|
47
48
|
// Emit final PERSIST_STATE event
|
|
48
|
-
this.emit(
|
|
49
|
+
this.emit(EventType.PERSIST_STATE, { isMigrating: false });
|
|
49
50
|
// Wait for PERSIST_STATE to process
|
|
50
51
|
await this.waitForAllListenersToComplete();
|
|
51
52
|
}
|
|
@@ -5,7 +5,7 @@ export interface LocalEventManagerOptions extends EventManagerOptions {
|
|
|
5
5
|
systemInfoIntervalMillis: number;
|
|
6
6
|
}
|
|
7
7
|
export declare class LocalEventManager extends EventManager {
|
|
8
|
-
private
|
|
8
|
+
#private;
|
|
9
9
|
constructor(options: LocalEventManagerOptions);
|
|
10
10
|
/**
|
|
11
11
|
* Creates a new `LocalEventManager` based on the provided `Configuration`.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { betterClearInterval, betterSetInterval } from '@apify/utilities';
|
|
2
2
|
import { serviceLocator } from '../service_locator.js';
|
|
3
|
-
import { EventManager } from './event_manager.js';
|
|
3
|
+
import { EventManager, EventType } from './event_manager.js';
|
|
4
4
|
export class LocalEventManager extends EventManager {
|
|
5
|
-
systemInfoIntervalMillis;
|
|
5
|
+
#systemInfoIntervalMillis;
|
|
6
6
|
constructor(options) {
|
|
7
7
|
super(options);
|
|
8
|
-
this
|
|
8
|
+
this.#systemInfoIntervalMillis = options.systemInfoIntervalMillis;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Creates a new `LocalEventManager` based on the provided `Configuration`.
|
|
@@ -28,7 +28,7 @@ export class LocalEventManager extends EventManager {
|
|
|
28
28
|
}
|
|
29
29
|
await super.init();
|
|
30
30
|
this.emitSystemInfoEvent = this.emitSystemInfoEvent.bind(this);
|
|
31
|
-
this.intervals.systemInfo = betterSetInterval(this.emitSystemInfoEvent.bind(this), this
|
|
31
|
+
this.intervals.systemInfo = betterSetInterval(this.emitSystemInfoEvent.bind(this), this.#systemInfoIntervalMillis);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* @inheritDoc
|
|
@@ -47,7 +47,7 @@ export class LocalEventManager extends EventManager {
|
|
|
47
47
|
const info = await this.createSystemInfo({
|
|
48
48
|
maxUsedCpuRatio: serviceLocator.getConfiguration().maxUsedCpuRatio,
|
|
49
49
|
});
|
|
50
|
-
this.events.emit(
|
|
50
|
+
this.events.emit(EventType.SYSTEM_INFO, info);
|
|
51
51
|
intervalCallback();
|
|
52
52
|
}
|
|
53
53
|
/**
|
package/http.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a `Retry-After` response header into a delay in milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* The header holds either a non-negative number of seconds or an HTTP-date.
|
|
5
|
+
* See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After).
|
|
6
|
+
*
|
|
7
|
+
* @returns The delay in milliseconds, or `null` if the header is absent, unparseable, or already elapsed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseRetryAfterHeader(value?: string | null): number | null;
|
package/http.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a `Retry-After` response header into a delay in milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* The header holds either a non-negative number of seconds or an HTTP-date.
|
|
5
|
+
* See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After).
|
|
6
|
+
*
|
|
7
|
+
* @returns The delay in milliseconds, or `null` if the header is absent, unparseable, or already elapsed.
|
|
8
|
+
*/
|
|
9
|
+
export function parseRetryAfterHeader(value) {
|
|
10
|
+
if (!value) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
// Per the spec this is a `delay-seconds`: digits only, so a negative or fractional value is not one.
|
|
15
|
+
if (/^\d+$/.test(trimmed)) {
|
|
16
|
+
// `Retry-After: 0` names no future deadline, same as an HTTP-date that has already passed. Reporting it
|
|
17
|
+
// as a zero delay would leave the domain unthrottled while still counting as a rate-limit event, so the
|
|
18
|
+
// caller would defer the request for free and re-send it immediately.
|
|
19
|
+
const delayMs = Number(trimmed) * 1000;
|
|
20
|
+
return delayMs > 0 ? delayMs : null;
|
|
21
|
+
}
|
|
22
|
+
const date = Date.parse(trimmed);
|
|
23
|
+
if (!Number.isNaN(date)) {
|
|
24
|
+
const delayMs = date - Date.now();
|
|
25
|
+
return delayMs > 0 ? delayMs : null;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -17,6 +17,6 @@ export * from './storages/index.js';
|
|
|
17
17
|
export * from './memory-storage/index.js';
|
|
18
18
|
export * from './validators.js';
|
|
19
19
|
export * from './cookie_utils.js';
|
|
20
|
+
export * from './http.js';
|
|
20
21
|
export * from './recoverable_state.js';
|
|
21
|
-
export { PseudoUrl } from '@apify/pseudo_url';
|
|
22
22
|
export type { StorageBackend } from '@crawlee/types';
|
package/index.js
CHANGED
|
@@ -17,5 +17,5 @@ export * from './storages/index.js';
|
|
|
17
17
|
export * from './memory-storage/index.js';
|
|
18
18
|
export * from './validators.js';
|
|
19
19
|
export * from './cookie_utils.js';
|
|
20
|
+
export * from './http.js';
|
|
20
21
|
export * from './recoverable_state.js';
|
|
21
|
-
export { PseudoUrl } from '@apify/pseudo_url';
|
package/log.js
CHANGED
|
@@ -30,6 +30,8 @@ import log, { Log, Logger, LoggerJson, LoggerText, LogLevel } from '@apify/log';
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export class BaseCrawleeLogger {
|
|
33
|
+
// kept as TS-private: the adaptive crawler's log proxy calls non-intercepted methods with `this === proxy`,
|
|
34
|
+
// where `#`-field access would throw at runtime
|
|
33
35
|
options;
|
|
34
36
|
warningsLogged = new Set();
|
|
35
37
|
constructor(options = {}) {
|
|
@@ -87,7 +89,9 @@ export class BaseCrawleeLogger {
|
|
|
87
89
|
*/
|
|
88
90
|
export class ApifyLogAdapter extends BaseCrawleeLogger {
|
|
89
91
|
apifyLog;
|
|
90
|
-
constructor(
|
|
92
|
+
constructor(
|
|
93
|
+
// kept as a TS-private parameter property: reached through the adaptive crawler's log proxy, see above
|
|
94
|
+
apifyLog, options) {
|
|
91
95
|
super(options ?? {});
|
|
92
96
|
this.apifyLog = apifyLog;
|
|
93
97
|
}
|
|
@@ -10,12 +10,8 @@ export interface MemoryStorageOptions {
|
|
|
10
10
|
logger?: CrawleeLogger;
|
|
11
11
|
}
|
|
12
12
|
export declare class MemoryStorageBackend implements storage.StorageBackend {
|
|
13
|
+
#private;
|
|
13
14
|
readonly logger?: CrawleeLogger;
|
|
14
|
-
/**
|
|
15
|
-
* Unique per-instance cache partition key. Mirrors the way `FileSystemStorageBackend` partitions its
|
|
16
|
-
* cache by storage directory: two distinct `MemoryStorageBackend` instances must not share cached backends.
|
|
17
|
-
*/
|
|
18
|
-
private readonly instanceCacheKey;
|
|
19
15
|
readonly keyValueStoreBackendCache: KeyValueStoreBackend[];
|
|
20
16
|
readonly datasetBackendCache: DatasetBackend[];
|
|
21
17
|
readonly requestQueueBackendCache: RequestQueueBackend[];
|
|
@@ -25,17 +21,13 @@ export declare class MemoryStorageBackend implements storage.StorageBackend {
|
|
|
25
21
|
* cache partitions in the storage backend cache.
|
|
26
22
|
*/
|
|
27
23
|
getStorageBackendCacheKey(): string;
|
|
28
|
-
private static resolveStorageKey;
|
|
29
24
|
createDatasetBackend(options?: storage.StorageIdentifier): Promise<storage.DatasetBackend>;
|
|
30
25
|
createKeyValueStoreBackend(options?: storage.StorageIdentifier): Promise<storage.KeyValueStoreBackend>;
|
|
31
26
|
createRequestQueueBackend(options?: storage.StorageIdentifier): Promise<RequestQueueBackend>;
|
|
32
27
|
storageExists(id: string, type: 'Dataset' | 'KeyValueStore' | 'RequestQueue'): Promise<boolean>;
|
|
33
28
|
/**
|
|
34
|
-
* Cleans up the
|
|
35
|
-
* resets the in-memory state of the cached
|
|
36
|
-
*
|
|
37
|
-
* As with `FileSystemStorageBackend`, the run's input (the `INPUT` key in the default key-value
|
|
38
|
-
* store) is preserved — only the rest of the default storages is cleared.
|
|
29
|
+
* Cleans up the run-scoped storages before the run starts. For the in-memory storage this simply
|
|
30
|
+
* resets the in-memory state of the cached backends.
|
|
39
31
|
*/
|
|
40
32
|
purge(): Promise<void>;
|
|
41
33
|
/**
|