@crawlee/core 4.0.0-beta.8 → 4.0.0-beta.80

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 (257) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +5 -6
  3. package/autoscaling/autoscaled_pool.js +12 -11
  4. package/autoscaling/client_load_signal.d.ts +25 -0
  5. package/autoscaling/client_load_signal.js +36 -0
  6. package/autoscaling/cpu_load_signal.d.ts +28 -0
  7. package/autoscaling/cpu_load_signal.js +24 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +23 -0
  9. package/autoscaling/event_loop_load_signal.js +35 -0
  10. package/autoscaling/index.d.ts +5 -1
  11. package/autoscaling/index.js +5 -1
  12. package/autoscaling/load_signal.d.ts +99 -0
  13. package/autoscaling/load_signal.js +135 -0
  14. package/autoscaling/memory_load_signal.d.ts +43 -0
  15. package/autoscaling/memory_load_signal.js +102 -0
  16. package/autoscaling/snapshotter.d.ts +35 -72
  17. package/autoscaling/snapshotter.js +78 -181
  18. package/autoscaling/system_status.d.ts +21 -36
  19. package/autoscaling/system_status.js +46 -84
  20. package/configuration.d.ts +88 -228
  21. package/configuration.js +162 -224
  22. package/cookie_utils.d.ts +4 -3
  23. package/cookie_utils.js +20 -13
  24. package/crawlers/context_pipeline.d.ts +70 -0
  25. package/crawlers/context_pipeline.js +122 -0
  26. package/crawlers/crawler_commons.d.ts +20 -30
  27. package/crawlers/crawler_commons.js +12 -21
  28. package/crawlers/crawler_utils.d.ts +2 -3
  29. package/crawlers/crawler_utils.js +1 -2
  30. package/crawlers/error_snapshotter.d.ts +3 -3
  31. package/crawlers/error_snapshotter.js +2 -3
  32. package/crawlers/error_tracker.d.ts +2 -2
  33. package/crawlers/error_tracker.js +0 -1
  34. package/crawlers/index.d.ts +1 -2
  35. package/crawlers/index.js +1 -2
  36. package/crawlers/internals/types.d.ts +7 -0
  37. package/crawlers/internals/types.js +1 -0
  38. package/crawlers/statistics.d.ts +22 -17
  39. package/crawlers/statistics.js +30 -26
  40. package/enqueue_links/enqueue_links.d.ts +44 -22
  41. package/enqueue_links/enqueue_links.js +51 -30
  42. package/enqueue_links/index.d.ts +0 -1
  43. package/enqueue_links/index.js +0 -1
  44. package/enqueue_links/shared.d.ts +25 -9
  45. package/enqueue_links/shared.js +69 -38
  46. package/errors.d.ts +53 -4
  47. package/errors.js +70 -5
  48. package/events/event_manager.d.ts +34 -8
  49. package/events/event_manager.js +8 -10
  50. package/events/index.d.ts +0 -1
  51. package/events/index.js +0 -1
  52. package/events/local_event_manager.d.ts +15 -5
  53. package/events/local_event_manager.js +37 -40
  54. package/index.d.ts +4 -3
  55. package/index.js +3 -2
  56. package/log.d.ts +82 -3
  57. package/log.js +102 -1
  58. package/memory-storage/consts.d.ts +4 -0
  59. package/memory-storage/consts.js +4 -0
  60. package/memory-storage/index.d.ts +1 -0
  61. package/memory-storage/index.js +1 -0
  62. package/memory-storage/memory-storage.d.ts +46 -0
  63. package/memory-storage/memory-storage.js +136 -0
  64. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  65. package/memory-storage/resource-clients/common/base-client.js +6 -0
  66. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  67. package/memory-storage/resource-clients/dataset.js +113 -0
  68. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  69. package/memory-storage/resource-clients/key-value-store.js +203 -0
  70. package/memory-storage/resource-clients/request-queue.d.ts +96 -0
  71. package/memory-storage/resource-clients/request-queue.js +421 -0
  72. package/memory-storage/utils.d.ts +17 -0
  73. package/memory-storage/utils.js +42 -0
  74. package/package.json +12 -10
  75. package/proxy_configuration.d.ts +14 -149
  76. package/proxy_configuration.js +19 -168
  77. package/recoverable_state.d.ts +120 -0
  78. package/recoverable_state.js +141 -0
  79. package/request.d.ts +84 -13
  80. package/request.js +107 -28
  81. package/router.d.ts +117 -15
  82. package/router.js +124 -30
  83. package/serialization.d.ts +0 -1
  84. package/serialization.js +1 -2
  85. package/service_locator.d.ts +156 -0
  86. package/service_locator.js +238 -0
  87. package/session_pool/consts.d.ts +1 -2
  88. package/session_pool/consts.js +1 -2
  89. package/session_pool/errors.d.ts +0 -1
  90. package/session_pool/errors.js +0 -1
  91. package/session_pool/fingerprint.d.ts +9 -0
  92. package/session_pool/fingerprint.js +30 -0
  93. package/session_pool/index.d.ts +0 -2
  94. package/session_pool/index.js +0 -2
  95. package/session_pool/session.d.ts +35 -73
  96. package/session_pool/session.js +46 -99
  97. package/session_pool/session_pool.d.ts +71 -72
  98. package/session_pool/session_pool.js +111 -100
  99. package/storages/access_checking.d.ts +0 -1
  100. package/storages/access_checking.js +5 -2
  101. package/storages/dataset.d.ts +101 -51
  102. package/storages/dataset.js +168 -126
  103. package/storages/index.d.ts +8 -7
  104. package/storages/index.js +6 -7
  105. package/storages/key_value_store.d.ts +165 -36
  106. package/storages/key_value_store.js +269 -122
  107. package/storages/key_value_store_codec.d.ts +32 -0
  108. package/storages/key_value_store_codec.js +113 -0
  109. package/storages/request_dedup_cache.d.ts +23 -0
  110. package/storages/request_dedup_cache.js +48 -0
  111. package/storages/request_list.d.ts +38 -82
  112. package/storages/request_list.js +65 -41
  113. package/storages/request_loader.d.ts +96 -0
  114. package/storages/request_loader.js +1 -0
  115. package/storages/request_manager.d.ts +33 -0
  116. package/storages/request_manager.js +1 -0
  117. package/storages/request_manager_tandem.d.ts +106 -0
  118. package/storages/request_manager_tandem.js +197 -0
  119. package/storages/request_queue.d.ts +303 -46
  120. package/storages/request_queue.js +629 -214
  121. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +28 -24
  122. package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +49 -46
  123. package/storages/storage_instance_manager.d.ts +91 -0
  124. package/storages/storage_instance_manager.js +258 -0
  125. package/storages/storage_stats.d.ts +48 -0
  126. package/storages/storage_stats.js +29 -0
  127. package/storages/utils.d.ts +51 -6
  128. package/storages/utils.js +60 -9
  129. package/system-info/cpu-info.d.ts +67 -0
  130. package/system-info/cpu-info.js +216 -0
  131. package/system-info/memory-info.d.ts +31 -0
  132. package/system-info/memory-info.js +115 -0
  133. package/system-info/ps-tree.d.ts +17 -0
  134. package/system-info/ps-tree.js +144 -0
  135. package/system-info/runtime.d.ts +14 -0
  136. package/system-info/runtime.js +80 -0
  137. package/typedefs.d.ts +1 -2
  138. package/typedefs.js +0 -1
  139. package/validators.d.ts +8 -1
  140. package/validators.js +8 -1
  141. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  142. package/autoscaling/autoscaled_pool.js.map +0 -1
  143. package/autoscaling/index.d.ts.map +0 -1
  144. package/autoscaling/index.js.map +0 -1
  145. package/autoscaling/snapshotter.d.ts.map +0 -1
  146. package/autoscaling/snapshotter.js.map +0 -1
  147. package/autoscaling/system_status.d.ts.map +0 -1
  148. package/autoscaling/system_status.js.map +0 -1
  149. package/configuration.d.ts.map +0 -1
  150. package/configuration.js.map +0 -1
  151. package/cookie_utils.d.ts.map +0 -1
  152. package/cookie_utils.js.map +0 -1
  153. package/crawlers/crawler_commons.d.ts.map +0 -1
  154. package/crawlers/crawler_commons.js.map +0 -1
  155. package/crawlers/crawler_extension.d.ts +0 -12
  156. package/crawlers/crawler_extension.d.ts.map +0 -1
  157. package/crawlers/crawler_extension.js +0 -14
  158. package/crawlers/crawler_extension.js.map +0 -1
  159. package/crawlers/crawler_utils.d.ts.map +0 -1
  160. package/crawlers/crawler_utils.js.map +0 -1
  161. package/crawlers/error_snapshotter.d.ts.map +0 -1
  162. package/crawlers/error_snapshotter.js.map +0 -1
  163. package/crawlers/error_tracker.d.ts.map +0 -1
  164. package/crawlers/error_tracker.js.map +0 -1
  165. package/crawlers/index.d.ts.map +0 -1
  166. package/crawlers/index.js.map +0 -1
  167. package/crawlers/statistics.d.ts.map +0 -1
  168. package/crawlers/statistics.js.map +0 -1
  169. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  170. package/enqueue_links/enqueue_links.js.map +0 -1
  171. package/enqueue_links/index.d.ts.map +0 -1
  172. package/enqueue_links/index.js.map +0 -1
  173. package/enqueue_links/shared.d.ts.map +0 -1
  174. package/enqueue_links/shared.js.map +0 -1
  175. package/errors.d.ts.map +0 -1
  176. package/errors.js.map +0 -1
  177. package/events/event_manager.d.ts.map +0 -1
  178. package/events/event_manager.js.map +0 -1
  179. package/events/index.d.ts.map +0 -1
  180. package/events/index.js.map +0 -1
  181. package/events/local_event_manager.d.ts.map +0 -1
  182. package/events/local_event_manager.js.map +0 -1
  183. package/http_clients/base-http-client.d.ts +0 -134
  184. package/http_clients/base-http-client.d.ts.map +0 -1
  185. package/http_clients/base-http-client.js +0 -33
  186. package/http_clients/base-http-client.js.map +0 -1
  187. package/http_clients/form-data-like.d.ts +0 -67
  188. package/http_clients/form-data-like.d.ts.map +0 -1
  189. package/http_clients/form-data-like.js +0 -5
  190. package/http_clients/form-data-like.js.map +0 -1
  191. package/http_clients/got-scraping-http-client.d.ts +0 -15
  192. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  193. package/http_clients/got-scraping-http-client.js +0 -69
  194. package/http_clients/got-scraping-http-client.js.map +0 -1
  195. package/http_clients/index.d.ts +0 -3
  196. package/http_clients/index.d.ts.map +0 -1
  197. package/http_clients/index.js +0 -3
  198. package/http_clients/index.js.map +0 -1
  199. package/index.d.ts.map +0 -1
  200. package/index.js.map +0 -1
  201. package/log.d.ts.map +0 -1
  202. package/log.js.map +0 -1
  203. package/proxy_configuration.d.ts.map +0 -1
  204. package/proxy_configuration.js.map +0 -1
  205. package/request.d.ts.map +0 -1
  206. package/request.js.map +0 -1
  207. package/router.d.ts.map +0 -1
  208. package/router.js.map +0 -1
  209. package/serialization.d.ts.map +0 -1
  210. package/serialization.js.map +0 -1
  211. package/session_pool/consts.d.ts.map +0 -1
  212. package/session_pool/consts.js.map +0 -1
  213. package/session_pool/errors.d.ts.map +0 -1
  214. package/session_pool/errors.js.map +0 -1
  215. package/session_pool/events.d.ts +0 -3
  216. package/session_pool/events.d.ts.map +0 -1
  217. package/session_pool/events.js +0 -3
  218. package/session_pool/events.js.map +0 -1
  219. package/session_pool/index.d.ts.map +0 -1
  220. package/session_pool/index.js.map +0 -1
  221. package/session_pool/session.d.ts.map +0 -1
  222. package/session_pool/session.js.map +0 -1
  223. package/session_pool/session_pool.d.ts.map +0 -1
  224. package/session_pool/session_pool.js.map +0 -1
  225. package/storages/access_checking.d.ts.map +0 -1
  226. package/storages/access_checking.js.map +0 -1
  227. package/storages/dataset.d.ts.map +0 -1
  228. package/storages/dataset.js.map +0 -1
  229. package/storages/index.d.ts.map +0 -1
  230. package/storages/index.js.map +0 -1
  231. package/storages/key_value_store.d.ts.map +0 -1
  232. package/storages/key_value_store.js.map +0 -1
  233. package/storages/request_list.d.ts.map +0 -1
  234. package/storages/request_list.js.map +0 -1
  235. package/storages/request_provider.d.ts +0 -307
  236. package/storages/request_provider.d.ts.map +0 -1
  237. package/storages/request_provider.js +0 -555
  238. package/storages/request_provider.js.map +0 -1
  239. package/storages/request_queue.d.ts.map +0 -1
  240. package/storages/request_queue.js.map +0 -1
  241. package/storages/request_queue_v2.d.ts +0 -87
  242. package/storages/request_queue_v2.d.ts.map +0 -1
  243. package/storages/request_queue_v2.js +0 -438
  244. package/storages/request_queue_v2.js.map +0 -1
  245. package/storages/sitemap_request_list.d.ts.map +0 -1
  246. package/storages/sitemap_request_list.js.map +0 -1
  247. package/storages/storage_manager.d.ts +0 -58
  248. package/storages/storage_manager.d.ts.map +0 -1
  249. package/storages/storage_manager.js +0 -105
  250. package/storages/storage_manager.js.map +0 -1
  251. package/storages/utils.d.ts.map +0 -1
  252. package/storages/utils.js.map +0 -1
  253. package/tsconfig.build.tsbuildinfo +0 -1
  254. package/typedefs.d.ts.map +0 -1
  255. package/typedefs.js.map +0 -1
  256. package/validators.d.ts.map +0 -1
  257. package/validators.js.map +0 -1
