@crawlee/playwright 4.0.0-beta.231 → 4.0.0-beta.233
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 +15 -9
- package/internals/adaptive-playwright-crawler.js +39 -36
- package/internals/playwright-crawler.js +18 -6
- package/internals/utils/playwright-utils.d.ts +11 -1
- package/internals/utils/rendering-type-prediction.d.ts +2 -1
- package/package.json +11 -9
|
@@ -43,16 +43,17 @@ export interface AdaptivePlaywrightCrawlerContext<UserData extends Dictionary =
|
|
|
43
43
|
* Playwright Page object. If accessed in HTTP-only rendering, this will throw an error and make the AdaptivePlaywrightCrawlerContext retry the request in a browser.
|
|
44
44
|
*/
|
|
45
45
|
page: Page;
|
|
46
|
-
/**
|
|
47
|
-
* Wait for an element matching the selector to appear and return a Cheerio object of the first matched element.
|
|
48
|
-
* Timeout defaults to 5s.
|
|
49
|
-
*/
|
|
50
|
-
querySelector(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
51
46
|
/**
|
|
52
47
|
* Wait for an element matching the selector to appear and return a Cheerio object of all matched elements.
|
|
48
|
+
* Throws when nothing matches - in the HTTP-only run, that makes the crawler retry the request in a browser.
|
|
53
49
|
* Timeout defaults to 5s.
|
|
54
50
|
*/
|
|
55
51
|
querySelectorAll(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
52
|
+
/**
|
|
53
|
+
* Like {@link AdaptivePlaywrightCrawlerContext.querySelectorAll|`querySelectorAll`}, but returns only the
|
|
54
|
+
* first match.
|
|
55
|
+
*/
|
|
56
|
+
querySelectorOne(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
56
57
|
/**
|
|
57
58
|
* Wait for an element matching the selector to appear.
|
|
58
59
|
* Timeout defaults to 5s.
|
|
@@ -91,6 +92,11 @@ type AdaptiveHook<ContextExtension = Dictionary<never>> = BrowserHook<AdaptiveHo
|
|
|
91
92
|
type AdaptivePostNavigationHook<ContextExtension = Dictionary<never>> = BrowserHook<Omit<AdaptiveHookContext, 'request'> & {
|
|
92
93
|
request: LoadedRequest<CrawlingRequest>;
|
|
93
94
|
}, ContextExtension>;
|
|
95
|
+
/** Passed to {@link AdaptivePlaywrightCrawlerOptions.resultChecker} and `resultComparator` alongside the results. */
|
|
96
|
+
export interface AdaptiveResultContext {
|
|
97
|
+
/** The request whose handler produced the results. */
|
|
98
|
+
request: CrawlingRequest;
|
|
99
|
+
}
|
|
94
100
|
export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends AdaptivePlaywrightCrawlerContext = AdaptivePlaywrightCrawlerContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<AdaptivePlaywrightCrawlerContext['request']>>, StatisticStateExtension extends AdaptivePlaywrightCrawlerStatisticState = AdaptivePlaywrightCrawlerStatisticState> extends Omit<BasicCrawlerOptions<AdaptivePlaywrightCrawlerContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension>, 'preNavigationHooks' | 'postNavigationHooks' | 'contextPipelineBuilder'>, Pick<PlaywrightCrawlerOptions, 'launchContext' | 'headless' | 'browserPool' | 'remoteBrowser'> {
|
|
95
101
|
/**
|
|
96
102
|
* Async functions that are sequentially evaluated before the navigation. Good for setting additional cookies.
|
|
@@ -121,7 +127,7 @@ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<
|
|
|
121
127
|
* If it returns false, the request is retried in a browser.
|
|
122
128
|
* If no callback is specified, every result is considered valid.
|
|
123
129
|
*/
|
|
124
|
-
resultChecker?: (result: StorageTransactionView) => boolean;
|
|
130
|
+
resultChecker?: (result: StorageTransactionView, context: AdaptiveResultContext) => boolean;
|
|
125
131
|
/**
|
|
126
132
|
* An optional callback that decides whether an error thrown during the plain HTTP request handler
|
|
127
133
|
* should be propagated (instead of falling back to browser navigation).
|
|
@@ -144,7 +150,7 @@ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<
|
|
|
144
150
|
*
|
|
145
151
|
* For a stricter, ready-made comparator that also takes enqueued requests and key-value store changes into account, see {@link fullResultComparator}.
|
|
146
152
|
*/
|
|
147
|
-
resultComparator?: (resultA: StorageTransactionView, resultB: StorageTransactionView) => boolean | 'equal' | 'different' | 'inconclusive';
|
|
153
|
+
resultComparator?: (resultA: StorageTransactionView, resultB: StorageTransactionView, context: AdaptiveResultContext) => boolean | 'equal' | 'different' | 'inconclusive';
|
|
148
154
|
/**
|
|
149
155
|
* A custom rendering type predictor. A predictor passed here is borrowed - the crawler never drives its
|
|
150
156
|
* lifecycle, so set it up yourself (the built-in {@link RenderingTypePredictor} needs `initialize()`).
|
|
@@ -160,9 +166,9 @@ export interface AdaptivePlaywrightCrawlerOptions<ContextExtension = Dictionary<
|
|
|
160
166
|
* ```javascript
|
|
161
167
|
* const crawler = new AdaptivePlaywrightCrawler({
|
|
162
168
|
* renderingTypeDetectionRatio: 0.1,
|
|
163
|
-
* async requestHandler({
|
|
169
|
+
* async requestHandler({ querySelectorAll, pushData, enqueueLinks, request, log }) {
|
|
164
170
|
* // This function is called to extract data from a single web page
|
|
165
|
-
* const $prices = await
|
|
171
|
+
* const $prices = await querySelectorAll('span.price')
|
|
166
172
|
*
|
|
167
173
|
* await pushData({
|
|
168
174
|
* url: request.url,
|
|
@@ -3,6 +3,7 @@ import { isDeepStrictEqual } from 'node:util';
|
|
|
3
3
|
import { BasicCrawler, ContextPipeline, ContextPipelineInitializationError, ContextPipelineInterruptedError, RequestHandlerError, resolveBaseUrlForEnqueueLinksFiltering, Router, Statistics, } from '@crawlee/basic';
|
|
4
4
|
import { extractUrlsFromPage } from '@crawlee/browser';
|
|
5
5
|
import { CheerioCrawler } from '@crawlee/cheerio';
|
|
6
|
+
import { ResponseWithUrl } from '@crawlee/http-client';
|
|
6
7
|
import { createStorageTransaction, EnqueueStrategy, OwnedOrInjected } from '@crawlee/core';
|
|
7
8
|
import { extractUrlsFromCheerio, parseArgument } from '@crawlee/utils/internal';
|
|
8
9
|
import { z } from 'zod';
|
|
@@ -43,9 +44,9 @@ const proxyLogMethods = [
|
|
|
43
44
|
* ```javascript
|
|
44
45
|
* const crawler = new AdaptivePlaywrightCrawler({
|
|
45
46
|
* renderingTypeDetectionRatio: 0.1,
|
|
46
|
-
* async requestHandler({
|
|
47
|
+
* async requestHandler({ querySelectorAll, pushData, enqueueLinks, request, log }) {
|
|
47
48
|
* // This function is called to extract data from a single web page
|
|
48
|
-
* const $prices = await
|
|
49
|
+
* const $prices = await querySelectorAll('span.price')
|
|
49
50
|
*
|
|
50
51
|
* await pushData({
|
|
51
52
|
* url: request.url,
|
|
@@ -146,7 +147,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
146
147
|
this.#resultComparator = resultComparator;
|
|
147
148
|
}
|
|
148
149
|
else if (resultChecker !== undefined) {
|
|
149
|
-
this.#resultComparator = (resultA, resultB) => this.#resultChecker(resultA) && this.#resultChecker(resultB);
|
|
150
|
+
this.#resultComparator = (resultA, resultB, context) => this.#resultChecker(resultA, context) && this.#resultChecker(resultB, context);
|
|
150
151
|
}
|
|
151
152
|
else {
|
|
152
153
|
this.#resultComparator = (resultA, resultB) => {
|
|
@@ -208,12 +209,12 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
208
209
|
get page() {
|
|
209
210
|
throw new Error(errorMessage('page'));
|
|
210
211
|
},
|
|
211
|
-
get querySelector() {
|
|
212
|
-
throw new Error(errorMessage('querySelector'));
|
|
213
|
-
},
|
|
214
212
|
get querySelectorAll() {
|
|
215
213
|
throw new Error(errorMessage('querySelectorAll'));
|
|
216
214
|
},
|
|
215
|
+
get querySelectorOne() {
|
|
216
|
+
throw new Error(errorMessage('querySelectorOne'));
|
|
217
|
+
},
|
|
217
218
|
get waitForSelector() {
|
|
218
219
|
throw new Error(errorMessage('waitForSelector'));
|
|
219
220
|
},
|
|
@@ -230,45 +231,34 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
230
231
|
get page() {
|
|
231
232
|
throw new Error('Page object was used in HTTP-only request handler');
|
|
232
233
|
},
|
|
233
|
-
async querySelector(selector) {
|
|
234
|
-
return cheerioContext.$(selector).first();
|
|
235
|
-
},
|
|
236
|
-
async querySelectorAll(selector) {
|
|
237
|
-
return cheerioContext.$(selector);
|
|
238
|
-
},
|
|
239
234
|
enqueueLinks: async (options = {}) => {
|
|
240
235
|
const urls = extractUrlsFromCheerio(cheerioContext.$, options.selector, options.baseUrl ?? cheerioContext.request.loadedUrl);
|
|
241
236
|
return (await this.#enqueueLinks(urls, options, cheerioContext.request));
|
|
242
237
|
},
|
|
243
|
-
|
|
238
|
+
// The HTTP crawler has already consumed the body; hand out a fresh copy, like the browser side does.
|
|
239
|
+
// HTML bodies are already decoded to UTF-8, so `arrayBuffer()` may disagree with the original headers.
|
|
240
|
+
response: new ResponseWithUrl(cheerioContext.body, cheerioContext.response),
|
|
244
241
|
};
|
|
245
242
|
}
|
|
246
243
|
async #adaptPlaywrightContext(playwrightContext) {
|
|
247
244
|
// Capture the original response to avoid infinite recursion when the getter is copied to the context
|
|
248
245
|
const originalResponse = playwrightContext.response;
|
|
249
246
|
return {
|
|
250
|
-
response: new
|
|
247
|
+
response: new ResponseWithUrl(Uint8Array.from(await originalResponse.body()), {
|
|
251
248
|
headers: originalResponse.headers(),
|
|
252
249
|
status: originalResponse.status(),
|
|
253
250
|
statusText: originalResponse.statusText(),
|
|
251
|
+
url: originalResponse.url(),
|
|
254
252
|
}),
|
|
255
|
-
async querySelector(selector, timeoutMs = 5000) {
|
|
256
|
-
const locator = playwrightContext.page.locator(selector).first();
|
|
257
|
-
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
258
|
-
const $ = await playwrightContext.parseWithCheerio();
|
|
259
|
-
return $(selector).first();
|
|
260
|
-
},
|
|
261
|
-
async querySelectorAll(selector, timeoutMs = 5000) {
|
|
262
|
-
const locator = playwrightContext.page.locator(selector).first();
|
|
263
|
-
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
264
|
-
const $ = await playwrightContext.parseWithCheerio();
|
|
265
|
-
return $(selector);
|
|
266
|
-
},
|
|
267
253
|
enqueueLinks: async (options = {}, timeoutMs = 5000) => {
|
|
268
254
|
// TODO consider using `context.parseWithCheerio` to make this universal and avoid code duplication
|
|
269
255
|
const selector = options.selector ?? 'a';
|
|
270
256
|
const locator = playwrightContext.page.locator(selector).first();
|
|
271
|
-
|
|
257
|
+
// Wait for client-rendered links, but like the HTTP side, enqueue nothing when there are none.
|
|
258
|
+
await locator.waitFor({ timeout: timeoutMs, state: 'attached' }).catch((error) => {
|
|
259
|
+
if (error.name !== 'TimeoutError')
|
|
260
|
+
throw error;
|
|
261
|
+
});
|
|
272
262
|
const urls = await extractUrlsFromPage(playwrightContext.page, selector, options.baseUrl ?? playwrightContext.request.loadedUrl);
|
|
273
263
|
return (await this.#enqueueLinks(urls, options, playwrightContext.request));
|
|
274
264
|
},
|
|
@@ -337,11 +327,14 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
337
327
|
// `finally` below, not earlier: the comparators read the journals after `crawlOne` returns.
|
|
338
328
|
const transactions = [];
|
|
339
329
|
try {
|
|
330
|
+
// A result the `resultChecker` rejected is as good as a detection saying the page needs a browser - but
|
|
331
|
+
// only once the browser run has succeeded. A thrown error says nothing about rendering.
|
|
332
|
+
let httpResultRejected = false;
|
|
340
333
|
if (renderingTypePrediction.renderingType === 'static' && !shouldDetectRenderingType) {
|
|
341
334
|
crawlingContext.log.debug(`Running HTTP-only request handler for ${crawlingContext.request.url}`);
|
|
342
335
|
this.statistics.state.httpOnlyRequestHandlerRuns++;
|
|
343
336
|
const plainHTTPRun = await this.#crawlOne('static', crawlingContext, crawlingContext.useState, transactions);
|
|
344
|
-
if (plainHTTPRun.ok && this.#resultChecker(plainHTTPRun.result)) {
|
|
337
|
+
if (plainHTTPRun.ok && this.#resultChecker(plainHTTPRun.result, { request: crawlingContext.request })) {
|
|
345
338
|
crawlingContext.log.debug(`HTTP-only request handler succeeded for ${crawlingContext.request.url}`);
|
|
346
339
|
plainHTTPRun.logs?.forEach(([log, method, ...args]) => log[method](...args));
|
|
347
340
|
await plainHTTPRun.result.commit();
|
|
@@ -362,6 +355,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
362
355
|
else {
|
|
363
356
|
crawlingContext.log.warning(`HTTP-only request handler returned a suspicious result for ${crawlingContext.request.url}`);
|
|
364
357
|
this.statistics.state.renderingTypeMispredictions++;
|
|
358
|
+
httpResultRejected = true;
|
|
365
359
|
}
|
|
366
360
|
}
|
|
367
361
|
crawlingContext.log.debug(`Running browser request handler for ${crawlingContext.request.url}`);
|
|
@@ -392,6 +386,10 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
392
386
|
}
|
|
393
387
|
browserRun.logs?.forEach(([log, method, ...args]) => log[method](...args));
|
|
394
388
|
await browserRun.result.commit();
|
|
389
|
+
// Past the shutdown fence, `teardown()` has already drained - and possibly torn down the predictor.
|
|
390
|
+
if (httpResultRejected && !this.#shutDown) {
|
|
391
|
+
this.#storeRenderingType(crawlingContext.request, 'clientOnly');
|
|
392
|
+
}
|
|
395
393
|
if (shouldDetectRenderingType) {
|
|
396
394
|
const detectionPromise = (async () => {
|
|
397
395
|
crawlingContext.log.debug(`Detecting rendering type for ${crawlingContext.request.url}`);
|
|
@@ -402,7 +400,9 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
402
400
|
if (!plainHTTPRun.ok) {
|
|
403
401
|
return 'clientOnly';
|
|
404
402
|
}
|
|
405
|
-
const comparisonResult = this.#resultComparator(plainHTTPRun.result, browserRun.result
|
|
403
|
+
const comparisonResult = this.#resultComparator(plainHTTPRun.result, browserRun.result, {
|
|
404
|
+
request: crawlingContext.request,
|
|
405
|
+
});
|
|
406
406
|
if (comparisonResult === true || comparisonResult === 'equal') {
|
|
407
407
|
return 'static';
|
|
408
408
|
}
|
|
@@ -413,13 +413,7 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
413
413
|
})();
|
|
414
414
|
crawlingContext.log.debug(`Detected rendering type ${detectionResult} for ${crawlingContext.request.url}`);
|
|
415
415
|
if (detectionResult !== undefined) {
|
|
416
|
-
|
|
417
|
-
// batching its writes, and the drain below catches whatever is still pending.
|
|
418
|
-
const stored = this.#renderingTypePredictor.value.storeResult(crawlingContext.request, detectionResult);
|
|
419
|
-
if (stored !== undefined) {
|
|
420
|
-
// Nothing downstream awaits this, so a failed write would otherwise be silent.
|
|
421
|
-
void this.#trackDetection(Promise.resolve(stored).catch((error) => this.log.exception(error, `Failed to store the rendering type detection result for ${crawlingContext.request.url}`)));
|
|
422
|
-
}
|
|
416
|
+
this.#storeRenderingType(crawlingContext.request, detectionResult);
|
|
423
417
|
}
|
|
424
418
|
})();
|
|
425
419
|
await this.#trackDetection(detectionPromise);
|
|
@@ -433,6 +427,15 @@ export class AdaptivePlaywrightCrawler extends BasicCrawler {
|
|
|
433
427
|
}
|
|
434
428
|
}
|
|
435
429
|
}
|
|
430
|
+
#storeRenderingType(request, renderingType) {
|
|
431
|
+
// Deliberately not awaited: a predictor that persists asynchronously gets to keep batching its writes,
|
|
432
|
+
// and `drainRenderingDetections()` catches whatever is still pending.
|
|
433
|
+
const stored = this.#renderingTypePredictor.value.storeResult(request, renderingType);
|
|
434
|
+
if (stored !== undefined) {
|
|
435
|
+
// Nothing downstream awaits this, so a failed write would otherwise be silent.
|
|
436
|
+
void this.#trackDetection(Promise.resolve(stored).catch((error) => this.log.exception(error, `Failed to store the rendering type detection result for ${request.url}`)));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
436
439
|
async #enqueueLinks(urls, options, request) {
|
|
437
440
|
const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
|
|
438
441
|
enqueueStrategy: options?.strategy,
|
|
@@ -114,6 +114,21 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
114
114
|
const locator = context.page.locator(selector).first();
|
|
115
115
|
await locator.waitFor({ timeout: timeoutMs, state: 'attached' });
|
|
116
116
|
};
|
|
117
|
+
const parseWithCheerioFn = async (selector, timeoutMs = 5_000) => {
|
|
118
|
+
if (selector) {
|
|
119
|
+
await waitForSelector(selector, timeoutMs);
|
|
120
|
+
}
|
|
121
|
+
return parseWithCheerio(context.page, this.ignoreShadowRoots, this.ignoreIframes);
|
|
122
|
+
};
|
|
123
|
+
const querySelectorAll = async (selector, timeoutMs) => {
|
|
124
|
+
const matches = (await parseWithCheerioFn(selector, timeoutMs))(selector);
|
|
125
|
+
// The browser located it but the Cheerio snapshot does not have it - a Playwright-only selector,
|
|
126
|
+
// or an element under a shadow root/iframe the snapshot was told to ignore.
|
|
127
|
+
if (matches.length === 0) {
|
|
128
|
+
throw new Error(`Selector '${selector}' not found in the page snapshot.`);
|
|
129
|
+
}
|
|
130
|
+
return matches;
|
|
131
|
+
};
|
|
117
132
|
const downloads = [];
|
|
118
133
|
context.page.on('download', (download) => downloads.push(download));
|
|
119
134
|
return {
|
|
@@ -128,12 +143,9 @@ export class PlaywrightCrawler extends BrowserCrawler {
|
|
|
128
143
|
},
|
|
129
144
|
blockRequests: async (options) => blockRequests(context.page, options),
|
|
130
145
|
waitForSelector,
|
|
131
|
-
parseWithCheerio:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
return parseWithCheerio(context.page, this.ignoreShadowRoots, this.ignoreIframes);
|
|
136
|
-
},
|
|
146
|
+
parseWithCheerio: parseWithCheerioFn,
|
|
147
|
+
querySelectorAll,
|
|
148
|
+
querySelectorOne: async (selector, timeoutMs) => (await querySelectorAll(selector, timeoutMs)).first(),
|
|
137
149
|
infiniteScroll: async (options) => infiniteScroll(context.page, options),
|
|
138
150
|
listDownloads: async () => downloads,
|
|
139
151
|
saveSnapshot: async (options) => saveSnapshot(context.page, {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { Configuration, type Request } from '@crawlee/browser';
|
|
21
21
|
import type { BatchAddRequestsResult, Dictionary } from '@crawlee/types';
|
|
22
|
-
import type { CheerioAPI } from 'cheerio';
|
|
22
|
+
import type { Cheerio, CheerioAPI } from 'cheerio';
|
|
23
|
+
import type { AnyNode } from 'domhandler';
|
|
23
24
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
24
25
|
import type { Download, Page, Response } from 'playwright';
|
|
25
26
|
import type { EnqueueLinksByClickingElementsOptions } from '../enqueue-links/click-elements.js';
|
|
@@ -420,6 +421,15 @@ export interface PlaywrightContextUtils {
|
|
|
420
421
|
* ```
|
|
421
422
|
*/
|
|
422
423
|
waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
|
|
424
|
+
/**
|
|
425
|
+
* Like {@link PlaywrightContextUtils.waitForSelector|`waitForSelector`}, but returns the matched elements
|
|
426
|
+
* as a Cheerio object. Throws when nothing matches.
|
|
427
|
+
*/
|
|
428
|
+
querySelectorAll(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
429
|
+
/**
|
|
430
|
+
* Like {@link PlaywrightContextUtils.querySelectorAll|`querySelectorAll`}, but returns only the first match.
|
|
431
|
+
*/
|
|
432
|
+
querySelectorOne(selector: string, timeoutMs?: number): Promise<Cheerio<AnyNode>>;
|
|
423
433
|
/**
|
|
424
434
|
* Returns Cheerio handle for `page.content()`, allowing to work with the data same way as with {@link CheerioCrawler}.
|
|
425
435
|
* When provided with the `selector` argument, it waits for it to be available first.
|
|
@@ -24,7 +24,8 @@ export interface IRenderingTypePredictor {
|
|
|
24
24
|
detectionProbabilityRecommendation: number;
|
|
25
25
|
}>;
|
|
26
26
|
/**
|
|
27
|
-
* Report a
|
|
27
|
+
* Report a rendering type established by a detection, or `clientOnly` when the `resultChecker` rejected an
|
|
28
|
+
* HTTP-only result, so that future predictions can take it into account.
|
|
28
29
|
*
|
|
29
30
|
* The crawler does not await this per detection - it collects the returned promises and drains them in
|
|
30
31
|
* `teardown()`, so an implementation that persists results asynchronously can keep batching its writes
|
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.233",
|
|
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.13.0"
|
|
@@ -49,14 +49,16 @@
|
|
|
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/
|
|
58
|
-
"@crawlee/
|
|
52
|
+
"@crawlee/basic": "4.0.0-beta.233",
|
|
53
|
+
"@crawlee/browser": "4.0.0-beta.233",
|
|
54
|
+
"@crawlee/browser-pool": "4.0.0-beta.233",
|
|
55
|
+
"@crawlee/cheerio": "4.0.0-beta.233",
|
|
56
|
+
"@crawlee/core": "4.0.0-beta.233",
|
|
57
|
+
"@crawlee/http-client": "4.0.0-beta.233",
|
|
58
|
+
"@crawlee/types": "4.0.0-beta.233",
|
|
59
|
+
"@crawlee/utils": "4.0.0-beta.233",
|
|
59
60
|
"cheerio": "^1.0.0",
|
|
61
|
+
"domhandler": "^6.0.0",
|
|
60
62
|
"jquery": "^3.7.1",
|
|
61
63
|
"ml-logistic-regression": "^2.0.0",
|
|
62
64
|
"ml-matrix": "^6.12.1",
|
|
@@ -79,5 +81,5 @@
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
},
|
|
82
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "3f074fb4fc8bf016aa1740cac0feac5ae84eca36"
|
|
83
85
|
}
|