@crawlee/core 4.0.0-beta.97 → 4.0.0-beta.98

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.97",
3
+ "version": "4.0.0-beta.98",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -53,9 +53,9 @@
53
53
  "@apify/pseudo_url": "^2.0.59",
54
54
  "@apify/timeout": "^0.4.4",
55
55
  "@apify/utilities": "^2.15.5",
56
- "@crawlee/fs-storage": "4.0.0-beta.97",
57
- "@crawlee/types": "4.0.0-beta.97",
58
- "@crawlee/utils": "4.0.0-beta.97",
56
+ "@crawlee/fs-storage": "4.0.0-beta.98",
57
+ "@crawlee/types": "4.0.0-beta.98",
58
+ "@crawlee/utils": "4.0.0-beta.98",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@sapphire/shapeshift": "^4.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -79,5 +79,5 @@
79
79
  }
80
80
  }
81
81
  },
82
- "gitHead": "43008dd43f4832d083353ba1b731c0b4607c9cfa"
82
+ "gitHead": "3b8cd86b13e253ab5fc71e631e12a68f7465cee5"
83
83
  }
@@ -17,11 +17,6 @@ import { RequestDeduplicationCache } from './request_dedup_cache.js';
17
17
  * @internal
18
18
  */
19
19
  const MAX_CACHED_REQUESTS = 2_000_000;
20
- /**
21
- * The maximum number of consecutive no-progress retries for unprocessed requests in `addRequestsBatched()`.
22
- * @internal
23
- */
24
- const MAX_UNPROCESSED_REQUESTS_RETRIES = 3;
25
20
  /**
26
21
  * Represents a queue of URLs to crawl, which is used for deep crawling of websites
27
22
  * where you start with several URLs and then recursively
@@ -327,32 +322,27 @@ export class RequestQueue {
327
322
  const requestIterator = generateRequests();
328
323
  const chunks = peekableAsyncIterable(chunkedAsyncIterable(requestIterator, effectiveChunkSize));
329
324
  const chunksIterator = chunks[Symbol.asyncIterator]();
330
- const attemptToAddToQueueAndAddAnyUnprocessed = async (providedRequests, cache = true, unsuccessfulAttempts = 0) => {
331
- const resultsToReturn = [];
332
- const apiResult = await this.addRequests(providedRequests, { forefront: options.forefront, cache });
333
- resultsToReturn.push(...apiResult.processedRequests);
334
- if (apiResult.unprocessedRequests.length) {
335
- // Count attempts that make no progress, so permanently rejected requests (e.g. a malformed
336
- // `userData` shape causing a 400) don't loop forever. Any progress resets the counter.
337
- const attempts = apiResult.processedRequests.length ? 0 : unsuccessfulAttempts + 1;
338
- if (attempts >= MAX_UNPROCESSED_REQUESTS_RETRIES) {
339
- this.log.warning(`Some requests were consistently rejected by the request queue and will be skipped after ${MAX_UNPROCESSED_REQUESTS_RETRIES} attempts. This usually means the request data is malformed (e.g. an invalid 'userData' shape).`, { unprocessedRequests: apiResult.unprocessedRequests });
340
- return resultsToReturn;
341
- }
342
- await sleep(waitBetweenBatchesMillis);
343
- resultsToReturn.push(...(await attemptToAddToQueueAndAddAnyUnprocessed(providedRequests.filter((r) => !apiResult.processedRequests.some((pr) => pr.uniqueKey === r.uniqueKey)), false, attempts)));
344
- }
345
- return resultsToReturn;
346
- };
347
325
  /**
348
326
  * Process a chunk: send it to the queue, then update the remaining budget if maxNewRequests is active.
327
+ *
328
+ * Requests the backend reports as unprocessed are warned about and skipped rather than retried:
329
+ * `unprocessedRequests` is what remains after the backend's own transient-error handling - a
330
+ * semantic rejection (e.g. a malformed `userData` shape) that re-sending would only re-poke.
331
+ * Retrying transient failures is the storage backend's job, not the frontend's.
349
332
  */
350
333
  const processChunk = async (chunk, cache = true) => {
351
- const results = await attemptToAddToQueueAndAddAnyUnprocessed(chunk, cache);
334
+ const { processedRequests, unprocessedRequests } = await this.addRequests(chunk, {
335
+ forefront: options.forefront,
336
+ cache,
337
+ });
338
+ if (unprocessedRequests.length > 0) {
339
+ this.log.warning('Some requests were rejected by the request queue and will be skipped. ' +
340
+ "This usually means the request data is malformed (e.g. an invalid 'userData' shape).", { unprocessedRequests });
341
+ }
352
342
  if (maxNewRequests !== undefined) {
353
- remainingBudget -= results.filter((r) => !r.wasAlreadyPresent).length;
343
+ remainingBudget -= processedRequests.filter((r) => !r.wasAlreadyPresent).length;
354
344
  }
355
- return results;
345
+ return processedRequests;
356
346
  };
357
347
  /**
358
348
  * Build the final result. When maxNewRequests is set, drains any remaining items