@crawlee/core 4.0.0-beta.92 → 4.0.0-beta.94
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 +10 -10
- package/autoscaling/autoscaled_pool.js +4 -3
- package/cookie_utils.js +4 -2
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/package.json +5 -5
- package/storages/request_queue.js +2 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ConcurrencyConsumer, IConcurrencySystem } from './concurrency_system.js';
|
|
2
2
|
import type { CrawleeLogger } from '../log.js';
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
* `runTaskFunction`.
|
|
4
|
+
* The two predicates that steer a task loop: *is there work ready?* and *are we done?* These are the parts of the loop
|
|
5
|
+
* a caller legitimately overrides, as opposed to the task itself (`runTaskFunction`), which the loop's driver owns.
|
|
7
6
|
*/
|
|
8
|
-
export interface
|
|
7
|
+
export interface TaskLoopPredicates {
|
|
9
8
|
/**
|
|
10
9
|
* A function that indicates whether `runTaskFunction` should be called.
|
|
11
10
|
* This function is called every time there is free capacity for a new task and it should
|
|
@@ -15,14 +14,14 @@ export interface AutoscaledPoolPredicateOptions {
|
|
|
15
14
|
isTaskReadyFunction?: () => Promise<boolean>;
|
|
16
15
|
/**
|
|
17
16
|
* A function that is called only when there are no tasks to be processed.
|
|
18
|
-
* If it resolves to `true` then the
|
|
17
|
+
* If it resolves to `true` then the run finishes. Being called only
|
|
19
18
|
* when there are no tasks being processed means that as long as `isTaskReadyFunction()`
|
|
20
19
|
* keeps resolving to `true`, `isFinishedFunction()` will never be called.
|
|
21
|
-
* To abort a run, use the {@link AutoscaledPool.abort} method.
|
|
22
20
|
*/
|
|
23
21
|
isFinishedFunction?: () => Promise<boolean>;
|
|
24
22
|
}
|
|
25
|
-
|
|
23
|
+
/** @internal */
|
|
24
|
+
export interface AutoscaledPoolOptions extends TaskLoopPredicates {
|
|
26
25
|
/**
|
|
27
26
|
* The governor that decides whether there is free compute for one more task. Typically a
|
|
28
27
|
* {@link ConcurrencySystem}, but any {@link IConcurrencySystem} works. Share a single instance across
|
|
@@ -62,8 +61,8 @@ export interface AutoscaledPoolOptions extends AutoscaledPoolPredicateOptions {
|
|
|
62
61
|
*
|
|
63
62
|
* Before running the pool, you need to implement the following three functions:
|
|
64
63
|
* {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
|
|
65
|
-
* {@link
|
|
66
|
-
* {@link
|
|
64
|
+
* {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
|
|
65
|
+
* {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
|
|
67
66
|
*
|
|
68
67
|
* The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
|
|
69
68
|
* The pool periodically queries `isTaskReadyFunction` for more tasks, managing optimal concurrency, until the function
|
|
@@ -104,7 +103,8 @@ export interface AutoscaledPoolOptions extends AutoscaledPoolPredicateOptions {
|
|
|
104
103
|
* await concurrencySystem.stop();
|
|
105
104
|
* }
|
|
106
105
|
* ```
|
|
107
|
-
*
|
|
106
|
+
*
|
|
107
|
+
* @internal
|
|
108
108
|
*/
|
|
109
109
|
export declare class AutoscaledPool {
|
|
110
110
|
private readonly log;
|
|
@@ -10,8 +10,8 @@ import { serviceLocator } from '../service_locator.js';
|
|
|
10
10
|
*
|
|
11
11
|
* Before running the pool, you need to implement the following three functions:
|
|
12
12
|
* {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
|
|
13
|
-
* {@link
|
|
14
|
-
* {@link
|
|
13
|
+
* {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
|
|
14
|
+
* {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
|
|
15
15
|
*
|
|
16
16
|
* The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
|
|
17
17
|
* The pool periodically queries `isTaskReadyFunction` for more tasks, managing optimal concurrency, until the function
|
|
@@ -52,7 +52,8 @@ import { serviceLocator } from '../service_locator.js';
|
|
|
52
52
|
* await concurrencySystem.stop();
|
|
53
53
|
* }
|
|
54
54
|
* ```
|
|
55
|
-
*
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
56
57
|
*/
|
|
57
58
|
export class AutoscaledPool {
|
|
58
59
|
log;
|
package/cookie_utils.js
CHANGED
|
@@ -103,8 +103,10 @@ export function mergeCookies(url, sourceCookies) {
|
|
|
103
103
|
if (!cookieString)
|
|
104
104
|
continue;
|
|
105
105
|
const cookie = Cookie.parse(cookieString);
|
|
106
|
-
if (!cookie)
|
|
107
|
-
|
|
106
|
+
if (!cookie) {
|
|
107
|
+
serviceLocator.getLogger().warning(`Skipping malformed cookie fragment: '${cookieString}'`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
108
110
|
const similarKeyCookie = jar.getCookiesSync(url).find((c) => {
|
|
109
111
|
return cookie.key !== c.key && cookie.key.toLowerCase() === c.key.toLowerCase();
|
|
110
112
|
});
|
package/iterables.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts any iterable or async iterable to an async iterable.
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* @yields Each item from the input iterable
|
|
6
|
+
*
|
|
7
|
+
* **Example usage:**
|
|
8
|
+
* ```ts
|
|
9
|
+
* const syncArray = [1, 2, 3];
|
|
10
|
+
* for await (const item of asyncifyIterable(syncArray)) {
|
|
11
|
+
* console.log(item); // 1, 2, 3
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function asyncifyIterable<T>(iterable: Iterable<T> | AsyncIterable<T>): AsyncIterable<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Lazily splits the input async iterable into chunks of specified size.
|
|
18
|
+
* The last chunk may contain fewer items if the total number of items
|
|
19
|
+
* is not evenly divisible by the chunk size.
|
|
20
|
+
* @internal
|
|
21
|
+
*
|
|
22
|
+
* @yields Arrays of items, each containing up to chunkSize items
|
|
23
|
+
*
|
|
24
|
+
* **Example usage:**
|
|
25
|
+
* ```ts
|
|
26
|
+
* const numbers = async function* () {
|
|
27
|
+
* for (let i = 1; i <= 10; i++) yield i;
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* for await (const chunk of chunkedAsyncIterable(numbers(), 3)) {
|
|
31
|
+
* console.log(chunk); // [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function chunkedAsyncIterable<T>(iterable: AsyncIterable<T> | Iterable<T>, chunkSize: number | (() => number)): AsyncIterable<T[]>;
|
|
36
|
+
/**
|
|
37
|
+
* An async iterator that also supports peeking at the next value without consuming it.
|
|
38
|
+
* Extends both AsyncIterator and AsyncIterable interfaces.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export interface PeekableAsyncIterator<T> extends AsyncIterator<T>, AsyncIterable<T> {
|
|
42
|
+
/**
|
|
43
|
+
* Peeks at the next value without consuming it from the iterator.
|
|
44
|
+
* Subsequent calls to peek() will return the same value until next() is called.
|
|
45
|
+
*
|
|
46
|
+
* @returns Promise that resolves to the next value, or undefined if the iterator is exhausted
|
|
47
|
+
*/
|
|
48
|
+
peek(): Promise<T | undefined>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* An async iterable that yields peekable async iterators.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export interface PeekableAsyncIterable<T> extends AsyncIterable<T> {
|
|
55
|
+
[Symbol.asyncIterator](): PeekableAsyncIterator<T>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Wraps an async iterable to provide peek functionality, allowing you to look at
|
|
59
|
+
* the next value without consuming it from the iterator.
|
|
60
|
+
* @internal
|
|
61
|
+
*
|
|
62
|
+
* @param iterable - The async iterable to make peekable
|
|
63
|
+
*
|
|
64
|
+
* **Example usage:**
|
|
65
|
+
* ```ts
|
|
66
|
+
* const numbers = async function* () {
|
|
67
|
+
* yield 1; yield 2; yield 3;
|
|
68
|
+
* };
|
|
69
|
+
*
|
|
70
|
+
* const peekable = peekableAsyncIterable(numbers());
|
|
71
|
+
* const iterator = peekable[Symbol.asyncIterator]();
|
|
72
|
+
*
|
|
73
|
+
* console.log(await iterator.peek()); // 1 (doesn't consume)
|
|
74
|
+
* console.log(await iterator.peek()); // 1 (still doesn't consume)
|
|
75
|
+
* console.log(await iterator.next()); // { value: 1, done: false } (now consumed)
|
|
76
|
+
* console.log(await iterator.peek()); // 2 (next value)
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function peekableAsyncIterable<T>(iterable: AsyncIterable<T> | Iterable<T>): PeekableAsyncIterable<T>;
|
package/iterables.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
/**
|
|
3
|
+
* Converts any iterable or async iterable to an async iterable.
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* @yields Each item from the input iterable
|
|
7
|
+
*
|
|
8
|
+
* **Example usage:**
|
|
9
|
+
* ```ts
|
|
10
|
+
* const syncArray = [1, 2, 3];
|
|
11
|
+
* for await (const item of asyncifyIterable(syncArray)) {
|
|
12
|
+
* console.log(item); // 1, 2, 3
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export async function* asyncifyIterable(iterable) {
|
|
17
|
+
yield* iterable;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Lazily splits the input async iterable into chunks of specified size.
|
|
21
|
+
* The last chunk may contain fewer items if the total number of items
|
|
22
|
+
* is not evenly divisible by the chunk size.
|
|
23
|
+
* @internal
|
|
24
|
+
*
|
|
25
|
+
* @yields Arrays of items, each containing up to chunkSize items
|
|
26
|
+
*
|
|
27
|
+
* **Example usage:**
|
|
28
|
+
* ```ts
|
|
29
|
+
* const numbers = async function* () {
|
|
30
|
+
* for (let i = 1; i <= 10; i++) yield i;
|
|
31
|
+
* };
|
|
32
|
+
*
|
|
33
|
+
* for await (const chunk of chunkedAsyncIterable(numbers(), 3)) {
|
|
34
|
+
* console.log(chunk); // [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export async function* chunkedAsyncIterable(iterable, chunkSize) {
|
|
39
|
+
const getChunkSize = typeof chunkSize === 'function' ? chunkSize : () => chunkSize;
|
|
40
|
+
if (typeof chunkSize === 'number' && chunkSize < 1) {
|
|
41
|
+
throw new Error(`Chunk size must be a positive number (${inspect(chunkSize)}) received`);
|
|
42
|
+
}
|
|
43
|
+
const iterator = Symbol.asyncIterator in iterable
|
|
44
|
+
? iterable[Symbol.asyncIterator]()
|
|
45
|
+
: iterable[Symbol.iterator]();
|
|
46
|
+
while (true) {
|
|
47
|
+
const currentSize = getChunkSize();
|
|
48
|
+
if (currentSize < 1)
|
|
49
|
+
break;
|
|
50
|
+
const chunk = [];
|
|
51
|
+
for (let i = 0; i < currentSize; i++) {
|
|
52
|
+
const next = await iterator.next();
|
|
53
|
+
if (next.done) {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
chunk.push(next.value);
|
|
57
|
+
}
|
|
58
|
+
if (chunk.length === 0)
|
|
59
|
+
break;
|
|
60
|
+
yield chunk;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wraps an async iterable to provide peek functionality, allowing you to look at
|
|
65
|
+
* the next value without consuming it from the iterator.
|
|
66
|
+
* @internal
|
|
67
|
+
*
|
|
68
|
+
* @param iterable - The async iterable to make peekable
|
|
69
|
+
*
|
|
70
|
+
* **Example usage:**
|
|
71
|
+
* ```ts
|
|
72
|
+
* const numbers = async function* () {
|
|
73
|
+
* yield 1; yield 2; yield 3;
|
|
74
|
+
* };
|
|
75
|
+
*
|
|
76
|
+
* const peekable = peekableAsyncIterable(numbers());
|
|
77
|
+
* const iterator = peekable[Symbol.asyncIterator]();
|
|
78
|
+
*
|
|
79
|
+
* console.log(await iterator.peek()); // 1 (doesn't consume)
|
|
80
|
+
* console.log(await iterator.peek()); // 1 (still doesn't consume)
|
|
81
|
+
* console.log(await iterator.next()); // { value: 1, done: false } (now consumed)
|
|
82
|
+
* console.log(await iterator.peek()); // 2 (next value)
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function peekableAsyncIterable(iterable) {
|
|
86
|
+
const iterator = asyncifyIterable(iterable)[Symbol.asyncIterator]();
|
|
87
|
+
let peekedValue;
|
|
88
|
+
let isExhausted = false;
|
|
89
|
+
const peekableIterator = {
|
|
90
|
+
async next() {
|
|
91
|
+
// If we have peeked a value, return it and clear the peek
|
|
92
|
+
if (peekedValue !== undefined) {
|
|
93
|
+
const result = peekedValue;
|
|
94
|
+
peekedValue = undefined;
|
|
95
|
+
if (result.done) {
|
|
96
|
+
isExhausted = true;
|
|
97
|
+
return { done: true, value: undefined };
|
|
98
|
+
}
|
|
99
|
+
return { done: false, value: result.value };
|
|
100
|
+
}
|
|
101
|
+
if (isExhausted) {
|
|
102
|
+
return { done: true, value: undefined };
|
|
103
|
+
}
|
|
104
|
+
const result = await iterator.next();
|
|
105
|
+
if (result.done) {
|
|
106
|
+
isExhausted = true;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
},
|
|
110
|
+
async peek() {
|
|
111
|
+
if (peekedValue !== undefined) {
|
|
112
|
+
return peekedValue.done ? undefined : peekedValue.value;
|
|
113
|
+
}
|
|
114
|
+
if (isExhausted) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
const result = await iterator.next();
|
|
118
|
+
peekedValue = { done: result.done ?? false, value: result.value };
|
|
119
|
+
if (result.done) {
|
|
120
|
+
isExhausted = true;
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return result.value;
|
|
124
|
+
},
|
|
125
|
+
[Symbol.asyncIterator]() {
|
|
126
|
+
return this;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
[Symbol.asyncIterator]() {
|
|
131
|
+
return peekableIterator;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
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.94",
|
|
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.94",
|
|
57
|
+
"@crawlee/types": "4.0.0-beta.94",
|
|
58
|
+
"@crawlee/utils": "4.0.0-beta.94",
|
|
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": "35f62988d94ddf8a3e304f81be16202ca6479c07"
|
|
83
83
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import {
|
|
2
|
+
import { downloadListOfUrls, isAsyncIterable, isIterable, sleep } from '@crawlee/utils';
|
|
3
3
|
import ow from 'ow';
|
|
4
4
|
import { LruCache } from '@apify/datastructures';
|
|
5
5
|
import { Configuration } from '../configuration.js';
|
|
6
6
|
import { getObjectType } from '../debug.js';
|
|
7
|
+
import { chunkedAsyncIterable, peekableAsyncIterable } from '../iterables.js';
|
|
7
8
|
import { Request } from '../request.js';
|
|
8
9
|
import { serviceLocator } from '../service_locator.js';
|
|
9
10
|
import { checkStorageAccess } from './access_checking.js';
|