@crawlee/playwright 4.0.0-rc.0 → 4.0.0-rc.1
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/README.md +1 -1
- package/index.d.ts +2 -1
- package/index.js +1 -1
- package/internals/adaptive-playwright-crawler.d.ts +29 -32
- package/internals/adaptive-playwright-crawler.js +181 -82
- package/internals/enqueue-links/click-elements.js +7 -5
- package/internals/playwright-crawler.d.ts +33 -71
- package/internals/playwright-crawler.js +23 -20
- package/internals/playwright-launcher.d.ts +8 -8
- package/internals/playwright-launcher.js +7 -3
- package/internals/utils/playwright-utils.d.ts +4 -34
- package/internals/utils/playwright-utils.js +10 -49
- package/internals/utils/rendering-type-prediction.d.ts +22 -9
- package/internals/utils/rendering-type-prediction.js +26 -18
- package/package.json +13 -18
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, GetUserDataFromRequest,
|
|
1
|
+
import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, GetUserDataFromRequest, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
|
|
2
2
|
import { BrowserCrawler } from '@crawlee/browser';
|
|
3
3
|
import type { Dictionary } from '@crawlee/types';
|
|
4
4
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
5
|
-
import type {
|
|
5
|
+
import type { Page, Response } from 'playwright';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import type { EnqueueLinksByClickingElementsOptions } from './enqueue-links/click-elements.js';
|
|
8
7
|
import type { PlaywrightLaunchContext } from './playwright-launcher.js';
|
|
9
|
-
import type {
|
|
8
|
+
import type { DirectNavigationOptions, HandleCloudflareChallengeOptions, PlaywrightContextUtils } from './utils/playwright-utils.js';
|
|
10
9
|
export type PlaywrightGotoOptions = NonNullable<Parameters<Page['goto']>[1]>;
|
|
11
10
|
export interface PlaywrightCrawlingContext<UserData extends Dictionary = any> extends BrowserCrawlingContext<Page, Response, UserData, PlaywrightGotoOptions>, PlaywrightContextUtils {
|
|
12
11
|
}
|
|
@@ -21,29 +20,6 @@ export interface PlaywrightCrawlerOptions<ContextExtension = Dictionary<never>,
|
|
|
21
20
|
* Can be also set via {@link Configuration}.
|
|
22
21
|
*/
|
|
23
22
|
headless?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Function that is called to process each request.
|
|
26
|
-
*
|
|
27
|
-
* The function receives the {@link PlaywrightCrawlingContext} as an argument, where:
|
|
28
|
-
* - `request` is an instance of the {@link Request} object with details about the URL to open, HTTP method etc.
|
|
29
|
-
* - `page` is an instance of the `Playwright`
|
|
30
|
-
* [`Page`](https://playwright.dev/docs/api/class-page)
|
|
31
|
-
* - `response` is an instance of the `Playwright`
|
|
32
|
-
* [`Response`](https://playwright.dev/docs/api/class-response),
|
|
33
|
-
* which is the main resource response as returned by `page.goto(request.url)`.
|
|
34
|
-
*
|
|
35
|
-
* The function must return a promise, which is then awaited by the crawler.
|
|
36
|
-
*
|
|
37
|
-
* If the function throws an exception, the crawler will try to re-crawl the
|
|
38
|
-
* request later, up to `option.maxRequestRetries` times.
|
|
39
|
-
* If all the retries fail, the crawler calls the function
|
|
40
|
-
* provided to the `failedRequestHandler` parameter.
|
|
41
|
-
* To make this work, you should **always**
|
|
42
|
-
* let your function throw exceptions rather than catch them.
|
|
43
|
-
* The exceptions are logged to the request using the
|
|
44
|
-
* {@link Request.pushErrorMessage} function.
|
|
45
|
-
*/
|
|
46
|
-
requestHandler?: RouterHandler<ExtendedContext, Routes> | RequestHandler<ExtendedContext>;
|
|
47
23
|
/**
|
|
48
24
|
* Async functions that are sequentially evaluated before the navigation. Good for setting additional cookies
|
|
49
25
|
* or browser properties before navigation. The function receives the `crawlingContext`; the options object
|
|
@@ -144,21 +120,12 @@ export interface PlaywrightCrawlerOptions<ContextExtension = Dictionary<never>,
|
|
|
144
120
|
* ```
|
|
145
121
|
* @category Crawlers
|
|
146
122
|
*/
|
|
147
|
-
export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends PlaywrightCrawlingContext = PlaywrightCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PlaywrightCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends BrowserCrawler<Page, Response,
|
|
123
|
+
export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends PlaywrightCrawlingContext = PlaywrightCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PlaywrightCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends BrowserCrawler<Page, Response, PlaywrightCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
124
|
+
#private;
|
|
125
|
+
/**
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
148
128
|
protected static optionsShape: {
|
|
149
|
-
headless: z.ZodOptional<z.ZodBoolean>;
|
|
150
|
-
launcher: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
151
|
-
navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
152
|
-
preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
153
|
-
postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
154
|
-
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
155
|
-
browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
156
|
-
browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
157
|
-
remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
158
|
-
saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
|
|
159
|
-
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
160
|
-
ignoreIframes: z.ZodDefault<z.ZodBoolean>;
|
|
161
|
-
ignoreShadowRoots: z.ZodDefault<z.ZodBoolean>;
|
|
162
129
|
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
163
130
|
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
164
131
|
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
@@ -190,27 +157,23 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
|
|
|
190
157
|
}, z.core.$strict>]>>;
|
|
191
158
|
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
192
159
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
193
|
-
httpClient: z.ZodOptional<z.
|
|
160
|
+
httpClient: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/http-client").BaseHttpClient>>;
|
|
194
161
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
195
|
-
configuration: z.ZodOptional<z.
|
|
162
|
+
configuration: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/browser").Configuration>>;
|
|
196
163
|
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
197
164
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
198
|
-
eventManager: z.ZodOptional<z.
|
|
165
|
+
eventManager: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/browser").EventManager>>;
|
|
199
166
|
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
200
167
|
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
201
168
|
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
169
|
+
initialConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
202
170
|
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
203
171
|
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
204
172
|
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
205
173
|
id: z.ZodOptional<z.ZodString>;
|
|
206
|
-
};
|
|
207
|
-
protected static optionsSchema: z.ZodObject<{
|
|
208
|
-
headless: z.ZodOptional<z.ZodBoolean>;
|
|
209
|
-
launcher: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
210
174
|
navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
211
175
|
preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
212
176
|
postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
213
|
-
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
214
177
|
browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
215
178
|
browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
216
179
|
remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
@@ -218,6 +181,11 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
|
|
|
218
181
|
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
219
182
|
ignoreIframes: z.ZodDefault<z.ZodBoolean>;
|
|
220
183
|
ignoreShadowRoots: z.ZodDefault<z.ZodBoolean>;
|
|
184
|
+
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
185
|
+
headless: z.ZodOptional<z.ZodBoolean>;
|
|
186
|
+
};
|
|
187
|
+
/** @internal */
|
|
188
|
+
protected static optionsSchema: z.ZodObject<{
|
|
221
189
|
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
222
190
|
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
223
191
|
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
@@ -249,44 +217,38 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
|
|
|
249
217
|
}, z.core.$strict>]>>;
|
|
250
218
|
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
251
219
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
252
|
-
httpClient: z.ZodOptional<z.
|
|
220
|
+
httpClient: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/http-client").BaseHttpClient>>;
|
|
253
221
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
254
|
-
configuration: z.ZodOptional<z.
|
|
222
|
+
configuration: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/browser").Configuration>>;
|
|
255
223
|
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
256
224
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
257
|
-
eventManager: z.ZodOptional<z.
|
|
225
|
+
eventManager: z.ZodOptional<z.ZodInstanceOf<import("@crawlee/browser").EventManager>>;
|
|
258
226
|
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
259
227
|
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
260
228
|
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
229
|
+
initialConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
261
230
|
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
262
231
|
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
263
232
|
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
264
233
|
id: z.ZodOptional<z.ZodString>;
|
|
234
|
+
navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
235
|
+
preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
236
|
+
postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
237
|
+
browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
238
|
+
browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
239
|
+
remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
240
|
+
saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
|
|
241
|
+
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
242
|
+
ignoreIframes: z.ZodDefault<z.ZodBoolean>;
|
|
243
|
+
ignoreShadowRoots: z.ZodDefault<z.ZodBoolean>;
|
|
244
|
+
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
245
|
+
headless: z.ZodOptional<z.ZodBoolean>;
|
|
265
246
|
}, z.core.$strict>;
|
|
266
247
|
/**
|
|
267
248
|
* All `PlaywrightCrawler` parameters are passed via an options object.
|
|
268
249
|
*/
|
|
269
250
|
constructor(options?: PlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
270
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
271
|
-
protected buildContextPipeline(): import("@crawlee/browser").ContextPipeline<import("@crawlee/browser").CrawlingContext<Dictionary>, BrowserCrawlingContext<Page, Response, Dictionary, Dictionary> & {
|
|
272
|
-
injectFile: (filePath: string, options?: InjectFileOptions) => Promise<unknown>;
|
|
273
|
-
injectJQuery: () => Promise<void>;
|
|
274
|
-
blockRequests: (options?: BlockRequestsOptions) => Promise<void>;
|
|
275
|
-
waitForSelector: (selector: string, timeoutMs?: number) => Promise<void>;
|
|
276
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
277
|
-
parseWithCheerio: (selector?: string, timeoutMs?: number) => Promise<import("@crawlee/browser").CheerioAPI>;
|
|
278
|
-
infiniteScroll: (options?: InfiniteScrollOptions) => Promise<void>;
|
|
279
|
-
listDownloads: () => Promise<Download[]>;
|
|
280
|
-
saveSnapshot: (options?: SaveSnapshotOptions) => Promise<void>;
|
|
281
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
282
|
-
enqueueLinksByClickingElements: (options: Omit<EnqueueLinksByClickingElementsOptions, "page" | "requestManager">) => Promise<import("@crawlee/types").BatchAddRequestsResult>;
|
|
283
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
284
|
-
compileScript: (scriptString: string, ctx?: Dictionary) => import("./utils/playwright-utils.js").CompiledScriptFunction;
|
|
285
|
-
closeCookieModals: () => Promise<void>;
|
|
286
|
-
handleCloudflareChallenge: (options?: HandleCloudflareChallengeOptions) => Promise<Response | undefined>;
|
|
287
|
-
}>;
|
|
288
251
|
protected navigationHandler(crawlingContext: PlaywrightCrawlingContext, gotoOptions: DirectNavigationOptions): Promise<Response | null>;
|
|
289
|
-
private enhanceContext;
|
|
290
252
|
}
|
|
291
253
|
/**
|
|
292
254
|
* Returns a `postNavigationHooks`-ready hook that runs {@link PlaywrightContextUtils.handleCloudflareChallenge}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BrowserCrawler, RequestState, Router, serviceLocator } from '@crawlee/browser';
|
|
2
|
+
import { assertBrowserPoolNotConfigured, parseArgument, schemas } from '@crawlee/utils/internal';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { playwrightBrowserPool, remotePlaywrightBrowserPool } from './playwright-browser-pool.js';
|
|
4
|
-
import { gotoExtended,
|
|
5
|
+
import { blockRequests, compileScript, enqueueLinksByClickingElements, gotoExtended, handleCloudflareChallenge, infiniteScroll, injectFile, injectJQuery, parseWithCheerio, saveSnapshot, } from './utils/playwright-utils.js';
|
|
5
6
|
/**
|
|
6
7
|
* Provides a simple framework for parallel crawling of web pages
|
|
7
8
|
* using headless Chromium, Firefox and Webkit browsers with [Playwright](https://github.com/microsoft/playwright).
|
|
@@ -68,18 +69,22 @@ import { gotoExtended, playwrightUtils } from './utils/playwright-utils.js';
|
|
|
68
69
|
* @category Crawlers
|
|
69
70
|
*/
|
|
70
71
|
export class PlaywrightCrawler extends BrowserCrawler {
|
|
72
|
+
/**
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
71
75
|
static optionsShape = {
|
|
72
76
|
...BrowserCrawler.optionsShape,
|
|
77
|
+
launchContext: schemas.anyObject.default(() => ({})),
|
|
73
78
|
headless: z.boolean().optional(),
|
|
74
|
-
launcher: schemas.anyObject.optional(),
|
|
75
79
|
};
|
|
80
|
+
/** @internal */
|
|
76
81
|
static optionsSchema = z.strictObject(PlaywrightCrawler.optionsShape);
|
|
77
82
|
/**
|
|
78
83
|
* All `PlaywrightCrawler` parameters are passed via an options object.
|
|
79
84
|
*/
|
|
80
85
|
constructor(options = {}) {
|
|
81
86
|
const parsedOptions = parseArgument(options, PlaywrightCrawler.optionsSchema, 'PlaywrightCrawlerOptions');
|
|
82
|
-
const { launchContext, headless, configuration,
|
|
87
|
+
const { launchContext, headless, configuration, ...browserCrawlerOptions } = parsedOptions;
|
|
83
88
|
if (launchContext.proxyUrl) {
|
|
84
89
|
throw new Error('PlaywrightCrawlerOptions.launchContext.proxyUrl is not allowed in PlaywrightCrawler.' +
|
|
85
90
|
'Use PlaywrightCrawlerOptions.proxyConfiguration');
|
|
@@ -93,21 +98,20 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
93
98
|
}
|
|
94
99
|
super({
|
|
95
100
|
...browserCrawlerOptions,
|
|
96
|
-
launchContext,
|
|
97
101
|
configuration,
|
|
98
102
|
browserPoolBuilder: (remoteBrowser) => remoteBrowser
|
|
99
103
|
? remotePlaywrightBrowserPool({ ...remoteBrowser, launchContext, headless, configuration })
|
|
100
104
|
: playwrightBrowserPool({ launchContext, headless, configuration }),
|
|
101
|
-
contextPipelineBuilder:
|
|
105
|
+
contextPipelineBuilder: () => this.#buildContextPipeline(),
|
|
102
106
|
});
|
|
103
107
|
}
|
|
104
|
-
buildContextPipeline() {
|
|
105
|
-
return
|
|
108
|
+
#buildContextPipeline() {
|
|
109
|
+
return this.buildContextPipeline().compose(this.#enhanceContext.bind(this));
|
|
106
110
|
}
|
|
107
111
|
async navigationHandler(crawlingContext, gotoOptions) {
|
|
108
112
|
return gotoExtended(crawlingContext.page, crawlingContext.request, gotoOptions);
|
|
109
113
|
}
|
|
110
|
-
async enhanceContext(context) {
|
|
114
|
+
async #enhanceContext(context) {
|
|
111
115
|
const waitForSelector = async (selector, timeoutMs = 5_000) => {
|
|
112
116
|
const locator = context.page.locator(selector).first();
|
|
113
117
|
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
@@ -115,38 +119,37 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
115
119
|
const downloads = [];
|
|
116
120
|
context.page.on('download', (download) => downloads.push(download));
|
|
117
121
|
return {
|
|
118
|
-
injectFile: async (filePath, options) =>
|
|
122
|
+
injectFile: async (filePath, options) => injectFile(context.page, filePath, options),
|
|
119
123
|
injectJQuery: async () => {
|
|
120
124
|
if (context.request.state === RequestState.BEFORE_NAV) {
|
|
121
125
|
context.log.warning('Using injectJQuery() in preNavigationHooks leads to unstable results. Use it in a postNavigationHook or a requestHandler instead.');
|
|
122
|
-
await
|
|
126
|
+
await injectJQuery(context.page);
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
125
|
-
await
|
|
129
|
+
await injectJQuery(context.page, { surviveNavigations: false });
|
|
126
130
|
},
|
|
127
|
-
blockRequests: async (options) =>
|
|
131
|
+
blockRequests: async (options) => blockRequests(context.page, options),
|
|
128
132
|
waitForSelector,
|
|
129
133
|
parseWithCheerio: async (selector, timeoutMs = 5_000) => {
|
|
130
134
|
if (selector) {
|
|
131
135
|
await waitForSelector(selector, timeoutMs);
|
|
132
136
|
}
|
|
133
|
-
return
|
|
137
|
+
return parseWithCheerio(context.page, this.ignoreShadowRoots, this.ignoreIframes);
|
|
134
138
|
},
|
|
135
|
-
infiniteScroll: async (options) =>
|
|
139
|
+
infiniteScroll: async (options) => infiniteScroll(context.page, options),
|
|
136
140
|
listDownloads: async () => downloads,
|
|
137
|
-
saveSnapshot: async (options) =>
|
|
141
|
+
saveSnapshot: async (options) => saveSnapshot(context.page, {
|
|
138
142
|
...options,
|
|
139
143
|
configuration: serviceLocator.getConfiguration(),
|
|
140
144
|
}),
|
|
141
|
-
enqueueLinksByClickingElements: async (options) =>
|
|
145
|
+
enqueueLinksByClickingElements: async (options) => enqueueLinksByClickingElements({
|
|
142
146
|
...options,
|
|
143
147
|
page: context.page,
|
|
144
148
|
requestManager: this.requestManager,
|
|
145
149
|
}),
|
|
146
|
-
compileScript: (scriptString, ctx) =>
|
|
147
|
-
closeCookieModals: async () => playwrightUtils.closeCookieModals(context.page),
|
|
150
|
+
compileScript: (scriptString, ctx) => compileScript(scriptString, ctx),
|
|
148
151
|
handleCloudflareChallenge: async (options) => {
|
|
149
|
-
return
|
|
152
|
+
return handleCloudflareChallenge(context.page, context.request.url, options);
|
|
150
153
|
},
|
|
151
154
|
};
|
|
152
155
|
}
|
|
@@ -72,11 +72,10 @@ export interface PlaywrightLaunchContext extends BrowserLaunchContext<LaunchOpti
|
|
|
72
72
|
*/
|
|
73
73
|
export declare class PlaywrightLauncher extends BrowserLauncher<PlaywrightPlugin> {
|
|
74
74
|
readonly configuration: Configuration;
|
|
75
|
+
/**
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
75
78
|
protected static optionsShape: {
|
|
76
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
77
|
-
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
78
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
79
|
-
launchContextOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
80
79
|
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
81
80
|
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
82
81
|
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -86,12 +85,11 @@ export declare class PlaywrightLauncher extends BrowserLauncher<PlaywrightPlugin
|
|
|
86
85
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
87
86
|
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
88
87
|
userAgent: z.ZodOptional<z.ZodString>;
|
|
89
|
-
};
|
|
90
|
-
protected static optionsSchema: z.ZodObject<{
|
|
91
88
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
92
89
|
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
};
|
|
91
|
+
/** @internal */
|
|
92
|
+
protected static optionsSchema: z.ZodObject<{
|
|
95
93
|
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
96
94
|
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
97
95
|
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -101,6 +99,8 @@ export declare class PlaywrightLauncher extends BrowserLauncher<PlaywrightPlugin
|
|
|
101
99
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
102
100
|
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
103
101
|
userAgent: z.ZodOptional<z.ZodString>;
|
|
102
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
103
|
+
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
104
104
|
}, z.core.$strict>;
|
|
105
105
|
/**
|
|
106
106
|
* All `PlaywrightLauncher` parameters are passed via this launchContext object.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BrowserLauncher, Configuration
|
|
1
|
+
import { BrowserLauncher, Configuration } from '@crawlee/browser';
|
|
2
2
|
import { PlaywrightPlugin } from '@crawlee/browser-pool';
|
|
3
|
+
import { parseArgument, schemas } from '@crawlee/utils/internal';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
/**
|
|
5
6
|
* `PlaywrightLauncher` is based on the `BrowserLauncher`. It launches `playwright` browser instance.
|
|
@@ -7,12 +8,15 @@ import { z } from 'zod';
|
|
|
7
8
|
*/
|
|
8
9
|
export class PlaywrightLauncher extends BrowserLauncher {
|
|
9
10
|
configuration;
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
10
14
|
static optionsShape = {
|
|
11
15
|
...BrowserLauncher.optionsShape,
|
|
12
|
-
// Passthrough
|
|
16
|
+
// Passthrough schema — the launcher module object must keep its prototype through parsing.
|
|
13
17
|
launcher: schemas.anyObject.optional(),
|
|
14
|
-
launchContextOptions: schemas.anyObject.optional(),
|
|
15
18
|
};
|
|
19
|
+
/** @internal */
|
|
16
20
|
static optionsSchema = z.strictObject(PlaywrightLauncher.optionsShape);
|
|
17
21
|
/**
|
|
18
22
|
* All `PlaywrightLauncher` parameters are passed via this launchContext object.
|
|
@@ -19,12 +19,11 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { Configuration, type Request } from '@crawlee/browser';
|
|
21
21
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
22
|
-
import {
|
|
22
|
+
import type { CheerioAPI } from 'cheerio';
|
|
23
23
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
24
24
|
import type { Download, Page, Response } from 'playwright';
|
|
25
25
|
import type { EnqueueLinksByClickingElementsOptions } from '../enqueue-links/click-elements.js';
|
|
26
26
|
import { enqueueLinksByClickingElements } from '../enqueue-links/click-elements.js';
|
|
27
|
-
import { RenderingTypePredictor } from './rendering-type-prediction.js';
|
|
28
27
|
export interface InjectFileOptions {
|
|
29
28
|
/**
|
|
30
29
|
* Enables the injected script to survive page navigations and reloads without need to be re-injected manually.
|
|
@@ -287,8 +286,7 @@ export declare function saveSnapshot(page: Page, options?: SaveSnapshotOptions):
|
|
|
287
286
|
* @param page Playwright [`Page`](https://playwright.dev/docs/api/class-page) object.
|
|
288
287
|
* @param ignoreShadowRoots
|
|
289
288
|
*/
|
|
290
|
-
export declare function parseWithCheerio(page: Page, ignoreShadowRoots?: boolean, ignoreIframes?: boolean): Promise<
|
|
291
|
-
export declare function closeCookieModals(page: Page): Promise<void>;
|
|
289
|
+
export declare function parseWithCheerio(page: Page, ignoreShadowRoots?: boolean, ignoreIframes?: boolean): Promise<CheerioAPI>;
|
|
292
290
|
export interface HandleCloudflareChallengeOptions {
|
|
293
291
|
/** Logging defaults to the `debug` level, use this flag to log to `info` level instead. */
|
|
294
292
|
verbose?: boolean;
|
|
@@ -335,7 +333,7 @@ export interface HandleCloudflareChallengeOptions {
|
|
|
335
333
|
* @param url current URL for request identification, only used for logging
|
|
336
334
|
* @param [options]
|
|
337
335
|
*/
|
|
338
|
-
declare function handleCloudflareChallenge(page: Page, url: string, options?: HandleCloudflareChallengeOptions): Promise<Response | undefined>;
|
|
336
|
+
export declare function handleCloudflareChallenge(page: Page, url: string, options?: HandleCloudflareChallengeOptions): Promise<Response | undefined>;
|
|
339
337
|
export interface PlaywrightContextUtils {
|
|
340
338
|
/**
|
|
341
339
|
* Injects a JavaScript file into current `page`.
|
|
@@ -434,7 +432,7 @@ export interface PlaywrightContextUtils {
|
|
|
434
432
|
* });
|
|
435
433
|
* ```
|
|
436
434
|
*/
|
|
437
|
-
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<
|
|
435
|
+
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioAPI>;
|
|
438
436
|
/**
|
|
439
437
|
* Scrolls to the bottom of a page, or until it times out.
|
|
440
438
|
* Loads dynamic content when it hits the bottom of a page, and then continues scrolling.
|
|
@@ -513,19 +511,6 @@ export interface PlaywrightContextUtils {
|
|
|
513
511
|
* secured copies beforehand.
|
|
514
512
|
*/
|
|
515
513
|
compileScript(scriptString: string, ctx?: Dictionary): CompiledScriptFunction;
|
|
516
|
-
/**
|
|
517
|
-
* Tries to close cookie consent modals on the page. Based on the I Don't Care About Cookies browser extension.
|
|
518
|
-
*
|
|
519
|
-
* Note that this method requires the idcac-playwright package to be installed.
|
|
520
|
-
* Crawlee does not include it by default due to licensing issues.
|
|
521
|
-
*
|
|
522
|
-
* To use this method, please install the package manually by running:
|
|
523
|
-
*
|
|
524
|
-
* ```bash
|
|
525
|
-
* npm install idcac-playwright
|
|
526
|
-
* ```
|
|
527
|
-
*/
|
|
528
|
-
closeCookieModals(): Promise<void>;
|
|
529
514
|
/**
|
|
530
515
|
* This helper tries to solve the Cloudflare challenge automatically by clicking on the checkbox.
|
|
531
516
|
* It will try to detect the Cloudflare page, click on the checkbox, and wait for 10 seconds (configurable
|
|
@@ -572,18 +557,3 @@ export interface PlaywrightContextUtils {
|
|
|
572
557
|
listDownloads(): Promise<Download[]>;
|
|
573
558
|
}
|
|
574
559
|
export { enqueueLinksByClickingElements };
|
|
575
|
-
/** @internal */
|
|
576
|
-
export declare const playwrightUtils: {
|
|
577
|
-
injectFile: typeof injectFile;
|
|
578
|
-
injectJQuery: typeof injectJQuery;
|
|
579
|
-
gotoExtended: typeof gotoExtended;
|
|
580
|
-
blockRequests: typeof blockRequests;
|
|
581
|
-
enqueueLinksByClickingElements: typeof enqueueLinksByClickingElements;
|
|
582
|
-
parseWithCheerio: typeof parseWithCheerio;
|
|
583
|
-
infiniteScroll: typeof infiniteScroll;
|
|
584
|
-
saveSnapshot: typeof saveSnapshot;
|
|
585
|
-
compileScript: typeof compileScript;
|
|
586
|
-
closeCookieModals: typeof closeCookieModals;
|
|
587
|
-
RenderingTypePredictor: typeof RenderingTypePredictor;
|
|
588
|
-
handleCloudflareChallenge: typeof handleCloudflareChallenge;
|
|
589
|
-
};
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
import { readFile } from 'node:fs/promises';
|
|
21
21
|
import { createRequire } from 'node:module';
|
|
22
22
|
import vm from 'node:vm';
|
|
23
|
-
import { Configuration, KeyValueStore,
|
|
24
|
-
import {
|
|
23
|
+
import { Configuration, KeyValueStore, serviceLocator, SessionError, validators } from '@crawlee/browser';
|
|
24
|
+
import { sleep } from '@crawlee/utils';
|
|
25
|
+
import { expandShadowRoots, parseArgument, schemas } from '@crawlee/utils/internal';
|
|
25
26
|
import { z } from 'zod';
|
|
26
27
|
import { LruCache } from '@apify/datastructures';
|
|
27
28
|
import { enqueueLinksByClickingElements } from '../enqueue-links/click-elements.js';
|
|
28
|
-
import { RenderingTypePredictor } from './rendering-type-prediction.js';
|
|
29
29
|
const getLog = () => serviceLocator.getChildLog('Playwright Utils');
|
|
30
30
|
const require = createRequire(import.meta.url);
|
|
31
31
|
const jqueryPath = require.resolve('jquery');
|
|
@@ -404,6 +404,7 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
404
404
|
? null
|
|
405
405
|
: (await page.evaluate(`(${expandShadowRoots.toString()})(document)`));
|
|
406
406
|
const pageContent = html || (await page.content());
|
|
407
|
+
// Full cheerio (parse5) import for browser compliant parser
|
|
407
408
|
const { load } = await import('cheerio');
|
|
408
409
|
const $ = load(pageContent);
|
|
409
410
|
if (page.frames().length > 1 && !ignoreIframes) {
|
|
@@ -437,35 +438,6 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
437
438
|
}
|
|
438
439
|
return $;
|
|
439
440
|
}
|
|
440
|
-
let idcacPlaywright = null;
|
|
441
|
-
async function getIdcacPlaywright() {
|
|
442
|
-
if (idcacPlaywright)
|
|
443
|
-
return idcacPlaywright;
|
|
444
|
-
try {
|
|
445
|
-
idcacPlaywright = await import('idcac-playwright');
|
|
446
|
-
}
|
|
447
|
-
catch (error) {
|
|
448
|
-
getLog().warning(`Failed to import 'idcac-playwright'.
|
|
449
|
-
|
|
450
|
-
We recently made idcac-playwright an optional dependency due to licensing issues.
|
|
451
|
-
To use this feature, please install it manually by running
|
|
452
|
-
|
|
453
|
-
npm install idcac-playwright
|
|
454
|
-
|
|
455
|
-
Original error message follows:
|
|
456
|
-
|
|
457
|
-
${error.message}
|
|
458
|
-
`);
|
|
459
|
-
}
|
|
460
|
-
return idcacPlaywright;
|
|
461
|
-
}
|
|
462
|
-
export async function closeCookieModals(page) {
|
|
463
|
-
parseArgument(page, validators.browserPage);
|
|
464
|
-
const idcac = await getIdcacPlaywright();
|
|
465
|
-
if (idcac?.getInjectableScript()) {
|
|
466
|
-
await page.evaluate(idcac.getInjectableScript());
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
441
|
/**
|
|
470
442
|
* This helper tries to solve the Cloudflare challenge automatically by clicking on the checkbox.
|
|
471
443
|
* It will try to detect the Cloudflare page, click on the checkbox, and wait for 10 seconds (configurable
|
|
@@ -490,7 +462,7 @@ export async function closeCookieModals(page) {
|
|
|
490
462
|
* @param url current URL for request identification, only used for logging
|
|
491
463
|
* @param [options]
|
|
492
464
|
*/
|
|
493
|
-
async function handleCloudflareChallenge(page, url, options = {}) {
|
|
465
|
+
export async function handleCloudflareChallenge(page, url, options = {}) {
|
|
494
466
|
options.isBlockedCallback ??= async () => {
|
|
495
467
|
const isBlocked = await page.evaluate(() => {
|
|
496
468
|
return document.querySelector('h1')?.textContent?.trim().includes('Sorry, you have been blocked');
|
|
@@ -512,7 +484,11 @@ async function handleCloudflareChallenge(page, url, options = {}) {
|
|
|
512
484
|
};
|
|
513
485
|
// check if we ended up on the CF challenge page
|
|
514
486
|
const isChallenge = async () => {
|
|
515
|
-
return options.isChallengeCallback(page).catch(() =>
|
|
487
|
+
return options.isChallengeCallback(page).catch(async () => {
|
|
488
|
+
// the challenge page navigates itself, which destroys the context of an in-flight check
|
|
489
|
+
await page.waitForLoadState('domcontentloaded').catch(() => { });
|
|
490
|
+
return options.isChallengeCallback(page).catch(() => false);
|
|
491
|
+
});
|
|
516
492
|
};
|
|
517
493
|
if (!(await isChallenge())) {
|
|
518
494
|
await retryBlocked();
|
|
@@ -576,18 +552,3 @@ async function handleCloudflareChallenge(page, url, options = {}) {
|
|
|
576
552
|
return (await page.reload()) ?? undefined;
|
|
577
553
|
}
|
|
578
554
|
export { enqueueLinksByClickingElements };
|
|
579
|
-
/** @internal */
|
|
580
|
-
export const playwrightUtils = {
|
|
581
|
-
injectFile,
|
|
582
|
-
injectJQuery,
|
|
583
|
-
gotoExtended,
|
|
584
|
-
blockRequests,
|
|
585
|
-
enqueueLinksByClickingElements,
|
|
586
|
-
parseWithCheerio,
|
|
587
|
-
infiniteScroll,
|
|
588
|
-
saveSnapshot,
|
|
589
|
-
compileScript,
|
|
590
|
-
closeCookieModals,
|
|
591
|
-
RenderingTypePredictor,
|
|
592
|
-
handleCloudflareChallenge,
|
|
593
|
-
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RecoverableStatePersistenceOptions, Request } from '@crawlee/core';
|
|
2
|
+
import type { Awaitable } from '@crawlee/types';
|
|
2
3
|
export type RenderingType = 'clientOnly' | 'static';
|
|
3
4
|
export interface RenderingTypePredictorOptions {
|
|
4
5
|
/** A number between 0 and 1 that determines the desired ratio of rendering type detections */
|
|
@@ -12,13 +13,24 @@ export interface RenderingTypePredictorOptions {
|
|
|
12
13
|
* @experimental
|
|
13
14
|
*/
|
|
14
15
|
export interface IRenderingTypePredictor {
|
|
15
|
-
/**
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Predict the rendering type for a request, and how likely the crawler should be to verify it.
|
|
18
|
+
*
|
|
19
|
+
* Called once per request, before navigation - the crawler awaits it, so prefer loading whatever the
|
|
20
|
+
* prediction needs up front over per-request I/O.
|
|
21
|
+
*/
|
|
22
|
+
predict(request: Request): Awaitable<{
|
|
17
23
|
renderingType: RenderingType;
|
|
18
24
|
detectionProbabilityRecommendation: number;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Report a detected rendering type, so that future predictions can take it into account.
|
|
28
|
+
*
|
|
29
|
+
* The crawler does not await this per detection - it collects the returned promises and drains them in
|
|
30
|
+
* `teardown()`, so an implementation that persists results asynchronously can keep batching its writes
|
|
31
|
+
* and still be sure they land before the crawl ends.
|
|
32
|
+
*/
|
|
33
|
+
storeResult(requests: Request | Request[], renderingType: RenderingType): Awaitable<void>;
|
|
22
34
|
}
|
|
23
35
|
/**
|
|
24
36
|
* Stores rendering type information for previously crawled URLs and predicts the rendering type for URLs that have yet to be crawled and recommends when rendering type detection should be performed.
|
|
@@ -27,12 +39,16 @@ export interface IRenderingTypePredictor {
|
|
|
27
39
|
*/
|
|
28
40
|
export declare class RenderingTypePredictor implements IRenderingTypePredictor {
|
|
29
41
|
#private;
|
|
30
|
-
private state;
|
|
31
42
|
constructor({ detectionRatio, persistenceOptions }: RenderingTypePredictorOptions);
|
|
32
43
|
/**
|
|
33
44
|
* Initialize the predictor by restoring persisted state.
|
|
34
45
|
*/
|
|
35
46
|
initialize(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Stop persisting the model, writing it out one last time. `initialize()` reopens the persistence window.
|
|
49
|
+
*/
|
|
50
|
+
teardown(): Promise<void>;
|
|
51
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
36
52
|
/**
|
|
37
53
|
* Predict the rendering type for a given URL and request label.
|
|
38
54
|
*/
|
|
@@ -44,7 +60,4 @@ export declare class RenderingTypePredictor implements IRenderingTypePredictor {
|
|
|
44
60
|
* Store the rendering type for a given URL and request label. This updates the underlying prediction model, which may be costly.
|
|
45
61
|
*/
|
|
46
62
|
storeResult(requests: Request | Request[], renderingType: RenderingType): void;
|
|
47
|
-
private resultCount;
|
|
48
|
-
private calculateFeatureVector;
|
|
49
|
-
private retrain;
|
|
50
63
|
}
|