@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.
@@ -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
- private readonly crawlerId;
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
- crawlerId;
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
- // Store whether the user explicitly provided an ID
317
- this.hasExplicitId = id !== undefined;
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
- ...(this.hasExplicitId ? { id: this.crawlerId } : {}),
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.crawlerId,
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.crawlerInstanceIndex === 0 ? null : { alias: `__default_${this.crawlerInstanceIndex}__` };
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.crawlerId}`;
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.useStateCrawlerIds.add(this.crawlerId);
880
- if (BasicCrawler.useStateCrawlerIds.size > 1) {
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.79",
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.79",
46
- "@crawlee/http-client": "4.0.0-beta.79",
47
- "@crawlee/types": "4.0.0-beta.79",
48
- "@crawlee/utils": "4.0.0-beta.79",
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.79"
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": "e897f0881c8142a1fd32f315263751296b4f1a22"
66
+ "gitHead": "96c57b4a0c999e4b2bd198792490af28db7aa42d"
67
67
  }