@crawlee/core 4.0.0-beta.126 → 4.0.0-beta.128
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/enqueue_links/enqueue_links.d.ts +2 -51
- package/enqueue_links/enqueue_links.js +2 -52
- package/package.json +6 -6
- package/session_pool/session_pool.d.ts +4 -1
- package/session_pool/session_pool.js +5 -2
- package/storages/sitemap_request_loader.d.ts +8 -1
- package/storages/sitemap_request_loader.js +19 -5
- package/storages/utils.js +11 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Dictionary } from '@crawlee/types';
|
|
2
|
+
import { EnqueueStrategy } from '@crawlee/utils';
|
|
2
3
|
import type { RequestQueueOperationOptions } from '../storages/request_queue.js';
|
|
3
4
|
import type { RequestTransform, SkippedRequestCallback, UrlPatternInput, UrlPatternObject } from './shared.js';
|
|
4
5
|
/**
|
|
@@ -127,57 +128,7 @@ export interface EnqueueUrlsOptions extends RequestQueueOperationOptions {
|
|
|
127
128
|
}
|
|
128
129
|
/** The combined options accepted by a crawler context's `enqueueLinks()` helper: `extractLinks()` + `enqueueUrls()`. */
|
|
129
130
|
export type EnqueueLinksOptions = ExtractLinksOptions & EnqueueUrlsOptions;
|
|
130
|
-
|
|
131
|
-
* The different enqueueing strategies available.
|
|
132
|
-
*
|
|
133
|
-
* Depending on the strategy you select, we will only check certain parts of the URLs found. Here is a diagram of each URL part and their name:
|
|
134
|
-
*
|
|
135
|
-
* ```md
|
|
136
|
-
* Protocol Domain
|
|
137
|
-
* ┌────┐ ┌─────────┐
|
|
138
|
-
* https://example.crawlee.dev/...
|
|
139
|
-
* │ └─────────────────┤
|
|
140
|
-
* │ Hostname │
|
|
141
|
-
* │ │
|
|
142
|
-
* └─────────────────────────┘
|
|
143
|
-
* Origin
|
|
144
|
-
*```
|
|
145
|
-
*
|
|
146
|
-
* - The `Protocol` is usually `http` or `https`
|
|
147
|
-
* - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
|
|
148
|
-
* - The `Hostname` is the full path to a website, including any subdomains. For example, `example.crawlee.dev` is the hostname of `https://example.crawlee.dev/`
|
|
149
|
-
* - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
|
|
150
|
-
*/
|
|
151
|
-
export declare enum EnqueueStrategy {
|
|
152
|
-
/**
|
|
153
|
-
* Matches any URLs found
|
|
154
|
-
*/
|
|
155
|
-
All = "all",
|
|
156
|
-
/**
|
|
157
|
-
* Matches any URLs that have the same hostname.
|
|
158
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
159
|
-
* `https://example.com/hello` will not be matched.
|
|
160
|
-
*
|
|
161
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
162
|
-
*/
|
|
163
|
-
SameHostname = "same-hostname",
|
|
164
|
-
/**
|
|
165
|
-
* Matches any URLs that have the same domain as the base URL.
|
|
166
|
-
* For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
|
|
167
|
-
* `https://example.com`.
|
|
168
|
-
*
|
|
169
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
170
|
-
*/
|
|
171
|
-
SameDomain = "same-domain",
|
|
172
|
-
/**
|
|
173
|
-
* Matches any URLs that have the same hostname and protocol.
|
|
174
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
175
|
-
* `http://wow.example.com/hello` will not be matched.
|
|
176
|
-
*
|
|
177
|
-
* > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
|
|
178
|
-
*/
|
|
179
|
-
SameOrigin = "same-origin"
|
|
180
|
-
}
|
|
131
|
+
export { EnqueueStrategy };
|
|
181
132
|
/** The `strategy` option accepted by {@link ExtractLinksOptions} and {@link EnqueueUrlsOptions}. */
|
|
182
133
|
export type EnqueueStrategyOption = EnqueueStrategy | 'all' | 'same-domain' | 'same-hostname' | 'same-origin';
|
|
183
134
|
/**
|
|
@@ -1,56 +1,6 @@
|
|
|
1
|
+
import { EnqueueStrategy } from '@crawlee/utils';
|
|
1
2
|
import { getDomain } from 'tldts';
|
|
2
|
-
|
|
3
|
-
* The different enqueueing strategies available.
|
|
4
|
-
*
|
|
5
|
-
* Depending on the strategy you select, we will only check certain parts of the URLs found. Here is a diagram of each URL part and their name:
|
|
6
|
-
*
|
|
7
|
-
* ```md
|
|
8
|
-
* Protocol Domain
|
|
9
|
-
* ┌────┐ ┌─────────┐
|
|
10
|
-
* https://example.crawlee.dev/...
|
|
11
|
-
* │ └─────────────────┤
|
|
12
|
-
* │ Hostname │
|
|
13
|
-
* │ │
|
|
14
|
-
* └─────────────────────────┘
|
|
15
|
-
* Origin
|
|
16
|
-
*```
|
|
17
|
-
*
|
|
18
|
-
* - The `Protocol` is usually `http` or `https`
|
|
19
|
-
* - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
|
|
20
|
-
* - The `Hostname` is the full path to a website, including any subdomains. For example, `example.crawlee.dev` is the hostname of `https://example.crawlee.dev/`
|
|
21
|
-
* - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
|
|
22
|
-
*/
|
|
23
|
-
export var EnqueueStrategy;
|
|
24
|
-
(function (EnqueueStrategy) {
|
|
25
|
-
/**
|
|
26
|
-
* Matches any URLs found
|
|
27
|
-
*/
|
|
28
|
-
EnqueueStrategy["All"] = "all";
|
|
29
|
-
/**
|
|
30
|
-
* Matches any URLs that have the same hostname.
|
|
31
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
32
|
-
* `https://example.com/hello` will not be matched.
|
|
33
|
-
*
|
|
34
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
35
|
-
*/
|
|
36
|
-
EnqueueStrategy["SameHostname"] = "same-hostname";
|
|
37
|
-
/**
|
|
38
|
-
* Matches any URLs that have the same domain as the base URL.
|
|
39
|
-
* For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
|
|
40
|
-
* `https://example.com`.
|
|
41
|
-
*
|
|
42
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
43
|
-
*/
|
|
44
|
-
EnqueueStrategy["SameDomain"] = "same-domain";
|
|
45
|
-
/**
|
|
46
|
-
* Matches any URLs that have the same hostname and protocol.
|
|
47
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
48
|
-
* `http://wow.example.com/hello` will not be matched.
|
|
49
|
-
*
|
|
50
|
-
* > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
|
|
51
|
-
*/
|
|
52
|
-
EnqueueStrategy["SameOrigin"] = "same-origin";
|
|
53
|
-
})(EnqueueStrategy || (EnqueueStrategy = {}));
|
|
3
|
+
export { EnqueueStrategy };
|
|
54
4
|
/**
|
|
55
5
|
* @internal
|
|
56
6
|
* This method helps resolve the baseUrl that will be used for filtering in {@link enqueueLinks}.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/core",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.128",
|
|
4
4
|
"description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"@apify/log": "^2.5.18",
|
|
53
53
|
"@apify/timeout": "^0.4.4",
|
|
54
54
|
"@apify/utilities": "^2.15.5",
|
|
55
|
-
"@crawlee/fs-storage": "4.0.0-beta.
|
|
56
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
57
|
-
"@crawlee/types": "4.0.0-beta.
|
|
58
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
55
|
+
"@crawlee/fs-storage": "4.0.0-beta.128",
|
|
56
|
+
"@crawlee/http-client": "4.0.0-beta.128",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.128",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.128",
|
|
59
59
|
"@sapphire/async-queue": "^1.5.5",
|
|
60
60
|
"@standard-schema/spec": "^1.0.0",
|
|
61
61
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "2c017f04d564e8fa13855bf19f31f43cb65f4f44"
|
|
82
82
|
}
|
|
@@ -181,8 +181,11 @@ export declare class SessionPool implements ISessionPool {
|
|
|
181
181
|
/**
|
|
182
182
|
* Removes listener from `persistState` event.
|
|
183
183
|
* This function should be called after you are done with using the `SessionPool` instance.
|
|
184
|
+
* @param options - Set `persistState` to false when the final state was already persisted by the event manager.
|
|
184
185
|
*/
|
|
185
|
-
teardown(
|
|
186
|
+
teardown({ persistState }?: {
|
|
187
|
+
persistState?: boolean;
|
|
188
|
+
}): Promise<void>;
|
|
186
189
|
/**
|
|
187
190
|
* Removes retired `Session` instances from `SessionPool`.
|
|
188
191
|
*/
|
|
@@ -264,15 +264,18 @@ export class SessionPool {
|
|
|
264
264
|
/**
|
|
265
265
|
* Removes listener from `persistState` event.
|
|
266
266
|
* This function should be called after you are done with using the `SessionPool` instance.
|
|
267
|
+
* @param options - Set `persistState` to false when the final state was already persisted by the event manager.
|
|
267
268
|
*/
|
|
268
|
-
async teardown() {
|
|
269
|
+
async teardown({ persistState = true } = {}) {
|
|
269
270
|
if (!this.#initPromise)
|
|
270
271
|
return;
|
|
271
272
|
await this.ensureInitialized();
|
|
272
273
|
if (this.#listener) {
|
|
273
274
|
this.#events.off(EventType.PERSIST_STATE, this.#listener);
|
|
274
275
|
}
|
|
275
|
-
|
|
276
|
+
if (persistState) {
|
|
277
|
+
await this.persistState();
|
|
278
|
+
}
|
|
276
279
|
}
|
|
277
280
|
/**
|
|
278
281
|
* Removes retired `Session` instances from `SessionPool`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BaseHttpClient } from '@crawlee/http-client';
|
|
2
|
-
import { type ParseSitemapOptions } from '@crawlee/utils';
|
|
2
|
+
import { EnqueueStrategy, type ParseSitemapOptions } from '@crawlee/utils';
|
|
3
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';
|
|
@@ -65,6 +65,13 @@ export interface SitemapRequestLoaderOptions extends UrlConstraints {
|
|
|
65
65
|
* @default 200
|
|
66
66
|
*/
|
|
67
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}`;
|
|
68
75
|
/**
|
|
69
76
|
* Advanced options for the underlying `parseSitemap` call.
|
|
70
77
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
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
4
|
import { z } from 'zod';
|
|
5
5
|
import { constructUrlPatternObjects, urlPatternSchema } from '../enqueue_links/shared.js';
|
|
@@ -16,6 +16,7 @@ const sitemapRequestLoaderOptionsSchema = z.strictObject({
|
|
|
16
16
|
signal: z.unknown().optional(),
|
|
17
17
|
timeoutMillis: schemas.anyNumber.optional(),
|
|
18
18
|
maxBufferSize: schemas.anyNumber.default(200),
|
|
19
|
+
enqueueStrategy: z.enum(EnqueueStrategy).default(EnqueueStrategy.SameHostname),
|
|
19
20
|
parseSitemapOptions: z.looseObject({}).optional(),
|
|
20
21
|
include: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
21
22
|
exclude: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
@@ -83,6 +84,10 @@ export class SitemapRequestLoader {
|
|
|
83
84
|
* Proxy URL to be used for sitemap loading.
|
|
84
85
|
*/
|
|
85
86
|
#proxyUrl;
|
|
87
|
+
/**
|
|
88
|
+
* Enqueue strategy applied to sitemap-derived URLs and stamped onto the emitted `Request` objects.
|
|
89
|
+
*/
|
|
90
|
+
#enqueueStrategy;
|
|
86
91
|
/**
|
|
87
92
|
* Logger instance.
|
|
88
93
|
*/
|
|
@@ -94,7 +99,7 @@ export class SitemapRequestLoader {
|
|
|
94
99
|
#persistenceOptions;
|
|
95
100
|
/** @internal */
|
|
96
101
|
constructor(options) {
|
|
97
|
-
const { include, exclude, persistStateKey, persistenceOptions, proxyUrl, maxBufferSize, sitemapUrls } = parseArgument(options, sitemapRequestLoaderOptionsSchema, 'SitemapRequestLoaderOptions');
|
|
102
|
+
const { include, exclude, persistStateKey, persistenceOptions, proxyUrl, maxBufferSize, sitemapUrls, enqueueStrategy, } = parseArgument(options, sitemapRequestLoaderOptionsSchema, 'SitemapRequestLoaderOptions');
|
|
98
103
|
this.#log = serviceLocator.getLogger().child({ prefix: 'SitemapRequestLoader' });
|
|
99
104
|
if (exclude?.length) {
|
|
100
105
|
this.#urlExcludePatternObjects.push(...constructUrlPatternObjects(exclude));
|
|
@@ -105,6 +110,7 @@ export class SitemapRequestLoader {
|
|
|
105
110
|
this.#persistStateKey = persistStateKey;
|
|
106
111
|
this.#persistenceOptions = { enable: true, ...persistenceOptions };
|
|
107
112
|
this.#proxyUrl = proxyUrl;
|
|
113
|
+
this.#enqueueStrategy = enqueueStrategy;
|
|
108
114
|
this.#urlQueueStream = this.createNewStream(maxBufferSize);
|
|
109
115
|
this.#sitemapParsingProgress.pendingSitemapUrls = new Set(sitemapUrls);
|
|
110
116
|
this.#events = serviceLocator.getEventManager();
|
|
@@ -213,6 +219,7 @@ export class SitemapRequestLoader {
|
|
|
213
219
|
...parseSitemapOptions,
|
|
214
220
|
maxDepth: 0,
|
|
215
221
|
emitNestedSitemaps: true,
|
|
222
|
+
enqueueStrategy: this.#enqueueStrategy,
|
|
216
223
|
})) {
|
|
217
224
|
if (!item.originSitemapUrl) {
|
|
218
225
|
// This is a nested sitemap
|
|
@@ -326,14 +333,21 @@ export class SitemapRequestLoader {
|
|
|
326
333
|
}
|
|
327
334
|
// Create a new stream, as we have read all the URLs from the current one.
|
|
328
335
|
// Pushing the urls back to the original stream might not be possible if it has been ended.
|
|
329
|
-
const
|
|
336
|
+
const previousStream = this.#urlQueueStream;
|
|
337
|
+
const newStream = this.createNewStream(previousStream.readableHighWaterMark);
|
|
330
338
|
for (const url of urlQueue) {
|
|
331
339
|
newStream.push(url);
|
|
332
340
|
}
|
|
333
|
-
if (
|
|
341
|
+
if (previousStream.writableEnded) {
|
|
334
342
|
newStream.end();
|
|
335
343
|
}
|
|
336
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');
|
|
337
351
|
await this.#store.setValue(this.#persistStateKey, {
|
|
338
352
|
sitemapParsingProgress: {
|
|
339
353
|
pendingSitemapUrls: Array.from(this.#sitemapParsingProgress.pendingSitemapUrls),
|
|
@@ -379,7 +393,7 @@ export class SitemapRequestLoader {
|
|
|
379
393
|
}
|
|
380
394
|
// A restored in-progress request already has its Request data; don't overwrite it.
|
|
381
395
|
if (!this.#requestData.has(nextUrl)) {
|
|
382
|
-
this.#requestData.set(nextUrl, new Request({ url: nextUrl }));
|
|
396
|
+
this.#requestData.set(nextUrl, new Request({ url: nextUrl, enqueueStrategy: this.#enqueueStrategy }));
|
|
383
397
|
}
|
|
384
398
|
this.inProgress.add(nextUrl);
|
|
385
399
|
return this.#requestData.get(nextUrl);
|
package/storages/utils.js
CHANGED
|
@@ -12,11 +12,20 @@ export async function purgeDefaultStorages(configurationOrOptions, storageBacken
|
|
|
12
12
|
const { configuration = serviceLocator.getConfiguration(), onlyPurgeOnce = false } = options;
|
|
13
13
|
({ storageBackend = serviceLocator.getStorageBackend() } = options);
|
|
14
14
|
const casted = storageBackend;
|
|
15
|
+
const runPurge = async () => {
|
|
16
|
+
try {
|
|
17
|
+
await casted.purge?.();
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
casted.__purged = undefined;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
15
24
|
// if `onlyPurgeOnce` is true, will purge anytime this function is called, otherwise - only on start
|
|
16
25
|
if (!onlyPurgeOnce || (configuration.purgeOnStart && !casted.__purged)) {
|
|
17
|
-
casted.__purged =
|
|
18
|
-
await casted.purge?.();
|
|
26
|
+
casted.__purged = runPurge();
|
|
19
27
|
}
|
|
28
|
+
await casted.__purged;
|
|
20
29
|
}
|
|
21
30
|
/**
|
|
22
31
|
* Easily create and manage state values. All state values are automatically persisted.
|