@@ -1,6 +1,5 @@
1
1
  import ow from 'ow';
2
- import { Configuration } from '../configuration.js';
3
- import { log as defaultLog } from '../log.js';
2
+ import { serviceLocator } from '../service_locator.js';
4
3
  import { KeyValueStore } from '../storages/key_value_store.js';
5
4
  import { ErrorTracker } from './error_tracker.js';
6
5
  /**
@@ -30,7 +29,7 @@ const errorTrackerConfig = {
30
29
  * statistics for requests.
31
30
  *
32
31
  * All statistic information is saved on key value store
33
- * under the key `SDK_CRAWLER_STATISTICS_*`, persists between
32
+ * under the key `CRAWLEE_CRAWLER_STATISTICS_*`, persists between
34
33
  * migrations and abort/resurrect
35
34
  *
36
35
  * @category Crawlers
@@ -48,7 +47,7 @@ export class Statistics {
48
47
  /**
49
48
  * Statistic instance id.
50
49
  */
51
- id = Statistics.id++; // assign an id while incrementing so it can be saved/restored from KV
50
+ id;
52
51
  /**
53
52
  * Current statistic state used for doing calculations on {@link Statistics.calculate} calls
54
53
  */
@@ -57,12 +56,8 @@ export class Statistics {
57
56
  * Contains the current retries histogram. Index 0 means 0 retries, index 2, 2 retries, and so on
58
57
  */
59
58
  requestRetryHistogram = [];
60
- /**
61
- * Contains the associated Configuration instance
62
- */
63
- config;
64
59
  keyValueStore = undefined;
65
- persistStateKey = `SDK_CRAWLER_STATISTICS_${this.id}`;
60
+ persistStateKey;
66
61
  logIntervalMillis;
67
62
  logMessage;
68
63
  listener;
@@ -70,8 +65,14 @@ export class Statistics {
70
65
  log;
71
66
  instanceStart;
72
67
  logInterval;
73
- events;
68
+ _events;
74
69
  persistenceOptions;
70
+ get events() {
71
+ if (!this._events) {
72
+ this._events = serviceLocator.getEventManager();
73
+ }
74
+ return this._events;
75
+ }
75
76
  /**
76
77
  * @internal
77
78
  */
@@ -81,22 +82,22 @@ export class Statistics {
81
82
  logMessage: ow.optional.string,
82
83
  log: ow.optional.object,
83
84
  keyValueStore: ow.optional.object,
84
- config: ow.optional.object,
85
85
  persistenceOptions: ow.optional.object,
86
86
  saveErrorSnapshots: ow.optional.boolean,
87
+ id: ow.optional.any(ow.number, ow.string),
87
88
  }));
