@crawlee/basic 4.0.0-beta.170 → 4.0.0-beta.172
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/index.d.ts +10 -0
- package/index.js +10 -0
- package/internals/autoscaling/autoscaled_pool.d.ts +196 -0
- package/internals/autoscaling/autoscaled_pool.js +385 -0
- package/internals/autoscaling/concurrency_system.d.ts +267 -0
- package/internals/autoscaling/concurrency_system.js +364 -0
- package/internals/autoscaling/cpu_load_signal.d.ts +43 -0
- package/internals/autoscaling/cpu_load_signal.js +46 -0
- package/internals/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/internals/autoscaling/event_loop_load_signal.js +60 -0
- package/internals/autoscaling/index.d.ts +9 -0
- package/internals/autoscaling/index.js +9 -0
- package/internals/autoscaling/load_signal.d.ts +98 -0
- package/internals/autoscaling/load_signal.js +103 -0
- package/internals/autoscaling/memory_load_signal.d.ts +47 -0
- package/internals/autoscaling/memory_load_signal.js +104 -0
- package/internals/autoscaling/snapshotter.d.ts +84 -0
- package/internals/autoscaling/snapshotter.js +67 -0
- package/internals/autoscaling/storage_backend_load_signal.d.ts +56 -0
- package/internals/autoscaling/storage_backend_load_signal.js +73 -0
- package/internals/autoscaling/system_status.d.ts +122 -0
- package/internals/autoscaling/system_status.js +139 -0
- package/internals/autoscaling/weighted_avg.d.ts +5 -0
- package/internals/autoscaling/weighted_avg.js +14 -0
- package/internals/basic-crawler.d.ts +11 -2
- package/internals/basic-crawler.js +13 -1
- package/internals/cookie_utils.d.ts +44 -0
- package/internals/cookie_utils.js +122 -0
- package/internals/crawler-run.d.ts +3 -1
- package/internals/crawler-run.js +1 -1
- package/internals/crawlers/context_pipeline.d.ts +70 -0
- package/internals/crawlers/context_pipeline.js +122 -0
- package/internals/crawlers/crawler_commons.d.ts +193 -0
- package/internals/crawlers/crawler_commons.js +1 -0
- package/internals/crawlers/error_snapshotter.d.ts +57 -0
- package/internals/crawlers/error_snapshotter.js +117 -0
- package/internals/crawlers/error_tracker.d.ts +54 -0
- package/internals/crawlers/error_tracker.js +308 -0
- package/internals/crawlers/index.d.ts +5 -0
- package/internals/crawlers/index.js +4 -0
- package/internals/crawlers/internals/types.d.ts +7 -0
- package/internals/crawlers/internals/types.js +1 -0
- package/internals/crawlers/statistics.d.ts +318 -0
- package/internals/crawlers/statistics.js +525 -0
- package/internals/enqueue_links/enqueue_links.d.ts +152 -0
- package/internals/enqueue_links/enqueue_links.js +76 -0
- package/internals/enqueue_links/index.d.ts +2 -0
- package/internals/enqueue_links/index.js +2 -0
- package/internals/enqueue_links/shared.d.ts +98 -0
- package/internals/enqueue_links/shared.js +259 -0
- package/internals/errors.d.ts +58 -0
- package/internals/errors.js +72 -0
- package/internals/http.d.ts +9 -0
- package/internals/http.js +28 -0
- package/internals/router.d.ts +306 -0
- package/internals/router.js +310 -0
- package/internals/session_pool/consts.d.ts +3 -0
- package/internals/session_pool/consts.js +3 -0
- package/internals/session_pool/errors.d.ts +7 -0
- package/internals/session_pool/errors.js +11 -0
- package/internals/session_pool/fingerprint.d.ts +9 -0
- package/internals/session_pool/fingerprint.js +30 -0
- package/internals/session_pool/index.d.ts +4 -0
- package/internals/session_pool/index.js +4 -0
- package/internals/session_pool/session.d.ts +153 -0
- package/internals/session_pool/session.js +221 -0
- package/internals/session_pool/session_pool.d.ts +242 -0
- package/internals/session_pool/session_pool.js +429 -0
- package/internals/sitemap_request_loader.d.ts +196 -0
- package/internals/sitemap_request_loader.js +432 -0
- package/internals/throttling_request_manager.d.ts +217 -0
- package/internals/throttling_request_manager.js +829 -0
- package/package.json +11 -7
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { serviceLocator } from '@crawlee/core';
|
|
2
|
+
import { Cookie, CookieJar } from 'tough-cookie';
|
|
3
|
+
import { CookieParseError } from './session_pool/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export function getCookiesFromResponse(response) {
|
|
8
|
+
const headers = response.headers;
|
|
9
|
+
const cookieHeaders = headers.getSetCookie();
|
|
10
|
+
try {
|
|
11
|
+
return cookieHeaders.map((cookie) => Cookie.parse(cookie));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
throw new CookieParseError(cookieHeaders);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Calculate cookie expiration date
|
|
19
|
+
* @param maxAgeSecs
|
|
20
|
+
* @returns Calculated date by session max age seconds.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function getDefaultCookieExpirationDate(maxAgeSecs) {
|
|
24
|
+
return new Date(Date.now() + maxAgeSecs * 1000);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Transforms tough-cookie to puppeteer cookie.
|
|
28
|
+
* @param toughCookie Cookie from CookieJar
|
|
29
|
+
* @return Cookie compatible with browser pool
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export function toughCookieToBrowserPoolCookie(toughCookie) {
|
|
33
|
+
return {
|
|
34
|
+
name: toughCookie.key,
|
|
35
|
+
value: toughCookie.value,
|
|
36
|
+
// Puppeteer and Playwright expect 'expires' to be 'Unix time in seconds', not ms
|
|
37
|
+
// If there is no expires date (so defaults to Infinity), we don't provide it to the browsers
|
|
38
|
+
expires: toughCookie.expires == null || toughCookie.expires === 'Infinity'
|
|
39
|
+
? undefined
|
|
40
|
+
: toughCookie.expires.getTime() / 1000,
|
|
41
|
+
domain: toughCookie.domain ? `${toughCookie.hostOnly ? '' : '.'}${toughCookie.domain}` : undefined,
|
|
42
|
+
path: toughCookie.path ?? undefined,
|
|
43
|
+
secure: toughCookie.secure,
|
|
44
|
+
httpOnly: toughCookie.httpOnly,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Transforms browser-pool cookie to tough-cookie.
|
|
49
|
+
* @param cookieObject Cookie object (for instance from the `page.cookies` method).
|
|
50
|
+
* @param maxAgeSecs Fallback expiration in seconds when the cookie itself has no `expires`.
|
|
51
|
+
* When omitted, such a cookie is stored as a session cookie (no automatic expiration).
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export function browserPoolCookieToToughCookie(cookieObject, maxAgeSecs) {
|
|
55
|
+
const isExpiresValid = cookieObject.expires && typeof cookieObject.expires === 'number' && cookieObject.expires > 0;
|
|
56
|
+
let expires;
|
|
57
|
+
if (isExpiresValid) {
|
|
58
|
+
expires = new Date(cookieObject.expires * 1000);
|
|
59
|
+
}
|
|
60
|
+
else if (maxAgeSecs != null) {
|
|
61
|
+
expires = getDefaultCookieExpirationDate(maxAgeSecs);
|
|
62
|
+
}
|
|
63
|
+
const domainHasLeadingDot = cookieObject.domain?.startsWith?.('.');
|
|
64
|
+
const domain = domainHasLeadingDot ? cookieObject.domain?.slice?.(1) : cookieObject.domain;
|
|
65
|
+
return new Cookie({
|
|
66
|
+
key: cookieObject.name,
|
|
67
|
+
value: cookieObject.value,
|
|
68
|
+
...(expires !== undefined && { expires }),
|
|
69
|
+
domain,
|
|
70
|
+
path: cookieObject.path,
|
|
71
|
+
secure: cookieObject.secure,
|
|
72
|
+
httpOnly: cookieObject.httpOnly,
|
|
73
|
+
hostOnly: !domainHasLeadingDot,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* @internal
|
|
78
|
+
* @param cookieString The cookie string to attempt parsing
|
|
79
|
+
* @returns Browser pool compatible cookie, or null if cookie cannot be parsed
|
|
80
|
+
*/
|
|
81
|
+
export function cookieStringToToughCookie(cookieString) {
|
|
82
|
+
const parsed = Cookie.parse(cookieString);
|
|
83
|
+
if (parsed) {
|
|
84
|
+
return toughCookieToBrowserPoolCookie(parsed);
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Merges multiple cookie strings. Keys are compared case-sensitively, warning will be logged
|
|
90
|
+
* if we see two cookies with same keys but different casing.
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
export function mergeCookies(url, sourceCookies) {
|
|
94
|
+
const jar = new CookieJar();
|
|
95
|
+
// ignore empty cookies
|
|
96
|
+
for (const sourceCookieString of sourceCookies) {
|
|
97
|
+
// ignore empty cookies
|
|
98
|
+
if (!sourceCookieString)
|
|
99
|
+
continue;
|
|
100
|
+
const cookies = sourceCookieString.split(/ *; */);
|
|
101
|
+
for (const cookieString of cookies) {
|
|
102
|
+
// ignore extra spaces
|
|
103
|
+
if (!cookieString)
|
|
104
|
+
continue;
|
|
105
|
+
const cookie = Cookie.parse(cookieString);
|
|
106
|
+
if (!cookie) {
|
|
107
|
+
serviceLocator.getLogger().warning(`Skipping malformed cookie fragment: '${cookieString}'`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
|
|
111
|
+
return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
|
|
112
|
+
});
|
|
113
|
+
if (similarKeyCookie) {
|
|
114
|
+
serviceLocator
|
|
115
|
+
.getLogger()
|
|
116
|
+
.warningOnce(`Found cookies with similar name during cookie merging: '${cookie.key}' and '${similarKeyCookie.key}'`);
|
|
117
|
+
}
|
|
118
|
+
jar.setCookieSync(cookie, url);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return jar.getCookieStringSync(url);
|
|
122
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CrawleeLogger } from '@crawlee/core';
|
|
2
|
+
import type { AutoscaledPoolOptions } from './autoscaling/autoscaled_pool.js';
|
|
3
|
+
import type { IConcurrencySystem } from './autoscaling/concurrency_system.js';
|
|
2
4
|
/** Everything a run needs to exist. Whatever a crawler resolves per run is resolved before the run is built. */
|
|
3
5
|
export interface CrawlerRunSetup {
|
|
4
6
|
log: CrawleeLogger;
|
package/internals/crawler-run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoscaledPool } from '
|
|
1
|
+
import { AutoscaledPool } from './autoscaling/autoscaled_pool.js';
|
|
2
2
|
/**
|
|
3
3
|
* Everything that belongs to a single {@link BasicCrawler.run|`crawler.run()`} — the task loop dispatching its
|
|
4
4
|
* requests, the concurrency governor those requests are booked against, and the bookkeeping that must not survive
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Awaitable } from '@crawlee/types';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a middleware step in the context pipeline.
|
|
4
|
+
*
|
|
5
|
+
* @template TCrawlingContext - The input context type for this middleware
|
|
6
|
+
* @template TCrawlingContextExtension - The enhanced output context type
|
|
7
|
+
*/
|
|
8
|
+
export interface ContextMiddleware<TCrawlingContext, TCrawlingContextExtension> {
|
|
9
|
+
/** The main middleware function that enhances the context */
|
|
10
|
+
action: (context: TCrawlingContext) => Awaitable<TCrawlingContextExtension>;
|
|
11
|
+
/** Optional cleanup function called after the consumer finishes or fails */
|
|
12
|
+
cleanup?: (context: TCrawlingContext & TCrawlingContextExtension, error?: unknown) => Awaitable<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
|
|
16
|
+
*
|
|
17
|
+
* The enhancement is done by a chain of middlewares that are added to the pipeline after its creation.
|
|
18
|
+
* This class provides a type-safe way to build a pipeline of context transformations where each step
|
|
19
|
+
* can enhance the context with additional properties or utilities.
|
|
20
|
+
*
|
|
21
|
+
* @template TContextBase - The base context type that serves as the starting point
|
|
22
|
+
* @template TCrawlingContext - The final context type after all middleware transformations
|
|
23
|
+
*/
|
|
24
|
+
export declare abstract class ContextPipeline<TContextBase, TCrawlingContext extends TContextBase> {
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new empty context pipeline.
|
|
27
|
+
*
|
|
28
|
+
* @template TContextBase - The base context type for the pipeline
|
|
29
|
+
* @returns A new ContextPipeline instance with no transformations
|
|
30
|
+
*/
|
|
31
|
+
static create<TContextBase>(): ContextPipeline<TContextBase, TContextBase>;
|
|
32
|
+
/**
|
|
33
|
+
* Adds a middleware to the pipeline, creating a new pipeline instance.
|
|
34
|
+
*
|
|
35
|
+
* This method provides a fluent interface for building context transformation pipelines.
|
|
36
|
+
* Each middleware can enhance the context with additional properties or utilities.
|
|
37
|
+
*
|
|
38
|
+
* @template TCrawlingContextExtension - The enhanced context type produced by this middleware
|
|
39
|
+
* @param middleware - The middleware to add to the pipeline
|
|
40
|
+
* @returns A new ContextPipeline instance with the added middleware
|
|
41
|
+
*/
|
|
42
|
+
abstract compose<TCrawlingContextExtension>(middleware: ContextMiddleware<TCrawlingContext, TCrawlingContextExtension>): ContextPipeline<TContextBase, TCrawlingContext & TCrawlingContextExtension>;
|
|
43
|
+
/**
|
|
44
|
+
* Chains another pipeline onto this one. The other pipeline's base context must match
|
|
45
|
+
* this pipeline's output context. Returns a new pipeline that runs this pipeline's
|
|
46
|
+
* middlewares first, then the other pipeline's middlewares.
|
|
47
|
+
*
|
|
48
|
+
* @template TFinalContext - The final context type after the chained pipeline's transformations
|
|
49
|
+
* @param other - The pipeline to append after this one
|
|
50
|
+
* @returns A new ContextPipeline combining both pipelines' middlewares
|
|
51
|
+
*/
|
|
52
|
+
abstract chain<TFinalContext extends TCrawlingContext>(other: ContextPipeline<TCrawlingContext, TFinalContext>): ContextPipeline<TContextBase, TFinalContext>;
|
|
53
|
+
/**
|
|
54
|
+
* Executes the middleware pipeline and passes the final context to a consumer function.
|
|
55
|
+
*
|
|
56
|
+
* This method runs the crawling context through the entire middleware chain, enhancing it
|
|
57
|
+
* at each step, and then passes the final enhanced context to the provided consumer function.
|
|
58
|
+
* Proper cleanup is performed even if exceptions occur during processing.
|
|
59
|
+
*
|
|
60
|
+
* @param crawlingContext - The initial context to process through the pipeline
|
|
61
|
+
* @param finalContextConsumer - The function that will receive the final enhanced context
|
|
62
|
+
*
|
|
63
|
+
* @throws {ContextPipelineInitializationError} When a middleware fails during initialization
|
|
64
|
+
* @throws {ContextPipelineInterruptedError} When the pipeline is intentionally interrupted during initialization
|
|
65
|
+
* @throws {RequestHandlerError} When the final context consumer throws an exception
|
|
66
|
+
* @throws {ContextPipelineCleanupError} When cleanup operations fail
|
|
67
|
+
* @throws {SessionError} Session errors are re-thrown as-is for special handling
|
|
68
|
+
*/
|
|
69
|
+
abstract call(crawlingContext: TContextBase, finalContextConsumer: (finalContext: TCrawlingContext) => Awaitable<unknown>): Promise<void>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { serviceLocator, SessionError } from '@crawlee/core';
|
|
2
|
+
import { ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, RequestHandlerError, } from '../errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Encapsulates the logic of gradually enhancing the crawling context with additional information and utilities.
|
|
5
|
+
*
|
|
6
|
+
* The enhancement is done by a chain of middlewares that are added to the pipeline after its creation.
|
|
7
|
+
* This class provides a type-safe way to build a pipeline of context transformations where each step
|
|
8
|
+
* can enhance the context with additional properties or utilities.
|
|
9
|
+
*
|
|
10
|
+
* @template TContextBase - The base context type that serves as the starting point
|
|
11
|
+
* @template TCrawlingContext - The final context type after all middleware transformations
|
|
12
|
+
*/
|
|
13
|
+
export class ContextPipeline {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new empty context pipeline.
|
|
16
|
+
*
|
|
17
|
+
* @template TContextBase - The base context type for the pipeline
|
|
18
|
+
* @returns A new ContextPipeline instance with no transformations
|
|
19
|
+
*/
|
|
20
|
+
static create() {
|
|
21
|
+
return new ContextPipelineImpl({ action: async (context) => context });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Implementation of the `ContextPipeline` logic. This hides implementation details such as the `middleware` and `parent`
|
|
26
|
+
* properties from the `ContextPipeline` interface, making type checking more reliable.
|
|
27
|
+
*/
|
|
28
|
+
class ContextPipelineImpl extends ContextPipeline {
|
|
29
|
+
#middleware;
|
|
30
|
+
#parent;
|
|
31
|
+
constructor(middleware, parent) {
|
|
32
|
+
super();
|
|
33
|
+
this.#middleware = middleware;
|
|
34
|
+
this.#parent = parent;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* @inheritdoc
|
|
38
|
+
*/
|
|
39
|
+
compose(middleware) {
|
|
40
|
+
return new ContextPipelineImpl(middleware, this);
|
|
41
|
+
}
|
|
42
|
+
chain(other) {
|
|
43
|
+
const otherMiddlewares = Array.from(other.middlewareChain()).reverse();
|
|
44
|
+
let result = this;
|
|
45
|
+
for (const middleware of otherMiddlewares) {
|
|
46
|
+
result = result.compose(middleware);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
*middlewareChain() {
|
|
51
|
+
let step = this;
|
|
52
|
+
while (step !== undefined) {
|
|
53
|
+
yield step.#middleware;
|
|
54
|
+
step = step.#parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @inheritdoc
|
|
59
|
+
*/
|
|
60
|
+
async call(crawlingContext, finalContextConsumer) {
|
|
61
|
+
const middlewares = Array.from(this.middlewareChain()).reverse();
|
|
62
|
+
const cleanupStack = [];
|
|
63
|
+
let consumerException;
|
|
64
|
+
try {
|
|
65
|
+
for (const { action, cleanup } of middlewares) {
|
|
66
|
+
try {
|
|
67
|
+
const contextExtension = await action(crawlingContext);
|
|
68
|
+
const extensionNames = [
|
|
69
|
+
...Object.getOwnPropertyNames(contextExtension),
|
|
70
|
+
...Object.getOwnPropertySymbols(contextExtension),
|
|
71
|
+
];
|
|
72
|
+
for (const key of extensionNames) {
|
|
73
|
+
try {
|
|
74
|
+
if (Object.getOwnPropertyDescriptor(crawlingContext, key)?.configurable !== false) {
|
|
75
|
+
Object.defineProperty(crawlingContext, key, Object.getOwnPropertyDescriptor(contextExtension, key));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
serviceLocator
|
|
80
|
+
.getLogger()
|
|
81
|
+
.debug(`Context pipeline failed to define property ${key.toString()}:`, error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (cleanup) {
|
|
85
|
+
cleanupStack.push(cleanup);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (exception) {
|
|
89
|
+
if (exception instanceof SessionError) {
|
|
90
|
+
throw exception; // Session errors are re-thrown as-is
|
|
91
|
+
}
|
|
92
|
+
if (exception instanceof ContextPipelineInterruptedError) {
|
|
93
|
+
throw exception;
|
|
94
|
+
}
|
|
95
|
+
throw new ContextPipelineInitializationError(exception);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await finalContextConsumer(crawlingContext);
|
|
100
|
+
}
|
|
101
|
+
catch (exception) {
|
|
102
|
+
if (exception instanceof SessionError) {
|
|
103
|
+
consumerException = exception;
|
|
104
|
+
throw exception; // Session errors are re-thrown as-is
|
|
105
|
+
}
|
|
106
|
+
consumerException = exception;
|
|
107
|
+
throw new RequestHandlerError(exception);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
try {
|
|
112
|
+
for (const cleanup of cleanupStack.reverse()) {
|
|
113
|
+
await cleanup(crawlingContext, consumerException);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (exception) {
|
|
117
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
118
|
+
throw new ContextPipelineCleanupError(exception);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { Awaitable, Dictionary, HttpRequestOptions, ISession, ProxyInfo, SendRequestOptions } from '@crawlee/types';
|
|
2
|
+
import type { ReadonlyDeep } from 'type-fest';
|
|
3
|
+
import type { AddRequestsBatchedResult, CrawleeLogger, Dataset, KeyValueStore, Request, RequestOptions, Source, StorageIdentifier } from '@crawlee/core';
|
|
4
|
+
import type { EnqueueUrlsOptions } from '../enqueue_links/index.js';
|
|
5
|
+
/** @internal */
|
|
6
|
+
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
7
|
+
/**
|
|
8
|
+
* A request input (URL string, request-options object, or {@link Request}) whose `userData` is typed
|
|
9
|
+
* according to its `label`, based on a router's route map.
|
|
10
|
+
*
|
|
11
|
+
* When the route map is open (the default `Record<string, ...>`), this is just the regular loose
|
|
12
|
+
* {@link Source} input. When the map declares concrete labels, providing a `label` requires the matching
|
|
13
|
+
* `userData` shape and rejects labels not present in the map; unlabeled requests keep loose `userData`.
|
|
14
|
+
*/
|
|
15
|
+
export type LabeledSource<Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? string | Source : string | Request | ({
|
|
16
|
+
requestsFromUrl?: string;
|
|
17
|
+
regex?: RegExp;
|
|
18
|
+
} & ({
|
|
19
|
+
[Label in keyof Routes & string]: Omit<Partial<RequestOptions<Routes[Label]>>, 'label'> & {
|
|
20
|
+
label: Label;
|
|
21
|
+
};
|
|
22
|
+
}[keyof Routes & string] | (Omit<Partial<RequestOptions>, 'label'> & {
|
|
23
|
+
label?: undefined;
|
|
24
|
+
})));
|
|
25
|
+
/**
|
|
26
|
+
* The iterable/array of {@link LabeledSource} inputs accepted by the label-aware `addRequests`/`run`
|
|
27
|
+
* methods of a crawler bound to a typed router.
|
|
28
|
+
*/
|
|
29
|
+
export type TypedRequestsLike<Routes extends Record<keyof Routes, Dictionary>> = AsyncIterable<LabeledSource<Routes>> | Iterable<LabeledSource<Routes>> | LabeledSource<Routes>[];
|
|
30
|
+
/**
|
|
31
|
+
* The label-aware `addRequests` method signature exposed on a request handler's context when the crawler is
|
|
32
|
+
* bound to a typed router. Mirrors {@link RestrictedCrawlingContext.addRequests} with typed sources.
|
|
33
|
+
*/
|
|
34
|
+
export type TypedContextAddRequests<Routes extends Record<keyof Routes, Dictionary>> = (requestsLike: ReadonlyDeep<LabeledSource<Routes>[]>, options?: ReadonlyDeep<EnqueueUrlsOptions>) => Promise<AddRequestsBatchedResult>;
|
|
35
|
+
/**
|
|
36
|
+
* An `enqueueLinks`-options object with its `label`/`userData` retyped according to a router's route map: a
|
|
37
|
+
* declared `label` requires the matching `userData` shape (unknown labels are rejected), while unlabeled
|
|
38
|
+
* calls keep loose `userData`. Returns the options unchanged when the route map is open (the default).
|
|
39
|
+
*/
|
|
40
|
+
type TypedEnqueueLinksOptions<Options, Routes extends Record<keyof Routes, Dictionary>> = string extends keyof Routes ? Options : Omit<Options, 'label' | 'userData'> & ({
|
|
41
|
+
[Label in keyof Routes & string]: {
|
|
42
|
+
label: Label;
|
|
43
|
+
userData?: Routes[Label];
|
|
44
|
+
};
|
|
45
|
+
}[keyof Routes & string] | {
|
|
46
|
+
label?: undefined;
|
|
47
|
+
userData?: Dictionary;
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Transforms a context's existing `enqueueLinks` method so that the `label`/`userData` in its options follow
|
|
51
|
+
* the router's route map, while preserving everything else about the signature (argument optionality and
|
|
52
|
+
* return type, which differ between crawler types).
|
|
53
|
+
*/
|
|
54
|
+
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;
|
|
55
|
+
export type WithRequired<T, K extends keyof T> = T & {
|
|
56
|
+
[P in K]-?: T[P];
|
|
57
|
+
};
|
|
58
|
+
export type LoadedRequest<R extends Request> = WithRequired<R, 'id' | 'loadedUrl'>;
|
|
59
|
+
/** @internal */
|
|
60
|
+
export type LoadedContext<Context extends RestrictedCrawlingContext> = IsAny<Context> extends true ? Context : {
|
|
61
|
+
request: LoadedRequest<Context['request']>;
|
|
62
|
+
} & Omit<Context, 'request'>;
|
|
63
|
+
export interface RestrictedCrawlingContext<UserData extends Dictionary = Dictionary> {
|
|
64
|
+
id: string;
|
|
65
|
+
session: ISession;
|
|
66
|
+
/**
|
|
67
|
+
* An object with information about currently used proxy by the crawler
|
|
68
|
+
* and configured by the {@link ProxyConfiguration} class.
|
|
69
|
+
*/
|
|
70
|
+
proxyInfo?: ProxyInfo;
|
|
71
|
+
/**
|
|
72
|
+
* The original {@link Request} object.
|
|
73
|
+
*/
|
|
74
|
+
request: Request<UserData>;
|
|
75
|
+
/**
|
|
76
|
+
* This function allows you to push data to a {@link Dataset} specified by name, or the one currently used by the crawler.
|
|
77
|
+
*
|
|
78
|
+
* Shortcut for `crawler.pushData()`.
|
|
79
|
+
*
|
|
80
|
+
* @param [data] Data to be pushed to the default dataset.
|
|
81
|
+
*/
|
|
82
|
+
pushData(data: ReadonlyDeep<Parameters<Dataset['pushData']>[0]>, datasetIdentifier?: string | StorageIdentifier): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Add requests directly to the request queue currently used by the crawler.
|
|
85
|
+
*
|
|
86
|
+
* Optionally, the function allows you to filter the target URLs using an array of glob or regexp patterns,
|
|
87
|
+
* the same way {@link CrawlingContext.enqueueLinks|`enqueueLinks`} does for extracted links.
|
|
88
|
+
*
|
|
89
|
+
* @param requests The requests to add
|
|
90
|
+
* @param options Options for the request queue
|
|
91
|
+
*/
|
|
92
|
+
addRequests: (requestsLike: ReadonlyDeep<(string | Source)[]>, options?: ReadonlyDeep<EnqueueUrlsOptions>) => Promise<AddRequestsBatchedResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Returns the state - a piece of mutable persistent data shared across all the request handler runs.
|
|
95
|
+
*/
|
|
96
|
+
useState: <State extends Dictionary = Dictionary>(defaultValue?: State) => Promise<State>;
|
|
97
|
+
/**
|
|
98
|
+
* Get a key-value store with given name or id, or the default one for the crawler.
|
|
99
|
+
*/
|
|
100
|
+
getKeyValueStore: (identifier?: string | StorageIdentifier) => Promise<Pick<KeyValueStore, 'id' | 'name' | 'getValue' | 'getAutoSavedValue' | 'setValue' | 'getPublicUrl'>>;
|
|
101
|
+
/**
|
|
102
|
+
* A preconfigured logger for the request handler.
|
|
103
|
+
*/
|
|
104
|
+
log: CrawleeLogger;
|
|
105
|
+
}
|
|
106
|
+
export interface CrawlingContext<UserData extends Dictionary = Dictionary> extends RestrictedCrawlingContext<UserData> {
|
|
107
|
+
/**
|
|
108
|
+
* Fires HTTP request via the internal HTTP client, allowing to override the request options on the fly.
|
|
109
|
+
*
|
|
110
|
+
* This is handy when you work with a browser crawler but want to execute some requests outside it (e.g. API requests).
|
|
111
|
+
* Check the [Skipping navigations for certain requests](https://crawlee.dev/js/docs/examples/skip-navigation) example for
|
|
112
|
+
* more detailed explanation of how to do that.
|
|
113
|
+
*
|
|
114
|
+
* ```ts
|
|
115
|
+
* async requestHandler({ sendRequest }) {
|
|
116
|
+
* const { body } = await sendRequest({
|
|
117
|
+
* // override headers only
|
|
118
|
+
* headers: { ... },
|
|
119
|
+
* });
|
|
120
|
+
* },
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
sendRequest: (requestOverrides?: Partial<HttpRequestOptions>, optionsOverrides?: SendRequestOptions) => Promise<Response>;
|
|
124
|
+
/**
|
|
125
|
+
* 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.
|
|
126
|
+
*
|
|
127
|
+
* The callback runs *outside* the request's storage transaction, so storage writes made here are
|
|
128
|
+
* applied immediately and are **not** rolled back when the request fails. In
|
|
129
|
+
* {@link AdaptivePlaywrightCrawler} it also runs once per request handler attempt, so a write
|
|
130
|
+
* here can land more than once for a single request. Push results from the request handler itself.
|
|
131
|
+
*/
|
|
132
|
+
registerDeferredCleanup(cleanup: () => Promise<unknown>): void;
|
|
133
|
+
/**
|
|
134
|
+
* Registers `callback` to run once the request's storage writes have been committed, or once
|
|
135
|
+
* committing them has failed - the point where a write made in the handler can finally be reacted
|
|
136
|
+
* to where it was made. Two things belong here: side effects that must agree with what actually
|
|
137
|
+
* landed (result counters, progress reporting), and handling of a write that did not land at all.
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* async requestHandler({ pushData, afterStorageCommit, useState }) {
|
|
141
|
+
* const state = await useState({ itemCount: 0 });
|
|
142
|
+
*
|
|
143
|
+
* await pushData(item);
|
|
144
|
+
* afterStorageCommit((error) => {
|
|
145
|
+
* if (error) {
|
|
146
|
+
* throw new NonRetryableError('The page is too big to store', { cause: error });
|
|
147
|
+
* }
|
|
148
|
+
* state.itemCount++;
|
|
149
|
+
* });
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* `useState()` is deliberately not transactional, which is what makes the counter case necessary:
|
|
154
|
+
* incrementing in the handler would keep the increment for a request that pushes an item and then
|
|
155
|
+
* fails, while the item itself is rolled back.
|
|
156
|
+
*
|
|
157
|
+
* Callbacks run in registration order, after the commit and before the request is marked as handled.
|
|
158
|
+
* The first one to throw propagates and the rest do not run; on a failed commit its error replaces
|
|
159
|
+
* the commit error, so a callback can decide what the request fails with. After a *successful*
|
|
160
|
+
* commit, a callback that throws fails the request **without a retry** whatever it throws - the
|
|
161
|
+
* writes are already durable and retrying would duplicate them, so anything that is not already a
|
|
162
|
+
* {@link NonRetryableError} is wrapped in an {@link AfterCommitError}.
|
|
163
|
+
*
|
|
164
|
+
* Callbacks are *not* run when the request handler itself fails - nothing was written, and
|
|
165
|
+
* `errorHandler` / `failedRequestHandler` cover that.
|
|
166
|
+
*
|
|
167
|
+
* Throws when storage is not transactional (`transactionalStorage: false`) - writes are then applied
|
|
168
|
+
* as they are made and throw at the call site on their own. In {@link AdaptivePlaywrightCrawler}
|
|
169
|
+
* the callback belongs to the current request handler attempt, so it runs only for the attempt whose
|
|
170
|
+
* writes are the ones being committed.
|
|
171
|
+
*/
|
|
172
|
+
afterStorageCommit(callback: (error?: Error) => Awaitable<void>): void;
|
|
173
|
+
/**
|
|
174
|
+
* Gives the current request `secs` more seconds to finish, for when how long it needs is only apparent
|
|
175
|
+
* once it is already running - a listing page that turns out to have far more to scroll through than
|
|
176
|
+
* usual, say. Prefer `requestHandlerTimeoutSecs`, or a per-route override via
|
|
177
|
+
* {@link Router.addHandler|`router.addHandler`}, whenever the time needed is known up front.
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* router.addHandler('LIST', async ({ extendTimeout, page }) => {
|
|
181
|
+
* const pageCount = await countPages(page);
|
|
182
|
+
* extendTimeout(pageCount * 10);
|
|
183
|
+
* await scrapeAllPages(page);
|
|
184
|
+
* });
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* Extends the request handler's own timeout and the crawler's internal one together, so the extension
|
|
188
|
+
* is not immediately undone by the latter. Calling it from a handler that has already timed out does
|
|
189
|
+
* nothing.
|
|
190
|
+
*/
|
|
191
|
+
extendTimeout(secs: number): void;
|
|
192
|
+
}
|
|
193
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { KeyValueStore } from '@crawlee/core';
|
|
2
|
+
import type { CrawlingContext } from '../crawlers/crawler_commons.js';
|
|
3
|
+
import type { ErrnoException } from './error_tracker.js';
|
|
4
|
+
import type { SnapshottableProperties } from './internals/types.js';
|
|
5
|
+
interface BrowserCrawlingContext {
|
|
6
|
+
saveSnapshot: (options: {
|
|
7
|
+
key: string;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface SnapshotResult {
|
|
11
|
+
screenshotFileName?: string;
|
|
12
|
+
htmlFileName?: string;
|
|
13
|
+
}
|
|
14
|
+
interface ErrorSnapshot {
|
|
15
|
+
screenshotFileName?: string;
|
|
16
|
+
screenshotFileUrl?: string;
|
|
17
|
+
htmlFileName?: string;
|
|
18
|
+
htmlFileUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ErrorSnapshotter class is used to capture a screenshot of the page and a snapshot of the HTML when an error occurs during web crawling.
|
|
22
|
+
*
|
|
23
|
+
* This functionality is opt-in, and can be enabled via the crawler options:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const crawler = new BasicCrawler({
|
|
27
|
+
* // ...
|
|
28
|
+
* statistics: new Statistics({ saveErrorSnapshots: true }),
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class ErrorSnapshotter {
|
|
33
|
+
static readonly MAX_ERROR_CHARACTERS = 30;
|
|
34
|
+
static readonly MAX_HASH_LENGTH = 30;
|
|
35
|
+
static readonly MAX_FILENAME_LENGTH = 250;
|
|
36
|
+
static readonly BASE_MESSAGE = "An error occurred";
|
|
37
|
+
static readonly SNAPSHOT_PREFIX = "ERROR_SNAPSHOT";
|
|
38
|
+
/**
|
|
39
|
+
* Capture a snapshot of the error context.
|
|
40
|
+
*/
|
|
41
|
+
captureSnapshot(error: ErrnoException, context: CrawlingContext & SnapshottableProperties): Promise<ErrorSnapshot>;
|
|
42
|
+
/**
|
|
43
|
+
* Captures a snapshot of the current page using the context.saveSnapshot function.
|
|
44
|
+
* This function is applicable for browser contexts only.
|
|
45
|
+
* Returns an object containing the filenames of the screenshot and HTML file.
|
|
46
|
+
*/
|
|
47
|
+
contextCaptureSnapshot(context: BrowserCrawlingContext, fileName: string): Promise<SnapshotResult | undefined>;
|
|
48
|
+
/**
|
|
49
|
+
* Save the HTML snapshot of the page, and return the key it was stored under.
|
|
50
|
+
*/
|
|
51
|
+
saveHTMLSnapshot(html: string, keyValueStore: Pick<KeyValueStore, 'setValue'>, fileName: string): Promise<string | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Generate a unique fileName for each error snapshot.
|
|
54
|
+
*/
|
|
55
|
+
generateFilename(error: ErrnoException): string;
|
|
56
|
+
}
|
|
57
|
+
export {};
|