@crawlee/basic 4.0.0-beta.99 → 4.0.0-rc.1

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 (80) hide show
  1. package/README.md +1 -1
  2. package/index.d.ts +11 -1
  3. package/index.js +11 -0
  4. package/internals/autoscaling/autoscaled_pool.d.ts +178 -0
  5. package/internals/autoscaling/autoscaled_pool.js +387 -0
  6. package/internals/autoscaling/concurrency_system.d.ts +235 -0
  7. package/internals/autoscaling/concurrency_system.js +357 -0
  8. package/internals/autoscaling/cpu_load_signal.d.ts +43 -0
  9. package/internals/autoscaling/cpu_load_signal.js +46 -0
  10. package/internals/autoscaling/event_loop_load_signal.d.ts +51 -0
  11. package/internals/autoscaling/event_loop_load_signal.js +60 -0
  12. package/internals/autoscaling/index.d.ts +9 -0
  13. package/internals/autoscaling/index.js +9 -0
  14. package/internals/autoscaling/load_signal.d.ts +98 -0
  15. package/internals/autoscaling/load_signal.js +103 -0
  16. package/internals/autoscaling/memory_load_signal.d.ts +45 -0
  17. package/internals/autoscaling/memory_load_signal.js +104 -0
  18. package/internals/autoscaling/snapshotter.d.ts +84 -0
  19. package/internals/autoscaling/snapshotter.js +67 -0
  20. package/internals/autoscaling/storage_backend_load_signal.d.ts +56 -0
  21. package/internals/autoscaling/storage_backend_load_signal.js +73 -0
  22. package/internals/autoscaling/system_status.d.ts +112 -0
  23. package/internals/autoscaling/system_status.js +139 -0
  24. package/internals/autoscaling/weighted_avg.d.ts +5 -0
  25. package/internals/autoscaling/weighted_avg.js +14 -0
  26. package/internals/basic-crawler.d.ts +185 -389
  27. package/internals/basic-crawler.js +982 -684
  28. package/internals/cookie_utils.d.ts +40 -0
  29. package/internals/cookie_utils.js +122 -0
  30. package/internals/crawler-run.d.ts +76 -0
  31. package/internals/crawler-run.js +142 -0
  32. package/internals/crawlers/context_pipeline.d.ts +75 -0
  33. package/internals/crawlers/context_pipeline.js +109 -0
  34. package/internals/crawlers/crawler_commons.d.ts +216 -0
  35. package/internals/crawlers/crawler_commons.js +1 -0
  36. package/internals/crawlers/error_snapshotter.d.ts +29 -0
  37. package/internals/crawlers/error_snapshotter.js +116 -0
  38. package/internals/crawlers/error_tracker.d.ts +51 -0
  39. package/internals/crawlers/error_tracker.js +316 -0
  40. package/internals/crawlers/index.d.ts +4 -0
  41. package/internals/crawlers/index.js +3 -0
  42. package/internals/crawlers/internals/types.d.ts +7 -0
  43. package/internals/crawlers/internals/types.js +1 -0
  44. package/internals/crawlers/statistics.d.ts +316 -0
  45. package/internals/crawlers/statistics.js +525 -0
  46. package/internals/crawling_request.d.ts +86 -0
  47. package/internals/crawling_request.js +127 -0
  48. package/internals/enqueue_links/enqueue_links.d.ts +152 -0
  49. package/internals/enqueue_links/enqueue_links.js +76 -0
  50. package/internals/enqueue_links/index.d.ts +3 -0
  51. package/internals/enqueue_links/index.js +3 -0
  52. package/internals/enqueue_links/shared.d.ts +106 -0
  53. package/internals/enqueue_links/shared.js +258 -0
  54. package/internals/errors.d.ts +68 -0
  55. package/internals/errors.js +83 -0
  56. package/internals/http.d.ts +9 -0
  57. package/internals/http.js +28 -0
  58. package/internals/request-timeout.d.ts +6 -0
  59. package/internals/request-timeout.js +27 -2
  60. package/internals/router.d.ts +299 -0
  61. package/internals/router.js +312 -0
  62. package/internals/send-request.d.ts +2 -1
  63. package/internals/send-request.js +1 -0
  64. package/internals/session_pool/consts.d.ts +1 -0
  65. package/internals/session_pool/consts.js +1 -0
  66. package/internals/session_pool/errors.d.ts +7 -0
  67. package/internals/session_pool/errors.js +11 -0
  68. package/internals/session_pool/fingerprint.d.ts +9 -0
  69. package/internals/session_pool/fingerprint.js +30 -0
  70. package/internals/session_pool/index.d.ts +4 -0
  71. package/internals/session_pool/index.js +4 -0
  72. package/internals/session_pool/session.d.ts +151 -0
  73. package/internals/session_pool/session.js +225 -0
  74. package/internals/session_pool/session_pool.d.ts +193 -0
  75. package/internals/session_pool/session_pool.js +453 -0
  76. package/internals/sitemap_request_loader.d.ts +158 -0
  77. package/internals/sitemap_request_loader.js +432 -0
  78. package/internals/throttling_request_manager.d.ts +229 -0
  79. package/internals/throttling_request_manager.js +843 -0
  80. package/package.json +17 -14