88
- const { logIntervalSecs = 60, logMessage = 'Statistics', keyValueStore, config = Configuration.getGlobalConfig(), persistenceOptions = {
89
+ const { logIntervalSecs = 60, logMessage = 'Statistics', keyValueStore, persistenceOptions = {
89
90
  enable: true,
90
- }, saveErrorSnapshots = false, } = options;
91
- this.log = (options.log ?? defaultLog).child({ prefix: 'Statistics' });
91
+ }, saveErrorSnapshots = false, id, } = options;
92
+ this.id = id ?? String(Statistics.id++);
93
+ this.persistStateKey = `CRAWLEE_CRAWLER_STATISTICS_${this.id}`;
94
+ this.log = (options.log ?? serviceLocator.getLogger()).child({ prefix: 'Statistics' });
92
95
  this.errorTracker = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
93
96
  this.errorTrackerRetry = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
94
97
  this.logIntervalMillis = logIntervalSecs * 1000;
95
98
  this.logMessage = logMessage;
96
99
  this.keyValueStore = keyValueStore;
97
100
  this.listener = this.persistState.bind(this);
98
- this.events = config.getEventManager();
99
- this.config = config;
100
101
  this.persistenceOptions = persistenceOptions;
101
102
  // initialize by "resetting"
102
103
  this.reset();
