@crawlee/puppeteer 4.0.0-beta.99 → 4.0.0-rc.0
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 +1 -0
- package/index.js +1 -0
- package/internals/enqueue-links/click-elements.d.ts +18 -54
- package/internals/enqueue-links/click-elements.js +27 -51
- package/internals/puppeteer-browser-pool.d.ts +55 -0
- package/internals/puppeteer-browser-pool.js +48 -0
- package/internals/puppeteer-crawler.d.ts +128 -103
- package/internals/puppeteer-crawler.js +21 -22
- package/internals/puppeteer-launcher.d.ts +22 -15
- package/internals/puppeteer-launcher.js +5 -5
- package/internals/utils/puppeteer_request_interception.js +6 -6
- package/internals/utils/puppeteer_utils.d.ts +5 -6
- package/internals/utils/puppeteer_utils.js +54 -50
- package/package.json +9 -10
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@crawlee/browser';
|
|
2
|
+
export * from './internals/puppeteer-browser-pool.js';
|
|
2
3
|
export * from './internals/puppeteer-crawler.js';
|
|
3
4
|
export * from './internals/puppeteer-launcher.js';
|
|
4
5
|
export * as puppeteerRequestInterception from './internals/utils/puppeteer_request_interception.js';
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@crawlee/browser';
|
|
2
|
+
export * from './internals/puppeteer-browser-pool.js';
|
|
2
3
|
export * from './internals/puppeteer-crawler.js';
|
|
3
4
|
export * from './internals/puppeteer-launcher.js';
|
|
4
5
|
export * as puppeteerRequestInterception from './internals/utils/puppeteer_request_interception.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IRequestManager, RequestTransform, SkippedRequestCallback, UrlPatternInput } from '@crawlee/browser';
|
|
2
2
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
3
3
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
4
4
|
import type { ClickOptions, Page, Target } from 'puppeteer';
|
|
@@ -25,64 +25,29 @@ export interface EnqueueLinksByClickingElementsOptions {
|
|
|
25
25
|
*/
|
|
26
26
|
clickOptions?: ClickOptions;
|
|
27
27
|
/**
|
|
28
|
-
* An array of
|
|
29
|
-
* containing glob pattern strings matching the URLs to be enqueued.
|
|
28
|
+
* An array of URL patterns that URLs must match to be enqueued.
|
|
30
29
|
*
|
|
31
|
-
*
|
|
32
|
-
* All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
|
|
33
|
-
*
|
|
34
|
-
* The matching is always case-insensitive.
|
|
35
|
-
* If you need case-sensitive matching, use `regexps` property directly.
|
|
36
|
-
*
|
|
37
|
-
* If `globs` is an empty array or `undefined`, then the function
|
|
38
|
-
* enqueues all the intercepted navigation requests produced by the page
|
|
39
|
-
* after clicking on elements matching the provided CSS selector.
|
|
40
|
-
*/
|
|
41
|
-
globs?: GlobInput[];
|
|
42
|
-
/**
|
|
43
|
-
* An array of glob pattern strings, regexp patterns or plain objects
|
|
44
|
-
* containing patterns matching URLs that will **never** be enqueued.
|
|
45
|
-
*
|
|
46
|
-
* The plain objects must include either the `glob` property or the `regexp` property.
|
|
30
|
+
* Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
|
|
47
31
|
*
|
|
48
32
|
* Glob matching is always case-insensitive.
|
|
49
|
-
* If you need case-sensitive matching,
|
|
50
|
-
*/
|
|
51
|
-
exclude?: readonly (GlobInput | RegExpInput)[];
|
|
52
|
-
/**
|
|
53
|
-
* An array of regular expressions or plain objects
|
|
54
|
-
* containing regular expressions matching the URLs to be enqueued.
|
|
55
|
-
*
|
|
56
|
-
* The plain objects must include at least the `regexp` property, which holds the regular expression.
|
|
57
|
-
* All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
|
|
33
|
+
* If you need case-sensitive matching, use a `RegExp`.
|
|
58
34
|
*
|
|
59
|
-
* If `
|
|
35
|
+
* If `include` is an empty array or `undefined`, then the function
|
|
60
36
|
* enqueues all the intercepted navigation requests produced by the page
|
|
61
37
|
* after clicking on elements matching the provided CSS selector.
|
|
62
38
|
*/
|
|
63
|
-
|
|
39
|
+
include?: UrlPatternInput[];
|
|
64
40
|
/**
|
|
65
|
-
*
|
|
66
|
-
* Please use `globs` or `regexps` instead.
|
|
67
|
-
*
|
|
68
|
-
* An array of {@link PseudoUrl} strings or plain objects
|
|
69
|
-
* containing {@link PseudoUrl} strings matching the URLs to be enqueued.
|
|
70
|
-
*
|
|
71
|
-
* The plain objects must include at least the `purl` property, which holds the pseudo-URL pattern string.
|
|
72
|
-
* All remaining keys will be used as request options for the corresponding enqueued {@link Request} objects.
|
|
41
|
+
* An array of URL patterns. Matching URLs will **not** be enqueued.
|
|
73
42
|
*
|
|
74
|
-
*
|
|
75
|
-
* If you need case-sensitive matching, use `regexps` property directly.
|
|
43
|
+
* Accepts glob pattern strings, `{ glob: string }` objects, `RegExp` instances, or `{ regexp: RegExp }` objects.
|
|
76
44
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* after clicking on elements matching the provided CSS selector.
|
|
80
|
-
*
|
|
81
|
-
* @deprecated prefer using `globs` or `regexps` instead
|
|
45
|
+
* Glob matching is always case-insensitive.
|
|
46
|
+
* If you need case-sensitive matching, use a `RegExp`.
|
|
82
47
|
*/
|
|
83
|
-
|
|
48
|
+
exclude?: readonly UrlPatternInput[];
|
|
84
49
|
/**
|
|
85
|
-
* After
|
|
50
|
+
* After request options are filtered by `include`/`exclude` patterns,
|
|
86
51
|
* this function can be used to remove them or modify their contents such as `userData`, `payload` or, most importantly
|
|
87
52
|
* `uniqueKey`. This is useful when you need to enqueue multiple `Requests` to the queue that share the same URL,
|
|
88
53
|
* but differ in methods or payloads, or to dynamically update or create `userData`.
|
|
@@ -97,8 +62,8 @@ export interface EnqueueLinksByClickingElementsOptions {
|
|
|
97
62
|
* }
|
|
98
63
|
* ```
|
|
99
64
|
*
|
|
100
|
-
* Note that `transformRequestFunction` has the highest priority and can overwrite
|
|
101
|
-
*
|
|
65
|
+
* Note that `transformRequestFunction` has the highest priority and can overwrite
|
|
66
|
+
* the global `label` option.
|
|
102
67
|
*
|
|
103
68
|
* The function receives a {@link RequestOptions} object and can return either:
|
|
104
69
|
* - The modified {@link RequestOptions} object
|
|
@@ -159,8 +124,7 @@ export interface EnqueueLinksByClickingElementsOptions {
|
|
|
159
124
|
* in `href` elements, but rather navigations are triggered in click handlers.
|
|
160
125
|
* If you're looking to find URLs in `href` attributes of the page, see {@link enqueueLinks}.
|
|
161
126
|
*
|
|
162
|
-
* Optionally, the function allows you to filter the target links' URLs using an array of
|
|
163
|
-
* and override settings of the enqueued {@link Request} objects.
|
|
127
|
+
* Optionally, the function allows you to filter the target links' URLs using an array of glob or regexp patterns.
|
|
164
128
|
*
|
|
165
129
|
* **IMPORTANT**: To be able to do this, this function uses various mutations on the page,
|
|
166
130
|
* such as changing the Z-index of elements being clicked and their visibility. Therefore,
|
|
@@ -182,9 +146,9 @@ export interface EnqueueLinksByClickingElementsOptions {
|
|
|
182
146
|
* page,
|
|
183
147
|
* requestManager,
|
|
184
148
|
* selector: 'a.product-detail',
|
|
185
|
-
*
|
|
186
|
-
* 'https://www.example.com/handbags
|
|
187
|
-
* 'https://www.example.com/purses
|
|
149
|
+
* include: [
|
|
150
|
+
* 'https://www.example.com/handbags/*',
|
|
151
|
+
* 'https://www.example.com/purses/*',
|
|
188
152
|
* ],
|
|
189
153
|
* });
|
|
190
154
|
* ```
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { URL } from 'node:url';
|
|
2
|
-
import { applyRequestTransform,
|
|
3
|
-
import
|
|
2
|
+
import { applyRequestTransform, constructUrlPatternObjects, createRequestOptions, filterRequestOptionsByPatterns, parseArgument, urlPatternSchema, Request, schemas, serviceLocator, } from '@crawlee/browser';
|
|
3
|
+
import { z } from 'zod';
|
|
4
4
|
import { addInterceptRequestHandler, removeInterceptRequestHandler } from '../utils/puppeteer_request_interception.js';
|
|
5
5
|
const STARTING_Z_INDEX = 2147400000;
|
|
6
6
|
const getLog = () => serviceLocator.getChildLog('Puppeteer Click Elements');
|
|
7
|
+
const enqueueLinksByClickingElementsOptionsSchema = z.strictObject({
|
|
8
|
+
page: schemas.objectWithKeys(['goto', 'evaluate']),
|
|
9
|
+
requestManager: schemas.objectWithKeys(['fetchNextRequest', 'addRequestsBatched']),
|
|
10
|
+
selector: z.string(),
|
|
11
|
+
userData: schemas.anyObject.optional(),
|
|
12
|
+
clickOptions: schemas.anyObject.optional(),
|
|
13
|
+
include: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
14
|
+
exclude: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
15
|
+
transformRequestFunction: schemas.anyFunction.optional(),
|
|
16
|
+
waitForPageIdleSecs: schemas.anyNumber.default(1),
|
|
17
|
+
maxWaitForPageIdleSecs: schemas.anyNumber.default(5),
|
|
18
|
+
label: z.string().optional(),
|
|
19
|
+
forefront: z.boolean().optional(),
|
|
20
|
+
skipNavigation: z.boolean().optional(),
|
|
21
|
+
onSkippedRequest: schemas.anyFunction.optional(),
|
|
22
|
+
});
|
|
7
23
|
/**
|
|
8
24
|
* The function finds elements matching a specific CSS selector in a Puppeteer page,
|
|
9
25
|
* clicks all those elements using a mouse move and a left mouse button click and intercepts
|
|
@@ -13,8 +29,7 @@ const getLog = () => serviceLocator.getChildLog('Puppeteer Click Elements');
|
|
|
13
29
|
* in `href` elements, but rather navigations are triggered in click handlers.
|
|
14
30
|
* If you're looking to find URLs in `href` attributes of the page, see {@link enqueueLinks}.
|
|
15
31
|
*
|
|
16
|
-
* Optionally, the function allows you to filter the target links' URLs using an array of
|
|
17
|
-
* and override settings of the enqueued {@link Request} objects.
|
|
32
|
+
* Optionally, the function allows you to filter the target links' URLs using an array of glob or regexp patterns.
|
|
18
33
|
*
|
|
19
34
|
* **IMPORTANT**: To be able to do this, this function uses various mutations on the page,
|
|
20
35
|
* such as changing the Z-index of elements being clicked and their visibility. Therefore,
|
|
@@ -36,9 +51,9 @@ const getLog = () => serviceLocator.getChildLog('Puppeteer Click Elements');
|
|
|
36
51
|
* page,
|
|
37
52
|
* requestManager,
|
|
38
53
|
* selector: 'a.product-detail',
|
|
39
|
-
*
|
|
40
|
-
* 'https://www.example.com/handbags
|
|
41
|
-
* 'https://www.example.com/purses
|
|
54
|
+
* include: [
|
|
55
|
+
* 'https://www.example.com/handbags/*',
|
|
56
|
+
* 'https://www.example.com/purses/*',
|
|
42
57
|
* ],
|
|
43
58
|
* });
|
|
44
59
|
* ```
|
|
@@ -46,51 +61,12 @@ const getLog = () => serviceLocator.getChildLog('Puppeteer Click Elements');
|
|
|
46
61
|
* @returns Promise that resolves to {@link BatchAddRequestsResult} object.
|
|
47
62
|
*/
|
|
48
63
|
export async function enqueueLinksByClickingElements(options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
requestManager: ow.object.hasKeys('fetchNextRequest', 'addRequestsBatched'),
|
|
52
|
-
selector: ow.string,
|
|
53
|
-
userData: ow.optional.object,
|
|
54
|
-
clickOptions: ow.optional.object,
|
|
55
|
-
pseudoUrls: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('purl'))),
|
|
56
|
-
globs: ow.optional.array.ofType(ow.any(ow.string, ow.object.hasKeys('glob'))),
|
|
57
|
-
regexps: ow.optional.array.ofType(ow.any(ow.regExp, ow.object.hasKeys('regexp'))),
|
|
58
|
-
exclude: ow.optional.array.ofType(ow.any(ow.string, ow.regExp, ow.object.hasKeys('glob'), ow.object.hasKeys('regexp'))),
|
|
59
|
-
transformRequestFunction: ow.optional.function,
|
|
60
|
-
waitForPageIdleSecs: ow.optional.number,
|
|
61
|
-
maxWaitForPageIdleSecs: ow.optional.number,
|
|
62
|
-
label: ow.optional.string,
|
|
63
|
-
forefront: ow.optional.boolean,
|
|
64
|
-
skipNavigation: ow.optional.boolean,
|
|
65
|
-
onSkippedRequest: ow.optional.function,
|
|
66
|
-
}));
|
|
67
|
-
const { page, requestManager, selector, clickOptions,
|
|
68
|
-
// oxlint-disable-next-line typescript/no-deprecated -- still accepted for backwards compat
|
|
69
|
-
pseudoUrls, globs, regexps, transformRequestFunction, waitForPageIdleSecs = 1, maxWaitForPageIdleSecs = 5, forefront, exclude, onSkippedRequest, } = options;
|
|
64
|
+
const parsedOptions = parseArgument(options, enqueueLinksByClickingElementsOptionsSchema, 'EnqueueLinksByClickingElementsOptions');
|
|
65
|
+
const { page, requestManager, selector, clickOptions, include, exclude, transformRequestFunction, waitForPageIdleSecs, maxWaitForPageIdleSecs, forefront, onSkippedRequest, } = parsedOptions;
|
|
70
66
|
const waitForPageIdleMillis = waitForPageIdleSecs * 1000;
|
|
71
67
|
const maxWaitForPageIdleMillis = maxWaitForPageIdleSecs * 1000;
|
|
72
|
-
const urlExcludePatternObjects = [];
|
|
73
|
-
const urlPatternObjects = [];
|
|
74
|
-
if (exclude?.length) {
|
|
75
|
-
for (const excl of exclude) {
|
|
76
|
-
if (typeof excl === 'string' || 'glob' in excl) {
|
|
77
|
-
urlExcludePatternObjects.push(...constructGlobObjectsFromGlobs([excl]));
|
|
78
|
-
}
|
|
79
|
-
else if (excl instanceof RegExp || 'regexp' in excl) {
|
|
80
|
-
urlExcludePatternObjects.push(...constructRegExpObjectsFromRegExps([excl]));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (pseudoUrls?.length) {
|
|
85
|
-
getLog().deprecated('`pseudoUrls` option is deprecated, use `globs` or `regexps` instead');
|
|
86
|
-
urlPatternObjects.push(...constructRegExpObjectsFromPseudoUrls(pseudoUrls));
|
|
87
|
-
}
|
|
88
|
-
if (globs?.length) {
|
|
89
|
-
urlPatternObjects.push(...constructGlobObjectsFromGlobs(globs));
|
|
90
|
-
}
|
|
91
|
-
if (regexps?.length) {
|
|
92
|
-
urlPatternObjects.push(...constructRegExpObjectsFromRegExps(regexps));
|
|
93
|
-
}
|
|
68
|
+
const urlExcludePatternObjects = exclude?.length ? constructUrlPatternObjects(exclude) : [];
|
|
69
|
+
const urlPatternObjects = include?.length ? constructUrlPatternObjects(include) : [];
|
|
94
70
|
const interceptedRequests = await clickElementsAndInterceptNavigationRequests({
|
|
95
71
|
page,
|
|
96
72
|
selector,
|
|
@@ -98,7 +74,7 @@ export async function enqueueLinksByClickingElements(options) {
|
|
|
98
74
|
maxWaitForPageIdleMillis,
|
|
99
75
|
clickOptions,
|
|
100
76
|
});
|
|
101
|
-
const requestOptions = createRequestOptions(interceptedRequests,
|
|
77
|
+
const requestOptions = createRequestOptions(interceptedRequests, parsedOptions);
|
|
102
78
|
const skippedByFilters = [];
|
|
103
79
|
let filteredOptions = filterRequestOptionsByPatterns(requestOptions, urlPatternObjects.length > 0 ? urlPatternObjects : undefined, urlExcludePatternObjects, undefined, (url) => skippedByFilters.push(url));
|
|
104
80
|
if (onSkippedRequest && skippedByFilters.length > 0) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Configuration } from '@crawlee/browser';
|
|
2
|
+
import type { BrowserPool, BrowserPoolHooks, BrowserPoolOptions, PuppeteerPlugin, RemoteBrowserPool, RemoteBrowserPoolOptions } from '@crawlee/browser-pool';
|
|
3
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
4
|
+
import type { Page } from 'puppeteer';
|
|
5
|
+
import type { PuppeteerLaunchContext } from './puppeteer-launcher.js';
|
|
6
|
+
/** A {@link BrowserPool} of Puppeteer browsers, as built by {@link puppeteerBrowserPool}. */
|
|
7
|
+
export type PuppeteerBrowserPool = BrowserPool<{
|
|
8
|
+
browserPlugins: [PuppeteerPlugin];
|
|
9
|
+
}, [PuppeteerPlugin]>;
|
|
10
|
+
export interface PuppeteerBrowserPoolOptions extends Omit<BrowserPoolOptions, 'browserPlugins'>, BrowserPoolHooks<ReturnType<PuppeteerPlugin['createController']>, ReturnType<PuppeteerPlugin['createLaunchContext']>, Page> {
|
|
11
|
+
/** How to launch the browser: proxy, user data dir, whether to use full Chrome, ... */
|
|
12
|
+
launchContext?: PuppeteerLaunchContext;
|
|
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 | 'new' | 'old';
|
|
18
|
+
/** Configuration to read the browser defaults from. Defaults to the global configuration. */
|
|
19
|
+
configuration?: Configuration;
|
|
20
|
+
}
|
|
21
|
+
export interface RemotePuppeteerBrowserPoolOptions extends Pick<PuppeteerBrowserPoolOptions, 'launchContext' | 'headless' | 'configuration'>, Omit<RemoteBrowserPoolOptions, 'browserPlugins'> {
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds a {@link BrowserPool} of Puppeteer browsers to pass to a {@link PuppeteerCrawler} 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 PuppeteerPlugin} by hand.
|
|
30
|
+
*
|
|
31
|
+
* **Example usage:**
|
|
32
|
+
*
|
|
33
|
+
* ```javascript
|
|
34
|
+
* const crawler = new PuppeteerCrawler({
|
|
35
|
+
* browserPool: puppeteerBrowserPool({ maxOpenPagesPerBrowser: 1 }),
|
|
36
|
+
* requestHandler: async ({ page }) => { ... },
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* The returned pool is *not* torn down by the crawler, which is what makes it shareable between crawlers.
|
|
41
|
+
*
|
|
42
|
+
* @category Browser management
|
|
43
|
+
*/
|
|
44
|
+
export declare function puppeteerBrowserPool(options?: PuppeteerBrowserPoolOptions): PuppeteerBrowserPool;
|
|
45
|
+
/**
|
|
46
|
+
* The {@link RemoteBrowserPool} counterpart of {@link puppeteerBrowserPool}: connects to a remote browser
|
|
47
|
+
* service (Browserbase, Browserless, Steel, ...) with a Puppeteer plugin derived from `launchContext`.
|
|
48
|
+
*
|
|
49
|
+
* A {@link PuppeteerCrawler} accepts the same connection details directly via
|
|
50
|
+
* {@link BrowserCrawlerOptions.remoteBrowser|`remoteBrowser`}; reach for this factory when you also need to
|
|
51
|
+
* tune the wrapping pool, or to share one remote pool between crawlers.
|
|
52
|
+
*
|
|
53
|
+
* @category Browser management
|
|
54
|
+
*/
|
|
55
|
+
export declare function remotePuppeteerBrowserPool(options: RemotePuppeteerBrowserPoolOptions): RemoteBrowserPool<Page>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PuppeteerLauncher } from './puppeteer-launcher.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a {@link BrowserPool} of Puppeteer browsers to pass to a {@link PuppeteerCrawler} 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 PuppeteerPlugin} by hand.
|
|
9
|
+
*
|
|
10
|
+
* **Example usage:**
|
|
11
|
+
*
|
|
12
|
+
* ```javascript
|
|
13
|
+
* const crawler = new PuppeteerCrawler({
|
|
14
|
+
* browserPool: puppeteerBrowserPool({ maxOpenPagesPerBrowser: 1 }),
|
|
15
|
+
* requestHandler: async ({ page }) => { ... },
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The returned pool is *not* torn down by the crawler, which is what makes it shareable between crawlers.
|
|
20
|
+
*
|
|
21
|
+
* @category Browser management
|
|
22
|
+
*/
|
|
23
|
+
export function puppeteerBrowserPool(options = {}) {
|
|
24
|
+
const { launchContext, headless, configuration, ...poolOptions } = options;
|
|
25
|
+
return puppeteerLauncher(launchContext, headless, configuration).createBrowserPool(poolOptions);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The {@link RemoteBrowserPool} counterpart of {@link puppeteerBrowserPool}: connects to a remote browser
|
|
29
|
+
* service (Browserbase, Browserless, Steel, ...) with a Puppeteer plugin derived from `launchContext`.
|
|
30
|
+
*
|
|
31
|
+
* A {@link PuppeteerCrawler} accepts the same connection details directly via
|
|
32
|
+
* {@link BrowserCrawlerOptions.remoteBrowser|`remoteBrowser`}; reach for this factory when you also need to
|
|
33
|
+
* tune the wrapping pool, or to share one remote pool between crawlers.
|
|
34
|
+
*
|
|
35
|
+
* @category Browser management
|
|
36
|
+
*/
|
|
37
|
+
export function remotePuppeteerBrowserPool(options) {
|
|
38
|
+
const { launchContext, headless, configuration, ...remoteOptions } = options;
|
|
39
|
+
return puppeteerLauncher(launchContext, headless, configuration).createRemoteBrowserPool(remoteOptions);
|
|
40
|
+
}
|
|
41
|
+
function puppeteerLauncher(launchContext = {}, headless, configuration) {
|
|
42
|
+
return new PuppeteerLauncher(headless == null
|
|
43
|
+
? launchContext
|
|
44
|
+
: {
|
|
45
|
+
...launchContext,
|
|
46
|
+
launchOptions: { ...launchContext.launchOptions, headless: headless },
|
|
47
|
+
}, configuration);
|
|
48
|
+
}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, GetUserDataFromRequest, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
|
|
2
2
|
import { BrowserCrawler } from '@crawlee/browser';
|
|
3
|
-
import type { PuppeteerPlugin } 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 { HTTPResponse, LaunchOptions, Page } from 'puppeteer';
|
|
6
|
+
import { z } from 'zod';
|
|
7
7
|
import type { EnqueueLinksByClickingElementsOptions } from './enqueue-links/click-elements.js';
|
|
8
8
|
import type { PuppeteerLaunchContext } from './puppeteer-launcher.js';
|
|
9
9
|
import type { InterceptHandler } from './utils/puppeteer_request_interception.js';
|
|
10
10
|
import type { BlockRequestsOptions, DirectNavigationOptions, InfiniteScrollOptions, InjectFileOptions, PuppeteerContextUtils, SaveSnapshotOptions } from './utils/puppeteer_utils.js';
|
|
11
11
|
export type PuppeteerGoToOptions = NonNullable<Parameters<Page['goto']>[1]>;
|
|
12
|
-
export interface PuppeteerCrawlingContext<UserData extends Dictionary =
|
|
12
|
+
export interface PuppeteerCrawlingContext<UserData extends Dictionary = any> extends BrowserCrawlingContext<Page, HTTPResponse, UserData, PuppeteerGoToOptions>, PuppeteerContextUtils {
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
export interface
|
|
16
|
-
}
|
|
17
|
-
export interface PuppeteerCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends PuppeteerCrawlingContext = PuppeteerCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PuppeteerCrawlingContext['request']>>> extends BrowserCrawlerOptions<Page, HTTPResponse, PuppeteerCrawlingContext, ContextExtension, ExtendedContext, {
|
|
18
|
-
browserPlugins: [PuppeteerPlugin];
|
|
19
|
-
}, Routes> {
|
|
14
|
+
export type PuppeteerHook<UserData extends Dictionary = any> = BrowserHook<PuppeteerCrawlingContext<UserData>>;
|
|
15
|
+
export interface PuppeteerCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends PuppeteerCrawlingContext = PuppeteerCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PuppeteerCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends BrowserCrawlerOptions<Page, HTTPResponse, PuppeteerCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
20
16
|
/**
|
|
21
17
|
* Options used by {@link launchPuppeteer} to start new Puppeteer instances.
|
|
22
18
|
*/
|
|
23
19
|
launchContext?: PuppeteerLaunchContext;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to run browser in headless mode. Defaults to `true`.
|
|
22
|
+
* Can be also set via {@link Configuration}.
|
|
23
|
+
*/
|
|
24
|
+
headless?: boolean | 'new' | 'old';
|
|
24
25
|
/**
|
|
25
26
|
* Async functions that are sequentially evaluated before the navigation. Good for setting additional cookies
|
|
26
27
|
* or browser properties before navigation. The function receives the `crawlingContext`; the options object
|
|
@@ -37,7 +38,7 @@ export interface PuppeteerCrawlerOptions<ContextExtension = Dictionary<never>, E
|
|
|
37
38
|
* ]
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
|
-
preNavigationHooks?: BrowserHook<PuppeteerCrawlingContext
|
|
41
|
+
preNavigationHooks?: BrowserHook<PuppeteerCrawlingContext<GetUserDataFromRequest<ExtendedContext['request']>>, ContextExtension>[];
|
|
41
42
|
/**
|
|
42
43
|
* Async functions that are sequentially evaluated after the navigation. Good for checking if the navigation was successful.
|
|
43
44
|
* The function accepts `crawlingContext` as the only parameter. A hook may optionally return a partial object
|
|
@@ -54,7 +55,7 @@ export interface PuppeteerCrawlerOptions<ContextExtension = Dictionary<never>, E
|
|
|
54
55
|
* ]
|
|
55
56
|
* ```
|
|
56
57
|
*/
|
|
57
|
-
postNavigationHooks?: BrowserHook<PuppeteerCrawlingContext
|
|
58
|
+
postNavigationHooks?: BrowserHook<PuppeteerCrawlingContext<GetUserDataFromRequest<ExtendedContext['request']>>, ContextExtension>[];
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
61
|
* Provides a simple framework for parallel crawling of web pages
|
|
@@ -121,103 +122,127 @@ export interface PuppeteerCrawlerOptions<ContextExtension = Dictionary<never>, E
|
|
|
121
122
|
* ```
|
|
122
123
|
* @category Crawlers
|
|
123
124
|
*/
|
|
124
|
-
export declare class PuppeteerCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends PuppeteerCrawlingContext = PuppeteerCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PuppeteerCrawlingContext['request']
|
|
125
|
-
browserPlugins: [PuppeteerPlugin];
|
|
126
|
-
}, LaunchOptions, PuppeteerCrawlingContext, ContextExtension, ExtendedContext, Routes> {
|
|
125
|
+
export declare class PuppeteerCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends PuppeteerCrawlingContext = PuppeteerCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<PuppeteerCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends BrowserCrawler<Page, HTTPResponse, LaunchOptions, PuppeteerCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
127
126
|
protected static optionsShape: {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
171
|
-
|
|
172
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
185
|
-
ignoreHttpErrorStatusCodes: import("ow").ArrayPredicate<number>;
|
|
186
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
187
|
-
blockedStatusCodes: import("ow").ArrayPredicate<number>;
|
|
188
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
189
|
-
retryOnBlocked: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
190
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
191
|
-
respectRobotsTxtFile: import("ow").AnyPredicate<boolean | object>;
|
|
192
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
193
|
-
onSkippedRequest: import("ow").Predicate<Function> & import("ow").BasePredicate<Function | undefined>;
|
|
194
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
195
|
-
httpClient: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
196
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
197
|
-
configuration: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
198
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
199
|
-
storageBackend: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
200
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
201
|
-
eventManager: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
202
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
203
|
-
logger: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
204
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
205
|
-
minConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
|
|
206
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
207
|
-
maxConcurrency: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
|
|
208
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
209
|
-
maxRequestsPerMinute: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
|
|
210
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
211
|
-
keepAlive: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
212
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
213
|
-
statisticsOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
|
|
214
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
215
|
-
id: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
|
|
127
|
+
headless: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
128
|
+
navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
129
|
+
preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
130
|
+
postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
131
|
+
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
132
|
+
browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
133
|
+
browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
134
|
+
remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
135
|
+
saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
|
|
136
|
+
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
137
|
+
ignoreIframes: z.ZodDefault<z.ZodBoolean>;
|
|
138
|
+
ignoreShadowRoots: z.ZodDefault<z.ZodBoolean>;
|
|
139
|
+
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
140
|
+
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
141
|
+
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
142
|
+
requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
143
|
+
requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
144
|
+
requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
145
|
+
requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
146
|
+
errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
147
|
+
failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
148
|
+
maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
149
|
+
sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
150
|
+
maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
151
|
+
maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
152
|
+
taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
153
|
+
concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
154
|
+
sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
155
|
+
statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
156
|
+
statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
157
|
+
additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
158
|
+
ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
159
|
+
blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
160
|
+
retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
|
|
161
|
+
respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
|
|
162
|
+
transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
163
|
+
requestQueue: z.ZodOptional<z.ZodEnum<{
|
|
164
|
+
deferred: "deferred";
|
|
165
|
+
writeThrough: "writeThrough";
|
|
166
|
+
}>>;
|
|
167
|
+
}, z.core.$strict>]>>;
|
|
168
|
+
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
169
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
170
|
+
httpClient: z.ZodOptional<z.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
|
|
171
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
172
|
+
configuration: z.ZodOptional<z.ZodCustom<import("@crawlee/browser").Configuration, import("@crawlee/browser").Configuration>>;
|
|
173
|
+
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
174
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
175
|
+
eventManager: z.ZodOptional<z.ZodCustom<import("@crawlee/browser").EventManager, import("@crawlee/browser").EventManager>>;
|
|
176
|
+
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
177
|
+
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
178
|
+
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
179
|
+
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
180
|
+
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
181
|
+
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
182
|
+
id: z.ZodOptional<z.ZodString>;
|
|
216
183
|
};
|
|
184
|
+
protected static optionsSchema: z.ZodObject<{
|
|
185
|
+
headless: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
186
|
+
navigationTimeoutSecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
187
|
+
preNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
188
|
+
postNavigationHooks: z.ZodDefault<z.ZodCustom<unknown[], unknown[]>>;
|
|
189
|
+
launchContext: z.ZodDefault<z.ZodCustom<Dictionary, Dictionary>>;
|
|
190
|
+
browserPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
191
|
+
browserPoolBuilder: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
192
|
+
remoteBrowser: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
193
|
+
saveResponseCookies: z.ZodDefault<z.ZodBoolean>;
|
|
194
|
+
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
195
|
+
ignoreIframes: z.ZodDefault<z.ZodBoolean>;
|
|
196
|
+
ignoreShadowRoots: z.ZodDefault<z.ZodBoolean>;
|
|
197
|
+
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
198
|
+
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
199
|
+
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
200
|
+
requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
201
|
+
requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
202
|
+
requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
203
|
+
requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
204
|
+
errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
205
|
+
failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
206
|
+
maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
207
|
+
sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
208
|
+
maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
209
|
+
maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
210
|
+
taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
211
|
+
concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
212
|
+
sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
213
|
+
statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
214
|
+
statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
215
|
+
additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
216
|
+
ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
217
|
+
blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
218
|
+
retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
|
|
219
|
+
respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
|
|
220
|
+
transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
221
|
+
requestQueue: z.ZodOptional<z.ZodEnum<{
|
|
222
|
+
deferred: "deferred";
|
|
223
|
+
writeThrough: "writeThrough";
|
|
224
|
+
}>>;
|
|
225
|
+
}, z.core.$strict>]>>;
|
|
226
|
+
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
227
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
228
|
+
httpClient: z.ZodOptional<z.ZodCustom<import("@crawlee/http-client").BaseHttpClient, import("@crawlee/http-client").BaseHttpClient>>;
|
|
229
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
230
|
+
configuration: z.ZodOptional<z.ZodCustom<import("@crawlee/browser").Configuration, import("@crawlee/browser").Configuration>>;
|
|
231
|
+
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
232
|
+
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
233
|
+
eventManager: z.ZodOptional<z.ZodCustom<import("@crawlee/browser").EventManager, import("@crawlee/browser").EventManager>>;
|
|
234
|
+
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
235
|
+
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
236
|
+
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
237
|
+
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
238
|
+
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
239
|
+
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
240
|
+
id: z.ZodOptional<z.ZodString>;
|
|
241
|
+
}, z.core.$strict>;
|
|
217
242
|
/**
|
|
218
243
|
* All `PuppeteerCrawler` parameters are passed via an options object.
|
|
219
244
|
*/
|
|
220
|
-
constructor(options?: PuppeteerCrawlerOptions<ContextExtension, ExtendedContext, Routes>);
|
|
245
|
+
constructor(options?: PuppeteerCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
221
246
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
222
247
|
protected buildContextPipeline(): import("@crawlee/browser").ContextPipeline<import("@crawlee/browser").CrawlingContext<Dictionary>, BrowserCrawlingContext<Page, HTTPResponse, Dictionary, Dictionary> & {
|
|
223
248
|
injectFile: (filePath: string, options?: InjectFileOptions) => Promise<unknown>;
|
|
@@ -237,7 +262,7 @@ export declare class PuppeteerCrawler<ContextExtension = Dictionary<never>, Exte
|
|
|
237
262
|
closeCookieModals: () => Promise<void>;
|
|
238
263
|
}>;
|
|
239
264
|
private enhanceContext;
|
|
240
|
-
protected
|
|
265
|
+
protected navigationHandler(crawlingContext: PuppeteerCrawlingContext, gotoOptions: DirectNavigationOptions): Promise<HTTPResponse | null>;
|
|
241
266
|
}
|
|
242
267
|
/**
|
|
243
268
|
* Creates new {@link Router} instance that works based on request labels.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BrowserCrawler, RequestState, Router } from '@crawlee/browser';
|
|
2
|
-
import { serviceLocator } from '@crawlee/core';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import { assertBrowserPoolNotConfigured, BrowserCrawler, RequestState, Router } from '@crawlee/browser';
|
|
2
|
+
import { parseArgument, serviceLocator } from '@crawlee/core';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { puppeteerBrowserPool, remotePuppeteerBrowserPool } from './puppeteer-browser-pool.js';
|
|
5
5
|
import { gotoExtended, puppeteerUtils } from './utils/puppeteer_utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* Provides a simple framework for parallel crawling of web pages
|
|
@@ -71,37 +71,36 @@ import { gotoExtended, puppeteerUtils } from './utils/puppeteer_utils.js';
|
|
|
71
71
|
export class PuppeteerCrawler extends BrowserCrawler {
|
|
72
72
|
static optionsShape = {
|
|
73
73
|
...BrowserCrawler.optionsShape,
|
|
74
|
-
|
|
74
|
+
// Deliberately looser than the declared type: Puppeteer's own accepted string values have moved over
|
|
75
|
+
// time (`'new'`/`'old'`, now `'shell'`), and the value is forwarded to it verbatim.
|
|
76
|
+
headless: z.union([z.boolean(), z.string()]).optional(),
|
|
75
77
|
};
|
|
78
|
+
static optionsSchema = z.strictObject(PuppeteerCrawler.optionsShape);
|
|
76
79
|
/**
|
|
77
80
|
* All `PuppeteerCrawler` parameters are passed via an options object.
|
|
78
81
|
*/
|
|
79
82
|
constructor(options = {}) {
|
|
80
|
-
|
|
81
|
-
const { launchContext
|
|
82
|
-
const browserPoolOptions = {
|
|
83
|
-
...options.browserPoolOptions,
|
|
84
|
-
};
|
|
83
|
+
const parsedOptions = parseArgument(options, PuppeteerCrawler.optionsSchema, 'PuppeteerCrawlerOptions');
|
|
84
|
+
const { launchContext, headless, configuration, proxyConfiguration, contextPipelineBuilder, ...browserCrawlerOptions } = parsedOptions;
|
|
85
85
|
if (launchContext.proxyUrl) {
|
|
86
86
|
throw new Error('PuppeteerCrawlerOptions.launchContext.proxyUrl is not allowed in PuppeteerCrawler.' +
|
|
87
87
|
'Use PuppeteerCrawlerOptions.proxyConfiguration');
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
launchContext.launchOptions ??= {};
|
|
96
|
-
launchContext.launchOptions.headless = headless;
|
|
89
|
+
if (options.browserPool) {
|
|
90
|
+
// The raw options, not the parsed ones: `launchContext` has a default, so by now it is always set.
|
|
91
|
+
assertBrowserPoolNotConfigured(new.target.name, {
|
|
92
|
+
launchContext: options.launchContext,
|
|
93
|
+
headless: options.headless,
|
|
94
|
+
});
|
|
97
95
|
}
|
|
98
|
-
const puppeteerLauncher = new PuppeteerLauncher(launchContext, options.configuration);
|
|
99
|
-
browserPoolOptions.browserPlugins = [puppeteerLauncher.createBrowserPlugin()];
|
|
100
96
|
super({
|
|
101
97
|
...browserCrawlerOptions,
|
|
102
98
|
launchContext,
|
|
99
|
+
configuration,
|
|
103
100
|
proxyConfiguration,
|
|
104
|
-
|
|
101
|
+
browserPoolBuilder: (remoteBrowser) => remoteBrowser
|
|
102
|
+
? remotePuppeteerBrowserPool({ ...remoteBrowser, launchContext, headless, configuration })
|
|
103
|
+
: puppeteerBrowserPool({ launchContext, headless, configuration }),
|
|
105
104
|
contextPipelineBuilder: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
|
|
106
105
|
});
|
|
107
106
|
}
|
|
@@ -146,7 +145,7 @@ export class PuppeteerCrawler extends BrowserCrawler {
|
|
|
146
145
|
closeCookieModals: async () => puppeteerUtils.closeCookieModals(context.page),
|
|
147
146
|
};
|
|
148
147
|
}
|
|
149
|
-
async
|
|
148
|
+
async navigationHandler(crawlingContext, gotoOptions) {
|
|
150
149
|
return gotoExtended(crawlingContext.page, crawlingContext.request, gotoOptions);
|
|
151
150
|
}
|
|
152
151
|
}
|
|
@@ -3,6 +3,7 @@ import { BrowserLauncher, Configuration } from '@crawlee/browser';
|
|
|
3
3
|
import { PuppeteerPlugin } from '@crawlee/browser-pool';
|
|
4
4
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
5
5
|
import type { Browser } from 'puppeteer';
|
|
6
|
+
import { z } from 'zod';
|
|
6
7
|
/**
|
|
7
8
|
* Apify extends the launch options of Puppeteer.
|
|
8
9
|
* You can use any of the Puppeteer compatible
|
|
@@ -69,24 +70,30 @@ export declare class PuppeteerLauncher extends BrowserLauncher<PuppeteerPlugin,
|
|
|
69
70
|
readonly configuration: Configuration;
|
|
70
71
|
protected static optionsShape: {
|
|
71
72
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
72
|
-
launcher: import("
|
|
73
|
+
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
74
|
+
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
75
|
+
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
76
|
+
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
browserPerProxy: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
|
|
79
|
+
userDataDir: z.ZodOptional<z.ZodString>;
|
|
73
80
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
useIncognitoPages: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
79
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
80
|
-
browserPerProxy: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
81
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
82
|
-
ignoreProxyCertificate: import("ow").BooleanPredicate & import("ow").BasePredicate<boolean | undefined>;
|
|
83
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
84
|
-
userDataDir: import("ow").StringPredicate & import("ow").BasePredicate<string | undefined>;
|
|
81
|
+
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
82
|
+
userAgent: z.ZodOptional<z.ZodString>;
|
|
83
|
+
};
|
|
84
|
+
protected static optionsSchema: z.ZodObject<{
|
|
85
85
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
86
|
-
|
|
86
|
+
launcher: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
87
|
+
proxyUrl: z.ZodOptional<z.ZodURL>;
|
|
88
|
+
useChrome: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
useIncognitoPages: z.ZodOptional<z.ZodBoolean>;
|
|
90
|
+
browserPerProxy: z.ZodOptional<z.ZodBoolean>;
|
|
91
|
+
ignoreProxyCertificate: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
+
userDataDir: z.ZodOptional<z.ZodString>;
|
|
87
93
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
launchOptions: z.ZodOptional<z.ZodCustom<import("@crawlee/types").Dictionary, import("@crawlee/types").Dictionary>>;
|
|
95
|
+
userAgent: z.ZodOptional<z.ZodString>;
|
|
96
|
+
}, z.core.$strict>;
|
|
90
97
|
/**
|
|
91
98
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
92
99
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BrowserLauncher, Configuration } from '@crawlee/browser';
|
|
1
|
+
import { BrowserLauncher, Configuration, parseArgument, schemas } from '@crawlee/browser';
|
|
2
2
|
import { PuppeteerPlugin } from '@crawlee/browser-pool';
|
|
3
|
-
import
|
|
3
|
+
import { z } from 'zod';
|
|
4
4
|
/**
|
|
5
5
|
* `PuppeteerLauncher` is based on the `BrowserLauncher`. It launches `puppeteer` browser instance.
|
|
6
6
|
* @ignore
|
|
@@ -9,14 +9,14 @@ export class PuppeteerLauncher extends BrowserLauncher {
|
|
|
9
9
|
configuration;
|
|
10
10
|
static optionsShape = {
|
|
11
11
|
...BrowserLauncher.optionsShape,
|
|
12
|
-
launcher:
|
|
12
|
+
launcher: schemas.anyObject.optional(),
|
|
13
13
|
};
|
|
14
|
+
static optionsSchema = z.strictObject(PuppeteerLauncher.optionsShape);
|
|
14
15
|
/**
|
|
15
16
|
* All `PuppeteerLauncher` parameters are passed via an launchContext object.
|
|
16
17
|
*/
|
|
17
18
|
constructor(launchContext = {}, configuration = Configuration.getGlobalConfiguration()) {
|
|
18
|
-
|
|
19
|
-
const { launcher = BrowserLauncher.requireLauncherOrThrow('puppeteer', 'apify/actor-node-puppeteer-chrome'), ...browserLauncherOptions } = launchContext;
|
|
19
|
+
const { launcher = BrowserLauncher.requireLauncherOrThrow('puppeteer', 'apify/actor-node-puppeteer-chrome'), ...browserLauncherOptions } = parseArgument(launchContext, PuppeteerLauncher.optionsSchema, 'PuppeteerLaunchContext');
|
|
20
20
|
super({
|
|
21
21
|
...browserLauncherOptions,
|
|
22
22
|
launcher,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import { serviceLocator } from '@crawlee/browser';
|
|
3
|
-
|
|
2
|
+
import { parseArgument, schemas, serviceLocator } from '@crawlee/browser';
|
|
3
|
+
const pageSchema = schemas.objectWithKeys(['goto', 'evaluate']);
|
|
4
4
|
// We use weak maps here so that the content gets discarded after page gets closed.
|
|
5
5
|
const pageInterceptRequestHandlersMap = new WeakMap(); // Maps page to an array of request interception handlers.
|
|
6
6
|
const pageInterceptRequestMasterHandlerMap = new WeakMap(); // Maps page to master request interception handler.
|
|
@@ -137,8 +137,8 @@ async function handleRequest(request, interceptRequestHandlers) {
|
|
|
137
137
|
* @param handler Request interception handler.
|
|
138
138
|
*/
|
|
139
139
|
export async function addInterceptRequestHandler(page, handler) {
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
parseArgument(page, pageSchema);
|
|
141
|
+
parseArgument(handler, schemas.anyFunction);
|
|
142
142
|
if (!pageInterceptRequestHandlersMap.has(page)) {
|
|
143
143
|
pageInterceptRequestHandlersMap.set(page, []);
|
|
144
144
|
}
|
|
@@ -174,8 +174,8 @@ export async function addInterceptRequestHandler(page, handler) {
|
|
|
174
174
|
* @param handler Request interception handler.
|
|
175
175
|
*/
|
|
176
176
|
export async function removeInterceptRequestHandler(page, handler) {
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
parseArgument(page, pageSchema);
|
|
178
|
+
parseArgument(handler, schemas.anyFunction);
|
|
179
179
|
const handlersArray = pageInterceptRequestHandlersMap.get(page).filter((item) => item !== handler);
|
|
180
180
|
pageInterceptRequestHandlersMap.set(page, handlersArray);
|
|
181
181
|
if (handlersArray.length === 0) {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import type { Request } from '@crawlee/browser';
|
|
21
21
|
import { Configuration } from '@crawlee/browser';
|
|
22
22
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
23
|
-
import { type CheerioRoot } from '@crawlee/utils';
|
|
23
|
+
import { type CheerioRoot } from '@crawlee/utils/internal';
|
|
24
24
|
import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js';
|
|
25
25
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
26
26
|
import type { HTTPResponse, Page, ResponseForRequest } from 'puppeteer';
|
|
@@ -387,8 +387,7 @@ export interface PuppeteerContextUtils {
|
|
|
387
387
|
* in `href` elements, but rather navigations are triggered in click handlers.
|
|
388
388
|
* If you're looking to find URLs in `href` attributes of the page, see {@link enqueueLinks}.
|
|
389
389
|
*
|
|
390
|
-
* Optionally, the function allows you to filter the target links' URLs using an array of
|
|
391
|
-
* and override settings of the enqueued {@link Request} objects.
|
|
390
|
+
* Optionally, the function allows you to filter the target links' URLs using an array of glob or regexp patterns.
|
|
392
391
|
*
|
|
393
392
|
* **IMPORTANT**: To be able to do this, this function uses various mutations on the page,
|
|
394
393
|
* such as changing the Z-index of elements being clicked and their visibility. Therefore,
|
|
@@ -409,9 +408,9 @@ export interface PuppeteerContextUtils {
|
|
|
409
408
|
* async requestHandler({ enqueueLinksByClickingElements }) {
|
|
410
409
|
* await enqueueLinksByClickingElements({
|
|
411
410
|
* selector: 'a.product-detail',
|
|
412
|
-
*
|
|
413
|
-
* 'https://www.example.com/handbags/**'
|
|
414
|
-
* 'https://www.example.com/purses/**'
|
|
411
|
+
* include: [
|
|
412
|
+
* 'https://www.example.com/handbags/**',
|
|
413
|
+
* 'https://www.example.com/purses/**',
|
|
415
414
|
* ],
|
|
416
415
|
* });
|
|
417
416
|
* });
|
|
@@ -20,10 +20,9 @@
|
|
|
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, serviceLocator, validators } from '@crawlee/browser';
|
|
23
|
+
import { Configuration, KeyValueStore, parseArgument, schemas, serviceLocator, validators } from '@crawlee/browser';
|
|
24
24
|
import { expandShadowRoots, sleep } from '@crawlee/utils';
|
|
25
|
-
import
|
|
26
|
-
import ow from 'ow';
|
|
25
|
+
import { z } from 'zod';
|
|
27
26
|
import { LruCache } from '@apify/datastructures';
|
|
28
27
|
import { enqueueLinksByClickingElements } from '../enqueue-links/click-elements.js';
|
|
29
28
|
import { addInterceptRequestHandler, removeInterceptRequestHandler } from './puppeteer_request_interception.js';
|
|
@@ -31,6 +30,37 @@ const require = createRequire(import.meta.url);
|
|
|
31
30
|
const jqueryPath = require.resolve('jquery');
|
|
32
31
|
const MAX_INJECT_FILE_CACHE_SIZE = 10;
|
|
33
32
|
const DEFAULT_BLOCK_REQUEST_URL_PATTERNS = ['.css', '.jpg', '.jpeg', '.png', '.svg', '.gif', '.woff', '.pdf', '.zip'];
|
|
33
|
+
const filePathSchema = z.string();
|
|
34
|
+
const injectFileOptionsSchema = z.strictObject({
|
|
35
|
+
surviveNavigations: z.boolean().optional(),
|
|
36
|
+
});
|
|
37
|
+
const responseUrlRulesSchema = schemas.arrayOf(z.union([z.string(), z.instanceof(RegExp)]), 'strings or RegExps');
|
|
38
|
+
const gotoExtendedRequestSchema = z.looseObject({
|
|
39
|
+
url: z.url(),
|
|
40
|
+
method: z.string().optional(),
|
|
41
|
+
headers: schemas.anyObject.optional(),
|
|
42
|
+
payload: z.union([z.string(), z.instanceof(Uint8Array)]).optional(),
|
|
43
|
+
});
|
|
44
|
+
const blockRequestsOptionsSchema = z.strictObject({
|
|
45
|
+
urlPatterns: schemas.arrayOf(z.string(), 'strings').default(DEFAULT_BLOCK_REQUEST_URL_PATTERNS),
|
|
46
|
+
extraUrlPatterns: schemas.arrayOf(z.string(), 'strings').default(() => []),
|
|
47
|
+
});
|
|
48
|
+
const infiniteScrollOptionsSchema = z.strictObject({
|
|
49
|
+
timeoutSecs: schemas.anyNumber.default(0),
|
|
50
|
+
maxScrollHeight: schemas.anyNumber.default(0),
|
|
51
|
+
waitForSecs: schemas.anyNumber.default(4),
|
|
52
|
+
scrollDownAndUp: z.boolean().default(false),
|
|
53
|
+
buttonSelector: z.string().optional(),
|
|
54
|
+
stopScrollCallback: schemas.anyFunction.optional(),
|
|
55
|
+
});
|
|
56
|
+
const saveSnapshotOptionsSchema = z.strictObject({
|
|
57
|
+
key: z.string().min(1).default('SNAPSHOT'),
|
|
58
|
+
screenshotQuality: schemas.anyNumber.default(50),
|
|
59
|
+
saveScreenshot: z.boolean().default(true),
|
|
60
|
+
saveHtml: z.boolean().default(true),
|
|
61
|
+
keyValueStoreName: z.string().optional(),
|
|
62
|
+
configuration: schemas.anyObject.optional(),
|
|
63
|
+
});
|
|
34
64
|
const getLog = () => serviceLocator.getChildLog('Puppeteer Utils');
|
|
35
65
|
/**
|
|
36
66
|
* Cache contents of previously injected files to limit file system access.
|
|
@@ -48,18 +78,16 @@ const injectedFilesCache = new LruCache({ maxLength: MAX_INJECT_FILE_CACHE_SIZE
|
|
|
48
78
|
* @param [options]
|
|
49
79
|
*/
|
|
50
80
|
export async function injectFile(page, filePath, options = {}) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
surviveNavigations: ow.optional.boolean,
|
|
55
|
-
}));
|
|
81
|
+
parseArgument(page, validators.browserPage);
|
|
82
|
+
parseArgument(filePath, filePathSchema);
|
|
83
|
+
const { surviveNavigations } = parseArgument(options, injectFileOptionsSchema);
|
|
56
84
|
let contents = injectedFilesCache.get(filePath);
|
|
57
85
|
if (!contents) {
|
|
58
86
|
contents = await readFile(filePath, 'utf8');
|
|
59
87
|
injectedFilesCache.add(filePath, contents);
|
|
60
88
|
}
|
|
61
89
|
const evalP = page.evaluate(contents);
|
|
62
|
-
if (
|
|
90
|
+
if (surviveNavigations) {
|
|
63
91
|
page.on('framenavigated', async () => page
|
|
64
92
|
.evaluate(contents)
|
|
65
93
|
.catch((error) => getLog().warning('An error occurred during the script injection!', { error })));
|
|
@@ -93,7 +121,7 @@ export async function injectFile(page, filePath, options = {}) {
|
|
|
93
121
|
* @param [options.surviveNavigations] Opt-out option to disable the JQuery reinjection after navigation.
|
|
94
122
|
*/
|
|
95
123
|
export async function injectJQuery(page, options) {
|
|
96
|
-
|
|
124
|
+
parseArgument(page, validators.browserPage);
|
|
97
125
|
return injectFile(page, jqueryPath, { surviveNavigations: options?.surviveNavigations ?? true });
|
|
98
126
|
}
|
|
99
127
|
/**
|
|
@@ -109,7 +137,7 @@ export async function injectJQuery(page, options) {
|
|
|
109
137
|
* @param ignoreShadowRoots
|
|
110
138
|
*/
|
|
111
139
|
export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIframes = false) {
|
|
112
|
-
|
|
140
|
+
parseArgument(page, validators.browserPage);
|
|
113
141
|
if (page.frames().length > 1 && !ignoreIframes) {
|
|
114
142
|
const frames = await page.$$('iframe');
|
|
115
143
|
await Promise.all(frames.map(async (frame) => {
|
|
@@ -142,7 +170,8 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
142
170
|
? null
|
|
143
171
|
: (await page.evaluate(`(${expandShadowRoots.toString()})(document)`));
|
|
144
172
|
const pageContent = html || (await page.content());
|
|
145
|
-
|
|
173
|
+
const { load } = await import('cheerio');
|
|
174
|
+
return load(pageContent);
|
|
146
175
|
}
|
|
147
176
|
/**
|
|
148
177
|
* Forces the Puppeteer browser tab to block loading URLs that match a provided pattern.
|
|
@@ -187,12 +216,8 @@ export async function parseWithCheerio(page, ignoreShadowRoots = false, ignoreIf
|
|
|
187
216
|
* @param [options]
|
|
188
217
|
*/
|
|
189
218
|
export async function blockRequests(page, options = {}) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
urlPatterns: ow.optional.array.ofType(ow.string),
|
|
193
|
-
extraUrlPatterns: ow.optional.array.ofType(ow.string),
|
|
194
|
-
}));
|
|
195
|
-
const { urlPatterns = DEFAULT_BLOCK_REQUEST_URL_PATTERNS, extraUrlPatterns = [] } = options;
|
|
219
|
+
parseArgument(page, validators.browserPage);
|
|
220
|
+
const { urlPatterns, extraUrlPatterns } = parseArgument(options, blockRequestsOptionsSchema);
|
|
196
221
|
const patternsToBlock = [...urlPatterns, ...extraUrlPatterns];
|
|
197
222
|
// We use CDP commands instead of request interception as the latter disables caching, which is not ideal
|
|
198
223
|
await sendCDPCommand(page, 'Network.setBlockedURLs', { urls: patternsToBlock });
|
|
@@ -249,9 +274,9 @@ export const blockResources = async (page, resourceTypes = ['stylesheet', 'font'
|
|
|
249
274
|
* @deprecated
|
|
250
275
|
*/
|
|
251
276
|
export async function cacheResponses(page, cache, responseUrlRules) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
277
|
+
parseArgument(page, validators.browserPage);
|
|
278
|
+
parseArgument(cache, schemas.anyObject);
|
|
279
|
+
parseArgument(responseUrlRules, responseUrlRulesSchema);
|
|
255
280
|
serviceLocator
|
|
256
281
|
.getLogger()
|
|
257
282
|
.deprecated('utils.puppeteer.cacheResponses() has a high impact on performance ' +
|
|
@@ -344,14 +369,9 @@ export function compileScript(scriptString, context = Object.create(null)) {
|
|
|
344
369
|
* @param [gotoOptions] Custom options for `page.goto()`.
|
|
345
370
|
*/
|
|
346
371
|
export async function gotoExtended(page, request, gotoOptions = {}) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
method: ow.optional.string,
|
|
351
|
-
headers: ow.optional.object,
|
|
352
|
-
payload: ow.optional.any(ow.string, ow.uint8Array),
|
|
353
|
-
}));
|
|
354
|
-
ow(gotoOptions, ow.object);
|
|
372
|
+
parseArgument(page, validators.browserPage);
|
|
373
|
+
parseArgument(request, gotoExtendedRequestSchema);
|
|
374
|
+
parseArgument(gotoOptions, schemas.anyObject);
|
|
355
375
|
gotoOptions = { ...gotoOptions };
|
|
356
376
|
if (gotoOptions.waitUntil === 'networkidle') {
|
|
357
377
|
gotoOptions.waitUntil = 'networkidle0';
|
|
@@ -407,16 +427,8 @@ export async function gotoExtended(page, request, gotoOptions = {}) {
|
|
|
407
427
|
* @param [options]
|
|
408
428
|
*/
|
|
409
429
|
export async function infiniteScroll(page, options = {}) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
timeoutSecs: ow.optional.number,
|
|
413
|
-
maxScrollHeight: ow.optional.number,
|
|
414
|
-
waitForSecs: ow.optional.number,
|
|
415
|
-
scrollDownAndUp: ow.optional.boolean,
|
|
416
|
-
buttonSelector: ow.optional.string,
|
|
417
|
-
stopScrollCallback: ow.optional.function,
|
|
418
|
-
}));
|
|
419
|
-
const { timeoutSecs = 0, maxScrollHeight = 0, waitForSecs = 4, scrollDownAndUp = false, buttonSelector, stopScrollCallback, } = options;
|
|
430
|
+
parseArgument(page, validators.browserPage);
|
|
431
|
+
const { timeoutSecs, maxScrollHeight, waitForSecs, scrollDownAndUp, buttonSelector, stopScrollCallback } = parseArgument(options, infiniteScrollOptionsSchema);
|
|
420
432
|
let finished;
|
|
421
433
|
const startTime = Date.now();
|
|
422
434
|
const CHECK_INTERVAL_MILLIS = 1000;
|
|
@@ -508,16 +520,8 @@ export async function infiniteScroll(page, options = {}) {
|
|
|
508
520
|
* @param [options]
|
|
509
521
|
*/
|
|
510
522
|
export async function saveSnapshot(page, options = {}) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
key: ow.optional.string.nonEmpty,
|
|
514
|
-
screenshotQuality: ow.optional.number,
|
|
515
|
-
saveScreenshot: ow.optional.boolean,
|
|
516
|
-
saveHtml: ow.optional.boolean,
|
|
517
|
-
keyValueStoreName: ow.optional.string,
|
|
518
|
-
configuration: ow.optional.object,
|
|
519
|
-
}));
|
|
520
|
-
const { key = 'SNAPSHOT', screenshotQuality = 50, saveScreenshot = true, saveHtml = true, keyValueStoreName, configuration, } = options;
|
|
523
|
+
parseArgument(page, validators.browserPage);
|
|
524
|
+
const { key, screenshotQuality, saveScreenshot, saveHtml, keyValueStoreName, configuration } = parseArgument(options, saveSnapshotOptionsSchema);
|
|
521
525
|
try {
|
|
522
526
|
const store = await KeyValueStore.open(keyValueStoreName ? { name: keyValueStoreName } : null, {
|
|
523
527
|
configuration: configuration ?? Configuration.getGlobalConfiguration(),
|
|
@@ -564,7 +568,7 @@ ${error.message}
|
|
|
564
568
|
return idcacPlaywright;
|
|
565
569
|
}
|
|
566
570
|
export async function closeCookieModals(page) {
|
|
567
|
-
|
|
571
|
+
parseArgument(page, validators.browserPage);
|
|
568
572
|
const idcac = await getIdcacPlaywright();
|
|
569
573
|
if (idcac?.getInjectableScript()) {
|
|
570
574
|
await page.evaluate(idcac.getInjectableScript());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/puppeteer",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
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"
|
|
@@ -48,17 +48,16 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/datastructures": "^2.0.3",
|
|
51
|
-
"@crawlee/browser": "4.0.0-
|
|
52
|
-
"@crawlee/browser-pool": "4.0.0-
|
|
53
|
-
"@crawlee/core": "4.0.0-
|
|
54
|
-
"@crawlee/types": "4.0.0-
|
|
55
|
-
"@crawlee/utils": "4.0.0-
|
|
51
|
+
"@crawlee/browser": "4.0.0-rc.0",
|
|
52
|
+
"@crawlee/browser-pool": "4.0.0-rc.0",
|
|
53
|
+
"@crawlee/core": "4.0.0-rc.0",
|
|
54
|
+
"@crawlee/types": "4.0.0-rc.0",
|
|
55
|
+
"@crawlee/utils": "4.0.0-rc.0",
|
|
56
56
|
"cheerio": "^1.0.0",
|
|
57
57
|
"devtools-protocol": "*",
|
|
58
|
-
"idcac-playwright": "^0.2.0",
|
|
59
58
|
"jquery": "^3.7.1",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
59
|
+
"tslib": "^2.8.1",
|
|
60
|
+
"zod": "^4.4.3"
|
|
62
61
|
},
|
|
63
62
|
"peerDependencies": {
|
|
64
63
|
"idcac-playwright": "^0.2.0",
|
|
@@ -79,5 +78,5 @@
|
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
},
|
|
82
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "79ab33dacdacb83e0197e6516d145f3aceef80c7"
|
|
83
82
|
}
|