@crawlee/core 4.0.0-beta.80 → 4.0.0-beta.81
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/autoscaling/autoscaled_pool.d.ts +9 -9
- package/autoscaling/autoscaled_pool.js +25 -25
- package/autoscaling/snapshotter.d.ts +3 -28
- package/autoscaling/snapshotter.js +0 -36
- package/autoscaling/system_status.d.ts +1 -1
- package/autoscaling/system_status.js +3 -3
- package/crawlers/statistics.d.ts +6 -6
- package/crawlers/statistics.js +8 -8
- package/package.json +5 -5
- package/proxy_configuration.d.ts +26 -14
- package/proxy_configuration.js +8 -11
- package/router.d.ts +1 -1
- package/session_pool/session.d.ts +2 -2
- package/session_pool/session.js +3 -3
- package/session_pool/session_pool.d.ts +23 -27
- package/session_pool/session_pool.js +29 -29
- package/storages/request_list.d.ts +16 -16
- package/storages/request_list.js +37 -37
- package/storages/request_queue.d.ts +20 -34
- package/storages/request_queue.js +13 -15
- package/storages/utils.d.ts +2 -2
- package/validators.js +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CrawleeLogger } from '../log.js';
|
|
2
2
|
import type { SnapshotterOptions } from './snapshotter.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { SystemStatusOptions } from './system_status.js';
|
|
4
4
|
export interface AutoscaledPoolOptions {
|
|
5
5
|
/**
|
|
6
6
|
* A function that performs an asynchronous resource-intensive task.
|
|
@@ -262,38 +262,38 @@ export declare class AutoscaledPool {
|
|
|
262
262
|
*
|
|
263
263
|
* It doesn't allow multiple concurrent runs of this method.
|
|
264
264
|
*/
|
|
265
|
-
|
|
265
|
+
private maybeRunTask;
|
|
266
266
|
/**
|
|
267
267
|
* Gets called every autoScaleIntervalSecs and evaluates the current system status.
|
|
268
268
|
* If the system IS NOT overloaded and the settings allow it, it scales up.
|
|
269
269
|
* If the system IS overloaded and the settings allow it, it scales down.
|
|
270
270
|
*/
|
|
271
|
-
|
|
271
|
+
private autoscale;
|
|
272
272
|
/**
|
|
273
273
|
* Scales the pool up by increasing
|
|
274
274
|
* the desired concurrency by the scaleUpStepRatio.
|
|
275
275
|
*
|
|
276
276
|
* @param systemStatus for logging
|
|
277
277
|
*/
|
|
278
|
-
|
|
278
|
+
private scaleUp;
|
|
279
279
|
/**
|
|
280
280
|
* Scales the pool down by decreasing
|
|
281
281
|
* the desired concurrency by the scaleDownStepRatio.
|
|
282
282
|
*
|
|
283
283
|
* @param systemStatus for logging
|
|
284
284
|
*/
|
|
285
|
-
|
|
285
|
+
private scaleDown;
|
|
286
286
|
/**
|
|
287
287
|
* If there are no running tasks and this.isFinishedFunction() returns true then closes
|
|
288
288
|
* the pool and resolves the pool's promise returned by the run() method.
|
|
289
289
|
*
|
|
290
290
|
* It doesn't allow multiple concurrent runs of this method.
|
|
291
291
|
*/
|
|
292
|
-
|
|
292
|
+
private maybeFinish;
|
|
293
293
|
/**
|
|
294
294
|
* Cleans up resources.
|
|
295
295
|
*/
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
private destroy;
|
|
297
|
+
private incrementTasksDonePerSecond;
|
|
298
|
+
private get isOverMaxRequestLimit();
|
|
299
299
|
}
|
|
@@ -128,9 +128,9 @@ export class AutoscaledPool {
|
|
|
128
128
|
this.isStopped = false;
|
|
129
129
|
this.resolve = null;
|
|
130
130
|
this.reject = null;
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
133
|
-
this.
|
|
131
|
+
this.autoscale = this.autoscale.bind(this);
|
|
132
|
+
this.maybeRunTask = this.maybeRunTask.bind(this);
|
|
133
|
+
this.incrementTasksDonePerSecond = this.incrementTasksDonePerSecond.bind(this);
|
|
134
134
|
// Create instances with correct options.
|
|
135
135
|
const ssoCopy = { ...systemStatusOptions };
|
|
136
136
|
ssoCopy.snapshotter ??= new Snapshotter({
|
|
@@ -203,14 +203,14 @@ export class AutoscaledPool {
|
|
|
203
203
|
await this.snapshotter.start();
|
|
204
204
|
await Promise.all(this.loadSignals.map((s) => s.start()));
|
|
205
205
|
// This interval checks the system status and updates the desired concurrency accordingly.
|
|
206
|
-
this.autoscaleInterval = betterSetInterval(this.
|
|
206
|
+
this.autoscaleInterval = betterSetInterval(this.autoscale, this.autoscaleIntervalMillis);
|
|
207
207
|
// This is here because if we scale down to let's say 1, then after each promise is finished
|
|
208
|
-
// this.
|
|
208
|
+
// this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
|
|
209
209
|
// in the crawler getting stuck and even after scaling up it never triggers another promise.
|
|
210
|
-
this.maybeRunInterval = betterSetInterval(this.
|
|
210
|
+
this.maybeRunInterval = betterSetInterval(this.maybeRunTask, this.maybeRunIntervalMillis);
|
|
211
211
|
if (this.maxTasksPerMinute !== Infinity) {
|
|
212
212
|
// Start the interval that resets the counter of tasks per minute.
|
|
213
|
-
this.tasksDonePerSecondInterval = betterSetInterval(this.
|
|
213
|
+
this.tasksDonePerSecondInterval = betterSetInterval(this.incrementTasksDonePerSecond, 1000);
|
|
214
214
|
}
|
|
215
215
|
try {
|
|
216
216
|
await poolPromise;
|
|
@@ -218,7 +218,7 @@ export class AutoscaledPool {
|
|
|
218
218
|
finally {
|
|
219
219
|
// If resolve is null, the pool is already destroyed.
|
|
220
220
|
if (this.resolve)
|
|
221
|
-
await this.
|
|
221
|
+
await this.destroy();
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
/**
|
|
@@ -236,7 +236,7 @@ export class AutoscaledPool {
|
|
|
236
236
|
this.isStopped = true;
|
|
237
237
|
if (this.resolve) {
|
|
238
238
|
this.resolve();
|
|
239
|
-
await this.
|
|
239
|
+
await this.destroy();
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
@@ -291,7 +291,7 @@ export class AutoscaledPool {
|
|
|
291
291
|
* every `maybeRunIntervalSecs` seconds. If you want to trigger the processing immediately, use this method.
|
|
292
292
|
*/
|
|
293
293
|
async notify() {
|
|
294
|
-
setImmediate(this.
|
|
294
|
+
setImmediate(this.maybeRunTask);
|
|
295
295
|
}
|
|
296
296
|
/**
|
|
297
297
|
* Starts a new task
|
|
@@ -301,7 +301,7 @@ export class AutoscaledPool {
|
|
|
301
301
|
*
|
|
302
302
|
* It doesn't allow multiple concurrent runs of this method.
|
|
303
303
|
*/
|
|
304
|
-
async
|
|
304
|
+
async maybeRunTask(intervalCallback) {
|
|
305
305
|
this.log.perf('Attempting to run a task.');
|
|
306
306
|
// Check if the function was invoked by the maybeRunInterval and use an empty function if not.
|
|
307
307
|
const done = intervalCallback || (() => { });
|
|
@@ -352,12 +352,12 @@ export class AutoscaledPool {
|
|
|
352
352
|
this.log.perf('Task will not run. No tasks are ready.');
|
|
353
353
|
done();
|
|
354
354
|
// No tasks could mean that we're finished with all tasks.
|
|
355
|
-
return this.
|
|
355
|
+
return this.maybeFinish();
|
|
356
356
|
}
|
|
357
357
|
// - we have already reached the maximum tasks per minute
|
|
358
358
|
// we need to check this *after* checking if a task is ready to prevent hanging the pool
|
|
359
359
|
// for an extra minute if there are no more tasks
|
|
360
|
-
if (this.
|
|
360
|
+
if (this.isOverMaxRequestLimit) {
|
|
361
361
|
this.log.perf('Task will not run. Maximum tasks per minute reached.');
|
|
362
362
|
return done();
|
|
363
363
|
}
|
|
@@ -367,7 +367,7 @@ export class AutoscaledPool {
|
|
|
367
367
|
this._tasksPerMinute[0]++;
|
|
368
368
|
// Try to run next task to build up concurrency,
|
|
369
369
|
// but defer it so it doesn't create a cycle.
|
|
370
|
-
setImmediate(this.
|
|
370
|
+
setImmediate(this.maybeRunTask);
|
|
371
371
|
// We need to restart interval here, so that it doesn't get blocked by a stalled task.
|
|
372
372
|
done();
|
|
373
373
|
// Execute the current task.
|
|
@@ -381,7 +381,7 @@ export class AutoscaledPool {
|
|
|
381
381
|
this.log.perf('Task finished.');
|
|
382
382
|
this._currentConcurrency--;
|
|
383
383
|
// Run task after the previous one finished.
|
|
384
|
-
setImmediate(this.
|
|
384
|
+
setImmediate(this.maybeRunTask);
|
|
385
385
|
}
|
|
386
386
|
catch (e) {
|
|
387
387
|
const err = e;
|
|
@@ -404,12 +404,12 @@ export class AutoscaledPool {
|
|
|
404
404
|
* If the system IS NOT overloaded and the settings allow it, it scales up.
|
|
405
405
|
* If the system IS overloaded and the settings allow it, it scales down.
|
|
406
406
|
*/
|
|
407
|
-
|
|
407
|
+
autoscale(intervalCallback) {
|
|
408
408
|
// Don't scale if paused.
|
|
409
409
|
if (this.isStopped)
|
|
410
410
|
return intervalCallback();
|
|
411
411
|
// Don't scale if we've hit the maximum requests per minute
|
|
412
|
-
if (this.
|
|
412
|
+
if (this.isOverMaxRequestLimit)
|
|
413
413
|
return intervalCallback();
|
|
414
414
|
// Only scale up if:
|
|
415
415
|
// - system has not been overloaded lately.
|
|
@@ -421,14 +421,14 @@ export class AutoscaledPool {
|
|
|
421
421
|
const minCurrentConcurrency = Math.floor(this._desiredConcurrency * this.desiredConcurrencyRatio);
|
|
422
422
|
const weAreReachingDesiredConcurrency = this._currentConcurrency >= minCurrentConcurrency;
|
|
423
423
|
if (isSystemIdle && weAreNotAtMax && weAreReachingDesiredConcurrency)
|
|
424
|
-
this.
|
|
424
|
+
this.scaleUp(systemStatus);
|
|
425
425
|
// Always scale down if:
|
|
426
426
|
// - the system has been overloaded lately.
|
|
427
427
|
const isSystemOverloaded = !isSystemIdle;
|
|
428
428
|
// - we're over min concurrency.
|
|
429
429
|
const weAreNotAtMin = this._desiredConcurrency > this._minConcurrency;
|
|
430
430
|
if (isSystemOverloaded && weAreNotAtMin)
|
|
431
|
-
this.
|
|
431
|
+
this.scaleDown(systemStatus);
|
|
432
432
|
// On periodic intervals, print comprehensive log information
|
|
433
433
|
if (this.loggingIntervalMillis > 0) {
|
|
434
434
|
const now = Date.now();
|
|
@@ -453,7 +453,7 @@ export class AutoscaledPool {
|
|
|
453
453
|
*
|
|
454
454
|
* @param systemStatus for logging
|
|
455
455
|
*/
|
|
456
|
-
|
|
456
|
+
scaleUp(systemStatus) {
|
|
457
457
|
const step = Math.ceil(this._desiredConcurrency * this.scaleUpStepRatio);
|
|
458
458
|
this._desiredConcurrency = Math.min(this._maxConcurrency, this._desiredConcurrency + step);
|
|
459
459
|
this.log.debug('scaling up', {
|
|
@@ -468,7 +468,7 @@ export class AutoscaledPool {
|
|
|
468
468
|
*
|
|
469
469
|
* @param systemStatus for logging
|
|
470
470
|
*/
|
|
471
|
-
|
|
471
|
+
scaleDown(systemStatus) {
|
|
472
472
|
const step = Math.ceil(this._desiredConcurrency * this.scaleDownStepRatio);
|
|
473
473
|
this._desiredConcurrency = Math.max(this._minConcurrency, this._desiredConcurrency - step);
|
|
474
474
|
this.log.debug('scaling down', {
|
|
@@ -483,7 +483,7 @@ export class AutoscaledPool {
|
|
|
483
483
|
*
|
|
484
484
|
* It doesn't allow multiple concurrent runs of this method.
|
|
485
485
|
*/
|
|
486
|
-
async
|
|
486
|
+
async maybeFinish() {
|
|
487
487
|
if (this.queryingIsFinished)
|
|
488
488
|
return;
|
|
489
489
|
if (this._currentConcurrency > 0)
|
|
@@ -509,7 +509,7 @@ export class AutoscaledPool {
|
|
|
509
509
|
/**
|
|
510
510
|
* Cleans up resources.
|
|
511
511
|
*/
|
|
512
|
-
async
|
|
512
|
+
async destroy() {
|
|
513
513
|
this.resolve = null;
|
|
514
514
|
this.reject = null;
|
|
515
515
|
betterClearInterval(this.autoscaleInterval);
|
|
@@ -520,12 +520,12 @@ export class AutoscaledPool {
|
|
|
520
520
|
await this.snapshotter.stop();
|
|
521
521
|
await Promise.all(this.loadSignals.map((s) => s.stop()));
|
|
522
522
|
}
|
|
523
|
-
|
|
523
|
+
incrementTasksDonePerSecond(intervalCallback) {
|
|
524
524
|
this._tasksPerMinute.unshift(0);
|
|
525
525
|
this._tasksPerMinute.pop();
|
|
526
526
|
return intervalCallback();
|
|
527
527
|
}
|
|
528
|
-
get
|
|
528
|
+
get isOverMaxRequestLimit() {
|
|
529
529
|
if (this.maxTasksPerMinute === Infinity) {
|
|
530
530
|
return false;
|
|
531
531
|
}
|
|
@@ -6,7 +6,6 @@ import type { CpuSnapshot } from './cpu_load_signal.js';
|
|
|
6
6
|
import type { EventLoopSnapshot } from './event_loop_load_signal.js';
|
|
7
7
|
import type { LoadSignal } from './load_signal.js';
|
|
8
8
|
import type { MemorySnapshot } from './memory_load_signal.js';
|
|
9
|
-
import type { SystemInfo } from './system_status.js';
|
|
10
9
|
export interface SnapshotterOptions {
|
|
11
10
|
/**
|
|
12
11
|
* Defines the interval of measuring the event loop response time.
|
|
@@ -77,9 +76,9 @@ export interface SnapshotterOptions {
|
|
|
77
76
|
* @category Scaling
|
|
78
77
|
*/
|
|
79
78
|
export declare class Snapshotter {
|
|
80
|
-
log: CrawleeLogger;
|
|
81
|
-
client: StorageBackend;
|
|
82
|
-
config: Configuration;
|
|
79
|
+
readonly log: CrawleeLogger;
|
|
80
|
+
readonly client: StorageBackend;
|
|
81
|
+
readonly config: Configuration;
|
|
83
82
|
private readonly memorySignal;
|
|
84
83
|
private readonly eventLoopSignal;
|
|
85
84
|
private readonly cpuSignal;
|
|
@@ -125,28 +124,4 @@ export declare class Snapshotter {
|
|
|
125
124
|
* by the sampleDurationMillis parameter. If omitted, it returns a full snapshot history.
|
|
126
125
|
*/
|
|
127
126
|
getClientSample(sampleDurationMillis?: number): ClientSnapshot[];
|
|
128
|
-
/**
|
|
129
|
-
* @deprecated Kept for backward compatibility.
|
|
130
|
-
*/
|
|
131
|
-
protected _snapshotMemory(systemInfo: SystemInfo): void;
|
|
132
|
-
/**
|
|
133
|
-
* @deprecated Kept for backward compatibility.
|
|
134
|
-
*/
|
|
135
|
-
protected _memoryOverloadWarning(systemInfo: SystemInfo): void;
|
|
136
|
-
/**
|
|
137
|
-
* @deprecated Kept for backward compatibility.
|
|
138
|
-
*/
|
|
139
|
-
protected _snapshotEventLoop(intervalCallback: () => unknown): void;
|
|
140
|
-
/**
|
|
141
|
-
* @deprecated Kept for backward compatibility.
|
|
142
|
-
*/
|
|
143
|
-
protected _snapshotCpu(systemInfo: SystemInfo): void;
|
|
144
|
-
/**
|
|
145
|
-
* @deprecated Kept for backward compatibility.
|
|
146
|
-
*/
|
|
147
|
-
protected _snapshotClient(intervalCallback: () => unknown): void;
|
|
148
|
-
/**
|
|
149
|
-
* @deprecated Pruning is now handled by individual signals.
|
|
150
|
-
*/
|
|
151
|
-
protected _pruneSnapshots(_snapshots: any[], _now: Date): void;
|
|
152
127
|
}
|
|
@@ -150,40 +150,4 @@ export class Snapshotter {
|
|
|
150
150
|
getClientSample(sampleDurationMillis) {
|
|
151
151
|
return this.clientSignal.getSample(sampleDurationMillis);
|
|
152
152
|
}
|
|
153
|
-
/**
|
|
154
|
-
* @deprecated Kept for backward compatibility.
|
|
155
|
-
*/
|
|
156
|
-
_snapshotMemory(systemInfo) {
|
|
157
|
-
this.memorySignal._onSystemInfo(systemInfo);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* @deprecated Kept for backward compatibility.
|
|
161
|
-
*/
|
|
162
|
-
_memoryOverloadWarning(systemInfo) {
|
|
163
|
-
this.memorySignal._memoryOverloadWarning(systemInfo);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* @deprecated Kept for backward compatibility.
|
|
167
|
-
*/
|
|
168
|
-
_snapshotEventLoop(intervalCallback) {
|
|
169
|
-
this.eventLoopSignal.handle(intervalCallback);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* @deprecated Kept for backward compatibility.
|
|
173
|
-
*/
|
|
174
|
-
_snapshotCpu(systemInfo) {
|
|
175
|
-
this.cpuSignal.handle(systemInfo);
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* @deprecated Kept for backward compatibility.
|
|
179
|
-
*/
|
|
180
|
-
_snapshotClient(intervalCallback) {
|
|
181
|
-
this.clientSignal.handle(intervalCallback);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* @deprecated Pruning is now handled by individual signals.
|
|
185
|
-
*/
|
|
186
|
-
_pruneSnapshots(_snapshots, _now) {
|
|
187
|
-
// no-op — signals prune themselves
|
|
188
|
-
}
|
|
189
153
|
}
|
|
@@ -75,7 +75,7 @@ export class SystemStatus {
|
|
|
75
75
|
* and `true` otherwise.
|
|
76
76
|
*/
|
|
77
77
|
getCurrentStatus() {
|
|
78
|
-
return this.
|
|
78
|
+
return this.isSystemIdle(this.currentHistoryMillis);
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Returns an {@link SystemInfo} object with the following structure:
|
|
@@ -94,12 +94,12 @@ export class SystemStatus {
|
|
|
94
94
|
* (which is configurable in the {@link Snapshotter}) and `true` otherwise.
|
|
95
95
|
*/
|
|
96
96
|
getHistoricalStatus() {
|
|
97
|
-
return this.
|
|
97
|
+
return this.isSystemIdle();
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
100
|
* Returns a system status object.
|
|
101
101
|
*/
|
|
102
|
-
|
|
102
|
+
isSystemIdle(sampleDurationMillis) {
|
|
103
103
|
const result = {
|
|
104
104
|
isSystemIdle: true,
|
|
105
105
|
memInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
|
package/crawlers/statistics.d.ts
CHANGED
|
@@ -26,11 +26,11 @@ export declare class Statistics {
|
|
|
26
26
|
/**
|
|
27
27
|
* An error tracker for final retry errors.
|
|
28
28
|
*/
|
|
29
|
-
errorTracker: ErrorTracker;
|
|
29
|
+
readonly errorTracker: ErrorTracker;
|
|
30
30
|
/**
|
|
31
31
|
* An error tracker for retry errors prior to the final retry.
|
|
32
32
|
*/
|
|
33
|
-
errorTrackerRetry: ErrorTracker;
|
|
33
|
+
readonly errorTrackerRetry: ErrorTracker;
|
|
34
34
|
/**
|
|
35
35
|
* Statistic instance id.
|
|
36
36
|
*/
|
|
@@ -44,7 +44,7 @@ export declare class Statistics {
|
|
|
44
44
|
*/
|
|
45
45
|
readonly requestRetryHistogram: number[];
|
|
46
46
|
protected keyValueStore?: KeyValueStore;
|
|
47
|
-
protected persistStateKey: string;
|
|
47
|
+
protected readonly persistStateKey: string;
|
|
48
48
|
private logIntervalMillis;
|
|
49
49
|
private logMessage;
|
|
50
50
|
private listener;
|
|
@@ -113,7 +113,7 @@ export declare class Statistics {
|
|
|
113
113
|
* Stops logging and remove event listeners, then persist
|
|
114
114
|
*/
|
|
115
115
|
stopCapturing(): Promise<void>;
|
|
116
|
-
|
|
116
|
+
private saveRetryCountForJob;
|
|
117
117
|
/**
|
|
118
118
|
* Persist internal state to the key value store
|
|
119
119
|
* @param options - Override the persistence options provided in the constructor
|
|
@@ -122,8 +122,8 @@ export declare class Statistics {
|
|
|
122
122
|
/**
|
|
123
123
|
* Loads the current statistic from the key value store if any
|
|
124
124
|
*/
|
|
125
|
-
protected
|
|
126
|
-
|
|
125
|
+
protected maybeLoadStatistics(): Promise<void>;
|
|
126
|
+
private teardown;
|
|
127
127
|
/**
|
|
128
128
|
* Make this class serializable when called with `JSON.stringify(statsInstance)` directly
|
|
129
129
|
* or through `keyValueStore.setValue('KEY', statsInstance)`
|
package/crawlers/statistics.js
CHANGED
|
@@ -129,7 +129,7 @@ export class Statistics {
|
|
|
129
129
|
this.requestRetryHistogram.length = 0;
|
|
130
130
|
this.requestsInProgress.clear();
|
|
131
131
|
this.instanceStart = Date.now();
|
|
132
|
-
this.
|
|
132
|
+
this.teardown();
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* @param options - Override the persistence options provided in the constructor
|
|
@@ -175,7 +175,7 @@ export class Statistics {
|
|
|
175
175
|
const jobDurationMillis = job.finish();
|
|
176
176
|
this.state.requestsFinished++;
|
|
177
177
|
this.state.requestTotalFinishedDurationMillis += jobDurationMillis;
|
|
178
|
-
this.
|
|
178
|
+
this.saveRetryCountForJob(retryCount);
|
|
179
179
|
if (jobDurationMillis < this.state.requestMinDurationMillis)
|
|
180
180
|
this.state.requestMinDurationMillis = jobDurationMillis;
|
|
181
181
|
if (jobDurationMillis > this.state.requestMaxDurationMillis)
|
|
@@ -192,7 +192,7 @@ export class Statistics {
|
|
|
192
192
|
return;
|
|
193
193
|
this.state.requestTotalFailedDurationMillis += job.finish();
|
|
194
194
|
this.state.requestsFailed++;
|
|
195
|
-
this.
|
|
195
|
+
this.saveRetryCountForJob(retryCount);
|
|
196
196
|
this.requestsInProgress.delete(id);
|
|
197
197
|
}
|
|
198
198
|
/**
|
|
@@ -230,7 +230,7 @@ export class Statistics {
|
|
|
230
230
|
this.state.crawlerStartedAt = new Date();
|
|
231
231
|
}
|
|
232
232
|
if (this.persistenceOptions.enable) {
|
|
233
|
-
await this.
|
|
233
|
+
await this.maybeLoadStatistics();
|
|
234
234
|
this.events.on("persistState" /* EventType.PERSIST_STATE */, this.listener);
|
|
235
235
|
}
|
|
236
236
|
this.logInterval = setInterval(() => {
|
|
@@ -244,11 +244,11 @@ export class Statistics {
|
|
|
244
244
|
* Stops logging and remove event listeners, then persist
|
|
245
245
|
*/
|
|
246
246
|
async stopCapturing() {
|
|
247
|
-
this.
|
|
247
|
+
this.teardown();
|
|
248
248
|
this.state.crawlerFinishedAt = new Date();
|
|
249
249
|
await this.persistState();
|
|
250
250
|
}
|
|
251
|
-
|
|
251
|
+
saveRetryCountForJob(retryCount) {
|
|
252
252
|
if (retryCount > 0)
|
|
253
253
|
this.state.requestsRetries++;
|
|
254
254
|
this.requestRetryHistogram[retryCount] ??= 0;
|
|
@@ -274,7 +274,7 @@ export class Statistics {
|
|
|
274
274
|
/**
|
|
275
275
|
* Loads the current statistic from the key value store if any
|
|
276
276
|
*/
|
|
277
|
-
async
|
|
277
|
+
async maybeLoadStatistics() {
|
|
278
278
|
// this might be called before startCapturing was called without using await, should not crash
|
|
279
279
|
if (!this.keyValueStore) {
|
|
280
280
|
return;
|
|
@@ -309,7 +309,7 @@ export class Statistics {
|
|
|
309
309
|
this.instanceStart = Date.now() - (+this.state.statsPersistedAt - savedState.crawlerLastStartTimestamp);
|
|
310
310
|
this.log.debug('Loaded from KeyValueStore');
|
|
311
311
|
}
|
|
312
|
-
|
|
312
|
+
teardown() {
|
|
313
313
|
// this can be called before a call to startCapturing happens (or in a 'finally' block)
|
|
314
314
|
// Only unsubscribe if event manager was already resolved — avoid eagerly resolving it
|
|
315
315
|
// (e.g. during the constructor's reset() call, which would capture the wrong context)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/core",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.81",
|
|
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"
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"@apify/pseudo_url": "^2.0.59",
|
|
54
54
|
"@apify/timeout": "^0.3.2",
|
|
55
55
|
"@apify/utilities": "^2.15.5",
|
|
56
|
-
"@crawlee/fs-storage": "4.0.0-beta.
|
|
57
|
-
"@crawlee/types": "4.0.0-beta.
|
|
58
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
56
|
+
"@crawlee/fs-storage": "4.0.0-beta.81",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.81",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.81",
|
|
59
59
|
"@sapphire/async-queue": "^1.5.5",
|
|
60
60
|
"@sapphire/shapeshift": "^4.0.0",
|
|
61
61
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "80dc6b4fc82237e63a51a71153809ec8dfd0cc50"
|
|
83
83
|
}
|
package/proxy_configuration.d.ts
CHANGED
|
@@ -24,6 +24,23 @@ export interface ProxyConfigurationOptions {
|
|
|
24
24
|
interface NewUrlOptions {
|
|
25
25
|
request?: Request;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Minimal contract that any object passed to a crawler as its `proxyConfiguration`
|
|
29
|
+
* option must satisfy.
|
|
30
|
+
*
|
|
31
|
+
* Implement this interface to plug a custom proxy-provisioning strategy into any Crawlee
|
|
32
|
+
* crawler — for example a remote proxy service or a thin wrapper around the built-in
|
|
33
|
+
* `ProxyConfiguration` with different rotation rules. *
|
|
34
|
+
*
|
|
35
|
+
* @category Scaling
|
|
36
|
+
*/
|
|
37
|
+
export interface IProxyConfiguration {
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new {@link ProxyInfo} object describing the proxy to use for the given
|
|
40
|
+
* request. Returns `undefined` when no proxy should be used.
|
|
41
|
+
*/
|
|
42
|
+
newProxyInfo(options?: NewUrlOptions): Promise<ProxyInfo | undefined>;
|
|
43
|
+
}
|
|
27
44
|
/**
|
|
28
45
|
* Configures connection to a proxy server with the provided options. Proxy servers are used to prevent target websites from blocking
|
|
29
46
|
* your crawlers based on IP address rate limits or blacklists. Setting proxy configuration in your crawlers automatically configures
|
|
@@ -52,14 +69,11 @@ interface NewUrlOptions {
|
|
|
52
69
|
* ```
|
|
53
70
|
* @category Scaling
|
|
54
71
|
*/
|
|
55
|
-
export declare class ProxyConfiguration {
|
|
56
|
-
isManInTheMiddle
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
protected newUrlFunction?: ProxyConfigurationFunction;
|
|
61
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
62
|
-
protected log: import("@crawlee/types").CrawleeLogger;
|
|
72
|
+
export declare class ProxyConfiguration implements IProxyConfiguration {
|
|
73
|
+
readonly isManInTheMiddle = false;
|
|
74
|
+
private nextCustomUrlIndex;
|
|
75
|
+
private proxyUrls?;
|
|
76
|
+
private newUrlFunction?;
|
|
63
77
|
/**
|
|
64
78
|
* Creates a {@link ProxyConfiguration} instance based on the provided options. Proxy servers are used to prevent target websites from
|
|
65
79
|
* blocking your crawlers based on IP address rate limits or blacklists. Setting proxy configuration in your crawlers automatically configures
|
|
@@ -98,14 +112,12 @@ export declare class ProxyConfiguration {
|
|
|
98
112
|
* For example, `http://bob:password123@proxy.example.com:8000`
|
|
99
113
|
*/
|
|
100
114
|
newUrl(options?: NewUrlOptions): Promise<string | undefined>;
|
|
101
|
-
|
|
115
|
+
private handleProxyUrlsList;
|
|
102
116
|
/**
|
|
103
117
|
* Calls the custom newUrlFunction and checks format of its return value
|
|
104
118
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
protected _throwCannotCombineCustomMethods(): never;
|
|
109
|
-
protected _throwNoOptionsProvided(): never;
|
|
119
|
+
private callNewUrlFunction;
|
|
120
|
+
private throwCannotCombineCustomMethods;
|
|
121
|
+
private throwNoOptionsProvided;
|
|
110
122
|
}
|
|
111
123
|
export {};
|
package/proxy_configuration.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import ow from 'ow';
|
|
2
|
-
import { serviceLocator } from './service_locator.js';
|
|
3
2
|
/**
|
|
4
3
|
* Configures connection to a proxy server with the provided options. Proxy servers are used to prevent target websites from blocking
|
|
5
4
|
* your crawlers based on IP address rate limits or blacklists. Setting proxy configuration in your crawlers automatically configures
|
|
@@ -32,9 +31,7 @@ export class ProxyConfiguration {
|
|
|
32
31
|
isManInTheMiddle = false;
|
|
33
32
|
nextCustomUrlIndex = 0;
|
|
34
33
|
proxyUrls;
|
|
35
|
-
usedProxyUrls = new Map();
|
|
36
34
|
newUrlFunction;
|
|
37
|
-
log = serviceLocator.getLogger().child({ prefix: 'ProxyConfiguration' });
|
|
38
35
|
/**
|
|
39
36
|
* Creates a {@link ProxyConfiguration} instance based on the provided options. Proxy servers are used to prevent target websites from
|
|
40
37
|
* blocking your crawlers based on IP address rate limits or blacklists. Setting proxy configuration in your crawlers automatically configures
|
|
@@ -67,9 +64,9 @@ export class ProxyConfiguration {
|
|
|
67
64
|
}));
|
|
68
65
|
const { proxyUrls, newUrlFunction } = options;
|
|
69
66
|
if (proxyUrls && newUrlFunction)
|
|
70
|
-
this.
|
|
67
|
+
this.throwCannotCombineCustomMethods();
|
|
71
68
|
if (!proxyUrls && !newUrlFunction && validateRequired)
|
|
72
|
-
this.
|
|
69
|
+
this.throwNoOptionsProvided();
|
|
73
70
|
this.proxyUrls = proxyUrls;
|
|
74
71
|
this.newUrlFunction = newUrlFunction;
|
|
75
72
|
}
|
|
@@ -103,17 +100,17 @@ export class ProxyConfiguration {
|
|
|
103
100
|
*/
|
|
104
101
|
async newUrl(options) {
|
|
105
102
|
if (this.newUrlFunction) {
|
|
106
|
-
return (await this.
|
|
103
|
+
return (await this.callNewUrlFunction({ request: options?.request })) ?? undefined;
|
|
107
104
|
}
|
|
108
|
-
return this.
|
|
105
|
+
return this.handleProxyUrlsList() ?? undefined;
|
|
109
106
|
}
|
|
110
|
-
|
|
107
|
+
handleProxyUrlsList() {
|
|
111
108
|
return this.proxyUrls[this.nextCustomUrlIndex++ % this.proxyUrls.length];
|
|
112
109
|
}
|
|
113
110
|
/**
|
|
114
111
|
* Calls the custom newUrlFunction and checks format of its return value
|
|
115
112
|
*/
|
|
116
|
-
async
|
|
113
|
+
async callNewUrlFunction(options) {
|
|
117
114
|
const proxyUrl = await this.newUrlFunction(options);
|
|
118
115
|
try {
|
|
119
116
|
if (proxyUrl) {
|
|
@@ -125,10 +122,10 @@ export class ProxyConfiguration {
|
|
|
125
122
|
throw new Error(`The provided newUrlFunction did not return a valid URL.\nCause: ${err.message}`);
|
|
126
123
|
}
|
|
127
124
|
}
|
|
128
|
-
|
|
125
|
+
throwCannotCombineCustomMethods() {
|
|
129
126
|
throw new Error('Cannot combine custom proxies "options.proxyUrls" with custom generating function "options.newUrlFunction".');
|
|
130
127
|
}
|
|
131
|
-
|
|
128
|
+
throwNoOptionsProvided() {
|
|
132
129
|
throw new Error('One of "options.proxyUrls" or "options.newUrlFunction" needs to be provided.');
|
|
133
130
|
}
|
|
134
131
|
}
|
package/router.d.ts
CHANGED
|
@@ -168,7 +168,7 @@ export declare class Router<Context extends Omit<RestrictedCrawlingContext, 'enq
|
|
|
168
168
|
* use Router.create() instead!
|
|
169
169
|
* @ignore
|
|
170
170
|
*/
|
|
171
|
-
|
|
171
|
+
private constructor();
|
|
172
172
|
/**
|
|
173
173
|
* Registers new route handler for given label. When the router declares a route map, the
|
|
174
174
|
* `label` is restricted to the declared labels and `request.userData` is typed accordingly.
|
|
@@ -66,7 +66,7 @@ export interface SessionOptions {
|
|
|
66
66
|
*/
|
|
67
67
|
export declare class Session implements ISession {
|
|
68
68
|
readonly id: string;
|
|
69
|
-
userData: Dictionary;
|
|
69
|
+
readonly userData: Dictionary;
|
|
70
70
|
private _maxErrorScore;
|
|
71
71
|
private _errorScoreDecrement;
|
|
72
72
|
private _createdAt;
|
|
@@ -157,5 +157,5 @@ export declare class Session implements ISession {
|
|
|
157
157
|
/**
|
|
158
158
|
* Checks if session is not usable. if it is not retires the session.
|
|
159
159
|
*/
|
|
160
|
-
|
|
160
|
+
private maybeSelfRetire;
|
|
161
161
|
}
|