@@ -194,6 +195,14 @@ export class Statistics {
194
195
  this._saveRetryCountForJob(retryCount);
195
196
  this.requestsInProgress.delete(id);
196
197
  }
198
+ /**
199
+ * Discards a started job without affecting the finished/failed counters, e.g. when a request
200
+ * turns out to be skipped (robots.txt, enqueue strategy) after `startJob` was already called for it.
201
+ * @ignore
202
+ */
203
+ discardJob(id) {
204
+ this.requestsInProgress.delete(id);
205
+ }
197
206
  /**
198
207
  * Calculate the current statistics
199
208
  */
@@ -216,7 +225,7 @@ export class Statistics {
216
225
  * displaying the current state in predefined intervals
217
226
  */
218
227
  async startCapturing() {
219
- this.keyValueStore ??= await KeyValueStore.open(null, { config: this.config });
228
+ this.keyValueStore ??= await KeyValueStore.open(null, { config: serviceLocator.getConfiguration() });
220
229
  if (this.state.crawlerStartedAt === null) {
221
230
  this.state.crawlerStartedAt = new Date();
222
231
  }
@@ -258,14 +267,8 @@ export class Statistics {
258
267
  return;
259
268
  }
260
269
  this.log.debug('Persisting state', { persistStateKey: this.persistStateKey });
261
- // use half the interval of `persistState` to avoid race conditions
262
- const persistStateIntervalMillis = this.config.get('persistStateIntervalMillis');
263
- const timeoutSecs = persistStateIntervalMillis / 2_000;
264
270
  await this.keyValueStore
265
- .setValue(this.persistStateKey, this.toJSON(), {
266
- timeoutSecs,
267
- doNotRetryTimeouts: true,
268
- })
271
+ .setValue(this.persistStateKey, this.toJSON())
269
272
  .catch((error) => this.log.warning(`Failed to persist the statistics to ${this.persistStateKey}`, { error }));
270
273
  }
271
274
  /**
@@ -308,7 +311,9 @@ export class Statistics {
308
311
  }
309
312
  _teardown() {
310
313
  // this can be called before a call to startCapturing happens (or in a 'finally' block)
311
- this.events.off("persistState" /* EventType.PERSIST_STATE */, this.listener);
314
+ // Only unsubscribe if event manager was already resolved — avoid eagerly resolving it
315
+ // (e.g. during the constructor's reset() call, which would capture the wrong context)
316
+ this._events?.off("persistState" /* EventType.PERSIST_STATE */, this.listener);
312
317
  if (this.logInterval) {
313
318
  clearInterval(this.logInterval);
314
319
  this.logInterval = null;
@@ -343,4 +348,3 @@ export class Statistics {
343
348
  return result;
344
349
  }
345
350
  }
346
- //# sourceMappingURL=statistics.js.map
@@ -1,25 +1,30 @@
1
- import type { Awaitable, BatchAddRequestsResult, Dictionary } from '@crawlee/types';
1
+ import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
2
2
  import { type RobotsTxtFile } from '@crawlee/utils';
3
3
  import type { SetRequired } from 'type-fest';
4
- import type { RequestProvider, RequestQueueOperationOptions } from '../storages/request_provider.js';
5
- import type { GlobInput, PseudoUrlInput, RegExpInput, RequestTransform } from './shared.js';
6
- export type SkippedRequestCallback = (args: {
7
- url: string;
8
- reason: 'robotsTxt';
9
- }) => Awaitable<void>;
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';
10
8
  export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
11
9
  /** Limit the amount of actually enqueued URLs to this number. Useful for testing across the entire crawling scope. */
12
10
  limit?: number;
13
11
  /** An array of URLs to enqueue. */
14
12
  urls?: readonly string[];
15
- /** A request queue to which the URLs will be enqueued. */
16
- requestQueue?: RequestProvider;
13
+ /** A request manager to which the URLs will be enqueued. */
14
+ requestManager?: IRequestManager;
17
15
  /** A CSS selector matching links to be enqueued. */
18
16
  selector?: string;
