zenrows 1.3.2 → 2.0.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.
- package/.editorconfig +16 -0
- package/LICENSE +1 -1
- package/README.md +112 -73
- package/dist/index.cjs +194 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +17 -14
- package/dist/index.js +155 -53
- package/package.json +28 -29
- package/vitest.config.ts +10 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -4
package/.editorconfig
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 2
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
tab_width = 2
|
|
11
|
+
|
|
12
|
+
[*.md]
|
|
13
|
+
trim_trailing_whitespace = false
|
|
14
|
+
|
|
15
|
+
[*.{yml,yaml}]
|
|
16
|
+
indent_size = 2
|
package/LICENSE
CHANGED
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,76 +1,89 @@
|
|
|
1
1
|
# ZenRows Node.js SDK
|
|
2
|
+
|
|
2
3
|
SDK to access [ZenRows](https://www.zenrows.com/) API directly from Node.js. ZenRows handles proxies rotation, headless browsers, and CAPTCHAs for you.
|
|
3
4
|
|
|
4
5
|
## Installation
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
Install the SDK with your package manager of choice.
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install zenrows
|
|
11
|
+
yarn add zenrows
|
|
12
|
+
pnpm install zenrows
|
|
13
|
+
bun install zenrows
|
|
9
14
|
```
|
|
10
15
|
|
|
11
16
|
## Usage
|
|
17
|
+
|
|
12
18
|
Start using the API by [creating your API Key](https://app.zenrows.com/register?p=free).
|
|
13
19
|
|
|
14
|
-
The SDK uses [
|
|
20
|
+
The SDK uses the official [fetch api](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html) for HTTP requests. The client's response will be a [`Response`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#response).
|
|
15
21
|
|
|
16
|
-
It also uses [
|
|
22
|
+
It also uses [fetch-retry](https://github.com/jonbern/fetch-retry) to automatically retry failed requests (status code 429 and 5XX). Retries are not active by default; you need to specify the number of retries, as shown below. It already includes an exponential back-off retry delay between failed requests.
|
|
17
23
|
|
|
18
24
|
```javascript
|
|
19
|
-
const { ZenRows } = require(
|
|
25
|
+
const { ZenRows } = require("zenrows");
|
|
20
26
|
|
|
21
|
-
const apiKey =
|
|
22
|
-
const url =
|
|
27
|
+
const apiKey = "YOUR-API-KEY";
|
|
28
|
+
const url = "https://www.zenrows.com/";
|
|
23
29
|
|
|
24
30
|
(async () => {
|
|
25
|
-
|
|
31
|
+
const client = new ZenRows(apiKey, { retries: 1 });
|
|
32
|
+
|
|
33
|
+
const response = await client.get(
|
|
34
|
+
url,
|
|
35
|
+
{
|
|
36
|
+
// Our algorithm allows to automatically extract content from any website
|
|
37
|
+
autoparse: false,
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
autoparse: false,
|
|
39
|
+
// CSS Selectors for data extraction (i.e. {"links":"a @href"} to get href attributes from links)
|
|
40
|
+
css_extractor: "",
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
// Enable Javascript with a headless browser (5 credits)
|
|
43
|
+
js_render: false,
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
// Use residential proxies (10 credits)
|
|
46
|
+
premium_proxy: false,
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
// Make your request from a given country. Requires premium_proxy
|
|
49
|
+
proxy_country: "",
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
// Wait for a given CSS Selector to load in the DOM. Requires js_render
|
|
52
|
+
wait_for: ".content",
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
// Wait a fixed amount of time in milliseconds. Requires js_render
|
|
55
|
+
wait: 2500,
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
// Block specific resources from loading, check docs for the full list. Requires js_render
|
|
58
|
+
block_resources: "image,media,font",
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
// Change the browser's window width and height. Requires js_render
|
|
61
|
+
window_width: 1920,
|
|
62
|
+
window_height: 1080,
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
window_height: 1080,
|
|
64
|
+
// Will automatically use either desktop or mobile user agents in the headers
|
|
65
|
+
device: "desktop",
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
// Will return the status code returned by the website
|
|
68
|
+
original_status: false,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
headers: {
|
|
72
|
+
Referrer: "https://www.google.com",
|
|
73
|
+
"User-Agent": "MyCustomUserAgent",
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
);
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}, {
|
|
62
|
-
headers: {
|
|
63
|
-
'Referrer': 'https://www.google.com',
|
|
64
|
-
'User-Agent': 'MyCustomUserAgent',
|
|
65
|
-
},
|
|
66
|
-
});
|
|
78
|
+
// You can also use response.json() if you're expecting JSON data.
|
|
79
|
+
const data = await response.text();
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
console.log(data);
|
|
69
82
|
|
|
70
|
-
|
|
83
|
+
/* <!doctype html> <html... */
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
// With the CSS selector {"links":"a @href"}
|
|
86
|
+
/*
|
|
74
87
|
{
|
|
75
88
|
links: [
|
|
76
89
|
'https://www.zenrows.com',
|
|
@@ -88,28 +101,34 @@ Sending headers to the target URL will overwrite our defaults. Be careful when d
|
|
|
88
101
|
|
|
89
102
|
### POST Requests
|
|
90
103
|
|
|
91
|
-
The SDK also offers POST requests by calling the `client.post` method. It can receive a new parameter `data` that represents the data sent in, for example, a form.
|
|
104
|
+
The SDK also offers POST requests by calling the `client.post` method. It can receive a new parameter `data` that represents the data sent in, for example, a form.
|
|
92
105
|
|
|
93
106
|
```javascript
|
|
94
|
-
const { ZenRows } = require(
|
|
107
|
+
const { ZenRows } = require("zenrows");
|
|
95
108
|
|
|
96
|
-
const apiKey =
|
|
97
|
-
const url =
|
|
109
|
+
const apiKey = "YOUR-API-KEY";
|
|
110
|
+
const url = "https://httpbin.org/anything";
|
|
98
111
|
|
|
99
112
|
(async () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
const client = new ZenRows(apiKey, { retries: 1 });
|
|
114
|
+
|
|
115
|
+
const response = await client.post(
|
|
116
|
+
url,
|
|
117
|
+
{
|
|
118
|
+
// The same params as in GET requests
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
data: new URLSearchParams({
|
|
122
|
+
key1: "value1",
|
|
123
|
+
key2: "value2",
|
|
124
|
+
}).toString(),
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
|
|
130
|
+
console.log(data);
|
|
131
|
+
/*
|
|
113
132
|
...
|
|
114
133
|
form: { key1: 'value1', key2: 'value2' },
|
|
115
134
|
...
|
|
@@ -124,23 +143,23 @@ To limit the concurrency, it uses [fastq](https://github.com/mcollina/fastq), wh
|
|
|
124
143
|
We use [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) in the example below, available from Node 12.9. It will wait for all the promises to finish, and the results are objects with a status marking them as fulfilled or rejected. The main difference with `Promise.all()` is that it won't fail if any requests fail. It might make your scraping more robust since the whole list of URLs will run, even if some of them fail.
|
|
125
144
|
|
|
126
145
|
```javascript
|
|
127
|
-
const { ZenRows } = require(
|
|
146
|
+
const { ZenRows } = require("zenrows");
|
|
128
147
|
|
|
129
|
-
const apiKey =
|
|
148
|
+
const apiKey = "YOUR-API-KEY";
|
|
130
149
|
|
|
131
150
|
(async () => {
|
|
132
|
-
|
|
151
|
+
const client = new ZenRows(apiKey, { concurrency: 5, retries: 1 });
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
const urls = [
|
|
154
|
+
"https://www.zenrows.com/",
|
|
155
|
+
// ...
|
|
156
|
+
];
|
|
138
157
|
|
|
139
|
-
|
|
158
|
+
const promises = urls.map((url) => client.get(url));
|
|
140
159
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
const results = await Promise.allSettled(promises);
|
|
161
|
+
console.log(results);
|
|
162
|
+
/*
|
|
144
163
|
[
|
|
145
164
|
{
|
|
146
165
|
status: 'fulfilled',
|
|
@@ -152,11 +171,29 @@ const apiKey = 'YOUR-API-KEY';
|
|
|
152
171
|
...
|
|
153
172
|
*/
|
|
154
173
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
// separate results list into rejected and fulfilled for later processing
|
|
175
|
+
const rejected = results.filter(({ status }) => status === "rejected");
|
|
176
|
+
const fulfilled = results.filter(({ status }) => status === "fulfilled");
|
|
158
177
|
})();
|
|
159
178
|
```
|
|
179
|
+
|
|
180
|
+
#### An important note about Promise.allSettled() on TypeScript
|
|
181
|
+
|
|
182
|
+
`Promise.allSettled()` does not narrow the type of the array elements in the callback function. This means that you will need to cast the type of the array elements to `PromiseSettledResult<Response>` to access the `status` and `value` properties.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const promises = urls.map((url) => client.get(url));
|
|
186
|
+
|
|
187
|
+
const results = await Promise.allSettled(promises);
|
|
188
|
+
|
|
189
|
+
const fulfilled = results
|
|
190
|
+
.filter(
|
|
191
|
+
(item): item is PromiseFulfilledResult<Response> =>
|
|
192
|
+
item.status === "fulfilled"
|
|
193
|
+
)
|
|
194
|
+
.map((item) => item.value.json());
|
|
195
|
+
```
|
|
196
|
+
|
|
160
197
|
## Examples
|
|
161
198
|
|
|
162
199
|
Take a look at the [examples directory](./examples) for Javascript and TypeScript files using the SDK.
|
|
@@ -167,11 +204,13 @@ Each file makes two requests, the first with CSS selectors and the second with C
|
|
|
167
204
|
cd examples
|
|
168
205
|
npm install
|
|
169
206
|
node index.js # JS example
|
|
170
|
-
npx
|
|
207
|
+
npx tsx index.ts # TS example
|
|
171
208
|
```
|
|
172
209
|
|
|
173
210
|
## Contributing
|
|
211
|
+
|
|
174
212
|
Pull requests are welcome. For significant changes, please open an issue first to discuss what you would like to change.
|
|
175
213
|
|
|
176
214
|
## License
|
|
215
|
+
|
|
177
216
|
[MIT](./LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
ZenRows: () => ZenRows
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(src_exports);
|
|
36
|
+
var import_fastq = __toESM(require("fastq"), 1);
|
|
37
|
+
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
38
|
+
|
|
39
|
+
// package.json
|
|
40
|
+
var package_default = {
|
|
41
|
+
name: "zenrows",
|
|
42
|
+
version: "2.0.1",
|
|
43
|
+
description: "ZenRows Node SDK",
|
|
44
|
+
main: "./dist/index.cjs",
|
|
45
|
+
module: "./dist/index.js",
|
|
46
|
+
types: "./dist/index.d.ts",
|
|
47
|
+
engines: {
|
|
48
|
+
node: ">=20"
|
|
49
|
+
},
|
|
50
|
+
packageManager: "pnpm@9.4.0",
|
|
51
|
+
exports: {
|
|
52
|
+
".": {
|
|
53
|
+
require: "./dist/index.cjs",
|
|
54
|
+
import: "./dist/index.js",
|
|
55
|
+
types: "./dist/index.d.ts"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
scripts: {
|
|
59
|
+
build: "tsup src/index.ts --dts --format cjs,esm --clean",
|
|
60
|
+
test: "vitest"
|
|
61
|
+
},
|
|
62
|
+
keywords: ["sdk", "zenrows", "scraping"],
|
|
63
|
+
author: "ZenRows",
|
|
64
|
+
repository: {
|
|
65
|
+
type: "git",
|
|
66
|
+
url: "https://github.com/ZenRows/zenrows-node-sdk.git"
|
|
67
|
+
},
|
|
68
|
+
homepage: "https://github.com/ZenRows/zenrows-node-sdk#readme",
|
|
69
|
+
url: "https://github.com/ZenRows/zenrows-node-sdk/issues",
|
|
70
|
+
type: "module",
|
|
71
|
+
license: "MIT",
|
|
72
|
+
devDependencies: {
|
|
73
|
+
"@biomejs/biome": "1.8.1",
|
|
74
|
+
"@types/async-retry": "^1.4.8",
|
|
75
|
+
"@types/node": "^20.14.2",
|
|
76
|
+
msw: "^2.3.1",
|
|
77
|
+
tsup: "^8.1.0",
|
|
78
|
+
typescript: "^5.4.5",
|
|
79
|
+
vitest: "^1.6.0"
|
|
80
|
+
},
|
|
81
|
+
dependencies: {
|
|
82
|
+
fastq: "^1.17.1",
|
|
83
|
+
"fetch-retry": "^6.0.0"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/index.ts
|
|
88
|
+
var API_URL = "https://api.zenrows.com/v1/";
|
|
89
|
+
var ZenRows = class {
|
|
90
|
+
apiKey;
|
|
91
|
+
clientConfig;
|
|
92
|
+
queue;
|
|
93
|
+
fetchWithRetry;
|
|
94
|
+
constructor(apiKey, clientConfig = {}) {
|
|
95
|
+
this.apiKey = apiKey;
|
|
96
|
+
this.clientConfig = clientConfig;
|
|
97
|
+
const retries = this.clientConfig.retries ?? 0;
|
|
98
|
+
this.queue = import_fastq.default.promise(
|
|
99
|
+
this,
|
|
100
|
+
this.worker,
|
|
101
|
+
this.clientConfig.concurrency ?? 5
|
|
102
|
+
);
|
|
103
|
+
this.fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
|
|
104
|
+
retryDelay: (attempt) => 2 ** attempt * 1e3,
|
|
105
|
+
// retryOn: [422, 503, 504],
|
|
106
|
+
retryOn: (attempt, error, response) => {
|
|
107
|
+
if (attempt > retries) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (error !== null || response?.status === 422 || response?.status === 503 || response?.status === 504) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
get(url, config, { headers = {} } = {}) {
|
|
118
|
+
return this.queue.push({ url, config, headers });
|
|
119
|
+
}
|
|
120
|
+
post(url, config, { headers = {}, data = {} } = {
|
|
121
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
122
|
+
}) {
|
|
123
|
+
const normalizedHeaders = Object.keys(headers).reduce(
|
|
124
|
+
(acc, key) => {
|
|
125
|
+
const value = headers[key];
|
|
126
|
+
if (value !== void 0) {
|
|
127
|
+
if (key.toLowerCase() === "content-type") {
|
|
128
|
+
acc["Content-Type"] = value;
|
|
129
|
+
} else {
|
|
130
|
+
acc[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return acc;
|
|
134
|
+
},
|
|
135
|
+
{}
|
|
136
|
+
);
|
|
137
|
+
return this.queue.push({
|
|
138
|
+
url,
|
|
139
|
+
method: "POST",
|
|
140
|
+
config,
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
143
|
+
...normalizedHeaders
|
|
144
|
+
},
|
|
145
|
+
data
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async worker({
|
|
149
|
+
url,
|
|
150
|
+
method = "GET",
|
|
151
|
+
config,
|
|
152
|
+
headers,
|
|
153
|
+
data
|
|
154
|
+
}) {
|
|
155
|
+
const params = new URLSearchParams({
|
|
156
|
+
url,
|
|
157
|
+
apikey: this.apiKey
|
|
158
|
+
});
|
|
159
|
+
if (config) {
|
|
160
|
+
for (const [key, value] of Object.entries(config)) {
|
|
161
|
+
if (value !== void 0) {
|
|
162
|
+
params.append(key, String(value));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (headers && Object.keys(headers).length) {
|
|
167
|
+
params.append("custom_headers", "true");
|
|
168
|
+
}
|
|
169
|
+
const finalHeaders = {
|
|
170
|
+
"User-Agent": `zenrows/${package_default.version} node`,
|
|
171
|
+
...headers
|
|
172
|
+
};
|
|
173
|
+
const fetchOptions = {
|
|
174
|
+
method,
|
|
175
|
+
headers: finalHeaders
|
|
176
|
+
};
|
|
177
|
+
if (method === "POST" && data) {
|
|
178
|
+
if (typeof data === "object") {
|
|
179
|
+
fetchOptions.body = JSON.stringify(data);
|
|
180
|
+
} else {
|
|
181
|
+
fetchOptions.body = String(data);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const response = await this.fetchWithRetry(
|
|
185
|
+
`${API_URL}?${params.toString()}`,
|
|
186
|
+
fetchOptions
|
|
187
|
+
);
|
|
188
|
+
return response;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
192
|
+
0 && (module.exports = {
|
|
193
|
+
ZenRows
|
|
194
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fetch_retry from 'fetch-retry';
|
|
2
|
+
import fastq from 'fastq';
|
|
3
|
+
|
|
4
|
+
type HttpMethods = "GET" | "POST" | "PUT";
|
|
5
|
+
interface ClientConfig {
|
|
6
|
+
concurrency?: number;
|
|
7
|
+
retries?: number;
|
|
8
|
+
}
|
|
9
|
+
interface ZenRowsConfig {
|
|
10
|
+
autoparse?: boolean;
|
|
11
|
+
css_extractor?: string;
|
|
12
|
+
js_render?: boolean;
|
|
13
|
+
premium_proxy?: boolean;
|
|
14
|
+
proxy_country?: string;
|
|
15
|
+
wait_for?: string;
|
|
16
|
+
wait?: number;
|
|
17
|
+
block_resources?: string;
|
|
18
|
+
window_width?: number;
|
|
19
|
+
window_height?: number;
|
|
20
|
+
device?: string;
|
|
21
|
+
original_status?: boolean;
|
|
22
|
+
custom_headers?: boolean;
|
|
23
|
+
[x: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
interface Headers {
|
|
26
|
+
[x: string]: string;
|
|
27
|
+
}
|
|
28
|
+
declare class ZenRows {
|
|
29
|
+
readonly apiKey: string;
|
|
30
|
+
readonly clientConfig: ClientConfig;
|
|
31
|
+
readonly queue: fastq.queueAsPromised<{
|
|
32
|
+
url: string;
|
|
33
|
+
method?: HttpMethods | undefined;
|
|
34
|
+
config?: ZenRowsConfig | undefined;
|
|
35
|
+
headers: Headers;
|
|
36
|
+
data?: unknown;
|
|
37
|
+
}, Response>;
|
|
38
|
+
readonly fetchWithRetry: (input: string | URL | Request, init?: (RequestInit & fetch_retry.RequestInitRetryParams<typeof fetch>) | undefined) => Promise<Response>;
|
|
39
|
+
constructor(apiKey: string, clientConfig?: ClientConfig);
|
|
40
|
+
get(url: string, config?: ZenRowsConfig, { headers }?: {
|
|
41
|
+
headers?: Headers;
|
|
42
|
+
}): Promise<Response>;
|
|
43
|
+
post(url: string, config?: ZenRowsConfig, { headers, data }?: {
|
|
44
|
+
headers?: Headers;
|
|
45
|
+
data?: unknown;
|
|
46
|
+
}): Promise<Response>;
|
|
47
|
+
private worker;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { ZenRows, type ZenRowsConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as fetch_retry from 'fetch-retry';
|
|
2
|
+
import fastq from 'fastq';
|
|
3
|
+
|
|
4
|
+
type HttpMethods = "GET" | "POST" | "PUT";
|
|
3
5
|
interface ClientConfig {
|
|
4
6
|
concurrency?: number;
|
|
5
7
|
retries?: number;
|
|
6
8
|
}
|
|
7
|
-
interface
|
|
9
|
+
interface ZenRowsConfig {
|
|
8
10
|
autoparse?: boolean;
|
|
9
11
|
css_extractor?: string;
|
|
10
12
|
js_render?: boolean;
|
|
@@ -28,20 +30,21 @@ declare class ZenRows {
|
|
|
28
30
|
readonly clientConfig: ClientConfig;
|
|
29
31
|
readonly queue: fastq.queueAsPromised<{
|
|
30
32
|
url: string;
|
|
31
|
-
method?:
|
|
32
|
-
config?:
|
|
33
|
+
method?: HttpMethods | undefined;
|
|
34
|
+
config?: ZenRowsConfig | undefined;
|
|
33
35
|
headers: Headers;
|
|
34
|
-
data?:
|
|
35
|
-
},
|
|
36
|
+
data?: unknown;
|
|
37
|
+
}, Response>;
|
|
38
|
+
readonly fetchWithRetry: (input: string | URL | Request, init?: (RequestInit & fetch_retry.RequestInitRetryParams<typeof fetch>) | undefined) => Promise<Response>;
|
|
36
39
|
constructor(apiKey: string, clientConfig?: ClientConfig);
|
|
37
|
-
get(url: string, config?:
|
|
40
|
+
get(url: string, config?: ZenRowsConfig, { headers }?: {
|
|
38
41
|
headers?: Headers;
|
|
39
|
-
}):
|
|
40
|
-
post(url: string, config?:
|
|
42
|
+
}): Promise<Response>;
|
|
43
|
+
post(url: string, config?: ZenRowsConfig, { headers, data }?: {
|
|
41
44
|
headers?: Headers;
|
|
42
|
-
data?:
|
|
43
|
-
}):
|
|
45
|
+
data?: unknown;
|
|
46
|
+
}): Promise<Response>;
|
|
44
47
|
private worker;
|
|
45
|
-
private applyRetries;
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
|
|
50
|
+
export { ZenRows, type ZenRowsConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,159 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import fastq from "fastq";
|
|
3
|
+
import fetchRetry from "fetch-retry";
|
|
4
|
+
|
|
5
|
+
// package.json
|
|
6
|
+
var package_default = {
|
|
7
|
+
name: "zenrows",
|
|
8
|
+
version: "2.0.1",
|
|
9
|
+
description: "ZenRows Node SDK",
|
|
10
|
+
main: "./dist/index.cjs",
|
|
11
|
+
module: "./dist/index.js",
|
|
12
|
+
types: "./dist/index.d.ts",
|
|
13
|
+
engines: {
|
|
14
|
+
node: ">=20"
|
|
15
|
+
},
|
|
16
|
+
packageManager: "pnpm@9.4.0",
|
|
17
|
+
exports: {
|
|
18
|
+
".": {
|
|
19
|
+
require: "./dist/index.cjs",
|
|
20
|
+
import: "./dist/index.js",
|
|
21
|
+
types: "./dist/index.d.ts"
|
|
17
22
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
},
|
|
24
|
+
scripts: {
|
|
25
|
+
build: "tsup src/index.ts --dts --format cjs,esm --clean",
|
|
26
|
+
test: "vitest"
|
|
27
|
+
},
|
|
28
|
+
keywords: ["sdk", "zenrows", "scraping"],
|
|
29
|
+
author: "ZenRows",
|
|
30
|
+
repository: {
|
|
31
|
+
type: "git",
|
|
32
|
+
url: "https://github.com/ZenRows/zenrows-node-sdk.git"
|
|
33
|
+
},
|
|
34
|
+
homepage: "https://github.com/ZenRows/zenrows-node-sdk#readme",
|
|
35
|
+
url: "https://github.com/ZenRows/zenrows-node-sdk/issues",
|
|
36
|
+
type: "module",
|
|
37
|
+
license: "MIT",
|
|
38
|
+
devDependencies: {
|
|
39
|
+
"@biomejs/biome": "1.8.1",
|
|
40
|
+
"@types/async-retry": "^1.4.8",
|
|
41
|
+
"@types/node": "^20.14.2",
|
|
42
|
+
msw: "^2.3.1",
|
|
43
|
+
tsup: "^8.1.0",
|
|
44
|
+
typescript: "^5.4.5",
|
|
45
|
+
vitest: "^1.6.0"
|
|
46
|
+
},
|
|
47
|
+
dependencies: {
|
|
48
|
+
fastq: "^1.17.1",
|
|
49
|
+
"fetch-retry": "^6.0.0"
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/index.ts
|
|
54
|
+
var API_URL = "https://api.zenrows.com/v1/";
|
|
55
|
+
var ZenRows = class {
|
|
56
|
+
apiKey;
|
|
57
|
+
clientConfig;
|
|
58
|
+
queue;
|
|
59
|
+
fetchWithRetry;
|
|
60
|
+
constructor(apiKey, clientConfig = {}) {
|
|
61
|
+
this.apiKey = apiKey;
|
|
62
|
+
this.clientConfig = clientConfig;
|
|
63
|
+
const retries = this.clientConfig.retries ?? 0;
|
|
64
|
+
this.queue = fastq.promise(
|
|
65
|
+
this,
|
|
66
|
+
this.worker,
|
|
67
|
+
this.clientConfig.concurrency ?? 5
|
|
68
|
+
);
|
|
69
|
+
this.fetchWithRetry = fetchRetry(fetch, {
|
|
70
|
+
retryDelay: (attempt) => 2 ** attempt * 1e3,
|
|
71
|
+
// retryOn: [422, 503, 504],
|
|
72
|
+
retryOn: (attempt, error, response) => {
|
|
73
|
+
if (attempt > retries) {
|
|
74
|
+
return false;
|
|
36
75
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
if (error !== null || response?.status === 422 || response?.status === 503 || response?.status === 504) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
get(url, config, { headers = {} } = {}) {
|
|
84
|
+
return this.queue.push({ url, config, headers });
|
|
85
|
+
}
|
|
86
|
+
post(url, config, { headers = {}, data = {} } = {
|
|
87
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
88
|
+
}) {
|
|
89
|
+
const normalizedHeaders = Object.keys(headers).reduce(
|
|
90
|
+
(acc, key) => {
|
|
91
|
+
const value = headers[key];
|
|
92
|
+
if (value !== void 0) {
|
|
93
|
+
if (key.toLowerCase() === "content-type") {
|
|
94
|
+
acc["Content-Type"] = value;
|
|
95
|
+
} else {
|
|
96
|
+
acc[key] = value;
|
|
97
|
+
}
|
|
54
98
|
}
|
|
99
|
+
return acc;
|
|
100
|
+
},
|
|
101
|
+
{}
|
|
102
|
+
);
|
|
103
|
+
return this.queue.push({
|
|
104
|
+
url,
|
|
105
|
+
method: "POST",
|
|
106
|
+
config,
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
109
|
+
...normalizedHeaders
|
|
110
|
+
},
|
|
111
|
+
data
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async worker({
|
|
115
|
+
url,
|
|
116
|
+
method = "GET",
|
|
117
|
+
config,
|
|
118
|
+
headers,
|
|
119
|
+
data
|
|
120
|
+
}) {
|
|
121
|
+
const params = new URLSearchParams({
|
|
122
|
+
url,
|
|
123
|
+
apikey: this.apiKey
|
|
124
|
+
});
|
|
125
|
+
if (config) {
|
|
126
|
+
for (const [key, value] of Object.entries(config)) {
|
|
127
|
+
if (value !== void 0) {
|
|
128
|
+
params.append(key, String(value));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (headers && Object.keys(headers).length) {
|
|
133
|
+
params.append("custom_headers", "true");
|
|
134
|
+
}
|
|
135
|
+
const finalHeaders = {
|
|
136
|
+
"User-Agent": `zenrows/${package_default.version} node`,
|
|
137
|
+
...headers
|
|
138
|
+
};
|
|
139
|
+
const fetchOptions = {
|
|
140
|
+
method,
|
|
141
|
+
headers: finalHeaders
|
|
142
|
+
};
|
|
143
|
+
if (method === "POST" && data) {
|
|
144
|
+
if (typeof data === "object") {
|
|
145
|
+
fetchOptions.body = JSON.stringify(data);
|
|
146
|
+
} else {
|
|
147
|
+
fetchOptions.body = String(data);
|
|
148
|
+
}
|
|
55
149
|
}
|
|
56
|
-
|
|
57
|
-
|
|
150
|
+
const response = await this.fetchWithRetry(
|
|
151
|
+
`${API_URL}?${params.toString()}`,
|
|
152
|
+
fetchOptions
|
|
153
|
+
);
|
|
154
|
+
return response;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
export {
|
|
158
|
+
ZenRows
|
|
159
|
+
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zenrows",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "ZenRows Node SDK",
|
|
5
|
-
"main": "dist/index.
|
|
6
|
-
"
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
11
|
+
"packageManager": "pnpm@9.4.0",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
7
19
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"prebuild": "node -p \"'export const VERSION = \\'' + require('./package.json').version + '\\';'\" > src/version.ts",
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"test": "jest"
|
|
20
|
+
"build": "tsup src/index.ts --dts --format cjs,esm --clean",
|
|
21
|
+
"test": "vitest"
|
|
13
22
|
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"sdk",
|
|
16
|
-
"zenrows",
|
|
17
|
-
"scraping"
|
|
18
|
-
],
|
|
23
|
+
"keywords": ["sdk", "zenrows", "scraping"],
|
|
19
24
|
"author": "ZenRows",
|
|
20
25
|
"repository": {
|
|
21
26
|
"type": "git",
|
|
@@ -23,25 +28,19 @@
|
|
|
23
28
|
},
|
|
24
29
|
"homepage": "https://github.com/ZenRows/zenrows-node-sdk#readme",
|
|
25
30
|
"url": "https://github.com/ZenRows/zenrows-node-sdk/issues",
|
|
31
|
+
"type": "module",
|
|
26
32
|
"license": "MIT",
|
|
27
33
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@types/
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"eslint-config-prettier": "^8.3.0",
|
|
36
|
-
"eslint-plugin-prettier": "^3.4.0",
|
|
37
|
-
"jest": "^27.2.1",
|
|
38
|
-
"prettier": "^2.3.2",
|
|
39
|
-
"ts-jest": "^27.0.5",
|
|
40
|
-
"typescript": "^4.4.2"
|
|
34
|
+
"@biomejs/biome": "1.8.1",
|
|
35
|
+
"@types/async-retry": "^1.4.8",
|
|
36
|
+
"@types/node": "^20.14.2",
|
|
37
|
+
"msw": "^2.3.1",
|
|
38
|
+
"tsup": "^8.1.0",
|
|
39
|
+
"typescript": "^5.4.5",
|
|
40
|
+
"vitest": "^1.6.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"fastq": "^1.13.0"
|
|
43
|
+
"fastq": "^1.17.1",
|
|
44
|
+
"fetch-retry": "^6.0.0"
|
|
46
45
|
}
|
|
47
46
|
}
|
package/vitest.config.ts
ADDED
package/dist/version.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const VERSION = "1.3.2";
|
package/dist/version.js
DELETED