@crawlee/core 3.17.1-beta.9 → 3.18.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.js +4 -1
- package/autoscaling/snapshotter.d.ts +2 -2
- package/cookie_utils.js +2 -0
- package/crawlers/statistics.d.ts +11 -2
- package/crawlers/statistics.js +7 -4
- package/enqueue_links/enqueue_links.d.ts +11 -54
- package/enqueue_links/enqueue_links.js +13 -63
- package/enqueue_links/shared.d.ts +1 -1
- package/errors.d.ts +20 -0
- package/errors.js +32 -1
- package/http_clients/got-scraping-http-client.js +8 -0
- package/index.mjs +3 -0
- package/package.json +7 -6
- package/router.d.ts +133 -14
- package/router.js +136 -34
- package/session_pool/session_pool.d.ts +4 -1
- package/session_pool/session_pool.js +5 -2
- package/storages/request_dedup_cache.d.ts +23 -0
- package/storages/request_dedup_cache.js +66 -0
- package/storages/request_list.d.ts +6 -0
- package/storages/request_list.js +16 -2
- package/storages/request_provider.d.ts +7 -0
- package/storages/request_provider.js +39 -9
- package/storages/sitemap_request_list.d.ts +12 -0
- package/storages/sitemap_request_list.js +22 -3
|
@@ -415,14 +415,17 @@ class AutoscaledPool {
|
|
|
415
415
|
this.isStopped = true;
|
|
416
416
|
await new Promise((resolve, reject) => {
|
|
417
417
|
let timeout;
|
|
418
|
+
let interval;
|
|
418
419
|
if (timeoutSecs) {
|
|
419
420
|
timeout = setTimeout(() => {
|
|
421
|
+
// Clean up the polling interval to prevent it from leaking on timeout.
|
|
422
|
+
clearInterval(interval);
|
|
420
423
|
const err = new Error("The pool's running tasks did not finish" +
|
|
421
424
|
`in ${timeoutSecs} secs after pool.pause() invocation.`);
|
|
422
425
|
reject(err);
|
|
423
426
|
}, timeoutSecs);
|
|
424
427
|
}
|
|
425
|
-
|
|
428
|
+
interval = setInterval(() => {
|
|
426
429
|
if (this._currentConcurrency <= 0) {
|
|
427
430
|
// Clean up timeout and interval to prevent process hanging.
|
|
428
431
|
if (timeout)
|
|
@@ -35,13 +35,13 @@ export interface SnapshotterOptions {
|
|
|
35
35
|
/**
|
|
36
36
|
* Defines the maximum number of new rate limit errors within
|
|
37
37
|
* the given interval.
|
|
38
|
-
* @default
|
|
38
|
+
* @default 3
|
|
39
39
|
*/
|
|
40
40
|
maxClientErrors?: number;
|
|
41
41
|
/**
|
|
42
42
|
* Sets the interval in seconds for which a history of resource snapshots
|
|
43
43
|
* will be kept. Increasing this to very high numbers will affect performance.
|
|
44
|
-
* @default
|
|
44
|
+
* @default 30
|
|
45
45
|
*/
|
|
46
46
|
snapshotHistorySecs?: number;
|
|
47
47
|
/** @internal */
|
package/cookie_utils.js
CHANGED
|
@@ -107,6 +107,8 @@ function mergeCookies(url, sourceCookies) {
|
|
|
107
107
|
if (!cookieString)
|
|
108
108
|
continue;
|
|
109
109
|
const cookie = tough_cookie_1.Cookie.parse(cookieString);
|
|
110
|
+
if (!cookie)
|
|
111
|
+
throw new errors_1.CookieParseError(cookieString);
|
|
110
112
|
const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
|
|
111
113
|
return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
|
|
112
114
|
});
|
package/crawlers/statistics.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare class Statistics {
|
|
|
35
35
|
/**
|
|
36
36
|
* Statistic instance id.
|
|
37
37
|
*/
|
|
38
|
-
readonly id:
|
|
38
|
+
readonly id: string;
|
|
39
39
|
/**
|
|
40
40
|
* Current statistic state used for doing calculations on {@link Statistics.calculate} calls
|
|
41
41
|
*/
|
|
@@ -166,13 +166,22 @@ export interface StatisticsOptions {
|
|
|
166
166
|
* @default false
|
|
167
167
|
*/
|
|
168
168
|
saveErrorSnapshots?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* A unique identifier for this statistics instance. This ID is used for persistence
|
|
171
|
+
* to the key value store, ensuring the same statistics can be loaded after script restarts.
|
|
172
|
+
*
|
|
173
|
+
* If not provided, an auto-incremented ID will be used for backward compatibility.
|
|
174
|
+
* This means statistics may not persist correctly across script restarts
|
|
175
|
+
* if crawler creation order changes.
|
|
176
|
+
*/
|
|
177
|
+
id?: string;
|
|
169
178
|
}
|
|
170
179
|
/**
|
|
171
180
|
* Format of the persisted stats
|
|
172
181
|
*/
|
|
173
182
|
export interface StatisticPersistedState extends Omit<StatisticState, 'statsPersistedAt'> {
|
|
174
183
|
requestRetryHistogram: number[];
|
|
175
|
-
statsId:
|
|
184
|
+
statsId: string;
|
|
176
185
|
requestAvgFailedDurationMillis: number;
|
|
177
186
|
requestAvgFinishedDurationMillis: number;
|
|
178
187
|
requestTotalDurationMillis: number;
|
package/crawlers/statistics.js
CHANGED
|
@@ -81,8 +81,8 @@ class Statistics {
|
|
|
81
81
|
enumerable: true,
|
|
82
82
|
configurable: true,
|
|
83
83
|
writable: true,
|
|
84
|
-
value:
|
|
85
|
-
});
|
|
84
|
+
value: void 0
|
|
85
|
+
});
|
|
86
86
|
/**
|
|
87
87
|
* Current statistic state used for doing calculations on {@link Statistics.calculate} calls
|
|
88
88
|
*/
|
|
@@ -120,7 +120,7 @@ class Statistics {
|
|
|
120
120
|
enumerable: true,
|
|
121
121
|
configurable: true,
|
|
122
122
|
writable: true,
|
|
123
|
-
value:
|
|
123
|
+
value: void 0
|
|
124
124
|
});
|
|
125
125
|
Object.defineProperty(this, "logIntervalMillis", {
|
|
126
126
|
enumerable: true,
|
|
@@ -184,10 +184,13 @@ class Statistics {
|
|
|
184
184
|
config: ow_1.default.optional.object,
|
|
185
185
|
persistenceOptions: ow_1.default.optional.object,
|
|
186
186
|
saveErrorSnapshots: ow_1.default.optional.boolean,
|
|
187
|
+
id: ow_1.default.optional.any(ow_1.default.number, ow_1.default.string),
|
|
187
188
|
}));
|
|
188
189
|
const { logIntervalSecs = 60, logMessage = 'Statistics', keyValueStore, config = configuration_1.Configuration.getGlobalConfig(), persistenceOptions = {
|
|
189
190
|
enable: true,
|
|
190
|
-
}, saveErrorSnapshots = false, } = options;
|
|
191
|
+
}, saveErrorSnapshots = false, id, } = options;
|
|
192
|
+
this.id = id ?? String(Statistics.id++);
|
|
193
|
+
this.persistStateKey = `SDK_CRAWLER_STATISTICS_${this.id}`;
|
|
191
194
|
this.log = (options.log ?? log_1.log).child({ prefix: 'Statistics' });
|
|
192
195
|
this.errorTracker = new error_tracker_1.ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
|
|
193
196
|
this.errorTrackerRetry = new error_tracker_1.ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
2
|
-
import { type RobotsTxtFile } from '@crawlee/utils';
|
|
2
|
+
import { EnqueueStrategy, type RobotsTxtFile } from '@crawlee/utils';
|
|
3
3
|
import type { SetRequired } from 'type-fest';
|
|
4
4
|
import type { Request } from '../request';
|
|
5
5
|
import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestProvider, RequestQueueOperationOptions } from '../storages';
|
|
6
6
|
import type { GlobInput, PseudoUrlInput, RegExpInput, RequestTransform, SkippedRequestCallback } from './shared';
|
|
7
|
+
export { EnqueueStrategy };
|
|
7
8
|
export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Limit the amount of actually enqueued URLs to this number. Useful for testing across the entire crawling scope.
|
|
11
|
+
* When called from a crawler context, the limit is further capped by what's left of the crawler's
|
|
12
|
+
* {@link BasicCrawlerOptions.maxRequestsPerCrawl|`maxRequestsPerCrawl`} budget.
|
|
13
|
+
*/
|
|
9
14
|
limit?: number;
|
|
10
15
|
/** An array of URLs to enqueue. */
|
|
11
16
|
urls?: readonly string[];
|
|
@@ -153,59 +158,11 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
|
|
|
153
158
|
* 1. based on robots.txt file,
|
|
154
159
|
* 2. because they don't match enqueueLinks filters,
|
|
155
160
|
* 3. or because the maxRequestsPerCrawl limit has been reached
|
|
156
|
-
*/
|
|
157
|
-
onSkippedRequest?: SkippedRequestCallback;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* The different enqueueing strategies available.
|
|
161
|
-
*
|
|
162
|
-
* 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:
|
|
163
|
-
*
|
|
164
|
-
* ```md
|
|
165
|
-
* Protocol Domain
|
|
166
|
-
* ┌────┐ ┌─────────┐
|
|
167
|
-
* https://example.crawlee.dev/...
|
|
168
|
-
* │ └─────────────────┤
|
|
169
|
-
* │ Hostname │
|
|
170
|
-
* │ │
|
|
171
|
-
* └─────────────────────────┘
|
|
172
|
-
* Origin
|
|
173
|
-
*```
|
|
174
|
-
*
|
|
175
|
-
* - The `Protocol` is usually `http` or `https`
|
|
176
|
-
* - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
|
|
177
|
-
* - 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/`
|
|
178
|
-
* - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
|
|
179
|
-
*/
|
|
180
|
-
export declare enum EnqueueStrategy {
|
|
181
|
-
/**
|
|
182
|
-
* Matches any URLs found
|
|
183
|
-
*/
|
|
184
|
-
All = "all",
|
|
185
|
-
/**
|
|
186
|
-
* Matches any URLs that have the same hostname.
|
|
187
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
188
|
-
* `https://example.com/hello` will not be matched.
|
|
189
|
-
*
|
|
190
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
191
|
-
*/
|
|
192
|
-
SameHostname = "same-hostname",
|
|
193
|
-
/**
|
|
194
|
-
* Matches any URLs that have the same domain as the base URL.
|
|
195
|
-
* For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
|
|
196
|
-
* `https://example.com`.
|
|
197
161
|
*
|
|
198
|
-
*
|
|
162
|
+
* When calling `enqueueLinks` through a crawler context, this callback runs in addition to (after) the
|
|
163
|
+
* crawler-level `onSkippedRequest`, it does not replace it.
|
|
199
164
|
*/
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Matches any URLs that have the same hostname and protocol.
|
|
203
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
204
|
-
* `http://wow.example.com/hello` will not be matched.
|
|
205
|
-
*
|
|
206
|
-
* > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
|
|
207
|
-
*/
|
|
208
|
-
SameOrigin = "same-origin"
|
|
165
|
+
onSkippedRequest?: SkippedRequestCallback;
|
|
209
166
|
}
|
|
210
167
|
/**
|
|
211
168
|
* This function enqueues the urls provided to the {@link RequestQueue} provided. If you want to automatically find and enqueue links,
|
|
@@ -244,7 +201,7 @@ export declare function enqueueLinks(options: SetRequired<Omit<EnqueueLinksOptio
|
|
|
244
201
|
* request domain, or a redirected one
|
|
245
202
|
* - In all other cases, we return the domain of the original request as that's the one we need to use for filtering
|
|
246
203
|
*/
|
|
247
|
-
export declare function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy, finalRequestUrl, originalRequestUrl, userProvidedBaseUrl, }: ResolveBaseUrl): string
|
|
204
|
+
export declare function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy, finalRequestUrl, originalRequestUrl, userProvidedBaseUrl, }: ResolveBaseUrl): string;
|
|
248
205
|
/**
|
|
249
206
|
* @internal
|
|
250
207
|
*/
|
|
@@ -4,62 +4,12 @@ exports.EnqueueStrategy = void 0;
|
|
|
4
4
|
exports.enqueueLinks = enqueueLinks;
|
|
5
5
|
exports.resolveBaseUrlForEnqueueLinksFiltering = resolveBaseUrlForEnqueueLinksFiltering;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const utils_1 = require("@crawlee/utils");
|
|
8
|
+
Object.defineProperty(exports, "EnqueueStrategy", { enumerable: true, get: function () { return utils_1.EnqueueStrategy; } });
|
|
7
9
|
const ow_1 = tslib_1.__importDefault(require("ow"));
|
|
8
10
|
const tldts_1 = require("tldts");
|
|
9
11
|
const log_1 = tslib_1.__importDefault(require("@apify/log"));
|
|
10
12
|
const shared_1 = require("./shared");
|
|
11
|
-
/**
|
|
12
|
-
* The different enqueueing strategies available.
|
|
13
|
-
*
|
|
14
|
-
* 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:
|
|
15
|
-
*
|
|
16
|
-
* ```md
|
|
17
|
-
* Protocol Domain
|
|
18
|
-
* ┌────┐ ┌─────────┐
|
|
19
|
-
* https://example.crawlee.dev/...
|
|
20
|
-
* │ └─────────────────┤
|
|
21
|
-
* │ Hostname │
|
|
22
|
-
* │ │
|
|
23
|
-
* └─────────────────────────┘
|
|
24
|
-
* Origin
|
|
25
|
-
*```
|
|
26
|
-
*
|
|
27
|
-
* - The `Protocol` is usually `http` or `https`
|
|
28
|
-
* - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
|
|
29
|
-
* - 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/`
|
|
30
|
-
* - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
|
|
31
|
-
*/
|
|
32
|
-
var EnqueueStrategy;
|
|
33
|
-
(function (EnqueueStrategy) {
|
|
34
|
-
/**
|
|
35
|
-
* Matches any URLs found
|
|
36
|
-
*/
|
|
37
|
-
EnqueueStrategy["All"] = "all";
|
|
38
|
-
/**
|
|
39
|
-
* Matches any URLs that have the same hostname.
|
|
40
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
41
|
-
* `https://example.com/hello` will not be matched.
|
|
42
|
-
*
|
|
43
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
44
|
-
*/
|
|
45
|
-
EnqueueStrategy["SameHostname"] = "same-hostname";
|
|
46
|
-
/**
|
|
47
|
-
* Matches any URLs that have the same domain as the base URL.
|
|
48
|
-
* For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
|
|
49
|
-
* `https://example.com`.
|
|
50
|
-
*
|
|
51
|
-
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
52
|
-
*/
|
|
53
|
-
EnqueueStrategy["SameDomain"] = "same-domain";
|
|
54
|
-
/**
|
|
55
|
-
* Matches any URLs that have the same hostname and protocol.
|
|
56
|
-
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
57
|
-
* `http://wow.example.com/hello` will not be matched.
|
|
58
|
-
*
|
|
59
|
-
* > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
|
|
60
|
-
*/
|
|
61
|
-
EnqueueStrategy["SameOrigin"] = "same-origin";
|
|
62
|
-
})(EnqueueStrategy || (exports.EnqueueStrategy = EnqueueStrategy = {}));
|
|
63
13
|
/**
|
|
64
14
|
* This function enqueues the urls provided to the {@link RequestQueue} provided. If you want to automatically find and enqueue links,
|
|
65
15
|
* you should use the context-aware `enqueueLinks` function provided on the crawler contexts.
|
|
@@ -109,7 +59,7 @@ async function enqueueLinks(options) {
|
|
|
109
59
|
exclude: ow_1.default.optional.array.ofType(ow_1.default.any(ow_1.default.string, ow_1.default.regExp, ow_1.default.object.hasKeys('glob'), ow_1.default.object.hasKeys('regexp'))),
|
|
110
60
|
regexps: ow_1.default.optional.array.ofType(ow_1.default.any(ow_1.default.regExp, ow_1.default.object.hasKeys('regexp'))),
|
|
111
61
|
transformRequestFunction: ow_1.default.optional.function,
|
|
112
|
-
strategy: ow_1.default.optional.string.oneOf(Object.values(EnqueueStrategy)),
|
|
62
|
+
strategy: ow_1.default.optional.string.oneOf(Object.values(utils_1.EnqueueStrategy)),
|
|
113
63
|
waitForAllRequestsToBeAdded: ow_1.default.optional.boolean,
|
|
114
64
|
}));
|
|
115
65
|
const { requestQueue, limit, urls, pseudoUrls, exclude, globs, regexps, transformRequestFunction, forefront, waitForAllRequestsToBeAdded, robotsTxtFile, onSkippedRequest, } = options;
|
|
@@ -136,19 +86,19 @@ async function enqueueLinks(options) {
|
|
|
136
86
|
urlPatternObjects.push(...(0, shared_1.constructRegExpObjectsFromRegExps)(regexps));
|
|
137
87
|
}
|
|
138
88
|
if (!urlPatternObjects.length) {
|
|
139
|
-
options.strategy ?? (options.strategy = EnqueueStrategy.SameHostname);
|
|
89
|
+
options.strategy ?? (options.strategy = utils_1.EnqueueStrategy.SameHostname);
|
|
140
90
|
}
|
|
141
91
|
const enqueueStrategyPatterns = [];
|
|
142
92
|
if (options.baseUrl) {
|
|
143
93
|
const url = new URL(options.baseUrl);
|
|
144
94
|
switch (options.strategy) {
|
|
145
|
-
case EnqueueStrategy.SameHostname:
|
|
95
|
+
case utils_1.EnqueueStrategy.SameHostname:
|
|
146
96
|
// We need to get the origin of the passed in domain in the event someone sets baseUrl
|
|
147
97
|
// to an url like https://example.com/deep/default/path and one of the found urls is an
|
|
148
98
|
// absolute relative path (/path/to/page)
|
|
149
99
|
enqueueStrategyPatterns.push({ glob: ignoreHttpSchema(`${url.origin}/**`) });
|
|
150
100
|
break;
|
|
151
|
-
case EnqueueStrategy.SameDomain: {
|
|
101
|
+
case utils_1.EnqueueStrategy.SameDomain: {
|
|
152
102
|
// Get the actual hostname from the base url
|
|
153
103
|
const baseUrlHostname = (0, tldts_1.getDomain)(url.hostname, { mixedInputs: false });
|
|
154
104
|
if (baseUrlHostname) {
|
|
@@ -163,12 +113,12 @@ async function enqueueLinks(options) {
|
|
|
163
113
|
}
|
|
164
114
|
break;
|
|
165
115
|
}
|
|
166
|
-
case EnqueueStrategy.SameOrigin: {
|
|
116
|
+
case utils_1.EnqueueStrategy.SameOrigin: {
|
|
167
117
|
// The same behavior as SameHostname, but respecting the protocol of the URL
|
|
168
118
|
enqueueStrategyPatterns.push({ glob: `${url.origin}/**` });
|
|
169
119
|
break;
|
|
170
120
|
}
|
|
171
|
-
case EnqueueStrategy.All:
|
|
121
|
+
case utils_1.EnqueueStrategy.All:
|
|
172
122
|
default:
|
|
173
123
|
enqueueStrategyPatterns.push({ glob: `http{s,}://**` });
|
|
174
124
|
break;
|
|
@@ -249,19 +199,19 @@ function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy, finalRequestU
|
|
|
249
199
|
const originalUrlOrigin = new URL(originalRequestUrl).origin;
|
|
250
200
|
const finalUrlOrigin = new URL(finalRequestUrl ?? originalRequestUrl).origin;
|
|
251
201
|
// We can assume users want to go off the domain in this case
|
|
252
|
-
if (enqueueStrategy === EnqueueStrategy.All) {
|
|
202
|
+
if (enqueueStrategy === utils_1.EnqueueStrategy.All) {
|
|
253
203
|
return finalUrlOrigin;
|
|
254
204
|
}
|
|
255
205
|
// If the user wants to ensure the same domain is accessed, regardless of subdomains, we check to ensure the domains match
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
if (enqueueStrategy === EnqueueStrategy.SameDomain) {
|
|
206
|
+
// If they don't (we went off domain via a redirect), we keep filtering against the original domain - returning
|
|
207
|
+
// undefined here would disable the filtering entirely and enqueue every link on the redirected page
|
|
208
|
+
if (enqueueStrategy === utils_1.EnqueueStrategy.SameDomain) {
|
|
259
209
|
const originalHostname = (0, tldts_1.getDomain)(originalUrlOrigin, { mixedInputs: false });
|
|
260
210
|
const finalHostname = (0, tldts_1.getDomain)(finalUrlOrigin, { mixedInputs: false });
|
|
261
211
|
if (originalHostname === finalHostname) {
|
|
262
212
|
return finalUrlOrigin;
|
|
263
213
|
}
|
|
264
|
-
return
|
|
214
|
+
return originalUrlOrigin;
|
|
265
215
|
}
|
|
266
216
|
// Always enqueue urls that are from the same origin in all other cases, as the filtering happens on the original request url, even if there was a redirect
|
|
267
217
|
// before actually finding the urls
|
|
@@ -66,7 +66,7 @@ export declare function createRequestOptions(sources: (string | Record<string, u
|
|
|
66
66
|
export interface RequestTransform {
|
|
67
67
|
/**
|
|
68
68
|
* @param original Request options to be modified.
|
|
69
|
-
* @returns The modified request options to enqueue.
|
|
69
|
+
* @returns The modified request options to enqueue, or any falsy value to skip the request.
|
|
70
70
|
*/
|
|
71
71
|
(original: RequestOptions): RequestOptions | false | undefined | null;
|
|
72
72
|
}
|
package/errors.d.ts
CHANGED
|
@@ -14,6 +14,26 @@ export declare class CriticalError extends NonRetryableError {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class MissingRouteError extends CriticalError {
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
19
|
+
*
|
|
20
|
+
* As the `userData` does not change between attempts, this error is non-retryable.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RequestValidationError extends NonRetryableError {
|
|
23
|
+
readonly label: string | symbol;
|
|
24
|
+
readonly issues: readonly {
|
|
25
|
+
readonly message: string;
|
|
26
|
+
readonly path?: readonly (PropertyKey | {
|
|
27
|
+
key: PropertyKey;
|
|
28
|
+
})[];
|
|
29
|
+
}[];
|
|
30
|
+
constructor(label: string | symbol, issues: readonly {
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly path?: readonly (PropertyKey | {
|
|
33
|
+
key: PropertyKey;
|
|
34
|
+
})[];
|
|
35
|
+
}[]);
|
|
36
|
+
}
|
|
17
37
|
/**
|
|
18
38
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
19
39
|
*
|
package/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SessionError = exports.RetryRequestError = exports.MissingRouteError = exports.CriticalError = exports.NonRetryableError = void 0;
|
|
3
|
+
exports.SessionError = exports.RetryRequestError = exports.RequestValidationError = exports.MissingRouteError = exports.CriticalError = exports.NonRetryableError = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Errors of `NonRetryableError` type will never be retried by the crawler.
|
|
6
6
|
*/
|
|
@@ -20,6 +20,37 @@ exports.CriticalError = CriticalError;
|
|
|
20
20
|
class MissingRouteError extends CriticalError {
|
|
21
21
|
}
|
|
22
22
|
exports.MissingRouteError = MissingRouteError;
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a request's `userData` does not match the {@link RouteSchemas|Standard Schema} registered for its label.
|
|
25
|
+
*
|
|
26
|
+
* As the `userData` does not change between attempts, this error is non-retryable.
|
|
27
|
+
*/
|
|
28
|
+
class RequestValidationError extends NonRetryableError {
|
|
29
|
+
constructor(label, issues) {
|
|
30
|
+
const details = issues
|
|
31
|
+
.map((issue) => {
|
|
32
|
+
const path = (issue.path ?? [])
|
|
33
|
+
.map((segment) => (typeof segment === 'object' ? segment.key : segment))
|
|
34
|
+
.join('.');
|
|
35
|
+
return `- ${path ? `${path}: ` : ''}${issue.message}`;
|
|
36
|
+
})
|
|
37
|
+
.join('\n');
|
|
38
|
+
super(`Request userData for label '${String(label)}' failed schema validation:\n${details}`);
|
|
39
|
+
Object.defineProperty(this, "label", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: label
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(this, "issues", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: issues
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.RequestValidationError = RequestValidationError;
|
|
23
54
|
/**
|
|
24
55
|
* Errors of `RetryRequestError` type will always be retried by the crawler.
|
|
25
56
|
*
|
|
@@ -22,6 +22,14 @@ class GotScrapingHttpClient {
|
|
|
22
22
|
});
|
|
23
23
|
return {
|
|
24
24
|
...gotResult,
|
|
25
|
+
complete: gotResult.complete,
|
|
26
|
+
headers: gotResult.headers,
|
|
27
|
+
ip: gotResult.ip,
|
|
28
|
+
redirectUrls: gotResult.redirectUrls,
|
|
29
|
+
statusCode: gotResult.statusCode,
|
|
30
|
+
statusMessage: gotResult.statusMessage,
|
|
31
|
+
trailers: gotResult.trailers,
|
|
32
|
+
url: gotResult.url,
|
|
25
33
|
body: gotResult.body,
|
|
26
34
|
request: { url: request.url, ...gotResult.request },
|
|
27
35
|
};
|
package/index.mjs
CHANGED
|
@@ -46,6 +46,7 @@ export const RequestQueue = mod.RequestQueue;
|
|
|
46
46
|
export const RequestQueueV1 = mod.RequestQueueV1;
|
|
47
47
|
export const RequestQueueV2 = mod.RequestQueueV2;
|
|
48
48
|
export const RequestState = mod.RequestState;
|
|
49
|
+
export const RequestValidationError = mod.RequestValidationError;
|
|
49
50
|
export const RetryRequestError = mod.RetryRequestError;
|
|
50
51
|
export const Router = mod.Router;
|
|
51
52
|
export const STATE_PERSISTENCE_KEY = mod.STATE_PERSISTENCE_KEY;
|
|
@@ -73,6 +74,7 @@ export const createDeserialize = mod.createDeserialize;
|
|
|
73
74
|
export const createEventLoopLoadSignal = mod.createEventLoopLoadSignal;
|
|
74
75
|
export const createRequestOptions = mod.createRequestOptions;
|
|
75
76
|
export const createRequests = mod.createRequests;
|
|
77
|
+
export const defaultRoute = mod.defaultRoute;
|
|
76
78
|
export const deserializeArray = mod.deserializeArray;
|
|
77
79
|
export const enqueueLinks = mod.enqueueLinks;
|
|
78
80
|
export const evaluateLoadSignalSample = mod.evaluateLoadSignalSample;
|
|
@@ -93,5 +95,6 @@ export const tryAbsoluteURL = mod.tryAbsoluteURL;
|
|
|
93
95
|
export const updateEnqueueLinksPatternCache = mod.updateEnqueueLinksPatternCache;
|
|
94
96
|
export const useState = mod.useState;
|
|
95
97
|
export const validateGlobPattern = mod.validateGlobPattern;
|
|
98
|
+
export const validateUserData = mod.validateUserData;
|
|
96
99
|
export const validators = mod.validators;
|
|
97
100
|
export const withCheckedStorageAccess = mod.withCheckedStorageAccess;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.0",
|
|
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": ">=16.0.0"
|
|
@@ -57,12 +57,13 @@
|
|
|
57
57
|
"@apify/datastructures": "^2.0.0",
|
|
58
58
|
"@apify/log": "^2.4.0",
|
|
59
59
|
"@apify/pseudo_url": "^2.0.30",
|
|
60
|
-
"@apify/timeout": "^0.
|
|
60
|
+
"@apify/timeout": "^0.4.0",
|
|
61
61
|
"@apify/utilities": "^2.7.10",
|
|
62
|
-
"@crawlee/memory-storage": "3.
|
|
63
|
-
"@crawlee/types": "3.
|
|
64
|
-
"@crawlee/utils": "3.
|
|
62
|
+
"@crawlee/memory-storage": "3.18.0",
|
|
63
|
+
"@crawlee/types": "3.18.0",
|
|
64
|
+
"@crawlee/utils": "3.18.0",
|
|
65
65
|
"@sapphire/async-queue": "^1.5.1",
|
|
66
|
+
"@standard-schema/spec": "^1.0.0",
|
|
66
67
|
"@vladfrangu/async_event_emitter": "^2.2.2",
|
|
67
68
|
"csv-stringify": "^6.2.0",
|
|
68
69
|
"fs-extra": "^11.0.0",
|
|
@@ -83,5 +84,5 @@
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
},
|
|
86
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "49c115e1ce3b3fbf2bef61f16bffeadfdcf5bd19"
|
|
87
88
|
}
|