@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +105 -97
- package/autoscaling/concurrency_system.d.ts +7 -22
- package/autoscaling/concurrency_system.js +112 -100
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +11 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/index.d.ts +1 -1
- package/autoscaling/index.js +1 -1
- package/autoscaling/load_signal.d.ts +8 -7
- package/autoscaling/load_signal.js +14 -12
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +41 -41
- package/autoscaling/snapshotter.d.ts +7 -10
- package/autoscaling/snapshotter.js +18 -18
- package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
- package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
- package/autoscaling/system_status.d.ts +9 -11
- package/autoscaling/system_status.js +13 -13
- package/configuration.d.ts +16 -16
- package/configuration.js +6 -6
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +14 -112
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/error_snapshotter.d.ts +2 -4
- package/crawlers/error_snapshotter.js +5 -5
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +164 -45
- package/crawlers/statistics.js +339 -153
- package/debug.js +4 -4
- package/enqueue_links/enqueue_links.d.ts +51 -159
- package/enqueue_links/enqueue_links.js +38 -231
- package/enqueue_links/shared.d.ts +34 -24
- package/enqueue_links/shared.js +54 -36
- package/errors.d.ts +43 -12
- package/errors.js +48 -9
- package/events/event_manager.d.ts +2 -2
- package/events/event_manager.js +7 -6
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +5 -5
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +3 -11
- package/memory-storage/memory-storage.js +42 -48
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +8 -13
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +36 -38
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +68 -80
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +8 -9
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +18 -14
- package/recoverable_state.d.ts +80 -45
- package/recoverable_state.js +163 -75
- package/request.d.ts +3 -4
- package/request.js +57 -44
- package/router.d.ts +6 -9
- package/router.js +23 -23
- package/serialization.js +14 -13
- package/service_locator.d.ts +19 -10
- package/service_locator.js +57 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +76 -74
- package/session_pool/session_pool.d.ts +6 -12
- package/session_pool/session_pool.js +85 -77
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +133 -32
- package/storages/index.d.ts +2 -1
- package/storages/index.js +2 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +204 -75
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +4 -23
- package/storages/request_list.js +103 -96
- package/storages/request_loader.d.ts +5 -0
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -18
- package/storages/request_queue.js +318 -177
- package/storages/sitemap_request_loader.d.ts +22 -70
- package/storages/sitemap_request_loader.js +122 -116
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +17 -17
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/throttling_request_manager.d.ts +239 -0
- package/storages/throttling_request_manager.js +646 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +6 -3
- package/storages/utils.js +11 -2
- package/system-info/runtime.js +7 -7
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -25
- package/validators.js +13 -25
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
|
@@ -1,43 +1,31 @@
|
|
|
1
|
-
import type { BaseHttpClient } from '@crawlee/
|
|
2
|
-
import { type ParseSitemapOptions } from '@crawlee/utils';
|
|
3
|
-
import type {
|
|
1
|
+
import type { BaseHttpClient } from '@crawlee/http-client';
|
|
2
|
+
import { EnqueueStrategy, type ParseSitemapOptions } from '@crawlee/utils';
|
|
3
|
+
import type { UrlPatternInput } from '../enqueue_links/shared.js';
|
|
4
4
|
import { Request } from '../request.js';
|
|
5
5
|
import type { IRequestLoader } from './request_loader.js';
|
|
6
6
|
import type { IRequestManager } from './request_manager.js';
|
|
7
7
|
interface UrlConstraints {
|
|
8
8
|
/**
|
|
9
|
-
* An array of
|
|
10
|
-
* containing glob pattern strings matching the URLs to be enqueued.
|
|
9
|
+
* An array of URL patterns that URLs must match to be included.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
11
|
+
* Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
* If you need case-sensitive matching, use `
|
|
13
|
+
* Glob matching is always case-insensitive.
|
|
14
|
+
* If you need case-sensitive matching, use a `RegExp`.
|
|
16
15
|
*
|
|
17
|
-
* If `
|
|
16
|
+
* If `include` is an empty array or `undefined`, then the `SitemapRequestLoader`
|
|
18
17
|
* includes all the URLs from the sitemap.
|
|
19
18
|
*/
|
|
20
|
-
|
|
19
|
+
include?: readonly UrlPatternInput[];
|
|
21
20
|
/**
|
|
22
|
-
* An array of
|
|
23
|
-
* containing patterns matching URLs that will **never** be included.
|
|
21
|
+
* An array of URL patterns. Matching URLs will **not** be included.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
23
|
+
* Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
|
|
26
24
|
*
|
|
27
25
|
* Glob matching is always case-insensitive.
|
|
28
|
-
* If you need case-sensitive matching,
|
|
26
|
+
* If you need case-sensitive matching, use a `RegExp`.
|
|
29
27
|
*/
|
|
30
|
-
exclude?: readonly
|
|
31
|
-
/**
|
|
32
|
-
* An array of regular expressions or plain objects
|
|
33
|
-
* containing regular expressions matching the URLs to be enqueued.
|
|
34
|
-
*
|
|
35
|
-
* The plain objects must include at least the `regexp` property, which holds the regular expression.
|
|
36
|
-
*
|
|
37
|
-
* If `regexps` is an empty array or `undefined`, and `globs` are also not defined, then the `SitemapRequestLoader`
|
|
38
|
-
* includes all the URLs from the sitemap.
|
|
39
|
-
*/
|
|
40
|
-
regexps?: readonly RegExpInput[];
|
|
28
|
+
exclude?: readonly UrlPatternInput[];
|
|
41
29
|
}
|
|
42
30
|
export interface SitemapRequestLoaderOptions extends UrlConstraints {
|
|
43
31
|
/**
|
|
@@ -77,6 +65,13 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
|
|
|
77
65
|
* @default 200
|
|
78
66
|
*/
|
|
79
67
|
maxBufferSize?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Keep only sitemap-derived URLs matching this strategy relative to the parent sitemap URL; non-`http(s)`
|
|
70
|
+
* schemes are always dropped. The filtering stays enforced after navigation (e.g. across redirects).
|
|
71
|
+
* Pass `'all'` to disable host filtering.
|
|
72
|
+
* @default EnqueueStrategy.SameHostname
|
|
73
|
+
*/
|
|
74
|
+
enqueueStrategy?: EnqueueStrategy | `${EnqueueStrategy}`;
|
|
80
75
|
/**
|
|
81
76
|
* Advanced options for the underlying `parseSitemap` call.
|
|
82
77
|
*/
|
|
@@ -92,55 +87,12 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
|
|
|
92
87
|
* The loading of the sitemap is performed in the background so that crawling can start before the sitemap is fully loaded.
|
|
93
88
|
*/
|
|
94
89
|
export declare class SitemapRequestLoader implements IRequestLoader {
|
|
90
|
+
#private;
|
|
95
91
|
/**
|
|
96
92
|
* Set of URLs that were returned by `fetchNextRequest()` and not marked as handled yet.
|
|
97
93
|
* @internal
|
|
98
94
|
*/
|
|
99
95
|
inProgress: Set<string>;
|
|
100
|
-
/**
|
|
101
|
-
* Map of returned Request objects that have not been marked as handled yet.
|
|
102
|
-
*
|
|
103
|
-
* We use this to persist custom user fields on the in-progress requests.
|
|
104
|
-
*/
|
|
105
|
-
private requestData;
|
|
106
|
-
/**
|
|
107
|
-
* Object for keeping track of the sitemap parsing progress.
|
|
108
|
-
*/
|
|
109
|
-
private sitemapParsingProgress;
|
|
110
|
-
/**
|
|
111
|
-
* Object stream of URLs parsed from the sitemaps.
|
|
112
|
-
* Using `highWaterMark`, this can manage the speed of the sitemap loading.
|
|
113
|
-
*
|
|
114
|
-
* Fetch the next URL to be processed using `fetchNextRequest()`.
|
|
115
|
-
*/
|
|
116
|
-
private urlQueueStream;
|
|
117
|
-
/**
|
|
118
|
-
* Indicates whether the request list sitemap loading was aborted.
|
|
119
|
-
*
|
|
120
|
-
* If the loading was aborted before the sitemaps were fully loaded, the request list might be missing some URLs.
|
|
121
|
-
* The `isSitemapFullyLoaded` method can be used to check if the sitemaps were fully loaded.
|
|
122
|
-
*
|
|
123
|
-
* If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
|
|
124
|
-
*/
|
|
125
|
-
private abortLoading;
|
|
126
|
-
/** Number of URLs that were marked as handled */
|
|
127
|
-
private handledUrlCount;
|
|
128
|
-
private persistStateKey?;
|
|
129
|
-
private store?;
|
|
130
|
-
private closed;
|
|
131
|
-
/**
|
|
132
|
-
* Proxy URL to be used for sitemap loading.
|
|
133
|
-
*/
|
|
134
|
-
private proxyUrl?;
|
|
135
|
-
/**
|
|
136
|
-
* Logger instance.
|
|
137
|
-
*/
|
|
138
|
-
private log;
|
|
139
|
-
private urlExcludePatternObjects;
|
|
140
|
-
private urlPatternObjects;
|
|
141
|
-
/** EventManager used to handle persistence */
|
|
142
|
-
private events;
|
|
143
|
-
private persistenceOptions;
|
|
144
96
|
/** @internal */
|
|
145
97
|
private constructor();
|
|
146
98
|
/**
|
|
@@ -156,7 +108,7 @@ export declare class SitemapRequestLoader implements IRequestLoader {
|
|
|
156
108
|
*/
|
|
157
109
|
private matchesUrl;
|
|
158
110
|
/**
|
|
159
|
-
* Checks whether the URL matches the `
|
|
111
|
+
* Checks whether the URL matches the `include` / `exclude` patterns provided in the `options`.
|
|
160
112
|
* @param url URL to be checked.
|
|
161
113
|
* @returns `true` if the URL matches the patterns, `false` otherwise.
|
|
162
114
|
*/
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
|
-
import { parseSitemap } from '@crawlee/utils';
|
|
2
|
+
import { EnqueueStrategy, parseSitemap } from '@crawlee/utils';
|
|
3
3
|
import { minimatch } from 'minimatch';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { constructUrlPatternObjects, urlPatternSchema } from '../enqueue_links/shared.js';
|
|
6
|
+
import { EventType } from '../events/event_manager.js';
|
|
6
7
|
import { Request } from '../request.js';
|
|
7
8
|
import { serviceLocator } from '../service_locator.js';
|
|
9
|
+
import { parseArgument, schemas } from '../validators.js';
|
|
8
10
|
import { KeyValueStore } from './key_value_store.js';
|
|
9
11
|
import { purgeDefaultStorages } from './utils.js';
|
|
12
|
+
const sitemapRequestLoaderOptionsSchema = z.strictObject({
|
|
13
|
+
sitemapUrls: schemas.arrayOf(z.string(), 'strings'),
|
|
14
|
+
proxyUrl: z.string().optional(),
|
|
15
|
+
persistStateKey: z.string().optional(),
|
|
16
|
+
signal: z.unknown().optional(),
|
|
17
|
+
timeoutMillis: schemas.anyNumber.optional(),
|
|
18
|
+
maxBufferSize: schemas.anyNumber.default(200),
|
|
19
|
+
enqueueStrategy: z.enum(EnqueueStrategy).default(EnqueueStrategy.SameHostname),
|
|
20
|
+
parseSitemapOptions: z.looseObject({}).optional(),
|
|
21
|
+
include: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
22
|
+
exclude: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
23
|
+
persistenceOptions: z.looseObject({}).optional(),
|
|
24
|
+
httpClient: schemas.httpClient.optional(),
|
|
25
|
+
});
|
|
10
26
|
/** @internal */
|
|
11
27
|
const STATE_PERSISTENCE_KEY = 'SITEMAP_REQUEST_LOADER_STATE';
|
|
12
28
|
/**
|
|
@@ -25,11 +41,11 @@ export class SitemapRequestLoader {
|
|
|
25
41
|
*
|
|
26
42
|
* We use this to persist custom user fields on the in-progress requests.
|
|
27
43
|
*/
|
|
28
|
-
requestData = new Map();
|
|
44
|
+
#requestData = new Map();
|
|
29
45
|
/**
|
|
30
46
|
* Object for keeping track of the sitemap parsing progress.
|
|
31
47
|
*/
|
|
32
|
-
sitemapParsingProgress = {
|
|
48
|
+
#sitemapParsingProgress = {
|
|
33
49
|
/**
|
|
34
50
|
* URL of the sitemap that is currently being parsed. `null` if no sitemap is being parsed.
|
|
35
51
|
*/
|
|
@@ -49,7 +65,7 @@ export class SitemapRequestLoader {
|
|
|
49
65
|
*
|
|
50
66
|
* Fetch the next URL to be processed using `fetchNextRequest()`.
|
|
51
67
|
*/
|
|
52
|
-
urlQueueStream;
|
|
68
|
+
#urlQueueStream;
|
|
53
69
|
/**
|
|
54
70
|
* Indicates whether the request list sitemap loading was aborted.
|
|
55
71
|
*
|
|
@@ -58,64 +74,46 @@ export class SitemapRequestLoader {
|
|
|
58
74
|
*
|
|
59
75
|
* If the loading is aborted and all the requests are handled, `isFinished()` will return `true`.
|
|
60
76
|
*/
|
|
61
|
-
abortLoading = false;
|
|
77
|
+
#abortLoading = false;
|
|
62
78
|
/** Number of URLs that were marked as handled */
|
|
63
|
-
handledUrlCount = 0;
|
|
64
|
-
persistStateKey;
|
|
65
|
-
store;
|
|
66
|
-
closed = false;
|
|
79
|
+
#handledUrlCount = 0;
|
|
80
|
+
#persistStateKey;
|
|
81
|
+
#store;
|
|
82
|
+
#closed = false;
|
|
67
83
|
/**
|
|
68
84
|
* Proxy URL to be used for sitemap loading.
|
|
69
85
|
*/
|
|
70
|
-
proxyUrl;
|
|
86
|
+
#proxyUrl;
|
|
87
|
+
/**
|
|
88
|
+
* Enqueue strategy applied to sitemap-derived URLs and stamped onto the emitted `Request` objects.
|
|
89
|
+
*/
|
|
90
|
+
#enqueueStrategy;
|
|
71
91
|
/**
|
|
72
92
|
* Logger instance.
|
|
73
93
|
*/
|
|
74
|
-
log;
|
|
75
|
-
urlExcludePatternObjects = [];
|
|
76
|
-
urlPatternObjects = [];
|
|
94
|
+
#log;
|
|
95
|
+
#urlExcludePatternObjects = [];
|
|
96
|
+
#urlPatternObjects = [];
|
|
77
97
|
/** EventManager used to handle persistence */
|
|
78
|
-
events;
|
|
79
|
-
persistenceOptions;
|
|
98
|
+
#events;
|
|
99
|
+
#persistenceOptions;
|
|
80
100
|
/** @internal */
|
|
81
101
|
constructor(options) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
proxyUrl: ow.optional.string,
|
|
85
|
-
persistStateKey: ow.optional.string,
|
|
86
|
-
signal: ow.optional.any(),
|
|
87
|
-
timeoutMillis: ow.optional.number,
|
|
88
|
-
maxBufferSize: ow.optional.number,
|
|
89
|
-
parseSitemapOptions: ow.optional.object,
|
|
90
|
-
globs: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('glob'))),
|
|
91
|
-
exclude: ow.optional.array.ofType(ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'))),
|
|
92
|
-
regexps: ow.optional.array.ofType(ow.any(ow.regExp, ow.object.hasKeys('regexp'))),
|
|
93
|
-
persistenceOptions: ow.optional.object,
|
|
94
|
-
}));
|
|
95
|
-
const { globs, exclude, regexps } = options;
|
|
96
|
-
this.log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
|
|
102
|
+
const { include, exclude, persistStateKey, persistenceOptions, proxyUrl, maxBufferSize, sitemapUrls, enqueueStrategy, } = parseArgument(options, sitemapRequestLoaderOptionsSchema, 'SitemapRequestLoaderOptions');
|
|
103
|
+
this.#log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
|
|
97
104
|
if (exclude?.length) {
|
|
98
|
-
|
|
99
|
-
if (typeof excl === 'string' || 'glob' in excl) {
|
|
100
|
-
this.urlExcludePatternObjects.push(...constructGlobObjectsFromGlobs([excl]));
|
|
101
|
-
}
|
|
102
|
-
else if (excl instanceof RegExp || 'regexp' in excl) {
|
|
103
|
-
this.urlExcludePatternObjects.push(...constructRegExpObjectsFromRegExps([excl]));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (globs?.length) {
|
|
108
|
-
this.urlPatternObjects.push(...constructGlobObjectsFromGlobs(globs));
|
|
105
|
+
this.#urlExcludePatternObjects.push(...constructUrlPatternObjects(exclude));
|
|
109
106
|
}
|
|
110
|
-
if (
|
|
111
|
-
this
|
|
107
|
+
if (include?.length) {
|
|
108
|
+
this.#urlPatternObjects.push(...constructUrlPatternObjects(include));
|
|
112
109
|
}
|
|
113
|
-
this
|
|
114
|
-
this
|
|
115
|
-
this
|
|
116
|
-
this
|
|
117
|
-
this
|
|
118
|
-
this.
|
|
110
|
+
this.#persistStateKey = persistStateKey;
|
|
111
|
+
this.#persistenceOptions = { enable: true, ...persistenceOptions };
|
|
112
|
+
this.#proxyUrl = proxyUrl;
|
|
113
|
+
this.#enqueueStrategy = enqueueStrategy;
|
|
114
|
+
this.#urlQueueStream = this.createNewStream(maxBufferSize);
|
|
115
|
+
this.#sitemapParsingProgress.pendingSitemapUrls = new Set(sitemapUrls);
|
|
116
|
+
this.#events = serviceLocator.getEventManager();
|
|
119
117
|
this.persistState = this.persistState.bind(this);
|
|
120
118
|
}
|
|
121
119
|
/**
|
|
@@ -143,13 +141,13 @@ export class SitemapRequestLoader {
|
|
|
143
141
|
};
|
|
144
142
|
}
|
|
145
143
|
/**
|
|
146
|
-
* Checks whether the URL matches the `
|
|
144
|
+
* Checks whether the URL matches the `include` / `exclude` patterns provided in the `options`.
|
|
147
145
|
* @param url URL to be checked.
|
|
148
146
|
* @returns `true` if the URL matches the patterns, `false` otherwise.
|
|
149
147
|
*/
|
|
150
148
|
isUrlMatchingPatterns(url) {
|
|
151
|
-
return (!this
|
|
152
|
-
(this
|
|
149
|
+
return (!this.#urlExcludePatternObjects.some(this.matchesUrl(url)) &&
|
|
150
|
+
(this.#urlPatternObjects.length === 0 || this.#urlPatternObjects.some(this.matchesUrl(url))));
|
|
153
151
|
}
|
|
154
152
|
/**
|
|
155
153
|
* Adds a URL to the queue of parsed URLs.
|
|
@@ -158,13 +156,13 @@ export class SitemapRequestLoader {
|
|
|
158
156
|
*/
|
|
159
157
|
async pushNextUrl(url) {
|
|
160
158
|
return new Promise((resolve) => {
|
|
161
|
-
if (this
|
|
159
|
+
if (this.#closed || (url && !this.isUrlMatchingPatterns(url))) {
|
|
162
160
|
resolve();
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
|
-
if (!this
|
|
163
|
+
if (!this.#urlQueueStream.push(url)) {
|
|
166
164
|
// This doesn't work with the 'drain' event (it's not emitted for some reason).
|
|
167
|
-
this
|
|
165
|
+
this.#urlQueueStream.once('readdata', () => {
|
|
168
166
|
resolve();
|
|
169
167
|
});
|
|
170
168
|
}
|
|
@@ -181,21 +179,21 @@ export class SitemapRequestLoader {
|
|
|
181
179
|
*/
|
|
182
180
|
async readNextUrl() {
|
|
183
181
|
return new Promise((resolve) => {
|
|
184
|
-
if (this
|
|
182
|
+
if (this.#closed) {
|
|
185
183
|
resolve(null);
|
|
186
184
|
return;
|
|
187
185
|
}
|
|
188
|
-
const result = this
|
|
186
|
+
const result = this.#urlQueueStream.read();
|
|
189
187
|
if (!result && !this.isSitemapFullyLoaded()) {
|
|
190
|
-
this
|
|
191
|
-
const nextUrl = this
|
|
188
|
+
this.#urlQueueStream.once('readable', () => {
|
|
189
|
+
const nextUrl = this.#urlQueueStream.read();
|
|
192
190
|
resolve(nextUrl);
|
|
193
191
|
});
|
|
194
192
|
}
|
|
195
193
|
else {
|
|
196
194
|
resolve(result);
|
|
197
195
|
}
|
|
198
|
-
this
|
|
196
|
+
this.#urlQueueStream.emit('readdata');
|
|
199
197
|
});
|
|
200
198
|
}
|
|
201
199
|
/**
|
|
@@ -204,8 +202,8 @@ export class SitemapRequestLoader {
|
|
|
204
202
|
* If this is `false`, the background processing is either still in progress or was aborted.
|
|
205
203
|
*/
|
|
206
204
|
isSitemapFullyLoaded() {
|
|
207
|
-
return (this
|
|
208
|
-
this
|
|
205
|
+
return (this.#sitemapParsingProgress.inProgressSitemapUrl === null &&
|
|
206
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.size === 0);
|
|
209
207
|
}
|
|
210
208
|
/**
|
|
211
209
|
* Start processing the sitemaps and loading the URLs.
|
|
@@ -213,34 +211,35 @@ export class SitemapRequestLoader {
|
|
|
213
211
|
* Resolves once all the sitemaps URLs have been fully loaded (sets `isSitemapFullyLoaded` to `true`).
|
|
214
212
|
*/
|
|
215
213
|
async load({ parseSitemapOptions, }) {
|
|
216
|
-
while (!this.isSitemapFullyLoaded() && !this
|
|
217
|
-
const sitemapUrl = this
|
|
218
|
-
this
|
|
214
|
+
while (!this.isSitemapFullyLoaded() && !this.#abortLoading) {
|
|
215
|
+
const sitemapUrl = this.#sitemapParsingProgress.inProgressSitemapUrl ??
|
|
216
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.values().next().value;
|
|
219
217
|
try {
|
|
220
|
-
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this
|
|
218
|
+
for await (const item of parseSitemap([{ type: 'url', url: sitemapUrl }], this.#proxyUrl, {
|
|
221
219
|
...parseSitemapOptions,
|
|
222
220
|
maxDepth: 0,
|
|
223
221
|
emitNestedSitemaps: true,
|
|
222
|
+
enqueueStrategy: this.#enqueueStrategy,
|
|
224
223
|
})) {
|
|
225
224
|
if (!item.originSitemapUrl) {
|
|
226
225
|
// This is a nested sitemap
|
|
227
|
-
this
|
|
226
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.add(item.loc);
|
|
228
227
|
continue;
|
|
229
228
|
}
|
|
230
|
-
if (!this
|
|
229
|
+
if (!this.#sitemapParsingProgress.inProgressEntries.has(item.loc)) {
|
|
231
230
|
await this.pushNextUrl(item.loc);
|
|
232
|
-
this
|
|
231
|
+
this.#sitemapParsingProgress.inProgressEntries.add(item.loc);
|
|
233
232
|
}
|
|
234
233
|
}
|
|
235
234
|
}
|
|
236
235
|
catch (e) {
|
|
237
|
-
this
|
|
236
|
+
this.#log.error('Error loading sitemap contents:', e);
|
|
238
237
|
}
|
|
239
|
-
this
|
|
240
|
-
this
|
|
241
|
-
this
|
|
238
|
+
this.#sitemapParsingProgress.pendingSitemapUrls.delete(sitemapUrl);
|
|
239
|
+
this.#sitemapParsingProgress.inProgressEntries.clear();
|
|
240
|
+
this.#sitemapParsingProgress.inProgressSitemapUrl = null;
|
|
242
241
|
}
|
|
243
|
-
this
|
|
242
|
+
this.#urlQueueStream.end();
|
|
244
243
|
}
|
|
245
244
|
/**
|
|
246
245
|
* Open a sitemap and start processing it.
|
|
@@ -259,15 +258,15 @@ export class SitemapRequestLoader {
|
|
|
259
258
|
void requestList.load({
|
|
260
259
|
parseSitemapOptions: { logger: serviceLocator.getLogger(), ...options.parseSitemapOptions, httpClient },
|
|
261
260
|
});
|
|
262
|
-
if (requestList
|
|
263
|
-
requestList
|
|
261
|
+
if (requestList.#persistenceOptions.enable) {
|
|
262
|
+
requestList.#events.on(EventType.PERSIST_STATE, requestList.persistState);
|
|
264
263
|
}
|
|
265
264
|
options?.signal?.addEventListener('abort', () => {
|
|
266
|
-
requestList
|
|
265
|
+
requestList.#abortLoading = true;
|
|
267
266
|
});
|
|
268
267
|
if (options.timeoutMillis) {
|
|
269
268
|
setTimeout(() => {
|
|
270
|
-
requestList
|
|
269
|
+
requestList.#abortLoading = true;
|
|
271
270
|
}, options.timeoutMillis);
|
|
272
271
|
}
|
|
273
272
|
return requestList;
|
|
@@ -278,14 +277,14 @@ export class SitemapRequestLoader {
|
|
|
278
277
|
async getTotalCount() {
|
|
279
278
|
// Total known so far = not-yet-fetched (still buffered in the stream) + in-progress (fetched but not
|
|
280
279
|
// yet handled) + already handled.
|
|
281
|
-
return this
|
|
280
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size + this.#handledUrlCount;
|
|
282
281
|
}
|
|
283
282
|
/**
|
|
284
283
|
* @inheritDoc
|
|
285
284
|
*/
|
|
286
285
|
async getPendingCount() {
|
|
287
286
|
// Pending = everything not yet handled = not-yet-fetched + in-progress.
|
|
288
|
-
return this
|
|
287
|
+
return this.#urlQueueStream.readableLength + this.inProgress.size;
|
|
289
288
|
}
|
|
290
289
|
/**
|
|
291
290
|
* Combines this list with a request manager (a {@link RequestQueue} by default) into a
|
|
@@ -302,31 +301,31 @@ export class SitemapRequestLoader {
|
|
|
302
301
|
* @inheritDoc
|
|
303
302
|
*/
|
|
304
303
|
async isFinished() {
|
|
305
|
-
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this
|
|
304
|
+
return ((await this.isEmpty()) && this.inProgress.size === 0 && (this.isSitemapFullyLoaded() || this.#abortLoading));
|
|
306
305
|
}
|
|
307
306
|
/**
|
|
308
307
|
* @inheritDoc
|
|
309
308
|
*/
|
|
310
309
|
async isEmpty() {
|
|
311
|
-
return this
|
|
310
|
+
return this.#urlQueueStream.readableLength === 0;
|
|
312
311
|
}
|
|
313
312
|
/**
|
|
314
313
|
* @inheritDoc
|
|
315
314
|
*/
|
|
316
315
|
async getHandledCount() {
|
|
317
|
-
return this
|
|
316
|
+
return this.#handledUrlCount;
|
|
318
317
|
}
|
|
319
318
|
/**
|
|
320
319
|
* @inheritDoc
|
|
321
320
|
*/
|
|
322
321
|
async persistState() {
|
|
323
|
-
if (this
|
|
322
|
+
if (this.#persistStateKey === undefined) {
|
|
324
323
|
return;
|
|
325
324
|
}
|
|
326
|
-
this
|
|
325
|
+
this.#store ??= await KeyValueStore.open();
|
|
327
326
|
const urlQueue = [];
|
|
328
|
-
while (this
|
|
329
|
-
const url = this
|
|
327
|
+
while (this.#urlQueueStream.readableLength > 0) {
|
|
328
|
+
const url = this.#urlQueueStream.read();
|
|
330
329
|
if (url === null) {
|
|
331
330
|
break;
|
|
332
331
|
}
|
|
@@ -334,48 +333,55 @@ export class SitemapRequestLoader {
|
|
|
334
333
|
}
|
|
335
334
|
// Create a new stream, as we have read all the URLs from the current one.
|
|
336
335
|
// Pushing the urls back to the original stream might not be possible if it has been ended.
|
|
337
|
-
const
|
|
336
|
+
const previousStream = this.#urlQueueStream;
|
|
337
|
+
const newStream = this.createNewStream(previousStream.readableHighWaterMark);
|
|
338
338
|
for (const url of urlQueue) {
|
|
339
339
|
newStream.push(url);
|
|
340
340
|
}
|
|
341
|
-
if (
|
|
341
|
+
if (previousStream.writableEnded) {
|
|
342
342
|
newStream.end();
|
|
343
343
|
}
|
|
344
|
-
this
|
|
345
|
-
|
|
344
|
+
this.#urlQueueStream = newStream;
|
|
345
|
+
// A `pushNextUrl()` call may be blocked on backpressure, waiting for a `readdata` event on the
|
|
346
|
+
// previous stream. That event is only ever emitted by `readNextUrl()` on the current stream, so
|
|
347
|
+
// after the swap the waiter would never be notified and the background sitemap loading would hang.
|
|
348
|
+
// Re-emit `readdata` on the previous stream to release any such pending waiter (its URL has already
|
|
349
|
+
// been transferred to the new stream above).
|
|
350
|
+
previousStream.emit('readdata');
|
|
351
|
+
await this.#store.setValue(this.#persistStateKey, {
|
|
346
352
|
sitemapParsingProgress: {
|
|
347
|
-
pendingSitemapUrls: Array.from(this
|
|
348
|
-
inProgressSitemapUrl: this
|
|
349
|
-
inProgressEntries: Array.from(this
|
|
353
|
+
pendingSitemapUrls: Array.from(this.#sitemapParsingProgress.pendingSitemapUrls),
|
|
354
|
+
inProgressSitemapUrl: this.#sitemapParsingProgress.inProgressSitemapUrl,
|
|
355
|
+
inProgressEntries: Array.from(this.#sitemapParsingProgress.inProgressEntries),
|
|
350
356
|
},
|
|
351
357
|
// Re-queue in-progress requests to the front so they are retried if the state is restored.
|
|
352
358
|
urlQueue: [...this.inProgress, ...urlQueue],
|
|
353
|
-
requestData: Array.from(this
|
|
354
|
-
abortLoading: this
|
|
355
|
-
closed: this
|
|
359
|
+
requestData: Array.from(this.#requestData.entries()),
|
|
360
|
+
abortLoading: this.#abortLoading,
|
|
361
|
+
closed: this.#closed,
|
|
356
362
|
});
|
|
357
363
|
}
|
|
358
364
|
async restoreState() {
|
|
359
365
|
await purgeDefaultStorages({ onlyPurgeOnce: true });
|
|
360
|
-
if (this
|
|
366
|
+
if (this.#persistStateKey === undefined) {
|
|
361
367
|
return;
|
|
362
368
|
}
|
|
363
|
-
this
|
|
364
|
-
const state = await this
|
|
369
|
+
this.#store ??= await KeyValueStore.open();
|
|
370
|
+
const state = await this.#store.getValue(this.#persistStateKey);
|
|
365
371
|
if (state === null) {
|
|
366
372
|
return;
|
|
367
373
|
}
|
|
368
|
-
this
|
|
374
|
+
this.#sitemapParsingProgress = {
|
|
369
375
|
pendingSitemapUrls: new Set(state.sitemapParsingProgress.pendingSitemapUrls),
|
|
370
376
|
inProgressSitemapUrl: state.sitemapParsingProgress.inProgressSitemapUrl,
|
|
371
377
|
inProgressEntries: new Set(state.sitemapParsingProgress.inProgressEntries),
|
|
372
378
|
};
|
|
373
|
-
this
|
|
379
|
+
this.#requestData = new Map(state.requestData ?? []);
|
|
374
380
|
for (const url of state.urlQueue) {
|
|
375
|
-
this
|
|
381
|
+
this.#urlQueueStream.push(url);
|
|
376
382
|
}
|
|
377
|
-
this
|
|
378
|
-
this
|
|
383
|
+
this.#abortLoading = state.abortLoading;
|
|
384
|
+
this.#closed = state.closed;
|
|
379
385
|
}
|
|
380
386
|
/**
|
|
381
387
|
* @inheritDoc
|
|
@@ -386,11 +392,11 @@ export class SitemapRequestLoader {
|
|
|
386
392
|
return null;
|
|
387
393
|
}
|
|
388
394
|
// A restored in-progress request already has its Request data; don't overwrite it.
|
|
389
|
-
if (!this
|
|
390
|
-
this
|
|
395
|
+
if (!this.#requestData.has(nextUrl)) {
|
|
396
|
+
this.#requestData.set(nextUrl, new Request({ url: nextUrl, enqueueStrategy: this.#enqueueStrategy }));
|
|
391
397
|
}
|
|
392
398
|
this.inProgress.add(nextUrl);
|
|
393
|
-
return this
|
|
399
|
+
return this.#requestData.get(nextUrl);
|
|
394
400
|
}
|
|
395
401
|
/**
|
|
396
402
|
* @inheritDoc
|
|
@@ -409,20 +415,20 @@ export class SitemapRequestLoader {
|
|
|
409
415
|
* Calling `fetchNextRequest()` after this method will always return `null`.
|
|
410
416
|
*/
|
|
411
417
|
async teardown() {
|
|
412
|
-
this
|
|
413
|
-
this
|
|
414
|
-
this
|
|
418
|
+
this.#closed = true;
|
|
419
|
+
this.#abortLoading = true;
|
|
420
|
+
this.#events.off(EventType.PERSIST_STATE, this.persistState);
|
|
415
421
|
await this.persistState();
|
|
416
|
-
this
|
|
422
|
+
this.#urlQueueStream.emit('readdata'); // unblocks the potentially waiting `pushNextUrl` call
|
|
417
423
|
}
|
|
418
424
|
/**
|
|
419
425
|
* @inheritDoc
|
|
420
426
|
*/
|
|
421
427
|
async markRequestAsHandled(request) {
|
|
422
|
-
this
|
|
428
|
+
this.#handledUrlCount += 1;
|
|
423
429
|
this.ensureInProgress(request.url);
|
|
424
430
|
this.inProgress.delete(request.url);
|
|
425
|
-
this
|
|
431
|
+
this.#requestData.delete(request.url);
|
|
426
432
|
}
|
|
427
433
|
ensureInProgress(url) {
|
|
428
434
|
if (!this.inProgress.has(url)) {
|