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

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.
Files changed (76) hide show
  1. package/errors.d.ts +0 -57
  2. package/errors.js +0 -71
  3. package/events/index.d.ts +1 -0
  4. package/events/system_info.d.ts +38 -0
  5. package/index.d.ts +1 -7
  6. package/index.js +1 -7
  7. package/internal.d.ts +8 -0
  8. package/internal.js +9 -0
  9. package/package.json +7 -9
  10. package/request.d.ts +4 -2
  11. package/storages/index.d.ts +0 -2
  12. package/storages/index.js +0 -2
  13. package/autoscaling/autoscaled_pool.d.ts +0 -196
  14. package/autoscaling/autoscaled_pool.js +0 -386
  15. package/autoscaling/concurrency_system.d.ts +0 -268
  16. package/autoscaling/concurrency_system.js +0 -364
  17. package/autoscaling/cpu_load_signal.d.ts +0 -43
  18. package/autoscaling/cpu_load_signal.js +0 -47
  19. package/autoscaling/event_loop_load_signal.d.ts +0 -51
  20. package/autoscaling/event_loop_load_signal.js +0 -60
  21. package/autoscaling/index.d.ts +0 -9
  22. package/autoscaling/index.js +0 -9
  23. package/autoscaling/load_signal.d.ts +0 -98
  24. package/autoscaling/load_signal.js +0 -103
  25. package/autoscaling/memory_load_signal.d.ts +0 -47
  26. package/autoscaling/memory_load_signal.js +0 -106
  27. package/autoscaling/snapshotter.d.ts +0 -84
  28. package/autoscaling/snapshotter.js +0 -67
  29. package/autoscaling/storage_backend_load_signal.d.ts +0 -56
  30. package/autoscaling/storage_backend_load_signal.js +0 -73
  31. package/autoscaling/system_status.d.ts +0 -159
  32. package/autoscaling/system_status.js +0 -139
  33. package/autoscaling/weighted_avg.d.ts +0 -5
  34. package/autoscaling/weighted_avg.js +0 -14
  35. package/cookie_utils.d.ts +0 -44
  36. package/cookie_utils.js +0 -122
  37. package/crawlers/context_pipeline.d.ts +0 -70
  38. package/crawlers/context_pipeline.js +0 -122
  39. package/crawlers/crawler_commons.d.ts +0 -199
  40. package/crawlers/error_snapshotter.d.ts +0 -57
  41. package/crawlers/error_snapshotter.js +0 -117
  42. package/crawlers/error_tracker.d.ts +0 -54
  43. package/crawlers/error_tracker.js +0 -308
  44. package/crawlers/index.d.ts +0 -5
  45. package/crawlers/index.js +0 -4
  46. package/crawlers/internals/types.d.ts +0 -7
  47. package/crawlers/internals/types.js +0 -1
  48. package/crawlers/statistics.d.ts +0 -319
  49. package/crawlers/statistics.js +0 -527
  50. package/enqueue_links/enqueue_links.d.ts +0 -156
  51. package/enqueue_links/enqueue_links.js +0 -77
  52. package/enqueue_links/index.d.ts +0 -2
  53. package/enqueue_links/index.js +0 -2
  54. package/enqueue_links/shared.d.ts +0 -100
  55. package/enqueue_links/shared.js +0 -259
  56. package/http.d.ts +0 -9
  57. package/http.js +0 -28
  58. package/router.d.ts +0 -306
  59. package/router.js +0 -309
  60. package/session_pool/consts.d.ts +0 -3
  61. package/session_pool/consts.js +0 -3
  62. package/session_pool/errors.d.ts +0 -7
  63. package/session_pool/errors.js +0 -11
  64. package/session_pool/fingerprint.d.ts +0 -9
  65. package/session_pool/fingerprint.js +0 -30
  66. package/session_pool/index.d.ts +0 -4
  67. package/session_pool/index.js +0 -4
  68. package/session_pool/session.d.ts +0 -153
  69. package/session_pool/session.js +0 -221
  70. package/session_pool/session_pool.d.ts +0 -242
  71. package/session_pool/session_pool.js +0 -431
  72. package/storages/sitemap_request_loader.d.ts +0 -197
  73. package/storages/sitemap_request_loader.js +0 -439
  74. package/storages/throttling_request_manager.d.ts +0 -223
  75. package/storages/throttling_request_manager.js +0 -835
  76. /package/{crawlers/crawler_commons.js → events/system_info.js} +0 -0
