@crawlee/playwright 4.0.0-beta.105 → 4.0.0-beta.107
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/internals/adaptive-playwright-crawler.d.ts +2 -15
- package/internals/adaptive-playwright-crawler.js +33 -40
- package/internals/playwright-crawler.d.ts +1 -1
- package/internals/playwright-crawler.js +1 -1
- package/internals/utils/rendering-type-prediction.d.ts +1 -1
- package/internals/utils/rendering-type-prediction.js +4 -3
- package/package.json +9 -10
|
@@ -174,23 +174,10 @@ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<
|
|
|
174
174
|
* @experimental
|
|
175
175
|
*/
|
|
176
176
|
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> {
|
|
177
|
-
private
|
|
178
|
-
private resultChecker;
|
|
179
|
-
private shouldPropagateError;
|
|
180
|
-
private resultComparator;
|
|
181
|
-
private staticContextPipeline;
|
|
182
|
-
private browserContextPipeline;
|
|
183
|
-
private individualRequestHandlerTimeoutMillis;
|
|
177
|
+
#private;
|
|
184
178
|
get stats(): AdaptivePlaywrightCrawlerStatistics;
|
|
185
|
-
private inFlightRenderingTypeDetections;
|
|
186
|
-
/**
|
|
187
|
-
* The write policy of the per-attempt transactions. Defaults the request queue to `deferred`:
|
|
188
|
-
* a discarded attempt's enqueues must never reach the queue.
|
|
189
|
-
*/
|
|
190
|
-
private readonly attemptWritePolicy;
|
|
191
|
-
private teardownHooks;
|
|
192
179
|
constructor(options?: AdaptivePlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes>);
|
|
193
|
-
protected
|
|
180
|
+
protected init(): Promise<void>;
|
|
194
181
|
protected buildContextPipeline(): ContextPipeline<CrawlingContext<Dictionary>, CrawlingContext<Dictionary> & {
|
|
195
182
|
readonly request: LoadedRequest<Request<Dictionary>>;
|
|
196
183
|
readonly response: Response;
|
|
@@ -83,24 +83,23 @@ const proxyLogMethods = [
|
|
|
83
83
|
* @experimental
|
|
84
84
|
*/
|
|
85
85
|
export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
86
|
-
renderingTypePredictor;
|
|
87
|
-
resultChecker;
|
|
88
|
-
shouldPropagateError;
|
|
89
|
-
resultComparator;
|
|
90
|
-
staticContextPipeline;
|
|
91
|
-
browserContextPipeline;
|
|
92
|
-
individualRequestHandlerTimeoutMillis;
|
|
86
|
+
#renderingTypePredictor;
|
|
87
|
+
#resultChecker;
|
|
88
|
+
#shouldPropagateError;
|
|
89
|
+
#resultComparator;
|
|
90
|
+
#staticContextPipeline;
|
|
91
|
+
#browserContextPipeline;
|
|
92
|
+
#individualRequestHandlerTimeoutMillis;
|
|
93
93
|
// The constructor always injects an `AdaptivePlaywrightCrawlerStatistics`, so narrowing the cast is sound.
|
|
94
94
|
get stats() {
|
|
95
95
|
return super.stats;
|
|
96
96
|
}
|
|
97
|
-
inFlightRenderingTypeDetections = 0;
|
|
98
97
|
/**
|
|
99
98
|
* The write policy of the per-attempt transactions. Defaults the request queue to `deferred`:
|
|
100
99
|
* a discarded attempt's enqueues must never reach the queue.
|
|
101
100
|
*/
|
|
102
|
-
attemptWritePolicy;
|
|
103
|
-
teardownHooks = [];
|
|
101
|
+
#attemptWritePolicy;
|
|
102
|
+
#teardownHooks = [];
|
|
104
103
|
constructor(options = {}) {
|
|
105
104
|
const { requestHandler, renderingTypeDetectionRatio = 0.1, renderingTypePredictor, resultChecker, shouldPropagateError, resultComparator, statistics, requestHandlerTimeoutSecs = 60, errorHandler, failedRequestHandler, preNavigationHooks = [], postNavigationHooks = [], extendContext, contextPipelineBuilder, transactionalStorage, ...rest } = options;
|
|
106
105
|
// The user's value is replaced by `false` in the `super` call below — validate it separately.
|
|
@@ -134,24 +133,24 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
134
133
|
// user-facing option (validated above) to those.
|
|
135
134
|
transactionalStorage: false,
|
|
136
135
|
});
|
|
137
|
-
this
|
|
136
|
+
this.#individualRequestHandlerTimeoutMillis = requestHandlerTimeoutSecs * 1000;
|
|
138
137
|
// `renderingTypeDetectionRatio` only configures the default predictor - an injected one brings its own
|
|
139
138
|
// detection ratio (and its own state), so the option is ignored in that case.
|
|
140
|
-
this
|
|
141
|
-
this
|
|
139
|
+
this.#renderingTypePredictor = OwnedOrInjected.resolve(renderingTypePredictor, () => new RenderingTypePredictor({ detectionRatio: renderingTypeDetectionRatio }));
|
|
140
|
+
this.#attemptWritePolicy = {
|
|
142
141
|
requestQueue: 'deferred',
|
|
143
142
|
...(typeof transactionalStorage === 'object' ? transactionalStorage : {}),
|
|
144
143
|
};
|
|
145
|
-
this
|
|
146
|
-
this
|
|
144
|
+
this.#resultChecker = resultChecker ?? (() => true);
|
|
145
|
+
this.#shouldPropagateError = shouldPropagateError ?? (() => false);
|
|
147
146
|
if (resultComparator !== undefined) {
|
|
148
|
-
this
|
|
147
|
+
this.#resultComparator = resultComparator;
|
|
149
148
|
}
|
|
150
149
|
else if (resultChecker !== undefined) {
|
|
151
|
-
this
|
|
150
|
+
this.#resultComparator = (resultA, resultB) => this.#resultChecker(resultA) && this.#resultChecker(resultB);
|
|
152
151
|
}
|
|
153
152
|
else {
|
|
154
|
-
this
|
|
153
|
+
this.#resultComparator = (resultA, resultB) => {
|
|
155
154
|
return (resultA.datasetItems.length === resultB.datasetItems.length &&
|
|
156
155
|
resultA.datasetItems.every((itemA, i) => {
|
|
157
156
|
const itemB = resultB.datasetItems[i];
|
|
@@ -181,19 +180,19 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
181
180
|
postNavigationHooks: postNavigationHooks,
|
|
182
181
|
extendContext,
|
|
183
182
|
});
|
|
184
|
-
this
|
|
185
|
-
this
|
|
183
|
+
this.#teardownHooks.push(browserCrawler.teardown.bind(browserCrawler));
|
|
184
|
+
this.#staticContextPipeline = staticCrawler.contextPipeline.compose({
|
|
186
185
|
action: this.adaptCheerioContext.bind(this),
|
|
187
186
|
});
|
|
188
|
-
this
|
|
187
|
+
this.#browserContextPipeline = browserCrawler.contextPipeline.compose({
|
|
189
188
|
action: this.adaptPlaywrightContext.bind(this),
|
|
190
189
|
});
|
|
191
190
|
}
|
|
192
|
-
async
|
|
191
|
+
async init() {
|
|
193
192
|
// Only the predictor we built ourselves is ours to initialize - an injected one is borrowed, so its
|
|
194
193
|
// lifecycle (including restoring persisted state) stays with whoever created it.
|
|
195
|
-
await this
|
|
196
|
-
return await super.
|
|
194
|
+
await this.#renderingTypePredictor.ifOwned((predictor) => predictor.initialize());
|
|
195
|
+
return await super.init();
|
|
197
196
|
}
|
|
198
197
|
buildContextPipeline() {
|
|
199
198
|
const errorMessage = (prop) => `The \`${prop}\` property is not available on the outer context pipeline of AdaptivePlaywrightCrawler - it is provided by the inner (static/browser) pipelines`;
|
|
@@ -289,7 +288,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
289
288
|
*/
|
|
290
289
|
async crawlOne(renderingType, context, useStateFunction, transactions) {
|
|
291
290
|
const transaction = createStorageTransaction({
|
|
292
|
-
policy: this
|
|
291
|
+
policy: this.#attemptWritePolicy,
|
|
293
292
|
commitTimeoutMillis: this.internalTimeoutMillis,
|
|
294
293
|
});
|
|
295
294
|
transactions.push(transaction);
|
|
@@ -309,16 +308,16 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
309
308
|
try {
|
|
310
309
|
const callAdaptiveRequestHandler = async () => {
|
|
311
310
|
if (renderingType === 'static') {
|
|
312
|
-
await this
|
|
311
|
+
await this.#staticContextPipeline.call(subCrawlerContext, this.requestHandler.bind(this));
|
|
313
312
|
}
|
|
314
313
|
else if (renderingType === 'clientOnly') {
|
|
315
|
-
await this
|
|
314
|
+
await this.#browserContextPipeline.call(subCrawlerContext, this.requestHandler.bind(this));
|
|
316
315
|
}
|
|
317
316
|
};
|
|
318
317
|
// this crawler overrides `runRequestHandler` and times each rendering-type run itself, so it has
|
|
319
318
|
// to resolve any per-route override too - otherwise routes would be silently ignored here
|
|
320
319
|
const routeTimeoutSecs = this.requestHandler.getTimeoutSecs?.(context.request.label);
|
|
321
|
-
const timeoutMillis = routeTimeoutSecs === undefined ? this
|
|
320
|
+
const timeoutMillis = routeTimeoutSecs === undefined ? this.#individualRequestHandlerTimeoutMillis : routeTimeoutSecs * 1000;
|
|
322
321
|
await addTimeoutToPromise(async () => transaction.run(callAdaptiveRequestHandler), timeoutMillis, 'Request handler timed out');
|
|
323
322
|
return { result: transaction, ok: true, logs };
|
|
324
323
|
}
|
|
@@ -330,14 +329,11 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
330
329
|
}
|
|
331
330
|
}
|
|
332
331
|
async runRequestHandler(crawlingContext) {
|
|
333
|
-
const renderingTypePrediction = this
|
|
332
|
+
const renderingTypePrediction = this.#renderingTypePredictor.value.predict(crawlingContext.request);
|
|
334
333
|
const shouldDetectRenderingType = Math.random() < renderingTypePrediction.detectionProbabilityRecommendation;
|
|
335
334
|
if (!shouldDetectRenderingType) {
|
|
336
335
|
crawlingContext.log.debug(`Predicted rendering type ${renderingTypePrediction.renderingType} for ${crawlingContext.request.url}`);
|
|
337
336
|
}
|
|
338
|
-
if (shouldDetectRenderingType) {
|
|
339
|
-
this.inFlightRenderingTypeDetections += 1;
|
|
340
|
-
}
|
|
341
337
|
// Every transaction created for this request - up to two, since the static-then-browser
|
|
342
338
|
// fall-through and the browser-then-detection pair are mutually exclusive. Disposed in the
|
|
343
339
|
// `finally` below, not earlier: the comparators read the journals after `crawlOne` returns.
|
|
@@ -347,7 +343,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
347
343
|
crawlingContext.log.debug(`Running HTTP-only request handler for ${crawlingContext.request.url}`);
|
|
348
344
|
this.stats.trackHttpOnlyRequestHandlerRun();
|
|
349
345
|
const plainHTTPRun = await this.crawlOne('static', crawlingContext, crawlingContext.useState, transactions);
|
|
350
|
-
if (plainHTTPRun.ok && this
|
|
346
|
+
if (plainHTTPRun.ok && this.#resultChecker(plainHTTPRun.result)) {
|
|
351
347
|
crawlingContext.log.debug(`HTTP-only request handler succeeded for ${crawlingContext.request.url}`);
|
|
352
348
|
plainHTTPRun.logs?.forEach(([log, method, ...args]) => log[method](...args));
|
|
353
349
|
await plainHTTPRun.result.commit();
|
|
@@ -358,7 +354,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
358
354
|
const actualError = plainHTTPRun.error instanceof RequestHandlerError
|
|
359
355
|
? plainHTTPRun.error.cause
|
|
360
356
|
: plainHTTPRun.error;
|
|
361
|
-
if (await this
|
|
357
|
+
if (await this.#shouldPropagateError(actualError, crawlingContext)) {
|
|
362
358
|
throw actualError;
|
|
363
359
|
}
|
|
364
360
|
crawlingContext.log.exception(actualError, `HTTP-only request handler failed for ${crawlingContext.request.url}`);
|
|
@@ -405,7 +401,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
405
401
|
if (!plainHTTPRun.ok) {
|
|
406
402
|
return 'clientOnly';
|
|
407
403
|
}
|
|
408
|
-
const comparisonResult = this
|
|
404
|
+
const comparisonResult = this.#resultComparator(plainHTTPRun.result, browserRun.result);
|
|
409
405
|
if (comparisonResult === true || comparisonResult === 'equal') {
|
|
410
406
|
return 'static';
|
|
411
407
|
}
|
|
@@ -416,14 +412,11 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
416
412
|
})();
|
|
417
413
|
crawlingContext.log.debug(`Detected rendering type ${detectionResult} for ${crawlingContext.request.url}`);
|
|
418
414
|
if (detectionResult !== undefined) {
|
|
419
|
-
this
|
|
415
|
+
this.#renderingTypePredictor.value.storeResult(crawlingContext.request, detectionResult);
|
|
420
416
|
}
|
|
421
417
|
}
|
|
422
418
|
}
|
|
423
419
|
finally {
|
|
424
|
-
if (shouldDetectRenderingType) {
|
|
425
|
-
this.inFlightRenderingTypeDetections -= 1;
|
|
426
|
-
}
|
|
427
420
|
// A still-open transaction here belongs to a discarded attempt - roll it back, then release.
|
|
428
421
|
for (const transaction of transactions) {
|
|
429
422
|
transaction.rollback();
|
|
@@ -456,7 +449,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
456
449
|
}
|
|
457
450
|
async teardown() {
|
|
458
451
|
await super.teardown();
|
|
459
|
-
for (const hook of this
|
|
452
|
+
for (const hook of this.#teardownHooks) {
|
|
460
453
|
await hook();
|
|
461
454
|
}
|
|
462
455
|
}
|
|
@@ -264,7 +264,7 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
|
|
|
264
264
|
closeCookieModals: () => Promise<void>;
|
|
265
265
|
handleCloudflareChallenge: (options?: HandleCloudflareChallengeOptions) => Promise<Response | undefined>;
|
|
266
266
|
}>;
|
|
267
|
-
protected
|
|
267
|
+
protected navigationHandler(crawlingContext: PlaywrightCrawlingContext, gotoOptions: DirectNavigationOptions): Promise<Response | null>;
|
|
268
268
|
private enhanceContext;
|
|
269
269
|
}
|
|
270
270
|
/**
|
|
@@ -109,7 +109,7 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
109
109
|
buildContextPipeline() {
|
|
110
110
|
return super.buildContextPipeline().compose({ action: this.enhanceContext.bind(this) });
|
|
111
111
|
}
|
|
112
|
-
async
|
|
112
|
+
async navigationHandler(crawlingContext, gotoOptions) {
|
|
113
113
|
return gotoExtended(crawlingContext.page, crawlingContext.request, gotoOptions);
|
|
114
114
|
}
|
|
115
115
|
async enhanceContext(context) {
|
|
@@ -26,7 +26,7 @@ export interface IRenderingTypePredictor {
|
|
|
26
26
|
* @experimental
|
|
27
27
|
*/
|
|
28
28
|
export declare class RenderingTypePredictor implements IRenderingTypePredictor {
|
|
29
|
-
private
|
|
29
|
+
#private;
|
|
30
30
|
private state;
|
|
31
31
|
constructor({ detectionRatio, persistenceOptions }: RenderingTypePredictorOptions);
|
|
32
32
|
/**
|
|
@@ -30,10 +30,11 @@ const mean = (values) => (values.length > 0 ? sum(values) / values.length : unde
|
|
|
30
30
|
* @experimental
|
|
31
31
|
*/
|
|
32
32
|
export class RenderingTypePredictor {
|
|
33
|
-
detectionRatio;
|
|
33
|
+
#detectionRatio;
|
|
34
|
+
// kept as TS-private: tests reach for it at runtime
|
|
34
35
|
state;
|
|
35
36
|
constructor({ detectionRatio, persistenceOptions }) {
|
|
36
|
-
this
|
|
37
|
+
this.#detectionRatio = detectionRatio;
|
|
37
38
|
this.state = new RecoverableState({
|
|
38
39
|
defaultState: {
|
|
39
40
|
logreg: new LogisticRegression({ numSteps: 1000, learningRate: 0.05 }),
|
|
@@ -86,7 +87,7 @@ export class RenderingTypePredictor {
|
|
|
86
87
|
renderingType: prediction === 1 ? 'static' : 'clientOnly',
|
|
87
88
|
detectionProbabilityRecommendation: Math.abs(scores[0] - scores[1]) < 0.1
|
|
88
89
|
? 1
|
|
89
|
-
: this
|
|
90
|
+
: this.#detectionRatio * Math.max(1, 5 - this.resultCount(label)),
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/playwright",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.107",
|
|
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,15 +49,14 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/datastructures": "^2.0.3",
|
|
51
51
|
"@apify/timeout": "^0.4.4",
|
|
52
|
-
"@crawlee/basic": "4.0.0-beta.
|
|
53
|
-
"@crawlee/browser": "4.0.0-beta.
|
|
54
|
-
"@crawlee/browser-pool": "4.0.0-beta.
|
|
55
|
-
"@crawlee/cheerio": "4.0.0-beta.
|
|
56
|
-
"@crawlee/core": "4.0.0-beta.
|
|
57
|
-
"@crawlee/types": "4.0.0-beta.
|
|
58
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
52
|
+
"@crawlee/basic": "4.0.0-beta.107",
|
|
53
|
+
"@crawlee/browser": "4.0.0-beta.107",
|
|
54
|
+
"@crawlee/browser-pool": "4.0.0-beta.107",
|
|
55
|
+
"@crawlee/cheerio": "4.0.0-beta.107",
|
|
56
|
+
"@crawlee/core": "4.0.0-beta.107",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.107",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.107",
|
|
59
59
|
"cheerio": "^1.0.0",
|
|
60
|
-
"idcac-playwright": "^0.1.3",
|
|
61
60
|
"jquery": "^3.7.1",
|
|
62
61
|
"ml-logistic-regression": "^2.0.0",
|
|
63
62
|
"ml-matrix": "^6.12.1",
|
|
@@ -85,5 +84,5 @@
|
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
},
|
|
88
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "5a3cb6242d0ae261df93c5e03cfebbb9b736e6b7"
|
|
89
88
|
}
|