webcrawlerapi-js 2.0.11 → 2.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 +52 -154
- package/dist/@types/api.d.ts +5 -2
- package/dist/@types/model.d.ts +56 -3
- package/dist/api.js +100 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,179 +1,77 @@
|
|
|
1
1
|
# JS client for WebcrawlerAPI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Webcrawler API for your AI agent. Get markdown of any website or page with a single call.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Preparation
|
|
8
|
-
1. Register to [dashboard|https://dash.webcrawlerapi.com/].
|
|
9
|
-
2. Get an [Access Key](https://dash.webcrawlerapi.com/access).
|
|
10
|
-
|
|
11
|
-
Our dashboard provides comprehensive analytics and monitoring tools to help you track your API usage and optimize your scraping operations.
|
|
5
|
+
Official JavaScript/TypeScript client for [WebcrawlerAPI](https://webcrawlerapi.com/) — web scraping and crawling with JS rendering, proxy rotation, and AI extraction.
|
|
12
6
|
|
|
13
7
|
## Installation
|
|
14
|
-
Install WebcrawlerAPI js package:
|
|
15
|
-
|
|
16
|
-
`npm i webcrawlerapi-js`
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
```
|
|
10
|
+
npm i webcrawlerapi-js
|
|
11
|
+
```
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
Use `crawlRawMarkdown` when you want a crawl to finish and immediately return the combined `/job/{id}/markdown` content.
|
|
13
|
+
Get your API key at [dash.webcrawlerapi.com/access](https://dash.webcrawlerapi.com/access).
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
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.
|
|
15
|
+
## Scrape a single page
|
|
25
16
|
|
|
26
17
|
```javascript
|
|
27
18
|
import webcrawlerapi from "webcrawlerapi-js"
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"url": "https://books.toscrape.com/",
|
|
36
|
-
"scrape_type": "markdown"
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Get job details
|
|
40
|
-
const job = await client.getJob(crawlJob.id);
|
|
41
|
-
console.log("Job status:", job.status);
|
|
42
|
-
|
|
43
|
-
// Output item content
|
|
44
|
-
for (const item of job.job_items) {
|
|
45
|
-
const content = await item.getContent()
|
|
46
|
-
console.log("---- Title: " + item.title + "----")
|
|
47
|
-
console.log(content.slice(0, 100))
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error instanceof webcrawlerapi.WebcrawlerApiError) {
|
|
51
|
-
console.error("Webcrawler error:", error.message)
|
|
52
|
-
}
|
|
53
|
-
console.error(error)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
20
|
+
const client = new webcrawlerapi.WebcrawlerClient("YOUR_API_KEY")
|
|
21
|
+
|
|
22
|
+
const result = await client.scrape({
|
|
23
|
+
url: "https://books.toscrape.com/",
|
|
24
|
+
output_formats: ["markdown"],
|
|
25
|
+
})
|
|
56
26
|
|
|
57
|
-
|
|
27
|
+
console.log(result.markdown)
|
|
58
28
|
```
|
|
59
29
|
|
|
60
|
-
|
|
61
|
-
In this mode, you get a job ID immediately and can poll for results. Asynchronous jobs are ideal for large-scale scraping operations where you want to process data in batches or handle long-running tasks efficiently.
|
|
30
|
+
## Crawl multiple pages
|
|
62
31
|
|
|
63
32
|
```javascript
|
|
64
33
|
import webcrawlerapi from "webcrawlerapi-js"
|
|
65
34
|
|
|
66
|
-
|
|
67
|
-
const client = new webcrawlerapi.WebcrawlerClient("YOUR API ACCESS KEY HERE")
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
try {
|
|
71
|
-
const jobWithId = await client.crawlAsync({
|
|
72
|
-
"items_limit": 10,
|
|
73
|
-
"url": "https://books.toscrape.com/",
|
|
74
|
-
"scrape_type": "markdown"
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Poll for job completion
|
|
78
|
-
const jobId = jobWithId.id
|
|
79
|
-
let asyncJob = await client.getJob(jobId)
|
|
80
|
-
|
|
81
|
-
// Check job status
|
|
82
|
-
console.log("Job id:", asyncJob.id)
|
|
83
|
-
console.log("Dashboard link:", `https://dash.webcrawlerapi.com/jobs/job/${asyncJob.id}`)
|
|
84
|
-
|
|
85
|
-
//wait until job is done
|
|
86
|
-
while (asyncJob.status !== "done") {
|
|
87
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
88
|
-
asyncJob = await client.getJob(jobId)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Once complete, access results
|
|
92
|
-
if (asyncJob.status === "done") {
|
|
93
|
-
for (const item of asyncJob.job_items) {
|
|
94
|
-
const content = await item.getContent()
|
|
95
|
-
console.log(content.slice(0, 100))
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
} catch (error) {
|
|
99
|
-
if (error instanceof webcrawlerapi.WebcrawlerApiError) {
|
|
100
|
-
console.error("Webcrawler error:", error.message)
|
|
101
|
-
}
|
|
102
|
-
console.error(error)
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
}
|
|
35
|
+
const client = new webcrawlerapi.WebcrawlerClient("YOUR_API_KEY")
|
|
106
36
|
|
|
107
|
-
|
|
108
|
-
|
|
37
|
+
const job = await client.crawl({
|
|
38
|
+
url: "https://books.toscrape.com/",
|
|
39
|
+
items_limit: 10,
|
|
40
|
+
output_formats: ["markdown"],
|
|
41
|
+
})
|
|
109
42
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import {WebcrawlerClient, WebcrawlerApiError} from "webcrawlerapi-js";
|
|
116
|
-
|
|
117
|
-
async function main() {
|
|
118
|
-
const client = new webcrawlerapi.WebcrawlerClient("YOUR_API_KEY")
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
// Basic crawl with markdown
|
|
122
|
-
const syncJob = await client.crawl({
|
|
123
|
-
"items_limit": 10,
|
|
124
|
-
"url": "https://books.toscrape.com/",
|
|
125
|
-
"scrape_type": "markdown",
|
|
126
|
-
}
|
|
127
|
-
)
|
|
128
|
-
// Type-safe status check
|
|
129
|
-
for (const item of syncJob.job_items) {
|
|
130
|
-
const content = await item.getContent()
|
|
131
|
-
console.log(content.slice(0,100))
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (error instanceof WebcrawlerApiError) {
|
|
135
|
-
console.error("API Error:", error.errorCode, error.message)
|
|
136
|
-
}
|
|
137
|
-
console.log(error)
|
|
138
|
-
}
|
|
43
|
+
for (const item of job.job_items) {
|
|
44
|
+
const content = await item.getContent()
|
|
45
|
+
console.log(item.title, content?.slice(0, 100))
|
|
139
46
|
}
|
|
140
|
-
|
|
141
|
-
main().catch(console.error)
|
|
142
47
|
```
|
|
143
48
|
|
|
144
|
-
##
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
status: 'new',
|
|
168
|
-
title: '',
|
|
169
|
-
last_error: '',
|
|
170
|
-
error_code: '',
|
|
171
|
-
created_at: '2024-12-28T21:36:04.468Z',
|
|
172
|
-
updated_at: '2024-12-28T21:36:04.435Z',
|
|
173
|
-
cost: 0,
|
|
174
|
-
referred_url: ''
|
|
175
|
-
}
|
|
176
|
-
],
|
|
177
|
-
recommended_pull_delay_ms: 5000
|
|
178
|
-
}
|
|
49
|
+
## Run an agent
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
import webcrawlerapi from "webcrawlerapi-js"
|
|
53
|
+
|
|
54
|
+
const client = new webcrawlerapi.WebcrawlerClient("YOUR_API_KEY")
|
|
55
|
+
|
|
56
|
+
const result = await client.runAgent({
|
|
57
|
+
prompt: "Visit the provided website and return the page title as JSON.",
|
|
58
|
+
urls: ["https://books.toscrape.com/"],
|
|
59
|
+
seed_urls_only: true,
|
|
60
|
+
output_schema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
title: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
required: ["title"],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
max_spend_usd: 0.02,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
console.log(result.status, result.data)
|
|
179
72
|
```
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
- [Scraping](docs/scraping.md) — single page, async mode, AI extraction, parameters, response fields
|
|
77
|
+
- [Crawling](docs/crawling.md) — multi-page, async mode, combined markdown, parameters, job items
|
package/dist/@types/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse, ScrapeResponseError, ScrapeId, Action, JobMarkdownResponse } from "./model";
|
|
1
|
+
import { CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse, ScrapeResponseError, ScrapeId, Action, JobMarkdownResponse, AgentRunRequest, AgentRun } from "./model";
|
|
2
2
|
import { JobStatus, ErrorCode } from "./constants";
|
|
3
3
|
import { WebcrawlerApiError } from "./errors";
|
|
4
4
|
export { WebcrawlerApiError, JobStatus, ErrorCode };
|
|
@@ -8,10 +8,13 @@ export declare class WebcrawlerClient {
|
|
|
8
8
|
protected apiKey: string;
|
|
9
9
|
protected basePath: string;
|
|
10
10
|
constructor(apiKey: string, basePath?: string, apiVersion?: string);
|
|
11
|
+
private getDefaultHeaders;
|
|
11
12
|
scrapeAsync(request: ScrapeRequest): Promise<ScrapeId>;
|
|
13
|
+
runAgentAsync(request: AgentRunRequest): Promise<AgentRun>;
|
|
14
|
+
getAgentJob(jobID: string): Promise<AgentRun>;
|
|
15
|
+
runAgent(request: AgentRunRequest, maxPolls?: number): Promise<AgentRun>;
|
|
12
16
|
getScrape(scrapeId: string): Promise<ScrapeResponse | ScrapeResponseError>;
|
|
13
17
|
scrape(request: ScrapeRequest, maxPolls?: number): Promise<ScrapeResponse | ScrapeResponseError>;
|
|
14
|
-
crawlRawMarkdown(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<string>;
|
|
15
18
|
crawl(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<Job>;
|
|
16
19
|
crawlAsync(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<JobId>;
|
|
17
20
|
getJob(jobID: string): Promise<Job>;
|
package/dist/@types/model.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
export interface ScrapeRequest {
|
|
2
2
|
url: string;
|
|
3
|
+
/** @deprecated Use output_formats instead */
|
|
3
4
|
output_format?: string;
|
|
5
|
+
output_formats?: ('markdown' | 'cleaned' | 'html' | 'links')[];
|
|
4
6
|
webhook_url?: string;
|
|
7
|
+
/** CSS selectors to clean from the output. */
|
|
5
8
|
clean_selectors?: string;
|
|
6
9
|
actions?: Action[];
|
|
10
|
+
/** A prompt to run on the scraped content, used to extract specific information or format the output (extra cost will be charged). */
|
|
7
11
|
prompt?: string;
|
|
12
|
+
/** JSON schema to enforce structured output when using a prompt. Follows the OpenAI Structured Outputs format. */
|
|
8
13
|
response_schema?: Record<string, any>;
|
|
9
|
-
|
|
14
|
+
/** Extract only the main content of the page. Works best for articles, blog posts, news. */
|
|
10
15
|
main_content_only?: boolean;
|
|
16
|
+
/** Maximum age of cached content in seconds. Set to 0 to always fetch fresh content. */
|
|
11
17
|
max_age?: number;
|
|
12
18
|
}
|
|
13
19
|
export interface ScrapeId {
|
|
@@ -34,17 +40,27 @@ export interface JobId {
|
|
|
34
40
|
id: string;
|
|
35
41
|
}
|
|
36
42
|
export interface CrawlRequest {
|
|
43
|
+
/** The seed URL where the crawler starts. Can be any valid URL. */
|
|
37
44
|
url?: string;
|
|
38
|
-
|
|
45
|
+
/** The crawler will stop when it reaches this limit of pages for this job. */
|
|
39
46
|
items_limit?: number;
|
|
47
|
+
/** @deprecated Use output_formats instead */
|
|
40
48
|
scrape_type?: string;
|
|
49
|
+
output_formats?: ('links' | 'markdown' | 'cleaned' | 'html')[];
|
|
50
|
+
/** Regular expression to whitelist URLs. Only URLs that match the pattern will be crawled. */
|
|
41
51
|
whitelist_regexp?: string;
|
|
52
|
+
/** Regular expression to blacklist URLs. URLs that match the pattern will be skipped. */
|
|
42
53
|
blacklist_regexp?: string;
|
|
54
|
+
/** URL where the server will send a POST request once the job is completed. */
|
|
43
55
|
webhook_url?: string;
|
|
44
56
|
actions?: Action[];
|
|
57
|
+
/** If true, the crawler will respect the website's robots.txt and skip disallowed pages. Default is false. */
|
|
45
58
|
respect_robots_txt?: boolean;
|
|
59
|
+
/** Extract only the main content of the page. Works best for articles, blog posts, news. */
|
|
46
60
|
main_content_only?: boolean;
|
|
61
|
+
/** Maximum crawl depth from the starting URL. 0 = starting page only, 1 = starting page + directly linked pages, etc. No limit by default. */
|
|
47
62
|
max_depth?: number;
|
|
63
|
+
/** Maximum age of cached content in seconds. Set to 0 to always fetch fresh content. */
|
|
48
64
|
max_age?: number;
|
|
49
65
|
}
|
|
50
66
|
export interface Job {
|
|
@@ -52,7 +68,9 @@ export interface Job {
|
|
|
52
68
|
org_id: string;
|
|
53
69
|
url: string;
|
|
54
70
|
status: string;
|
|
55
|
-
|
|
71
|
+
/** @deprecated Use output_formats instead */
|
|
72
|
+
scrape_type?: string;
|
|
73
|
+
output_formats?: string[];
|
|
56
74
|
whitelist_regexp: string;
|
|
57
75
|
blacklist_regexp: string;
|
|
58
76
|
items_limit: number;
|
|
@@ -83,6 +101,9 @@ export interface JobItem {
|
|
|
83
101
|
cleaned_content_url?: string;
|
|
84
102
|
error_code?: string;
|
|
85
103
|
getContent(): Promise<string | null>;
|
|
104
|
+
getMarkdown(): Promise<string | null>;
|
|
105
|
+
getCleaned(): Promise<string | null>;
|
|
106
|
+
getHTML(): Promise<string | null>;
|
|
86
107
|
}
|
|
87
108
|
export interface Action {
|
|
88
109
|
type: string;
|
|
@@ -98,3 +119,35 @@ export interface UploadS3Action extends Action {
|
|
|
98
119
|
export interface JobMarkdownResponse {
|
|
99
120
|
content_url: string;
|
|
100
121
|
}
|
|
122
|
+
export interface AgentRunRequest {
|
|
123
|
+
/** Natural-language task for the agent to complete. */
|
|
124
|
+
prompt: string;
|
|
125
|
+
/** Optional seed URLs the agent can use while running the task. */
|
|
126
|
+
urls?: string[];
|
|
127
|
+
/** If true, the agent only uses the provided seed URLs. */
|
|
128
|
+
seed_urls_only?: boolean;
|
|
129
|
+
/** Optional JSON schema for structured agent output. */
|
|
130
|
+
output_schema?: Record<string, any>;
|
|
131
|
+
/** Model to use for the run. Omit to use the API default. */
|
|
132
|
+
model?: string;
|
|
133
|
+
/** Maximum spend for this run in USD. Required by the API. */
|
|
134
|
+
max_spend_usd: number;
|
|
135
|
+
}
|
|
136
|
+
export interface AgentRun {
|
|
137
|
+
id: string;
|
|
138
|
+
status: string;
|
|
139
|
+
prompt: string;
|
|
140
|
+
model: string;
|
|
141
|
+
data?: any;
|
|
142
|
+
error_reason?: string;
|
|
143
|
+
balance_used_usd: number;
|
|
144
|
+
max_spend_usd?: number;
|
|
145
|
+
urls?: string[];
|
|
146
|
+
created_at: string;
|
|
147
|
+
updated_at: string;
|
|
148
|
+
success: boolean;
|
|
149
|
+
}
|
|
150
|
+
export interface AgentRunList {
|
|
151
|
+
items: AgentRun[];
|
|
152
|
+
total: number;
|
|
153
|
+
}
|
package/dist/api.js
CHANGED
|
@@ -24,8 +24,47 @@ const BASE_PATH = "https://api.webcrawlerapi.com";
|
|
|
24
24
|
const initialPullDelayMs = 2000;
|
|
25
25
|
const MaxPullRetries = 100;
|
|
26
26
|
const DEFAULT_POLL_DELAY_SECONDS = 2;
|
|
27
|
+
const AgentTerminalStatuses = ["done", "error", "canceled"];
|
|
27
28
|
__exportStar(require("./model"), exports);
|
|
28
29
|
const SCRAPE_VERSION = "v2";
|
|
30
|
+
function resolveContentUrl(job, item) {
|
|
31
|
+
// Prefer output_formats if present and non-empty, using priority: markdown > cleaned > html
|
|
32
|
+
if (Array.isArray(job.output_formats) && job.output_formats.length > 0) {
|
|
33
|
+
const priority = ['markdown', 'cleaned', 'html'];
|
|
34
|
+
for (const fmt of priority) {
|
|
35
|
+
if (job.output_formats.includes(fmt)) {
|
|
36
|
+
switch (fmt) {
|
|
37
|
+
case 'markdown': return item.markdown_content_url;
|
|
38
|
+
case 'cleaned': return item.cleaned_content_url;
|
|
39
|
+
case 'html': return item.raw_content_url;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
// Fall back to scrape_type for backward compatibility
|
|
46
|
+
switch (job.scrape_type) {
|
|
47
|
+
case 'html': return item.raw_content_url;
|
|
48
|
+
case 'cleaned': return item.cleaned_content_url;
|
|
49
|
+
case 'markdown': return item.markdown_content_url;
|
|
50
|
+
default: return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function fetchContentUrl(url) {
|
|
54
|
+
if (!url) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
headers: {
|
|
59
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
60
|
+
'Accept': '*/*'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`Failed to fetch content: ${response.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
return response.text();
|
|
67
|
+
}
|
|
29
68
|
function addGetContentMethod(job) {
|
|
30
69
|
// Transform each job item to include getContent method
|
|
31
70
|
job.job_items = job.job_items.map(item => ({
|
|
@@ -34,31 +73,17 @@ function addGetContentMethod(job) {
|
|
|
34
73
|
if (job.status !== constants_1.JobStatus.DONE || this.status !== constants_1.JobStatus.DONE) {
|
|
35
74
|
return null;
|
|
36
75
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
if (!contentUrl) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const response = await fetch(contentUrl, {
|
|
53
|
-
headers: {
|
|
54
|
-
'Accept-Encoding': 'gzip, deflate, br',
|
|
55
|
-
'Accept': '*/*'
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new Error(`Failed to fetch content: ${response.statusText}`);
|
|
60
|
-
}
|
|
61
|
-
return await response.text();
|
|
76
|
+
const contentUrl = resolveContentUrl(job, this);
|
|
77
|
+
return fetchContentUrl(contentUrl);
|
|
78
|
+
},
|
|
79
|
+
getMarkdown: async function () {
|
|
80
|
+
return fetchContentUrl(this.markdown_content_url);
|
|
81
|
+
},
|
|
82
|
+
getCleaned: async function () {
|
|
83
|
+
return fetchContentUrl(this.cleaned_content_url);
|
|
84
|
+
},
|
|
85
|
+
getHTML: async function () {
|
|
86
|
+
return fetchContentUrl(this.raw_content_url);
|
|
62
87
|
}
|
|
63
88
|
}));
|
|
64
89
|
return job;
|
|
@@ -70,6 +95,19 @@ class WebcrawlerClient {
|
|
|
70
95
|
this.basePath = basePath;
|
|
71
96
|
this.apiVersion = apiVersion;
|
|
72
97
|
}
|
|
98
|
+
getDefaultHeaders(cacheControl = false) {
|
|
99
|
+
const headers = {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
102
|
+
"User-Agent": "WebcrawlerAPI-NodeJS-Client"
|
|
103
|
+
};
|
|
104
|
+
if (cacheControl) {
|
|
105
|
+
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
|
|
106
|
+
headers['Pragma'] = 'no-cache';
|
|
107
|
+
headers['Expires'] = '0';
|
|
108
|
+
}
|
|
109
|
+
return headers;
|
|
110
|
+
}
|
|
73
111
|
async scrapeAsync(request) {
|
|
74
112
|
const apiUrl = `${this.basePath}/${SCRAPE_VERSION}/scrape?async=true`;
|
|
75
113
|
const requestOptions = {
|
|
@@ -84,6 +122,41 @@ class WebcrawlerClient {
|
|
|
84
122
|
const response = await this.sendRequest(apiUrl, requestOptions);
|
|
85
123
|
return { id: response.id };
|
|
86
124
|
}
|
|
125
|
+
async runAgentAsync(request) {
|
|
126
|
+
const url = `${this.basePath}/${this.apiVersion}/agent`;
|
|
127
|
+
const requestOptions = {
|
|
128
|
+
'method': 'POST',
|
|
129
|
+
'headers': this.getDefaultHeaders(),
|
|
130
|
+
'body': JSON.stringify(request),
|
|
131
|
+
};
|
|
132
|
+
return await this.sendRequest(url, requestOptions);
|
|
133
|
+
}
|
|
134
|
+
async getAgentJob(jobID) {
|
|
135
|
+
const url = `${this.basePath}/${this.apiVersion}/agent/job/${jobID}`;
|
|
136
|
+
const requestOptions = {
|
|
137
|
+
'method': 'GET',
|
|
138
|
+
'headers': this.getDefaultHeaders(true)
|
|
139
|
+
};
|
|
140
|
+
return await this.sendRequest(url, requestOptions);
|
|
141
|
+
}
|
|
142
|
+
async runAgent(request, maxPolls = MaxPullRetries) {
|
|
143
|
+
const run = await this.runAgentAsync(request);
|
|
144
|
+
if (!run.id) {
|
|
145
|
+
throw new errors_1.WebcrawlerApiError('invalid_response', 'Failed to fetch agent job status', 0);
|
|
146
|
+
}
|
|
147
|
+
if (AgentTerminalStatuses.includes(run.status)) {
|
|
148
|
+
return run;
|
|
149
|
+
}
|
|
150
|
+
for (let i = 0; i < maxPolls; i++) {
|
|
151
|
+
await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_DELAY_SECONDS * 1000));
|
|
152
|
+
const timestamp = new Date().getTime();
|
|
153
|
+
const agentJob = await this.getAgentJob(`${run.id}?t=${timestamp}`);
|
|
154
|
+
if (AgentTerminalStatuses.includes(agentJob.status)) {
|
|
155
|
+
return agentJob;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw new errors_1.WebcrawlerApiError('timeout', 'Agent run took too long, please retry or increase the number of polling retries', 0);
|
|
159
|
+
}
|
|
87
160
|
async getScrape(scrapeId) {
|
|
88
161
|
const url = `${this.basePath}/${SCRAPE_VERSION}/scrape/${scrapeId}`;
|
|
89
162
|
const requestOptions = {
|
|
@@ -138,23 +211,10 @@ class WebcrawlerClient {
|
|
|
138
211
|
// Return the last known state if maxPolls is reached
|
|
139
212
|
return result;
|
|
140
213
|
}
|
|
141
|
-
async crawlRawMarkdown(crawlRequest, actions) {
|
|
142
|
-
const markdownRequest = {
|
|
143
|
-
...crawlRequest,
|
|
144
|
-
scrape_type: crawlRequest.scrape_type ?? 'markdown'
|
|
145
|
-
};
|
|
146
|
-
const job = await this.crawl(markdownRequest, actions);
|
|
147
|
-
if (job.status !== constants_1.JobStatus.DONE) {
|
|
148
|
-
throw new errors_1.WebcrawlerApiError('job_not_done', `Job finished with status ${job.status}`, 0);
|
|
149
|
-
}
|
|
150
|
-
if (job.scrape_type !== 'markdown') {
|
|
151
|
-
throw new errors_1.WebcrawlerApiError('invalid_scrape_type', 'CrawlRawMarkdown requires scrape_type to be markdown', 0);
|
|
152
|
-
}
|
|
153
|
-
return this.getJobMarkdownContent(job.id);
|
|
154
|
-
}
|
|
155
214
|
async crawl(crawlRequest, actions) {
|
|
156
215
|
const url = `${this.basePath}/${this.apiVersion}/crawl`;
|
|
157
216
|
const requestBody = {
|
|
217
|
+
output_formats: ['markdown'],
|
|
158
218
|
...crawlRequest,
|
|
159
219
|
actions: actions ? (Array.isArray(actions) ? actions : [actions]) : undefined
|
|
160
220
|
};
|
|
@@ -191,6 +251,7 @@ class WebcrawlerClient {
|
|
|
191
251
|
async crawlAsync(crawlRequest, actions) {
|
|
192
252
|
const url = `${this.basePath}/${this.apiVersion}/crawl`;
|
|
193
253
|
const requestBody = {
|
|
254
|
+
output_formats: ['markdown'],
|
|
194
255
|
...crawlRequest,
|
|
195
256
|
actions: actions ? (Array.isArray(actions) ? actions : [actions]) : undefined
|
|
196
257
|
};
|