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