@crawlee/playwright 4.0.0-beta.124 → 4.0.0-beta.126

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 CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from '@crawlee/browser';
2
+ export * from './internals/playwright-browser-pool.js';
2
3
  export * from './internals/playwright-crawler.js';
3
4
  export * from './internals/playwright-launcher.js';
4
5
  export * from './internals/adaptive-playwright-crawler.js';
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from '@crawlee/browser';
2
+ export * from './internals/playwright-browser-pool.js';
2
3
  export * from './internals/playwright-crawler.js';
3
4
  export * from './internals/playwright-launcher.js';
4
5
  export * from './internals/adaptive-playwright-crawler.js';
@@ -1,34 +1,39 @@
1
1
  import type { BrowserHook, LoadedRequest, Request, RouterHandler, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
2
2
  import type { BasicCrawlerOptions } from '@crawlee/basic';
3
3
  import { BasicCrawler } from '@crawlee/basic';
4
- import type { ContextPipeline, CrawlingContext, EnqueueLinksOptions, GetUserDataFromRequest, RouterRoutes, StatisticPersistedState, StatisticState, StorageTransactionView } from '@crawlee/core';
5
- import { Statistics } from '@crawlee/core';
4
+ import type { ContextPipeline, CrawlingContext, EnqueueLinksOptions, GetUserDataFromRequest, RouterRoutes, StorageTransactionView } from '@crawlee/core';
6
5
  import type { Dictionary, Awaitable } from '@crawlee/types';
7
6
  import { type CheerioRoot } from '@crawlee/utils/internal';
8
7
  import { type Cheerio } from 'cheerio';
9
8
  import type { AnyNode } from 'domhandler';
10
9
  // @ts-ignore optional peer dependency or compatibility with es2022
11
10
  import type { Page } from 'playwright';
11
+ import { z } from 'zod';
12
12
  import type { PlaywrightCrawlingContext, PlaywrightGotoOptions } from './playwright-crawler.js';
13
13
  import { type IRenderingTypePredictor } from './utils/rendering-type-prediction.js';
14
- interface AdaptivePlaywrightCrawlerStatisticState extends StatisticState {
15
- httpOnlyRequestHandlerRuns?: number;
16
- browserRequestHandlerRuns?: number;
17
- renderingTypeMispredictions?: number;
18
- }
19
- interface AdaptivePlaywrightCrawlerPersistedStatisticState extends StatisticPersistedState {
20
- httpOnlyRequestHandlerRuns?: number;
21
- browserRequestHandlerRuns?: number;
22
- renderingTypeMispredictions?: number;
23
- }
24
- declare class AdaptivePlaywrightCrawlerStatistics extends Statistics {
25
- get state(): AdaptivePlaywrightCrawlerStatisticState;
26
- protected defaultState(): AdaptivePlaywrightCrawlerStatisticState;
27
- protected deserializeState(persistedState: AdaptivePlaywrightCrawlerPersistedStatisticState): AdaptivePlaywrightCrawlerStatisticState;
28
- trackHttpOnlyRequestHandlerRun(): void;
29
- trackBrowserRequestHandlerRun(): void;
30
- trackRenderingTypeMisprediction(): void;
31
- }
14
+ declare const adaptiveStatisticStateSchema: z.ZodObject<{
15
+ httpOnlyRequestHandlerRuns: z.ZodDefault<z.ZodNumber>;
16
+ browserRequestHandlerRuns: z.ZodDefault<z.ZodNumber>;
17
+ renderingTypeMispredictions: z.ZodDefault<z.ZodNumber>;
18
+ }, z.core.$strip>;
19
+ /**
20
+ * The extra statistics fields {@link AdaptivePlaywrightCrawler} tracks on top of the built-in
21
+ * {@link StatisticState} ones. They are available on `crawler.stats.state` and are persisted with the rest of
22
+ * the statistics.
23
+ */
24
+ export type AdaptivePlaywrightCrawlerStatisticState = z.infer<typeof adaptiveStatisticStateSchema>;
25
+ /**
26
+ * The {@link AdaptivePlaywrightCrawlerStatisticState} fields as a {@link Statistics} state extension, defaults
27
+ * and all. A {@link Statistics} instance to be injected into an {@link AdaptivePlaywrightCrawler} has to carry
28
+ * them - `deserialize.extend()` your own fields onto this one and pass the result as `stateExtension`.
29
+ */
30
+ export declare const adaptivePlaywrightCrawlerStatisticState: {
31
+ deserialize: z.ZodObject<{
32
+ httpOnlyRequestHandlerRuns: z.ZodDefault<z.ZodNumber>;
33
+ browserRequestHandlerRuns: z.ZodDefault<z.ZodNumber>;
34
+ renderingTypeMispredictions: z.ZodDefault<z.ZodNumber>;
35
+ }, z.core.$strip>;
36
+ };
32
37
  export interface AdaptivePlaywrightCrawlerContext<UserData extends Dictionary = any> extends CrawlingContext<UserData> {
33
38
  request: LoadedRequest<Request<UserData>>;
34
39
  /**
@@ -87,7 +92,7 @@ type AdaptiveHook<ContextExtension = Dictionary<never>> = BrowserHook<AdaptiveHo
87
92
  type AdaptivePostNavigationHook<ContextExtension = Dictionary<never>> = BrowserHook<Omit<AdaptiveHookContext, 'request'> & {
88
93
  request: LoadedRequest<Request>;
89
94
  }, ContextExtension>;
90
- export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<AdaptivePlaywrightCrawlerContext['request']>>> extends Omit<BasicCrawlerOptions<AdaptivePlaywrightCrawlerContext, ContextExtension, ExtendedContext, Routes>, 'preNavigationHooks' | 'postNavigationHooks'> {
95
+ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<AdaptivePlaywrightCrawlerContext['request']>>, StatisticStateExtension extends AdaptivePlaywrightCrawlerStatisticState = AdaptivePlaywrightCrawlerStatisticState> extends Omit<BasicCrawlerOptions<AdaptivePlaywrightCrawlerContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>, 'preNavigationHooks' | 'postNavigationHooks'> {
91
96
  /**
92
97
  * Async functions that are sequentially evaluated before the navigation. Good for setting additional cookies.
93
98
  * The function accepts a subset of the crawling context. If you attempt to access the `page` property during HTTP-only crawling,
@@ -177,10 +182,9 @@ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<
177
182
  *
178
183
  * @experimental
179
184
  */
180
- export declare class AdaptivePlaywrightCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<AdaptivePlaywrightCrawlerContext['request']>>> extends BasicCrawler<AdaptivePlaywrightCrawlerContext, ContextExtension, ExtendedContext, Routes> {
185
+ export declare class AdaptivePlaywrightCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<AdaptivePlaywrightCrawlerContext['request']>>, StatisticStateExtension extends AdaptivePlaywrightCrawlerStatisticState = AdaptivePlaywrightCrawlerStatisticState> extends BasicCrawler<AdaptivePlaywrightCrawlerContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
181
186
  #private;
182
- get stats(): AdaptivePlaywrightCrawlerStatistics;
183
- constructor(options?: AdaptivePlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes>);
187
+ constructor(options?: AdaptivePlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
184
188
  protected init(): Promise<void>;
185
189
  protected buildContextPipeline(): ContextPipeline<CrawlingContext<Dictionary>, CrawlingContext<Dictionary> & {
186
190
  readonly request: LoadedRequest<Request<Dictionary>>;
@@ -8,39 +8,22 @@ import { z } from 'zod';
8
8
  import { addTimeoutToPromise } from '@apify/timeout';
9
9
  import { PlaywrightCrawler } from './playwright-crawler.js';
10
10
  import { RenderingTypePredictor, } from './utils/rendering-type-prediction.js';
11
- class AdaptivePlaywrightCrawlerStatistics extends Statistics {
12
- get state() {
13
- return super.state;
14
- }
15
- defaultState() {
16
- return {
17
- ...super.defaultState(),
18
- httpOnlyRequestHandlerRuns: 0,
19
- browserRequestHandlerRuns: 0,
20
- renderingTypeMispredictions: 0,
21
- };
22
- }
23
- deserializeState(persistedState) {
24
- return {
25
- ...super.deserializeState(persistedState),
26
- httpOnlyRequestHandlerRuns: persistedState.httpOnlyRequestHandlerRuns,
27
- browserRequestHandlerRuns: persistedState.browserRequestHandlerRuns,
28
- renderingTypeMispredictions: persistedState.renderingTypeMispredictions,
29
- };
30
- }
31
- trackHttpOnlyRequestHandlerRun() {
32
- this.state.httpOnlyRequestHandlerRuns ??= 0;
33
- this.state.httpOnlyRequestHandlerRuns += 1;
34
- }
35
- trackBrowserRequestHandlerRun() {
36
- this.state.browserRequestHandlerRuns ??= 0;
37
- this.state.browserRequestHandlerRuns += 1;
38
- }
39
- trackRenderingTypeMisprediction() {
40
- this.state.renderingTypeMispredictions ??= 0;
41
- this.state.renderingTypeMispredictions += 1;
42
- }
43
- }
11
+ const adaptiveStatisticStateSchema = z.object({
12
+ /** How many requests were handled by the HTTP-only request handler. */
13
+ httpOnlyRequestHandlerRuns: z.number().default(0),
14
+ /** How many requests were handled in a browser. */
15
+ browserRequestHandlerRuns: z.number().default(0),
16
+ /** How many times the HTTP-only handler produced a result the `resultChecker` rejected. */
17
+ renderingTypeMispredictions: z.number().default(0),
18
+ });
19
+ /**
20
+ * The {@link AdaptivePlaywrightCrawlerStatisticState} fields as a {@link Statistics} state extension, defaults
21
+ * and all. A {@link Statistics} instance to be injected into an {@link AdaptivePlaywrightCrawler} has to carry
22
+ * them - `deserialize.extend()` your own fields onto this one and pass the result as `stateExtension`.
23
+ */
24
+ export const adaptivePlaywrightCrawlerStatisticState = {
25
+ deserialize: adaptiveStatisticStateSchema,
26
+ };
44
27
  const proxyLogMethods = [
45
28
  'error',
46
29
  'exception',
@@ -88,10 +71,6 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
88
71
  #staticContextPipeline;
89
72
  #browserContextPipeline;
90
73
  #individualRequestHandlerTimeoutMillis;
91
- // The constructor always injects an `AdaptivePlaywrightCrawlerStatistics`, so narrowing the cast is sound.
92
- get stats() {
93
- return super.stats;
94
- }
95
74
  /**
96
75
  * The write policy of the per-attempt transactions. Defaults the request queue to `deferred`:
97
76
  * a discarded attempt's enqueues must never reach the queue.
@@ -103,6 +82,16 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
103
82
  // The user's value is replaced by `false` in the `super` call below — validate it separately,
104
83
  // wrapped in an object so the error still names the field.
105
84
  parseArgument({ transactionalStorage }, z.object({ transactionalStorage: BasicCrawler.optionsShape.transactionalStorage }), 'AdaptivePlaywrightCrawlerOptions');
85
+ // The extra fields are only tracked if the injected instance was built with them - the types enforce that,
86
+ // but plain JS callers would otherwise silently increment `undefined` into a sticky `NaN`. Extend
87
+ // `adaptivePlaywrightCrawlerStatisticState` to satisfy this.
88
+ if (statistics !== undefined) {
89
+ parseArgument(statistics.state, z.object({
90
+ httpOnlyRequestHandlerRuns: z.number(),
91
+ browserRequestHandlerRuns: z.number(),
92
+ renderingTypeMispredictions: z.number(),
93
+ }), 'statistics.state');
94
+ }
106
95
  // Per-attempt buffering is load-bearing here: the handler runs up to twice per request and the
107
96
  // losing attempt's writes must be discardable.
108
97
  if (transactionalStorage === false) {
@@ -111,20 +100,19 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
111
100
  'attempts. `transactionalStorage: false` is therefore not supported; a write policy ' +
112
101
  'object is accepted and forwarded to the per-attempt transactions.');
113
102
  }
114
- if (statistics !== undefined && !(statistics instanceof AdaptivePlaywrightCrawlerStatistics)) {
115
- throw new Error('AdaptivePlaywrightCrawler tracks extra fields on its own Statistics subclass and cannot use a ' +
116
- 'plain `statistics` instance. Omit the option to let the crawler build its own.');
117
- }
118
103
  super({
119
104
  ...rest,
120
105
  errorHandler,
121
106
  failedRequestHandler,
122
107
  requestHandler,
123
108
  requestHandlerTimeoutSecs,
124
- // Inject our subclass so the base tracks the extra adaptive fields instead of building a plain `Statistics`.
109
+ // The base would build a `Statistics` without the adaptive fields, so provide a default that has them.
110
+ // The cast covers a `StatisticStateExtension` that adds further fields - those can only come from an
111
+ // injected instance, in which case this default is never built.
125
112
  statistics: statistics ??
126
- new AdaptivePlaywrightCrawlerStatistics({
113
+ new Statistics({
127
114
  logMessage: `${AdaptivePlaywrightCrawler.name} request statistics:`,
115
+ stateExtension: adaptivePlaywrightCrawlerStatisticState,
128
116
  }),
129
117
  contextPipelineBuilder: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
130
118
  // The base crawler must not wrap requests in a transaction of its own - this crawler opens
@@ -334,7 +322,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
334
322
  try {
335
323
  if (renderingTypePrediction.renderingType === 'static' && !shouldDetectRenderingType) {
336
324
  crawlingContext.log.debug(`Running HTTP-only request handler for ${crawlingContext.request.url}`);
337
- this.stats.trackHttpOnlyRequestHandlerRun();
325
+ this.stats.state.httpOnlyRequestHandlerRuns++;
338
326
  const plainHTTPRun = await this.crawlOne('static', crawlingContext, crawlingContext.useState, transactions);
339
327
  if (plainHTTPRun.ok && this.#resultChecker(plainHTTPRun.result)) {
340
328
  crawlingContext.log.debug(`HTTP-only request handler succeeded for ${crawlingContext.request.url}`);
@@ -354,11 +342,11 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
354
342
  }
355
343
  else {
356
344
  crawlingContext.log.warning(`HTTP-only request handler returned a suspicious result for ${crawlingContext.request.url}`);
357
- this.stats.trackRenderingTypeMisprediction();
345
+ this.stats.state.renderingTypeMispredictions++;
358
346
  }
359
347
  }
360
348
  crawlingContext.log.debug(`Running browser request handler for ${crawlingContext.request.url}`);
361
- this.stats.trackBrowserRequestHandlerRun();
349
+ this.stats.state.browserRequestHandlerRuns++;
362
350
  // Run the request handler in a browser. The copy of the crawler state is kept so that we can perform
363
351
  // a rendering type detection if necessary. Without this measure, the HTTP request handler would run
364
352
  // under different conditions, which could change its behavior. Changes done to the crawler state by
@@ -0,0 +1,71 @@
1
+ import type { Configuration } from '@crawlee/browser';
2
+ import type { BrowserPool, BrowserPoolHooks, BrowserPoolOptions, PlaywrightPlugin, RemoteBrowserPool, RemoteBrowserPoolOptions } from '@crawlee/browser-pool';
3
+ // @ts-ignore optional peer dependency or compatibility with es2022
4
+ import type { Page } from 'playwright';
5
+ import type { PlaywrightLaunchContext } from './playwright-launcher.js';
6
+ /** A {@link BrowserPool} of Playwright browsers, as built by {@link playwrightBrowserPool}. */
7
+ export type PlaywrightBrowserPool = BrowserPool<{
8
+ browserPlugins: [PlaywrightPlugin];
9
+ }, [PlaywrightPlugin]>;
10
+ export interface PlaywrightBrowserPoolOptions extends Omit<BrowserPoolOptions, 'browserPlugins'>, BrowserPoolHooks<ReturnType<PlaywrightPlugin['createController']>, ReturnType<PlaywrightPlugin['createLaunchContext']>, Page> {
11
+ /** How to launch the browser: which Playwright browser type, proxy, user data dir, ... */
12
+ launchContext?: PlaywrightLaunchContext;
13
+ /**
14
+ * Whether to run the browser in headless mode. Shorthand for `launchContext.launchOptions.headless`.
15
+ * Defaults to `true`, and can also be set via {@link Configuration}.
16
+ */
17
+ headless?: boolean;
18
+ /** Configuration to read the browser defaults from. Defaults to the global configuration. */
19
+ configuration?: Configuration;
20
+ }
21
+ export interface RemotePlaywrightBrowserPoolOptions extends Pick<PlaywrightBrowserPoolOptions, 'launchContext' | 'headless' | 'configuration'>, Omit<RemoteBrowserPoolOptions, 'browserPlugins'> {
22
+ }
23
+ /**
24
+ * Builds a {@link BrowserPool} of Playwright browsers to pass to a {@link PlaywrightCrawler} as its
25
+ * {@link BrowserCrawlerOptions.browserPool|`browserPool`}.
26
+ *
27
+ * It accepts every {@link BrowserPoolOptions|`BrowserPool` option} plus the crawler's own `launchContext` and
28
+ * `headless`, and derives the browser plugin from them - so a pool built here always matches the crawler it is
29
+ * given to, and configuring one never means assembling a {@link PlaywrightPlugin} by hand.
30
+ *
31
+ * **Example usage:**
32
+ *
33
+ * ```javascript
34
+ * const crawler = new PlaywrightCrawler({
35
+ * browserPool: playwrightBrowserPool({
36
+ * maxOpenPagesPerBrowser: 1,
37
+ * launchContext: { launcher: firefox },
38
+ * }),
39
+ * requestHandler: async ({ page }) => { ... },
40
+ * });
41
+ * ```
42
+ *
43
+ * The returned pool is *not* torn down by the crawler, which is what makes it shareable between crawlers.
44
+ *
45
+ * @category Browser management
46
+ */
47
+ export declare function playwrightBrowserPool(options?: PlaywrightBrowserPoolOptions): PlaywrightBrowserPool;
48
+ /**
49
+ * The {@link RemoteBrowserPool} counterpart of {@link playwrightBrowserPool}: connects to a remote browser
50
+ * service (Browserbase, Browserless, Steel, ...) with a Playwright plugin derived from `launchContext`.
51
+ *
52
+ * A {@link PlaywrightCrawler} accepts the same connection details directly via
53
+ * {@link BrowserCrawlerOptions.remoteBrowser|`remoteBrowser`}; reach for this factory when you also need to
54
+ * tune the wrapping pool, or to share one remote pool between crawlers.
55
+ *
56
+ * **Example usage:**
57
+ *
58
+ * ```javascript
59
+ * const crawler = new PlaywrightCrawler({
60
+ * browserPool: remotePlaywrightBrowserPool({
61
+ * endpoint: 'wss://production-sfo.browserless.io?token=xxx',
62
+ * maxOpenBrowsers: 2,
63
+ * browserPoolOptions: { useFingerprints: false },
64
+ * }),
65
+ * requestHandler: async ({ page }) => { ... },
66
+ * });
67
+ * ```
68
+ *
69
+ * @category Browser management
70
+ */
71
+ export declare function remotePlaywrightBrowserPool(options: RemotePlaywrightBrowserPoolOptions): RemoteBrowserPool<Page>;
@@ -0,0 +1,61 @@
1
+ import { PlaywrightLauncher } from './playwright-launcher.js';
2
+ /**
3
+ * Builds a {@link BrowserPool} of Playwright browsers to pass to a {@link PlaywrightCrawler} as its
4
+ * {@link BrowserCrawlerOptions.browserPool|`browserPool`}.
5
+ *
6
+ * It accepts every {@link BrowserPoolOptions|`BrowserPool` option} plus the crawler's own `launchContext` and
7
+ * `headless`, and derives the browser plugin from them - so a pool built here always matches the crawler it is
8
+ * given to, and configuring one never means assembling a {@link PlaywrightPlugin} by hand.
9
+ *
10
+ * **Example usage:**
11
+ *
12
+ * ```javascript
13
+ * const crawler = new PlaywrightCrawler({
14
+ * browserPool: playwrightBrowserPool({
15
+ * maxOpenPagesPerBrowser: 1,
16
+ * launchContext: { launcher: firefox },
17
+ * }),
18
+ * requestHandler: async ({ page }) => { ... },
19
+ * });
20
+ * ```
21
+ *
22
+ * The returned pool is *not* torn down by the crawler, which is what makes it shareable between crawlers.
23
+ *
24
+ * @category Browser management
25
+ */
26
+ export function playwrightBrowserPool(options = {}) {
27
+ const { launchContext, headless, configuration, ...poolOptions } = options;
28
+ return playwrightLauncher(launchContext, headless, configuration).createBrowserPool(poolOptions);
29
+ }
30
+ /**
31
+ * The {@link RemoteBrowserPool} counterpart of {@link playwrightBrowserPool}: connects to a remote browser
32
+ * service (Browserbase, Browserless, Steel, ...) with a Playwright plugin derived from `launchContext`.
33
+ *
34
+ * A {@link PlaywrightCrawler} accepts the same connection details directly via
35
+ * {@link BrowserCrawlerOptions.remoteBrowser|`remoteBrowser`}; reach for this factory when you also need to
36
+ * tune the wrapping pool, or to share one remote pool between crawlers.
37
+ *
38
+ * **Example usage:**
39
+ *
40
+ * ```javascript
41
+ * const crawler = new PlaywrightCrawler({
42
+ * browserPool: remotePlaywrightBrowserPool({
43
+ * endpoint: 'wss://production-sfo.browserless.io?token=xxx',
44
+ * maxOpenBrowsers: 2,
45
+ * browserPoolOptions: { useFingerprints: false },
46
+ * }),
47
+ * requestHandler: async ({ page }) => { ... },
48
+ * });
49
+ * ```
50
+ *
51
+ * @category Browser management
52
+ */
53
+ export function remotePlaywrightBrowserPool(options) {
54
+ const { launchContext, headless, configuration, ...remoteOptions } = options;
55
+ return playwrightLauncher(launchContext, headless, configuration).createRemoteBrowserPool(remoteOptions);
56
+ }
57
+ function playwrightLauncher(launchContext = {}, headless, configuration) {
58
+ return new PlaywrightLauncher(headless == null
59
+ ? launchContext
60
+ : { ...launchContext, launchOptions: { ...launchContext.launchOptions, headless } }, configuration);
61
+ }
@@ -1,6 +1,5 @@
1
1
  import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, GetUserDataFromRequest, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
2
2
  import { BrowserCrawler } from '@crawlee/browser';
3
- import type { PlaywrightPlugin } from '@crawlee/browser-pool';
4
3
  import type { Dictionary } from '@crawlee/types';
5
4
  // @ts-ignore optional peer dependency or compatibility with es2022
6
5
  import type { Download, LaunchOptions, Page, Response } from 'playwright';
@@ -12,13 +11,16 @@ export type PlaywrightGotoOptions = NonNullable<Parameters<Page['goto']>[1]>;
12
11
  export interface PlaywrightCrawlingContext<UserData extends Dictionary = any> extends BrowserCrawlingContext<Page, Response, UserData, PlaywrightGotoOptions>, PlaywrightContextUtils {
13
12
  }
14
13
  export type PlaywrightHook<UserData extends Dictionary = any> = BrowserHook<PlaywrightCrawlingContext<UserData>>;
15
- export interface PlaywrightCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends PlaywrightCrawlingContext = PlaywrightCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PlaywrightCrawlingContext['request']>>> extends BrowserCrawlerOptions<Page, Response, PlaywrightCrawlingContext, ContextExtension, ExtendedContext, {
16
- browserPlugins: [PlaywrightPlugin];
17
- }, Routes> {
14
+ export interface PlaywrightCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends PlaywrightCrawlingContext = PlaywrightCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PlaywrightCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends BrowserCrawlerOptions<Page, Response, PlaywrightCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
18
15
  /**
19
16
  * The same options as used by {@link launchPlaywright}.
20
17
  */
21
18
  launchContext?: PlaywrightLaunchContext;
19
+ /**
20
+ * Whether to run browser in headless mode. Defaults to `true`.
21
+ * Can be also set via {@link Configuration}.
22
+ */
23
+ headless?: boolean;
22
24
  /**
23
25
  * Function that is called to process each request.
24
26
  *
@@ -142,18 +144,16 @@ export interface PlaywrightCrawlerOptions<ContextExtension = Dictionary<never>,
142
144
  * ```
143
145
  * @category Crawlers
144
146
  */
145
- export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends PlaywrightCrawlingContext = PlaywrightCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PlaywrightCrawlingContext['request']>>> extends BrowserCrawler<Page, Response, {
146
- browserPlugins: [PlaywrightPlugin];
147
- }, LaunchOptions, PlaywrightCrawlingContext, ContextExtension, ExtendedContext, Routes> {
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> {
148
148
  protected static optionsShape: {
149
- browserPoolOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
149
+ headless: z.ZodOptional<z.ZodBoolean>;
150
150
  launcher: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
151
151
  navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
152
152
  preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
153
153
  postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
154
154
  launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
155
- headless: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
156
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
157
  remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
158
158
  saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
159
159
  proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
@@ -205,14 +205,14 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
205
205
  id: z.ZodOptional<z.ZodString>;
206
206
  };
207
207
  protected static optionsSchema: z.ZodObject<{
208
- browserPoolOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
208
+ headless: z.ZodOptional<z.ZodBoolean>;
209
209
  launcher: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
210
210
  navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
211
211
  preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
212
212
  postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
213
213
  launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
214
- headless: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
215
214
  browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
215
+ browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
216
216
  remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
217
217
  saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
218
218
  proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
@@ -266,7 +266,7 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
266
266
  /**
267
267
  * All `PlaywrightCrawler` parameters are passed via an options object.
268
268
  */
269
- constructor(options?: PlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes>);
269
+ constructor(options?: PlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
270
270
  // @ts-ignore optional peer dependency or compatibility with es2022
271
271
  protected buildContextPipeline(): import("@crawlee/browser").ContextPipeline<import("@crawlee/browser").CrawlingContext<Dictionary>, BrowserCrawlingContext<Page, Response, Dictionary, Dictionary> & {
272
272
  injectFile: (filePath: string, options?: InjectFileOptions) => Promise<unknown>;
@@ -1,6 +1,6 @@
1
- import { BrowserCrawler, parseArgument, RequestState, Router, schemas, serviceLocator } from '@crawlee/browser';
1
+ import { assertBrowserPoolNotConfigured, BrowserCrawler, parseArgument, RequestState, Router, schemas, serviceLocator, } from '@crawlee/browser';
2
2
  import { z } from 'zod';
3
- import { PlaywrightLauncher } from './playwright-launcher.js';
3
+ import { playwrightBrowserPool, remotePlaywrightBrowserPool } from './playwright-browser-pool.js';
4
4
  import { gotoExtended, playwrightUtils } from './utils/playwright-utils.js';
5
5
  /**
6
6
  * Provides a simple framework for parallel crawling of web pages
@@ -70,7 +70,7 @@ import { gotoExtended, playwrightUtils } from './utils/playwright-utils.js';
70
70
  export class PlaywrightCrawler extends BrowserCrawler {
71
71
  static optionsShape = {
72
72
  ...BrowserCrawler.optionsShape,
73
- browserPoolOptions: schemas.anyObject.optional(),
73
+ headless: z.boolean().optional(),
74
74
  launcher: schemas.anyObject.optional(),
75
75
  };
76
76
  static optionsSchema = z.strictObject(PlaywrightCrawler.optionsShape);
@@ -79,29 +79,25 @@ export class PlaywrightCrawler extends BrowserCrawler {
79
79
  */
80
80
  constructor(options = {}) {
81
81
  const parsedOptions = parseArgument(options, PlaywrightCrawler.optionsSchema, 'PlaywrightCrawlerOptions');
82
- const { launchContext, headless, contextPipelineBuilder, ...browserCrawlerOptions } = parsedOptions;
83
- const browserPoolOptions = {
84
- ...parsedOptions.browserPoolOptions,
85
- };
82
+ const { launchContext, headless, configuration, contextPipelineBuilder, ...browserCrawlerOptions } = parsedOptions;
86
83
  if (launchContext.proxyUrl) {
87
84
  throw new Error('PlaywrightCrawlerOptions.launchContext.proxyUrl is not allowed in PlaywrightCrawler.' +
88
85
  'Use PlaywrightCrawlerOptions.proxyConfiguration');
89
86
  }
90
- // `browserPlugins` is working when it's not overridden by `launchContext`,
91
- // which for crawlers it is always overridden. Hence the error to use the other option.
92
- if (browserPoolOptions.browserPlugins) {
93
- throw new Error('browserPoolOptions.browserPlugins is disallowed. Use launchContext.launcher instead.');
94
- }
95
- if (headless != null) {
96
- launchContext.launchOptions ??= {};
97
- launchContext.launchOptions.headless = headless;
87
+ if (options.browserPool) {
88
+ // The raw options, not the parsed ones: `launchContext` has a default, so by now it is always set.
89
+ assertBrowserPoolNotConfigured(new.target.name, {
90
+ launchContext: options.launchContext,
91
+ headless: options.headless,
92
+ });
98
93
  }
99
- const playwrightLauncher = new PlaywrightLauncher(launchContext, parsedOptions.configuration);
100
- browserPoolOptions.browserPlugins = [playwrightLauncher.createBrowserPlugin()];
101
94
  super({
102
95
  ...browserCrawlerOptions,
103
96
  launchContext,
104
- browserPoolOptions,
97
+ configuration,
98
+ browserPoolBuilder: (remoteBrowser) => remoteBrowser
99
+ ? remotePlaywrightBrowserPool({ ...remoteBrowser, launchContext, headless, configuration })
100
+ : playwrightBrowserPool({ launchContext, headless, configuration }),
105
101
  contextPipelineBuilder: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
106
102
  });
107
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/playwright",
3
- "version": "4.0.0-beta.124",
3
+ "version": "4.0.0-beta.126",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -49,13 +49,13 @@
49
49
  "dependencies": {
50
50
  "@apify/datastructures": "^2.0.3",
51
51
  "@apify/timeout": "^0.4.4",
52
- "@crawlee/basic": "4.0.0-beta.124",
53
- "@crawlee/browser": "4.0.0-beta.124",
54
- "@crawlee/browser-pool": "4.0.0-beta.124",
55
- "@crawlee/cheerio": "4.0.0-beta.124",
56
- "@crawlee/core": "4.0.0-beta.124",
57
- "@crawlee/types": "4.0.0-beta.124",
58
- "@crawlee/utils": "4.0.0-beta.124",
52
+ "@crawlee/basic": "4.0.0-beta.126",
53
+ "@crawlee/browser": "4.0.0-beta.126",
54
+ "@crawlee/browser-pool": "4.0.0-beta.126",
55
+ "@crawlee/cheerio": "4.0.0-beta.126",
56
+ "@crawlee/core": "4.0.0-beta.126",
57
+ "@crawlee/types": "4.0.0-beta.126",
58
+ "@crawlee/utils": "4.0.0-beta.126",
59
59
  "cheerio": "^1.0.0",
60
60
  "jquery": "^3.7.1",
61
61
  "ml-logistic-regression": "^2.0.0",
@@ -84,5 +84,5 @@
84
84
  }
85
85
  }
86
86
  },
87
- "gitHead": "0694ee1b94c755b98141671baa93cc363f2bf8e3"
87
+ "gitHead": "5f0a8c1a43d4587f8640881fc55836a23e1c4f30"
88
88
  }