webcrawlerapi-js 1.1.6 → 2.0.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 +40 -78
- package/dist/@types/api.d.ts +4 -5
- package/dist/@types/model.d.ts +23 -12
- package/dist/api.js +89 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,34 +20,36 @@ The package is regularly updated with new features and improvements to ensure th
|
|
|
20
20
|
## JavaScript Examples
|
|
21
21
|
|
|
22
22
|
### Synchronous Job
|
|
23
|
-
In this mode, the promise will be resolved when all data is ready. Synchronous jobs are perfect for quick scraping tasks where you need immediate results and don't want to handle polling logic.
|
|
23
|
+
In this mode, the promise will be resolved when all data is ready. Synchronous jobs are perfect for quick scraping tasks where you need immediate results and don't want to handle polling logic. However, for long running jobs we recommend to use async crawling or the webhook.
|
|
24
24
|
|
|
25
25
|
```javascript
|
|
26
26
|
import webcrawlerapi from "webcrawlerapi-js"
|
|
27
27
|
|
|
28
28
|
async function main() {
|
|
29
|
-
const client = new WebcrawlerClient(
|
|
30
|
-
"YOUR API ACCESS KEY HERE",
|
|
31
|
-
)
|
|
29
|
+
const client = new WebcrawlerClient("YOUR API ACCESS KEY HERE")
|
|
32
30
|
|
|
33
31
|
try {
|
|
34
|
-
const
|
|
32
|
+
const crawlJob = await client.crawl({
|
|
35
33
|
"items_limit": 10,
|
|
36
|
-
"url": "https://
|
|
34
|
+
"url": "https://books.toscrape.com/",
|
|
37
35
|
"scrape_type": "markdown"
|
|
38
36
|
})
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
// Get job details
|
|
39
|
+
const job = await client.getJob(crawlJob.id);
|
|
40
|
+
console.log("Job status:", job.status);
|
|
41
|
+
|
|
42
|
+
// Output item content
|
|
43
|
+
for (const item of job.job_items) {
|
|
41
44
|
const content = await item.getContent()
|
|
45
|
+
console.log("---- Title: " + item.title + "----")
|
|
42
46
|
console.log(content.slice(0, 100))
|
|
43
47
|
}
|
|
44
|
-
console.log(syncJob)
|
|
45
|
-
|
|
46
48
|
} catch (error) {
|
|
47
49
|
if (error instanceof webcrawlerapi.WebcrawlerApiError) {
|
|
48
50
|
console.error("Webcrawler error:", error.message)
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
console.error(error)
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -61,27 +63,32 @@ In this mode, you get a job ID immediately and can poll for results. Asynchronou
|
|
|
61
63
|
import webcrawlerapi from "webcrawlerapi-js"
|
|
62
64
|
|
|
63
65
|
async function main() {
|
|
64
|
-
const client = new WebcrawlerClient(
|
|
65
|
-
"YOUR API ACCESS KEY HERE",
|
|
66
|
-
)
|
|
66
|
+
const client = new WebcrawlerClient("YOUR API ACCESS KEY HERE")
|
|
67
67
|
|
|
68
68
|
try {
|
|
69
|
-
|
|
69
|
+
try {
|
|
70
70
|
const jobWithId = await client.crawlAsync({
|
|
71
71
|
"items_limit": 10,
|
|
72
|
-
"url": "https://
|
|
72
|
+
"url": "https://books.toscrape.com/",
|
|
73
73
|
"scrape_type": "markdown"
|
|
74
74
|
})
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
// Poll for job completion
|
|
77
77
|
const jobId = jobWithId.id
|
|
78
78
|
let asyncJob = await client.getJob(jobId)
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
// Check job status
|
|
81
|
-
console.log("Job
|
|
82
|
-
|
|
81
|
+
console.log("Job id:", asyncJob.id)
|
|
82
|
+
console.log("Dashboard link:", `https://dash.webcrawlerapi.com/jobs/job/${asyncJob.id}`)
|
|
83
|
+
|
|
84
|
+
//wait until job is done
|
|
85
|
+
while (asyncJob.status !== "done") {
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
87
|
+
asyncJob = await client.getJob(jobId)
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
// Once complete, access results
|
|
84
|
-
if (asyncJob.status === "
|
|
91
|
+
if (asyncJob.status === "done") {
|
|
85
92
|
for (const item of asyncJob.job_items) {
|
|
86
93
|
const content = await item.getContent()
|
|
87
94
|
console.log(content.slice(0, 100))
|
|
@@ -91,6 +98,8 @@ async function main() {
|
|
|
91
98
|
if (error instanceof webcrawlerapi.WebcrawlerApiError) {
|
|
92
99
|
console.error("Webcrawler error:", error.message)
|
|
93
100
|
}
|
|
101
|
+
console.error(error)
|
|
102
|
+
return
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
@@ -102,87 +111,40 @@ main().catch(console.error)
|
|
|
102
111
|
The SDK provides full TypeScript support with type safety and error handling. Our TypeScript integration ensures you catch potential issues at compile-time and provides excellent IDE support with autocompletion and inline documentation.
|
|
103
112
|
|
|
104
113
|
```typescript
|
|
105
|
-
import {
|
|
114
|
+
import {WebcrawlerClient, WebcrawlerApiError} from "webcrawlerapi-js";
|
|
106
115
|
|
|
107
116
|
async function main() {
|
|
108
|
-
const client = new WebcrawlerClient(
|
|
109
|
-
"YOUR_API_KEY",
|
|
110
|
-
// Optional base URL, defaults to production API
|
|
111
|
-
"http://localhost:8080"
|
|
112
|
-
)
|
|
117
|
+
const client = new WebcrawlerClient("YOUR_API_KEY")
|
|
113
118
|
|
|
114
119
|
try {
|
|
115
120
|
// Basic crawl with markdown
|
|
116
121
|
const syncJob = await client.crawl({
|
|
117
|
-
"items_limit":
|
|
122
|
+
"items_limit": 10,
|
|
118
123
|
"url": "https://books.toscrape.com/",
|
|
119
|
-
"scrape_type": "markdown"
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
// Type-safe status check
|
|
123
|
-
if (syncJob.status === "done") {
|
|
124
|
-
for (const item of syncJob.job_items) {
|
|
125
|
-
if (item.markdown_content_url) {
|
|
126
|
-
// Access typed properties
|
|
127
|
-
console.log("Status code:", item.page_status_code)
|
|
128
|
-
console.log("Content URL:", item.markdown_content_url)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
124
|
+
"scrape_type": "markdown",
|
|
131
125
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"url": "https://books.toscrape.com/"
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
if (scrapeJob.status === "done") {
|
|
142
|
-
const content = scrapeJob.getContent()
|
|
143
|
-
console.log("Scraped content:", content)
|
|
126
|
+
)
|
|
127
|
+
// Type-safe status check
|
|
128
|
+
for (const item of syncJob.job_items) {
|
|
129
|
+
const content = await item.getContent()
|
|
130
|
+
console.log(content.slice(0,100))
|
|
144
131
|
}
|
|
145
|
-
|
|
146
132
|
} catch (error) {
|
|
147
|
-
// Type-safe error handling
|
|
148
133
|
if (error instanceof WebcrawlerApiError) {
|
|
149
134
|
console.error("API Error:", error.errorCode, error.message)
|
|
150
|
-
// errorCode is typed and can be used for specific error handling
|
|
151
|
-
if (error.errorCode === "invalid_request") {
|
|
152
|
-
// Handle invalid request
|
|
153
|
-
}
|
|
154
135
|
}
|
|
136
|
+
console.log(error)
|
|
155
137
|
}
|
|
156
138
|
}
|
|
157
139
|
|
|
158
140
|
main().catch(console.error)
|
|
159
141
|
```
|
|
160
142
|
|
|
161
|
-
### Error Handling
|
|
162
|
-
|
|
163
|
-
The SDK provides typed error handling through the `WebcrawlerApiError` class. Our error handling system is designed to provide detailed, actionable feedback that helps you quickly identify and resolve issues in your scraping pipeline.
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
try {
|
|
167
|
-
const syncJob = await client.crawl({
|
|
168
|
-
"items_limit": 2,
|
|
169
|
-
"url": "https://books.toscrape.com/",
|
|
170
|
-
// This will cause an error due to missing scrape_type
|
|
171
|
-
})
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (error instanceof WebcrawlerApiError) {
|
|
174
|
-
// Typed error properties
|
|
175
|
-
console.error("Error code:", error.errorCode)
|
|
176
|
-
console.error("Error message:", error.message)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
143
|
## Response Structure
|
|
182
144
|
|
|
183
145
|
Example of a job response. The response structure is carefully designed to provide comprehensive information about your scraping job, including detailed status updates, timing information, and resource usage metrics.
|
|
184
146
|
|
|
185
|
-
```
|
|
147
|
+
```js
|
|
186
148
|
{
|
|
187
149
|
id: '49c4942b-b7d9-4d62-94b5-b54a3016ac51',
|
|
188
150
|
url: 'https://stripe.com/',
|
package/dist/@types/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse, Action } from "./model";
|
|
1
|
+
import { CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse, ScrapeResponseError, ScrapeId, Action } from "./model";
|
|
2
2
|
import { JobStatus } from "./constants";
|
|
3
3
|
import { WebcrawlerApiError } from "./errors";
|
|
4
4
|
export { WebcrawlerApiError, JobStatus };
|
|
@@ -8,10 +8,9 @@ export declare class WebcrawlerClient {
|
|
|
8
8
|
protected apiKey: string;
|
|
9
9
|
protected basePath: string;
|
|
10
10
|
constructor(apiKey: string, basePath?: string, apiVersion?: string);
|
|
11
|
-
scrapeAsync(
|
|
12
|
-
|
|
13
|
-
scrape(
|
|
14
|
-
getScrapeResult(scrapeID: string): Promise<ScrapeResponse>;
|
|
11
|
+
scrapeAsync(request: ScrapeRequest): Promise<ScrapeId>;
|
|
12
|
+
getScrape(scrapeId: string): Promise<ScrapeResponse | ScrapeResponseError>;
|
|
13
|
+
scrape(request: ScrapeRequest, maxPolls?: number): Promise<ScrapeResponse | ScrapeResponseError>;
|
|
15
14
|
crawl(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<Job>;
|
|
16
15
|
crawlAsync(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<JobId>;
|
|
17
16
|
getJob(jobID: string): Promise<Job>;
|
package/dist/@types/model.d.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
export interface ScrapeRequest {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
url: string;
|
|
3
|
+
output_format: string;
|
|
4
|
+
webhook_url?: string;
|
|
5
|
+
clean_selectors?: string;
|
|
6
|
+
actions?: Action[];
|
|
7
|
+
prompt?: string;
|
|
7
8
|
}
|
|
8
|
-
export interface
|
|
9
|
+
export interface ScrapeId {
|
|
9
10
|
id: string;
|
|
10
11
|
}
|
|
11
12
|
export interface ScrapeResponse {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
success: boolean;
|
|
14
|
+
status?: string;
|
|
15
|
+
markdown?: string;
|
|
16
|
+
cleaned_content?: string;
|
|
17
|
+
raw_content?: string;
|
|
15
18
|
page_status_code: number;
|
|
16
|
-
|
|
17
|
-
structured_data
|
|
18
|
-
|
|
19
|
+
page_title?: string;
|
|
20
|
+
structured_data?: any;
|
|
21
|
+
}
|
|
22
|
+
export interface ScrapeResponseError {
|
|
23
|
+
success: boolean;
|
|
24
|
+
error_code: string;
|
|
25
|
+
error_message: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface JobId {
|
|
29
|
+
id: string;
|
|
19
30
|
}
|
|
20
31
|
export interface CrawlRequest {
|
|
21
32
|
url?: string;
|
package/dist/api.js
CHANGED
|
@@ -22,7 +22,46 @@ Object.defineProperty(exports, "WebcrawlerApiError", { enumerable: true, get: fu
|
|
|
22
22
|
const BASE_PATH = "https://api.webcrawlerapi.com";
|
|
23
23
|
const initialPullDelayMs = 2000;
|
|
24
24
|
const MaxPullRetries = 100;
|
|
25
|
+
const DEFAULT_POLL_DELAY_SECONDS = 2;
|
|
25
26
|
__exportStar(require("./model"), exports);
|
|
27
|
+
const SCRAPE_VERSION = "v2";
|
|
28
|
+
function addGetContentMethod(job) {
|
|
29
|
+
// Transform each job item to include getContent method
|
|
30
|
+
job.job_items = job.job_items.map(item => ({
|
|
31
|
+
...item,
|
|
32
|
+
getContent: async function () {
|
|
33
|
+
if (job.status !== constants_1.JobStatus.DONE || this.status !== constants_1.JobStatus.DONE) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
let contentUrl;
|
|
37
|
+
switch (job.scrape_type) {
|
|
38
|
+
case 'html':
|
|
39
|
+
contentUrl = this.raw_content_url;
|
|
40
|
+
break;
|
|
41
|
+
case 'cleaned':
|
|
42
|
+
contentUrl = this.cleaned_content_url;
|
|
43
|
+
break;
|
|
44
|
+
case 'markdown':
|
|
45
|
+
contentUrl = this.markdown_content_url;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (!contentUrl) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const response = await fetch(contentUrl, {
|
|
52
|
+
headers: {
|
|
53
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
54
|
+
'Accept': '*/*'
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`Failed to fetch content: ${response.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
return await response.text();
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
return job;
|
|
64
|
+
}
|
|
26
65
|
class WebcrawlerClient {
|
|
27
66
|
constructor(apiKey, basePath = BASE_PATH, apiVersion = "v1") {
|
|
28
67
|
this.apiVersion = "v1";
|
|
@@ -30,8 +69,9 @@ class WebcrawlerClient {
|
|
|
30
69
|
this.basePath = basePath;
|
|
31
70
|
this.apiVersion = apiVersion;
|
|
32
71
|
}
|
|
33
|
-
async scrapeAsync(
|
|
34
|
-
const
|
|
72
|
+
async scrapeAsync(request) {
|
|
73
|
+
const apiUrl = `${this.basePath}/${SCRAPE_VERSION}/scrape?async=true`;
|
|
74
|
+
console.log(request);
|
|
35
75
|
const requestOptions = {
|
|
36
76
|
'method': 'POST',
|
|
37
77
|
'headers': {
|
|
@@ -39,46 +79,13 @@ class WebcrawlerClient {
|
|
|
39
79
|
'Authorization': `Bearer ${this.apiKey}`,
|
|
40
80
|
"User-Agent": "WebcrawlerAPI-NodeJS-Client"
|
|
41
81
|
},
|
|
42
|
-
'body': JSON.stringify(
|
|
43
|
-
};
|
|
44
|
-
return await this.sendRequest(url, requestOptions);
|
|
45
|
-
}
|
|
46
|
-
async scrapeWithMeta(scrapeRequest, maxPollingRetries = MaxPullRetries) {
|
|
47
|
-
const url = `${this.basePath}/${this.apiVersion}/scrape`;
|
|
48
|
-
const requestOptions = {
|
|
49
|
-
'method': 'POST',
|
|
50
|
-
'headers': {
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
53
|
-
},
|
|
54
|
-
'body': JSON.stringify(scrapeRequest),
|
|
82
|
+
'body': JSON.stringify(request),
|
|
55
83
|
};
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
throw new errors_1.WebcrawlerApiError('invalid_response', 'Failed to fetch job status', 0);
|
|
59
|
-
}
|
|
60
|
-
let delayIntervalMs = initialPullDelayMs;
|
|
61
|
-
for (let i = 0; i < maxPollingRetries; i++) {
|
|
62
|
-
await new Promise(resolve => setTimeout(resolve, delayIntervalMs));
|
|
63
|
-
const scrapeResult = await this.getScrapeResult(jobIdResponse.id);
|
|
64
|
-
if (scrapeRequest.debug) {
|
|
65
|
-
console.log(`Scrape result: ${JSON.stringify(scrapeResult)}`);
|
|
66
|
-
}
|
|
67
|
-
if (scrapeResult.status !== constants_1.JobStatus.IN_PROGRESS && scrapeResult.status !== constants_1.JobStatus.NEW) {
|
|
68
|
-
return scrapeResult;
|
|
69
|
-
}
|
|
70
|
-
if (scrapeResult.recommended_pull_delay_ms > 0) {
|
|
71
|
-
delayIntervalMs = scrapeResult.recommended_pull_delay_ms;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
throw new errors_1.WebcrawlerApiError('timeout', 'Scraping took too long, please retry or increase the number of polling retries', 0);
|
|
75
|
-
}
|
|
76
|
-
async scrape(scrapeRequest, maxPollingRetries = MaxPullRetries) {
|
|
77
|
-
const scrapeResult = await this.scrapeWithMeta(scrapeRequest, maxPollingRetries);
|
|
78
|
-
return scrapeResult.structured_data;
|
|
84
|
+
const response = await this.sendRequest(apiUrl, requestOptions);
|
|
85
|
+
return { id: response.id };
|
|
79
86
|
}
|
|
80
|
-
async
|
|
81
|
-
const url = `${this.basePath}/${
|
|
87
|
+
async getScrape(scrapeId) {
|
|
88
|
+
const url = `${this.basePath}/${SCRAPE_VERSION}/scrape/${scrapeId}`;
|
|
82
89
|
const requestOptions = {
|
|
83
90
|
'method': 'GET',
|
|
84
91
|
'headers': {
|
|
@@ -90,7 +97,46 @@ class WebcrawlerClient {
|
|
|
90
97
|
'Expires': '0'
|
|
91
98
|
},
|
|
92
99
|
};
|
|
93
|
-
|
|
100
|
+
const responseData = await this.sendRequest(url, requestOptions);
|
|
101
|
+
const status = responseData.status;
|
|
102
|
+
if (status === "done") {
|
|
103
|
+
return responseData;
|
|
104
|
+
}
|
|
105
|
+
else if (status === "error") {
|
|
106
|
+
return responseData;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// in_progress or any other status
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
status: status,
|
|
113
|
+
page_status_code: 0
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async scrape(request, maxPolls = 100) {
|
|
118
|
+
// Start the scraping job
|
|
119
|
+
const scrapeIdResponse = await this.scrapeAsync(request);
|
|
120
|
+
const scrapeId = scrapeIdResponse.id;
|
|
121
|
+
let polls = 0;
|
|
122
|
+
let result;
|
|
123
|
+
while (polls < maxPolls) {
|
|
124
|
+
result = await this.getScrape(scrapeId);
|
|
125
|
+
// Return immediately if scrape is done
|
|
126
|
+
if ('status' in result && result.status === "done") {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
// Return immediately if there's an error
|
|
130
|
+
if ('error_code' in result) {
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
// Continue polling if status is in_progress or any other non-terminal status
|
|
134
|
+
// Wait before next poll
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_DELAY_SECONDS * 1000));
|
|
136
|
+
polls++;
|
|
137
|
+
}
|
|
138
|
+
// Return the last known state if maxPolls is reached
|
|
139
|
+
return result;
|
|
94
140
|
}
|
|
95
141
|
async crawl(crawlRequest, actions) {
|
|
96
142
|
const url = `${this.basePath}/${this.apiVersion}/crawl`;
|
|
@@ -120,40 +166,6 @@ class WebcrawlerClient {
|
|
|
120
166
|
const timestamp = new Date().getTime();
|
|
121
167
|
const job = await this.getJob(`${jobIdResponse.id}?t=${timestamp}`);
|
|
122
168
|
if (job.status !== constants_1.JobStatus.IN_PROGRESS && job.status !== constants_1.JobStatus.NEW) {
|
|
123
|
-
// Transform each job item to include getContent method
|
|
124
|
-
job.job_items = job.job_items.map(item => ({
|
|
125
|
-
...item,
|
|
126
|
-
getContent: async function () {
|
|
127
|
-
if (job.status !== constants_1.JobStatus.DONE || this.status !== constants_1.JobStatus.DONE) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
let contentUrl;
|
|
131
|
-
switch (job.scrape_type) {
|
|
132
|
-
case 'html':
|
|
133
|
-
contentUrl = this.raw_content_url;
|
|
134
|
-
break;
|
|
135
|
-
case 'cleaned':
|
|
136
|
-
contentUrl = this.cleaned_content_url;
|
|
137
|
-
break;
|
|
138
|
-
case 'markdown':
|
|
139
|
-
contentUrl = this.markdown_content_url;
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
if (!contentUrl) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
const response = await fetch(contentUrl, {
|
|
146
|
-
headers: {
|
|
147
|
-
'Accept-Encoding': 'gzip, deflate, br',
|
|
148
|
-
'Accept': '*/*'
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
if (!response.ok) {
|
|
152
|
-
throw new Error(`Failed to fetch content: ${response.statusText}`);
|
|
153
|
-
}
|
|
154
|
-
return await response.text();
|
|
155
|
-
}
|
|
156
|
-
}));
|
|
157
169
|
return job;
|
|
158
170
|
}
|
|
159
171
|
if (job.recommended_pull_delay_ms > 0) {
|
|
@@ -191,7 +203,8 @@ class WebcrawlerClient {
|
|
|
191
203
|
'Expires': '0'
|
|
192
204
|
}
|
|
193
205
|
};
|
|
194
|
-
|
|
206
|
+
const job = await this.sendRequest(url, requestOptions);
|
|
207
|
+
return addGetContentMethod(job);
|
|
195
208
|
}
|
|
196
209
|
async sendRequest(url, requestOptions) {
|
|
197
210
|
let response;
|