19
17
  /** Sets {@link Request.userData} for newly enqueued requests. */
20
18
  userData?: Dictionary;
21
- /** Sets {@link Request.label} for newly enqueued requests. */
19
+ /**
20
+ * Sets {@link Request.label} for newly enqueued requests.
21
+ *
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`.
24
+ */
22
25
  label?: string;
26
+ /** Sets {@link Request.sessionId} for newly enqueued requests. */
27
+ sessionId?: string;
23
28
  /**
24
29
  * If set to `true`, tells the crawler to skip navigation and process the request directly.
25
30
  * @default false
@@ -49,7 +54,6 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
49
54
  * containing patterns matching URLs that will **never** be enqueued.
50
55
  *
51
56
  * The plain objects must include either the `glob` property or the `regexp` property.
52
- * All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
53
57
  *
54
58
  * Glob matching is always case-insensitive.
55
59
  * If you need case-sensitive matching, provide a regexp.
@@ -86,12 +90,12 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
86
90
  */
87
91
  pseudoUrls?: readonly PseudoUrlInput[];
88
92
  /**
89
- * Just before a new {@link Request} is constructed and enqueued to the {@link RequestQueue}, this function can be used
90
- * to remove it or modify its contents such as `userData`, `payload` or, most importantly `uniqueKey`. This is useful
93
+ * After request options are filtered by patterns, this function can be used
94
+ * to remove them or modify their contents such as `userData`, `payload` or, most importantly `uniqueKey`. This is useful
91
95
  * when you need to enqueue multiple `Requests` to the queue that share the same URL, but differ in methods or payloads,
92
96
  * or to dynamically update or create `userData`.
93
97
  *
94
- * For example: by adding `keepUrlFragment: true` to the `request` object, URL fragments will not be removed
98
+ * For example: by adding `keepUrlFragment: true` to the request options, URL fragments will not be removed
95
99
  * when `uniqueKey` is computed.
96
100
  *
97
101
  * **Example:**
@@ -105,9 +109,13 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
105
109
  * }
106
110
  * ```
107
111
  *
108
- * Note that `transformRequestFunction` has a priority over request options
109
- * specified in `globs`, `regexps`, or `pseudoUrls` objects,
110
- * and thus some options could be over-written by `transformRequestFunction`.
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.
114
+ *
115
+ * The function receives a {@link RequestOptions} object and can return either:
116
+ * - The modified {@link RequestOptions} object
117
+ * - `'unchanged'` to keep the original options as-is
118
+ * - A falsy value or `'skip'` to exclude the request from the queue
111
119
  */
112
120
  transformRequestFunction?: RequestTransform;
113
121
  /**
@@ -138,10 +146,21 @@ export interface EnqueueLinksOptions extends RequestQueueOperationOptions {
138
146
  * RobotsTxtFile instance for the current request that triggered the `enqueueLinks`.
139
147
  * If provided, disallowed URLs will be ignored.
140
148
  */
141
- robotsTxtFile?: RobotsTxtFile;
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
+ };
142
158
  /**
143
159
  * When a request is skipped for some reason, you can use this callback to act on it.
144
- * This is currently fired only for requests skipped based on robots.txt file.
160
+ * This is currently fired for requests skipped
161
+ * 1. based on robots.txt file,
162
+ * 2. because they don't match enqueueLinks filters,
163
+ * 3. or because the maxRequestsPerCrawl limit has been reached
145
164
  */
146
165
  onSkippedRequest?: SkippedRequestCallback;
147
166
  }
