@crawlee/core 4.0.0-beta.8 → 4.0.0-beta.81
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 +14 -15
- package/autoscaling/autoscaled_pool.js +37 -36
- package/autoscaling/client_load_signal.d.ts +25 -0
- package/autoscaling/client_load_signal.js +36 -0
- package/autoscaling/cpu_load_signal.d.ts +28 -0
- package/autoscaling/cpu_load_signal.js +24 -0
- package/autoscaling/event_loop_load_signal.d.ts +23 -0
- package/autoscaling/event_loop_load_signal.js +35 -0
- package/autoscaling/index.d.ts +5 -1
- package/autoscaling/index.js +5 -1
- package/autoscaling/load_signal.d.ts +99 -0
- package/autoscaling/load_signal.js +135 -0
- package/autoscaling/memory_load_signal.d.ts +43 -0
- package/autoscaling/memory_load_signal.js +102 -0
- package/autoscaling/snapshotter.d.ts +29 -91
- package/autoscaling/snapshotter.js +65 -204
- package/autoscaling/system_status.d.ts +22 -37
- package/autoscaling/system_status.js +49 -87
- package/configuration.d.ts +88 -228
- package/configuration.js +162 -224
- package/cookie_utils.d.ts +4 -3
- package/cookie_utils.js +20 -13
- package/crawlers/context_pipeline.d.ts +70 -0
- package/crawlers/context_pipeline.js +122 -0
- package/crawlers/crawler_commons.d.ts +20 -30
- package/crawlers/crawler_commons.js +12 -21
- package/crawlers/crawler_utils.d.ts +2 -3
- package/crawlers/crawler_utils.js +1 -2
- package/crawlers/error_snapshotter.d.ts +3 -3
- package/crawlers/error_snapshotter.js +2 -3
- package/crawlers/error_tracker.d.ts +2 -2
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -2
- package/crawlers/index.js +1 -2
- package/crawlers/internals/types.d.ts +7 -0
- package/crawlers/internals/types.js +1 -0
- package/crawlers/statistics.d.ts +28 -23
- package/crawlers/statistics.js +38 -34
- package/enqueue_links/enqueue_links.d.ts +44 -22
- package/enqueue_links/enqueue_links.js +51 -30
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +25 -9
- package/enqueue_links/shared.js +69 -38
- package/errors.d.ts +53 -4
- package/errors.js +70 -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 -5
- package/events/local_event_manager.js +37 -40
- package/index.d.ts +4 -3
- package/index.js +3 -2
- package/log.d.ts +82 -3
- package/log.js +102 -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 +46 -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 +113 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +203 -0
- package/memory-storage/resource-clients/request-queue.d.ts +96 -0
- package/memory-storage/resource-clients/request-queue.js +421 -0
- package/memory-storage/utils.d.ts +17 -0
- package/memory-storage/utils.js +42 -0
- package/package.json +12 -10
- package/proxy_configuration.d.ts +29 -152
- package/proxy_configuration.js +21 -173
- package/recoverable_state.d.ts +120 -0
- package/recoverable_state.js +141 -0
- package/request.d.ts +84 -13
- package/request.js +107 -28
- package/router.d.ts +118 -16
- package/router.js +124 -30
- package/serialization.d.ts +0 -1
- package/serialization.js +1 -2
- package/service_locator.d.ts +156 -0
- package/service_locator.js +238 -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 -75
- package/session_pool/session.js +49 -102
- package/session_pool/session_pool.d.ts +85 -90
- package/session_pool/session_pool.js +131 -120
- package/storages/access_checking.d.ts +0 -1
- package/storages/access_checking.js +5 -2
- package/storages/dataset.d.ts +101 -51
- package/storages/dataset.js +168 -126
- package/storages/index.d.ts +8 -7
- package/storages/index.js +6 -7
- package/storages/key_value_store.d.ts +165 -36
- package/storages/key_value_store.js +269 -122
- 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 +23 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +53 -97
- package/storages/request_list.js +99 -75
- 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 +106 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +628 -215
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +28 -24
- package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +49 -46
- package/storages/storage_instance_manager.d.ts +91 -0
- package/storages/storage_instance_manager.js +258 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- package/storages/utils.d.ts +51 -6
- package/storages/utils.js +60 -9
- 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 +1 -2
- 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/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_extension.d.ts +0 -12
- package/crawlers/crawler_extension.d.ts.map +0 -1
- package/crawlers/crawler_extension.js +0 -14
- package/crawlers/crawler_extension.js.map +0 -1
- package/crawlers/crawler_utils.d.ts.map +0 -1
- 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/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.map +0 -1
- 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 -307
- 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!
|
|
@@ -90,10 +89,11 @@ export class RequestList {
|
|
|
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
|
|
@@ -114,14 +114,14 @@ export class RequestList {
|
|
|
114
114
|
sources;
|
|
115
115
|
sourcesFunction;
|
|
116
116
|
proxyConfiguration;
|
|
117
|
-
|
|
117
|
+
httpClient;
|
|
118
118
|
/**
|
|
119
119
|
* To create new instance of `RequestList` we need to use `RequestList.open()` factory method.
|
|
120
120
|
* @param options All `RequestList` configuration options
|
|
121
121
|
* @internal
|
|
122
122
|
*/
|
|
123
123
|
constructor(options = {}) {
|
|
124
|
-
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false,
|
|
124
|
+
const { sources, sourcesFunction, persistStateKey, persistRequestsKey, state, proxyConfiguration, keepDuplicateUrls = false, httpClient, } = options;
|
|
125
125
|
if (!(sources || sourcesFunction)) {
|
|
126
126
|
throw new ArgumentError('At least one of "sources" or "sourcesFunction" must be provided.', this.constructor);
|
|
127
127
|
}
|
|
@@ -137,11 +137,12 @@ export class RequestList {
|
|
|
137
137
|
}),
|
|
138
138
|
keepDuplicateUrls: ow.optional.boolean,
|
|
139
139
|
proxyConfiguration: ow.optional.object,
|
|
140
|
+
httpClient: ow.optional.object,
|
|
140
141
|
}));
|
|
141
|
-
this.persistStateKey = persistStateKey ? `
|
|
142
|
-
this.persistRequestsKey = persistRequestsKey ? `
|
|
142
|
+
this.persistStateKey = persistStateKey ? `CRAWLEE_${persistStateKey}` : persistStateKey;
|
|
143
|
+
this.persistRequestsKey = persistRequestsKey ? `CRAWLEE_${persistRequestsKey}` : persistRequestsKey;
|
|
143
144
|
this.initialState = state;
|
|
144
|
-
this.
|
|
145
|
+
this.httpClient = httpClient;
|
|
145
146
|
// 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
147
|
this.keepDuplicateUrls = keepDuplicateUrls;
|
|
147
148
|
// Will be empty after initialization to save memory.
|
|
@@ -149,6 +150,7 @@ export class RequestList {
|
|
|
149
150
|
this.sourcesFunction = sourcesFunction;
|
|
150
151
|
// The proxy configuration used for `requestsFromUrl` requests.
|
|
151
152
|
this.proxyConfiguration = proxyConfiguration;
|
|
153
|
+
this.persistState = this.persistState.bind(this);
|
|
152
154
|
}
|
|
153
155
|
/**
|
|
154
156
|
* Loads all remote sources of URLs and potentially starts periodic state persistence.
|
|
@@ -160,21 +162,21 @@ export class RequestList {
|
|
|
160
162
|
}
|
|
161
163
|
this.isLoading = true;
|
|
162
164
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
163
|
-
const [state, persistedRequests] = await this.
|
|
165
|
+
const [state, persistedRequests] = await this.loadStateAndPersistedRequests();
|
|
164
166
|
// Add persisted requests / new sources in a memory efficient way because with very
|
|
165
167
|
// large lists, we were running out of memory.
|
|
166
168
|
if (persistedRequests) {
|
|
167
|
-
await this.
|
|
169
|
+
await this.addPersistedRequests(persistedRequests);
|
|
168
170
|
}
|
|
169
171
|
else {
|
|
170
|
-
await this.
|
|
172
|
+
await this.addRequestsFromSources();
|
|
171
173
|
}
|
|
172
|
-
this.
|
|
174
|
+
this.restoreState(state);
|
|
173
175
|
this.isInitialized = true;
|
|
174
176
|
if (this.persistRequestsKey && !this.areRequestsPersisted)
|
|
175
|
-
await this.
|
|
177
|
+
await this.persistRequests();
|
|
176
178
|
if (this.persistStateKey) {
|
|
177
|
-
|
|
179
|
+
serviceLocator.getEventManager().on("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
178
180
|
}
|
|
179
181
|
return this;
|
|
180
182
|
}
|
|
@@ -183,17 +185,18 @@ export class RequestList {
|
|
|
183
185
|
* This needs to be done in a memory efficient way. We should update the input
|
|
184
186
|
* to a Stream once apify-client supports streams.
|
|
185
187
|
*/
|
|
186
|
-
async
|
|
188
|
+
async addPersistedRequests(persistedRequests) {
|
|
187
189
|
// We don't need the sources so we purge them to
|
|
188
190
|
// prevent them from hanging in memory.
|
|
189
191
|
for (let i = 0; i < this.sources.length; i++) {
|
|
192
|
+
// oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
|
|
190
193
|
delete this.sources[i];
|
|
191
194
|
}
|
|
192
195
|
this.sources = [];
|
|
193
196
|
this.areRequestsPersisted = true;
|
|
194
197
|
const requestStream = createDeserialize(persistedRequests);
|
|
195
198
|
for await (const request of requestStream) {
|
|
196
|
-
this.
|
|
199
|
+
this.addRequest(request);
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
202
|
/**
|
|
@@ -202,20 +205,21 @@ export class RequestList {
|
|
|
202
205
|
* We need to avoid keeping both sources and requests in memory
|
|
203
206
|
* to reduce memory footprint with very large sources.
|
|
204
207
|
*/
|
|
205
|
-
async
|
|
208
|
+
async addRequestsFromSources() {
|
|
206
209
|
// We'll load all sources in sequence to ensure that they get loaded in the right order.
|
|
207
210
|
const sourcesCount = this.sources.length;
|
|
208
211
|
for (let i = 0; i < sourcesCount; i++) {
|
|
209
212
|
const source = this.sources[i];
|
|
210
213
|
// Using delete here to drop the original object ASAP to free memory
|
|
211
214
|
// .pop would reverse the array and .shift is SLOW.
|
|
215
|
+
// oxlint-disable-next-line typescript/no-array-delete
|
|
212
216
|
delete this.sources[i];
|
|
213
217
|
if (typeof source === 'object' && source.requestsFromUrl) {
|
|
214
|
-
const fetchedRequests = await this.
|
|
215
|
-
await this.
|
|
218
|
+
const fetchedRequests = await this.fetchRequestsFromUrl(source);
|
|
219
|
+
await this.addFetchedRequests(source, fetchedRequests);
|
|
216
220
|
}
|
|
217
221
|
else {
|
|
218
|
-
this.
|
|
222
|
+
this.addRequest(source);
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
// Drop the original array full of empty indexes.
|
|
@@ -225,9 +229,12 @@ export class RequestList {
|
|
|
225
229
|
const sourcesFromFunction = await this.sourcesFunction();
|
|
226
230
|
const sourcesFromFunctionCount = sourcesFromFunction.length;
|
|
227
231
|
for (let i = 0; i < sourcesFromFunctionCount; i++) {
|
|
228
|
-
const source = sourcesFromFunction
|
|
229
|
-
|
|
232
|
+
const source = sourcesFromFunction[i];
|
|
233
|
+
// oxlint-disable-next-line typescript/no-array-delete -- intentional, drop the slot so V8 can collect the object
|
|
234
|
+
delete sourcesFromFunction[i];
|
|
235
|
+
this.addRequest(source);
|
|
230
236
|
}
|
|
237
|
+
sourcesFromFunction.length = 0;
|
|
231
238
|
}
|
|
232
239
|
catch (e) {
|
|
233
240
|
const err = e;
|
|
@@ -254,12 +261,23 @@ export class RequestList {
|
|
|
254
261
|
this.log.exception(err, 'Attempted to persist state, but failed.');
|
|
255
262
|
}
|
|
256
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Removes the `PERSIST_STATE` event listener registered during initialization and persists
|
|
266
|
+
* the current state one last time. Call this when you are done with the `RequestList` to avoid
|
|
267
|
+
* leaking the listener (and the requests it retains) on the shared event manager.
|
|
268
|
+
*/
|
|
269
|
+
async teardown() {
|
|
270
|
+
serviceLocator.getEventManager().off("persistState" /* EventType.PERSIST_STATE */, this.persistState);
|
|
271
|
+
if (this.persistStateKey) {
|
|
272
|
+
await this.persistState();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
257
275
|
/**
|
|
258
276
|
* Unlike persistState(), this is used only internally, since the sources
|
|
259
277
|
* are automatically persisted at RequestList initialization (if the persistRequestsKey is set),
|
|
260
278
|
* but there's no reason to persist it again afterwards, because RequestList is immutable.
|
|
261
279
|
*/
|
|
262
|
-
async
|
|
280
|
+
async persistRequests() {
|
|
263
281
|
const serializedRequests = await serializeArray(this.requests);
|
|
264
282
|
this.store ??= await KeyValueStore.open();
|
|
265
283
|
await this.store.setValue(this.persistRequestsKey, serializedRequests, { contentType: CONTENT_TYPE_BINARY });
|
|
@@ -268,7 +286,7 @@ export class RequestList {
|
|
|
268
286
|
/**
|
|
269
287
|
* Restores RequestList state from a state object.
|
|
270
288
|
*/
|
|
271
|
-
|
|
289
|
+
restoreState(state) {
|
|
272
290
|
// If there's no state it means we've not persisted any (yet).
|
|
273
291
|
if (!state)
|
|
274
292
|
return;
|
|
@@ -316,14 +334,14 @@ export class RequestList {
|
|
|
316
334
|
this.inProgress.delete(uniqueKey);
|
|
317
335
|
}
|
|
318
336
|
}
|
|
319
|
-
// All in-progress requests need to be re-crawled
|
|
320
|
-
this.
|
|
337
|
+
// All in-progress requests were interrupted and need to be re-crawled.
|
|
338
|
+
this.requestsToRetry = [...this.inProgress];
|
|
321
339
|
}
|
|
322
340
|
/**
|
|
323
341
|
* Attempts to load state and requests using the `RequestList` configuration
|
|
324
342
|
* and returns a tuple of [state, requests] where each may be null if not loaded.
|
|
325
343
|
*/
|
|
326
|
-
async
|
|
344
|
+
async loadStateAndPersistedRequests() {
|
|
327
345
|
let state;
|
|
328
346
|
let persistedRequests;
|
|
329
347
|
if (this.initialState) {
|
|
@@ -331,12 +349,12 @@ export class RequestList {
|
|
|
331
349
|
this.log.debug('Loaded state from options.state argument.');
|
|
332
350
|
}
|
|
333
351
|
else if (this.persistStateKey) {
|
|
334
|
-
state = await this.
|
|
352
|
+
state = await this.getPersistedState(this.persistStateKey);
|
|
335
353
|
if (state)
|
|
336
354
|
this.log.debug('Loaded state from key value store using the persistStateKey.');
|
|
337
355
|
}
|
|
338
356
|
if (this.persistRequestsKey) {
|
|
339
|
-
persistedRequests = await this.
|
|
357
|
+
persistedRequests = await this.getPersistedState(this.persistRequestsKey);
|
|
340
358
|
if (persistedRequests)
|
|
341
359
|
this.log.debug('Loaded requests from key value store using the persistRequestsKey.');
|
|
342
360
|
}
|
|
@@ -347,7 +365,7 @@ export class RequestList {
|
|
|
347
365
|
* Note that the object's fields can change in future releases.
|
|
348
366
|
*/
|
|
349
367
|
getState() {
|
|
350
|
-
this.
|
|
368
|
+
this.ensureIsInitialized();
|
|
351
369
|
return {
|
|
352
370
|
nextIndex: this.nextIndex,
|
|
353
371
|
nextUniqueKey: this.nextIndex < this.requests.length ? this.requests[this.nextIndex].uniqueKey : null,
|
|
@@ -358,25 +376,24 @@ export class RequestList {
|
|
|
358
376
|
* @inheritDoc
|
|
359
377
|
*/
|
|
360
378
|
async isEmpty() {
|
|
361
|
-
this.
|
|
362
|
-
return this.
|
|
379
|
+
this.ensureIsInitialized();
|
|
380
|
+
return this.requestsToRetry.length === 0 && this.nextIndex >= this.requests.length;
|
|
363
381
|
}
|
|
364
382
|
/**
|
|
365
383
|
* @inheritDoc
|
|
366
384
|
*/
|
|
367
385
|
async isFinished() {
|
|
368
|
-
this.
|
|
386
|
+
this.ensureIsInitialized();
|
|
369
387
|
return this.inProgress.size === 0 && this.nextIndex >= this.requests.length;
|
|
370
388
|
}
|
|
371
389
|
/**
|
|
372
390
|
* @inheritDoc
|
|
373
391
|
*/
|
|
374
392
|
async fetchNextRequest() {
|
|
375
|
-
this.
|
|
376
|
-
// First
|
|
377
|
-
const uniqueKey = this.
|
|
393
|
+
this.ensureIsInitialized();
|
|
394
|
+
// First re-serve any requests that were interrupted before the last state persist.
|
|
395
|
+
const uniqueKey = this.requestsToRetry.shift();
|
|
378
396
|
if (uniqueKey) {
|
|
379
|
-
this.reclaimed.delete(uniqueKey);
|
|
380
397
|
const index = this.uniqueKeyToIndex[uniqueKey];
|
|
381
398
|
return this.ensureRequest(this.requests[index], index);
|
|
382
399
|
}
|
|
@@ -412,31 +429,21 @@ export class RequestList {
|
|
|
412
429
|
/**
|
|
413
430
|
* @inheritDoc
|
|
414
431
|
*/
|
|
415
|
-
async
|
|
432
|
+
async markRequestAsHandled(request) {
|
|
416
433
|
const { uniqueKey } = request;
|
|
417
|
-
this.
|
|
418
|
-
this.
|
|
419
|
-
this.
|
|
434
|
+
this.ensureUniqueKeyValid(uniqueKey);
|
|
435
|
+
this.ensureInProgress(uniqueKey);
|
|
436
|
+
this.ensureIsInitialized();
|
|
420
437
|
this.inProgress.delete(uniqueKey);
|
|
421
438
|
this.isStatePersisted = false;
|
|
422
439
|
}
|
|
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
440
|
/**
|
|
434
441
|
* Adds all fetched requests from a URL from a remote resource.
|
|
435
442
|
*/
|
|
436
|
-
async
|
|
443
|
+
async addFetchedRequests(source, fetchedRequests) {
|
|
437
444
|
const { requestsFromUrl, regex } = source;
|
|
438
445
|
const originalLength = this.requests.length;
|
|
439
|
-
fetchedRequests.forEach((request) => this.
|
|
446
|
+
fetchedRequests.forEach((request) => this.addRequest(request));
|
|
440
447
|
const fetchedCount = fetchedRequests.length;
|
|
441
448
|
const importedCount = this.requests.length - originalLength;
|
|
442
449
|
this.log.info('Fetched and loaded Requests from a remote resource.', {
|
|
@@ -448,7 +455,7 @@ export class RequestList {
|
|
|
448
455
|
sample: JSON.stringify(fetchedRequests.slice(0, 5)),
|
|
449
456
|
});
|
|
450
457
|
}
|
|
451
|
-
async
|
|
458
|
+
async getPersistedState(key) {
|
|
452
459
|
this.store ??= await KeyValueStore.open();
|
|
453
460
|
const state = await this.store.getValue(key);
|
|
454
461
|
return state;
|
|
@@ -456,7 +463,7 @@ export class RequestList {
|
|
|
456
463
|
/**
|
|
457
464
|
* Fetches URLs from requestsFromUrl and returns them in format of list of requests
|
|
458
465
|
*/
|
|
459
|
-
async
|
|
466
|
+
async fetchRequestsFromUrl(source) {
|
|
460
467
|
const { requestsFromUrl, regex, ...sharedOpts } = source;
|
|
461
468
|
// Download remote resource and parse URLs.
|
|
462
469
|
let urlsArr;
|
|
@@ -464,7 +471,7 @@ export class RequestList {
|
|
|
464
471
|
urlsArr = await this._downloadListOfUrls({
|
|
465
472
|
url: requestsFromUrl,
|
|
466
473
|
urlRegExp: regex,
|
|
467
|
-
proxyUrl: await this.proxyConfiguration?.
|
|
474
|
+
proxyUrl: (await this.proxyConfiguration?.newProxyInfo())?.url,
|
|
468
475
|
});
|
|
469
476
|
}
|
|
470
477
|
catch (err) {
|
|
@@ -482,7 +489,7 @@ export class RequestList {
|
|
|
482
489
|
* If the `source` parameter is a string or plain object and not an instance
|
|
483
490
|
* of a `Request`, then the function creates a `Request` instance.
|
|
484
491
|
*/
|
|
485
|
-
|
|
492
|
+
addRequest(source) {
|
|
486
493
|
let request;
|
|
487
494
|
const type = typeof source;
|
|
488
495
|
if (type === 'string') {
|
|
@@ -504,7 +511,7 @@ export class RequestList {
|
|
|
504
511
|
request.uniqueKey += `-${this.requests.length}`;
|
|
505
512
|
}
|
|
506
513
|
const { uniqueKey } = request;
|
|
507
|
-
this.
|
|
514
|
+
this.ensureUniqueKeyValid(uniqueKey);
|
|
508
515
|
// Skip requests with duplicate uniqueKey
|
|
509
516
|
if (!Object.hasOwn(this.uniqueKeyToIndex, uniqueKey)) {
|
|
510
517
|
this.uniqueKeyToIndex[uniqueKey] = this.requests.length;
|
|
@@ -518,26 +525,23 @@ export class RequestList {
|
|
|
518
525
|
* Helper function that validates unique key.
|
|
519
526
|
* Throws an error if uniqueKey is not a non-empty string.
|
|
520
527
|
*/
|
|
521
|
-
|
|
528
|
+
ensureUniqueKeyValid(uniqueKey) {
|
|
522
529
|
if (typeof uniqueKey !== 'string' || !uniqueKey) {
|
|
523
530
|
throw new Error("Request object's uniqueKey must be a non-empty string");
|
|
524
531
|
}
|
|
525
532
|
}
|
|
526
533
|
/**
|
|
527
|
-
* Checks that request is
|
|
534
|
+
* Checks that a request is currently being processed and throws an error if not.
|
|
528
535
|
*/
|
|
529
|
-
|
|
536
|
+
ensureInProgress(uniqueKey) {
|
|
530
537
|
if (!this.inProgress.has(uniqueKey)) {
|
|
531
538
|
throw new Error(`The request is not being processed (uniqueKey: ${uniqueKey})`);
|
|
532
539
|
}
|
|
533
|
-
if (this.reclaimed.has(uniqueKey)) {
|
|
534
|
-
throw new Error(`The request was already reclaimed (uniqueKey: ${uniqueKey})`);
|
|
535
|
-
}
|
|
536
540
|
}
|
|
537
541
|
/**
|
|
538
542
|
* Throws an error if request list wasn't initialized.
|
|
539
543
|
*/
|
|
540
|
-
|
|
544
|
+
ensureIsInitialized() {
|
|
541
545
|
if (!this.isInitialized) {
|
|
542
546
|
throw new Error('RequestList is not initialized; you must call "await requestList.initialize()" before using it!');
|
|
543
547
|
}
|
|
@@ -545,15 +549,33 @@ export class RequestList {
|
|
|
545
549
|
/**
|
|
546
550
|
* Returns the total number of unique requests present in the `RequestList`.
|
|
547
551
|
*/
|
|
548
|
-
|
|
549
|
-
this.
|
|
552
|
+
async getTotalCount() {
|
|
553
|
+
this.ensureIsInitialized();
|
|
550
554
|
return this.requests.length;
|
|
551
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Returns the number of pending requests in the `RequestList`.
|
|
558
|
+
*/
|
|
559
|
+
async getPendingCount() {
|
|
560
|
+
this.ensureIsInitialized();
|
|
561
|
+
return this.requests.length - (this.nextIndex - this.inProgress.size);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
565
|
+
* {@link RequestManagerTandem}, allowing requests to be added and reclaimed while still
|
|
566
|
+
* being read from this list first.
|
|
567
|
+
*/
|
|
568
|
+
async toTandem(requestManager) {
|
|
569
|
+
// Import here to avoid circular imports.
|
|
570
|
+
const { RequestManagerTandem } = await import('./request_manager_tandem.js');
|
|
571
|
+
const { RequestQueue } = await import('./request_queue.js');
|
|
572
|
+
return new RequestManagerTandem(this, requestManager ?? (await RequestQueue.open()));
|
|
573
|
+
}
|
|
552
574
|
/**
|
|
553
575
|
* @inheritDoc
|
|
554
576
|
*/
|
|
555
|
-
|
|
556
|
-
this.
|
|
577
|
+
async getHandledCount() {
|
|
578
|
+
this.ensureIsInitialized();
|
|
557
579
|
return this.nextIndex - this.inProgress.size;
|
|
558
580
|
}
|
|
559
581
|
/**
|
|
@@ -633,7 +655,9 @@ export class RequestList {
|
|
|
633
655
|
* @internal wraps public utility for mocking purposes
|
|
634
656
|
*/
|
|
635
657
|
async _downloadListOfUrls(options) {
|
|
636
|
-
return downloadListOfUrls(
|
|
658
|
+
return downloadListOfUrls({
|
|
659
|
+
...options,
|
|
660
|
+
httpClient: this.httpClient,
|
|
661
|
+
});
|
|
637
662
|
}
|
|
638
663
|
}
|
|
639
|
-
//# sourceMappingURL=request_list.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Dictionary } from '@crawlee/types';
|
|
2
|
+
import type { Request } from '../request.js';
|
|
3
|
+
import type { IRequestManager } from './request_manager.js';
|
|
4
|
+
import type { RequestQueueOperationInfo } from './request_queue.js';
|
|
5
|
+
/**
|
|
6
|
+
* An abstract interface defining a read-only stream of requests to crawl.
|
|
7
|
+
*
|
|
8
|
+
* Request loaders are used to manage and provide access to a storage of crawling requests.
|
|
9
|
+
*
|
|
10
|
+
* Key responsibilities:
|
|
11
|
+
* - Fetching the next request to be processed.
|
|
12
|
+
* - Marking requests as handled once they are no longer in progress.
|
|
13
|
+
* - Managing state information such as the total and handled request counts.
|
|
14
|
+
*
|
|
15
|
+
* ## Request lifecycle contract
|
|
16
|
+
*
|
|
17
|
+
* Every request returned by {@link IRequestLoader.fetchNextRequest} is considered **in progress**
|
|
18
|
+
* until it is passed to {@link IRequestLoader.markRequestAsHandled}. Once you fetch a request, you are
|
|
19
|
+
* obligated to eventually mark it as handled — there is no way to hand a request back to a loader
|
|
20
|
+
* (only an {@link IRequestManager} can reclaim requests for a retry). "Handled" therefore means
|
|
21
|
+
* "finished with this request", whether processing succeeded or was abandoned after exhausting retries.
|
|
22
|
+
*
|
|
23
|
+
* Honoring this contract matters for three reasons:
|
|
24
|
+
* - **Restarts and migrations:** loaders that persist their state (see {@link IRequestLoader.persistState})
|
|
25
|
+
* treat in-progress requests as interrupted and re-serve them after a restart. A request that is fetched
|
|
26
|
+
* but never marked handled will be crawled again.
|
|
27
|
+
* - **Termination detection:** {@link IRequestLoader.isFinished} only resolves to `true` once nothing is
|
|
28
|
+
* in progress. Leaving a request unmarked keeps the crawler running indefinitely.
|
|
29
|
+
* - **Bookkeeping:** the handled and pending counts are derived from the set of in-progress requests, so
|
|
30
|
+
* skipping {@link IRequestLoader.markRequestAsHandled} corrupts {@link IRequestLoader.getHandledCount}
|
|
31
|
+
* and {@link IRequestLoader.getPendingCount}.
|
|
32
|
+
*
|
|
33
|
+
* Concrete implementations such as {@link RequestList} or {@link SitemapRequestLoader} build on this interface.
|
|
34
|
+
* The {@link IRequestManager} interface extends it with the capability to enqueue and reclaim requests.
|
|
35
|
+
*/
|
|
36
|
+
export interface IRequestLoader {
|
|
37
|
+
/**
|
|
38
|
+
* Returns an approximation of the total number of requests in the loader (i.e. pending + handled).
|
|
39
|
+
*/
|
|
40
|
+
getTotalCount(): Promise<number>;
|
|
41
|
+
/**
|
|
42
|
+
* Returns an approximation of the number of pending requests in the loader.
|
|
43
|
+
*/
|
|
44
|
+
getPendingCount(): Promise<number>;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the number of requests in the loader that have been handled.
|
|
47
|
+
*/
|
|
48
|
+
getHandledCount(): Promise<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Returns `true` if all requests were already handled and there are no more left.
|
|
51
|
+
*/
|
|
52
|
+
isFinished(): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves to `true` if the next call to {@link IRequestLoader.fetchNextRequest} function
|
|
55
|
+
* would return `null`, otherwise it resolves to `false`.
|
|
56
|
+
* Note that even if the loader is empty, there might be some pending requests currently being processed.
|
|
57
|
+
*/
|
|
58
|
+
isEmpty(): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Gets the next {@link Request} to process, or `null` if there are no more pending requests.
|
|
61
|
+
*
|
|
62
|
+
* The returned request is marked as **in progress** and remains so until it is passed to
|
|
63
|
+
* {@link IRequestLoader.markRequestAsHandled}. The caller is responsible for eventually marking
|
|
64
|
+
* every fetched request as handled; otherwise the loader never considers itself finished and the
|
|
65
|
+
* request may be re-served after a restart. See the request lifecycle contract on {@link IRequestLoader}.
|
|
66
|
+
*/
|
|
67
|
+
fetchNextRequest<T extends Dictionary = Dictionary>(): Promise<Request<T> | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Can be used to iterate over the loader instance in a `for await .. of` loop.
|
|
70
|
+
* Provides an alternative for the repeated use of `fetchNextRequest`.
|
|
71
|
+
*/
|
|
72
|
+
[Symbol.asyncIterator](): AsyncGenerator<Request>;
|
|
73
|
+
/**
|
|
74
|
+
* Marks a request previously returned by {@link IRequestLoader.fetchNextRequest} as handled,
|
|
75
|
+
* removing it from the set of in-progress requests.
|
|
76
|
+
*
|
|
77
|
+
* Call this once you are done with the request — whether processing succeeded or was abandoned after
|
|
78
|
+
* exhausting retries. Because a loader cannot take a request back, marking it handled is the only way to
|
|
79
|
+
* signal completion; failing to do so prevents {@link IRequestLoader.isFinished} from ever resolving to
|
|
80
|
+
* `true` and skews the handled and pending counts. See the request lifecycle contract on {@link IRequestLoader}.
|
|
81
|
+
*/
|
|
82
|
+
markRequestAsHandled(request: Request): Promise<RequestQueueOperationInfo | void | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Persists the current state of the loader into the default {@link KeyValueStore}.
|
|
85
|
+
*
|
|
86
|
+
* Not all loaders support persistence; implementations that do not should leave this `undefined`.
|
|
87
|
+
*/
|
|
88
|
+
persistState?(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Combines the loader with a request manager to support adding and reclaiming requests.
|
|
91
|
+
*
|
|
92
|
+
* @param requestManager Request manager to combine the loader with. If not provided, the default
|
|
93
|
+
* {@link RequestQueue} is used.
|
|
94
|
+
*/
|
|
95
|
+
toTandem?(requestManager?: IRequestManager): Promise<IRequestManager>;
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Request, Source } from '../request.js';
|
|
2
|
+
import type { IRequestLoader } from './request_loader.js';
|
|
3
|
+
import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationInfo, RequestQueueOperationOptions } from './request_queue.js';
|
|
4
|
+
export type RequestsLike = AsyncIterable<Source | string> | Iterable<Source | string> | (Source | string)[];
|
|
5
|
+
/**
|
|
6
|
+
* Extends the read-only {@link IRequestLoader} interface with the capability to enqueue new requests
|
|
7
|
+
* and reclaim failed ones.
|
|
8
|
+
*/
|
|
9
|
+
export interface IRequestManager extends IRequestLoader {
|
|
10
|
+
/**
|
|
11
|
+
* Reclaims request to the provider if its processing failed.
|
|
12
|
+
* The request will be returned by some subsequent `fetchNextRequest()` call.
|
|
13
|
+
*/
|
|
14
|
+
reclaimRequest(request: Request, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo | null>;
|
|
15
|
+
addRequest(requestLike: Source, options?: RequestQueueOperationOptions): Promise<RequestQueueOperationInfo>;
|
|
16
|
+
addRequestsBatched(requests: RequestsLike, options?: AddRequestsBatchedOptions): Promise<AddRequestsBatchedResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Remove all requests from the queue but keep the queue itself, resetting it
|
|
19
|
+
* so it can be reused (e.g. across multiple `crawler.run()` calls).
|
|
20
|
+
*
|
|
21
|
+
* Implementations that do not support purging may leave this `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
purge?(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Tells the manager how long a consumer expects to hold a request fetched via `fetchNextRequest()`
|
|
26
|
+
* before marking it handled or reclaiming it (typically the request-handler timeout plus padding).
|
|
27
|
+
*
|
|
28
|
+
* Managers backed by a storage backend that reserves requests via locking use this to avoid handing
|
|
29
|
+
* the same request out again while it is still being processed. Implementations that do not need
|
|
30
|
+
* this hint may leave it `undefined`.
|
|
31
|
+
*/
|
|
32
|
+
setExpectedRequestProcessingTimeSecs?(secs: number): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|