@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.
Files changed (109) hide show
  1. package/autoscaling/autoscaled_pool.d.ts +3 -21
  2. package/autoscaling/autoscaled_pool.js +105 -97
  3. package/autoscaling/concurrency_system.d.ts +7 -22
  4. package/autoscaling/concurrency_system.js +112 -100
  5. package/autoscaling/cpu_load_signal.d.ts +1 -2
  6. package/autoscaling/cpu_load_signal.js +11 -10
  7. package/autoscaling/event_loop_load_signal.d.ts +1 -4
  8. package/autoscaling/event_loop_load_signal.js +18 -18
  9. package/autoscaling/index.d.ts +1 -1
  10. package/autoscaling/index.js +1 -1
  11. package/autoscaling/load_signal.d.ts +8 -7
  12. package/autoscaling/load_signal.js +14 -12
  13. package/autoscaling/memory_load_signal.d.ts +3 -12
  14. package/autoscaling/memory_load_signal.js +41 -41
  15. package/autoscaling/snapshotter.d.ts +7 -10
  16. package/autoscaling/snapshotter.js +18 -18
  17. package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
  18. package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
  19. package/autoscaling/system_status.d.ts +9 -11
  20. package/autoscaling/system_status.js +13 -13
  21. package/configuration.d.ts +16 -16
  22. package/configuration.js +6 -6
  23. package/crawlers/context_pipeline.js +6 -6
  24. package/crawlers/crawler_commons.d.ts +14 -112
  25. package/crawlers/crawler_commons.js +1 -107
  26. package/crawlers/error_snapshotter.d.ts +2 -4
  27. package/crawlers/error_snapshotter.js +5 -5
  28. package/crawlers/index.d.ts +1 -1
  29. package/crawlers/index.js +0 -1
  30. package/crawlers/statistics.d.ts +164 -45
  31. package/crawlers/statistics.js +339 -153
  32. package/debug.js +4 -4
  33. package/enqueue_links/enqueue_links.d.ts +51 -159
  34. package/enqueue_links/enqueue_links.js +38 -231
  35. package/enqueue_links/shared.d.ts +34 -24
  36. package/enqueue_links/shared.js +54 -36
  37. package/errors.d.ts +43 -12
  38. package/errors.js +48 -9
  39. package/events/event_manager.d.ts +2 -2
  40. package/events/event_manager.js +7 -6
  41. package/events/local_event_manager.d.ts +1 -1
  42. package/events/local_event_manager.js +5 -5
  43. package/http.d.ts +9 -0
  44. package/http.js +28 -0
  45. package/index.d.ts +1 -1
  46. package/index.js +1 -1
  47. package/log.js +5 -1
  48. package/memory-storage/memory-storage.d.ts +3 -11
  49. package/memory-storage/memory-storage.js +42 -48
  50. package/memory-storage/resource-clients/dataset.d.ts +1 -1
  51. package/memory-storage/resource-clients/dataset.js +8 -13
  52. package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
  53. package/memory-storage/resource-clients/key-value-store.js +36 -38
  54. package/memory-storage/resource-clients/request-queue.d.ts +4 -23
  55. package/memory-storage/resource-clients/request-queue.js +68 -80
  56. package/owned_or_injected.d.ts +1 -3
  57. package/owned_or_injected.js +17 -17
  58. package/package.json +8 -9
  59. package/proxy_configuration.d.ts +1 -3
  60. package/proxy_configuration.js +18 -14
  61. package/recoverable_state.d.ts +80 -45
  62. package/recoverable_state.js +163 -75
  63. package/request.d.ts +3 -4
  64. package/request.js +57 -44
  65. package/router.d.ts +6 -9
  66. package/router.js +23 -23
  67. package/serialization.js +14 -13
  68. package/service_locator.d.ts +19 -10
  69. package/service_locator.js +57 -48
  70. package/session_pool/session.d.ts +3 -14
  71. package/session_pool/session.js +76 -74
  72. package/session_pool/session_pool.d.ts +6 -12
  73. package/session_pool/session_pool.js +85 -77
  74. package/storages/batched_adds.d.ts +37 -0
  75. package/storages/batched_adds.js +73 -0
  76. package/storages/dataset.d.ts +12 -1
  77. package/storages/dataset.js +133 -32
  78. package/storages/index.d.ts +2 -1
  79. package/storages/index.js +2 -1
  80. package/storages/key_value_store.d.ts +19 -4
  81. package/storages/key_value_store.js +204 -75
  82. package/storages/request_dedup_cache.d.ts +1 -2
  83. package/storages/request_dedup_cache.js +9 -9
  84. package/storages/request_list.d.ts +4 -23
  85. package/storages/request_list.js +103 -96
  86. package/storages/request_loader.d.ts +5 -0
  87. package/storages/request_manager_tandem.d.ts +1 -10
  88. package/storages/request_manager_tandem.js +27 -27
  89. package/storages/request_queue.d.ts +21 -18
  90. package/storages/request_queue.js +318 -177
  91. package/storages/sitemap_request_loader.d.ts +22 -70
  92. package/storages/sitemap_request_loader.js +122 -116
  93. package/storages/storage_instance_manager.d.ts +1 -2
  94. package/storages/storage_instance_manager.js +17 -17
  95. package/storages/storage_stats.d.ts +1 -1
  96. package/storages/storage_stats.js +4 -4
  97. package/storages/throttling_request_manager.d.ts +239 -0
  98. package/storages/throttling_request_manager.js +646 -0
  99. package/storages/transaction.d.ts +252 -0
  100. package/storages/transaction.js +251 -0
  101. package/storages/utils.d.ts +6 -3
  102. package/storages/utils.js +11 -2
  103. package/system-info/runtime.js +7 -7
  104. package/url.d.ts +9 -0
  105. package/url.js +11 -0
  106. package/validators.d.ts +22 -25
  107. package/validators.js +13 -25
  108. package/storages/access_checking.d.ts +0 -12
  109. package/storages/access_checking.js +0 -17
