webcrawlerapi-js 2.0.11 → 2.1.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 CHANGED
@@ -1,179 +1,52 @@
1
1
  # JS client for WebcrawlerAPI
2
2
 
3
- Official client for [WebcrawlerAPI](https://webcrawlerapi.com/). This SDK provides a robust and type-safe way to interact with WebcrawlerAPI's powerful web scraping capabilities, making it easy to extract structured data from any website.
3
+ Webcrawler API for your AI agent. Get markdown of any website or page with a single call.
4
4
 
5
- WebcrawlerAPI allows you to extract data from any website with just a simple API call. The service handles all the complexities of web scraping, including JavaScript rendering, proxy rotation, and rate limiting, so you can focus on using the data.
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
8
 
16
- `npm i webcrawlerapi-js`
17
-
18
- The package is regularly updated with new features and improvements to ensure the best possible scraping experience.
9
+ ```
10
+ npm i webcrawlerapi-js
11
+ ```
19
12
 
20
- ## JavaScript Examples
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
- ### Synchronous Job
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
- async function main() {
30
- const client = new webcrawlerapi.WebcrawlerClient("YOUR API ACCESS KEY HERE")
31
-
32
- try {
33
- const crawlJob = await client.crawl({
34
- "items_limit": 10,
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
- main().catch(console.error)
27
+ console.log(result.markdown)
58
28
  ```
59
29
 
60
- ### Asynchronous Job
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
- async function main() {
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
- main().catch(console.error)
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
- ## TypeScript Usage
111
-
112
- 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.
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
- ## Response Structure
145
-
146
- 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.
147
-
148
- ```js
149
- {
150
- id: '49c4942b-b7d9-4d62-94b5-b54a3016ac51',
151
- url: 'https://stripe.com/',
152
- scrape_type: 'markdown',
153
- whitelist_regexp: '',
154
- blacklist_regexp: '',
155
- items_limit: 10,
156
- created_at: '2024-12-28T21:36:04.417Z',
157
- finished_at: null,
158
- updated_at: '2024-12-28T21:36:04.383Z',
159
- webhook_url: '',
160
- status: 'in_progress',
161
- job_items: [
162
- {
163
- id: 'f26cefe1-09d1-4d4c-8b74-b65e075e230d',
164
- job_id: '49c4942a-b7d9-4d62-94b5-b54a3016ac51',
165
- original_url: 'https://stripe.com/',
166
- page_status_code: 0,
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
- }
179
- ```
49
+ ## Documentation
50
+
51
+ - [Scraping](docs/scraping.md) — single page, async mode, AI extraction, parameters, response fields
52
+ - [Crawling](docs/crawling.md) — multi-page, async mode, combined markdown, parameters, job items
@@ -11,7 +11,6 @@ export declare class WebcrawlerClient {
11
11
  scrapeAsync(request: ScrapeRequest): Promise<ScrapeId>;
12
12
  getScrape(scrapeId: string): Promise<ScrapeResponse | ScrapeResponseError>;
13
13
  scrape(request: ScrapeRequest, maxPolls?: number): Promise<ScrapeResponse | ScrapeResponseError>;
14
- crawlRawMarkdown(crawlRequest: CrawlRequest, actions?: Action | Action[]): Promise<string>;
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>;
@@ -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
- respect_robots_txt?: boolean;
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
- crawler_id?: string;
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
- scrape_type: string;
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;
package/dist/api.js CHANGED
@@ -26,6 +26,44 @@ const MaxPullRetries = 100;
26
26
  const DEFAULT_POLL_DELAY_SECONDS = 2;
27
27
  __exportStar(require("./model"), exports);
28
28
  const SCRAPE_VERSION = "v2";
29
+ function resolveContentUrl(job, item) {
30
+ // Prefer output_formats if present and non-empty, using priority: markdown > cleaned > html
31
+ if (Array.isArray(job.output_formats) && job.output_formats.length > 0) {
32
+ const priority = ['markdown', 'cleaned', 'html'];
33
+ for (const fmt of priority) {
34
+ if (job.output_formats.includes(fmt)) {
35
+ switch (fmt) {
36
+ case 'markdown': return item.markdown_content_url;
37
+ case 'cleaned': return item.cleaned_content_url;
38
+ case 'html': return item.raw_content_url;
39
+ }
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ // Fall back to scrape_type for backward compatibility
45
+ switch (job.scrape_type) {
46
+ case 'html': return item.raw_content_url;
47
+ case 'cleaned': return item.cleaned_content_url;
48
+ case 'markdown': return item.markdown_content_url;
49
+ default: return undefined;
50
+ }
51
+ }
52
+ async function fetchContentUrl(url) {
53
+ if (!url) {
54
+ return null;
55
+ }
56
+ const response = await fetch(url, {
57
+ headers: {
58
+ 'Accept-Encoding': 'gzip, deflate, br',
59
+ 'Accept': '*/*'
60
+ }
61
+ });
62
+ if (!response.ok) {
63
+ throw new Error(`Failed to fetch content: ${response.statusText}`);
64
+ }
65
+ return response.text();
66
+ }
29
67
  function addGetContentMethod(job) {
30
68
  // Transform each job item to include getContent method
31
69
  job.job_items = job.job_items.map(item => ({
@@ -34,31 +72,17 @@ function addGetContentMethod(job) {
34
72
  if (job.status !== constants_1.JobStatus.DONE || this.status !== constants_1.JobStatus.DONE) {
35
73
  return null;
36
74
  }
37
- let contentUrl;
38
- switch (job.scrape_type) {
39
- case 'html':
40
- contentUrl = this.raw_content_url;
41
- break;
42
- case 'cleaned':
43
- contentUrl = this.cleaned_content_url;
44
- break;
45
- case 'markdown':
46
- contentUrl = this.markdown_content_url;
47
- break;
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();
75
+ const contentUrl = resolveContentUrl(job, this);
76
+ return fetchContentUrl(contentUrl);
77
+ },
78
+ getMarkdown: async function () {
79
+ return fetchContentUrl(this.markdown_content_url);
80
+ },
81
+ getCleaned: async function () {
82
+ return fetchContentUrl(this.cleaned_content_url);
83
+ },
84
+ getHTML: async function () {
85
+ return fetchContentUrl(this.raw_content_url);
62
86
  }
63
87
  }));
64
88
  return job;
@@ -138,23 +162,10 @@ class WebcrawlerClient {
138
162
  // Return the last known state if maxPolls is reached
139
163
  return result;
140
164
  }
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
165
  async crawl(crawlRequest, actions) {
156
166
  const url = `${this.basePath}/${this.apiVersion}/crawl`;
157
167
  const requestBody = {
168
+ output_formats: ['markdown'],
158
169
  ...crawlRequest,
159
170
  actions: actions ? (Array.isArray(actions) ? actions : [actions]) : undefined
160
171
  };
@@ -191,6 +202,7 @@ class WebcrawlerClient {
191
202
  async crawlAsync(crawlRequest, actions) {
192
203
  const url = `${this.basePath}/${this.apiVersion}/crawl`;
193
204
  const requestBody = {
205
+ output_formats: ['markdown'],
194
206
  ...crawlRequest,
195
207
  actions: actions ? (Array.isArray(actions) ? actions : [actions]) : undefined
196
208
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcrawlerapi-js",
3
- "version": "2.0.11",
3
+ "version": "2.1.0",
4
4
  "description": "JS client for WebcrawlerAPI",
5
5
  "main": "./dist/api.js",
6
6
  "types": "./dist/@types/api.d.ts",