@crawlee/browser 4.0.0-beta.105 → 4.0.0-beta.106
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.
|
@@ -227,8 +227,7 @@ export interface BrowserCrawlerOptions<Page extends CommonPage = CommonPage, Res
|
|
|
227
227
|
* @category Crawlers
|
|
228
228
|
*/
|
|
229
229
|
export declare abstract class BrowserCrawler<Page extends CommonPage = CommonPage, Response extends BaseResponse = BaseResponse, InternalBrowserPoolOptions extends BrowserPoolOptions = BrowserPoolOptions, LaunchOptions extends Dictionary | undefined = Dictionary, Context extends BrowserCrawlingContext<Page, Response> = BrowserCrawlingContext<Page, Response>, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>, GoToOptions extends Dictionary = Dictionary> extends BasicCrawler<Context, ContextExtension, ExtendedContext, Routes> {
|
|
230
|
-
|
|
231
|
-
private browserPoolDep;
|
|
230
|
+
#private;
|
|
232
231
|
/**
|
|
233
232
|
* A reference to the underlying browser pool that manages the crawler's browsers. Typed as
|
|
234
233
|
* {@link IBrowserPool} so custom implementations can be plugged in via the `browserPool` constructor option.
|
|
@@ -237,10 +236,6 @@ export declare abstract class BrowserCrawler<Page extends CommonPage = CommonPag
|
|
|
237
236
|
launchContext: BrowserLaunchContext<LaunchOptions, unknown>;
|
|
238
237
|
protected readonly ignoreShadowRoots: boolean;
|
|
239
238
|
protected readonly ignoreIframes: boolean;
|
|
240
|
-
private readonly navigationTimeoutMillis;
|
|
241
|
-
private readonly preNavigationHooks;
|
|
242
|
-
private readonly postNavigationHooks;
|
|
243
|
-
private readonly saveResponseCookies;
|
|
244
239
|
protected static optionsShape: {
|
|
245
240
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
246
241
|
navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
|
|
@@ -367,7 +362,7 @@ export declare abstract class BrowserCrawler<Page extends CommonPage = CommonPag
|
|
|
367
362
|
* Transforms proxy-related errors to `SessionError`.
|
|
368
363
|
*/
|
|
369
364
|
private throwIfProxyError;
|
|
370
|
-
protected abstract
|
|
365
|
+
protected abstract navigationHandler(crawlingContext: BrowserCrawlingContext<Page, Response>, gotoOptions: GoToOptions): Promise<Context['response'] | null | undefined>;
|
|
371
366
|
private processResponse;
|
|
372
367
|
/**
|
|
373
368
|
* Function for cleaning up after all requests are processed.
|
|
@@ -58,21 +58,21 @@ function isNavigationTimeoutError(error) {
|
|
|
58
58
|
*/
|
|
59
59
|
export class BrowserCrawler extends BasicCrawler {
|
|
60
60
|
/** Backs the {@link BrowserCrawler.browserPool|`browserPool`} getter. */
|
|
61
|
-
browserPoolDep;
|
|
61
|
+
#browserPoolDep;
|
|
62
62
|
/**
|
|
63
63
|
* A reference to the underlying browser pool that manages the crawler's browsers. Typed as
|
|
64
64
|
* {@link IBrowserPool} so custom implementations can be plugged in via the `browserPool` constructor option.
|
|
65
65
|
*/
|
|
66
66
|
get browserPool() {
|
|
67
|
-
return this
|
|
67
|
+
return this.#browserPoolDep.value;
|
|
68
68
|
}
|
|
69
69
|
launchContext;
|
|
70
70
|
ignoreShadowRoots;
|
|
71
71
|
ignoreIframes;
|
|
72
|
-
navigationTimeoutMillis;
|
|
73
|
-
preNavigationHooks;
|
|
74
|
-
postNavigationHooks;
|
|
75
|
-
saveResponseCookies;
|
|
72
|
+
#navigationTimeoutMillis;
|
|
73
|
+
#preNavigationHooks;
|
|
74
|
+
#postNavigationHooks;
|
|
75
|
+
#saveResponseCookies;
|
|
76
76
|
static optionsShape = {
|
|
77
77
|
...BasicCrawler.optionsShape,
|
|
78
78
|
navigationTimeoutSecs: ow.optional.number.greaterThan(0),
|
|
@@ -103,18 +103,18 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
103
103
|
// hook eats into the same window the navigation uses. The navigation itself is bounded by
|
|
104
104
|
// capping its `gotoOptions.timeout` to the remaining budget.
|
|
105
105
|
const windowGuard = (step) => skipGuard(async (ctx) => {
|
|
106
|
-
const remaining = remainingNavigationWindowMillis(ctx, this
|
|
106
|
+
const remaining = remainingNavigationWindowMillis(ctx, this.#navigationTimeoutMillis);
|
|
107
107
|
if (remaining <= 0) {
|
|
108
|
-
throw new TimeoutError(`Navigation timed out after ${this
|
|
108
|
+
throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
|
|
109
109
|
}
|
|
110
|
-
return addTimeoutToPromise(async () => step(ctx), remaining, `Navigation timed out after ${this
|
|
110
|
+
return addTimeoutToPromise(async () => step(ctx), remaining, `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
|
|
111
111
|
});
|
|
112
112
|
let pipeline = contextPipelineBuilder().compose({ action: this.prepareNavigation.bind(this) });
|
|
113
|
-
for (const hook of this
|
|
113
|
+
for (const hook of this.#preNavigationHooks) {
|
|
114
114
|
pipeline = pipeline.compose(windowGuard(hook));
|
|
115
115
|
}
|
|
116
116
|
pipeline = pipeline.compose(skipGuard(this.navigate.bind(this)));
|
|
117
|
-
for (const hook of this
|
|
117
|
+
for (const hook of this.#postNavigationHooks) {
|
|
118
118
|
pipeline = pipeline.compose(windowGuard(hook));
|
|
119
119
|
}
|
|
120
120
|
return pipeline
|
|
@@ -125,23 +125,23 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
125
125
|
extendContext,
|
|
126
126
|
});
|
|
127
127
|
this.launchContext = launchContext;
|
|
128
|
-
this
|
|
128
|
+
this.#navigationTimeoutMillis = navigationTimeoutSecs * 1000;
|
|
129
129
|
// The public option hooks are extension-aware; internal storage uses the base context type
|
|
130
130
|
// (the pipeline composes hooks against the concrete context, which does not statically carry
|
|
131
131
|
// `ContextExtension`). The extension members are present at runtime regardless.
|
|
132
|
-
this
|
|
133
|
-
this
|
|
132
|
+
this.#preNavigationHooks = preNavigationHooks;
|
|
133
|
+
this.#postNavigationHooks = postNavigationHooks;
|
|
134
134
|
this.ignoreIframes = ignoreIframes;
|
|
135
135
|
this.ignoreShadowRoots = ignoreShadowRoots;
|
|
136
136
|
if (headless != null) {
|
|
137
137
|
this.launchContext.launchOptions ??= {};
|
|
138
138
|
this.launchContext.launchOptions.headless = headless;
|
|
139
139
|
}
|
|
140
|
-
this
|
|
140
|
+
this.#saveResponseCookies = saveResponseCookies;
|
|
141
141
|
// `browserPool` wins over `remoteBrowser` — a passed-in pool is used as-is (borrowed), the sugar is ignored.
|
|
142
142
|
// The default is only built when no pool was injected, so all the option/launchContext fiddling below stays
|
|
143
143
|
// inside the factory.
|
|
144
|
-
this
|
|
144
|
+
this.#browserPoolDep = OwnedOrInjected.resolve(browserPool, () => {
|
|
145
145
|
const resolvedBrowserPoolOptions = browserPoolOptions ?? {};
|
|
146
146
|
if (launchContext?.userAgent) {
|
|
147
147
|
if (resolvedBrowserPoolOptions.useFingerprints)
|
|
@@ -168,7 +168,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
getNavigationTimeoutMillis() {
|
|
171
|
-
return this
|
|
171
|
+
return this.#navigationTimeoutMillis;
|
|
172
172
|
}
|
|
173
173
|
buildContextPipeline() {
|
|
174
174
|
return ContextPipeline.create().compose({
|
|
@@ -263,31 +263,31 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
263
263
|
return {
|
|
264
264
|
// Default to the full navigation timeout so a pre-navigation hook can read it; `navigate` narrows it
|
|
265
265
|
// to the remaining shared window unless a hook overrode it (see there).
|
|
266
|
-
gotoOptions: { timeout: this
|
|
267
|
-
[COOKIES_BEFORE_HOOKS]: this.
|
|
266
|
+
gotoOptions: { timeout: this.#navigationTimeoutMillis },
|
|
267
|
+
[COOKIES_BEFORE_HOOKS]: this.getCookieHeaderFromRequest(crawlingContext.request),
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
async navigate(crawlingContext) {
|
|
271
271
|
tryCancel();
|
|
272
272
|
const gotoOptions = crawlingContext.gotoOptions;
|
|
273
|
-
const remaining = remainingNavigationWindowMillis(crawlingContext, this
|
|
273
|
+
const remaining = remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis);
|
|
274
274
|
if (remaining <= 0) {
|
|
275
|
-
throw new TimeoutError(`Navigation timed out after ${this
|
|
275
|
+
throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
|
|
276
276
|
}
|
|
277
277
|
// If a hook left the default `navigationTimeoutMillis` in place, bound the goto to whatever is left of the
|
|
278
278
|
// shared navigation window. If it overrode the value - including `0`, Playwright's "no timeout" - honour
|
|
279
279
|
// that verbatim as the goto's own timeout. The driver enforces this natively (so a timed-out goto is
|
|
280
280
|
// aborted, not left lingering) and `handleNavigationTimeout` turns its error into our own message.
|
|
281
281
|
const gotoTimeout = gotoOptions;
|
|
282
|
-
if (gotoTimeout.timeout === this
|
|
282
|
+
if (gotoTimeout.timeout === this.#navigationTimeoutMillis) {
|
|
283
283
|
gotoTimeout.timeout = remaining;
|
|
284
284
|
}
|
|
285
285
|
const cookiesBeforeHooks = readContextField(crawlingContext, COOKIES_BEFORE_HOOKS);
|
|
286
|
-
const cookiesAfterHooks = this.
|
|
286
|
+
const cookiesAfterHooks = this.getCookieHeaderFromRequest(crawlingContext.request);
|
|
287
287
|
await this.applyCookies(crawlingContext, cookiesBeforeHooks, cookiesAfterHooks);
|
|
288
288
|
let response;
|
|
289
289
|
try {
|
|
290
|
-
response = (await this.
|
|
290
|
+
response = (await this.navigationHandler(crawlingContext, gotoOptions)) ?? undefined;
|
|
291
291
|
}
|
|
292
292
|
catch (error) {
|
|
293
293
|
await this.handleNavigationTimeout(crawlingContext, error);
|
|
@@ -320,7 +320,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
320
320
|
* Copies cookies from the live browser page into the session cookie jar.
|
|
321
321
|
*/
|
|
322
322
|
async persistCookiesFromPage(crawlingContext) {
|
|
323
|
-
if (!this
|
|
323
|
+
if (!this.#saveResponseCookies || !crawlingContext.session) {
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
326
326
|
const { cookies } = await this.browserPool.extractPageState(crawlingContext.page);
|
|
@@ -390,7 +390,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
390
390
|
session?.markBad();
|
|
391
391
|
// The driver was handed the remaining window (usually shorter than `navigationTimeoutSecs` once the
|
|
392
392
|
// hooks have run), so it names that value in its own error; report the configured window instead.
|
|
393
|
-
throw new TimeoutError(`Navigation timed out after ${this
|
|
393
|
+
throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
/**
|
|
@@ -398,7 +398,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
398
398
|
*/
|
|
399
399
|
throwIfProxyError(error) {
|
|
400
400
|
if (this.isProxyError(error)) {
|
|
401
|
-
throw new SessionError(this.
|
|
401
|
+
throw new SessionError(this.getMessageFromError(error));
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
async processResponse(response, crawlingContext) {
|
|
@@ -415,7 +415,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
415
415
|
}
|
|
416
416
|
if (this.sessionPool && response && session) {
|
|
417
417
|
if (typeof response === 'object' && typeof response.status === 'function') {
|
|
418
|
-
this.
|
|
418
|
+
this.throwOnBlockedRequest(response.status());
|
|
419
419
|
}
|
|
420
420
|
else {
|
|
421
421
|
this.log.debug('Got a malformed Browser response.', { request, response });
|
|
@@ -428,7 +428,7 @@ export class BrowserCrawler extends BasicCrawler {
|
|
|
428
428
|
* @ignore
|
|
429
429
|
*/
|
|
430
430
|
async teardown() {
|
|
431
|
-
await this
|
|
431
|
+
await this.#browserPoolDep.ifOwned((pool) => pool.destroy());
|
|
432
432
|
await super.teardown();
|
|
433
433
|
}
|
|
434
434
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/browser",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.106",
|
|
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,10 +48,10 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/timeout": "^0.4.4",
|
|
51
|
-
"@crawlee/basic": "4.0.0-beta.
|
|
52
|
-
"@crawlee/browser-pool": "4.0.0-beta.
|
|
53
|
-
"@crawlee/types": "4.0.0-beta.
|
|
54
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
51
|
+
"@crawlee/basic": "4.0.0-beta.106",
|
|
52
|
+
"@crawlee/browser-pool": "4.0.0-beta.106",
|
|
53
|
+
"@crawlee/types": "4.0.0-beta.106",
|
|
54
|
+
"@crawlee/utils": "4.0.0-beta.106",
|
|
55
55
|
"ow": "^2.0.0",
|
|
56
56
|
"tslib": "^2.8.1",
|
|
57
57
|
"type-fest": "^4.41.0"
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "c622f1fc65e65221ea245817c58ecc0ffb4a5cb0"
|
|
79
79
|
}
|