@crawlee/playwright 4.0.0-beta.210 → 4.0.0-beta.212
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 +0 -11
- package/internals/adaptive-playwright-crawler.js +13 -13
- package/internals/playwright-crawler.d.ts +0 -1
- package/internals/playwright-crawler.js +2 -2
- package/internals/utils/rendering-type-prediction.d.ts +0 -3
- package/internals/utils/rendering-type-prediction.js +7 -7
- package/package.json +9 -9
|
@@ -185,18 +185,7 @@ export declare class AdaptivePlaywrightCrawler<ContextExtension = Dictionary<nev
|
|
|
185
185
|
#private;
|
|
186
186
|
constructor(options?: AdaptivePlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
187
187
|
protected init(): Promise<void>;
|
|
188
|
-
private adaptCheerioContext;
|
|
189
|
-
private adaptPlaywrightContext;
|
|
190
|
-
/**
|
|
191
|
-
* Runs one request handler attempt inside its own {@link StorageTransaction}, wrapping the inner
|
|
192
|
-
* (static or browser) context pipeline. The transaction is pushed to `transactions` *at creation
|
|
193
|
-
* time, before the `try`* - the `ok: false` branch of the returned {@link Result} carries no
|
|
194
|
-
* result, and failed attempts are routine here. The caller owns the outcome and disposal.
|
|
195
|
-
*/
|
|
196
|
-
private crawlOne;
|
|
197
188
|
protected runRequestHandler(crawlingContext: CrawlingContext): Promise<void>;
|
|
198
|
-
private enqueueLinks;
|
|
199
|
-
private createLogProxy;
|
|
200
189
|
/**
|
|
201
190
|
* Number of rendering type detections that have not settled yet, including results the predictor is
|
|
202
191
|
* still persisting.
|
|
@@ -185,8 +185,8 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
185
185
|
});
|
|
186
186
|
this.#staticCrawler = staticCrawler;
|
|
187
187
|
this.#browserCrawler = browserCrawler;
|
|
188
|
-
this.#staticContextPipeline = staticCrawler.contextPipeline.compose(this
|
|
189
|
-
this.#browserContextPipeline = browserCrawler.contextPipeline.compose(this
|
|
188
|
+
this.#staticContextPipeline = staticCrawler.contextPipeline.compose(this.#adaptCheerioContext.bind(this));
|
|
189
|
+
this.#browserContextPipeline = browserCrawler.contextPipeline.compose(this.#adaptPlaywrightContext.bind(this));
|
|
190
190
|
}
|
|
191
191
|
async init() {
|
|
192
192
|
// A crawler can be run again after a teardown.
|
|
@@ -225,7 +225,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
225
225
|
},
|
|
226
226
|
}));
|
|
227
227
|
}
|
|
228
|
-
async adaptCheerioContext(cheerioContext) {
|
|
228
|
+
async #adaptCheerioContext(cheerioContext) {
|
|
229
229
|
return {
|
|
230
230
|
get page() {
|
|
231
231
|
throw new Error('Page object was used in HTTP-only request handler');
|
|
@@ -238,12 +238,12 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
238
238
|
},
|
|
239
239
|
enqueueLinks: async (options = {}) => {
|
|
240
240
|
const urls = extractUrlsFromCheerio(cheerioContext.$, options.selector, options.baseUrl ?? cheerioContext.request.loadedUrl);
|
|
241
|
-
return (await this
|
|
241
|
+
return (await this.#enqueueLinks(urls, options, cheerioContext.request));
|
|
242
242
|
},
|
|
243
243
|
response: cheerioContext.response,
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
-
async adaptPlaywrightContext(playwrightContext) {
|
|
246
|
+
async #adaptPlaywrightContext(playwrightContext) {
|
|
247
247
|
// Capture the original response to avoid infinite recursion when the getter is copied to the context
|
|
248
248
|
const originalResponse = playwrightContext.response;
|
|
249
249
|
return {
|
|
@@ -270,7 +270,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
270
270
|
const locator = playwrightContext.page.locator(selector).first();
|
|
271
271
|
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
272
272
|
const urls = await extractUrlsFromPage(playwrightContext.page, selector, options.baseUrl ?? playwrightContext.request.loadedUrl);
|
|
273
|
-
return (await this
|
|
273
|
+
return (await this.#enqueueLinks(urls, options, playwrightContext.request));
|
|
274
274
|
},
|
|
275
275
|
};
|
|
276
276
|
}
|
|
@@ -280,7 +280,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
280
280
|
* time, before the `try`* - the `ok: false` branch of the returned {@link Result} carries no
|
|
281
281
|
* result, and failed attempts are routine here. The caller owns the outcome and disposal.
|
|
282
282
|
*/
|
|
283
|
-
async crawlOne(renderingType, context, useStateFunction, transactions) {
|
|
283
|
+
async #crawlOne(renderingType, context, useStateFunction, transactions) {
|
|
284
284
|
const transaction = createStorageTransaction({
|
|
285
285
|
policy: this.#attemptWritePolicy,
|
|
286
286
|
commitTimeoutMillis: this.internalTimeoutMillis,
|
|
@@ -290,7 +290,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
290
290
|
const deferredCleanup = [];
|
|
291
291
|
const attemptBoundContextHelpers = {
|
|
292
292
|
useState: useStateFunction,
|
|
293
|
-
log: this
|
|
293
|
+
log: this.#createLogProxy(context.log, logs),
|
|
294
294
|
registerDeferredCleanup: (cleanup) => deferredCleanup.push(cleanup),
|
|
295
295
|
};
|
|
296
296
|
const subCrawlerContext = Object.defineProperties({}, Object.getOwnPropertyDescriptors(context));
|
|
@@ -340,7 +340,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
340
340
|
if (renderingTypePrediction.renderingType === 'static' && !shouldDetectRenderingType) {
|
|
341
341
|
crawlingContext.log.debug(`Running HTTP-only request handler for ${crawlingContext.request.url}`);
|
|
342
342
|
this.statistics.state.httpOnlyRequestHandlerRuns++;
|
|
343
|
-
const plainHTTPRun = await this
|
|
343
|
+
const plainHTTPRun = await this.#crawlOne('static', crawlingContext, crawlingContext.useState, transactions);
|
|
344
344
|
if (plainHTTPRun.ok && this.#resultChecker(plainHTTPRun.result)) {
|
|
345
345
|
crawlingContext.log.debug(`HTTP-only request handler succeeded for ${crawlingContext.request.url}`);
|
|
346
346
|
plainHTTPRun.logs?.forEach(([log, method, ...args]) => log[method](...args));
|
|
@@ -385,7 +385,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
385
385
|
return this.stateCopy;
|
|
386
386
|
},
|
|
387
387
|
};
|
|
388
|
-
const browserRun = await this
|
|
388
|
+
const browserRun = await this.#crawlOne('clientOnly', crawlingContext, stateTracker.getLiveState.bind(stateTracker), transactions);
|
|
389
389
|
if (!browserRun.ok) {
|
|
390
390
|
throw browserRun.error;
|
|
391
391
|
}
|
|
@@ -396,7 +396,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
396
396
|
crawlingContext.log.debug(`Detecting rendering type for ${crawlingContext.request.url}`);
|
|
397
397
|
// The detection attempt's transaction is never committed - its writes exist only for the
|
|
398
398
|
// result comparison.
|
|
399
|
-
const plainHTTPRun = await this
|
|
399
|
+
const plainHTTPRun = await this.#crawlOne('static', crawlingContext, stateTracker.getStateCopy.bind(stateTracker), transactions);
|
|
400
400
|
const detectionResult = (() => {
|
|
401
401
|
if (!plainHTTPRun.ok) {
|
|
402
402
|
return 'clientOnly';
|
|
@@ -432,7 +432,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
|
-
async enqueueLinks(urls, options, request) {
|
|
435
|
+
async #enqueueLinks(urls, options, request) {
|
|
436
436
|
const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
|
|
437
437
|
enqueueStrategy: options?.strategy,
|
|
438
438
|
finalRequestUrl: request.loadedUrl,
|
|
@@ -448,7 +448,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
448
448
|
strategy: options.strategy ?? EnqueueStrategy.SameHostname,
|
|
449
449
|
});
|
|
450
450
|
}
|
|
451
|
-
createLogProxy(log, logs) {
|
|
451
|
+
#createLogProxy(log, logs) {
|
|
452
452
|
return new Proxy(log, {
|
|
453
453
|
get(target, propertyName) {
|
|
454
454
|
if (proxyLogMethods.includes(propertyName)) {
|
|
@@ -249,7 +249,6 @@ export declare class PlaywrightCrawler<ContextExtension = Dictionary<never>, Ext
|
|
|
249
249
|
*/
|
|
250
250
|
constructor(options?: PlaywrightCrawlerOptions<ContextExtension, ExtendedContext, Routes, StatisticStateExtension>);
|
|
251
251
|
protected navigationHandler(crawlingContext: PlaywrightCrawlingContext, gotoOptions: DirectNavigationOptions): Promise<Response | null>;
|
|
252
|
-
private enhanceContext;
|
|
253
252
|
}
|
|
254
253
|
/**
|
|
255
254
|
* Returns a `postNavigationHooks`-ready hook that runs {@link PlaywrightContextUtils.handleCloudflareChallenge}
|
|
@@ -106,12 +106,12 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
#buildContextPipeline() {
|
|
109
|
-
return this.buildContextPipeline().compose(this
|
|
109
|
+
return this.buildContextPipeline().compose(this.#enhanceContext.bind(this));
|
|
110
110
|
}
|
|
111
111
|
async navigationHandler(crawlingContext, gotoOptions) {
|
|
112
112
|
return gotoExtended(crawlingContext.page, crawlingContext.request, gotoOptions);
|
|
113
113
|
}
|
|
114
|
-
async enhanceContext(context) {
|
|
114
|
+
async #enhanceContext(context) {
|
|
115
115
|
const waitForSelector = async (selector, timeoutMs = 5_000) => {
|
|
116
116
|
const locator = context.page.locator(selector).first();
|
|
117
117
|
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
@@ -60,7 +60,4 @@ export declare class RenderingTypePredictor implements IRenderingTypePredictor {
|
|
|
60
60
|
* Store the rendering type for a given URL and request label. This updates the underlying prediction model, which may be costly.
|
|
61
61
|
*/
|
|
62
62
|
storeResult(requests: Request | Request[], renderingType: RenderingType): void;
|
|
63
|
-
private resultCount;
|
|
64
|
-
private calculateFeatureVector;
|
|
65
|
-
private retrain;
|
|
66
63
|
}
|
|
@@ -104,14 +104,14 @@ export class RenderingTypePredictor {
|
|
|
104
104
|
return { renderingType: 'clientOnly', detectionProbabilityRecommendation: 1 };
|
|
105
105
|
}
|
|
106
106
|
const predictionUrl = new URL(loadedUrl ?? url);
|
|
107
|
-
const urlFeature = new Matrix([this
|
|
107
|
+
const urlFeature = new Matrix([this.#calculateFeatureVector(urlComponents(predictionUrl), label)]);
|
|
108
108
|
const [prediction] = logreg.predict(urlFeature);
|
|
109
109
|
const scores = [logreg.classifiers[0].testScores(urlFeature), logreg.classifiers[1].testScores(urlFeature)];
|
|
110
110
|
return {
|
|
111
111
|
renderingType: prediction === 1 ? 'static' : 'clientOnly',
|
|
112
112
|
detectionProbabilityRecommendation: Math.abs(scores[0] - scores[1]) < 0.1
|
|
113
113
|
? 1
|
|
114
|
-
: this.#detectionRatio * Math.max(1, 5 - this
|
|
114
|
+
: this.#detectionRatio * Math.max(1, 5 - this.#resultCount(label)),
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
@@ -129,20 +129,20 @@ export class RenderingTypePredictor {
|
|
|
129
129
|
}
|
|
130
130
|
state.detectionResults.get(renderingType).get(label).push(urlComponents(resultUrl));
|
|
131
131
|
}
|
|
132
|
-
this
|
|
132
|
+
this.#retrain();
|
|
133
133
|
}
|
|
134
|
-
resultCount(label) {
|
|
134
|
+
#resultCount(label) {
|
|
135
135
|
return Array.from(this.#state.currentValue.detectionResults.values())
|
|
136
136
|
.map((results) => results.get(label)?.length ?? 0)
|
|
137
137
|
.reduce((acc, value) => acc + value, 0);
|
|
138
138
|
}
|
|
139
|
-
calculateFeatureVector(url, label) {
|
|
139
|
+
#calculateFeatureVector(url, label) {
|
|
140
140
|
return [
|
|
141
141
|
mean((this.#state.currentValue.detectionResults.get('static')?.get(label) ?? []).map((otherUrl) => calculateUrlSimilarity(url, otherUrl) ?? 0)) ?? 0,
|
|
142
142
|
mean((this.#state.currentValue.detectionResults.get('clientOnly')?.get(label) ?? []).map((otherUrl) => calculateUrlSimilarity(url, otherUrl) ?? 0)) ?? 0,
|
|
143
143
|
];
|
|
144
144
|
}
|
|
145
|
-
retrain() {
|
|
145
|
+
#retrain() {
|
|
146
146
|
const X = [
|
|
147
147
|
[0, 1],
|
|
148
148
|
[1, 0],
|
|
@@ -151,7 +151,7 @@ export class RenderingTypePredictor {
|
|
|
151
151
|
for (const [renderingType, urlsByLabel] of this.#state.currentValue.detectionResults.entries()) {
|
|
152
152
|
for (const [label, urls] of urlsByLabel) {
|
|
153
153
|
for (const url of urls) {
|
|
154
|
-
X.push(this
|
|
154
|
+
X.push(this.#calculateFeatureVector(url, label));
|
|
155
155
|
Y.push(renderingType === 'static' ? 1 : 0);
|
|
156
156
|
}
|
|
157
157
|
}
|
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.212",
|
|
4
4
|
"description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/datastructures": "^3.0.1",
|
|
51
51
|
"@apify/timeout": "^1.0.1",
|
|
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.212",
|
|
53
|
+
"@crawlee/browser": "4.0.0-beta.212",
|
|
54
|
+
"@crawlee/browser-pool": "4.0.0-beta.212",
|
|
55
|
+
"@crawlee/cheerio": "4.0.0-beta.212",
|
|
56
|
+
"@crawlee/core": "4.0.0-beta.212",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.212",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.212",
|
|
59
59
|
"cheerio": "^1.0.0",
|
|
60
60
|
"jquery": "^3.7.1",
|
|
61
61
|
"ml-logistic-regression": "^2.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "171a63a5d7f2a6d74cbce58479204ac20d30b87e"
|
|
84
84
|
}
|