@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.
@@ -1,12 +1,11 @@
1
- import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, GetUserDataFromRequest, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
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 { Download, LaunchOptions, Page, Response } from 'playwright';
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 { BlockRequestsOptions, DirectNavigationOptions, HandleCloudflareChallengeOptions, InfiniteScrollOptions, InjectFileOptions, PlaywrightContextUtils, SaveSnapshotOptions } from './utils/playwright-utils.js';
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, LaunchOptions, PlaywrightCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
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.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
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.ZodCustom<import("@crawlee/browser").Configuration, import("@crawlee/browser").Configuration>>;
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.ZodCustom<import("@crawlee/browser").EventManager, import("@crawlee/browser").EventManager>>;
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.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
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.ZodCustom<import("@crawlee/browser").Configuration, import("@crawlee/browser").Configuration>>;
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.ZodCustom<import("@crawlee/browser").EventManager, import("@crawlee/browser").EventManager>>;
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 { assertBrowserPoolNotConfigured, BrowserCrawler, parseArgument, RequestState, Router, schemas, serviceLocator, } from '@crawlee/browser';
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, playwrightUtils } from './utils/playwright-utils.js';
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, contextPipelineBuilder, ...browserCrawlerOptions } = parsedOptions;
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: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
105
+ contextPipelineBuilder: () => this.#buildContextPipeline(),
102
106
  });
103
107
  }
104
- buildContextPipeline() {
105
- return super.buildContextPipeline().compose({ action: this.enhanceContext.bind(this) });
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) => playwrightUtils.injectFile(context.page, 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 playwrightUtils.injectJQuery(context.page);
126
+ await injectJQuery(context.page);
123
127
  return;
124
128
  }
125
- await playwrightUtils.injectJQuery(context.page, { surviveNavigations: false });
129
+ await injectJQuery(context.page, { surviveNavigations: false });
126
130
  },
127
- blockRequests: async (options) => playwrightUtils.blockRequests(context.page, 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 playwrightUtils.parseWithCheerio(context.page, this.ignoreShadowRoots, this.ignoreIframes);
137
+ return parseWithCheerio(context.page, this.ignoreShadowRoots, this.ignoreIframes);
134
138
  },
135
- infiniteScroll: async (options) => playwrightUtils.infiniteScroll(context.page, options),
139
+ infiniteScroll: async (options) => infiniteScroll(context.page, options),
136
140
  listDownloads: async () => downloads,
137
- saveSnapshot: async (options) => playwrightUtils.saveSnapshot(context.page, {
141
+ saveSnapshot: async (options) => saveSnapshot(context.page, {
138
142
  ...options,
139
143
  configuration: serviceLocator.getConfiguration(),
140
144
  }),
141
- enqueueLinksByClickingElements: async (options) => playwrightUtils.enqueueLinksByClickingElements({
145
+ enqueueLinksByClickingElements: async (options) => enqueueLinksByClickingElements({
142
146
  ...options,
143
147
  page: context.page,
144
148
  requestManager: this.requestManager,
145
149
  }),
146
- compileScript: (scriptString, ctx) => playwrightUtils.compileScript(scriptString, ctx),
147
- closeCookieModals: async () => playwrightUtils.closeCookieModals(context.page),
150
+ compileScript: (scriptString, ctx) => compileScript(scriptString, ctx),
148
151
  handleCloudflareChallenge: async (options) => {
149
- return playwrightUtils.handleCloudflareChallenge(context.page, context.request.url, options);
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
- // @ts-ignore optional peer dependency or compatibility with es2022
94
- launchContextOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
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, parseArgument, schemas } from '@crawlee/browser';
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 schemas — the launcher module object must keep its prototype through parsing.
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 { type CheerioRoot } from '@crawlee/utils/internal';
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<CheerioRoot>;
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<CheerioRoot>;
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, parseArgument, schemas, serviceLocator, SessionError, validators, } from '@crawlee/browser';
24
- import { expandShadowRoots, sleep } from '@crawlee/utils';
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(() => false);
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
- /** Predict the rendering type for a request, and how likely the crawler should be to verify it. */
16
- predict(request: Request): {
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
- /** Report a detected rendering type, so that future predictions can take it into account. */
21
- storeResult(requests: Request | Request[], renderingType: RenderingType): void;
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
  }