@@ -1,308 +0,0 @@
1
- import { inspect } from 'node:util';
2
- import { ErrorSnapshotter } from './error_snapshotter.js';
3
- const extractPathFromStackTraceLine = (line) => {
4
- const lastStartingRoundBracketIndex = line.lastIndexOf('(');
5
- if (lastStartingRoundBracketIndex !== -1) {
6
- const closingRoundBracketIndex = line.indexOf(')', lastStartingRoundBracketIndex);
7
- if (closingRoundBracketIndex !== -1) {
8
- return line.slice(lastStartingRoundBracketIndex + 1, closingRoundBracketIndex);
9
- }
10
- }
11
- return line;
12
- };
13
- // https://v8.dev/docs/stack-trace-api#appendix%3A-stack-trace-format
14
- const getPathFromStackTrace = (stack) => {
15
- for (const line of stack) {
16
- const path = extractPathFromStackTraceLine(line);
17
- if (path.startsWith('node:') || path.includes('/node_modules/') || path.includes('\\node_modules\\')) {
18
- continue;
19
- }
20
- return path;
21
- }
22
- return extractPathFromStackTraceLine(stack[0]);
23
- };
24
- const getStackTraceGroup = (error, storage, showFullStack) => {
25
- const stack = error.stack?.split('\n').map((line) => line.trim());
26
- let sliceAt = -1;
27
- if (stack) {
28
- for (let i = 0; i < stack.length; i++) {
29
- if (stack[i].startsWith('at ') || stack[i].startsWith('eval at ')) {
30
- sliceAt = i;
31
- break;
32
- }
33
- }
34
- }
35
- let normalizedStackTrace = null;
36
- if (sliceAt !== -1) {
37
- normalizedStackTrace = showFullStack
38
- ? stack
39
- .slice(sliceAt)
40
- .map((x) => x.trim())
41
- .join('\n')
42
- : getPathFromStackTrace(stack.slice(sliceAt));
43
- }
44
- if (!normalizedStackTrace) {
45
- normalizedStackTrace = 'missing stack trace';
46
- }
47
- if (!(normalizedStackTrace in storage)) {
48
- storage[normalizedStackTrace] = Object.create(null);
49
- }
50
- return storage[normalizedStackTrace];
51
- };
52
- const getErrorCodeGroup = (error, storage) => {
53
- let { code } = error;
54
- if (code === undefined) {
55
- code = 'missing error code';
56
- }
57
- if (!(code in storage)) {
58
- storage[code] = Object.create(null);
59
- }
60
- return storage[String(code)];
61
- };
62
- const getErrorNameGroup = (error, storage) => {
63
- const { name } = error;
64
- if (!(name in storage)) {
65
- storage[name] = Object.create(null);
66
- }
67
- return storage[name];
68
- };
69
- const findBiggestWordIntersection = (a, b) => {
70
- let maxStreak = 0;
71
- let bStreakIndex = -1;
72
- let aStreakIndex = -1;
73
- for (let aIndex = 0; aIndex < a.length; aIndex++) {
74
- let bIndex = -1;
75
- do {
76
- let aWalkIndex = aIndex;
77
- bIndex = b.indexOf(a[aIndex], bIndex + 1);
78
- let bWalkIndex = bIndex;
79
- let streak = 0;
80
- while (aWalkIndex < a.length && bWalkIndex < b.length && b[bWalkIndex++] === a[aWalkIndex++]) {
81
- streak++;
82
- }
83
- if (streak > maxStreak) {
84
- maxStreak = streak;
85
- aStreakIndex = aIndex;
86
- bStreakIndex = bIndex;
87
- }
88
- } while (bIndex !== -1);
89
- }
90
- return {
91
- maxStreak,
92
- aStreakIndex,
93
- bStreakIndex,
94
- };
95
- };
96
- const arrayCount = (array, target) => {
97
- let result = 0;
98
- for (const item of array) {
99
- if (item === target) {
100
- result++;
101
- }
102
- }
103
- return result;
104
- };
105
- const calculatePlaceholder = (a, b) => {
106
- const { maxStreak, aStreakIndex, bStreakIndex } = findBiggestWordIntersection(a, b);
107
- if (maxStreak === 0) {
108
- return ['_'];
109
- }
110
- const leftA = a.slice(0, aStreakIndex);
111
- const leftB = b.slice(0, bStreakIndex);
112
- const rightA = a.slice(aStreakIndex + maxStreak);
113
- const rightB = b.slice(bStreakIndex + maxStreak);
114
- const output = [];
115
- if (leftA.length !== 0 || leftB.length !== 0) {
116
- output.push(...calculatePlaceholder(leftA, leftB));
117
- }
118
- output.push(...a.slice(aStreakIndex, aStreakIndex + maxStreak));
119
- if (rightA.length !== 0 || rightB.length !== 0) {
120
- output.push(...calculatePlaceholder(rightA, rightB));
121
- }
122
- return output;
123
- };
124
- const normalizedCalculatePlaceholder = (a, b) => {
125
- const output = calculatePlaceholder(a, b);
126
- // We can't be too general
127
- if (arrayCount(output, '_') / output.length >= 0.5) {
128
- return ['_'];
129
- }
130
- return output;
131
- };
132
- // Merge A (missing placeholders) into B (can contain placeholders but does not have to)
133
- const mergeMessages = (a, b, storage) => {
134
- const placeholder = normalizedCalculatePlaceholder(a.split(' '), b.split(' ')).join(' ');
135
- if (placeholder === '_') {
136
- return undefined;
137
- }
138
- const count = storage[a].count + storage[b].count;
139
- delete storage[a];
140
- delete storage[b];
141
- storage[placeholder] = Object.assign(Object.create(null), {
142
- count,
143
- });
144
- return placeholder;
145
- };
146
- const getErrorMessageGroup = (error, storage, showFullMessage) => {
147
- let { message } = error;
148
- if (!message) {
149
- try {
150
- message =
151
- typeof error === 'string'
152
- ? error
153
- : `Unknown error message. Received non-error object: ${JSON.stringify(error)}`;
154
- }
155
- catch {
156
- message = `Unknown error message. Received non-error object, and could not stringify it: ${inspect(error, {
157
- depth: 0,
158
- })}`;
159
- }
160
- }
161
- if (!showFullMessage) {
162
- const newLineIndex = message.indexOf('\n');
163
- message = message.slice(0, newLineIndex === -1 ? undefined : newLineIndex);
164
- }
165
- if (!(message in storage)) {
166
- storage[message] = Object.assign(Object.create(null), {
167
- count: 0,
168
- });
169
- // This actually safe, since we Object.create(null) so no prototype pollution can happen.
170
- // eslint-disable-next-line no-restricted-syntax, guard-for-in
171
- for (const existingMessage in storage) {
172
- const newMessage = mergeMessages(message, existingMessage, storage);
173
- if (newMessage) {
174
- message = newMessage;
175
- break;
176
- }
177
- }
178
- }
179
- return storage[message];
180
- };
181
- const increaseCount = (group) => {
182
- if (!('count' in group)) {
183
- // In case users don't want to display error message
184
- group.count = 0;
185
- }
186
- group.count++;
187
- };
188
- /**
189
- * This class tracks errors and computes a summary of information like:
190
- * - where the errors happened
191
- * - what the error names are
192
- * - what the error codes are
193
- * - what is the general error message
194
- *
195
- * This is extremely useful when there are dynamic error messages, such as argument validation.
196
- *
197
- * Since the structure of the `tracker.result` object differs when using different options,
198
- * it's typed as `Record<string, unknown>`. The most deep object has a `count` property, which is a number.
199
- *
200
- * It's possible to get the total amount of errors via the `tracker.total` property.
201
- */
202
- export class ErrorTracker {
203
- #options;
204
- result;
205
- total;
206
- errorSnapshotter;
207
- constructor(options = {}) {
208
- this.#options = {
209
- showErrorCode: true,
210
- showErrorName: true,
211
- showStackTrace: true,
212
- showFullStack: false,
213
- showErrorMessage: true,
214
- showFullMessage: false,
215
- saveErrorSnapshots: false,
216
- ...options,
217
- };
218
- if (this.#options.saveErrorSnapshots) {
219
- this.errorSnapshotter = new ErrorSnapshotter();
220
- }
221
- this.result = Object.create(null);
222
- this.total = 0;
223
- }
224
- updateGroup(error) {
225
- let group = this.result;
226
- if (this.#options.showStackTrace) {
227
- group = getStackTraceGroup(error, group, this.#options.showFullStack);
228
- }
229
- if (this.#options.showErrorCode) {
230
- group = getErrorCodeGroup(error, group);
231
- }
232
- if (this.#options.showErrorName) {
233
- group = getErrorNameGroup(error, group);
234
- }
235
- if (this.#options.showErrorMessage) {
236
- group = getErrorMessageGroup(error, group, this.#options.showFullMessage);
237
- }
238
- increaseCount(group);
239
- return group;
240
- }
241
- add(error) {
242
- this.total++;
243
- this.updateGroup(error);
244
- if (typeof error.cause === 'object' && error.cause !== null) {
245
- this.add(error.cause);
246
- }
247
- }
248
- /**
249
- * This method is async, because it captures a snapshot of the error context.
250
- * We added this new method to avoid breaking changes.
251
- */
252
- async addAsync(error, context) {
253
- this.total++;
254
- const group = this.updateGroup(error);
255
- // Capture a snapshot (screenshot and HTML) on the first occurrence of an error
256
- if (group.count === 1 && context) {
257
- await this.captureSnapshot(group, error, context).catch(() => { });
258
- }
259
- if (typeof error.cause === 'object' && error.cause !== null) {
260
- await this.addAsync(error.cause);
261
- }
262
- }
263
- getUniqueErrorCount() {
264
- let count = 0;
265
- const goDeeper = (group) => {
266
- if ('count' in group) {
267
- count++;
268
- return;
269
- }
270
- // eslint-disable-next-line guard-for-in, no-restricted-syntax
271
- for (const key in group) {
272
- goDeeper(group[key]);
273
- }
274
- };
275
- goDeeper(this.result);
276
- return count;
277
- }
278
- getMostPopularErrors(count) {
279
- const result = [];
280
- const goDeeper = (group, path) => {
281
- if ('count' in group) {
282
- result.push([group.count, path]);
283
- return;
284
- }
285
- // eslint-disable-next-line guard-for-in, no-restricted-syntax
286
- for (const key in group) {
287
- goDeeper(group[key], [...path, key]);
288
- }
289
- };
290
- goDeeper(this.result, []);
291
- return result.sort((a, b) => b[0] - a[0]).slice(0, count);
292
- }
293
- async captureSnapshot(storage, error, context) {
294
- if (!this.errorSnapshotter) {
295
- return;
296
- }
297
- const { screenshotFileUrl, htmlFileUrl } = await this.errorSnapshotter.captureSnapshot(error, context);
298
- storage.firstErrorScreenshotUrl = screenshotFileUrl;
299
- storage.firstErrorHtmlUrl = htmlFileUrl;
300
- }
301
- reset() {
302
- // This actually safe, since we Object.create(null) so no prototype pollution can happen.
303
- // eslint-disable-next-line no-restricted-syntax, guard-for-in
304
- for (const key in this.result) {
305
- delete this.result[key];
306
- }
307
- }
308
- }
@@ -1,5 +0,0 @@
1
- export * from './context_pipeline.js';
2
- export type * from './crawler_commons.js';
3
- export * from './statistics.js';
4
- export * from './error_tracker.js';
5
- export * from './error_snapshotter.js';
package/crawlers/index.js DELETED
@@ -1,4 +0,0 @@
1
- export * from './context_pipeline.js';
2
- export * from './statistics.js';
3
- export * from './error_tracker.js';
4
- export * from './error_snapshotter.js';
@@ -1,7 +0,0 @@
1
- export interface BrowserPage {
2
- content: () => Promise<string>;
3
- }
4
- export interface SnapshottableProperties {
5
- body?: unknown;
6
- page?: BrowserPage;
7
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,319 +0,0 @@
1
- import type { CrawleeLogger } from '../log.js';
2
- import type { SyncStateConversion } from '../recoverable_state.js';
3
- import { KeyValueStore } from '../storages/key_value_store.js';
4
- import { ErrorTracker } from './error_tracker.js';
5
- /**
6
- * Persistence-related options to control how and when crawler's data gets persisted.
7
- */
8
- export interface PersistenceOptions {
9
- /**
10
- * Use this flag to disable or enable periodic persistence to key value store.
11
- * @default true
12
- */
13
- enable?: boolean;
14
- }
15
- /**
16
- * The statistics surface a crawler depends on: recording per-request outcomes, tracking errors, and driving the
17
- * capture lifecycle for a run. Injected via the crawler's `statistics` option, so a custom implementation can be
18
- * plugged in without subclassing the crawler.
19
- *
20
- * `StateExtension` describes the custom fields tracked alongside the built-in {@link StatisticState} ones - see
21
- * {@link StatisticsOptions.stateExtension}.
22
- *
23
- * The owned-only mutators the crawler uses to *own* a default it built - `reset()`/`resetStore()` - are deliberately
24
- * absent: an injected instance is borrowed, and the crawler never wipes it. Those live on the concrete
25
- * {@link Statistics} only.
26
- *
27
- * @category Crawlers
28
- */
29
- export interface IStatistics<StateExtension extends object = {}> {
30
- /** Tracker for errors on the final retry of a request. */
31
- readonly errorTracker: ErrorTracker;
32
- /** Tracker for errors on retries prior to the final one. */
33
- readonly errorTrackerRetry: ErrorTracker;
34
- /** The live statistics state the crawler reads for status messages and the final summary. */
35
- readonly state: StatisticState & StateExtension;
36
- /** Retries histogram - index `i` holds the number of requests that settled after `i` retries. */
37
- readonly requestRetryHistogram: number[];
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
- /** Marks a started request as failed, updating the failed counters and durations. */
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
- /** Increments the counter for the given HTTP status code. */
47
- registerStatusCode(code: number): void;
48
- /** Computes the derived aggregates (averages, per-minute rates, totals) from the current state. */
49
- calculate(): CalculatedStatistics;
50
- /** Begins a capture window: loads any persisted state, subscribes to persistence events, starts periodic logging. */
51
- startCapturing(): Promise<void>;
52
- /** Ends the capture window: stops logging, unsubscribes, and persists the final state. */
53
- stopCapturing(): Promise<void>;
54
- /**
55
- * Persists the current state to the key-value store. Optional - the crawler calls it on migration, but a backend
56
- * with no persistence of its own can omit it.
57
- */
58
- persistState?(): Promise<void>;
59
- }
60
- /** The derived aggregates computed by {@link IStatistics.calculate} from the current {@link StatisticState}. */
61
- export interface CalculatedStatistics {
62
- /** Mean duration of a failed request, in milliseconds; `Infinity` when nothing has failed. */
63
- requestAvgFailedDurationMillis: 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
- /** Requests failed per minute over the run so far. */
69
- requestsFailedPerMinute: number;
70
- /** Combined duration of all succeeded and failed requests, in milliseconds. */
71
- requestTotalDurationMillis: number;
72
- /** Total number of settled requests (succeeded plus failed). */
73
- requestsTotal: number;
74
- /** Wall-clock runtime since capturing started, in milliseconds. */
75
- crawlerRuntimeMillis: number;
76
- }
77
- /**
78
- * The statistics class provides an interface to collecting and logging run
79
- * statistics for requests.
80
- *
81
- * All statistic information is saved on key value store
82
- * under the key `CRAWLEE_CRAWLER_STATISTICS_*`, persists between
83
- * migrations and abort/resurrect
84
- *
85
- * Custom fields are tracked by passing {@link StatisticsOptions.stateExtension|`stateExtension`} - the extra fields are then part
86
- * of {@link Statistics.state|`state`}, persisted and restored along with the built-in ones.
87
- *
88
- * @category Crawlers
89
- */
90
- export declare class Statistics<StateExtension extends object = {}, PersistedStateExtension extends object = StateExtension> implements IStatistics<StateExtension> {
91
- #private;
92
- /**
93
- * An error tracker for final retry errors.
94
- */
95
- readonly errorTracker: ErrorTracker;
96
- /**
97
- * An error tracker for retry errors prior to the final retry.
98
- */
99
- readonly errorTrackerRetry: ErrorTracker;
100
- /**
101
- * Statistic instance id.
102
- */
103
- readonly id: string;
104
- private readonly log;
105
- /**
106
- * Current statistic state used for doing calculations on {@link Statistics.calculate} calls
107
- */
108
- get state(): StatisticState & StateExtension;
109
- /**
110
- * Contains the current retries histogram. Index 0 means 0 retries, index 2, 2 retries, and so on
111
- */
112
- get requestRetryHistogram(): number[];
113
- /**
114
- * Construct a statistics instance to pass to a crawler via its `statistics` option, e.g. to preconfigure
115
- * persistence or error snapshots, share it across sequential runs, or track extra fields via `state`.
116
- */
117
- constructor(options?: StatisticsOptions<StateExtension, PersistedStateExtension>);
118
- /**
119
- * Set the current statistic instance to pristine values.
120
- *
121
- * The persisted record is left alone - use {@link Statistics.resetStore} to clear that as well.
122
- */
123
- reset(): void;
124
- /**
125
- * Clear the persisted statistics record, leaving the in-memory state alone.
126
- *
127
- * Throws while capturing - the next PERSIST_STATE event would write the record straight back.
128
- */
129
- resetStore(): Promise<void>;
130
- /**
131
- * Increments the status code counter.
132
- */
133
- registerStatusCode(code: number): void;
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.
144
- * @ignore
145
- */
146
- discardRequestRecord(id: number | string): void;
147
- /**
148
- * Calculate the current statistics
149
- */
150
- calculate(): CalculatedStatistics;
151
- /**
152
- * Initializes the key value store for persisting the statistics,
153
- * displaying the current state in predefined intervals
154
- */
155
- startCapturing(): Promise<void>;
156
- /**
157
- * Stops logging and remove event listeners, then persist
158
- */
159
- stopCapturing(): Promise<void>;
160
- private saveRetryCountForRequest;
161
- /**
162
- * Persist internal state to the key value store.
163
- *
164
- * Statistics are bookkeeping - a store that refuses the write is worth a warning, not a failed crawl. The
165
- * crawler calls this from its migration handler, where a rejection would go unhandled.
166
- */
167
- persistState(): Promise<void>;
168
- /**
169
- * Make this class serializable when called with `JSON.stringify(statsInstance)` directly
170
- * or through `keyValueStore.setValue('KEY', statsInstance)`
171
- */
172
- toJSON(): StatisticPersistedState & PersistedStateExtension;
173
- }
174
- /**
175
- * Configuration for the {@link Statistics} instance used by the crawler
176
- */
177
- export interface StatisticsOptions<StateExtension extends object = {}, PersistedStateExtension extends object = StateExtension> {
178
- /**
179
- * Interval in seconds to log the current statistics
180
- * @default 60
181
- */
182
- logIntervalSecs?: number;
183
- /**
184
- * Message to log with the current statistics
185
- * @default 'Statistics'
186
- */
187
- logMessage?: string;
188
- /**
189
- * Parent logger instance, the statistics will create a child logger from this.
190
- * @default crawler.log
191
- */
192
- log?: CrawleeLogger;
193
- /**
194
- * Key value store instance to persist the statistics.
195
- * If not provided, the default one will be used when capturing starts
196
- */
197
- keyValueStore?: KeyValueStore;
198
- /**
199
- * Control how and when to persist the statistics.
200
- */
201
- persistenceOptions?: PersistenceOptions;
202
- /**
203
- * Save HTML snapshot (and a screenshot if possible) when an error occurs.
204
- * @default false
205
- */
206
- saveErrorSnapshots?: boolean;
207
- /**
208
- * A unique identifier for this statistics instance. This ID is used for persistence
209
- * to the key value store, ensuring the same statistics can be loaded after script restarts.
210
- *
211
- * If not provided, an auto-incremented ID will be used for backward compatibility.
212
- * This means statistics may not persist correctly across script restarts
213
- * if crawler creation order changes.
214
- */
215
- id?: string;
216
- /**
217
- * Custom fields to track alongside the built-in {@link StatisticState} ones. They become part of
218
- * {@link Statistics.state|`state`} (typed as such), are persisted with the rest of the state, and are
219
- * restored on migration or resurrect.
220
- *
221
- * ```ts
222
- * const statistics = new Statistics({ stateExtension: { defaultState: { productsFound: 0 } } });
223
- * statistics.state.productsFound++;
224
- * ```
225
- */
226
- stateExtension?: StatisticStateExtensionOptions<StateExtension, PersistedStateExtension>;
227
- }
228
- /**
229
- * How the custom fields of {@link StatisticsOptions.stateExtension} are initialized and converted to and from the
230
- * persisted record - the same three things {@link RecoverableStateOptions} asks for, scoped to the custom half
231
- * of the statistics state.
232
- */
233
- export interface StatisticStateExtensionOptions<StateExtension extends object, PersistedStateExtension extends object = StateExtension> {
234
- /**
235
- * The values the fields start with, and the ones {@link Statistics.reset} restores. A plain value is
236
- * deep-copied with `structuredClone` each time it is used; pass a factory for a state `structuredClone` cannot
237
- * rebuild.
238
- *
239
- * Can be omitted when `deserialize` supplies its own defaults, which is then the single place the fields are
240
- * declared - see the example on {@link StatisticStateExtensionOptions.deserialize|`deserialize`}.
241
- */
242
- defaultState?: StateExtension | (() => StateExtension);
243
- /**
244
- * Rebuilds the custom fields from the persisted record, and the place to validate them before trusting them.
245
- * Receives the whole record, so it has to supply a value for every field - `.default()` in a schema, or
246
- * {@link StatisticStateExtensionOptions.defaultState|`defaultState`} alongside a conversion that copes with
247
- * a missing field itself.
248
- *
249
- * ```ts
250
- * const statistics = new Statistics({
251
- * stateExtension: { deserialize: z.object({ productsFound: z.number().default(0) }) },
252
- * });
253
- * ```
254
- *
255
- * Without it, the declared fields are restored as they were persisted - which is a record off the key-value
256
- * store taken at its word, `productsFound` included in whatever type it happens to hold.
257
- *
258
- * A conversion that rejects the record costs the custom fields their persisted values (they start from the
259
- * defaults, with a warning) and nothing else.
260
- */
261
- deserialize?: SyncStateConversion<unknown, StateExtension>;
262
- /**
263
- * Converts the custom fields to the JSON-serializable form they are persisted in. Not needed for fields that
264
- * already are one - pair it with `deserialize` for the fields that are not.
265
- */
266
- serialize?: SyncStateConversion<StateExtension, PersistedStateExtension>;
267
- }
268
- /**
269
- * Format of the persisted stats.
270
- *
271
- * The `null`s are `Infinity` on the way out - JSON has no infinity, so a record written before anything
272
- * succeeded or failed carries a `null` in its place.
273
- */
274
- export interface StatisticPersistedState extends Omit<StatisticState, 'statsPersistedAt' | 'crawlerStartedAt' | 'crawlerFinishedAt' | 'requestMinDurationMillis' | 'requestsFailedPerMinute' | 'requestsSucceededPerMinute' | 'requestRetryHistogram' | 'instanceStart'> {
275
- statsId: string;
276
- requestsFailedPerMinute: number | null;
277
- requestsSucceededPerMinute: number | null;
278
- /** ISO strings - the live state keeps these as `Date`s. */
279
- crawlerStartedAt: string | null;
280
- crawlerFinishedAt: string | null;
281
- statsPersistedAt: string;
282
- requestMinDurationMillis: number | null;
283
- /** A retry count that no request ever reached leaves a `null` here. */
284
- requestRetryHistogram: (number | null)[];
285
- requestAvgFailedDurationMillis: number | null;
286
- requestAvgSucceededDurationMillis: number | null;
287
- requestTotalDurationMillis: number;
288
- requestsTotal: number;
289
- /** {@link StatisticState.instanceStart} of the run that wrote the record. */
290
- crawlerLastStartTimestamp: number;
291
- }
292
- /**
293
- * Contains the statistics state
294
- */
295
- export interface StatisticState {
296
- requestsSucceeded: number;
297
- requestsFailed: number;
298
- requestsRetries: number;
299
- requestsFailedPerMinute: number;
300
- requestsSucceededPerMinute: number;
301
- requestMinDurationMillis: number;
302
- requestMaxDurationMillis: number;
303
- requestTotalFailedDurationMillis: number;
304
- requestTotalSucceededDurationMillis: number;
305
- crawlerStartedAt: Date | string | null;
306
- crawlerFinishedAt: Date | string | null;
307
- crawlerRuntimeMillis: number;
308
- statsPersistedAt: Date | string | null;
309
- errors: Record<string, unknown>;
310
- retryErrors: Record<string, unknown>;
311
- requestsWithStatusCode: Record<string, number>;
312
- /** Retries histogram - index `i` holds the number of requests that settled after `i` retries. */
313
- requestRetryHistogram: number[];
314
- /**
315
- * When the current capture window started, as a `Date.now()` timestamp. Rebased on load so that the runtime
316
- * reported by {@link Statistics.calculate} spans a migration rather than restarting from zero.
317
- */
318
- instanceStart: number;
319
- }