package/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { inspect } from 'node:util';
2
- import ow from 'ow';
2
+ import { parseArgument, schemas } from './validators.js';
3
3
  /**
4
4
  * Creates a standardized debug info from request and response. This info is usually added to dataset under the hidden `#debug` field.
5
5
  *
@@ -12,9 +12,9 @@ import ow from 'ow';
12
12
  * @internal
13
13
  */
14
14
  export function createRequestDebugInfo(request, response = {}, additionalFields = {}) {
15
- ow(request, ow.object);
16
- ow(response, ow.object);
17
- ow(additionalFields, ow.object);
15
+ parseArgument(request, schemas.anyObject);
16
+ parseArgument(response, schemas.anyObject);
17
+ parseArgument(additionalFields, schemas.anyObject);
18
18
  return {
19
19
  requestId: request.id,
20
20
  url: request.url,
@@ -1,26 +1,31 @@
1
- import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
2
- import { type RobotsTxtFile } from '@crawlee/utils';
3
- import type { SetRequired } from 'type-fest';
4
- import { Request } from '../request.js';
5
- import type { IRequestManager } from '../storages/request_manager.js';
6
- import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, RequestQueueOperationOptions } from '../storages/request_queue.js';
7
- import type { GlobInput, PseudoUrlInput, RegExpInput, RequestTransform, SkippedRequestCallback } from './shared.js';
8
- export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
1
+ import type { Dictionary } from '@crawlee/types';
2
+ import { EnqueueStrategy } from '@crawlee/utils';
3
+ import type { RequestQueueOperationOptions } from '../storages/request_queue.js';
4
+ import type { RequestTransform, SkippedRequestCallback, UrlPatternInput, UrlPatternObject } from './shared.js';
5
+ /**
6
+ * Options shared by the `extractLinks()` context helper across crawler types.
7
+ */
8
+ export interface ExtractLinksOptions {
9
+ /** A CSS selector matching links to be extracted. */
10
+ selector?: string;
11
+ /**
12
+ * A base URL that will be used to resolve relative URLs when using Cheerio. Ignored when using Puppeteer,
13
+ * since the relative URL resolution is done inside the browser automatically.
14
+ */
15
+ baseUrl?: string;
16
+ }
17
+ /**
18
+ * Options accepted by the `enqueueUrls()` context helper exposed by `BasicCrawler`.
19
+ */
20
+ export interface EnqueueUrlsOptions extends RequestQueueOperationOptions {
9
21
  /** Limit the amount of actually enqueued URLs to this number. Useful for testing across the entire crawling scope. */
10
22
  limit?: number;
11
- /** An array of URLs to enqueue. */
12
- urls?: readonly string[];
13
- /** A request manager to which the URLs will be enqueued. */
14
- requestManager?: IRequestManager;
15
- /** A CSS selector matching links to be enqueued. */
16
- selector?: string;
17
23
  /** Sets {@link Request.userData} for newly enqueued requests. */
18
24
  userData?: Dictionary;
19
25
  /**
20
26
  * Sets {@link Request.label} for newly enqueued requests.
21
27
  *
22
- * This option has the lowest priority and can be overwritten by request options
23
- * specified in `globs`, `regexps`, or `pseudoUrls` objects, as well as by `transformRequestFunction`.
28
+ * Can be overwritten by `transformRequestFunction`.
24
29
  */
25
30
  label?: string;
26
31
  /** Sets {@link Request.sessionId} for newly enqueued requests. */
@@ -31,66 +36,36 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
31
36
  */
32
37
  skipNavigation?: boolean;
33
38
  /**
34
- * A base URL that will be used to resolve relative URLs when using Cheerio. Ignored when using Puppeteer,
35
- * since the relative URL resolution is done inside the browser automatically.
39
+ * A base URL that will be used to resolve relative URLs.
36
40
  */
37
41
  baseUrl?: string;
38
42
  /**
39
- * An array of glob pattern strings or plain objects
40
- * containing glob pattern strings matching the URLs to be enqueued.
41
- *
42
- * The plain objects must include at least the `glob` property, which holds the glob pattern string.
43
- * All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
44
- *
45
- * The matching is always case-insensitive.
46
- * If you need case-sensitive matching, use `regexps` property directly.
47
- *
48
- * If `globs` is an empty array or `undefined`, and `regexps` are also not defined, then the function
49
- * enqueues the links with the same subdomain.
50
- */
51
- globs?: readonly GlobInput[];
52
- /**
53
- * An array of glob pattern strings, regexp patterns or plain objects
54
- * containing patterns matching URLs that will **never** be enqueued.
43
+ * An array of URL patterns that URLs must match to be enqueued.
55
44
  *
56
- * The plain objects must include either the `glob` property or the `regexp` property.
45
+ * Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
57
46
  *
58
47
  * Glob matching is always case-insensitive.
59
- * If you need case-sensitive matching, provide a regexp.
60
- */
61
- exclude?: readonly (GlobInput | RegExpInput)[];
62
- /**
63
- * An array of regular expressions or plain objects
64
- * containing regular expressions matching the URLs to be enqueued.
48
+ * If you need case-sensitive matching, use a `RegExp`.
65
49
  *
66
- * The plain objects must include at least the `regexp` property, which holds the regular expression.
67
- * All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
50
+ * The patterns are combined with the {@link EnqueueUrlsOptions.strategy|`strategy`} using AND logic - a URL
51
+ * must match at least one `include` pattern **and** satisfy the strategy to be enqueued. To match URLs across
52
+ * hostnames, pass an explicit {@link EnqueueStrategy.All} strategy.
68
53
  *
69
- * If `regexps` is an empty array or `undefined`, and `globs` are also not defined, then the function
70
- * enqueues the links with the same subdomain.
54
+ * If `undefined`, the links are enqueued based on the {@link EnqueueUrlsOptions.strategy|`strategy`} alone.
55
+ * Passing an empty array is not allowed.
71
56
  */
72
- regexps?: readonly RegExpInput[];
57
+ include?: readonly UrlPatternInput[];
73
58
  /**
74
- * *NOTE:* In future versions of SDK the options will be removed.
75
- * Please use `globs` or `regexps` instead.
76
- *
77
- * An array of {@link PseudoUrl} strings or plain objects
78
- * containing {@link PseudoUrl} strings matching the URLs to be enqueued.
79
- *
80
- * The plain objects must include at least the `purl` property, which holds the pseudo-URL string.
81
- * All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
82
- *
83
- * With a pseudo-URL string, the matching is always case-insensitive.
84
- * If you need case-sensitive matching, use `regexps` property directly.
59
+ * An array of URL patterns. Matching URLs will **not** be enqueued.
85
60
  *
86
- * If `pseudoUrls` is an empty array or `undefined`, then the function
87
- * enqueues the links with the same subdomain.
61
+ * Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
88
62
  *
89
- * @deprecated prefer using `globs` or `regexps` instead
63
+ * Glob matching is always case-insensitive.
64
+ * If you need case-sensitive matching, use a `RegExp`.
90
65
  */
91
- pseudoUrls?: readonly PseudoUrlInput[];
66
+ exclude?: readonly UrlPatternInput[];
92
67
  /**
93
- * After request options are filtered by patterns, this function can be used
68
+ * After request options are filtered by `include`/`exclude` patterns, this function can be used
94
69
  * to remove them or modify their contents such as `userData`, `payload` or, most importantly `uniqueKey`. This is useful
95
70
  * when you need to enqueue multiple `Requests` to the queue that share the same URL, but differ in methods or payloads,
96
71
  * or to dynamically update or create `userData`.
@@ -109,8 +84,8 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
109
84
  * }
110
85
  * ```
111
86
  *
112
- * Note that `transformRequestFunction` has the highest priority and can overwrite request options
113
- * specified in `globs`, `regexps`, or `pseudoUrls` objects, as well as the global `label` option.
87
+ * Note that `transformRequestFunction` has the highest priority and can overwrite
88
+ * the global `label` option.
114
89
  *
115
90
  * The function receives a {@link RequestOptions} object and can return either:
116
91
  * - The modified {@link RequestOptions} object
@@ -136,25 +111,12 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
136
111
  *
137
112
  * @default EnqueueStrategy.SameHostname
138
113
  */
139
- strategy?: EnqueueStrategy | 'all' | 'same-domain' | 'same-hostname' | 'same-origin';
114
+ strategy?: EnqueueStrategyOption;
140
115
  /**
141
116
  * By default, only the first batch (1000) of found requests will be added to the queue before resolving the call.
142
117
  * You can use this option to wait for adding all of them.
143
118
  */
144
119
  waitForAllRequestsToBeAdded?: boolean;
145
- /**
146
- * RobotsTxtFile instance for the current request that triggered the `enqueueLinks`.
147
- * If provided, disallowed URLs will be ignored.
148
- */
149
- robotsTxtFile?: Pick<RobotsTxtFile, 'isAllowed'>;
150
- /**
151
- * Mirrors {@link BasicCrawlerOptions.respectRobotsTxtFile}: pass `false` to disable filtering or
152
- * `{ userAgent }` to evaluate rules for a specific user-agent. Defaults to `*` when
153
- * {@link EnqueueLinksOptions.robotsTxtFile|`robotsTxtFile`} is provided.
154
- */
155
- respectRobotsTxtFile?: boolean | {
156
- userAgent?: string;
157
- };
158
120
  /**
159
121
  * When a request is skipped for some reason, you can use this callback to act on it.
160
122
  * This is currently fired for requests skipped
@@ -164,86 +126,11 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
164
126
  */
165
127
  onSkippedRequest?: SkippedRequestCallback;
166
128
  }
167
- /**
168
- * The different enqueueing strategies available.
169
- *
170
- * 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:
171
- *
172
- * ```md
173
- * Protocol Domain
174
- * ┌────┐ ┌─────────┐
175
- * https://example.crawlee.dev/...
176
- * │ └─────────────────┤
177
- * │ Hostname │
178
- * │ │
179
- * └─────────────────────────┘
180
- * Origin
181
- *```
182
- *
183
- * - The `Protocol` is usually `http` or `https`
184
- * - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
185
- * - 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/`
186
- * - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
187
- */
188
- export declare enum EnqueueStrategy {
189
- /**
190
- * Matches any URLs found
191
- */
192
- All = "all",
193
- /**
194
- * Matches any URLs that have the same hostname.
195
- * For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
196
- * `https://example.com/hello` will not be matched.
197
- *
198
- * > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
199
- */
200
- SameHostname = "same-hostname",
201
- /**
202
- * Matches any URLs that have the same domain as the base URL.
203
- * For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
204
- * `https://example.com`.
205
- *
206
- * > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
207
- */
208
- SameDomain = "same-domain",
209
- /**
210
- * Matches any URLs that have the same hostname and protocol.
211
- * For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
212
- * `http://wow.example.com/hello` will not be matched.
213
- *
214
- * > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
215
- */
216
- SameOrigin = "same-origin"
217
- }
218
- /**
219
- * This function enqueues the urls provided to the {@link RequestQueue} provided. If you want to automatically find and enqueue links,
220
- * you should use the context-aware `enqueueLinks` function provided on the crawler contexts.
221
- *
222
- * Optionally, the function allows you to filter the target links' URLs using an array of globs or regular expressions
223
- * and override settings of the enqueued {@link Request} objects.
224
- *
225
- * **Example usage**
226
- *
227
- * ```javascript
228
- * await enqueueLinks({
229
- * urls: aListOfFoundUrls,
230
- * requestManager,
231
- * selector: 'a.product-detail',
232
- * globs: [
233
- * 'https://www.example.com/handbags/*',
234
- * 'https://www.example.com/purses/*'
235
- * ],
236
- * });
237
- * ```
238
- *
239
- * @param options All `enqueueLinks()` parameters are passed via an options object.
240
- * @returns Promise that resolves to {@link BatchAddRequestsResult} object.
241
- */
242
- export declare function enqueueLinks(options: SetRequired<Omit<EnqueueLinksOptions, 'requestManager'>, 'urls'> & {
243
- requestManager: {
244
- addRequestsBatched: (requests: Request<Dictionary>[], options: AddRequestsBatchedOptions) => Promise<AddRequestsBatchedResult>;
245
- };
246
- }): Promise<BatchAddRequestsResult>;
129
+ /** The combined options accepted by a crawler context's `enqueueLinks()` helper: `extractLinks()` + `enqueueUrls()`. */
130
+ export type EnqueueLinksOptions = ExtractLinksOptions & EnqueueUrlsOptions;
131
+ export { EnqueueStrategy };
132
+ /** The `strategy` option accepted by {@link ExtractLinksOptions} and {@link EnqueueUrlsOptions}. */
133
+ export type EnqueueStrategyOption = EnqueueStrategy | 'all' | 'same-domain' | 'same-hostname' | 'same-origin';
247
134
  /**
248
135
  * @internal
249
136
  * This method helps resolve the baseUrl that will be used for filtering in {@link enqueueLinks}.
@@ -258,7 +145,12 @@ export declare function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy
258
145
  */
259
146
  export interface ResolveBaseUrl {
260
147
  userProvidedBaseUrl?: string;
261
- enqueueStrategy?: EnqueueLinksOptions['strategy'];
148
+ enqueueStrategy?: EnqueueStrategyOption;
262
149
  originalRequestUrl: string;
263
150
  finalRequestUrl?: string;
264
151
  }
152
+ /**
153
+ * @internal
154
+ * Builds the glob patterns a URL must match to satisfy the given enqueue `strategy`, anchored at `baseUrl`.
155
+ */
156
+ export declare function buildEnqueueStrategyPatterns(baseUrl: string, strategy: EnqueueStrategyOption): UrlPatternObject[];
@@ -1,234 +1,6 @@
1
- import ow from 'ow';
1
+ import { EnqueueStrategy } from '@crawlee/utils';
2
2
  import { getDomain } from 'tldts';
3
- import { Request } from '../request.js';
4
- import { serviceLocator } from '../service_locator.js';
5
- import { applyRequestTransform, constructGlobObjectsFromGlobs, constructRegExpObjectsFromPseudoUrls, constructRegExpObjectsFromRegExps, createRequestOptions, filterRequestOptionsByPatterns, } from './shared.js';
6
- /**
7
- * The different enqueueing strategies available.
8
- *
9
- * 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:
10
- *
11
- * ```md
12
- * Protocol Domain
13
- * ┌────┐ ┌─────────┐
14
- * https://example.crawlee.dev/...
15
- * │ └─────────────────┤
16
- * │ Hostname │
17
- * │ │
18
- * └─────────────────────────┘
19
- * Origin
20
- *```
21
- *
22
- * - The `Protocol` is usually `http` or `https`
23
- * - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
24
- * - 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/`
25
- * - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
26
- */
27
- export var EnqueueStrategy;
28
- (function (EnqueueStrategy) {
29
- /**
30
- * Matches any URLs found
31
- */
32
- EnqueueStrategy["All"] = "all";
33
- /**
34
- * Matches any URLs that have the same hostname.
35
- * For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
36
- * `https://example.com/hello` will not be matched.
37
- *
38
- * > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
39
- */
40
- EnqueueStrategy["SameHostname"] = "same-hostname";
41
- /**
42
- * Matches any URLs that have the same domain as the base URL.
43
- * For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
44
- * `https://example.com`.
45
- *
46
- * > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
47
- */
48
- EnqueueStrategy["SameDomain"] = "same-domain";
49
- /**
50
- * Matches any URLs that have the same hostname and protocol.
51
- * For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
52
- * `http://wow.example.com/hello` will not be matched.
53
- *
54
- * > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
55
- */
56
- EnqueueStrategy["SameOrigin"] = "same-origin";
57
- })(EnqueueStrategy || (EnqueueStrategy = {}));
58
- /**
59
- * This function enqueues the urls provided to the {@link RequestQueue} provided. If you want to automatically find and enqueue links,
60
- * you should use the context-aware `enqueueLinks` function provided on the crawler contexts.
61
- *
62
- * Optionally, the function allows you to filter the target links' URLs using an array of globs or regular expressions
63
- * and override settings of the enqueued {@link Request} objects.
64
- *
65
- * **Example usage**
66
- *
67
- * ```javascript
68
- * await enqueueLinks({
69
- * urls: aListOfFoundUrls,
70
- * requestManager,
71
- * selector: 'a.product-detail',
72
- * globs: [
73
- * 'https://www.example.com/handbags/*',
74
- * 'https://www.example.com/purses/*'
75
- * ],
76
- * });
77
- * ```
78
- *
79
- * @param options All `enqueueLinks()` parameters are passed via an options object.
80
- * @returns Promise that resolves to {@link BatchAddRequestsResult} object.
81
- */
82
- export async function enqueueLinks(options) {
83
- if (!options || Object.keys(options).length === 0) {
84
- throw new RangeError([
85
- 'enqueueLinks() was called without the required options. You can only do that when you use the `crawlingContext.enqueueLinks()` method in request handlers.',
86
- 'Check out our guide on how to use enqueueLinks() here: https://crawlee.dev/js/docs/examples/crawl-relative-links',
87
- ].join('\n'));
88
- }
89
- ow(options, ow.object.exactShape({
90
- urls: ow.array.ofType(ow.string),
91
- requestManager: ow.object.hasKeys('addRequestsBatched'),
92
- robotsTxtFile: ow.optional.object.hasKeys('isAllowed'),
93
- respectRobotsTxtFile: ow.optional.any(ow.boolean, ow.object.exactShape({ userAgent: ow.optional.string })),
94
- onSkippedRequest: ow.optional.function,
95
- forefront: ow.optional.boolean,
96
- skipNavigation: ow.optional.boolean,
97
- sessionId: ow.optional.string,
98
- limit: ow.optional.number,
99
- selector: ow.optional.string,
100
- baseUrl: ow.optional.string,
101
- userData: ow.optional.object,
102
- label: ow.optional.string,
103
- pseudoUrls: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('purl'))),
104
- globs: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('glob'))),
105
- exclude: ow.optional.array.ofType(ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'))),
106
- regexps: ow.optional.array.ofType(ow.any(ow.regExp, ow.object.hasKeys('regexp'))),
107
- transformRequestFunction: ow.optional.function,
108
- strategy: ow.optional.string.oneOf(Object.values(EnqueueStrategy)),
109
- waitForAllRequestsToBeAdded: ow.optional.boolean,
110
- }));
111
- const { requestManager, limit, urls,
112
- // oxlint-disable-next-line typescript/no-deprecated -- still accepted for backwards compat
113
- pseudoUrls, exclude, globs, regexps, transformRequestFunction, forefront, waitForAllRequestsToBeAdded, robotsTxtFile, onSkippedRequest, } = options;
114
- const urlExcludePatternObjects = [];
115
- const urlPatternObjects = [];
116
- if (exclude?.length) {
117
- for (const excl of exclude) {
118
- if (typeof excl === 'string' || 'glob' in excl) {
119
- urlExcludePatternObjects.push(...constructGlobObjectsFromGlobs([excl]));
120
- }
121
- else if (excl instanceof RegExp || 'regexp' in excl) {
122
- urlExcludePatternObjects.push(...constructRegExpObjectsFromRegExps([excl]));
123
- }
124
- }
125
- }
126
- if (pseudoUrls?.length) {
127
- serviceLocator.getLogger().deprecated('`pseudoUrls` option is deprecated, use `globs` or `regexps` instead');
128
- urlPatternObjects.push(...constructRegExpObjectsFromPseudoUrls(pseudoUrls));
129
- }
130
- if (globs?.length) {
131
- urlPatternObjects.push(...constructGlobObjectsFromGlobs(globs));
132
- }
133
- if (regexps?.length) {
134
- urlPatternObjects.push(...constructRegExpObjectsFromRegExps(regexps));
135
- }
136
- if (!urlPatternObjects.length) {
137
- options.strategy ??= EnqueueStrategy.SameHostname;
138
- }
139
- const enqueueStrategyPatterns = [];
140
- if (options.baseUrl) {
141
- const url = new URL(options.baseUrl);
142
- switch (options.strategy) {
143
- case EnqueueStrategy.SameHostname:
144
- // We need to get the origin of the passed in domain in the event someone sets baseUrl
145
- // to an url like https://example.com/deep/default/path and one of the found urls is an
146
- // absolute relative path (/path/to/page)
147
- enqueueStrategyPatterns.push({ glob: ignoreHttpSchema(`${url.origin}/**`) });
148
- break;
149
- case EnqueueStrategy.SameDomain: {
150
- // Get the actual hostname from the base url
151
- const baseUrlHostname = getDomain(url.hostname, { mixedInputs: false });
152
- if (baseUrlHostname) {
153
- // We have a hostname, so we can use it to match all links on the page that point to it and any subdomains of it
154
- url.hostname = baseUrlHostname;
155
- enqueueStrategyPatterns.push({ glob: ignoreHttpSchema(`${url.origin.replace(baseUrlHostname, `*.${baseUrlHostname}`)}/**`) }, { glob: ignoreHttpSchema(`${url.origin}/**`) });
156
- }
157
- else {
158
- // We don't have a hostname (can happen for ips for instance), so reproduce the same behavior
159
- // as SameDomainAndSubdomain
160
- enqueueStrategyPatterns.push({ glob: ignoreHttpSchema(`${url.origin}/**`) });
161
- }
162
- break;
163
- }
164
- case EnqueueStrategy.SameOrigin: {
165
- // The same behavior as SameHostname, but respecting the protocol of the URL
166
- enqueueStrategyPatterns.push({ glob: `${url.origin}/**` });
167
- break;
168
- }
169
- case EnqueueStrategy.All:
170
- default:
171
- enqueueStrategyPatterns.push({ glob: `http{s,}://**` });
172
- break;
173
- }
174
- }
175
- async function reportSkippedRequests(skippedRequests, reason) {
176
- if (onSkippedRequest && skippedRequests.length > 0) {
177
- await Promise.all(skippedRequests.map((request) => {
178
- return onSkippedRequest({
179
- url: request.url,
180
- reason: request.skippedReason ?? reason,
181
- });
182
- }));
183
- }
184
- }
185
- let requestOptions = createRequestOptions(urls, options);
186
- if (robotsTxtFile && options.respectRobotsTxtFile !== false) {
187
- const robotsUserAgent = typeof options.respectRobotsTxtFile === 'object' ? (options.respectRobotsTxtFile.userAgent ?? '*') : '*';
188
- const skippedRequests = [];
189
- requestOptions = requestOptions.filter((request) => {
190
- if (robotsTxtFile.isAllowed(request.url, robotsUserAgent)) {
191
- return true;
192
- }
193
- skippedRequests.push(request);
194
- return false;
195
- });
196
- await reportSkippedRequests(skippedRequests, 'robotsTxt');
197
- }
198
- async function createFilteredRequests() {
199
- const skippedRequests = [];
200
- // Step 1: Filter request options by exclude patterns, user patterns (globs/regexps), and strategy patterns.
201
- // Pattern-level options (label, userData, method, etc.) are merged during this step.
202
- let filteredOptions;
203
- if (urlPatternObjects.length === 0) {
204
- filteredOptions = filterRequestOptionsByPatterns(requestOptions, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, urlExcludePatternObjects, options.strategy, (url) => skippedRequests.push(url));
205
- }
206
- else {
207
- // Filter by user patterns first (with exclude)
208
- const afterUserPatterns = filterRequestOptionsByPatterns(requestOptions, urlPatternObjects, urlExcludePatternObjects, options.strategy, (url) => skippedRequests.push(url));
209
- // ...then filter by the enqueue links strategy (making this an AND check)
210
- filteredOptions = filterRequestOptionsByPatterns(afterUserPatterns, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, [], options.strategy, (url) => skippedRequests.push(url));
211
- }
212
- await reportSkippedRequests(skippedRequests.map((url) => ({ url })), 'filters');
213
- // Step 2: Apply transformRequestFunction on request options - it has the highest priority
214
- if (transformRequestFunction) {
215
- const skippedByTransform = [];
216
- filteredOptions = applyRequestTransform(filteredOptions, transformRequestFunction, (r) => skippedByTransform.push(r));
217
- await reportSkippedRequests(skippedByTransform, 'transform');
218
- }
219
- // Step 3: Create Request instances from the final request options
220
- return filteredOptions.map((opts) => new Request(opts));
221
- }
222
- const { addedRequests, requestsOverLimit } = await requestManager.addRequestsBatched(await createFilteredRequests(), {
223
- forefront,
224
- waitForAllRequestsToBeAdded,
225
- maxNewRequests: limit,
226
- });
227
- if (requestsOverLimit?.length !== undefined && requestsOverLimit.length > 0) {
228
- await reportSkippedRequests(requestsOverLimit.map((r) => ({ url: typeof r === 'string' ? r : r.url })), 'enqueueLimit');
229
- }
230
- return { processedRequests: addedRequests, unprocessedRequests: [] };
231
- }
3
+ export { EnqueueStrategy };
232
4
  /**
233
5
  * @internal
234
6
  * This method helps resolve the baseUrl that will be used for filtering in {@link enqueueLinks}.
@@ -264,7 +36,42 @@ export function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy, finalR
264
36
  return originalUrlOrigin;
265
37
  }
266
38
  /**
267
- * Internal function that changes the enqueue globs to match both http and https
39
+ * @internal
40
+ * Builds the glob patterns a URL must match to satisfy the given enqueue `strategy`, anchored at `baseUrl`.
41
+ */
42
+ export function buildEnqueueStrategyPatterns(baseUrl, strategy) {
43
+ const url = new URL(baseUrl);
44
+ switch (strategy) {
45
+ case EnqueueStrategy.SameHostname:
46
+ // We need to get the origin of the passed in domain in the event someone sets baseUrl
47
+ // to an url like https://example.com/deep/default/path and one of the found urls is an
48
+ // absolute relative path (/path/to/page)
49
+ return [{ glob: ignoreHttpSchema(`${url.origin}/**`) }];
50
+ case EnqueueStrategy.SameDomain: {
51
+ // Get the actual hostname from the base url
52
+ const baseUrlHostname = getDomain(url.hostname, { mixedInputs: false });
53
+ if (baseUrlHostname) {
54
+ // We have a hostname, so we can use it to match all links on the page that point to it and any subdomains of it
55
+ url.hostname = baseUrlHostname;
56
+ return [
57
+ { glob: ignoreHttpSchema(`${url.origin.replace(baseUrlHostname, `*.${baseUrlHostname}`)}/**`) },
58
+ { glob: ignoreHttpSchema(`${url.origin}/**`) },
59
+ ];
60
+ }
61
+ // We don't have a hostname (can happen for ips for instance), so reproduce the same behavior
62
+ // as SameDomainAndSubdomain
63
+ return [{ glob: ignoreHttpSchema(`${url.origin}/**`) }];
64
+ }
65
+ case EnqueueStrategy.SameOrigin:
66
+ // The same behavior as SameHostname, but respecting the protocol of the URL
67
+ return [{ glob: `${url.origin}/**` }];
68
+ case EnqueueStrategy.All:
69
+ default:
70
+ return [{ glob: `http{s,}://**` }];
71
+ }
72
+ }
73
+ /**
74
+ * Internal function that changes the enqueue glob patterns to match both http and https
268
75
  */
269
76
  function ignoreHttpSchema(pattern) {
270
77
  return pattern.replace(/^(https?):\/\//, 'http{s,}://');