@crawlee/core 4.0.0-beta.95 → 4.0.0-beta.97
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/configuration.d.ts +6 -0
- package/configuration.js +6 -0
- package/crawlers/crawler_commons.d.ts +19 -0
- package/crawlers/index.d.ts +0 -1
- package/crawlers/index.js +0 -1
- package/package.json +6 -6
- package/router.d.ts +56 -5
- package/router.js +53 -3
- package/crawlers/crawler_utils.d.ts +0 -9
- package/crawlers/crawler_utils.js +0 -11
package/configuration.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export declare const crawleeConfigFields: {
|
|
|
24
24
|
memoryMbytes: ConfigField<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
|
|
25
25
|
/** @default 60_000 */
|
|
26
26
|
persistStateIntervalMillis: ConfigField<z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
|
|
27
|
+
/**
|
|
28
|
+
* Internal safety-net timeout for a single request, in milliseconds. When unset the crawler derives it from
|
|
29
|
+
* the request handler timeout (twice it, and never below 5 minutes).
|
|
30
|
+
*/
|
|
31
|
+
internalTimeoutMillis: ConfigField<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
|
|
27
32
|
/** @default 1_000 */
|
|
28
33
|
systemInfoIntervalMillis: ConfigField<z.ZodDefault<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>>>;
|
|
29
34
|
/** @default 'INPUT' */
|
|
@@ -100,6 +105,7 @@ export interface Configuration extends ResolvedConfigValues {
|
|
|
100
105
|
* `defaultKeyValueStoreId` | `CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID` | `'default'`
|
|
101
106
|
* `defaultRequestQueueId` | `CRAWLEE_DEFAULT_REQUEST_QUEUE_ID` | `'default'`
|
|
102
107
|
* `persistStateIntervalMillis` | `CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS` | `60_000`
|
|
108
|
+
* `internalTimeoutMillis` | `CRAWLEE_INTERNAL_TIMEOUT` | -
|
|
103
109
|
* `purgeOnStart` | `CRAWLEE_PURGE_ON_START` | `true`
|
|
104
110
|
* `persistStorage` | `CRAWLEE_PERSIST_STORAGE` | `true`
|
|
105
111
|
* `storageDir` | `CRAWLEE_STORAGE_DIR` | `'./storage'`
|
package/configuration.js
CHANGED
|
@@ -54,6 +54,11 @@ export const crawleeConfigFields = {
|
|
|
54
54
|
memoryMbytes: field(coerceNumber.optional(), 'CRAWLEE_MEMORY_MBYTES'),
|
|
55
55
|
/** @default 60_000 */
|
|
56
56
|
persistStateIntervalMillis: field(coerceNumber.default(60_000), 'CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS'),
|
|
57
|
+
/**
|
|
58
|
+
* Internal safety-net timeout for a single request, in milliseconds. When unset the crawler derives it from
|
|
59
|
+
* the request handler timeout (twice it, and never below 5 minutes).
|
|
60
|
+
*/
|
|
61
|
+
internalTimeoutMillis: field(coerceNumber.optional(), 'CRAWLEE_INTERNAL_TIMEOUT'),
|
|
57
62
|
/** @default 1_000 */
|
|
58
63
|
systemInfoIntervalMillis: field(coerceNumber.default(1_000)),
|
|
59
64
|
/** @default 'INPUT' */
|
|
@@ -118,6 +123,7 @@ export const crawleeConfigFields = {
|
|
|
118
123
|
* `defaultKeyValueStoreId` | `CRAWLEE_DEFAULT_KEY_VALUE_STORE_ID` | `'default'`
|
|
119
124
|
* `defaultRequestQueueId` | `CRAWLEE_DEFAULT_REQUEST_QUEUE_ID` | `'default'`
|
|
120
125
|
* `persistStateIntervalMillis` | `CRAWLEE_PERSIST_STATE_INTERVAL_MILLIS` | `60_000`
|
|
126
|
+
* `internalTimeoutMillis` | `CRAWLEE_INTERNAL_TIMEOUT` | -
|
|
121
127
|
* `purgeOnStart` | `CRAWLEE_PURGE_ON_START` | `true`
|
|
122
128
|
* `persistStorage` | `CRAWLEE_PERSIST_STORAGE` | `true`
|
|
123
129
|
* `storageDir` | `CRAWLEE_STORAGE_DIR` | `'./storage'`
|
|
@@ -184,6 +184,25 @@ export interface CrawlingContext<UserData extends Dictionary = Dictionary> exten
|
|
|
184
184
|
* Register a function to be called at the very end of the request handling process. This is useful for resources that should be accessible to error handlers, for instance.
|
|
185
185
|
*/
|
|
186
186
|
registerDeferredCleanup(cleanup: () => Promise<unknown>): void;
|
|
187
|
+
/**
|
|
188
|
+
* Gives the current request `secs` more seconds to finish, for when how long it needs is only apparent
|
|
189
|
+
* once it is already running - a listing page that turns out to have far more to scroll through than
|
|
190
|
+
* usual, say. Prefer `requestHandlerTimeoutSecs`, or a per-route override via
|
|
191
|
+
* {@link Router.addHandler|`router.addHandler`}, whenever the time needed is known up front.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* router.addHandler('LIST', async ({ extendTimeout, page }) => {
|
|
195
|
+
* const pageCount = await countPages(page);
|
|
196
|
+
* extendTimeout(pageCount * 10);
|
|
197
|
+
* await scrapeAllPages(page);
|
|
198
|
+
* });
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* Extends the request handler's own timeout and the crawler's internal one together, so the extension
|
|
202
|
+
* is not immediately undone by the latter. Calling it from a handler that has already timed out does
|
|
203
|
+
* nothing.
|
|
204
|
+
*/
|
|
205
|
+
extendTimeout(secs: number): void;
|
|
187
206
|
}
|
|
188
207
|
/**
|
|
189
208
|
* A partial implementation of {@link RestrictedCrawlingContext} that stores parameters of calls to context methods for later inspection.
|
package/crawlers/index.d.ts
CHANGED
package/crawlers/index.js
CHANGED
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.97",
|
|
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"
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"@apify/datastructures": "^2.0.3",
|
|
52
52
|
"@apify/log": "^2.5.18",
|
|
53
53
|
"@apify/pseudo_url": "^2.0.59",
|
|
54
|
-
"@apify/timeout": "^0.
|
|
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.97",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.97",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.97",
|
|
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": "43008dd43f4832d083353ba1b731c0b4607c9cfa"
|
|
83
83
|
}
|
package/router.d.ts
CHANGED
|
@@ -70,6 +70,20 @@ export interface RouterHandler<Context extends Omit<RestrictedCrawlingContext, '
|
|
|
70
70
|
(ctx: Context): Awaitable<void>;
|
|
71
71
|
}
|
|
72
72
|
export type GetUserDataFromRequest<T> = T extends Request<infer Y> ? Y : never;
|
|
73
|
+
/**
|
|
74
|
+
* Per-route overrides, passed as the last argument of {@link Router.addHandler|`addHandler`} and
|
|
75
|
+
* {@link Router.addDefaultHandler|`addDefaultHandler`}.
|
|
76
|
+
*/
|
|
77
|
+
export interface RouteOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Overrides the crawler's `requestHandlerTimeoutSecs` for this route only. Useful when one kind of page
|
|
80
|
+
* needs markedly more time than the rest - a listing page behind an infinite scroll, say - and you do not
|
|
81
|
+
* want to raise the timeout for every other page to accommodate it.
|
|
82
|
+
*
|
|
83
|
+
* Applies only to this route's handler. The navigation and the navigation hooks keep their own timeouts.
|
|
84
|
+
*/
|
|
85
|
+
requestHandlerTimeoutSecs?: number;
|
|
86
|
+
}
|
|
73
87
|
export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary>> = {
|
|
74
88
|
[Label in keyof Routes]: (ctx: Omit<Context, 'request'> & {
|
|
75
89
|
request: Request<Routes[Label]>;
|
|
@@ -180,10 +194,31 @@ export type RouterRoutes<Context, Routes extends Record<keyof Routes, Dictionary
|
|
|
180
194
|
* request.userData.price; // number, inferred from the schema and validated at runtime
|
|
181
195
|
* });
|
|
182
196
|
* ```
|
|
197
|
+
*
|
|
198
|
+
* A single route can take longer than the rest without raising the crawler-wide
|
|
199
|
+
* `requestHandlerTimeoutSecs` for everything - pass a per-route timeout as the last argument:
|
|
200
|
+
*
|
|
201
|
+
* ```ts
|
|
202
|
+
* // LIST pages scroll through a lot of content, DETAIL pages are quick
|
|
203
|
+
* router.addHandler('LIST', async (ctx) => { ... }, { requestHandlerTimeoutSecs: 120 });
|
|
204
|
+
* router.addHandler('DETAIL', async (ctx) => { ... }); // keeps the crawler's default
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* When the time a route needs is only apparent once it is already running, call
|
|
208
|
+
* {@link CrawlingContext.extendTimeout|`context.extendTimeout`} from inside the handler:
|
|
209
|
+
*
|
|
210
|
+
* ```ts
|
|
211
|
+
* router.addHandler('LIST', async ({ page, extendTimeout }) => {
|
|
212
|
+
* const pageCount = await countPages(page);
|
|
213
|
+
* extendTimeout(pageCount * 10); // ask for 10 more seconds per page
|
|
214
|
+
* await scrapeAllPages(page);
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
183
217
|
*/
|
|
184
218
|
export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enqueueLinks'>, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> {
|
|
185
219
|
private readonly routes;
|
|
186
220
|
private readonly schemas;
|
|
221
|
+
private readonly timeouts;
|
|
187
222
|
private readonly middlewares;
|
|
188
223
|
/**
|
|
189
224
|
* use Router.create() instead!
|
|
@@ -192,22 +227,26 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
192
227
|
private constructor();
|
|
193
228
|
/**
|
|
194
229
|
* Registers new route handler for given label. When the router declares a route map, the
|
|
195
|
-
* `label` is restricted to the declared labels and `request.userData` is typed accordingly.
|
|
230
|
+
* `label` is restricted to the declared labels and `request.userData` is typed accordingly. Pass
|
|
231
|
+
* {@link RouteOptions|`options`} to give this route its own `requestHandlerTimeoutSecs`,
|
|
232
|
+
* overriding the crawler's default for requests with this label.
|
|
196
233
|
*/
|
|
197
|
-
addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label], Routes>) => Awaitable<void
|
|
234
|
+
addHandler<Label extends keyof Routes & string>(label: Label, handler: (ctx: RouterHandlerContext<Context, Routes[Label], Routes>) => Awaitable<void>, options?: RouteOptions): void;
|
|
198
235
|
/**
|
|
199
236
|
* Registers new route handler for given label, explicitly typing `request.userData` via the
|
|
200
237
|
* `UserData` type argument. Useful when the router has no declared route map (the open default)
|
|
201
238
|
* and you want to type a single handler, or to register a handler under a `symbol` label.
|
|
202
239
|
*/
|
|
203
|
-
addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void
|
|
240
|
+
addHandler<UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(label: RouterLabel<Routes>, handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>, options?: RouteOptions): void;
|
|
204
241
|
/**
|
|
205
242
|
* Registers default route handler. As a fallback it can receive any request (including labels not
|
|
206
243
|
* declared in the route map). When the router was created with a {@link defaultRoute} schema,
|
|
207
244
|
* `request.userData` is typed from it; otherwise it defaults to the context's (loosely typed) `userData`.
|
|
208
|
-
* Pass an explicit `UserData` type argument to narrow it.
|
|
245
|
+
* Pass an explicit `UserData` type argument to narrow it. Pass {@link RouteOptions|`options`} to give the
|
|
246
|
+
* default route its own `requestHandlerTimeoutSecs`, overriding the crawler's default for requests that fall
|
|
247
|
+
* through to it.
|
|
209
248
|
*/
|
|
210
|
-
addDefaultHandler<UserData extends Dictionary = DefaultRouteUserData<Routes, GetUserDataFromRequest<Context['request']>>>(handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void
|
|
249
|
+
addDefaultHandler<UserData extends Dictionary = DefaultRouteUserData<Routes, GetUserDataFromRequest<Context['request']>>>(handler: (ctx: RouterHandlerContext<Context, UserData, Routes>) => Awaitable<void>, options?: RouteOptions): void;
|
|
211
250
|
/**
|
|
212
251
|
* Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
|
|
213
252
|
* to validate `request.userData` when requests are added.
|
|
@@ -219,6 +258,18 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
219
258
|
* Multiple middlewares can be registered, they will be fired in the same order.
|
|
220
259
|
*/
|
|
221
260
|
use(middleware: (ctx: Context) => Awaitable<void>): void;
|
|
261
|
+
/**
|
|
262
|
+
* Returns the `requestHandlerTimeoutSecs` registered for a label, or `undefined` when the route did not
|
|
263
|
+
* override it and the crawler's own timeout should apply. Falls back to the default route the same way
|
|
264
|
+
* {@link Router.getHandler|`getHandler`} does, so a label with no route of its own inherits whatever
|
|
265
|
+
* the default route asked for. Used by the crawler; not meant to be called directly.
|
|
266
|
+
*/
|
|
267
|
+
getTimeoutSecs(label?: string | symbol): number | undefined;
|
|
268
|
+
/**
|
|
269
|
+
* The longest `requestHandlerTimeoutSecs` any route asked for, or `undefined` when no route overrides it.
|
|
270
|
+
* The crawler needs an upper bound up front, before it knows which routes a run will actually hit.
|
|
271
|
+
*/
|
|
272
|
+
getMaxTimeoutSecs(): number | undefined;
|
|
222
273
|
/**
|
|
223
274
|
* Returns route handler for given label. If no label is provided, the default request handler will be returned.
|
|
224
275
|
*/
|
package/router.js
CHANGED
|
@@ -139,29 +139,58 @@ export async function validateUserData(label, schema, userData) {
|
|
|
139
139
|
* request.userData.price; // number, inferred from the schema and validated at runtime
|
|
140
140
|
* });
|
|
141
141
|
* ```
|
|
142
|
+
*
|
|
143
|
+
* A single route can take longer than the rest without raising the crawler-wide
|
|
144
|
+
* `requestHandlerTimeoutSecs` for everything - pass a per-route timeout as the last argument:
|
|
145
|
+
*
|
|
146
|
+
* ```ts
|
|
147
|
+
* // LIST pages scroll through a lot of content, DETAIL pages are quick
|
|
148
|
+
* router.addHandler('LIST', async (ctx) => { ... }, { requestHandlerTimeoutSecs: 120 });
|
|
149
|
+
* router.addHandler('DETAIL', async (ctx) => { ... }); // keeps the crawler's default
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* When the time a route needs is only apparent once it is already running, call
|
|
153
|
+
* {@link CrawlingContext.extendTimeout|`context.extendTimeout`} from inside the handler:
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* router.addHandler('LIST', async ({ page, extendTimeout }) => {
|
|
157
|
+
* const pageCount = await countPages(page);
|
|
158
|
+
* extendTimeout(pageCount * 10); // ask for 10 more seconds per page
|
|
159
|
+
* await scrapeAllPages(page);
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
142
162
|
*/
|
|
143
163
|
export class Router {
|
|
144
164
|
routes = new Map();
|
|
145
165
|
schemas = new Map();
|
|
166
|
+
timeouts = new Map();
|
|
146
167
|
middlewares = [];
|
|
147
168
|
/**
|
|
148
169
|
* use Router.create() instead!
|
|
149
170
|
* @ignore
|
|
150
171
|
*/
|
|
151
172
|
constructor() { }
|
|
152
|
-
addHandler(label, handler) {
|
|
173
|
+
addHandler(label, handler, options = {}) {
|
|
153
174
|
this.validate(label);
|
|
154
175
|
this.routes.set(label, handler);
|
|
176
|
+
if (options.requestHandlerTimeoutSecs !== undefined) {
|
|
177
|
+
this.timeouts.set(label, options.requestHandlerTimeoutSecs);
|
|
178
|
+
}
|
|
155
179
|
}
|
|
156
180
|
/**
|
|
157
181
|
* Registers default route handler. As a fallback it can receive any request (including labels not
|
|
158
182
|
* declared in the route map). When the router was created with a {@link defaultRoute} schema,
|
|
159
183
|
* `request.userData` is typed from it; otherwise it defaults to the context's (loosely typed) `userData`.
|
|
160
|
-
* Pass an explicit `UserData` type argument to narrow it.
|
|
184
|
+
* Pass an explicit `UserData` type argument to narrow it. Pass {@link RouteOptions|`options`} to give the
|
|
185
|
+
* default route its own `requestHandlerTimeoutSecs`, overriding the crawler's default for requests that fall
|
|
186
|
+
* through to it.
|
|
161
187
|
*/
|
|
162
|
-
addDefaultHandler(handler) {
|
|
188
|
+
addDefaultHandler(handler, options = {}) {
|
|
163
189
|
this.validate(defaultRoute);
|
|
164
190
|
this.routes.set(defaultRoute, handler);
|
|
191
|
+
if (options.requestHandlerTimeoutSecs !== undefined) {
|
|
192
|
+
this.timeouts.set(defaultRoute, options.requestHandlerTimeoutSecs);
|
|
193
|
+
}
|
|
165
194
|
}
|
|
166
195
|
/**
|
|
167
196
|
* Returns the {@link RouteSchemas|Standard Schema} registered for a label, if any. Used by the crawler
|
|
@@ -190,6 +219,25 @@ export class Router {
|
|
|
190
219
|
use(middleware) {
|
|
191
220
|
this.middlewares.push(middleware);
|
|
192
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Returns the `requestHandlerTimeoutSecs` registered for a label, or `undefined` when the route did not
|
|
224
|
+
* override it and the crawler's own timeout should apply. Falls back to the default route the same way
|
|
225
|
+
* {@link Router.getHandler|`getHandler`} does, so a label with no route of its own inherits whatever
|
|
226
|
+
* the default route asked for. Used by the crawler; not meant to be called directly.
|
|
227
|
+
*/
|
|
228
|
+
getTimeoutSecs(label) {
|
|
229
|
+
if (label && this.routes.has(label)) {
|
|
230
|
+
return this.timeouts.get(label);
|
|
231
|
+
}
|
|
232
|
+
return this.timeouts.get(defaultRoute);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The longest `requestHandlerTimeoutSecs` any route asked for, or `undefined` when no route overrides it.
|
|
236
|
+
* The crawler needs an upper bound up front, before it knows which routes a run will actually hit.
|
|
237
|
+
*/
|
|
238
|
+
getMaxTimeoutSecs() {
|
|
239
|
+
return this.timeouts.size > 0 ? Math.max(...this.timeouts.values()) : undefined;
|
|
240
|
+
}
|
|
193
241
|
/**
|
|
194
242
|
* Returns route handler for given label. If no label is provided, the default request handler will be returned.
|
|
195
243
|
*/
|
|
@@ -233,6 +281,8 @@ export class Router {
|
|
|
233
281
|
obj.addDefaultHandler = router.addDefaultHandler.bind(router);
|
|
234
282
|
obj.getSchema = router.getSchema.bind(router);
|
|
235
283
|
obj.getHandler = router.getHandler.bind(router);
|
|
284
|
+
obj.getTimeoutSecs = router.getTimeoutSecs.bind(router);
|
|
285
|
+
obj.getMaxTimeoutSecs = router.getMaxTimeoutSecs.bind(router);
|
|
236
286
|
obj.use = router.use.bind(router);
|
|
237
287
|
// `Reflect.ownKeys` (unlike `Object.entries`) also yields the `defaultRoute` symbol key.
|
|
238
288
|
for (const label of Reflect.ownKeys(routesOrSchemas ?? {})) {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { TimeoutError } from '@apify/timeout';
|
|
2
|
-
/**
|
|
3
|
-
* Handles timeout request
|
|
4
|
-
* @internal
|
|
5
|
-
*/
|
|
6
|
-
export function handleRequestTimeout({ session, errorMessage }) {
|
|
7
|
-
session?.markBad();
|
|
8
|
-
const timeoutMillis = /(\d+)\s?ms/.exec(errorMessage)?.[1]; // first capturing group
|
|
9
|
-
const timeoutSecs = Number(timeoutMillis) / 1000;
|
|
10
|
-
throw new TimeoutError(`Navigation timed out after ${timeoutSecs} seconds.`);
|
|
11
|
-
}
|