@crawlee/basic 4.0.0-beta.79 → 4.0.0-beta.80
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/basic-crawler.d.ts +21 -8
- package/internals/basic-crawler.js +15 -91
- package/package.json +7 -7
|
@@ -367,20 +367,34 @@ export interface BasicCrawlerOptions<Context extends CrawlingContext = CrawlingC
|
|
|
367
367
|
* ```
|
|
368
368
|
* @category Crawlers
|
|
369
369
|
*/
|
|
370
|
+
/**
|
|
371
|
+
* Identifies a crawler instance for storage aliasing, `useState()` and status-message events.
|
|
372
|
+
*/
|
|
373
|
+
interface CrawlerIdentity {
|
|
374
|
+
/**
|
|
375
|
+
* 0-based instantiation order across all crawlers in the process.
|
|
376
|
+
* Note that the value can be subject to race conditions between different script invocations.
|
|
377
|
+
*/
|
|
378
|
+
readonly instanceIndex: number;
|
|
379
|
+
/** The user-supplied `id` option, or a fallback derived from `instanceIndex`. */
|
|
380
|
+
readonly id: string;
|
|
381
|
+
/** Whether `id` came from the user (as opposed to being derived from `instanceIndex`). */
|
|
382
|
+
readonly hasExplicitId: boolean;
|
|
383
|
+
}
|
|
370
384
|
export declare class BasicCrawler<Context extends CrawlingContext = CrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension> {
|
|
371
385
|
#private;
|
|
372
386
|
protected static readonly CRAWLEE_STATE_KEY = "CRAWLEE_STATE";
|
|
373
|
-
/**
|
|
374
|
-
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
375
|
-
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
376
|
-
*/
|
|
377
|
-
private static useStateCrawlerIds;
|
|
378
387
|
/**
|
|
379
388
|
* Tracks the number of crawler instances created. The first crawler uses the default
|
|
380
389
|
* request queue; subsequent ones get their own queue via a unique alias so they don't
|
|
381
390
|
* collide.
|
|
382
391
|
*/
|
|
383
392
|
private static instanceCount;
|
|
393
|
+
/**
|
|
394
|
+
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
395
|
+
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
396
|
+
*/
|
|
397
|
+
private static useStateAnonymousIndices;
|
|
384
398
|
/**
|
|
385
399
|
* A reference to the underlying {@link Statistics} class that collects and logs run statistics for requests.
|
|
386
400
|
*/
|
|
@@ -479,9 +493,7 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
479
493
|
private _closeEvents?;
|
|
480
494
|
private loggedPerRun;
|
|
481
495
|
private readonly robotsTxtFileCache;
|
|
482
|
-
|
|
483
|
-
private readonly hasExplicitId;
|
|
484
|
-
private readonly crawlerInstanceIndex;
|
|
496
|
+
protected readonly identity: CrawlerIdentity;
|
|
485
497
|
private readonly contextPipelineOptions;
|
|
486
498
|
protected static optionsShape: {
|
|
487
499
|
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
@@ -814,3 +826,4 @@ export interface CrawlerRunOptions extends CrawlerAddRequestsOptions {
|
|
|
814
826
|
*/
|
|
815
827
|
export declare function createBasicRouter<Context extends BasicCrawlingContext = BasicCrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
|
|
816
828
|
export declare function createBasicRouter<Context extends BasicCrawlingContext = BasicCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
|
|
829
|
+
export {};
|
|
@@ -37,90 +37,19 @@ class LazyDefaultHttpClient {
|
|
|
37
37
|
*/
|
|
38
38
|
const SAFE_MIGRATION_WAIT_MILLIS = 20000;
|
|
39
39
|
const deferredCleanupKey = Symbol('deferredCleanup');
|
|
40
|
-
/**
|
|
41
|
-
* Provides a simple framework for parallel crawling of web pages.
|
|
42
|
-
* The URLs to crawl are fed either from a static list of URLs
|
|
43
|
-
* or from a dynamic queue of URLs enabling recursive crawling of websites.
|
|
44
|
-
*
|
|
45
|
-
* `BasicCrawler` is a low-level tool that requires the user to implement the page
|
|
46
|
-
* download and data extraction functionality themselves.
|
|
47
|
-
* If we want a crawler that already facilitates this functionality,
|
|
48
|
-
* we should consider using {@link CheerioCrawler}, {@link PuppeteerCrawler} or {@link PlaywrightCrawler}.
|
|
49
|
-
*
|
|
50
|
-
* `BasicCrawler` invokes the user-provided {@link BasicCrawlerOptions.requestHandler|`requestHandler`}
|
|
51
|
-
* for each {@link Request} object, which represents a single URL to crawl.
|
|
52
|
-
* The {@link Request} objects are fed from the {@link IRequestManager|request manager} provided via the
|
|
53
|
-
* {@link BasicCrawlerOptions.requestManager|`requestManager`} constructor option (a {@link RequestQueue} is
|
|
54
|
-
* itself a request manager). If no `requestManager` is provided, the crawler opens the default {@link RequestQueue}
|
|
55
|
-
* either when the {@link BasicCrawler.addRequests|`crawler.addRequests()`} function is called, or if the `requests`
|
|
56
|
-
* parameter (representing the initial requests) of the {@link BasicCrawler.run|`crawler.run()`} function is provided.
|
|
57
|
-
*
|
|
58
|
-
* To read requests from a read-only source such as a {@link RequestList} or {@link SitemapRequestLoader} while
|
|
59
|
-
* still being able to enqueue new ones, combine the loader with a queue into a {@link RequestManagerTandem} using
|
|
60
|
-
* {@link IRequestLoader.toTandem|`requestLoader.toTandem()`} and pass the result as `requestManager`. The tandem
|
|
61
|
-
* first processes URLs from the loader and automatically enqueues them into the queue, ensuring a single URL is not
|
|
62
|
-
* crawled multiple times.
|
|
63
|
-
*
|
|
64
|
-
* > The legacy {@link BasicCrawlerOptions.requestList|`requestList`} and
|
|
65
|
-
* > {@link BasicCrawlerOptions.requestQueue|`requestQueue`} options are deprecated. They are still accepted and
|
|
66
|
-
* > folded into a single `requestManager` (combined into a tandem when both are given), but new code should use
|
|
67
|
-
* > `requestManager` directly.
|
|
68
|
-
*
|
|
69
|
-
* The crawler finishes if there are no more {@link Request} objects to crawl.
|
|
70
|
-
*
|
|
71
|
-
* New requests are only dispatched when there is enough free CPU and memory available,
|
|
72
|
-
* using the functionality provided by the {@link AutoscaledPool} class.
|
|
73
|
-
* All {@link AutoscaledPool} configuration options can be passed to the {@link BasicCrawlerOptions.autoscaledPoolOptions|`autoscaledPoolOptions`}
|
|
74
|
-
* parameter of the `BasicCrawler` constructor.
|
|
75
|
-
* For user convenience, the {@link AutoscaledPoolOptions.minConcurrency|`minConcurrency`} and
|
|
76
|
-
* {@link AutoscaledPoolOptions.maxConcurrency|`maxConcurrency`} options of the
|
|
77
|
-
* underlying {@link AutoscaledPool} constructor are available directly in the `BasicCrawler` constructor.
|
|
78
|
-
*
|
|
79
|
-
* **Example usage:**
|
|
80
|
-
*
|
|
81
|
-
* ```javascript
|
|
82
|
-
* import { BasicCrawler, Dataset } from 'crawlee';
|
|
83
|
-
*
|
|
84
|
-
* // Create a crawler instance
|
|
85
|
-
* const crawler = new BasicCrawler({
|
|
86
|
-
* async requestHandler({ request, sendRequest }) {
|
|
87
|
-
* // 'request' contains an instance of the Request class
|
|
88
|
-
* // Here we simply fetch the HTML of the page and store it to a dataset
|
|
89
|
-
* const { body } = await sendRequest({
|
|
90
|
-
* url: request.url,
|
|
91
|
-
* method: request.method,
|
|
92
|
-
* body: request.payload,
|
|
93
|
-
* headers: request.headers,
|
|
94
|
-
* });
|
|
95
|
-
*
|
|
96
|
-
* await Dataset.pushData({
|
|
97
|
-
* url: request.url,
|
|
98
|
-
* html: body,
|
|
99
|
-
* })
|
|
100
|
-
* },
|
|
101
|
-
* });
|
|
102
|
-
*
|
|
103
|
-
* // Enqueue the initial requests and run the crawler
|
|
104
|
-
* await crawler.run([
|
|
105
|
-
* 'http://www.example.com/page-1',
|
|
106
|
-
* 'http://www.example.com/page-2',
|
|
107
|
-
* ]);
|
|
108
|
-
* ```
|
|
109
|
-
* @category Crawlers
|
|
110
|
-
*/
|
|
111
40
|
export class BasicCrawler {
|
|
112
41
|
static CRAWLEE_STATE_KEY = 'CRAWLEE_STATE';
|
|
113
|
-
/**
|
|
114
|
-
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
115
|
-
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
116
|
-
*/
|
|
117
|
-
static useStateCrawlerIds = new Set();
|
|
118
42
|
/**
|
|
119
43
|
* Tracks the number of crawler instances created. The first crawler uses the default
|
|
120
44
|
* request queue; subsequent ones get their own queue via a unique alias so they don't
|
|
121
45
|
* collide.
|
|
122
46
|
*/
|
|
123
47
|
static instanceCount = 0;
|
|
48
|
+
/**
|
|
49
|
+
* Tracks crawler instances that accessed shared state without having an explicit id.
|
|
50
|
+
* Used to detect and warn about multiple crawlers sharing the same state.
|
|
51
|
+
*/
|
|
52
|
+
static useStateAnonymousIndices = new Set();
|
|
124
53
|
/**
|
|
125
54
|
* A reference to the underlying {@link Statistics} class that collects and logs run statistics for requests.
|
|
126
55
|
*/
|
|
@@ -233,9 +162,7 @@ export class BasicCrawler {
|
|
|
233
162
|
_closeEvents;
|
|
234
163
|
loggedPerRun = new Set();
|
|
235
164
|
robotsTxtFileCache;
|
|
236
|
-
|
|
237
|
-
hasExplicitId;
|
|
238
|
-
crawlerInstanceIndex;
|
|
165
|
+
identity;
|
|
239
166
|
contextPipelineOptions;
|
|
240
167
|
static optionsShape = {
|
|
241
168
|
contextPipelineBuilder: ow.optional.object,
|
|
@@ -313,11 +240,8 @@ export class BasicCrawler {
|
|
|
313
240
|
this.#log = serviceLocator.getLogger().child({ prefix: this.constructor.name });
|
|
314
241
|
// Initialize the Configuration instance to avoid lazy loading in the components
|
|
315
242
|
serviceLocator.getConfiguration();
|
|
316
|
-
|
|
317
|
-
this.
|
|
318
|
-
// Store the user-provided ID, or generate a unique one for tracking purposes (not for state key)
|
|
319
|
-
this.crawlerId = id ?? cryptoRandomObjectId();
|
|
320
|
-
this.crawlerInstanceIndex = BasicCrawler.instanceCount++;
|
|
243
|
+
const instanceIndex = BasicCrawler.instanceCount++;
|
|
244
|
+
this.identity = { instanceIndex, hasExplicitId: id !== undefined, id: id ?? String(instanceIndex) };
|
|
321
245
|
if (requestManager !== undefined) {
|
|
322
246
|
if (requestList !== undefined || requestQueue !== undefined) {
|
|
323
247
|
throw new Error('The `requestManager` option cannot be used in conjunction with `requestList` and/or `requestQueue`');
|
|
@@ -369,7 +293,7 @@ export class BasicCrawler {
|
|
|
369
293
|
this.stats = new Statistics({
|
|
370
294
|
logMessage: `${this.constructor.name} request statistics:`,
|
|
371
295
|
log: this.log,
|
|
372
|
-
|
|
296
|
+
id: this.identity.id,
|
|
373
297
|
...statisticsOptions,
|
|
374
298
|
});
|
|
375
299
|
if (sessionPool && proxyConfiguration) {
|
|
@@ -639,7 +563,7 @@ export class BasicCrawler {
|
|
|
639
563
|
// Setting the status message is not a storage concern, so we intentionally don't route it
|
|
640
564
|
// through the storage client anymore.
|
|
641
565
|
serviceLocator.getEventManager().emit("statusMessage" /* EventType.STATUS_MESSAGE */, {
|
|
642
|
-
crawlerId: this.
|
|
566
|
+
crawlerId: this.identity.id,
|
|
643
567
|
message,
|
|
644
568
|
isStatusMessageTerminal: options.isStatusMessageTerminal,
|
|
645
569
|
level: options.level,
|
|
@@ -829,7 +753,7 @@ export class BasicCrawler {
|
|
|
829
753
|
async openOwnedRequestQueue() {
|
|
830
754
|
// The first crawler instance uses the default queue (null identifier);
|
|
831
755
|
// subsequent instances get their own queue via a unique alias so they don't collide.
|
|
832
|
-
const identifier = this.
|
|
756
|
+
const identifier = this.identity.instanceIndex === 0 ? null : { alias: `__default_${this.identity.id}__` };
|
|
833
757
|
const requestQueue = await RequestQueue.open(identifier, { config: serviceLocator.getConfiguration() });
|
|
834
758
|
this.ownedRequestManager = requestQueue;
|
|
835
759
|
return requestQueue;
|
|
@@ -872,12 +796,12 @@ export class BasicCrawler {
|
|
|
872
796
|
}
|
|
873
797
|
async useState(defaultValue = {}) {
|
|
874
798
|
const kvs = await KeyValueStore.open(null, { config: serviceLocator.getConfiguration() });
|
|
875
|
-
if (this.hasExplicitId) {
|
|
876
|
-
const stateKey = `${BasicCrawler.CRAWLEE_STATE_KEY}_${this.
|
|
799
|
+
if (this.identity.hasExplicitId) {
|
|
800
|
+
const stateKey = `${BasicCrawler.CRAWLEE_STATE_KEY}_${this.identity.id}`;
|
|
877
801
|
return kvs.getAutoSavedValue(stateKey, defaultValue);
|
|
878
802
|
}
|
|
879
|
-
BasicCrawler.
|
|
880
|
-
if (BasicCrawler.
|
|
803
|
+
BasicCrawler.useStateAnonymousIndices.add(this.identity.instanceIndex);
|
|
804
|
+
if (BasicCrawler.useStateAnonymousIndices.size > 1) {
|
|
881
805
|
serviceLocator
|
|
882
806
|
.getLogger()
|
|
883
807
|
.warningOnce('Multiple crawler instances are calling useState() without an explicit `id` option. \n' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/basic",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.80",
|
|
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"
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"@apify/datastructures": "^2.0.0",
|
|
43
43
|
"@apify/timeout": "^0.3.2",
|
|
44
44
|
"@apify/utilities": "^2.15.5",
|
|
45
|
-
"@crawlee/core": "4.0.0-beta.
|
|
46
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
47
|
-
"@crawlee/types": "4.0.0-beta.
|
|
48
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
45
|
+
"@crawlee/core": "4.0.0-beta.80",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.80",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.80",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.80",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
50
|
"fs-extra": "^11.3.0",
|
|
51
51
|
"ow": "^2.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"type-fest": "^4.41.0"
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
|
-
"@crawlee/impit-client": "^4.0.0-beta.
|
|
57
|
+
"@crawlee/impit-client": "^4.0.0-beta.80"
|
|
58
58
|
},
|
|
59
59
|
"lerna": {
|
|
60
60
|
"command": {
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "96c57b4a0c999e4b2bd198792490af28db7aa42d"
|
|
67
67
|
}
|