@crawlee/core 4.0.0-beta.105 → 4.0.0-beta.107
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 +85 -85
- package/autoscaling/client_load_signal.d.ts +1 -5
- package/autoscaling/client_load_signal.js +20 -20
- package/autoscaling/concurrency_system.d.ts +5 -20
- package/autoscaling/concurrency_system.js +81 -80
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +10 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/load_signal.d.ts +1 -1
- package/autoscaling/load_signal.js +12 -11
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +40 -41
- package/autoscaling/snapshotter.d.ts +1 -4
- package/autoscaling/snapshotter.js +12 -12
- package/autoscaling/system_status.d.ts +1 -3
- package/autoscaling/system_status.js +11 -11
- package/configuration.d.ts +1 -1
- package/configuration.js +3 -3
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/statistics.d.ts +1 -8
- package/crawlers/statistics.js +45 -44
- package/events/event_manager.d.ts +1 -1
- package/events/event_manager.js +3 -3
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +3 -3
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +1 -5
- package/memory-storage/memory-storage.js +2 -2
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +6 -5
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +13 -12
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +59 -58
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +5 -5
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +8 -8
- package/recoverable_state.d.ts +1 -10
- package/recoverable_state.js +41 -41
- package/request.d.ts +1 -2
- package/request.js +10 -13
- package/router.d.ts +1 -4
- package/router.js +23 -23
- package/serialization.js +8 -9
- package/service_locator.d.ts +1 -10
- package/service_locator.js +48 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +54 -54
- package/session_pool/session_pool.d.ts +2 -11
- package/session_pool/session_pool.js +59 -58
- package/storages/dataset.d.ts +1 -1
- package/storages/dataset.js +5 -5
- package/storages/key_value_store.d.ts +1 -4
- package/storages/key_value_store.js +21 -20
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +2 -22
- package/storages/request_list.js +74 -73
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +2 -18
- package/storages/request_queue.js +37 -35
- package/storages/sitemap_request_loader.d.ts +1 -44
- package/storages/sitemap_request_loader.js +87 -87
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +17 -17
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/transaction.d.ts +1 -3
- package/storages/transaction.js +17 -17
- package/system-info/runtime.js +7 -7
package/storages/request_list.js
CHANGED
|
@@ -72,7 +72,7 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
|
|
|
72
72
|
* @category Sources
|
|
73
73
|
*/
|
|
74
74
|
export class RequestList {
|
|
75
|
-
log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
|
|
75
|
+
#log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
|
|
76
76
|
/**
|
|
77
77
|
* Array of all requests from all sources, in the order as they appeared in sources.
|
|
78
78
|
* All requests in the array have distinct uniqueKey!
|
|
@@ -80,9 +80,9 @@ export class RequestList {
|
|
|
80
80
|
*/
|
|
81
81
|
requests = [];
|
|
82
82
|
/** Index to the next item in requests array to fetch. All previous requests are either handled or in progress. */
|
|
83
|
-
nextIndex = 0;
|
|
83
|
+
#nextIndex = 0;
|
|
84
84
|
/** Dictionary, key is Request.uniqueKey, value is corresponding index in the requests array. */
|
|
85
|
-
uniqueKeyToIndex = {};
|
|
85
|
+
#uniqueKeyToIndex = {};
|
|
86
86
|
/**
|
|
87
87
|
* Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
|
|
88
88
|
* @internal
|
|
@@ -93,7 +93,7 @@ export class RequestList {
|
|
|
93
93
|
* re-crawled after a restart. They are served before advancing through the rest of the sources.
|
|
94
94
|
* @internal
|
|
95
95
|
*/
|
|
96
|
-
requestsToRetry = [];
|
|
96
|
+
#requestsToRetry = [];
|
|
97
97
|
/**
|
|
98
98
|
* Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
|
|
99
99
|
* @internal
|
|
@@ -104,17 +104,18 @@ export class RequestList {
|
|
|
104
104
|
* @internal
|
|
105
105
|
*/
|
|
106
106
|
areRequestsPersisted = false;
|
|
107
|
-
isLoading = false;
|
|
108
|
-
isInitialized = false;
|
|
109
|
-
persistStateKey;
|
|
110
|
-
persistRequestsKey;
|
|
111
|
-
initialState;
|
|
112
|
-
store;
|
|
113
|
-
keepDuplicateUrls;
|
|
107
|
+
#isLoading = false;
|
|
108
|
+
#isInitialized = false;
|
|
109
|
+
#persistStateKey;
|
|
110
|
+
#persistRequestsKey;
|
|
111
|
+
#initialState;
|
|
112
|
+
#store;
|
|
113
|
+
#keepDuplicateUrls;
|
|
114
|
+
// kept as TS-private: request_list tests read this field directly
|
|
114
115
|
sources;
|
|
115
|
-
sourcesFunction;
|
|
116
|
-
proxyConfiguration;
|
|
117
|
-
httpClient;
|
|
116
|
+
#sourcesFunction;
|
|
117
|
+
#proxyConfiguration;
|
|
118
|
+
#httpClient;
|
|
118
119
|
/**
|
|
119
120
|
* To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
|
|
120
121
|
* @param options All `RequestList` configuration options
|
|
@@ -139,17 +140,17 @@ export class RequestList {
|
|
|
139
140
|
proxyConfiguration: ow.optional.object,
|
|
140
141
|
httpClient: ow.optional.object,
|
|
141
142
|
}));
|
|
142
|
-
this
|
|
143
|
-
this
|
|
144
|
-
this
|
|
145
|
-
this
|
|
143
|
+
this.#persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
|
|
144
|
+
this.#persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
|
|
145
|
+
this.#initialState = state;
|
|
146
|
+
this.#httpClient = httpClient;
|
|
146
147
|
// If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
|
|
147
|
-
this
|
|
148
|
+
this.#keepDuplicateUrls = keepDuplicateUrls;
|
|
148
149
|
// Will be empty after initialization to save memory.
|
|
149
150
|
this.sources = sources ? [...sources] : [];
|
|
150
|
-
this
|
|
151
|
+
this.#sourcesFunction = sourcesFunction;
|
|
151
152
|
// The proxy configuration used for `requestsFromUrl` requests.
|
|
152
|
-
this
|
|
153
|
+
this.#proxyConfiguration = proxyConfiguration;
|
|
153
154
|
this.persistState = this.persistState.bind(this);
|
|
154
155
|
}
|
|
155
156
|
/**
|
|
@@ -157,10 +158,10 @@ export class RequestList {
|
|
|
157
158
|
* This function must be called before you can start using the instance in a meaningful way.
|
|
158
159
|
*/
|
|
159
160
|
async initialize() {
|
|
160
|
-
if (this
|
|
161
|
+
if (this.#isLoading) {
|
|
161
162
|
throw new Error('RequestList sources are already loading or were loaded.');
|
|
162
163
|
}
|
|
163
|
-
this
|
|
164
|
+
this.#isLoading = true;
|
|
164
165
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
165
166
|
const [state, persistedRequests] = await this.loadStateAndPersistedRequests();
|
|
166
167
|
// Add persisted requests / new sources in a memory efficient way because with very
|
|
@@ -172,10 +173,10 @@ export class RequestList {
|
|
|
172
173
|
await this.addRequestsFromSources();
|
|
173
174
|
}
|
|
174
175
|
this.restoreState(state);
|
|
175
|
-
this
|
|
176
|
-
if (this
|
|
176
|
+
this.#isInitialized = true;
|
|
177
|
+
if (this.#persistRequestsKey && !this.areRequestsPersisted)
|
|
177
178
|
await this.persistRequests();
|
|
178
|
-
if (this
|
|
179
|
+
if (this.#persistStateKey) {
|
|
179
180
|
serviceLocator.getEventManager().on("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
180
181
|
}
|
|
181
182
|
return this;
|
|
@@ -224,9 +225,9 @@ export class RequestList {
|
|
|
224
225
|
}
|
|
225
226
|
// Drop the original array full of empty indexes.
|
|
226
227
|
this.sources = [];
|
|
227
|
-
if (this
|
|
228
|
+
if (this.#sourcesFunction) {
|
|
228
229
|
try {
|
|
229
|
-
const sourcesFromFunction = await this
|
|
230
|
+
const sourcesFromFunction = await this.#sourcesFunction();
|
|
230
231
|
const sourcesFromFunctionCount = sourcesFromFunction.length;
|
|
231
232
|
for (let i = 0; i < sourcesFromFunctionCount; i++) {
|
|
232
233
|
const source = sourcesFromFunction[i];
|
|
@@ -246,19 +247,19 @@ export class RequestList {
|
|
|
246
247
|
* @inheritDoc
|
|
247
248
|
*/
|
|
248
249
|
async persistState() {
|
|
249
|
-
if (!this
|
|
250
|
+
if (!this.#persistStateKey) {
|
|
250
251
|
throw new Error('Cannot persist state. options.persistStateKey is not set.');
|
|
251
252
|
}
|
|
252
253
|
if (this.isStatePersisted)
|
|
253
254
|
return;
|
|
254
255
|
try {
|
|
255
|
-
this
|
|
256
|
-
await this
|
|
256
|
+
this.#store ??= await KeyValueStore.open();
|
|
257
|
+
await this.#store.setValue(this.#persistStateKey, this.getState());
|
|
257
258
|
this.isStatePersisted = true;
|
|
258
259
|
}
|
|
259
260
|
catch (e) {
|
|
260
261
|
const err = e;
|
|
261
|
-
this
|
|
262
|
+
this.#log.exception(err, 'Attempted to persist state, but failed.');
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
/**
|
|
@@ -268,7 +269,7 @@ export class RequestList {
|
|
|
268
269
|
*/
|
|
269
270
|
async teardown() {
|
|
270
271
|
serviceLocator.getEventManager().off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
271
|
-
if (this
|
|
272
|
+
if (this.#persistStateKey) {
|
|
272
273
|
await this.persistState();
|
|
273
274
|
}
|
|
274
275
|
}
|
|
@@ -279,8 +280,8 @@ export class RequestList {
|
|
|
279
280
|
*/
|
|
280
281
|
async persistRequests() {
|
|
281
282
|
const serializedRequests = await serializeArray(this.requests);
|
|
282
|
-
this
|
|
283
|
-
await this
|
|
283
|
+
this.#store ??= await KeyValueStore.open();
|
|
284
|
+
await this.#store.setValue(this.#persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
|
|
284
285
|
this.areRequestsPersisted = true;
|
|
285
286
|
}
|
|
286
287
|
/**
|
|
@@ -303,7 +304,7 @@ export class RequestList {
|
|
|
303
304
|
}
|
|
304
305
|
const deleteFromInProgress = [];
|
|
305
306
|
state.inProgress.forEach((uniqueKey) => {
|
|
306
|
-
const index = this
|
|
307
|
+
const index = this.#uniqueKeyToIndex[uniqueKey];
|
|
307
308
|
if (typeof index !== 'number') {
|
|
308
309
|
throw new Error('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
|
|
309
310
|
}
|
|
@@ -311,7 +312,7 @@ export class RequestList {
|
|
|
311
312
|
deleteFromInProgress.push(uniqueKey);
|
|
312
313
|
}
|
|
313
314
|
});
|
|
314
|
-
this
|
|
315
|
+
this.#nextIndex = state.nextIndex;
|
|
315
316
|
this.inProgress = new Set(state.inProgress);
|
|
316
317
|
// WORKAROUND:
|
|
317
318
|
// It happened to some users that state object contained something like:
|
|
@@ -327,7 +328,7 @@ export class RequestList {
|
|
|
327
328
|
// As a workaround, we just remove all inProgress requests whose index >= nextIndex,
|
|
328
329
|
// since they will be crawled again.
|
|
329
330
|
if (deleteFromInProgress.length) {
|
|
330
|
-
this
|
|
331
|
+
this.#log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
|
|
331
332
|
deleteFromInProgress,
|
|
332
333
|
});
|
|
333
334
|
for (const uniqueKey of deleteFromInProgress) {
|
|
@@ -335,7 +336,7 @@ export class RequestList {
|
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
// All in-progress requests were interrupted and need to be re-crawled.
|
|
338
|
-
this
|
|
339
|
+
this.#requestsToRetry = [...this.inProgress];
|
|
339
340
|
}
|
|
340
341
|
/**
|
|
341
342
|
* Attempts to load state and requests using the `RequestList` configuration
|
|
@@ -344,19 +345,19 @@ export class RequestList {
|
|
|
344
345
|
async loadStateAndPersistedRequests() {
|
|
345
346
|
let state;
|
|
346
347
|
let persistedRequests;
|
|
347
|
-
if (this
|
|
348
|
-
state = this
|
|
349
|
-
this
|
|
348
|
+
if (this.#initialState) {
|
|
349
|
+
state = this.#initialState;
|
|
350
|
+
this.#log.debug('Loaded state from options.state argument.');
|
|
350
351
|
}
|
|
351
|
-
else if (this
|
|
352
|
-
state = await this.getPersistedState(this
|
|
352
|
+
else if (this.#persistStateKey) {
|
|
353
|
+
state = await this.getPersistedState(this.#persistStateKey);
|
|
353
354
|
if (state)
|
|
354
|
-
this
|
|
355
|
+
this.#log.debug('Loaded state from key value store using the persistStateKey.');
|
|
355
356
|
}
|
|
356
|
-
if (this
|
|
357
|
-
persistedRequests = await this.getPersistedState(this
|
|
357
|
+
if (this.#persistRequestsKey) {
|
|
358
|
+
persistedRequests = await this.getPersistedState(this.#persistRequestsKey);
|
|
358
359
|
if (persistedRequests)
|
|
359
|
-
this
|
|
360
|
+
this.#log.debug('Loaded requests from key value store using the persistRequestsKey.');
|
|
360
361
|
}
|
|
361
362
|
return [state, persistedRequests];
|
|
362
363
|
}
|
|
@@ -367,8 +368,8 @@ export class RequestList {
|
|
|
367
368
|
getState() {
|
|
368
369
|
this.ensureIsInitialized();
|
|
369
370
|
return {
|
|
370
|
-
nextIndex: this
|
|
371
|
-
nextUniqueKey: this
|
|
371
|
+
nextIndex: this.#nextIndex,
|
|
372
|
+
nextUniqueKey: this.#nextIndex < this.requests.length ? this.requests[this.#nextIndex].uniqueKey : null,
|
|
372
373
|
inProgress: [...this.inProgress],
|
|
373
374
|
};
|
|
374
375
|
}
|
|
@@ -377,14 +378,14 @@ export class RequestList {
|
|
|
377
378
|
*/
|
|
378
379
|
async isEmpty() {
|
|
379
380
|
this.ensureIsInitialized();
|
|
380
|
-
return this
|
|
381
|
+
return this.#requestsToRetry.length === 0 && this.#nextIndex >= this.requests.length;
|
|
381
382
|
}
|
|
382
383
|
/**
|
|
383
384
|
* @inheritDoc
|
|
384
385
|
*/
|
|
385
386
|
async isFinished() {
|
|
386
387
|
this.ensureIsInitialized();
|
|
387
|
-
return this.inProgress.size === 0 && this
|
|
388
|
+
return this.inProgress.size === 0 && this.#nextIndex >= this.requests.length;
|
|
388
389
|
}
|
|
389
390
|
/**
|
|
390
391
|
* @inheritDoc
|
|
@@ -392,17 +393,17 @@ export class RequestList {
|
|
|
392
393
|
async fetchNextRequest() {
|
|
393
394
|
this.ensureIsInitialized();
|
|
394
395
|
// First re-serve any requests that were interrupted before the last state persist.
|
|
395
|
-
const uniqueKey = this
|
|
396
|
+
const uniqueKey = this.#requestsToRetry.shift();
|
|
396
397
|
if (uniqueKey) {
|
|
397
|
-
const index = this
|
|
398
|
+
const index = this.#uniqueKeyToIndex[uniqueKey];
|
|
398
399
|
return this.ensureRequest(this.requests[index], index);
|
|
399
400
|
}
|
|
400
401
|
// Otherwise return next request.
|
|
401
|
-
if (this
|
|
402
|
-
const index = this
|
|
402
|
+
if (this.#nextIndex < this.requests.length) {
|
|
403
|
+
const index = this.#nextIndex;
|
|
403
404
|
const request = this.requests[index];
|
|
404
405
|
this.inProgress.add(request.uniqueKey);
|
|
405
|
-
this
|
|
406
|
+
this.#nextIndex++;
|
|
406
407
|
this.isStatePersisted = false;
|
|
407
408
|
return this.ensureRequest(request, index);
|
|
408
409
|
}
|
|
@@ -446,7 +447,7 @@ export class RequestList {
|
|
|
446
447
|
fetchedRequests.forEach((request) => this.addRequest(request));
|
|
447
448
|
const fetchedCount = fetchedRequests.length;
|
|
448
449
|
const importedCount = this.requests.length - originalLength;
|
|
449
|
-
this
|
|
450
|
+
this.#log.info('Fetched and loaded Requests from a remote resource.', {
|
|
450
451
|
requestsFromUrl,
|
|
451
452
|
regex,
|
|
452
453
|
fetchedCount,
|
|
@@ -456,8 +457,8 @@ export class RequestList {
|
|
|
456
457
|
});
|
|
457
458
|
}
|
|
458
459
|
async getPersistedState(key) {
|
|
459
|
-
this
|
|
460
|
-
const state = await this
|
|
460
|
+
this.#store ??= await KeyValueStore.open();
|
|
461
|
+
const state = await this.#store.getValue(key);
|
|
461
462
|
return state;
|
|
462
463
|
}
|
|
463
464
|
/**
|
|
@@ -468,10 +469,10 @@ export class RequestList {
|
|
|
468
469
|
// Download remote resource and parse URLs.
|
|
469
470
|
let urlsArr;
|
|
470
471
|
try {
|
|
471
|
-
urlsArr = await this.
|
|
472
|
+
urlsArr = await this.downloadListOfUrls({
|
|
472
473
|
url: requestsFromUrl,
|
|
473
474
|
urlRegExp: regex,
|
|
474
|
-
proxyUrl: (await this
|
|
475
|
+
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
475
476
|
});
|
|
476
477
|
}
|
|
477
478
|
catch (err) {
|
|
@@ -479,7 +480,7 @@ export class RequestList {
|
|
|
479
480
|
}
|
|
480
481
|
// Skip if resource contained no URLs.
|
|
481
482
|
if (!urlsArr.length) {
|
|
482
|
-
this
|
|
483
|
+
this.#log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
|
|
483
484
|
return [];
|
|
484
485
|
}
|
|
485
486
|
return urlsArr.map((url) => ({ url, ...sharedOpts }));
|
|
@@ -507,18 +508,18 @@ export class RequestList {
|
|
|
507
508
|
const hasUniqueKey = Reflect.has(Object(source), 'uniqueKey');
|
|
508
509
|
request.uniqueKey ??= Request.computeUniqueKey(request);
|
|
509
510
|
// Add index to uniqueKey if duplicates are to be kept
|
|
510
|
-
if (this
|
|
511
|
+
if (this.#keepDuplicateUrls && !hasUniqueKey) {
|
|
511
512
|
request.uniqueKey += `-${this.requests.length}`;
|
|
512
513
|
}
|
|
513
514
|
const { uniqueKey } = request;
|
|
514
515
|
this.ensureUniqueKeyValid(uniqueKey);
|
|
515
516
|
// Skip requests with duplicate uniqueKey
|
|
516
|
-
if (!Object.hasOwn(this
|
|
517
|
-
this
|
|
517
|
+
if (!Object.hasOwn(this.#uniqueKeyToIndex, uniqueKey)) {
|
|
518
|
+
this.#uniqueKeyToIndex[uniqueKey] = this.requests.length;
|
|
518
519
|
this.requests.push(request);
|
|
519
520
|
}
|
|
520
|
-
else if (this
|
|
521
|
-
this
|
|
521
|
+
else if (this.#keepDuplicateUrls) {
|
|
522
|
+
this.#log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
/**
|
|
@@ -542,7 +543,7 @@ export class RequestList {
|
|
|
542
543
|
* Throws an error if request list wasn't initialized.
|
|
543
544
|
*/
|
|
544
545
|
ensureIsInitialized() {
|
|
545
|
-
if (!this
|
|
546
|
+
if (!this.#isInitialized) {
|
|
546
547
|
throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
|
|
547
548
|
}
|
|
548
549
|
}
|
|
@@ -558,7 +559,7 @@ export class RequestList {
|
|
|
558
559
|
*/
|
|
559
560
|
async getPendingCount() {
|
|
560
561
|
this.ensureIsInitialized();
|
|
561
|
-
return this.requests.length - (this
|
|
562
|
+
return this.requests.length - (this.#nextIndex - this.inProgress.size);
|
|
562
563
|
}
|
|
563
564
|
/**
|
|
564
565
|
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
@@ -576,7 +577,7 @@ export class RequestList {
|
|
|
576
577
|
*/
|
|
577
578
|
async getHandledCount() {
|
|
578
579
|
this.ensureIsInitialized();
|
|
579
|
-
return this
|
|
580
|
+
return this.#nextIndex - this.inProgress.size;
|
|
580
581
|
}
|
|
581
582
|
/**
|
|
582
583
|
* Opens a request list and returns a promise resolving to an instance
|
|
@@ -654,10 +655,10 @@ export class RequestList {
|
|
|
654
655
|
/**
|
|
655
656
|
* @internal wraps public utility for mocking purposes
|
|
656
657
|
*/
|
|
657
|
-
async
|
|
658
|
+
async downloadListOfUrls(options) {
|
|
658
659
|
return downloadListOfUrls({
|
|
659
660
|
...options,
|
|
660
|
-
httpClient: this
|
|
661
|
+
httpClient: this.#httpClient,
|
|
661
662
|
});
|
|
662
663
|
}
|
|
663
664
|
}
|
|
@@ -9,16 +9,7 @@ import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueO
|
|
|
9
9
|
* It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
|
|
10
10
|
*/
|
|
11
11
|
export declare class RequestManagerTandem implements IRequestManager {
|
|
12
|
-
private
|
|
13
|
-
private requestLoader;
|
|
14
|
-
private requestManagerPromise?;
|
|
15
|
-
private resolvedRequestManager?;
|
|
16
|
-
private requestManagerFactory;
|
|
17
|
-
/**
|
|
18
|
-
* The latest expected request-processing time hinted via {@link setExpectedRequestProcessingTimeSecs}.
|
|
19
|
-
* Remembered so it can be applied to the writable manager once it is lazily resolved.
|
|
20
|
-
*/
|
|
21
|
-
private expectedRequestProcessingSecs?;
|
|
12
|
+
#private;
|
|
22
13
|
/**
|
|
23
14
|
* @param requestLoader The read-only loader to read requests from first.
|
|
24
15
|
* @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
|
|
@@ -5,16 +5,16 @@ import { serviceLocator } from '../service_locator.js';
|
|
|
5
5
|
* It first reads requests from the loader and then, when needed, transfers them in batches to the manager.
|
|
6
6
|
*/
|
|
7
7
|
export class RequestManagerTandem {
|
|
8
|
-
log;
|
|
9
|
-
requestLoader;
|
|
10
|
-
requestManagerPromise;
|
|
11
|
-
resolvedRequestManager;
|
|
12
|
-
requestManagerFactory;
|
|
8
|
+
#log;
|
|
9
|
+
#requestLoader;
|
|
10
|
+
#requestManagerPromise;
|
|
11
|
+
#resolvedRequestManager;
|
|
12
|
+
#requestManagerFactory;
|
|
13
13
|
/**
|
|
14
14
|
* The latest expected request-processing time hinted via {@link setExpectedRequestProcessingTimeSecs}.
|
|
15
15
|
* Remembered so it can be applied to the writable manager once it is lazily resolved.
|
|
16
16
|
*/
|
|
17
|
-
expectedRequestProcessingSecs;
|
|
17
|
+
#expectedRequestProcessingSecs;
|
|
18
18
|
/**
|
|
19
19
|
* @param requestLoader The read-only loader to read requests from first.
|
|
20
20
|
* @param requestManager The writable manager to transfer requests into and enqueue new ones. May be passed as a
|
|
@@ -22,24 +22,24 @@ export class RequestManagerTandem {
|
|
|
22
22
|
* (e.g. a lazily-opened default {@link RequestQueue}).
|
|
23
23
|
*/
|
|
24
24
|
constructor(requestLoader, requestManager) {
|
|
25
|
-
this
|
|
26
|
-
this
|
|
27
|
-
this
|
|
25
|
+
this.#log = serviceLocator.getLogger().child({ prefix: 'RequestManagerTandem' });
|
|
26
|
+
this.#requestLoader = requestLoader;
|
|
27
|
+
this.#requestManagerFactory = typeof requestManager === 'function' ? requestManager : () => requestManager;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Resolves the writable request manager, opening it lazily (via the factory) on first use and memoizing the result.
|
|
31
31
|
* @private
|
|
32
32
|
*/
|
|
33
33
|
async getRequestManager() {
|
|
34
|
-
if (this
|
|
35
|
-
this
|
|
36
|
-
this
|
|
34
|
+
if (this.#resolvedRequestManager === undefined) {
|
|
35
|
+
this.#requestManagerPromise ??= Promise.resolve(this.#requestManagerFactory());
|
|
36
|
+
this.#resolvedRequestManager = await this.#requestManagerPromise;
|
|
37
37
|
// Apply any hint received before the manager was resolved.
|
|
38
|
-
if (this
|
|
39
|
-
await this
|
|
38
|
+
if (this.#expectedRequestProcessingSecs !== undefined) {
|
|
39
|
+
await this.#resolvedRequestManager.setExpectedRequestProcessingTimeSecs?.(this.#expectedRequestProcessingSecs);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
return this
|
|
42
|
+
return this.#resolvedRequestManager;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Transfers a single request from the read-only loader to the writable manager.
|
|
@@ -50,7 +50,7 @@ export class RequestManagerTandem {
|
|
|
50
50
|
* @private
|
|
51
51
|
*/
|
|
52
52
|
async transferNextRequestToQueue() {
|
|
53
|
-
const request = await this
|
|
53
|
+
const request = await this.#requestLoader.fetchNextRequest();
|
|
54
54
|
if (request === null) {
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
@@ -60,12 +60,12 @@ export class RequestManagerTandem {
|
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
this
|
|
63
|
+
this.#log.exception(error, 'Adding request from the RequestLoader to the RequestManager failed, the request has been dropped.', { url: request.url, uniqueKey: request.uniqueKey });
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
66
|
finally {
|
|
67
67
|
// Mark it as handled so that the request doesn't get stuck in the `inProgress` state in the loader.
|
|
68
|
-
await this
|
|
68
|
+
await this.#requestLoader.markRequestAsHandled(request);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
@@ -76,8 +76,8 @@ export class RequestManagerTandem {
|
|
|
76
76
|
async fetchNextRequest() {
|
|
77
77
|
// First, try to transfer a request from the requestList
|
|
78
78
|
const [listEmpty, listFinished] = await Promise.all([
|
|
79
|
-
this
|
|
80
|
-
this
|
|
79
|
+
this.#requestLoader.isEmpty(),
|
|
80
|
+
this.#requestLoader.isFinished(),
|
|
81
81
|
]);
|
|
82
82
|
if (!listEmpty && !listFinished) {
|
|
83
83
|
// If the transfer failed, the request was dropped; don't fetch from the manager this round (matching
|
|
@@ -94,7 +94,7 @@ export class RequestManagerTandem {
|
|
|
94
94
|
*/
|
|
95
95
|
async isFinished() {
|
|
96
96
|
const requestManager = await this.getRequestManager();
|
|
97
|
-
const storagesFinished = await Promise.all([this
|
|
97
|
+
const storagesFinished = await Promise.all([this.#requestLoader.isFinished(), requestManager.isFinished()]);
|
|
98
98
|
return storagesFinished.every(Boolean);
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
@@ -102,7 +102,7 @@ export class RequestManagerTandem {
|
|
|
102
102
|
*/
|
|
103
103
|
async isEmpty() {
|
|
104
104
|
const requestManager = await this.getRequestManager();
|
|
105
|
-
const storagesEmpty = await Promise.all([this
|
|
105
|
+
const storagesEmpty = await Promise.all([this.#requestLoader.isEmpty(), requestManager.isEmpty()]);
|
|
106
106
|
return storagesEmpty.every(Boolean);
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
@@ -120,7 +120,7 @@ export class RequestManagerTandem {
|
|
|
120
120
|
const [managerTotal, loaderTotal] = await Promise.all([
|
|
121
121
|
requestManager.getTotalCount(),
|
|
122
122
|
// count only pending to avoid double counting, requests marked as "handled" have been moved to requestManager
|
|
123
|
-
this
|
|
123
|
+
this.#requestLoader.getPendingCount(),
|
|
124
124
|
]);
|
|
125
125
|
return managerTotal + loaderTotal;
|
|
126
126
|
}
|
|
@@ -131,7 +131,7 @@ export class RequestManagerTandem {
|
|
|
131
131
|
const requestManager = await this.getRequestManager();
|
|
132
132
|
const [managerPending, loaderPending] = await Promise.all([
|
|
133
133
|
requestManager.getPendingCount(),
|
|
134
|
-
this
|
|
134
|
+
this.#requestLoader.getPendingCount(),
|
|
135
135
|
]);
|
|
136
136
|
return managerPending + loaderPending;
|
|
137
137
|
}
|
|
@@ -175,7 +175,7 @@ export class RequestManagerTandem {
|
|
|
175
175
|
* @inheritdoc
|
|
176
176
|
*/
|
|
177
177
|
async persistState() {
|
|
178
|
-
await this
|
|
178
|
+
await this.#requestLoader.persistState?.();
|
|
179
179
|
}
|
|
180
180
|
/**
|
|
181
181
|
* Purges the writable request manager so the tandem can be reused (e.g. across repeated `crawler.run()` calls).
|
|
@@ -191,7 +191,7 @@ export class RequestManagerTandem {
|
|
|
191
191
|
* @inheritdoc
|
|
192
192
|
*/
|
|
193
193
|
async setExpectedRequestProcessingTimeSecs(secs) {
|
|
194
|
-
this
|
|
195
|
-
await this
|
|
194
|
+
this.#expectedRequestProcessingSecs = secs;
|
|
195
|
+
await this.#resolvedRequestManager?.setExpectedRequestProcessingTimeSecs?.(secs);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -44,29 +44,13 @@ import type { StorageOpenOptions } from './utils.js';
|
|
|
44
44
|
* @category Sources
|
|
45
45
|
*/
|
|
46
46
|
export declare class RequestQueue implements IStorage, IRequestManager {
|
|
47
|
+
#private;
|
|
47
48
|
readonly id: string;
|
|
48
49
|
readonly name?: string;
|
|
49
50
|
readonly backend: RequestQueueBackend;
|
|
50
|
-
private proxyConfiguration?;
|
|
51
51
|
readonly log: CrawleeLogger;
|
|
52
52
|
private requestCache;
|
|
53
|
-
/**
|
|
54
|
-
* Remembers the `requestId` of every request already submitted to the client — including background
|
|
55
|
-
* batches that `requestCache` skips — so overlapping URL sets aren't re-submitted.
|
|
56
|
-
* See {@link RequestDeduplicationCache} for why this is a separate, cheaper cache.
|
|
57
|
-
*/
|
|
58
|
-
private requestSeenCache;
|
|
59
|
-
private queuePausedForMigration;
|
|
60
53
|
private inProgressRequestBatchCount;
|
|
61
|
-
/**
|
|
62
|
-
* The largest expected request-processing time (in seconds) seen so far via
|
|
63
|
-
* {@link setExpectedRequestProcessingTimeSecs}. Used to ensure that value is only ever raised, never
|
|
64
|
-
* lowered, before being forwarded to the storage backend.
|
|
65
|
-
*/
|
|
66
|
-
private expectedRequestProcessingSecs;
|
|
67
|
-
private httpClient?;
|
|
68
|
-
private readonly events;
|
|
69
|
-
private readonly statsTracker;
|
|
70
54
|
/**
|
|
71
55
|
* Backend-independent usage counters tracked for this request queue (write operations and
|
|
72
56
|
* queue-head reads issued to the underlying storage backend). Counted per backend call.
|
|
@@ -276,7 +260,7 @@ export declare class RequestQueue implements IStorage, IRequestManager {
|
|
|
276
260
|
/**
|
|
277
261
|
* @internal wraps public utility for mocking purposes
|
|
278
262
|
*/
|
|
279
|
-
private
|
|
263
|
+
private downloadListOfUrls;
|
|
280
264
|
/**
|
|
281
265
|
* Opens a request queue and returns a promise resolving to an instance
|
|
282
266
|
* of the {@link RequestQueue} class.
|