@@ -0,0 +1,56 @@
1
+ import type { LoadSignal, LoadSignalStartContext, LoadSnapshot } from './load_signal.js';
2
+ /**
3
+ * A snapshot produced by the built-in storage backend (rate-limit) signal.
4
+ * @internal
5
+ */
6
+ export interface StorageBackendSnapshot extends LoadSnapshot {
7
+ rateLimitErrorCount: number;
8
+ }
9
+ /**
10
+ * Tuning for the built-in **storage backend** (rate-limit) load signal, as accepted both by
11
+ * {@link StorageBackendLoadSignal} and by the
12
+ * {@link LoadSignalsOptions.storageBackend|`storageBackend`} shorthand on {@link LoadSignalsOptions}.
13
+ */
14
+ export interface StorageBackendLoadSignalOptions {
15
+ /**
16
+ * Defines the interval of checking the current state of the storage backend, in seconds.
17
+ * @default 1
18
+ */
19
+ snapshotIntervalSecs?: number;
20
+ /**
21
+ * Defines the maximum number of new rate limit errors within the given interval.
22
+ * @default 3
23
+ */
24
+ maxErrors?: number;
25
+ /**
26
+ * Maximum ratio of overloaded snapshots in a sample before the storage backend counts as overloaded.
27
+ * @default 0.3
28
+ */
29
+ overloadedRatio?: number;
30
+ }
31
+ /**
32
+ * Periodically checks the storage backend for rate-limit errors (HTTP 429) and reports overload when the error delta
33
+ * exceeds a threshold.
34
+ *
35
+ * Built by default; construct one yourself only to wrap or adapt it — see {@link LoadSignal}.
36
+ *
37
+ * Switch it off entirely ({@link LoadSignalsOptions.storageBackend|`storageBackend: false`}) if the storage backend
38
+ * reports no rate-limit statistics, since it otherwise polls it every second to no purpose.
39
+ *
40
+ * @category Scaling
41
+ */
42
+ export declare class StorageBackendLoadSignal implements LoadSignal {
43
+ #private;
44
+ readonly name = "storageBackendInfo";
45
+ readonly overloadedRatio: number;
46
+ constructor(options?: StorageBackendLoadSignalOptions);
47
+ start(context: LoadSignalStartContext): Promise<void>;
48
+ stop(): Promise<void>;
49
+ getSample(sampleDurationMillis?: number): LoadSnapshot[];
50
+ /**
51
+ * Records one snapshot, overloaded when rate-limit errors grew by more than the configured limit since the
52
+ * previous one.
53
+ * @internal Also lets tests drive the measurement without waiting on a timer.
54
+ */
55
+ handle(intervalCallback: () => unknown): void;
56
+ }
@@ -0,0 +1,73 @@
1
+ import { serviceLocator } from '@crawlee/core';
2
+ import { betterClearInterval, betterSetInterval } from '@apify/utilities';
3
+ import { SnapshotStore } from './load_signal.js';
4
+ const RATE_LIMIT_ERROR_RETRY_COUNT = 2;
5
+ /**
6
+ * Periodically checks the storage backend for rate-limit errors (HTTP 429) and reports overload when the error delta
7
+ * exceeds a threshold.
8
+ *
9
+ * Built by default; construct one yourself only to wrap or adapt it — see {@link LoadSignal}.
10
+ *
11
+ * Switch it off entirely ({@link LoadSignalsOptions.storageBackend|`storageBackend: false`}) if the storage backend
12
+ * reports no rate-limit statistics, since it otherwise polls it every second to no purpose.
13
+ *
14
+ * @category Scaling
15
+ */
16
+ export class StorageBackendLoadSignal {
17
+ name = 'storageBackendInfo';
18
+ overloadedRatio;
19
+ #store = new SnapshotStore();
20
+ #intervalMillis;
21
+ #maxErrors;
22
+ #interval;
23
+ #storageBackend;
24
+ constructor(options = {}) {
25
+ this.overloadedRatio = options.overloadedRatio ?? 0.3;
26
+ this.#intervalMillis = (options.snapshotIntervalSecs ?? 1) * 1000;
27
+ this.#maxErrors = options.maxErrors ?? 3;
28
+ this.handle = this.handle.bind(this);
29
+ }
30
+ async start(context) {
31
+ this.#store.useSampleWindow(context.maxSampleWindowMillis);
32
+ // A new session starts from a clean slate, or its first measurement diffs the error count against the previous
33
+ // session's — possibly against a different backend, since it is resolved afresh just below.
34
+ this.#store.clear();
35
+ // Resolved here rather than in the constructor, where asking for the backend would instantiate a default one
36
+ // as a side effect - long before the crawler that owns the run has had a chance to register its own.
37
+ this.#storageBackend = serviceLocator.getStorageBackend();
38
+ this.#interval = betterSetInterval(this.handle, this.#intervalMillis);
39
+ }
40
+ async stop() {
41
+ if (this.#interval)
42
+ betterClearInterval(this.#interval);
43
+ this.#interval = undefined;
44
+ this.#storageBackend = undefined;
45
+ }
46
+ getSample(sampleDurationMillis) {
47
+ return this.#store.getSample(sampleDurationMillis);
48
+ }
49
+ /**
50
+ * Records one snapshot, overloaded when rate-limit errors grew by more than the configured limit since the
51
+ * previous one.
52
+ * @internal Also lets tests drive the measurement without waiting on a timer.
53
+ */
54
+ handle(intervalCallback) {
55
+ const now = new Date();
56
+ const allErrorCounts = this.#storageBackend?.stats?.rateLimitErrors ?? [];
57
+ const currentErrCount = allErrorCounts[RATE_LIMIT_ERROR_RETRY_COUNT] || 0;
58
+ const snapshot = {
59
+ createdAt: now,
60
+ isOverloaded: false,
61
+ rateLimitErrorCount: currentErrCount,
62
+ };
63
+ const all = this.#store.getAll();
64
+ const previousSnapshot = all[all.length - 1];
65
+ if (previousSnapshot) {
66
+ const delta = currentErrCount - previousSnapshot.rateLimitErrorCount;
67
+ if (delta > this.#maxErrors)
68
+ snapshot.isOverloaded = true;
69
+ }
70
+ this.#store.push(snapshot, now);
71
+ intervalCallback();
72
+ }
73
+ }
@@ -0,0 +1,112 @@
1
+ import type { SystemInfo } from '@crawlee/core';
2
+ import type { LoadSignal } from './load_signal.js';
3
+ import type { Snapshotter } from './snapshotter.js';
4
+ /**
5
+ * How far back the *current* system status looks by default — the window that gates task dispatch.
6
+ * @internal
7
+ */
8
+ export declare const DEFAULT_CURRENT_HISTORY_SECS = 5;
9
+ /**
10
+ * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
11
+ * therefore how much history the signals are asked to retain.
12
+ * @internal
13
+ */
14
+ export declare const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
15
+ /**
16
+ * An implementation detail of the {@link ConcurrencySystem} — configure it through
17
+ * {@link ConcurrencySystemOptions} (`loadSignals`, `currentHistorySecs` and `snapshotHistorySecs`).
18
+ * @internal
19
+ */
20
+ export interface SystemStatusOptions {
21
+ /**
22
+ * Defines max age of snapshots used in the {@link SystemStatus.getCurrentStatus} measurement.
23
+ * @default 5
24
+ */
25
+ currentHistorySecs?: number;
26
+ /**
27
+ * Defines max age of snapshots used in the {@link SystemStatus.getHistoricalStatus} measurement — the window
28
+ * autoscaling decisions are based on. Applied uniformly to every signal, built-in or custom, so that a signal's
29
+ * private retention cannot silently widen the window.
30
+ * @default 30
31
+ */
32
+ historySecs?: number;
33
+ /**
34
+ * The `Snapshotter` whose built-in signals are evaluated.
35
+ */
36
+ snapshotter: Snapshotter;
37
+ /**
38
+ * Additional load signals to include in the system status evaluation.
39
+ * These are evaluated alongside the built-in memory, CPU, event loop,
40
+ * and storage backend signals. If any signal reports overload, the system
41
+ * is considered overloaded. Each signal carries its own overload ratio.
42
+ */
43
+ loadSignals?: LoadSignal[];
44
+ }
45
+ export interface FinalStatistics {
46
+ requestsSucceeded: number;
47
+ requestsFailed: number;
48
+ retryHistogram: number[];
49
+ requestAvgFailedDurationMillis: number;
50
+ requestAvgSucceededDurationMillis: number;
51
+ requestsSucceededPerMinute: number;
52
+ requestsFailedPerMinute: number;
53
+ requestTotalDurationMillis: number;
54
+ requestsTotal: number;
55
+ crawlerRuntimeMillis: number;
56
+ }
57
+ /**
58
+ * Reads the overload verdict of every signal — the {@link Snapshotter}'s built-in four plus any custom ones — and
59
+ * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
60
+ * is overloaded whenever at least one of them is.
61
+ *
62
+ * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
63
+ * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
64
+ * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
65
+ *
66
+ * An implementation detail of the {@link ConcurrencySystem}, configured through
67
+ * {@link ConcurrencySystemOptions}.
68
+ * @internal
69
+ */
70
+ export declare class SystemStatus {
71
+ #private;
72
+ constructor(options: SystemStatusOptions);
73
+ /**
74
+ * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
75
+ * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
76
+ * defaults.
77
+ */
78
+ get maxSampleWindowMillis(): number;
79
+ /**
80
+ * Returns an {@link SystemInfo} object with the following structure:
81
+ *
82
+ * ```javascript
83
+ * {
84
+ * isSystemIdle: Boolean,
85
+ * memInfo: Object,
86
+ * eventLoopInfo: Object,
87
+ * cpuInfo: Object
88
+ * }
89
+ * ```
90
+ *
91
+ * Where the `isSystemIdle` property is set to `false` if the system
92
+ * has been overloaded in the last `options.currentHistorySecs` seconds,
93
+ * and `true` otherwise.
94
+ */
95
+ getCurrentStatus(): SystemInfo;
96
+ /**
97
+ * Returns an {@link SystemInfo} object with the following structure:
98
+ *
99
+ * ```javascript
100
+ * {
101
+ * isSystemIdle: Boolean,
102
+ * memInfo: Object,
103
+ * eventLoopInfo: Object,
104
+ * cpuInfo: Object
105
+ * }
106
+ * ```
107
+ *
108
+ * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
109
+ * `historySecs` seconds and `true` otherwise.
110
+ */
111
+ getHistoricalStatus(): SystemInfo;
112
+ }
@@ -0,0 +1,139 @@
1
+ import { evaluateLoadSignalSample } from './load_signal.js';
2
+ /**
3
+ * How far back the *current* system status looks by default — the window that gates task dispatch.
4
+ * @internal
5
+ */
6
+ export const DEFAULT_CURRENT_HISTORY_SECS = 5;
7
+ /**
8
+ * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
9
+ * therefore how much history the signals are asked to retain.
10
+ * @internal
11
+ */
12
+ export const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
13
+ /** The four built-in signal names that map to typed `SystemInfo` fields, and the option that switches each off. */
14
+ const BUILTIN_SIGNAL_OPTION_KEYS = {
15
+ memInfo: 'memory',
16
+ eventLoopInfo: 'eventLoop',
17
+ cpuInfo: 'cpu',
18
+ storageBackendInfo: 'storageBackend',
19
+ };
20
+ const BUILTIN_SIGNAL_NAMES = new Set(Object.keys(BUILTIN_SIGNAL_OPTION_KEYS));
21
+ /**
22
+ * Reads the overload verdict of every signal — the {@link Snapshotter}'s built-in four plus any custom ones — and
23
+ * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
24
+ * is overloaded whenever at least one of them is.
25
+ *
26
+ * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
27
+ * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
28
+ * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
29
+ *
30
+ * An implementation detail of the {@link ConcurrencySystem}, configured through
31
+ * {@link ConcurrencySystemOptions}.
32
+ * @internal
33
+ */
34
+ export class SystemStatus {
35
+ #currentHistoryMillis;
36
+ #historyMillis;
37
+ #signals;
38
+ constructor(options) {
39
+ const { currentHistorySecs = DEFAULT_CURRENT_HISTORY_SECS, historySecs = DEFAULT_SNAPSHOT_HISTORY_SECS, snapshotter, loadSignals = [], } = options;
40
+ this.#currentHistoryMillis = currentHistorySecs * 1000;
41
+ this.#historyMillis = historySecs * 1000;
42
+ this.#signals = [...snapshotter.getLoadSignals(), ...loadSignals];
43
+ this.#assertUniqueSignalNames();
44
+ }
45
+ /**
46
+ * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
47
+ * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
48
+ * defaults.
49
+ */
50
+ get maxSampleWindowMillis() {
51
+ return Math.max(this.#currentHistoryMillis, this.#historyMillis);
52
+ }
53
+ /**
54
+ * Signal names are the keys of the reported {@link SystemInfo}, so a duplicate would leave a status object that
55
+ * contradicts actual behavior: both signals are still evaluated (any overloaded one holds concurrency down), but
56
+ * only the last is reported.
57
+ */
58
+ #assertUniqueSignalNames() {
59
+ const seen = new Set();
60
+ for (const { name } of this.#signals) {
61
+ if (!seen.has(name)) {
62
+ seen.add(name);
63
+ continue;
64
+ }
65
+ const hint = BUILTIN_SIGNAL_NAMES.has(name)
66
+ ? `it is the name of a built-in signal. To replace that signal, switch it off with \`loadSignals: { ${BUILTIN_SIGNAL_OPTION_KEYS[name]}: false }\` and keep your implementation in \`loadSignals.custom\`; to run yours alongside it, give it a different name.`
67
+ : 'two custom signals cannot share a name - rename one of them.';
68
+ throw new Error(`Duplicate load signal name ${JSON.stringify(name)}: ${hint}`);
69
+ }
70
+ }
71
+ /**
72
+ * Returns an {@link SystemInfo} object with the following structure:
73
+ *
74
+ * ```javascript
75
+ * {
76
+ * isSystemIdle: Boolean,
77
+ * memInfo: Object,
78
+ * eventLoopInfo: Object,
79
+ * cpuInfo: Object
80
+ * }
81
+ * ```
82
+ *
83
+ * Where the `isSystemIdle` property is set to `false` if the system
84
+ * has been overloaded in the last `options.currentHistorySecs` seconds,
85
+ * and `true` otherwise.
86
+ */
87
+ getCurrentStatus() {
88
+ return this.#isSystemIdle(this.#currentHistoryMillis);
89
+ }
90
+ /**
91
+ * Returns an {@link SystemInfo} object with the following structure:
92
+ *
93
+ * ```javascript
94
+ * {
95
+ * isSystemIdle: Boolean,
96
+ * memInfo: Object,
97
+ * eventLoopInfo: Object,
98
+ * cpuInfo: Object
99
+ * }
100
+ * ```
101
+ *
102
+ * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
103
+ * `historySecs` seconds and `true` otherwise.
104
+ */
105
+ getHistoricalStatus() {
106
+ return this.#isSystemIdle(this.#historyMillis);
107
+ }
108
+ /**
109
+ * Returns a system status object.
110
+ */
111
+ #isSystemIdle(sampleDurationMillis) {
112
+ const result = {
113
+ isSystemIdle: true,
114
+ memInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
115
+ eventLoopInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
116
+ cpuInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
117
+ storageBackendInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
118
+ };
119
+ let loadSignalInfo;
120
+ for (const signal of this.#signals) {
121
+ const sample = signal.getSample(sampleDurationMillis);
122
+ const info = evaluateLoadSignalSample(sample, signal.overloadedRatio);
123
+ if (info.isOverloaded) {
124
+ result.isSystemIdle = false;
125
+ }
126
+ if (BUILTIN_SIGNAL_NAMES.has(signal.name)) {
127
+ result[signal.name] = info;
128
+ }
129
+ else {
130
+ loadSignalInfo ??= {};
131
+ loadSignalInfo[signal.name] = info;
132
+ }
133
+ }
134
+ if (loadSignalInfo) {
135
+ result.loadSignalInfo = loadSignalInfo;
136
+ }
137
+ return result;
138
+ }
139
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
+ * @ignore
4
+ */
5
+ export declare function weightedAvg(arrValues: number[], arrWeights: number[]): number;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
+ * @ignore
4
+ */
5
+ export function weightedAvg(arrValues, arrWeights) {
6
+ const result = arrValues
7
+ .map((value, i) => {
8
+ const weight = arrWeights[i];
9
+ const sum = value * weight;
10
+ return [sum, weight];
11
+ })
12
+ .reduce((p, c) => [p[0] + c[0], p[1] + c[1]], [0, 0]);
13
+ return result[0] / result[1];
14
+ }