zenrows 1.0.5 → 1.2.0
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/README.md +71 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +33 -6
- package/dist/queue.d.ts +4 -0
- package/dist/queue.js +37 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -13,8 +13,10 @@ Start using the API by [creating your API Key](https://app.zenrows.com/register?
|
|
|
13
13
|
|
|
14
14
|
The SDK uses [axios](https://axios-http.com/) for HTTP requests. The client's response will be an `AxiosPromise` if using TypeScript, a regular `Promise` otherwise.
|
|
15
15
|
|
|
16
|
+
It also uses [axios-retry](https://github.com/softonic/axios-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
|
+
|
|
16
18
|
```javascript
|
|
17
|
-
const { ZenRows } = require('zenrows');
|
|
19
|
+
const { ZenRows } = require('zenrows', { retries: 1 });
|
|
18
20
|
|
|
19
21
|
const apiKey = 'YOUR-API-KEY';
|
|
20
22
|
const url = 'https://www.zenrows.com/';
|
|
@@ -37,6 +39,30 @@ const url = 'https://www.zenrows.com/';
|
|
|
37
39
|
|
|
38
40
|
// Make your request from a given country. Requires premium_proxy
|
|
39
41
|
proxy_country: '',
|
|
42
|
+
|
|
43
|
+
// Wait for a given CSS Selector to load in the DOM. Requires js_render
|
|
44
|
+
wait_for: '.content',
|
|
45
|
+
|
|
46
|
+
// Wait a fixed amount of time in milliseconds. Requires js_render
|
|
47
|
+
wait: 2500,
|
|
48
|
+
|
|
49
|
+
// Block specific resources from loading, check docs for the full list. Requires js_render
|
|
50
|
+
block_resources: 'image,media,font',
|
|
51
|
+
|
|
52
|
+
// Change the browser's window width and height. Requires js_render
|
|
53
|
+
window_width: 1920,
|
|
54
|
+
window_height: 1080,
|
|
55
|
+
|
|
56
|
+
// Will automatically use either desktop or mobile user agents in the headers
|
|
57
|
+
device: 'desktop',
|
|
58
|
+
|
|
59
|
+
// Will return the status code returned by the website
|
|
60
|
+
original_status: false,
|
|
61
|
+
}, {
|
|
62
|
+
headers: {
|
|
63
|
+
Referrer: 'https://www.google.com',
|
|
64
|
+
'User-Agent': 'MyCustomUserAgent',
|
|
65
|
+
},
|
|
40
66
|
});
|
|
41
67
|
|
|
42
68
|
console.log(data);
|
|
@@ -56,6 +82,50 @@ const url = 'https://www.zenrows.com/';
|
|
|
56
82
|
})();
|
|
57
83
|
```
|
|
58
84
|
|
|
85
|
+
You can also pass optional parameters and headers; the list above is a reference. For more info, check out [the documentation page](https://www.zenrows.com/documentation).
|
|
86
|
+
|
|
87
|
+
Sending headers to the target URL will overwrite our defaults. Be careful when doing it and contact us if there is any problem.
|
|
88
|
+
|
|
89
|
+
### Concurrency
|
|
90
|
+
|
|
91
|
+
To limit the concurrency, it uses [fastq](https://github.com/mcollina/fastq), which will simultaneously send a maximum of requests. The concurrency is determined by the plan you are in, so take a look at the [pricing](https://www.zenrows.com/pricing) and set it accordingly. Take into account that each client instance will have its own limit, meaning that two different scripts will not share it, and 429 (Too Many Requests) errors might arise.
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const { ZenRows } = require('zenrows');
|
|
97
|
+
|
|
98
|
+
const apiKey = 'YOUR-API-KEY';
|
|
99
|
+
|
|
100
|
+
(async () => {
|
|
101
|
+
const client = new ZenRows(apiKey, { concurrency: 5, retries: 1 });
|
|
102
|
+
|
|
103
|
+
const urls = [
|
|
104
|
+
'https://www.zenrows.com/',
|
|
105
|
+
// ...
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const promises = urls.map(url => client.get(url));
|
|
109
|
+
|
|
110
|
+
const results = await Promise.allSettled(promises);
|
|
111
|
+
console.log(results);
|
|
112
|
+
/*
|
|
113
|
+
[
|
|
114
|
+
{
|
|
115
|
+
status: 'fulfilled',
|
|
116
|
+
value: {
|
|
117
|
+
status: 200,
|
|
118
|
+
statusText: 'OK',
|
|
119
|
+
data: `<!doctype html> <html lang="en"> <head> ...
|
|
120
|
+
|
|
121
|
+
...
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
// separate results list into rejected and fulfilled for later processing
|
|
125
|
+
const rejected = results.filter(({ status }) => status === 'rejected');
|
|
126
|
+
const fulfilled = results.filter(({ status }) => status === 'fulfilled');
|
|
127
|
+
})();
|
|
128
|
+
```
|
|
59
129
|
## Examples
|
|
60
130
|
|
|
61
131
|
Take a look at the [examples directory](./examples) for Javascript and TypeScript files using the SDK.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
import { AxiosPromise } from 'axios';
|
|
2
|
+
import * as fastq from 'fastq';
|
|
3
|
+
interface ClientConfig {
|
|
4
|
+
concurrency?: number;
|
|
5
|
+
retries?: number;
|
|
6
|
+
}
|
|
2
7
|
interface Config {
|
|
3
8
|
autoparse?: boolean;
|
|
4
9
|
css_extractor?: string;
|
|
5
10
|
js_render?: boolean;
|
|
6
11
|
premium_proxy?: boolean;
|
|
7
12
|
proxy_country?: string;
|
|
13
|
+
wait_for?: string;
|
|
14
|
+
wait?: number;
|
|
15
|
+
block_resources?: string;
|
|
16
|
+
window_width?: number;
|
|
17
|
+
window_height?: number;
|
|
18
|
+
device?: string;
|
|
19
|
+
original_status?: boolean;
|
|
20
|
+
custom_headers?: boolean;
|
|
8
21
|
[x: string]: unknown;
|
|
9
22
|
}
|
|
23
|
+
interface Headers {
|
|
24
|
+
[x: string]: string;
|
|
25
|
+
}
|
|
10
26
|
declare class ZenRows {
|
|
11
27
|
readonly apiKey: string;
|
|
12
|
-
|
|
13
|
-
|
|
28
|
+
readonly clientConfig: ClientConfig;
|
|
29
|
+
readonly queue: fastq.queueAsPromised<{
|
|
30
|
+
url: string;
|
|
31
|
+
config?: Config | undefined;
|
|
32
|
+
headers: Headers;
|
|
33
|
+
}, import("axios").AxiosResponse<any>>;
|
|
34
|
+
constructor(apiKey: string, clientConfig?: ClientConfig);
|
|
35
|
+
get(url: string, config?: Config, { headers }?: {
|
|
36
|
+
headers?: Headers;
|
|
37
|
+
}): AxiosPromise;
|
|
38
|
+
private worker;
|
|
39
|
+
private applyRetries;
|
|
14
40
|
}
|
|
15
41
|
export { ZenRows };
|
package/dist/index.js
CHANGED
|
@@ -2,23 +2,50 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZenRows = void 0;
|
|
4
4
|
const axios_1 = require("axios");
|
|
5
|
+
const axios_retry_1 = require("axios-retry");
|
|
6
|
+
const fastq = require("fastq");
|
|
5
7
|
const version_1 = require("./version");
|
|
6
8
|
const API_URL = 'https://api.zenrows.com/v1/';
|
|
7
9
|
class ZenRows {
|
|
8
|
-
constructor(apiKey) {
|
|
10
|
+
constructor(apiKey, clientConfig = {}) {
|
|
11
|
+
var _a;
|
|
9
12
|
this.apiKey = apiKey;
|
|
13
|
+
this.clientConfig = clientConfig;
|
|
14
|
+
this.queue = fastq.promise(this, this.worker, (_a = this.clientConfig.concurrency) !== null && _a !== void 0 ? _a : 5);
|
|
15
|
+
this.applyRetries();
|
|
10
16
|
}
|
|
11
|
-
get(url, config) {
|
|
17
|
+
get(url, config, { headers = {} } = {}) {
|
|
18
|
+
return this.queue.push({ url, config, headers });
|
|
19
|
+
}
|
|
20
|
+
worker({ url, config, headers }) {
|
|
12
21
|
const params = Object.assign(Object.assign({}, config), { url, apikey: this.apiKey });
|
|
13
|
-
const
|
|
14
|
-
'User-Agent': `zenrows/${version_1.VERSION} node`,
|
|
15
|
-
};
|
|
22
|
+
const finalHeaders = Object.assign({ 'User-Agent': `zenrows/${version_1.VERSION} node` }, headers);
|
|
16
23
|
const axiosRequestConfig = {
|
|
17
24
|
baseURL: API_URL,
|
|
18
25
|
params,
|
|
19
|
-
headers,
|
|
26
|
+
headers: finalHeaders,
|
|
20
27
|
};
|
|
28
|
+
if (headers && Object.keys(headers).length) {
|
|
29
|
+
params.custom_headers = true;
|
|
30
|
+
}
|
|
21
31
|
return (0, axios_1.default)(axiosRequestConfig);
|
|
22
32
|
}
|
|
33
|
+
applyRetries() {
|
|
34
|
+
var _a;
|
|
35
|
+
const retries = (_a = this.clientConfig.retries) !== null && _a !== void 0 ? _a : 0;
|
|
36
|
+
if (retries > 0) {
|
|
37
|
+
(0, axios_retry_1.default)(axios_1.default, {
|
|
38
|
+
retries,
|
|
39
|
+
retryDelay: axios_retry_1.default.exponentialDelay,
|
|
40
|
+
retryCondition: (error) => {
|
|
41
|
+
var _a;
|
|
42
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 429) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return axios_retry_1.default.isNetworkOrIdempotentRequestError(error);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
23
50
|
}
|
|
24
51
|
exports.ZenRows = ZenRows;
|
package/dist/queue.d.ts
ADDED
package/dist/queue.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// TODO review disabled!!!!
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.queue = void 0;
|
|
14
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
15
|
+
const queue = (concurrency) => {
|
|
16
|
+
let running = 0;
|
|
17
|
+
const tasks = [];
|
|
18
|
+
return {
|
|
19
|
+
enqueue: (givenTask, ...givenParams) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
tasks.push({ task: givenTask, params: givenParams });
|
|
21
|
+
if (running >= concurrency) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
running += 1;
|
|
25
|
+
while (tasks.length > 0) {
|
|
26
|
+
const nextTask = tasks.shift();
|
|
27
|
+
if (nextTask) {
|
|
28
|
+
const { task, params } = nextTask;
|
|
29
|
+
// eslint-disable-next-line no-await-in-loop
|
|
30
|
+
yield task(...params);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
running -= 1;
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
exports.queue = queue;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.0
|
|
1
|
+
export declare const VERSION = "1.2.0";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zenrows",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "ZenRows Node SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"typescript": "^4.4.2"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"axios": "^0.21.4"
|
|
43
|
+
"axios": "^0.21.4",
|
|
44
|
+
"axios-retry": "^3.2.4",
|
|
45
|
+
"fastq": "^1.13.0"
|
|
44
46
|
}
|
|
45
47
|
}
|