@crawlee/core 4.0.0-beta.12 → 4.0.0-beta.121
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 +17 -13
- package/autoscaling/autoscaled_pool.d.ts +67 -172
- package/autoscaling/autoscaled_pool.js +165 -320
- package/autoscaling/client_load_signal.d.ts +55 -0
- package/autoscaling/client_load_signal.js +73 -0
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +351 -0
- package/autoscaling/cpu_load_signal.d.ts +43 -0
- package/autoscaling/cpu_load_signal.js +47 -0
- package/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/autoscaling/event_loop_load_signal.js +60 -0
- package/autoscaling/index.d.ts +6 -1
- package/autoscaling/index.js +6 -1
- package/autoscaling/load_signal.d.ts +99 -0
- package/autoscaling/load_signal.js +104 -0
- package/autoscaling/memory_load_signal.d.ts +47 -0
- package/autoscaling/memory_load_signal.js +106 -0
- package/autoscaling/snapshotter.d.ts +58 -163
- package/autoscaling/snapshotter.js +45 -263
- package/autoscaling/system_status.d.ts +62 -84
- package/autoscaling/system_status.js +92 -122
- package/autoscaling/weighted_avg.d.ts +5 -0
- package/autoscaling/weighted_avg.js +14 -0
- package/byte_utils.d.ts +17 -0
- package/byte_utils.js +42 -0
- package/configuration.d.ts +96 -223
- package/configuration.js +170 -222
- package/cookie_utils.d.ts +4 -3
- package/cookie_utils.js +22 -13
- package/crawlers/context_pipeline.d.ts +10 -1
- package/crawlers/context_pipeline.js +31 -8
- package/crawlers/crawler_commons.d.ts +90 -83
- package/crawlers/crawler_commons.js +1 -116
- package/crawlers/error_snapshotter.d.ts +2 -5
- package/crawlers/error_snapshotter.js +7 -8
- package/crawlers/error_tracker.d.ts +0 -1
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -3
- package/crawlers/index.js +0 -3
- package/crawlers/internals/types.d.ts +0 -1
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +143 -59
- package/crawlers/statistics.js +243 -153
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +59 -68
- package/enqueue_links/enqueue_links.js +57 -62
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +40 -27
- package/enqueue_links/shared.js +90 -68
- package/errors.d.ts +72 -4
- package/errors.js +89 -5
- package/events/event_manager.d.ts +35 -9
- package/events/event_manager.js +10 -12
- package/events/index.d.ts +0 -1
- package/events/index.js +0 -1
- package/events/local_event_manager.d.ts +15 -3
- package/events/local_event_manager.js +39 -13
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +7 -4
- package/index.js +6 -3
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +82 -3
- package/log.js +106 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +38 -0
- package/memory-storage/memory-storage.js +130 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +40 -0
- package/memory-storage/resource-clients/dataset.js +114 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +204 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +422 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned_or_injected.d.ts +58 -0
- package/owned_or_injected.js +98 -0
- package/package.json +13 -12
- package/proxy_configuration.d.ts +24 -132
- package/proxy_configuration.js +24 -143
- package/recoverable_state.d.ts +140 -0
- package/recoverable_state.js +212 -0
- package/request.d.ts +86 -17
- package/request.js +120 -41
- package/router.d.ts +193 -21
- package/router.js +188 -43
- package/serialization.d.ts +0 -1
- package/serialization.js +9 -11
- package/service_locator.d.ts +165 -0
- package/service_locator.js +253 -0
- package/session_pool/consts.d.ts +1 -2
- package/session_pool/consts.js +1 -2
- package/session_pool/errors.d.ts +0 -1
- package/session_pool/errors.js +0 -1
- package/session_pool/fingerprint.d.ts +9 -0
- package/session_pool/fingerprint.js +30 -0
- package/session_pool/index.d.ts +0 -2
- package/session_pool/index.js +0 -2
- package/session_pool/session.d.ts +35 -89
- package/session_pool/session.js +82 -142
- package/session_pool/session_pool.d.ts +69 -90
- package/session_pool/session_pool.js +151 -150
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +114 -54
- package/storages/dataset.js +285 -144
- package/storages/index.d.ts +10 -8
- package/storages/index.js +8 -8
- package/storages/key_value_store.d.ts +185 -42
- package/storages/key_value_store.js +424 -151
- package/storages/key_value_store_codec.d.ts +32 -0
- package/storages/key_value_store_codec.js +113 -0
- package/storages/request_dedup_cache.d.ts +22 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +52 -116
- package/storages/request_list.js +159 -133
- package/storages/request_loader.d.ts +101 -0
- package/storages/request_loader.js +1 -0
- package/storages/request_manager.d.ts +33 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +97 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +757 -216
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +37 -88
- package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +137 -143
- package/storages/storage_instance_manager.d.ts +87 -0
- package/storages/storage_instance_manager.js +256 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- package/storages/throttling_request_manager.d.ts +216 -0
- package/storages/throttling_request_manager.js +453 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +58 -11
- package/storages/utils.js +64 -13
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +8 -1
- package/validators.js +10 -3
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js.map +0 -1
- package/http_clients/base-http-client.d.ts +0 -134
- package/http_clients/base-http-client.d.ts.map +0 -1
- package/http_clients/base-http-client.js +0 -33
- package/http_clients/base-http-client.js.map +0 -1
- package/http_clients/form-data-like.d.ts +0 -67
- package/http_clients/form-data-like.d.ts.map +0 -1
- package/http_clients/form-data-like.js +0 -5
- package/http_clients/form-data-like.js.map +0 -1
- package/http_clients/got-scraping-http-client.d.ts +0 -15
- package/http_clients/got-scraping-http-client.d.ts.map +0 -1
- package/http_clients/got-scraping-http-client.js +0 -69
- package/http_clients/got-scraping-http-client.js.map +0 -1
- package/http_clients/index.d.ts +0 -3
- package/http_clients/index.d.ts.map +0 -1
- package/http_clients/index.js +0 -3
- package/http_clients/index.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts.map +0 -1
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_provider.d.ts +0 -308
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -555
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -438
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -58
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -105
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
package/storages/request_list.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { downloadListOfUrls } from '@crawlee/utils';
|
|
2
2
|
import ow, { ArgumentError } from 'ow';
|
|
3
|
-
import {
|
|
4
|
-
import { log } from '../log.js';
|
|
3
|
+
import { EventType } from '../events/event_manager.js';
|
|
5
4
|
import { Request } from '../request.js';
|
|
6
5
|
import { createDeserialize, serializeArray } from '../serialization.js';
|
|
6
|
+
import { serviceLocator } from '../service_locator.js';
|
|
7
7
|
import { KeyValueStore } from './key_value_store.js';
|
|
8
8
|
import { purgeDefaultStorages } from './utils.js';
|
|
9
9
|
/** @internal */
|
|
@@ -33,8 +33,8 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
|
|
|
33
33
|
* > In practical terms, such a combination can be useful when there is a large number of initial URLs,
|
|
34
34
|
* > but more URLs would be added dynamically by the crawler.
|
|
35
35
|
*
|
|
36
|
-
* `RequestList` has an internal state where it stores information about which requests were already handled
|
|
37
|
-
* which are in progress
|
|
36
|
+
* `RequestList` has an internal state where it stores information about which requests were already handled
|
|
37
|
+
* and which are in progress. The state may be automatically persisted to the default
|
|
38
38
|
* {@link KeyValueStore} by setting the `persistStateKey` option so that if the Node.js process is restarted,
|
|
39
39
|
* the crawling can continue where it left off. The automated persisting is launched upon receiving the `persistState`
|
|
40
40
|
* event that is periodically emitted by {@link EventManager}.
|
|
@@ -73,7 +73,7 @@ const CONTENT_TYPE_BINARY = 'application/octet-stream';
|
|
|
73
73
|
* @category Sources
|
|
74
74
|
*/
|
|
75
75
|
export class RequestList {
|
|
76
|
-
log =
|
|
76
|
+
#log = serviceLocator.getLogger().child({ prefix: 'RequestList' });
|
|
77
77
|
/**
|
|
78
78
|
* Array of all requests from all sources, in the order as they appeared in sources.
|
|
79
79
|
* All requests in the array have distinct uniqueKey!
|
|
@@ -81,19 +81,20 @@ export class RequestList {
|
|
|
81
81
|
*/
|
|
82
82
|
requests = [];
|
|
83
83
|
/** Index to the next item in requests array to fetch. All previous requests are either handled or in progress. */
|
|
84
|
-
nextIndex = 0;
|
|
84
|
+
#nextIndex = 0;
|
|
85
85
|
/** Dictionary, key is Request.uniqueKey, value is corresponding index in the requests array. */
|
|
86
|
-
uniqueKeyToIndex = {};
|
|
86
|
+
#uniqueKeyToIndex = {};
|
|
87
87
|
/**
|
|
88
88
|
* Set of `uniqueKey`s of requests that were returned by fetchNextRequest().
|
|
89
89
|
* @internal
|
|
90
90
|
*/
|
|
91
91
|
inProgress = new Set();
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
93
|
+
* `uniqueKey`s of requests that were in progress when the state was last persisted and thus need to be
|
|
94
|
+
* re-crawled after a restart. They are served before advancing through the rest of the sources.
|
|
94
95
|
* @internal
|
|
95
96
|
*/
|
|
96
|
-
|
|
97
|
+
#requestsToRetry = [];
|
|
97
98
|
/**
|
|
98
99
|
* Starts as true because until we handle the first request, the list is effectively persisted by doing nothing.
|
|
99
100
|
* @internal
|
|
@@ -104,24 +105,25 @@ export class RequestList {
|
|
|
104
105
|
* @internal
|
|
105
106
|
*/
|
|
106
107
|
areRequestsPersisted = false;
|
|
107
|
-
isLoading = false;
|
|
108
|
-
isInitialized = false;
|
|
109
|
-
persistStateKey;
|
|
110
|
-
persistRequestsKey;
|
|
111
|
-
initialState;
|
|
112
|
-
store;
|
|
113
|
-
keepDuplicateUrls;
|
|
108
|
+
#isLoading = false;
|
|
109
|
+
#isInitialized = false;
|
|
110
|
+
#persistStateKey;
|
|
111
|
+
#persistRequestsKey;
|
|
112
|
+
#initialState;
|
|
113
|
+
#store;
|
|
114
|
+
#keepDuplicateUrls;
|
|
115
|
+
// kept as TS-private: request_list tests read this field directly
|
|
114
116
|
sources;
|
|
115
|
-
sourcesFunction;
|
|
116
|
-
proxyConfiguration;
|
|
117
|
-
|
|
117
|
+
#sourcesFunction;
|
|
118
|
+
#proxyConfiguration;
|
|
119
|
+
#httpClient;
|
|
118
120
|
/**
|
|
119
121
|
* To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
|
|
120
122
|
* @param options All `RequestList` configuration options
|
|
121
123
|
* @internal
|
|
122
124
|
*/
|
|
123
125
|
constructor(options = {}) {
|
|
124
|
-
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false,
|
|
126
|
+
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, httpClient, } = options;
|
|
125
127
|
if (!(sources || sourcesFunction)) {
|
|
126
128
|
throw new ArgumentError('At least one of "sources" or "sourcesFunction" must be provided.', this.constructor);
|
|
127
129
|
}
|
|
@@ -137,44 +139,46 @@ export class RequestList {
|
|
|
137
139
|
}),
|
|
138
140
|
keepDuplicateUrls: ow.optional.boolean,
|
|
139
141
|
proxyConfiguration: ow.optional.object,
|
|
142
|
+
httpClient: ow.optional.object,
|
|
140
143
|
}));
|
|
141
|
-
this
|
|
142
|
-
this
|
|
143
|
-
this
|
|
144
|
-
this
|
|
144
|
+
this.#persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
|
|
145
|
+
this.#persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
|
|
146
|
+
this.#initialState = state;
|
|
147
|
+
this.#httpClient = httpClient;
|
|
145
148
|
// If this option is set then all requests will get a pre-generated unique ID and duplicate URLs will be kept in the list.
|
|
146
|
-
this
|
|
149
|
+
this.#keepDuplicateUrls = keepDuplicateUrls;
|
|
147
150
|
// Will be empty after initialization to save memory.
|
|
148
151
|
this.sources = sources ? [...sources] : [];
|
|
149
|
-
this
|
|
152
|
+
this.#sourcesFunction = sourcesFunction;
|
|
150
153
|
// The proxy configuration used for `requestsFromUrl` requests.
|
|
151
|
-
this
|
|
154
|
+
this.#proxyConfiguration = proxyConfiguration;
|
|
155
|
+
this.persistState = this.persistState.bind(this);
|
|
152
156
|
}
|
|
153
157
|
/**
|
|
154
158
|
* Loads all remote sources of URLs and potentially starts periodic state persistence.
|
|
155
159
|
* This function must be called before you can start using the instance in a meaningful way.
|
|
156
160
|
*/
|
|
157
161
|
async initialize() {
|
|
158
|
-
if (this
|
|
162
|
+
if (this.#isLoading) {
|
|
159
163
|
throw new Error('RequestList sources are already loading or were loaded.');
|
|
160
164
|
}
|
|
161
|
-
this
|
|
165
|
+
this.#isLoading = true;
|
|
162
166
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
163
|
-
const [state, persistedRequests] = await this.
|
|
167
|
+
const [state, persistedRequests] = await this.loadStateAndPersistedRequests();
|
|
164
168
|
// Add persisted requests / new sources in a memory efficient way because with very
|
|
165
169
|
// large lists, we were running out of memory.
|
|
166
170
|
if (persistedRequests) {
|
|
167
|
-
await this.
|
|
171
|
+
await this.addPersistedRequests(persistedRequests);
|
|
168
172
|
}
|
|
169
173
|
else {
|
|
170
|
-
await this.
|
|
174
|
+
await this.addRequestsFromSources();
|
|
171
175
|
}
|
|
172
|
-
this.
|
|
173
|
-
this
|
|
174
|
-
if (this
|
|
175
|
-
await this.
|
|
176
|
-
if (this
|
|
177
|
-
|
|
176
|
+
this.restoreState(state);
|
|
177
|
+
this.#isInitialized = true;
|
|
178
|
+
if (this.#persistRequestsKey && !this.areRequestsPersisted)
|
|
179
|
+
await this.persistRequests();
|
|
180
|
+
if (this.#persistStateKey) {
|
|
181
|
+
serviceLocator.getEventManager().on(EventType.PERSIST_STATE, this.persistState);
|
|
178
182
|
}
|
|
179
183
|
return this;
|
|
180
184
|
}
|
|
@@ -183,17 +187,18 @@ export class RequestList {
|
|
|
183
187
|
* This needs to be done in a memory efficient way. We should update the input
|
|
184
188
|
* to a Stream once apify-client supports streams.
|
|
185
189
|
*/
|
|
186
|
-
async
|
|
190
|
+
async addPersistedRequests(persistedRequests) {
|
|
187
191
|
// We don't need the sources so we purge them to
|
|
188
192
|
// prevent them from hanging in memory.
|
|
189
193
|
for (let i = 0; i < this.sources.length; i++) {
|
|
194
|
+
// oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
|
|
190
195
|
delete this.sources[i];
|
|
191
196
|
}
|
|
192
197
|
this.sources = [];
|
|
193
198
|
this.areRequestsPersisted = true;
|
|
194
199
|
const requestStream = createDeserialize(persistedRequests);
|
|
195
200
|
for await (const request of requestStream) {
|
|
196
|
-
this.
|
|
201
|
+
this.addRequest(request);
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
/**
|
|
@@ -202,32 +207,36 @@ export class RequestList {
|
|
|
202
207
|
* We need to avoid keeping both sources and requests in memory
|
|
203
208
|
* to reduce memory footprint with very large sources.
|
|
204
209
|
*/
|
|
205
|
-
async
|
|
210
|
+
async addRequestsFromSources() {
|
|
206
211
|
// We'll load all sources in sequence to ensure that they get loaded in the right order.
|
|
207
212
|
const sourcesCount = this.sources.length;
|
|
208
213
|
for (let i = 0; i < sourcesCount; i++) {
|
|
209
214
|
const source = this.sources[i];
|
|
210
215
|
// Using delete here to drop the original object ASAP to free memory
|
|
211
216
|
// .pop would reverse the array and .shift is SLOW.
|
|
217
|
+
// oxlint-disable-next-line typescript/no-array-delete
|
|
212
218
|
delete this.sources[i];
|
|
213
219
|
if (typeof source === 'object' && source.requestsFromUrl) {
|
|
214
|
-
const fetchedRequests = await this.
|
|
215
|
-
await this.
|
|
220
|
+
const fetchedRequests = await this.fetchRequestsFromUrl(source);
|
|
221
|
+
await this.addFetchedRequests(source, fetchedRequests);
|
|
216
222
|
}
|
|
217
223
|
else {
|
|
218
|
-
this.
|
|
224
|
+
this.addRequest(source);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
// Drop the original array full of empty indexes.
|
|
222
228
|
this.sources = [];
|
|
223
|
-
if (this
|
|
229
|
+
if (this.#sourcesFunction) {
|
|
224
230
|
try {
|
|
225
|
-
const sourcesFromFunction = await this
|
|
231
|
+
const sourcesFromFunction = await this.#sourcesFunction();
|
|
226
232
|
const sourcesFromFunctionCount = sourcesFromFunction.length;
|
|
227
233
|
for (let i = 0; i < sourcesFromFunctionCount; i++) {
|
|
228
|
-
const source = sourcesFromFunction
|
|
229
|
-
|
|
234
|
+
const source = sourcesFromFunction[i];
|
|
235
|
+
// oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
|
|
236
|
+
delete sourcesFromFunction[i];
|
|
237
|
+
this.addRequest(source);
|
|
230
238
|
}
|
|
239
|
+
sourcesFromFunction.length = 0;
|
|
231
240
|
}
|
|
232
241
|
catch (e) {
|
|
233
242
|
const err = e;
|
|
@@ -239,19 +248,30 @@ export class RequestList {
|
|
|
239
248
|
* @inheritDoc
|
|
240
249
|
*/
|
|
241
250
|
async persistState() {
|
|
242
|
-
if (!this
|
|
251
|
+
if (!this.#persistStateKey) {
|
|
243
252
|
throw new Error('Cannot persist state. options.persistStateKey is not set.');
|
|
244
253
|
}
|
|
245
254
|
if (this.isStatePersisted)
|
|
246
255
|
return;
|
|
247
256
|
try {
|
|
248
|
-
this
|
|
249
|
-
await this
|
|
257
|
+
this.#store ??= await KeyValueStore.open();
|
|
258
|
+
await this.#store.setValue(this.#persistStateKey, this.getState());
|
|
250
259
|
this.isStatePersisted = true;
|
|
251
260
|
}
|
|
252
261
|
catch (e) {
|
|
253
262
|
const err = e;
|
|
254
|
-
this
|
|
263
|
+
this.#log.exception(err, 'Attempted to persist state, but failed.');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Removes the `PERSIST_STATE` event listener registered during initialization and persists
|
|
268
|
+
* the current state one last time. Call this when you are done with the `RequestList` to avoid
|
|
269
|
+
* leaking the listener (and the requests it retains) on the shared event manager.
|
|
270
|
+
*/
|
|
271
|
+
async teardown() {
|
|
272
|
+
serviceLocator.getEventManager().off(EventType.PERSIST_STATE, this.persistState);
|
|
273
|
+
if (this.#persistStateKey) {
|
|
274
|
+
await this.persistState();
|
|
255
275
|
}
|
|
256
276
|
}
|
|
257
277
|
/**
|
|
@@ -259,16 +279,16 @@ export class RequestList {
|
|
|
259
279
|
* are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
|
|
260
280
|
* but there's no reason to persist it again afterwards, because RequestList is immutable.
|
|
261
281
|
*/
|
|
262
|
-
async
|
|
282
|
+
async persistRequests() {
|
|
263
283
|
const serializedRequests = await serializeArray(this.requests);
|
|
264
|
-
this
|
|
265
|
-
await this
|
|
284
|
+
this.#store ??= await KeyValueStore.open();
|
|
285
|
+
await this.#store.setValue(this.#persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
|
|
266
286
|
this.areRequestsPersisted = true;
|
|
267
287
|
}
|
|
268
288
|
/**
|
|
269
289
|
* Restores RequestList state from a state object.
|
|
270
290
|
*/
|
|
271
|
-
|
|
291
|
+
restoreState(state) {
|
|
272
292
|
// If there's no state it means we've not persisted any (yet).
|
|
273
293
|
if (!state)
|
|
274
294
|
return;
|
|
@@ -285,7 +305,7 @@ export class RequestList {
|
|
|
285
305
|
}
|
|
286
306
|
const deleteFromInProgress = [];
|
|
287
307
|
state.inProgress.forEach((uniqueKey) => {
|
|
288
|
-
const index = this
|
|
308
|
+
const index = this.#uniqueKeyToIndex[uniqueKey];
|
|
289
309
|
if (typeof index !== 'number') {
|
|
290
310
|
throw new Error('The state object is not consistent with RequestList. Unknown uniqueKey is present in the state.');
|
|
291
311
|
}
|
|
@@ -293,7 +313,7 @@ export class RequestList {
|
|
|
293
313
|
deleteFromInProgress.push(uniqueKey);
|
|
294
314
|
}
|
|
295
315
|
});
|
|
296
|
-
this
|
|
316
|
+
this.#nextIndex = state.nextIndex;
|
|
297
317
|
this.inProgress = new Set(state.inProgress);
|
|
298
318
|
// WORKAROUND:
|
|
299
319
|
// It happened to some users that state object contained something like:
|
|
@@ -309,36 +329,36 @@ export class RequestList {
|
|
|
309
329
|
// As a workaround, we just remove all inProgress requests whose index >= nextIndex,
|
|
310
330
|
// since they will be crawled again.
|
|
311
331
|
if (deleteFromInProgress.length) {
|
|
312
|
-
this
|
|
332
|
+
this.#log.warning("RequestList's in-progress field is not consistent, skipping invalid in-progress entries", {
|
|
313
333
|
deleteFromInProgress,
|
|
314
334
|
});
|
|
315
335
|
for (const uniqueKey of deleteFromInProgress) {
|
|
316
336
|
this.inProgress.delete(uniqueKey);
|
|
317
337
|
}
|
|
318
338
|
}
|
|
319
|
-
// All in-progress requests need to be re-crawled
|
|
320
|
-
this
|
|
339
|
+
// All in-progress requests were interrupted and need to be re-crawled.
|
|
340
|
+
this.#requestsToRetry = [...this.inProgress];
|
|
321
341
|
}
|
|
322
342
|
/**
|
|
323
343
|
* Attempts to load state and requests using the `RequestList` configuration
|
|
324
344
|
* and returns a tuple of [state, requests] where each may be null if not loaded.
|
|
325
345
|
*/
|
|
326
|
-
async
|
|
346
|
+
async loadStateAndPersistedRequests() {
|
|
327
347
|
let state;
|
|
328
348
|
let persistedRequests;
|
|
329
|
-
if (this
|
|
330
|
-
state = this
|
|
331
|
-
this
|
|
349
|
+
if (this.#initialState) {
|
|
350
|
+
state = this.#initialState;
|
|
351
|
+
this.#log.debug('Loaded state from options.state argument.');
|
|
332
352
|
}
|
|
333
|
-
else if (this
|
|
334
|
-
state = await this.
|
|
353
|
+
else if (this.#persistStateKey) {
|
|
354
|
+
state = await this.getPersistedState(this.#persistStateKey);
|
|
335
355
|
if (state)
|
|
336
|
-
this
|
|
356
|
+
this.#log.debug('Loaded state from key value store using the persistStateKey.');
|
|
337
357
|
}
|
|
338
|
-
if (this
|
|
339
|
-
persistedRequests = await this.
|
|
358
|
+
if (this.#persistRequestsKey) {
|
|
359
|
+
persistedRequests = await this.getPersistedState(this.#persistRequestsKey);
|
|
340
360
|
if (persistedRequests)
|
|
341
|
-
this
|
|
361
|
+
this.#log.debug('Loaded requests from key value store using the persistRequestsKey.');
|
|
342
362
|
}
|
|
343
363
|
return [state, persistedRequests];
|
|
344
364
|
}
|
|
@@ -347,10 +367,10 @@ export class RequestList {
|
|
|
347
367
|
* Note that the object's fields can change in future releases.
|
|
348
368
|
*/
|
|
349
369
|
getState() {
|
|
350
|
-
this.
|
|
370
|
+
this.ensureIsInitialized();
|
|
351
371
|
return {
|
|
352
|
-
nextIndex: this
|
|
353
|
-
nextUniqueKey: this
|
|
372
|
+
nextIndex: this.#nextIndex,
|
|
373
|
+
nextUniqueKey: this.#nextIndex < this.requests.length ? this.requests[this.#nextIndex].uniqueKey : null,
|
|
354
374
|
inProgress: [...this.inProgress],
|
|
355
375
|
};
|
|
356
376
|
}
|
|
@@ -358,34 +378,33 @@ export class RequestList {
|
|
|
358
378
|
* @inheritDoc
|
|
359
379
|
*/
|
|
360
380
|
async isEmpty() {
|
|
361
|
-
this.
|
|
362
|
-
return this.
|
|
381
|
+
this.ensureIsInitialized();
|
|
382
|
+
return this.#requestsToRetry.length === 0 && this.#nextIndex >= this.requests.length;
|
|
363
383
|
}
|
|
364
384
|
/**
|
|
365
385
|
* @inheritDoc
|
|
366
386
|
*/
|
|
367
387
|
async isFinished() {
|
|
368
|
-
this.
|
|
369
|
-
return this.inProgress.size === 0 && this
|
|
388
|
+
this.ensureIsInitialized();
|
|
389
|
+
return this.inProgress.size === 0 && this.#nextIndex >= this.requests.length;
|
|
370
390
|
}
|
|
371
391
|
/**
|
|
372
392
|
* @inheritDoc
|
|
373
393
|
*/
|
|
374
394
|
async fetchNextRequest() {
|
|
375
|
-
this.
|
|
376
|
-
// First
|
|
377
|
-
const uniqueKey = this.
|
|
395
|
+
this.ensureIsInitialized();
|
|
396
|
+
// First re-serve any requests that were interrupted before the last state persist.
|
|
397
|
+
const uniqueKey = this.#requestsToRetry.shift();
|
|
378
398
|
if (uniqueKey) {
|
|
379
|
-
this
|
|
380
|
-
const index = this.uniqueKeyToIndex[uniqueKey];
|
|
399
|
+
const index = this.#uniqueKeyToIndex[uniqueKey];
|
|
381
400
|
return this.ensureRequest(this.requests[index], index);
|
|
382
401
|
}
|
|
383
402
|
// Otherwise return next request.
|
|
384
|
-
if (this
|
|
385
|
-
const index = this
|
|
403
|
+
if (this.#nextIndex < this.requests.length) {
|
|
404
|
+
const index = this.#nextIndex;
|
|
386
405
|
const request = this.requests[index];
|
|
387
406
|
this.inProgress.add(request.uniqueKey);
|
|
388
|
-
this
|
|
407
|
+
this.#nextIndex++;
|
|
389
408
|
this.isStatePersisted = false;
|
|
390
409
|
return this.ensureRequest(request, index);
|
|
391
410
|
}
|
|
@@ -412,34 +431,24 @@ export class RequestList {
|
|
|
412
431
|
/**
|
|
413
432
|
* @inheritDoc
|
|
414
433
|
*/
|
|
415
|
-
async
|
|
434
|
+
async markRequestAsHandled(request) {
|
|
416
435
|
const { uniqueKey } = request;
|
|
417
|
-
this.
|
|
418
|
-
this.
|
|
419
|
-
this.
|
|
436
|
+
this.ensureUniqueKeyValid(uniqueKey);
|
|
437
|
+
this.ensureInProgress(uniqueKey);
|
|
438
|
+
this.ensureIsInitialized();
|
|
420
439
|
this.inProgress.delete(uniqueKey);
|
|
421
440
|
this.isStatePersisted = false;
|
|
422
441
|
}
|
|
423
|
-
/**
|
|
424
|
-
* @inheritDoc
|
|
425
|
-
*/
|
|
426
|
-
async reclaimRequest(request) {
|
|
427
|
-
const { uniqueKey } = request;
|
|
428
|
-
this._ensureUniqueKeyValid(uniqueKey);
|
|
429
|
-
this._ensureInProgressAndNotReclaimed(uniqueKey);
|
|
430
|
-
this._ensureIsInitialized();
|
|
431
|
-
this.reclaimed.add(uniqueKey);
|
|
432
|
-
}
|
|
433
442
|
/**
|
|
434
443
|
* Adds all fetched requests from a URL from a remote resource.
|
|
435
444
|
*/
|
|
436
|
-
async
|
|
445
|
+
async addFetchedRequests(source, fetchedRequests) {
|
|
437
446
|
const { requestsFromUrl, regex } = source;
|
|
438
447
|
const originalLength = this.requests.length;
|
|
439
|
-
fetchedRequests.forEach((request) => this.
|
|
448
|
+
fetchedRequests.forEach((request) => this.addRequest(request));
|
|
440
449
|
const fetchedCount = fetchedRequests.length;
|
|
441
450
|
const importedCount = this.requests.length - originalLength;
|
|
442
|
-
this
|
|
451
|
+
this.#log.info('Fetched and loaded Requests from a remote resource.', {
|
|
443
452
|
requestsFromUrl,
|
|
444
453
|
regex,
|
|
445
454
|
fetchedCount,
|
|
@@ -448,23 +457,23 @@ export class RequestList {
|
|
|
448
457
|
sample: JSON.stringify(fetchedRequests.slice(0, 5)),
|
|
449
458
|
});
|
|
450
459
|
}
|
|
451
|
-
async
|
|
452
|
-
this
|
|
453
|
-
const state = await this
|
|
460
|
+
async getPersistedState(key) {
|
|
461
|
+
this.#store ??= await KeyValueStore.open();
|
|
462
|
+
const state = await this.#store.getValue(key);
|
|
454
463
|
return state;
|
|
455
464
|
}
|
|
456
465
|
/**
|
|
457
466
|
* Fetches URLs from requestsFromUrl and returns them in format of list of requests
|
|
458
467
|
*/
|
|
459
|
-
async
|
|
468
|
+
async fetchRequestsFromUrl(source) {
|
|
460
469
|
const { requestsFromUrl, regex, ...sharedOpts } = source;
|
|
461
470
|
// Download remote resource and parse URLs.
|
|
462
471
|
let urlsArr;
|
|
463
472
|
try {
|
|
464
|
-
urlsArr = await this.
|
|
473
|
+
urlsArr = await this.downloadListOfUrls({
|
|
465
474
|
url: requestsFromUrl,
|
|
466
475
|
urlRegExp: regex,
|
|
467
|
-
proxyUrl: await this
|
|
476
|
+
proxyUrl: (await this.#proxyConfiguration?.newProxyInfo())?.url,
|
|
468
477
|
});
|
|
469
478
|
}
|
|
470
479
|
catch (err) {
|
|
@@ -472,7 +481,7 @@ export class RequestList {
|
|
|
472
481
|
}
|
|
473
482
|
// Skip if resource contained no URLs.
|
|
474
483
|
if (!urlsArr.length) {
|
|
475
|
-
this
|
|
484
|
+
this.#log.warning('The fetched list contains no valid URLs.', { requestsFromUrl, regex });
|
|
476
485
|
return [];
|
|
477
486
|
}
|
|
478
487
|
return urlsArr.map((url) => ({ url, ...sharedOpts }));
|
|
@@ -482,7 +491,7 @@ export class RequestList {
|
|
|
482
491
|
* If the `source` parameter is a string or plain object and not an instance
|
|
483
492
|
* of a `Request`, then the function creates a `Request` instance.
|
|
484
493
|
*/
|
|
485
|
-
|
|
494
|
+
addRequest(source) {
|
|
486
495
|
let request;
|
|
487
496
|
const type = typeof source;
|
|
488
497
|
if (type === 'string') {
|
|
@@ -500,61 +509,76 @@ export class RequestList {
|
|
|
500
509
|
const hasUniqueKey = Reflect.has(Object(source), 'uniqueKey');
|
|
501
510
|
request.uniqueKey ??= Request.computeUniqueKey(request);
|
|
502
511
|
// Add index to uniqueKey if duplicates are to be kept
|
|
503
|
-
if (this
|
|
512
|
+
if (this.#keepDuplicateUrls && !hasUniqueKey) {
|
|
504
513
|
request.uniqueKey += `-${this.requests.length}`;
|
|
505
514
|
}
|
|
506
515
|
const { uniqueKey } = request;
|
|
507
|
-
this.
|
|
516
|
+
this.ensureUniqueKeyValid(uniqueKey);
|
|
508
517
|
// Skip requests with duplicate uniqueKey
|
|
509
|
-
if (!Object.hasOwn(this
|
|
510
|
-
this
|
|
518
|
+
if (!Object.hasOwn(this.#uniqueKeyToIndex, uniqueKey)) {
|
|
519
|
+
this.#uniqueKeyToIndex[uniqueKey] = this.requests.length;
|
|
511
520
|
this.requests.push(request);
|
|
512
521
|
}
|
|
513
|
-
else if (this
|
|
514
|
-
this
|
|
522
|
+
else if (this.#keepDuplicateUrls) {
|
|
523
|
+
this.#log.warning(`Duplicate uniqueKey: ${uniqueKey} found while the keepDuplicateUrls option was set. Check your sources' unique keys.`);
|
|
515
524
|
}
|
|
516
525
|
}
|
|
517
526
|
/**
|
|
518
527
|
* Helper function that validates unique key.
|
|
519
528
|
* Throws an error if uniqueKey is not a non-empty string.
|
|
520
529
|
*/
|
|
521
|
-
|
|
530
|
+
ensureUniqueKeyValid(uniqueKey) {
|
|
522
531
|
if (typeof uniqueKey !== 'string' || !uniqueKey) {
|
|
523
532
|
throw new Error("Request object's uniqueKey must be a non-empty string");
|
|
524
533
|
}
|
|
525
534
|
}
|
|
526
535
|
/**
|
|
527
|
-
* Checks that request is
|
|
536
|
+
* Checks that a request is currently being processed and throws an error if not.
|
|
528
537
|
*/
|
|
529
|
-
|
|
538
|
+
ensureInProgress(uniqueKey) {
|
|
530
539
|
if (!this.inProgress.has(uniqueKey)) {
|
|
531
540
|
throw new Error(`The request is not being processed (uniqueKey: ${uniqueKey})`);
|
|
532
541
|
}
|
|
533
|
-
if (this.reclaimed.has(uniqueKey)) {
|
|
534
|
-
throw new Error(`The request was already reclaimed (uniqueKey: ${uniqueKey})`);
|
|
535
|
-
}
|
|
536
542
|
}
|
|
537
543
|
/**
|
|
538
544
|
* Throws an error if request list wasn't initialized.
|
|
539
545
|
*/
|
|
540
|
-
|
|
541
|
-
if (!this
|
|
546
|
+
ensureIsInitialized() {
|
|
547
|
+
if (!this.#isInitialized) {
|
|
542
548
|
throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
|
|
543
549
|
}
|
|
544
550
|
}
|
|
545
551
|
/**
|
|
546
552
|
* Returns the total number of unique requests present in the `RequestList`.
|
|
547
553
|
*/
|
|
548
|
-
|
|
549
|
-
this.
|
|
554
|
+
async getTotalCount() {
|
|
555
|
+
this.ensureIsInitialized();
|
|
550
556
|
return this.requests.length;
|
|
551
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Returns the number of pending requests in the `RequestList`.
|
|
560
|
+
*/
|
|
561
|
+
async getPendingCount() {
|
|
562
|
+
this.ensureIsInitialized();
|
|
563
|
+
return this.requests.length - (this.#nextIndex - this.inProgress.size);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
567
|
+
* {@link RequestManagerTandem}, allowing requests to be added and reclaimed while still
|
|
568
|
+
* being read from this list first.
|
|
569
|
+
*/
|
|
570
|
+
async toTandem(requestManager) {
|
|
571
|
+
// Import here to avoid circular imports.
|
|
572
|
+
const { RequestManagerTandem } = await import('./request_manager_tandem.js');
|
|
573
|
+
const { RequestQueue } = await import('./request_queue.js');
|
|
574
|
+
return new RequestManagerTandem(this, requestManager ?? (await RequestQueue.open()));
|
|
575
|
+
}
|
|
552
576
|
/**
|
|
553
577
|
* @inheritDoc
|
|
554
578
|
*/
|
|
555
|
-
|
|
556
|
-
this.
|
|
557
|
-
return this
|
|
579
|
+
async getHandledCount() {
|
|
580
|
+
this.ensureIsInitialized();
|
|
581
|
+
return this.#nextIndex - this.inProgress.size;
|
|
558
582
|
}
|
|
559
583
|
/**
|
|
560
584
|
* Opens a request list and returns a promise resolving to an instance
|
|
@@ -632,8 +656,10 @@ export class RequestList {
|
|
|
632
656
|
/**
|
|
633
657
|
* @internal wraps public utility for mocking purposes
|
|
634
658
|
*/
|
|
635
|
-
async
|
|
636
|
-
return downloadListOfUrls(
|
|
659
|
+
async downloadListOfUrls(options) {
|
|
660
|
+
return downloadListOfUrls({
|
|
661
|
+
...options,
|
|
662
|
+
httpClient: this.#httpClient,
|
|
663
|
+
});
|
|
637
664
|
}
|
|
638
665
|
}
|
|
639
|
-
//# sourceMappingURL=request_list.js.map
|