@crawlee/playwright 4.0.0-beta.80 → 4.0.0-beta.81
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.
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { BrowserHook, LoadedRequest, Request, RouterHandler, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
|
|
2
2
|
import type { BasicCrawlerOptions } from '@crawlee/basic';
|
|
3
3
|
import { BasicCrawler } from '@crawlee/basic';
|
|
4
|
-
import type { ContextPipeline, CrawlingContext, EnqueueLinksOptions, GetUserDataFromRequest,
|
|
4
|
+
import type { ContextPipeline, CrawlingContext, EnqueueLinksOptions, GetUserDataFromRequest, RouterRoutes, StatisticsOptions, StatisticState } from '@crawlee/core';
|
|
5
5
|
import { RequestHandlerResult, Statistics } from '@crawlee/core';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Dictionary, Awaitable } from '@crawlee/types';
|
|
7
7
|
import { type CheerioRoot } from '@crawlee/utils';
|
|
8
8
|
import { type Cheerio } from 'cheerio';
|
|
9
9
|
import type { AnyNode } from 'domhandler';
|
|
10
10
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
11
11
|
import type { Page } from 'playwright';
|
|
12
|
-
import type { SetRequired } from 'type-fest';
|
|
13
12
|
import type { PlaywrightCrawlingContext, PlaywrightGotoOptions } from './playwright-crawler.js';
|
|
14
13
|
import { RenderingTypePredictor } from './utils/rendering-type-prediction.js';
|
|
15
14
|
interface AdaptivePlaywrightCrawlerStatisticState extends StatisticState {
|
|
@@ -21,7 +20,7 @@ declare class AdaptivePlaywrightCrawlerStatistics extends Statistics {
|
|
|
21
20
|
state: AdaptivePlaywrightCrawlerStatisticState;
|
|
22
21
|
constructor(options?: StatisticsOptions);
|
|
23
22
|
reset(): void;
|
|
24
|
-
protected
|
|
23
|
+
protected maybeLoadStatistics(): Promise<void>;
|
|
25
24
|
trackHttpOnlyRequestHandlerRun(): void;
|
|
26
25
|
trackBrowserRequestHandlerRun(): void;
|
|
27
26
|
trackRenderingTypeMisprediction(): void;
|
|
@@ -37,10 +36,15 @@ export interface AdaptivePlaywrightCrawlerContext<UserData extends Dictionary =
|
|
|
37
36
|
*/
|
|
38
37
|
page: Page;
|
|
39
38
|
/**
|
|
40
|
-
* Wait for an element matching the selector to appear and return a Cheerio object of matched
|
|
39
|
+
* Wait for an element matching the selector to appear and return a Cheerio object of the first matched element.
|
|
41
40
|
* Timeout defaults to 5s.
|
|
42
41
|
*/
|
|
43
42
|
querySelector(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
43
|
+
/**
|
|
44
|
+
* Wait for an element matching the selector to appear and return a Cheerio object of all matched elements.
|
|
45
|
+
* Timeout defaults to 5s.
|
|
46
|
+
*/
|
|
47
|
+
querySelectorAll(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
44
48
|
/**
|
|
45
49
|
* Wait for an element matching the selector to appear.
|
|
46
50
|
* Timeout defaults to 5s.
|
|
@@ -130,6 +134,8 @@ export interface AdaptivePlaywrightCrawlerOptions<ExtendedContext extends Adapti
|
|
|
130
134
|
* If it returns 'inconclusive', the detection result won't be used.
|
|
131
135
|
* If no result comparator is specified, but there is a `resultChecker`, any site where the `resultChecker` returns true is considered static.
|
|
132
136
|
* If neither `resultComparator` nor `resultChecker` are specified, a deep comparison of returned dataset items is used as a default.
|
|
137
|
+
*
|
|
138
|
+
* For a stricter, ready-made comparator that also takes enqueued requests and key-value store changes into account, see {@link fullResultComparator}.
|
|
133
139
|
*/
|
|
134
140
|
resultComparator?: (resultA: RequestHandlerResult, resultB: RequestHandlerResult) => boolean | 'equal' | 'different' | 'inconclusive';
|
|
135
141
|
/**
|
|
@@ -191,6 +197,7 @@ export declare class AdaptivePlaywrightCrawler<ExtendedContext extends AdaptiveP
|
|
|
191
197
|
readonly response: Response;
|
|
192
198
|
readonly page: Page;
|
|
193
199
|
readonly querySelector: AdaptivePlaywrightCrawlerContext["querySelector"];
|
|
200
|
+
readonly querySelectorAll: AdaptivePlaywrightCrawlerContext["querySelectorAll"];
|
|
194
201
|
readonly waitForSelector: AdaptivePlaywrightCrawlerContext["waitForSelector"];
|
|
195
202
|
readonly parseWithCheerio: AdaptivePlaywrightCrawlerContext["parseWithCheerio"];
|
|
196
203
|
}>;
|
|
@@ -198,19 +205,43 @@ export declare class AdaptivePlaywrightCrawler<ExtendedContext extends AdaptiveP
|
|
|
198
205
|
private adaptPlaywrightContext;
|
|
199
206
|
private crawlOne;
|
|
200
207
|
protected runRequestHandler(crawlingContext: CrawlingContext): Promise<void>;
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
private commitResult;
|
|
209
|
+
private allowStorageAccess;
|
|
203
210
|
/**
|
|
204
211
|
* Reading the pending request count queries the underlying request manager, which counts as storage access.
|
|
205
212
|
* Since this is internal crawler bookkeeping used to compute the `enqueueLinks` limit (not user-initiated storage
|
|
206
213
|
* access), it must be allowed even while a request handler runs inside the storage-access guard.
|
|
207
214
|
*/
|
|
208
215
|
protected getPendingRequestCountApproximation(): Promise<number>;
|
|
209
|
-
|
|
216
|
+
private enqueueLinks;
|
|
210
217
|
private createLogProxy;
|
|
211
218
|
teardown(): Promise<void>;
|
|
212
219
|
}
|
|
213
220
|
export declare function createAdaptivePlaywrightRouter<Context extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
|
|
214
221
|
export declare function createAdaptivePlaywrightRouter<Context extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
|
|
215
222
|
export declare function createAdaptivePlaywrightRouter<Context extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
|
|
223
|
+
/**
|
|
224
|
+
* An opt-in {@link AdaptivePlaywrightCrawlerOptions.resultComparator|`resultComparator`} that considers two
|
|
225
|
+
* request handler results equal only if *all* of their observable effects match - the pushed dataset items, the
|
|
226
|
+
* enqueued requests, and the key-value store changes. This is stricter than the default comparator, which only
|
|
227
|
+
* compares dataset items.
|
|
228
|
+
*
|
|
229
|
+
* **Beware:** enqueued URLs are compared exactly. The same page rendered in a browser and via plain HTTP often
|
|
230
|
+
* yields links that differ only in tracking query parameters, for example:
|
|
231
|
+
* - `https://sdk.apify.com/docs/guides/getting-started`
|
|
232
|
+
* - `https://sdk.apify.com/docs/guides/getting-started?__hsfp=1136113150&__hssc=7591405.1.173549427712`
|
|
233
|
+
*
|
|
234
|
+
* Such links are treated as *different*, which will make the crawler favor browser rendering for those pages.
|
|
235
|
+
*
|
|
236
|
+
* **Example usage:**
|
|
237
|
+
* ```ts
|
|
238
|
+
* const crawler = new AdaptivePlaywrightCrawler({
|
|
239
|
+
* resultComparator: fullResultComparator,
|
|
240
|
+
* async requestHandler({ pushData, enqueueLinks }) {
|
|
241
|
+
* // ...
|
|
242
|
+
* },
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export declare function fullResultComparator(resultA: RequestHandlerResult, resultB: RequestHandlerResult): boolean;
|
|
216
247
|
export {};
|
|
@@ -19,8 +19,8 @@ class AdaptivePlaywrightCrawlerStatistics extends Statistics {
|
|
|
19
19
|
this.state.browserRequestHandlerRuns = 0;
|
|
20
20
|
this.state.renderingTypeMispredictions = 0;
|
|
21
21
|
}
|
|
22
|
-
async
|
|
23
|
-
await super.
|
|
22
|
+
async maybeLoadStatistics() {
|
|
23
|
+
await super.maybeLoadStatistics();
|
|
24
24
|
const savedState = await this.keyValueStore?.getValue(this.persistStateKey);
|
|
25
25
|
if (!savedState) {
|
|
26
26
|
return;
|
|
@@ -183,6 +183,9 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
183
183
|
get querySelector() {
|
|
184
184
|
throw new Error(errorMessage('querySelector'));
|
|
185
185
|
},
|
|
186
|
+
get querySelectorAll() {
|
|
187
|
+
throw new Error(errorMessage('querySelectorAll'));
|
|
188
|
+
},
|
|
186
189
|
get waitForSelector() {
|
|
187
190
|
throw new Error(errorMessage('waitForSelector'));
|
|
188
191
|
},
|
|
@@ -203,6 +206,9 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
203
206
|
throw new Error('Page object was used in HTTP-only request handler');
|
|
204
207
|
},
|
|
205
208
|
async querySelector(selector) {
|
|
209
|
+
return cheerioContext.$(selector).first();
|
|
210
|
+
},
|
|
211
|
+
async querySelectorAll(selector) {
|
|
206
212
|
return cheerioContext.$(selector);
|
|
207
213
|
},
|
|
208
214
|
enqueueLinks: async (options = {}) => {
|
|
@@ -226,6 +232,12 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
226
232
|
statusText: originalResponse.statusText(),
|
|
227
233
|
}),
|
|
228
234
|
async querySelector(selector, timeoutMs = 5000) {
|
|
235
|
+
const locator = playwrightContext.page.locator(selector).first();
|
|
236
|
+
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
237
|
+
const $ = await playwrightContext.parseWithCheerio();
|
|
238
|
+
return $(selector).first();
|
|
239
|
+
},
|
|
240
|
+
async querySelectorAll(selector, timeoutMs = 5000) {
|
|
229
241
|
const locator = playwrightContext.page.locator(selector).first();
|
|
230
242
|
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
231
243
|
const $ = await playwrightContext.parseWithCheerio();
|
|
@@ -449,3 +461,32 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
449
461
|
export function createAdaptivePlaywrightRouter(routesOrSchemas) {
|
|
450
462
|
return Router.create(routesOrSchemas);
|
|
451
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* An opt-in {@link AdaptivePlaywrightCrawlerOptions.resultComparator|`resultComparator`} that considers two
|
|
466
|
+
* request handler results equal only if *all* of their observable effects match - the pushed dataset items, the
|
|
467
|
+
* enqueued requests, and the key-value store changes. This is stricter than the default comparator, which only
|
|
468
|
+
* compares dataset items.
|
|
469
|
+
*
|
|
470
|
+
* **Beware:** enqueued URLs are compared exactly. The same page rendered in a browser and via plain HTTP often
|
|
471
|
+
* yields links that differ only in tracking query parameters, for example:
|
|
472
|
+
* - `https://sdk.apify.com/docs/guides/getting-started`
|
|
473
|
+
* - `https://sdk.apify.com/docs/guides/getting-started?__hsfp=1136113150&__hssc=7591405.1.173549427712`
|
|
474
|
+
*
|
|
475
|
+
* Such links are treated as *different*, which will make the crawler favor browser rendering for those pages.
|
|
476
|
+
*
|
|
477
|
+
* **Example usage:**
|
|
478
|
+
* ```ts
|
|
479
|
+
* const crawler = new AdaptivePlaywrightCrawler({
|
|
480
|
+
* resultComparator: fullResultComparator,
|
|
481
|
+
* async requestHandler({ pushData, enqueueLinks }) {
|
|
482
|
+
* // ...
|
|
483
|
+
* },
|
|
484
|
+
* });
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
export function fullResultComparator(resultA, resultB) {
|
|
488
|
+
return (isDeepStrictEqual(resultA.datasetItems, resultB.datasetItems) &&
|
|
489
|
+
isDeepStrictEqual(resultA.enqueuedUrls, resultB.enqueuedUrls) &&
|
|
490
|
+
isDeepStrictEqual(resultA.enqueuedUrlLists, resultB.enqueuedUrlLists) &&
|
|
491
|
+
isDeepStrictEqual(resultA.keyValueStoreChanges, resultB.keyValueStoreChanges));
|
|
492
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { RecoverableStatePersistenceOptions, Request } from '@crawlee/core';
|
|
2
2
|
export type RenderingType = 'clientOnly' | 'static';
|
|
3
|
-
type URLComponents = string[];
|
|
4
|
-
type FeatureVector = [staticResultsSimilarity: number, clientOnlyResultsSimilarity: number];
|
|
5
3
|
export interface RenderingTypePredictorOptions {
|
|
6
4
|
/** A number between 0 and 1 that determines the desired ratio of rendering type detections */
|
|
7
5
|
detectionRatio: number;
|
|
@@ -32,7 +30,6 @@ export declare class RenderingTypePredictor {
|
|
|
32
30
|
*/
|
|
33
31
|
storeResult(requests: Request | Request[], renderingType: RenderingType): void;
|
|
34
32
|
private resultCount;
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
private calculateFeatureVector;
|
|
34
|
+
private retrain;
|
|
37
35
|
}
|
|
38
|
-
export {};
|
|
@@ -10,10 +10,17 @@ const calculateUrlSimilarity = (a, b) => {
|
|
|
10
10
|
if (a[0] !== b[0]) {
|
|
11
11
|
return 0;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const maxLength = Math.max(a.length, b.length);
|
|
14
|
+
// Only the hostname is present (no path components to compare) - the hosts already match.
|
|
15
|
+
if (maxLength <= 1) {
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
for (let i = 1; i < maxLength; i++) {
|
|
14
19
|
values.push(stringComparison.jaroWinkler.similarity(a[i] ?? '', b[i] ?? '') > 0.8 ? 1 : 0);
|
|
15
20
|
}
|
|
16
|
-
|
|
21
|
+
// The first component (index 0, the hostname) is excluded from the comparison above,
|
|
22
|
+
// so it must also be excluded from the denominator of the weighted average.
|
|
23
|
+
return sum(values) / (maxLength - 1);
|
|
17
24
|
};
|
|
18
25
|
const sum = (values) => values.reduce((acc, value) => acc + value);
|
|
19
26
|
const mean = (values) => (values.length > 0 ? sum(values) / values.length : undefined);
|
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.81",
|
|
4
4
|
"description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/datastructures": "^2.0.3",
|
|
51
51
|
"@apify/timeout": "^0.3.2",
|
|
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.81",
|
|
53
|
+
"@crawlee/browser": "4.0.0-beta.81",
|
|
54
|
+
"@crawlee/browser-pool": "4.0.0-beta.81",
|
|
55
|
+
"@crawlee/cheerio": "4.0.0-beta.81",
|
|
56
|
+
"@crawlee/core": "4.0.0-beta.81",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.81",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.81",
|
|
59
59
|
"cheerio": "^1.0.0",
|
|
60
60
|
"idcac-playwright": "^0.1.3",
|
|
61
61
|
"jquery": "^3.7.1",
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "80dc6b4fc82237e63a51a71153809ec8dfd0cc50"
|
|
89
89
|
}
|