@crawlee/core 4.0.0-beta.97 → 4.0.0-beta.99
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.
|
@@ -17,7 +17,6 @@ export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
|
17
17
|
* When the route map is open (the default `Record<string, ...>`), this is just the regular loose
|
|
18
18
|
* {@link Source} input. When the map declares concrete labels, providing a `label` requires the matching
|
|
19
19
|
* `userData` shape and rejects labels not present in the map; unlabeled requests keep loose `userData`.
|
|
20
|
-
* @internal
|
|
21
20
|
*/
|
|
22
21
|
export type LabeledSource<Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? string | Source : string | Request | ({
|
|
23
22
|
requestsFromUrl?: string;
|
|
@@ -38,7 +37,6 @@ export type TypedRequestsLike<Routes extends Record<keyof Routes, Dictionary>> =
|
|
|
38
37
|
/**
|
|
39
38
|
* The label-aware `addRequests` method signature exposed on a request handler's context when the crawler is
|
|
40
39
|
* bound to a typed router. Mirrors {@link RestrictedCrawlingContext.addRequests} with typed sources.
|
|
41
|
-
* @internal
|
|
42
40
|
*/
|
|
43
41
|
export type TypedContextAddRequests<Routes extends Record<keyof Routes, Dictionary>> = (requestsLike: ReadonlyDeep<LabeledSource<Routes>[]>, options?: ReadonlyDeep<RequestQueueOperationOptions>) => Promise<void>;
|
|
44
42
|
/**
|
|
@@ -59,10 +57,8 @@ type TypedEnqueueLinksOptions<Options, Routes extends Record<keyof Routes, Dicti
|
|
|
59
57
|
* Transforms a context's existing `enqueueLinks` method so that the `label`/`userData` in its options follow
|
|
60
58
|
* the router's route map, while preserving everything else about the signature (argument optionality and
|
|
61
59
|
* return type, which differ between crawler types).
|
|
62
|
-
* @internal
|
|
63
60
|
*/
|
|
64
61
|
export type TypedContextEnqueueLinks<EnqueueLinks, Routes extends Record<keyof Routes, Dictionary>> = EnqueueLinks extends (options?: infer Options) => infer Result ? (options?: TypedEnqueueLinksOptions<Options, Routes>) => Result : EnqueueLinks extends (options: infer Options) => infer Result ? (options: TypedEnqueueLinksOptions<Options, Routes>) => Result : EnqueueLinks;
|
|
65
|
-
/** @internal */
|
|
66
62
|
export type WithRequired<T, K extends keyof T> = T & {
|
|
67
63
|
[P in K]-?: T[P];
|
|
68
64
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/core",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.99",
|
|
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.
|
|
57
|
-
"@crawlee/types": "4.0.0-beta.
|
|
58
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
56
|
+
"@crawlee/fs-storage": "4.0.0-beta.99",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.99",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.99",
|
|
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": "
|
|
82
|
+
"gitHead": "ad2748380941842bb10cff100f4b4caad92049e3"
|
|
83
83
|
}
|
package/router.d.ts
CHANGED
|
@@ -48,7 +48,6 @@ export type RoutesFromSchemas<Schemas extends RouteSchemas> = {
|
|
|
48
48
|
/**
|
|
49
49
|
* The `userData` type of the default route: inferred from the {@link defaultRoute} schema when the route map
|
|
50
50
|
* carries one, otherwise the provided `Fallback`.
|
|
51
|
-
* @internal
|
|
52
51
|
*/
|
|
53
52
|
export type DefaultRouteUserData<Routes, Fallback extends Dictionary> = Routes extends {
|
|
54
53
|
[defaultRoute]: infer DefaultUserData extends Dictionary;
|
|
@@ -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
|
|
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 -=
|
|
343
|
+
remainingBudget -= processedRequests.filter((r) => !r.wasAlreadyPresent).length;
|
|
354
344
|
}
|
|
355
|
-
return
|
|
345
|
+
return processedRequests;
|
|
356
346
|
};
|
|
357
347
|
/**
|
|
358
348
|
* Build the final result. When maxNewRequests is set, drains any remaining items
|
|
@@ -17,8 +17,6 @@ type Hashable = string;
|
|
|
17
17
|
* The manager itself does not resolve identifiers — callers pass explicit `id`, `name`, or `alias` (at most one),
|
|
18
18
|
* and a pre-bound `backendOpener` promise. When none of `id`, `name`, `alias` are provided, the manager automatically
|
|
19
19
|
* assigns a reserved default alias.
|
|
20
|
-
*
|
|
21
|
-
* @ignore
|
|
22
20
|
*/
|
|
23
21
|
export declare class StorageInstanceManager {
|
|
24
22
|
private readonly cache;
|
|
@@ -118,8 +118,6 @@ class StorageCache {
|
|
|
118
118
|
* The manager itself does not resolve identifiers — callers pass explicit `id`, `name`, or `alias` (at most one),
|
|
119
119
|
* and a pre-bound `backendOpener` promise. When none of `id`, `name`, `alias` are provided, the manager automatically
|
|
120
120
|
* assigns a reserved default alias.
|
|
121
|
-
*
|
|
122
|
-
* @ignore
|
|
123
121
|
*/
|
|
124
122
|
export class StorageInstanceManager {
|
|
125
123
|
cache = new StorageCache();
|