@crawlee/core 4.0.0-beta.169 → 4.0.0-beta.170

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.
@@ -80,12 +80,12 @@ export interface LoadSignalInfo {
80
80
  actualRatio: number;
81
81
  }
82
82
  export interface FinalStatistics {
83
- requestsFinished: number;
83
+ requestsSucceeded: number;
84
84
  requestsFailed: number;
85
85
  retryHistogram: number[];
86
86
  requestAvgFailedDurationMillis: number;
87
- requestAvgFinishedDurationMillis: number;
88
- requestsFinishedPerMinute: number;
87
+ requestAvgSucceededDurationMillis: number;
88
+ requestsSucceededPerMinute: number;
89
89
  requestsFailedPerMinute: number;
90
90
  requestTotalDurationMillis: number;
91
91
  requestsTotal: number;
@@ -33,16 +33,16 @@ export interface IStatistics<StateExtension extends object = {}> {
33
33
  readonly errorTrackerRetry: ErrorTracker;
34
34
  /** The live statistics state the crawler reads for status messages and the final summary. */
35
35
  readonly state: StatisticState & StateExtension;
36
- /** Retries histogram - index `i` holds the number of requests that finished after `i` retries. */
36
+ /** Retries histogram - index `i` holds the number of requests that settled after `i` retries. */
37
37
  readonly requestRetryHistogram: number[];
38
- /** Marks a request as started, so its duration can be measured on finish/fail. */
39
- startJob(id: number | string): void;
40
- /** Marks a started request as finished, updating the finished counters and durations. */
41
- finishJob(id: number | string, retryCount: number): void;
38
+ /** Marks a request as started, so its duration can be measured on success/failure. */
39
+ recordRequestStart(id: number | string): void;
40
+ /** Marks a started request as succeeded, updating the succeeded counters and durations. */
41
+ recordRequestSuccess(id: number | string, retryCount: number): void;
42
42
  /** Marks a started request as failed, updating the failed counters and durations. */
43
- failJob(id: number | string, retryCount: number): void;
44
- /** Drops a started request without counting it as finished or failed (e.g. skipped by robots.txt). */
45
- discardJob(id: number | string): void;
43
+ recordRequestFailure(id: number | string, retryCount: number): void;
44
+ /** Drops a started request without counting it as succeeded or failed (e.g. skipped by robots.txt). */
45
+ discardRequestRecord(id: number | string): void;
46
46
  /** Increments the counter for the given HTTP status code. */
47
47
  registerStatusCode(code: number): void;
48
48
  /** Computes the derived aggregates (averages, per-minute rates, totals) from the current state. */
@@ -61,15 +61,15 @@ export interface IStatistics<StateExtension extends object = {}> {
61
61
  export interface CalculatedStatistics {
62
62
  /** Mean duration of a failed request, in milliseconds; `Infinity` when nothing has failed. */
63
63
  requestAvgFailedDurationMillis: number;
64
- /** Mean duration of a finished request, in milliseconds; `Infinity` when nothing has finished. */
65
- requestAvgFinishedDurationMillis: number;
66
- /** Requests finished per minute over the run so far. */
67
- requestsFinishedPerMinute: number;
64
+ /** Mean duration of a succeeded request, in milliseconds; `Infinity` when nothing has succeeded. */
65
+ requestAvgSucceededDurationMillis: number;
66
+ /** Requests succeeded per minute over the run so far. */
67
+ requestsSucceededPerMinute: number;
68
68
  /** Requests failed per minute over the run so far. */
69
69
  requestsFailedPerMinute: number;
70
- /** Combined duration of all finished and failed requests, in milliseconds. */
70
+ /** Combined duration of all succeeded and failed requests, in milliseconds. */
71
71
  requestTotalDurationMillis: number;
72
- /** Total number of settled requests (finished plus failed). */
72
+ /** Total number of settled requests (succeeded plus failed). */
73
73
  requestsTotal: number;
74
74
  /** Wall-clock runtime since capturing started, in milliseconds. */
75
75
  crawlerRuntimeMillis: number;
@@ -131,27 +131,19 @@ export declare class Statistics<StateExtension extends object = {}, PersistedSta
131
131
  * Increments the status code counter.
132
132
  */
133
133
  registerStatusCode(code: number): void;
134
- /**
135
- * Starts a job
136
- * @ignore
137
- */
138
- startJob(id: number | string): void;
139
- /**
140
- * Mark job as finished and sets the state
141
- * @ignore
142
- */
143
- finishJob(id: number | string, retryCount: number): void;
144
- /**
145
- * Mark job as failed and sets the state
146
- * @ignore
147
- */
148
- failJob(id: number | string, retryCount: number): void;
149
- /**
150
- * Discards a started job without affecting the finished/failed counters, e.g. when a request
151
- * turns out to be skipped (robots.txt, enqueue strategy) after `startJob` was already called for it.
134
+ /** @ignore */
135
+ recordRequestStart(id: number | string): void;
136
+ /** @ignore */
137
+ recordRequestSuccess(id: number | string, retryCount: number): void;
138
+ /** @ignore */
139
+ recordRequestFailure(id: number | string, retryCount: number): void;
140
+ /**
141
+ * Discards a started request without affecting the succeeded/failed counters, e.g. when a request
142
+ * turns out to be skipped (robots.txt, enqueue strategy) after `recordRequestStart` was already
143
+ * called for it.
152
144
  * @ignore
153
145
  */
154
- discardJob(id: number | string): void;
146
+ discardRequestRecord(id: number | string): void;
155
147
  /**
156
148
  * Calculate the current statistics
157
149
  */
@@ -165,7 +157,7 @@ export declare class Statistics<StateExtension extends object = {}, PersistedSta
165
157
  * Stops logging and remove event listeners, then persist
166
158
  */
167
159
  stopCapturing(): Promise<void>;
168
- private saveRetryCountForJob;
160
+ private saveRetryCountForRequest;
169
161
  /**
170
162
  * Persist internal state to the key value store.
171
163
  *
@@ -277,12 +269,12 @@ export interface StatisticStateExtensionOptions<StateExtension extends object, P
277
269
  * Format of the persisted stats.
278
270
  *
279
271
  * The `null`s are `Infinity` on the way out - JSON has no infinity, so a record written before anything
280
- * finished or failed carries a `null` in its place.
272
+ * succeeded or failed carries a `null` in its place.
281
273
  */
282
- export interface StatisticPersistedState extends Omit<StatisticState, 'statsPersistedAt' | 'crawlerStartedAt' | 'crawlerFinishedAt' | 'requestMinDurationMillis' | 'requestsFailedPerMinute' | 'requestsFinishedPerMinute' | 'requestRetryHistogram' | 'instanceStart'> {
274
+ export interface StatisticPersistedState extends Omit<StatisticState, 'statsPersistedAt' | 'crawlerStartedAt' | 'crawlerFinishedAt' | 'requestMinDurationMillis' | 'requestsFailedPerMinute' | 'requestsSucceededPerMinute' | 'requestRetryHistogram' | 'instanceStart'> {
283
275
  statsId: string;
284
276
  requestsFailedPerMinute: number | null;
285
- requestsFinishedPerMinute: number | null;
277
+ requestsSucceededPerMinute: number | null;
286
278
  /** ISO strings - the live state keeps these as `Date`s. */
287
279
  crawlerStartedAt: string | null;
288
280
  crawlerFinishedAt: string | null;
@@ -291,7 +283,7 @@ export interface StatisticPersistedState extends Omit<StatisticState, 'statsPers
291
283
  /** A retry count that no request ever reached leaves a `null` here. */
292
284
  requestRetryHistogram: (number | null)[];
293
285
  requestAvgFailedDurationMillis: number | null;
294
- requestAvgFinishedDurationMillis: number | null;
286
+ requestAvgSucceededDurationMillis: number | null;
295
287
  requestTotalDurationMillis: number;
296
288
  requestsTotal: number;
297
289
  /** {@link StatisticState.instanceStart} of the run that wrote the record. */
@@ -301,15 +293,15 @@ export interface StatisticPersistedState extends Omit<StatisticState, 'statsPers
301
293
  * Contains the statistics state
302
294
  */
303
295
  export interface StatisticState {
304
- requestsFinished: number;
296
+ requestsSucceeded: number;
305
297
  requestsFailed: number;
306
298
  requestsRetries: number;
307
299
  requestsFailedPerMinute: number;
308
- requestsFinishedPerMinute: number;
300
+ requestsSucceededPerMinute: number;
309
301
  requestMinDurationMillis: number;
310
302
  requestMaxDurationMillis: number;
311
303
  requestTotalFailedDurationMillis: number;
312
- requestTotalFinishedDurationMillis: number;
304
+ requestTotalSucceededDurationMillis: number;
313
305
  crawlerStartedAt: Date | string | null;
314
306
  crawlerFinishedAt: Date | string | null;
315
307
  crawlerRuntimeMillis: number;
@@ -317,7 +309,7 @@ export interface StatisticState {
317
309
  errors: Record<string, unknown>;
318
310
  retryErrors: Record<string, unknown>;
319
311
  requestsWithStatusCode: Record<string, number>;
320
- /** Retries histogram - index `i` holds the number of requests that finished after `i` retries. */
312
+ /** Retries histogram - index `i` holds the number of requests that settled after `i` retries. */
321
313
  requestRetryHistogram: number[];
322
314
  /**
323
315
  * When the current capture window started, as a `Date.now()` timestamp. Rebased on load so that the runtime
@@ -7,15 +7,13 @@ import { ErrorTracker } from './error_tracker.js';
7
7
  /**
8
8
  * @ignore
9
9
  */
10
- class Job {
10
+ class RequestProcessingRecord {
11
11
  #lastRunAt = null;
12
- #durationMillis;
13
12
  run() {
14
13
  this.#lastRunAt = Date.now();
15
14
  }
16
15
  finish() {
17
- this.#durationMillis = Date.now() - this.#lastRunAt;
18
- return this.#durationMillis;
16
+ return Date.now() - this.#lastRunAt;
19
17
  }
20
18
  }
21
19
  const statisticsOptionsSchema = z.strictObject({
@@ -53,15 +51,15 @@ const errorTrackerConfig = {
53
51
  * validated by their own conversion on the way back, so the keys it does not know about are simply dropped here.
54
52
  */
55
53
  const persistedStatisticState = z.object({
56
- requestsFinished: z.number(),
54
+ requestsSucceeded: z.number(),
57
55
  requestsFailed: z.number(),
58
56
  requestsRetries: z.number(),
59
57
  requestsFailedPerMinute: z.number().nullable(),
60
- requestsFinishedPerMinute: z.number().nullable(),
58
+ requestsSucceededPerMinute: z.number().nullable(),
61
59
  requestMinDurationMillis: z.number().nullable(),
62
60
  requestMaxDurationMillis: z.number(),
63
61
  requestTotalFailedDurationMillis: z.number(),
64
- requestTotalFinishedDurationMillis: z.number(),
62
+ requestTotalSucceededDurationMillis: z.number(),
65
63
  crawlerStartedAt: z.string().nullable(),
66
64
  crawlerFinishedAt: z.string().nullable(),
67
65
  statsPersistedAt: z.string(),
@@ -72,7 +70,7 @@ const persistedStatisticState = z.object({
72
70
  requestRetryHistogram: z.array(z.number().nullable()),
73
71
  statsId: z.string(),
74
72
  requestAvgFailedDurationMillis: z.number().nullable(),
75
- requestAvgFinishedDurationMillis: z.number().nullable(),
73
+ requestAvgSucceededDurationMillis: z.number().nullable(),
76
74
  requestTotalDurationMillis: z.number(),
77
75
  requestsTotal: z.number(),
78
76
  requestsWithStatusCode: z.record(z.string(), z.number()),
@@ -97,11 +95,11 @@ function buildStatisticStateCodec(statistics) {
97
95
  return z.codec(persistedStatisticState, z.custom(), {
98
96
  decode: (record) => ({
99
97
  ...statistics.defaultState(),
100
- requestsFinished: record.requestsFinished,
98
+ requestsSucceeded: record.requestsSucceeded,
101
99
  requestsFailed: record.requestsFailed,
102
100
  requestsRetries: record.requestsRetries,
103
101
  requestTotalFailedDurationMillis: record.requestTotalFailedDurationMillis,
104
- requestTotalFinishedDurationMillis: record.requestTotalFinishedDurationMillis,
102
+ requestTotalSucceededDurationMillis: record.requestTotalSucceededDurationMillis,
105
103
  // Restoring the `null` as-is would make every later `duration < min` comparison fail, leaving the
106
104
  // minimum `null` for the rest of the run.
107
105
  requestMinDurationMillis: record.requestMinDurationMillis ?? Infinity,
@@ -117,10 +115,12 @@ function buildStatisticStateCodec(statistics) {
117
115
  instanceStart: Date.now() - (new Date(record.statsPersistedAt).getTime() - record.crawlerLastStartTimestamp),
118
116
  }),
119
117
  encode: (state) => {
118
+ // The per-minute rates fall through into `counters` and are overwritten below by `calculate()`'s,
119
+ // which are the ones the record carries.
120
120
  const { requestsWithStatusCode, errors, retryErrors, requestRetryHistogram, instanceStart, ...counters } = state;
121
121
  // Every rate and average `calculate()` derives is `Infinity` until the run is long enough, or until
122
- // something has finished or failed, to divide by.
123
- const { requestAvgFailedDurationMillis, requestAvgFinishedDurationMillis, requestsFailedPerMinute, requestsFinishedPerMinute, ...aggregates } = statistics.calculate();
122
+ // something has succeeded or failed, to divide by.
123
+ const { requestAvgFailedDurationMillis, requestAvgSucceededDurationMillis, requestsFailedPerMinute, requestsSucceededPerMinute, ...aggregates } = statistics.calculate();
124
124
  return {
125
125
  ...counters,
126
126
  requestMinDurationMillis: finiteOrNull(state.requestMinDurationMillis),
@@ -134,9 +134,9 @@ function buildStatisticStateCodec(statistics) {
134
134
  statsId: statistics.statsId,
135
135
  ...aggregates,
136
136
  requestAvgFailedDurationMillis: finiteOrNull(requestAvgFailedDurationMillis),
137
- requestAvgFinishedDurationMillis: finiteOrNull(requestAvgFinishedDurationMillis),
137
+ requestAvgSucceededDurationMillis: finiteOrNull(requestAvgSucceededDurationMillis),
138
138
  requestsFailedPerMinute: finiteOrNull(requestsFailedPerMinute),
139
- requestsFinishedPerMinute: finiteOrNull(requestsFinishedPerMinute),
139
+ requestsSucceededPerMinute: finiteOrNull(requestsSucceededPerMinute),
140
140
  requestsWithStatusCode,
141
141
  errors,
142
142
  retryErrors,
@@ -284,15 +284,15 @@ export class Statistics {
284
284
  /** The built-in half of {@link Statistics.defaultState}, before any custom fields are merged over it. */
285
285
  #builtInDefaultState() {
286
286
  return {
287
- requestsFinished: 0,
287
+ requestsSucceeded: 0,
288
288
  requestsFailed: 0,
289
289
  requestsRetries: 0,
290
290
  requestsFailedPerMinute: 0,
291
- requestsFinishedPerMinute: 0,
291
+ requestsSucceededPerMinute: 0,
292
292
  requestMinDurationMillis: Infinity,
293
293
  requestMaxDurationMillis: 0,
294
294
  requestTotalFailedDurationMillis: 0,
295
- requestTotalFinishedDurationMillis: 0,
295
+ requestTotalSucceededDurationMillis: 0,
296
296
  crawlerStartedAt: null,
297
297
  crawlerFinishedAt: null,
298
298
  statsPersistedAt: null,
@@ -323,70 +323,62 @@ export class Statistics {
323
323
  }
324
324
  this.state.requestsWithStatusCode[s]++;
325
325
  }
326
- /**
327
- * Starts a job
328
- * @ignore
329
- */
330
- startJob(id) {
331
- let job = this.#requestsInProgress.get(id);
332
- if (!job)
333
- job = new Job();
334
- job.run();
335
- this.#requestsInProgress.set(id, job);
326
+ /** @ignore */
327
+ recordRequestStart(id) {
328
+ let record = this.#requestsInProgress.get(id);
329
+ if (!record)
330
+ record = new RequestProcessingRecord();
331
+ record.run();
332
+ this.#requestsInProgress.set(id, record);
336
333
  }
337
- /**
338
- * Mark job as finished and sets the state
339
- * @ignore
340
- */
341
- finishJob(id, retryCount) {
342
- const job = this.#requestsInProgress.get(id);
343
- if (!job)
334
+ /** @ignore */
335
+ recordRequestSuccess(id, retryCount) {
336
+ const record = this.#requestsInProgress.get(id);
337
+ if (!record)
344
338
  return;
345
- const jobDurationMillis = job.finish();
346
- this.state.requestsFinished++;
347
- this.state.requestTotalFinishedDurationMillis += jobDurationMillis;
348
- this.saveRetryCountForJob(retryCount);
349
- if (jobDurationMillis < this.state.requestMinDurationMillis)
350
- this.state.requestMinDurationMillis = jobDurationMillis;
351
- if (jobDurationMillis > this.state.requestMaxDurationMillis)
352
- this.state.requestMaxDurationMillis = jobDurationMillis;
339
+ const durationMillis = record.finish();
340
+ this.state.requestsSucceeded++;
341
+ this.state.requestTotalSucceededDurationMillis += durationMillis;
342
+ this.saveRetryCountForRequest(retryCount);
343
+ if (durationMillis < this.state.requestMinDurationMillis)
344
+ this.state.requestMinDurationMillis = durationMillis;
345
+ if (durationMillis > this.state.requestMaxDurationMillis)
346
+ this.state.requestMaxDurationMillis = durationMillis;
353
347
  this.#requestsInProgress.delete(id);
354
348
  }
355
- /**
356
- * Mark job as failed and sets the state
357
- * @ignore
358
- */
359
- failJob(id, retryCount) {
360
- const job = this.#requestsInProgress.get(id);
361
- if (!job)
349
+ /** @ignore */
350
+ recordRequestFailure(id, retryCount) {
351
+ const record = this.#requestsInProgress.get(id);
352
+ if (!record)
362
353
  return;
363
- this.state.requestTotalFailedDurationMillis += job.finish();
354
+ this.state.requestTotalFailedDurationMillis += record.finish();
364
355
  this.state.requestsFailed++;
365
- this.saveRetryCountForJob(retryCount);
356
+ this.saveRetryCountForRequest(retryCount);
366
357
  this.#requestsInProgress.delete(id);
367
358
  }
368
359
  /**
369
- * Discards a started job without affecting the finished/failed counters, e.g. when a request
370
- * turns out to be skipped (robots.txt, enqueue strategy) after `startJob` was already called for it.
360
+ * Discards a started request without affecting the succeeded/failed counters, e.g. when a request
361
+ * turns out to be skipped (robots.txt, enqueue strategy) after `recordRequestStart` was already
362
+ * called for it.
371
363
  * @ignore
372
364
  */
373
- discardJob(id) {
365
+ discardRequestRecord(id) {
374
366
  this.#requestsInProgress.delete(id);
375
367
  }
376
368
  /**
377
369
  * Calculate the current statistics
378
370
  */
379
371
  calculate() {
380
- const { requestsFailed, requestsFinished, requestTotalFailedDurationMillis, requestTotalFinishedDurationMillis, } = this.state;
372
+ const { requestsFailed, requestsSucceeded, requestTotalFailedDurationMillis, requestTotalSucceededDurationMillis, } = this.state;
381
373
  const totalMillis = Date.now() - this.state.instanceStart;
382
374
  const totalMinutes = totalMillis / 1000 / 60;
383
375
  return {
384
376
  requestAvgFailedDurationMillis: Math.round(requestTotalFailedDurationMillis / requestsFailed) || Infinity,
385
- requestAvgFinishedDurationMillis: Math.round(requestTotalFinishedDurationMillis / requestsFinished) || Infinity,
386
- requestsFinishedPerMinute: Math.round(requestsFinished / totalMinutes) || 0,
377
+ requestAvgSucceededDurationMillis: Math.round(requestTotalSucceededDurationMillis / requestsSucceeded) || Infinity,
378
+ requestsSucceededPerMinute: Math.round(requestsSucceeded / totalMinutes) || 0,
387
379
  requestsFailedPerMinute: Math.floor(requestsFailed / totalMinutes) || 0,
388
- requestTotalDurationMillis: requestTotalFinishedDurationMillis + requestTotalFailedDurationMillis,
389
- requestsTotal: requestsFailed + requestsFinished,
380
+ requestTotalDurationMillis: requestTotalSucceededDurationMillis + requestTotalFailedDurationMillis,
381
+ requestsTotal: requestsFailed + requestsSucceeded,
390
382
  crawlerRuntimeMillis: totalMillis,
391
383
  };
392
384
  }
@@ -420,7 +412,7 @@ export class Statistics {
420
412
  this.state.crawlerFinishedAt = new Date();
421
413
  await this.#recoverableState.teardown();
422
414
  }
423
- saveRetryCountForJob(retryCount) {
415
+ saveRetryCountForRequest(retryCount) {
424
416
  if (retryCount > 0)
425
417
  this.state.requestsRetries++;
426
418
  this.requestRetryHistogram[retryCount] ??= 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.169",
3
+ "version": "4.0.0-beta.170",
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"
@@ -52,10 +52,10 @@
52
52
  "@apify/log": "^3.0.1",
53
53
  "@apify/timeout": "^1.0.1",
54
54
  "@apify/utilities": "^3.0.1",
55
- "@crawlee/fs-storage": "4.0.0-beta.169",
56
- "@crawlee/http-client": "4.0.0-beta.169",
57
- "@crawlee/types": "4.0.0-beta.169",
58
- "@crawlee/utils": "4.0.0-beta.169",
55
+ "@crawlee/fs-storage": "4.0.0-beta.170",
56
+ "@crawlee/http-client": "4.0.0-beta.170",
57
+ "@crawlee/types": "4.0.0-beta.170",
58
+ "@crawlee/utils": "4.0.0-beta.170",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@standard-schema/spec": "^1.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -78,5 +78,5 @@
78
78
  }
79
79
  }
80
80
  },
81
- "gitHead": "a42dc93fa0ef68180d83ab12af362082690f73e2"
81
+ "gitHead": "86e1c891da983125e9e18f1a2327fbcf5e0f5fc2"
82
82
  }