@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.1
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/README.md +1 -1
- package/configuration.d.ts +16 -47
- package/configuration.js +13 -25
- package/debug.js +4 -4
- package/errors.d.ts +28 -38
- package/errors.js +33 -47
- package/events/event_manager.d.ts +2 -2
- package/events/event_manager.js +7 -6
- package/events/index.d.ts +1 -0
- package/events/local_event_manager.d.ts +1 -8
- package/events/local_event_manager.js +13 -13
- package/events/system_info.d.ts +38 -0
- package/index.d.ts +2 -8
- package/index.js +4 -8
- package/internal.d.ts +8 -0
- package/internal.js +9 -0
- package/log.d.ts +10 -11
- package/log.js +52 -20
- package/memory-storage/memory-storage.d.ts +15 -18
- package/memory-storage/memory-storage.js +80 -58
- package/memory-storage/resource-clients/dataset.d.ts +1 -6
- package/memory-storage/resource-clients/dataset.js +23 -31
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -10
- package/memory-storage/resource-clients/key-value-store.js +43 -67
- package/memory-storage/resource-clients/request-queue.d.ts +1 -42
- package/memory-storage/resource-clients/request-queue.js +109 -117
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +17 -20
- package/proxy_configuration.d.ts +21 -26
- package/proxy_configuration.js +35 -25
- package/recoverable_state.d.ts +104 -47
- package/recoverable_state.js +199 -74
- package/request.d.ts +20 -107
- package/request.js +78 -244
- package/serialization.js +17 -16
- package/service_locator.d.ts +22 -10
- package/service_locator.js +59 -48
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +13 -8
- package/storages/dataset.js +149 -40
- package/storages/index.d.ts +4 -4
- package/storages/index.js +2 -4
- package/storages/key_value_store.d.ts +16 -35
- package/storages/key_value_store.js +223 -110
- package/storages/key_value_store_codec.js +6 -11
- package/storages/request_dedup_cache.d.ts +1 -4
- package/storages/request_dedup_cache.js +15 -15
- package/storages/request_list.d.ts +9 -104
- package/storages/request_list.js +236 -233
- package/storages/request_loader.d.ts +49 -18
- package/storages/request_loader.js +36 -1
- package/storages/request_manager.d.ts +86 -0
- package/storages/request_manager_tandem.d.ts +14 -38
- package/storages/request_manager_tandem.js +67 -64
- package/storages/request_queue.d.ts +23 -50
- package/storages/request_queue.js +371 -226
- package/storages/storage_instance_manager.d.ts +2 -4
- package/storages/storage_instance_manager.js +21 -21
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/transaction.d.ts +270 -0
- package/storages/transaction.js +296 -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 +23 -25
- package/validators.js +14 -25
- package/autoscaling/autoscaled_pool.d.ts +0 -213
- package/autoscaling/autoscaled_pool.js +0 -378
- package/autoscaling/client_load_signal.d.ts +0 -59
- package/autoscaling/client_load_signal.js +0 -73
- package/autoscaling/concurrency_system.d.ts +0 -283
- package/autoscaling/concurrency_system.js +0 -350
- package/autoscaling/cpu_load_signal.d.ts +0 -44
- package/autoscaling/cpu_load_signal.js +0 -46
- package/autoscaling/event_loop_load_signal.d.ts +0 -54
- package/autoscaling/event_loop_load_signal.js +0 -60
- package/autoscaling/index.d.ts +0 -9
- package/autoscaling/index.js +0 -9
- package/autoscaling/load_signal.d.ts +0 -99
- package/autoscaling/load_signal.js +0 -103
- package/autoscaling/memory_load_signal.d.ts +0 -56
- package/autoscaling/memory_load_signal.js +0 -106
- package/autoscaling/snapshotter.d.ts +0 -87
- package/autoscaling/snapshotter.js +0 -67
- package/autoscaling/system_status.d.ts +0 -161
- package/autoscaling/system_status.js +0 -139
- package/autoscaling/weighted_avg.d.ts +0 -5
- package/autoscaling/weighted_avg.js +0 -14
- package/cookie_utils.d.ts +0 -44
- package/cookie_utils.js +0 -122
- package/crawlers/context_pipeline.d.ts +0 -70
- package/crawlers/context_pipeline.js +0 -122
- package/crawlers/crawler_commons.d.ts +0 -257
- package/crawlers/crawler_commons.js +0 -107
- package/crawlers/error_snapshotter.d.ts +0 -59
- package/crawlers/error_snapshotter.js +0 -117
- package/crawlers/error_tracker.d.ts +0 -54
- package/crawlers/error_tracker.js +0 -308
- package/crawlers/index.d.ts +0 -5
- package/crawlers/index.js +0 -5
- package/crawlers/internals/types.d.ts +0 -7
- package/crawlers/statistics.d.ts +0 -209
- package/crawlers/statistics.js +0 -350
- package/enqueue_links/enqueue_links.d.ts +0 -264
- package/enqueue_links/enqueue_links.js +0 -271
- package/enqueue_links/index.d.ts +0 -2
- package/enqueue_links/index.js +0 -2
- package/enqueue_links/shared.d.ts +0 -83
- package/enqueue_links/shared.js +0 -221
- package/router.d.ts +0 -309
- package/router.js +0 -309
- package/session_pool/consts.d.ts +0 -3
- package/session_pool/consts.js +0 -3
- package/session_pool/errors.d.ts +0 -7
- package/session_pool/errors.js +0 -11
- package/session_pool/fingerprint.d.ts +0 -9
- package/session_pool/fingerprint.js +0 -30
- package/session_pool/index.d.ts +0 -4
- package/session_pool/index.js +0 -4
- package/session_pool/session.d.ts +0 -161
- package/session_pool/session.js +0 -218
- package/session_pool/session_pool.d.ts +0 -246
- package/session_pool/session_pool.js +0 -386
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
- package/storages/sitemap_request_loader.d.ts +0 -249
- package/storages/sitemap_request_loader.js +0 -432
- /package/{crawlers/internals/types.js → events/system_info.js} +0 -0
package/request.js
CHANGED
|
@@ -1,46 +1,33 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import
|
|
3
|
-
import ow from 'ow';
|
|
2
|
+
import { z } from 'zod';
|
|
4
3
|
import { cryptoRandomObjectId, normalizeUrl } from '@apify/utilities';
|
|
5
4
|
import { serviceLocator } from './service_locator.js';
|
|
6
|
-
import {
|
|
5
|
+
import { parseArgument, schemas } from './validators.js';
|
|
7
6
|
// new properties on the Request object breaks serialization
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
uniqueKey:
|
|
12
|
-
method:
|
|
13
|
-
payload:
|
|
14
|
-
noRetry:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
state: ow.optional.number.greaterThanOrEqual(0).lessThanOrEqual(6),
|
|
29
|
-
};
|
|
30
|
-
export var RequestState;
|
|
31
|
-
(function (RequestState) {
|
|
32
|
-
RequestState[RequestState["UNPROCESSED"] = 0] = "UNPROCESSED";
|
|
33
|
-
RequestState[RequestState["BEFORE_NAV"] = 1] = "BEFORE_NAV";
|
|
34
|
-
RequestState[RequestState["AFTER_NAV"] = 2] = "AFTER_NAV";
|
|
35
|
-
RequestState[RequestState["REQUEST_HANDLER"] = 3] = "REQUEST_HANDLER";
|
|
36
|
-
RequestState[RequestState["DONE"] = 4] = "DONE";
|
|
37
|
-
RequestState[RequestState["ERROR_HANDLER"] = 5] = "ERROR_HANDLER";
|
|
38
|
-
RequestState[RequestState["ERROR"] = 6] = "ERROR";
|
|
39
|
-
RequestState[RequestState["SKIPPED"] = 7] = "SKIPPED";
|
|
40
|
-
})(RequestState || (RequestState = {}));
|
|
7
|
+
// Compiled once: every `Request` runs it, and the generated fast path skips zod's interpreter.
|
|
8
|
+
const requestOptionsSchema = z.compile(z.looseObject({
|
|
9
|
+
url: z.string(),
|
|
10
|
+
uniqueKey: z.string().optional(),
|
|
11
|
+
method: z.string().optional(),
|
|
12
|
+
payload: z.union([z.string(), z.instanceof(Uint8Array)]).optional(),
|
|
13
|
+
noRetry: z.boolean().optional(),
|
|
14
|
+
sessionId: z.string().optional(),
|
|
15
|
+
maxRetries: schemas.anyNumber.optional(),
|
|
16
|
+
headers: z.looseObject({}).optional(),
|
|
17
|
+
userData: z.looseObject({}).optional(),
|
|
18
|
+
label: z.string().optional(),
|
|
19
|
+
keepUrlFragment: z.boolean().optional(),
|
|
20
|
+
useExtendedUniqueKey: z.boolean().optional(),
|
|
21
|
+
alwaysEnqueue: z.boolean().optional(),
|
|
22
|
+
skipNavigation: z.boolean().optional(),
|
|
23
|
+
crawlDepth: schemas.anyNumber
|
|
24
|
+
.refine((value) => value >= 0, 'Expected a number greater than or equal to 0')
|
|
25
|
+
.optional(),
|
|
26
|
+
}));
|
|
41
27
|
/**
|
|
42
|
-
* Represents a URL to be crawled, optionally including HTTP method, headers, payload and other metadata
|
|
43
|
-
*
|
|
28
|
+
* Represents a URL to be crawled, optionally including HTTP method, headers, payload and other metadata, together
|
|
29
|
+
* with the processing state a request queue keeps about it (`retryCount`, `errorMessages`, `handledAt`, ...).
|
|
30
|
+
* Inside a crawler, requests are {@link CrawlingRequest}s, which add the crawler's own per-request settings.
|
|
44
31
|
*
|
|
45
32
|
* Each `Request` instance has the `uniqueKey` property, which can be either specified
|
|
46
33
|
* manually in the constructor or generated automatically from the URL. Two requests with the same `uniqueKey`
|
|
@@ -56,21 +43,15 @@ export var RequestState;
|
|
|
56
43
|
* const request = new Request({
|
|
57
44
|
* url: 'http://www.example.com',
|
|
58
45
|
* headers: { Accept: 'application/json' },
|
|
46
|
+
* userData: { foo: 'bar' },
|
|
59
47
|
* });
|
|
60
48
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* request.userData.foo = 'bar';
|
|
64
|
-
* request.pushErrorMessage(new Error('Request failed!'));
|
|
65
|
-
*
|
|
66
|
-
* ...
|
|
67
|
-
*
|
|
68
|
-
* const foo = request.userData.foo;
|
|
49
|
+
* await requestQueue.addRequest(request);
|
|
69
50
|
* ```
|
|
70
51
|
* @category Sources
|
|
71
52
|
*/
|
|
72
53
|
class CrawleeRequest {
|
|
73
|
-
/**
|
|
54
|
+
/** Storage-assigned request ID. Only present on requests that went through a {@link RequestQueue}. */
|
|
74
55
|
id;
|
|
75
56
|
/** URL of the web page to crawl. */
|
|
76
57
|
url;
|
|
@@ -101,7 +82,7 @@ class CrawleeRequest {
|
|
|
101
82
|
/** Object with HTTP headers. Key is header name, value is the value. */
|
|
102
83
|
headers;
|
|
103
84
|
/** Private store for the custom user data assigned to the request. */
|
|
104
|
-
|
|
85
|
+
#userData = {};
|
|
105
86
|
/**
|
|
106
87
|
* Custom user data assigned to the request.
|
|
107
88
|
*
|
|
@@ -116,27 +97,19 @@ class CrawleeRequest {
|
|
|
116
97
|
handledAt;
|
|
117
98
|
/**
|
|
118
99
|
* `Request` parameters including the URL, HTTP method and headers, and others.
|
|
100
|
+
*
|
|
101
|
+
* Processing state (`retryCount`, `errorMessages`, `handledAt`, `loadedUrl`, `id`) is not an option;
|
|
102
|
+
* requests coming back from a storage are rebuilt with {@link Request.fromSchema}.
|
|
119
103
|
*/
|
|
120
104
|
constructor(options) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// skip url, because it is validated above
|
|
130
|
-
if (prop === 'url') {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const predicate = requestOptionalPredicates[prop];
|
|
134
|
-
const value = options[prop];
|
|
135
|
-
if (predicate) {
|
|
136
|
-
ow(value, `RequestOptions.${prop}`, predicate);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
const { id, url, loadedUrl, uniqueKey, payload, noRetry = false, retryCount = 0, sessionId, maxRetries, errorMessages = [], headers = {}, userData = {}, label, handledAt, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
|
|
105
|
+
// A bare URL is a common slip — point at the object form instead of a generic type error.
|
|
106
|
+
if (typeof options === 'string') {
|
|
107
|
+
throw new TypeError(`\`Request\` options must be an object, got the string '${options}'. ` +
|
|
108
|
+
'Did you mean `new Request({ url })`?');
|
|
109
|
+
}
|
|
110
|
+
parseArgument(options, schemas.anyObject, 'RequestOptions');
|
|
111
|
+
parseArgument(options, requestOptionsSchema, 'RequestOptions');
|
|
112
|
+
const { url, uniqueKey, payload, noRetry = false, sessionId, maxRetries, headers = {}, userData = {}, label, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, skipNavigation, enqueueStrategy, crawlDepth, } = options;
|
|
140
113
|
let { method = 'GET' } = options;
|
|
141
114
|
method = method.toUpperCase();
|
|
142
115
|
if (method === 'GET' && payload)
|
|
@@ -144,9 +117,7 @@ class CrawleeRequest {
|
|
|
144
117
|
if (uniqueKey && alwaysEnqueue) {
|
|
145
118
|
throw new Error('`alwaysEnqueue` cannot be used together with a custom `uniqueKey`.');
|
|
146
119
|
}
|
|
147
|
-
this.id = id;
|
|
148
120
|
this.url = url;
|
|
149
|
-
this.loadedUrl = loadedUrl;
|
|
150
121
|
this.uniqueKey =
|
|
151
122
|
uniqueKey ||
|
|
152
123
|
CrawleeRequest.computeUniqueKey({
|
|
@@ -160,61 +131,76 @@ class CrawleeRequest {
|
|
|
160
131
|
this.method = method;
|
|
161
132
|
this.payload = payload;
|
|
162
133
|
this.noRetry = noRetry;
|
|
163
|
-
this.retryCount =
|
|
164
|
-
this.errorMessages = [
|
|
134
|
+
this.retryCount = 0;
|
|
135
|
+
this.errorMessages = [];
|
|
165
136
|
this.headers = { ...headers };
|
|
166
|
-
this.handledAt = handledAt instanceof Date ? handledAt.toISOString() : handledAt;
|
|
167
137
|
if (label) {
|
|
168
138
|
userData.label = label;
|
|
169
139
|
}
|
|
140
|
+
// Read `__crawlee` explicitly - on a `userData` coming from another Request instance the
|
|
141
|
+
// bag is non-enumerable, so the spread alone would silently drop the internal state
|
|
142
|
+
// (e.g. `skipNavigation`) when a request is re-wrapped after a storage round trip.
|
|
143
|
+
this.#userData = { __crawlee: userData.__crawlee ?? {}, ...userData };
|
|
144
|
+
// `userData` must stay an enumerable own accessor — serialization in the storages relies on it
|
|
170
145
|
Object.defineProperties(this, {
|
|
171
|
-
_userData: {
|
|
172
|
-
value: { __crawlee: {}, ...userData },
|
|
173
|
-
enumerable: false,
|
|
174
|
-
writable: true,
|
|
175
|
-
},
|
|
176
146
|
userData: {
|
|
177
|
-
get: () => this
|
|
147
|
+
get: () => this.#userData,
|
|
178
148
|
set: (value) => {
|
|
179
149
|
Object.defineProperties(value, {
|
|
180
150
|
__crawlee: {
|
|
181
|
-
value: this.
|
|
151
|
+
value: this.#userData.__crawlee,
|
|
182
152
|
enumerable: false,
|
|
183
153
|
writable: true,
|
|
184
154
|
},
|
|
185
155
|
toJSON: {
|
|
186
156
|
value: () => {
|
|
187
|
-
if (Object.keys(this.
|
|
157
|
+
if (Object.keys(this.#userData.__crawlee).length > 0) {
|
|
188
158
|
return {
|
|
189
|
-
...this
|
|
190
|
-
__crawlee: this.
|
|
159
|
+
...this.#userData,
|
|
160
|
+
__crawlee: this.#userData.__crawlee,
|
|
191
161
|
};
|
|
192
162
|
}
|
|
193
|
-
return this
|
|
163
|
+
return this.#userData;
|
|
194
164
|
},
|
|
195
165
|
enumerable: false,
|
|
196
166
|
writable: true,
|
|
197
167
|
},
|
|
198
168
|
});
|
|
199
|
-
this
|
|
169
|
+
this.#userData = value;
|
|
200
170
|
},
|
|
201
171
|
enumerable: true,
|
|
202
172
|
},
|
|
203
173
|
});
|
|
204
174
|
// reassign userData to ensure internal `__crawlee` object is non-enumerable
|
|
205
175
|
this.userData = userData;
|
|
176
|
+
const crawlee = (this.userData.__crawlee ??= {});
|
|
206
177
|
if (skipNavigation != null)
|
|
207
|
-
|
|
178
|
+
crawlee.skipNavigation = skipNavigation;
|
|
208
179
|
if (maxRetries != null)
|
|
209
|
-
|
|
180
|
+
crawlee.maxRetries = maxRetries;
|
|
210
181
|
if (crawlDepth != null)
|
|
211
|
-
|
|
182
|
+
crawlee.crawlDepth ??= crawlDepth;
|
|
212
183
|
if (sessionId)
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
if (enqueueStrategy)
|
|
216
|
-
|
|
217
|
-
|
|
184
|
+
crawlee.sessionId = sessionId;
|
|
185
|
+
// A stored request keeps the strategy it was enqueued under.
|
|
186
|
+
if (enqueueStrategy)
|
|
187
|
+
crawlee.enqueueStrategy ??= enqueueStrategy;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Rebuilds a request from its stored form, including the processing state a
|
|
191
|
+
* {@link RequestOptions} object cannot carry. Subclasses get instances of themselves.
|
|
192
|
+
*/
|
|
193
|
+
static fromSchema(schema) {
|
|
194
|
+
const { id, retryCount = 0, errorMessages = [], handledAt, loadedUrl, ...options } = schema;
|
|
195
|
+
const request = new this(options);
|
|
196
|
+
request.id = id;
|
|
197
|
+
request.retryCount = retryCount;
|
|
198
|
+
request.errorMessages = [...errorMessages];
|
|
199
|
+
request.loadedUrl = loadedUrl;
|
|
200
|
+
// `apify-client` parses `*At` fields into `Date`s, whatever the schema says.
|
|
201
|
+
request.handledAt =
|
|
202
|
+
handledAt instanceof Date ? handledAt.toISOString() : handledAt;
|
|
203
|
+
return request;
|
|
218
204
|
}
|
|
219
205
|
/**
|
|
220
206
|
* Converts the Crawlee Request object to a `fetch` API Request object.
|
|
@@ -227,54 +213,6 @@ class CrawleeRequest {
|
|
|
227
213
|
body: this.payload,
|
|
228
214
|
});
|
|
229
215
|
}
|
|
230
|
-
/**
|
|
231
|
-
* Tells the crawler processing this request to skip the navigation and process the request directly.
|
|
232
|
-
*
|
|
233
|
-
* When this is set to `true`, the crawling context will not contain the results of the navigation
|
|
234
|
-
* (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
|
|
235
|
-
* Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
|
|
236
|
-
*/
|
|
237
|
-
get skipNavigation() {
|
|
238
|
-
return this.userData.__crawlee?.skipNavigation ?? false;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Tells the crawler processing this request to skip the navigation and process the request directly.
|
|
242
|
-
*
|
|
243
|
-
* When this is set to `true`, the crawling context will not contain the results of the navigation
|
|
244
|
-
* (e.g. `response`, `body`, `contentType`, `$` or `request.loadedUrl`).
|
|
245
|
-
* Accessing these properties will throw a {@link NavigationSkippedError} at runtime.
|
|
246
|
-
*/
|
|
247
|
-
set skipNavigation(value) {
|
|
248
|
-
if (!this.userData.__crawlee) {
|
|
249
|
-
this.userData.__crawlee = { skipNavigation: value };
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
this.userData.__crawlee.skipNavigation = value;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Depth of the request in the current crawl tree.
|
|
257
|
-
* Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
|
|
258
|
-
*/
|
|
259
|
-
get crawlDepth() {
|
|
260
|
-
return this.userData.__crawlee?.crawlDepth ?? 0;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Depth of the request in the current crawl tree.
|
|
264
|
-
* Note that this is dependent on the crawler setup and might produce unexpected results when used with multiple crawlers.
|
|
265
|
-
*/
|
|
266
|
-
set crawlDepth(value) {
|
|
267
|
-
this.userData.__crawlee ??= {};
|
|
268
|
-
this.userData.__crawlee.crawlDepth = value;
|
|
269
|
-
}
|
|
270
|
-
/** 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. */
|
|
271
|
-
get sessionId() {
|
|
272
|
-
return this.userData.__crawlee?.sessionId;
|
|
273
|
-
}
|
|
274
|
-
set sessionId(value) {
|
|
275
|
-
this.userData.__crawlee ??= {};
|
|
276
|
-
this.userData.__crawlee.sessionId = value;
|
|
277
|
-
}
|
|
278
216
|
/** shortcut for getting `request.userData.label` */
|
|
279
217
|
get label() {
|
|
280
218
|
return this.userData.label;
|
|
@@ -283,110 +221,6 @@ class CrawleeRequest {
|
|
|
283
221
|
set label(value) {
|
|
284
222
|
this.userData.label = value;
|
|
285
223
|
}
|
|
286
|
-
/** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
|
|
287
|
-
get maxRetries() {
|
|
288
|
-
return this.userData.__crawlee?.maxRetries;
|
|
289
|
-
}
|
|
290
|
-
/** Maximum number of retries for this request. Allows to override the global `maxRequestRetries` option of `BasicCrawler`. */
|
|
291
|
-
set maxRetries(value) {
|
|
292
|
-
if (!this.userData.__crawlee) {
|
|
293
|
-
this.userData.__crawlee = { maxRetries: value };
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
this.userData.__crawlee.maxRetries = value;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/** Describes the request's current lifecycle state. */
|
|
300
|
-
get state() {
|
|
301
|
-
return this.userData.__crawlee?.state ?? RequestState.UNPROCESSED;
|
|
302
|
-
}
|
|
303
|
-
/** Describes the request's current lifecycle state. */
|
|
304
|
-
set state(value) {
|
|
305
|
-
if (!this.userData.__crawlee) {
|
|
306
|
-
this.userData.__crawlee = { state: value };
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
this.userData.__crawlee.state = value;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Reason for skipping this request.
|
|
314
|
-
*/
|
|
315
|
-
get skippedReason() {
|
|
316
|
-
return this.userData.__crawlee?.skippedReason;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Reason for skipping this request.
|
|
320
|
-
*/
|
|
321
|
-
set skippedReason(value) {
|
|
322
|
-
if (!this.userData.__crawlee) {
|
|
323
|
-
this.userData.__crawlee = { skippedReason: value };
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
this.userData.__crawlee.skippedReason = value;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
get enqueueStrategy() {
|
|
330
|
-
return this.userData.__crawlee?.enqueueStrategy;
|
|
331
|
-
}
|
|
332
|
-
set enqueueStrategy(value) {
|
|
333
|
-
if (!this.userData.__crawlee) {
|
|
334
|
-
this.userData.__crawlee = { enqueueStrategy: value };
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
this.userData.__crawlee.enqueueStrategy = value;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Stores information about an error that occurred during processing of this request.
|
|
342
|
-
*
|
|
343
|
-
* You should always use Error instances when throwing errors in JavaScript.
|
|
344
|
-
*
|
|
345
|
-
* Nevertheless, to improve the debugging experience when using third party libraries
|
|
346
|
-
* that may not always throw an Error instance, the function performs a type
|
|
347
|
-
* inspection of the passed argument and attempts to extract as much information
|
|
348
|
-
* as possible, since just throwing a bad type error makes any debugging rather difficult.
|
|
349
|
-
*
|
|
350
|
-
* @param errorOrMessage Error object or error message to be stored in the request.
|
|
351
|
-
* @param [options]
|
|
352
|
-
*/
|
|
353
|
-
pushErrorMessage(errorOrMessage, options = {}) {
|
|
354
|
-
const { omitStack } = options;
|
|
355
|
-
let message;
|
|
356
|
-
const type = typeof errorOrMessage;
|
|
357
|
-
if (type === 'object') {
|
|
358
|
-
if (!errorOrMessage) {
|
|
359
|
-
message = 'null';
|
|
360
|
-
}
|
|
361
|
-
else if (errorOrMessage instanceof Error) {
|
|
362
|
-
message = omitStack
|
|
363
|
-
? errorOrMessage.message
|
|
364
|
-
: // .stack includes the message
|
|
365
|
-
errorOrMessage.stack;
|
|
366
|
-
}
|
|
367
|
-
else if (Reflect.has(Object(errorOrMessage), 'message')) {
|
|
368
|
-
message = Reflect.get(Object(errorOrMessage), 'message');
|
|
369
|
-
}
|
|
370
|
-
else if (errorOrMessage.toString() !== '[object Object]') {
|
|
371
|
-
message = errorOrMessage.toString();
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
try {
|
|
375
|
-
message = util.inspect(errorOrMessage);
|
|
376
|
-
}
|
|
377
|
-
catch (err) {
|
|
378
|
-
message = 'Unable to extract any message from the received object.';
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
else if (type === 'undefined') {
|
|
383
|
-
message = 'undefined';
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
message = errorOrMessage.toString();
|
|
387
|
-
}
|
|
388
|
-
this.errorMessages.push(message);
|
|
389
|
-
}
|
|
390
224
|
/** @internal */
|
|
391
225
|
static computeUniqueKey({ url, method = 'GET', payload, keepUrlFragment = false, useExtendedUniqueKey = false, alwaysEnqueue = false, }) {
|
|
392
226
|
const normalizedMethod = method.toUpperCase();
|
package/serialization.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { pipeline as streamPipeline, Readable, Writable } from 'node:stream';
|
|
2
2
|
import util from 'node:util';
|
|
3
3
|
import zlib from 'node:zlib';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import StreamArray from 'stream-json/streamers/stream-array.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { parseArgument, schemas } from './validators.js';
|
|
6
7
|
const pipeline = util.promisify(streamPipeline);
|
|
8
|
+
const uint8ArraySchema = z.instanceof(Uint8Array);
|
|
7
9
|
/**
|
|
8
10
|
* Transforms an array of items to a JSON in a streaming
|
|
9
11
|
* fashion to save memory. It operates in batches to speed
|
|
@@ -11,32 +13,31 @@ const pipeline = util.promisify(streamPipeline);
|
|
|
11
13
|
* @internal
|
|
12
14
|
*/
|
|
13
15
|
class ArrayToJson extends Readable {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
#offset = 0;
|
|
17
|
+
#batchSize;
|
|
18
|
+
#data;
|
|
17
19
|
constructor(data, options = {}) {
|
|
18
20
|
super({
|
|
19
21
|
...options,
|
|
20
22
|
autoDestroy: true,
|
|
21
23
|
emitClose: true,
|
|
22
24
|
});
|
|
23
|
-
this.data = data;
|
|
24
25
|
const { batchSize = 10000 } = options;
|
|
25
|
-
this
|
|
26
|
-
this
|
|
26
|
+
this.#batchSize = batchSize;
|
|
27
|
+
this.#data = data;
|
|
27
28
|
this.push('[');
|
|
28
29
|
}
|
|
29
30
|
_read() {
|
|
30
31
|
try {
|
|
31
|
-
const items = this
|
|
32
|
+
const items = this.#data.slice(this.#offset, this.#offset + this.#batchSize);
|
|
32
33
|
if (items.length) {
|
|
33
34
|
const json = JSON.stringify(items);
|
|
34
35
|
// Strip brackets to flatten the batch.
|
|
35
36
|
const itemString = json.substring(1, json.length - 1);
|
|
36
|
-
if (this
|
|
37
|
+
if (this.#offset > 0)
|
|
37
38
|
this.push(',', 'utf8');
|
|
38
39
|
this.push(itemString, 'utf8');
|
|
39
|
-
this
|
|
40
|
+
this.#offset += this.#batchSize;
|
|
40
41
|
}
|
|
41
42
|
else {
|
|
42
43
|
this.push(']');
|
|
@@ -58,7 +59,7 @@ class ArrayToJson extends Readable {
|
|
|
58
59
|
* @internal
|
|
59
60
|
*/
|
|
60
61
|
export async function serializeArray(data) {
|
|
61
|
-
|
|
62
|
+
parseArgument(data, schemas.anyArray);
|
|
62
63
|
const { chunks, collector } = createChunkCollector();
|
|
63
64
|
await pipeline(new ArrayToJson(data), zlib.createGzip(), collector);
|
|
64
65
|
return Buffer.concat(chunks);
|
|
@@ -73,9 +74,9 @@ export async function serializeArray(data) {
|
|
|
73
74
|
* @internal
|
|
74
75
|
*/
|
|
75
76
|
export async function deserializeArray(compressedData) {
|
|
76
|
-
|
|
77
|
+
parseArgument(compressedData, uint8ArraySchema);
|
|
77
78
|
const { chunks, collector } = createChunkCollector({ fromValuesStream: true });
|
|
78
|
-
await pipeline(Readable.from([compressedData]), zlib.createGunzip(), StreamArray.
|
|
79
|
+
await pipeline(Readable.from([compressedData]), zlib.createGunzip(), StreamArray.withParserAsStream(), collector);
|
|
79
80
|
return chunks;
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
@@ -88,8 +89,8 @@ export async function deserializeArray(compressedData) {
|
|
|
88
89
|
* @internal
|
|
89
90
|
*/
|
|
90
91
|
export function createDeserialize(compressedData) {
|
|
91
|
-
|
|
92
|
-
const streamArray = StreamArray.
|
|
92
|
+
parseArgument(compressedData, uint8ArraySchema);
|
|
93
|
+
const streamArray = StreamArray.withParserAsStream();
|
|
93
94
|
const destination = pluckValue(streamArray);
|
|
94
95
|
streamPipeline(Readable.from([compressedData]), zlib.createGunzip(), destination, (err) => destination.emit(err));
|
|
95
96
|
return destination;
|
package/service_locator.d.ts
CHANGED
|
@@ -60,8 +60,20 @@ interface ServiceLocatorInterface {
|
|
|
60
60
|
getChildLog(prefix: string): CrawleeLogger;
|
|
61
61
|
/**
|
|
62
62
|
* Get the storage instance manager (shared across all storage types).
|
|
63
|
+
* @internal
|
|
63
64
|
*/
|
|
64
65
|
getStorageInstanceManager(): StorageInstanceManager;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the currently set services without triggering the implicit creation of defaults.
|
|
68
|
+
* Used to inherit already-materialized services into crawler-scoped service locators.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
getServicesIfSet(): {
|
|
72
|
+
configuration?: Configuration;
|
|
73
|
+
eventManager?: EventManager;
|
|
74
|
+
storageBackend?: StorageBackend;
|
|
75
|
+
logger?: CrawleeLogger;
|
|
76
|
+
};
|
|
65
77
|
/**
|
|
66
78
|
* Resets the service locator to its initial state.
|
|
67
79
|
* Used mainly for testing purposes.
|
|
@@ -101,16 +113,7 @@ interface ServiceLocatorInterface {
|
|
|
101
113
|
* ```
|
|
102
114
|
*/
|
|
103
115
|
export declare class ServiceLocator implements ServiceLocatorInterface {
|
|
104
|
-
private
|
|
105
|
-
private eventManager?;
|
|
106
|
-
private storageBackend?;
|
|
107
|
-
private logger?;
|
|
108
|
-
/**
|
|
109
|
-
* Unified storage instance manager for Dataset, KeyValueStore, and RequestQueue.
|
|
110
|
-
* Shared across all ServiceLocator instances (global singleton), matching crawlee-python.
|
|
111
|
-
* Per-crawler isolation is achieved via `clientCacheKey`, not separate manager instances.
|
|
112
|
-
*/
|
|
113
|
-
private static storageInstanceManager?;
|
|
116
|
+
#private;
|
|
114
117
|
/**
|
|
115
118
|
* Creates a new ServiceLocator instance.
|
|
116
119
|
*
|
|
@@ -120,6 +123,13 @@ export declare class ServiceLocator implements ServiceLocatorInterface {
|
|
|
120
123
|
* @param logger Optional logger instance to use
|
|
121
124
|
*/
|
|
122
125
|
constructor(configuration?: Configuration, eventManager?: EventManager, storageBackend?: StorageBackend, logger?: CrawleeLogger);
|
|
126
|
+
/** @internal */
|
|
127
|
+
getServicesIfSet(): {
|
|
128
|
+
configuration?: Configuration;
|
|
129
|
+
eventManager?: EventManager;
|
|
130
|
+
storageBackend?: StorageBackend;
|
|
131
|
+
logger?: CrawleeLogger;
|
|
132
|
+
};
|
|
123
133
|
getConfiguration(): Configuration;
|
|
124
134
|
setConfiguration(configuration: Configuration): void;
|
|
125
135
|
getEventManager(): EventManager;
|
|
@@ -129,7 +139,9 @@ export declare class ServiceLocator implements ServiceLocatorInterface {
|
|
|
129
139
|
getLogger(): CrawleeLogger;
|
|
130
140
|
setLogger(logger: CrawleeLogger): void;
|
|
131
141
|
getChildLog(prefix: string): CrawleeLogger;
|
|
142
|
+
/** @internal */
|
|
132
143
|
getStorageInstanceManager(): StorageInstanceManager;
|
|
144
|
+
/** @internal */
|
|
133
145
|
reset(): void;
|
|
134
146
|
}
|
|
135
147
|
/**
|