@@ -208,7 +227,7 @@ export declare enum EnqueueStrategy {
208
227
  * ```javascript
209
228
  * await enqueueLinks({
210
229
  * urls: aListOfFoundUrls,
211
- * requestQueue,
230
+ * requestManager,
212
231
  * selector: 'a.product-detail',
213
232
  * globs: [
214
233
  * 'https://www.example.com/handbags/*',
@@ -220,7 +239,11 @@ export declare enum EnqueueStrategy {
220
239
  * @param options All `enqueueLinks()` parameters are passed via an options object.
221
240
  * @returns Promise that resolves to {@link BatchAddRequestsResult} object.
222
241
  */
223
- export declare function enqueueLinks(options: SetRequired<EnqueueLinksOptions, 'requestQueue' | 'urls'>): Promise<BatchAddRequestsResult>;
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>;
224
247
  /**
225
248
  * @internal
226
249
  * This method helps resolve the baseUrl that will be used for filtering in {@link enqueueLinks}.
@@ -239,4 +262,3 @@ export interface ResolveBaseUrl {
239
262
  originalRequestUrl: string;
240
263
  finalRequestUrl?: string;
241
264
  }
242
- //# sourceMappingURL=enqueue_links.d.ts.map
@@ -1,7 +1,8 @@
1
1
  import ow from 'ow';
2
2
  import { getDomain } from 'tldts';
3
- import log from '@apify/log';
4
- import { constructGlobObjectsFromGlobs, constructRegExpObjectsFromPseudoUrls, constructRegExpObjectsFromRegExps, createRequestOptions, createRequests, filterRequestsByPatterns, } from './shared.js';
3
+ import { Request } from '../request.js';
4
+ import { serviceLocator } from '../service_locator.js';
5
+ import { applyRequestTransform, constructGlobObjectsFromGlobs, constructRegExpObjectsFromPseudoUrls, constructRegExpObjectsFromRegExps, createRequestOptions, filterRequestOptionsByPatterns, } from './shared.js';
5
6
  /**
6
7
  * The different enqueueing strategies available.
7
8
  *
@@ -66,7 +67,7 @@ export var EnqueueStrategy;
66
67
  * ```javascript
67
68
  * await enqueueLinks({
68
69
  * urls: aListOfFoundUrls,
69
- * requestQueue,
70
+ * requestManager,
70
71
  * selector: 'a.product-detail',
71
72
  * globs: [
72
73
  * 'https://www.example.com/handbags/*',
@@ -87,11 +88,13 @@ export async function enqueueLinks(options) {
87
88
  }
88
89
  ow(options, ow.object.exactShape({
89
90
  urls: ow.array.ofType(ow.string),
90
- requestQueue: ow.object.hasKeys('fetchNextRequest', 'addRequest'),
91
+ requestManager: ow.object.hasKeys('addRequestsBatched'),
91
92
  robotsTxtFile: ow.optional.object.hasKeys('isAllowed'),
93
+ respectRobotsTxtFile: ow.optional.any(ow.boolean, ow.object.exactShape({ userAgent: ow.optional.string })),
92
94
  onSkippedRequest: ow.optional.function,
93
95
  forefront: ow.optional.boolean,
94
96
  skipNavigation: ow.optional.boolean,
97
+ sessionId: ow.optional.string,
95
98
  limit: ow.optional.number,
96
99
  selector: ow.optional.string,
97
100
  baseUrl: ow.optional.string,
@@ -105,7 +108,9 @@ export async function enqueueLinks(options) {
105
108
  strategy: ow.optional.string.oneOf(Object.values(EnqueueStrategy)),
106
109
  waitForAllRequestsToBeAdded: ow.optional.boolean,
107
110
  }));
108
- const { requestQueue, limit, urls, pseudoUrls, exclude, globs, regexps, transformRequestFunction, forefront, waitForAllRequestsToBeAdded, robotsTxtFile, onSkippedRequest, } = options;
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;
109
114
  const urlExcludePatternObjects = [];
110
115
  const urlPatternObjects = [];
111
116
  if (exclude?.length) {
@@ -119,7 +124,7 @@ export async function enqueueLinks(options) {
119
124
  }
120
125
  }
121
126
  if (pseudoUrls?.length) {
122
- log.deprecated('`pseudoUrls` option is deprecated, use `globs` or `regexps` instead');
127
+ serviceLocator.getLogger().deprecated('`pseudoUrls` option is deprecated, use `globs` or `regexps` instead');
123
128
  urlPatternObjects.push(...constructRegExpObjectsFromPseudoUrls(pseudoUrls));
124
129
  }
125
130
  if (globs?.length) {
@@ -167,44 +172,61 @@ export async function enqueueLinks(options) {
167
172
  break;
168
173
  }
169
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
+ }
170
185
  let requestOptions = createRequestOptions(urls, options);
171
- if (robotsTxtFile) {
186
+ if (robotsTxtFile && options.respectRobotsTxtFile !== false) {
187
+ const robotsUserAgent = typeof options.respectRobotsTxtFile === 'object' ? (options.respectRobotsTxtFile.userAgent ?? '*') : '*';
172
188
  const skippedRequests = [];
173
189
  requestOptions = requestOptions.filter((request) => {
174
- if (robotsTxtFile.isAllowed(request.url)) {
190
+ if (robotsTxtFile.isAllowed(request.url, robotsUserAgent)) {
175
191
  return true;
176
192
  }
177
193
  skippedRequests.push(request);
178
194
  return false;
179
195
  });
180
- if (onSkippedRequest && skippedRequests.length > 0) {
181
- await Promise.all(skippedRequests.map((request) => {
182
- return onSkippedRequest({ url: request.url, reason: 'robotsTxt' });
183
- }));
184
- }
196
+ await reportSkippedRequests(skippedRequests, 'robotsTxt');
185
197
  }
186
- if (transformRequestFunction) {
187
- requestOptions = requestOptions
188
- .map((request) => transformRequestFunction(request))
189
- .filter((r) => !!r);
190
- }
191
- function createFilteredRequests() {
192
- // No user provided patterns means we can skip an extra filtering step
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;
193
203
  if (urlPatternObjects.length === 0) {
194
- return createRequests(requestOptions, enqueueStrategyPatterns, urlExcludePatternObjects, options.strategy);
204
+ filteredOptions = filterRequestOptionsByPatterns(requestOptions, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, urlExcludePatternObjects, options.strategy, (url) => skippedRequests.push(url));
195
205
  }
196
- // Generate requests based on the user patterns first
197
- const generatedRequestsFromUserFilters = createRequests(requestOptions, urlPatternObjects, urlExcludePatternObjects, options.strategy);
198
- // ...then filter them by the enqueue links strategy (making this an AND check)
199
- return filterRequestsByPatterns(generatedRequestsFromUserFilters, enqueueStrategyPatterns);
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));
200
221
  }
201
- let requests = createFilteredRequests();
202
- if (limit)
203
- requests = requests.slice(0, limit);
204
- const { addedRequests } = await requestQueue.addRequestsBatched(requests, {
222
+ const { addedRequests, requestsOverLimit } = await requestManager.addRequestsBatched(await createFilteredRequests(), {
205
223
  forefront,
206
224
  waitForAllRequestsToBeAdded,
225
+ maxNewRequests: limit,
207
226
  });
227
+ if (requestsOverLimit?.length !== undefined && requestsOverLimit.length > 0) {
228
+ await reportSkippedRequests(requestsOverLimit.map((r) => ({ url: typeof r === 'string' ? r : r.url })), 'enqueueLimit');
229
+ }
208
230
  return { processedRequests: addedRequests, unprocessedRequests: [] };
209
231
  }
210
232
  /**
@@ -247,4 +269,3 @@ export function resolveBaseUrlForEnqueueLinksFiltering({ enqueueStrategy, finalR
247
269
  function ignoreHttpSchema(pattern) {
248
270
  return pattern.replace(/^(https?):\/\//, 'http{s,}://');
249
271
  }
250
- //# sourceMappingURL=enqueue_links.js.map
@@ -1,3 +1,2 @@
1
1
  export * from './enqueue_links.js';
2
2
  export * from './shared.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,2 @@
1
1
  export * from './enqueue_links.js';
2
2
  export * from './shared.js';
3
- //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
+ import type { Awaitable } from '@crawlee/types';
1
2
  import type { RequestOptions } from '../request.js';
2
- import { Request } from '../request.js';
3
3
  import type { EnqueueLinksOptions } from './enqueue_links.js';
4
4
  export { tryAbsoluteURL } from '@crawlee/utils';
5
5
  export type UrlPatternObject = {
@@ -18,6 +18,11 @@ export type RegExpObject = {
18
18
  regexp: RegExp;
19
19
  } & Pick<RequestOptions, 'method' | 'payload' | 'label' | 'userData' | 'headers'>;
20
20
  export type RegExpInput = RegExp | RegExpObject;
21
+ export type SkippedRequestReason = 'robotsTxt' | 'limit' | 'enqueueLimit' | 'filters' | 'transform' | 'redirect' | 'depth';
22
+ export type SkippedRequestCallback = (args: {
23
+ url: string;
24
+ reason: SkippedRequestReason;
25
+ }) => Awaitable<void>;
21
26
  /**
22
27
  * @ignore
23
28
  */
@@ -45,23 +50,34 @@ export declare function validateGlobPattern(glob: string): string;
45
50
  */
46
51
  export declare function constructRegExpObjectsFromRegExps(regexps: readonly RegExpInput[]): RegExpObject[];
47
52
  /**
53
+ * Filters request options by URL patterns and merges pattern-level options (label, userData, method, payload, headers)
54
+ * from the first matching pattern into each RequestOptions entry.
55
+ *
56
+ * When `includePatterns` is empty/undefined, all options pass through (only exclude filtering applies).
48
57
  * @ignore
49
58
  */
50
- export declare function createRequests(requestOptions: (string | RequestOptions)[], urlPatternObjects?: UrlPatternObject[], excludePatternObjects?: UrlPatternObject[], strategy?: EnqueueLinksOptions['strategy']): Request[];
51
- export declare function filterRequestsByPatterns(requests: Request[], patterns?: UrlPatternObject[]): Request[];
59
+ export declare function filterRequestOptionsByPatterns(requestOptions: RequestOptions[], includePatterns: UrlPatternObject[] | undefined, excludePatterns?: UrlPatternObject[], strategy?: EnqueueLinksOptions['strategy'], onSkippedUrl?: (url: string) => void): RequestOptions[];
52
60
  /**
53
61
  * @ignore
54
62
  */
55
- export declare function createRequestOptions(sources: readonly (string | Record<string, unknown>)[], options?: Pick<EnqueueLinksOptions, 'label' | 'userData' | 'baseUrl' | 'skipNavigation' | 'strategy'>): RequestOptions[];
63
+ export declare function createRequestOptions(sources: readonly (string | Record<string, unknown>)[], options?: Pick<EnqueueLinksOptions, 'label' | 'userData' | 'baseUrl' | 'skipNavigation' | 'sessionId' | 'strategy'>): RequestOptions[];
56
64
  /**
57
- * Takes an Apify {@link RequestOptions} object and changes its attributes in a desired way. This user-function is used
58
- * {@link enqueueLinks} to modify requests before enqueuing them.
65
+ * Takes a {@link RequestOptions} object and changes its attributes in a desired way. This user-function is used
66
+ * by {@link enqueueLinks} to modify request options before they are converted to {@link Request} instances.
59
67
  */
60
68
  export interface RequestTransform {
61
69
  /**
62
70
  * @param original Request options to be modified.
63
- * @returns The modified request options to enqueue.
71
+ * @returns The modified request options to enqueue, `'unchanged'` to keep the original options as-is,
72
+ * or a falsy value / `'skip'` to exclude the request from the queue.
64
73
  */
65
- (original: RequestOptions): RequestOptions | false | undefined | null;
74
+ (original: RequestOptions): RequestOptions | false | undefined | null | 'skip' | 'unchanged';
66
75
  }
67
- //# sourceMappingURL=shared.d.ts.map
76
+ /**
77
+ * Applies a {@link RequestTransform} function to a list of request options.
78
+ * Options for which the transform returns a falsy value are removed from the list.
79
+ * @param onSkipped Called with the original request options when the transform returns a falsy value (i.e. the request is skipped).
80
+ * @ignore
81
+ * @internal
82
+ */
83
+ export declare function applyRequestTransform(requestOptions: RequestOptions[], transformFn: RequestTransform, onSkipped?: (requestOptions: RequestOptions) => void): RequestOptions[];
@@ -1,7 +1,6 @@
1
1
  import { URL } from 'node:url';
2
- import { minimatch } from 'minimatch';
2
+ import { Minimatch } from 'minimatch';
3
3
  import { purlToRegExp } from '@apify/pseudo_url';
4
- import { Request } from '../request.js';
5
4
  export { tryAbsoluteURL } from '@crawlee/utils';
6
5
  const MAX_ENQUEUE_LINKS_CACHE_SIZE = 1000;
7
6
  /**
@@ -110,51 +109,37 @@ export function constructRegExpObjectsFromRegExps(regexps) {
110
109
  });
111
110
  }
112
111
  /**
112
+ * Filters request options by URL patterns and merges pattern-level options (label, userData, method, payload, headers)
113
+ * from the first matching pattern into each RequestOptions entry.
114
+ *
115
+ * When `includePatterns` is empty/undefined, all options pass through (only exclude filtering applies).
113
116
  * @ignore
114
117
  */
115
- export function createRequests(requestOptions, urlPatternObjects, excludePatternObjects = [], strategy) {
118
+ export function filterRequestOptionsByPatterns(requestOptions, includePatterns, excludePatterns = [], strategy, onSkippedUrl) {
119
+ const excludeMatchers = excludePatterns.map(createPatternObjectMatcher);
120
+ const includeMatchers = includePatterns?.length ? includePatterns.map(createPatternObjectMatcher) : undefined;
116
121
  return requestOptions
117
- .map((opts) => ({ url: typeof opts === 'string' ? opts : opts.url, opts }))
118
122
  .filter(({ url }) => {
119
- return !excludePatternObjects.some((excludePatternObject) => {
120
- const { regexp, glob } = excludePatternObject;
121
- return (regexp && url.match(regexp)) || (glob && minimatch(url, glob, { nocase: true }));
122
- });
123
+ const matchesExclude = excludeMatchers.some(({ match }) => match(url));
124
+ if (matchesExclude) {
125
+ onSkippedUrl?.(url);
126
+ }
127
+ return !matchesExclude;
123
128
  })
124
- .map(({ url, opts }) => {
125
- if (!urlPatternObjects || !urlPatternObjects.length) {
126
- return new Request(typeof opts === 'string' ? { url: opts, enqueueStrategy: strategy } : { ...opts });
127
- }
128
- for (const urlPatternObject of urlPatternObjects) {
129
- const { regexp, glob, ...requestRegExpOptions } = urlPatternObject;
130
- if ((regexp && url.match(regexp)) || (glob && minimatch(url, glob, { nocase: true }))) {
131
- const request = typeof opts === 'string'
132
- ? { url: opts, ...requestRegExpOptions, enqueueStrategy: strategy }
133
- : { ...opts, ...requestRegExpOptions, enqueueStrategy: strategy };
134
- return new Request(request);
129
+ .map((opts) => {
130
+ if (!includeMatchers) {
131
+ return { ...opts, enqueueStrategy: strategy };
132
+ }
133
+ for (const { match, glob, regexp, ...patternOptions } of includeMatchers) {
134
+ if (match(opts.url)) {
135
+ return { ...opts, ...patternOptions, enqueueStrategy: strategy };
135
136
  }
136
137
  }
137
138
  // didn't match any positive pattern
139
+ onSkippedUrl?.(opts.url);
138
140
  return null;
139
141
  })
140
- .filter((request) => request);
141
- }
142
- export function filterRequestsByPatterns(requests, patterns) {
143
- if (!patterns?.length) {
144
- return requests;
145
- }
146
- const filtered = [];
147
- for (const request of requests) {
148
- for (const urlPatternObject of patterns) {
149
- const { regexp, glob } = urlPatternObject;
150
- if ((regexp && request.url.match(regexp)) || (glob && minimatch(request.url, glob, { nocase: true }))) {
151
- filtered.push(request);
152
- // Break the pattern loop, as we already matched this request once
153
- break;
154
- }
155
- }
156
- }
157
- return filtered;
142
+ .filter((opts) => opts !== null);
158
143
  }
159
144
  /**
160
145
  * @ignore
@@ -184,7 +169,53 @@ export function createRequestOptions(sources, options = {}) {
184
169
  if (options.skipNavigation) {
185
170
  requestOptions.skipNavigation = true;
186
171
  }
172
+ if (options.sessionId) {
173
+ requestOptions.sessionId = options.sessionId;
174
+ }
187
175
  return requestOptions;
188
176
  });
189
177
  }
190
- //# sourceMappingURL=shared.js.map
178
+ /**
179
+ * @ignore
180
+ */
181
+ function createPatternObjectMatcher(urlPatternObject) {
182
+ const { regexp, glob } = urlPatternObject;
183
+ let match;
184
+ if (regexp) {
185
+ match = (url) => regexp.test(url);
186
+ }
187
+ else if (glob) {
188
+ const m = new Minimatch(glob, { nocase: true });
189
+ match = (url) => m.match(url);
190
+ }
191
+ else {
192
+ match = () => false;
193
+ }
194
+ return { ...urlPatternObject, match };
195
+ }
196
+ /**
197
+ * Applies a {@link RequestTransform} function to a list of request options.
198
+ * Options for which the transform returns a falsy value are removed from the list.
199
+ * @param onSkipped Called with the original request options when the transform returns a falsy value (i.e. the request is skipped).
200
+ * @ignore
201
+ * @internal
202
+ */
203
+ export function applyRequestTransform(requestOptions, transformFn, onSkipped) {
204
+ return requestOptions
205
+ .map((opts) => {
206
+ const transformed = transformFn(opts);
207
+ if (transformed === 'skip') {
208
+ onSkipped?.(opts);
209
+ return null;
210
+ }
211
+ if (transformed === 'unchanged') {
212
+ return opts;
213
+ }
214
+ if (!transformed) {
215
+ onSkipped?.(opts);
216
+ return null;
217
+ }
218
+ return transformed;
219
+ })
220
+ .filter((r) => r !== null);
221
+ }