webcrawlerapi-js 1.0.3 → 1.0.5

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,60 +1,85 @@
1
- # JS client for WebcrawlerAPI scrapers
1
+ # JS client for WebcrawlerAPI
2
2
 
3
- Official client for [WebcrawlerAPI](https://webcrawlerapi.com/) scrapers.
3
+ Official client for [WebcrawlerAPI](https://webcrawlerapi.com/).
4
4
 
5
- ## Installation
6
- Install WebcrawlerAPI js package:
7
-
8
- `npm i webcrawlerapi-js`
5
+ WebcrawlerAPI allows you to extract data from any website with just a simple API call.
9
6
 
10
7
  ## Preparation
11
8
  1. Register to [dashboard|https://dash.webcrawlerapi.com/].
12
9
  2. Get an [Access Key](https://dash.webcrawlerapi.com/access).
13
10
 
11
+ ## Installation
12
+ Install WebcrawlerAPI js package:
13
+
14
+ `npm i webcrawlerapi-js`
15
+
14
16
  ## Request example
15
17
 
16
18
  ```javascript
17
- const webcrawlerapi = require('webcrawlerapi-js');
19
+ import webcrawlerapi from "webcrawlerapi-js";
18
20
 
19
21
  async function main() {
20
22
  const client = new webcrawlerapi.WebcrawlerClient(
21
- "YOUR API KEY HERE"
23
+ "YOUR API ACCESS KEY HERE",
24
+ )
25
+ // sync way - promise will be resolved with the all the data
26
+ const syncJob = await client.crawl({
27
+ "items_limit": 10,
28
+ "url": "https://stripe.com/",
29
+ "scrape_type": "markdown"
30
+ }
22
31
  )
23
- const response = await client.scrape({
24
- input: {
25
- "url": "https://www.funda.nl/detail/koop/heerhugowaard/huis-govert-flinckplantsoen-1/89968455/"
26
- },
27
- crawler_id: "webcrawler/funda",
28
- })
29
- console.log(response)
32
+ console.log(syncJob);
33
+
34
+ // or async - get the job id and then poll the job status and get the data
35
+
36
+ const jobWithId = await client.crawlAsync({
37
+ "items_limit": 10,
38
+ "url": "https://stripe.com/",
39
+ "scrape_type": "markdown"
40
+ }
41
+ )
42
+ // wait for job to complete
43
+ const jobId = jobWithId.id;
44
+ let asyncJob = await client.getJob(jobId);
45
+
46
+ console.log(asyncJob);
30
47
  }
31
48
 
32
49
  main().catch(console.error);
33
50
  ```
34
51
 
35
52
  ## Response example
36
- ```json
53
+ ```javascript
37
54
  {
38
- city: 'Heerhugowaard',
39
- price: 325000,
40
- images: [ 'https://cloud.funda.nl/valentina_media/191/215/183_2160.jpg' ],
41
- status: 'inonderhandeling',
42
- videos: [],
43
- address: 'Govert Flinckplantsoen 1',
44
- country: 'Nederland',
45
- province: 'Noord-Holland',
46
- plot_area: '183 m²',
47
- post_code: '1701NH',
48
- description: 'De woning is met liefde en zorg 53 jaar bewoond door...',
49
- living_area: 127,
50
- house_number: 1,
51
- energie_label: 'd',
52
- property_type: 'woonhuis',
53
- publication_date: '2024-05-28T00:00:00',
54
- number_of_bedrooms: 4,
55
- coordinates_latitude: 52.67685,
56
- year_of_construction: 1971,
57
- coordinates_longitude: 4.8560443,
58
- house_number_extension: ''
55
+ id: '49c4942b-b7d9-4d62-94b5-b54a3016ac51',
56
+ org_id: 'clxsnorta00075wuuqxgzzvxm',
57
+ url: 'https://stripe.com/',
58
+ scrape_type: 'markdown',
59
+ whitelist_regexp: '',
60
+ blacklist_regexp: '',
61
+ allow_subdomains: false,
62
+ items_limit: 10,
63
+ created_at: '2024-12-28T21:36:04.417Z',
64
+ finished_at: null,
65
+ updated_at: '2024-12-28T21:36:04.383Z',
66
+ webhook_url: '',
67
+ status: 'in_progress',
68
+ job_items: [
69
+ {
70
+ id: 'f26cefe1-09d1-4d4c-8b74-b65e075e230d',
71
+ job_id: '49c4942a-b7d9-4d62-94b5-b54a3016ac51',
72
+ original_url: 'https://stripe.com/',
73
+ page_status_code: 0,
74
+ status: 'new',
75
+ title: '',
76
+ last_error: '',
77
+ created_at: '2024-12-28T21:36:04.468Z',
78
+ updated_at: '2024-12-28T21:36:04.435Z',
79
+ cost: 0,
80
+ referred_url: ''
81
+ }
82
+ ],
83
+ recommended_pull_delay_ms: 5000
59
84
  }
60
85
  ```
package/api.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {ScrapeIDResponse, ScrapeRequest, ScrapeResponse} from "./model";
1
+ import {CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse} from "./model";
2
2
 
3
3
  const BASE_PATH = "https://api.webcrawlerapi.com"
4
4
  const initialPullDelayMs = 2000
@@ -15,14 +15,15 @@ export class WebcrawlerClient {
15
15
  this.apiVersion = apiVersion;
16
16
  }
17
17
 
18
- public async scrapeAsync(scrapeRequest: ScrapeRequest): Promise<ScrapeIDResponse> {
18
+ public async scrapeAsync(scrapeRequest: ScrapeRequest): Promise<JobId> {
19
19
  const url = `${this.basePath}/${this.apiVersion}/scrape`;
20
20
 
21
21
  const requestOptions = {
22
22
  'method': 'POST',
23
23
  'headers': {
24
24
  'Content-Type': 'application/json',
25
- 'Authorization': `Bearer ${this.apiKey}`
25
+ 'Authorization': `Bearer ${this.apiKey}`,
26
+ "User-Agent": "WebcrawlerAPI-NodeJS-Client"
26
27
  },
27
28
  'body': JSON.stringify(scrapeRequest),
28
29
  };
@@ -41,7 +42,7 @@ export class WebcrawlerClient {
41
42
  }
42
43
  }
43
44
 
44
- public async scrapeWithMeta(scrapeRequest: ScrapeRequest): Promise<ScrapeResponse> {
45
+ public async scrapeWithMeta(scrapeRequest: ScrapeRequest, maxPollingRetries: number = MaxPullRetries): Promise<ScrapeResponse> {
45
46
  const url = `${this.basePath}/${this.apiVersion}/scrape`;
46
47
 
47
48
  const requestOptions = {
@@ -53,16 +54,16 @@ export class WebcrawlerClient {
53
54
  'body': JSON.stringify(scrapeRequest),
54
55
  };
55
56
 
56
- const scrapeIDResponse: ScrapeIDResponse = await this.sendRequest(url, requestOptions);
57
+ const jobIdResponse: JobId = await this.sendRequest(url, requestOptions);
57
58
 
58
- if (scrapeIDResponse.id === '') {
59
+ if (jobIdResponse.id === '') {
59
60
  throw new Error("Failed to fetch job status");
60
61
  }
61
62
 
62
63
  let delayIntervalMs = initialPullDelayMs;
63
- for (let i = 0; i < MaxPullRetries; i++) {
64
+ for (let i = 0; i < maxPollingRetries; i++) {
64
65
  await new Promise(resolve => setTimeout(resolve, delayIntervalMs));
65
- const scrapeResult = await this.getScrapeResult(scrapeIDResponse.id);
66
+ const scrapeResult = await this.getScrapeResult(jobIdResponse.id);
66
67
  if (scrapeRequest.debug) {
67
68
  console.log(`Scrape result: ${JSON.stringify(scrapeResult)}`);
68
69
  }
@@ -73,11 +74,11 @@ export class WebcrawlerClient {
73
74
  delayIntervalMs = scrapeResult.recommended_pull_delay_ms;
74
75
  }
75
76
  }
76
- throw new Error("Scraping took too long, please retry");
77
+ throw new Error("Scraping took too long, please retry or increase the number of polling retries");
77
78
  }
78
79
 
79
- public async scrape(scrapeRequest: ScrapeRequest): Promise<ScrapeResponse> {
80
- const scrapeResult = await this.scrapeWithMeta(scrapeRequest);
80
+ public async scrape(scrapeRequest: ScrapeRequest, maxPollingRetries: number = MaxPullRetries): Promise<any> {
81
+ const scrapeResult = await this.scrapeWithMeta(scrapeRequest, maxPollingRetries);
81
82
  return scrapeResult.structured_data;
82
83
  }
83
84
 
@@ -87,8 +88,83 @@ export class WebcrawlerClient {
87
88
  'method': 'GET',
88
89
  'headers': {
89
90
  'Authorization': `Bearer ${this.apiKey}`,
91
+ "User-Agent": "WebcrawlerAPI-NodeJS-Client"
92
+ },
93
+ };
94
+ const response = await fetch(url, requestOptions);
95
+ if (response.ok) {
96
+ return response.json();
97
+ }
98
+
99
+ try {
100
+ const data = await response.json();
101
+ throw new Error(
102
+ `failed to fetch job status ${response.status} ${response.statusText}: ${JSON.stringify(data)}`
103
+ );
104
+ } catch (e) {
105
+ throw new Error(
106
+ `failed to fetch job status ${response.status} ${response.statusText}`
107
+ );
108
+ }
109
+ }
110
+
111
+ public async crawl(crawlRequest: CrawlRequest): Promise<Job> {
112
+ const url = `${this.basePath}/${this.apiVersion}/crawl`;
113
+
114
+ const requestOptions = {
115
+ 'method': 'POST',
116
+ 'headers': {
117
+ 'Content-Type': 'application/json',
118
+ 'Authorization': `Bearer ${this.apiKey}`,
119
+ "User-Agent": "WebcrawlerAPI-NodeJS-Client"
120
+ },
121
+ 'body': JSON.stringify(crawlRequest),
122
+ };
123
+
124
+ const jobIdResponse: JobId = await this.sendRequest(url, requestOptions);
125
+
126
+ if (jobIdResponse.id === '') {
127
+ throw new Error("Failed to fetch job status");
128
+ }
129
+
130
+ let delayIntervalMs = initialPullDelayMs;
131
+ for (let i = 0; i < MaxPullRetries; i++) {
132
+ await new Promise(resolve => setTimeout(resolve, delayIntervalMs));
133
+ const job = await this.getJob(jobIdResponse.id);
134
+ if (job.status !== 'in_progress' && job.status !== 'new') {
135
+ return job;
136
+ }
137
+ if (job.recommended_pull_delay_ms > 0) {
138
+ delayIntervalMs = job.recommended_pull_delay_ms;
139
+ }
140
+ }
141
+ }
142
+
143
+ public async crawlAsync(crawlRequest: CrawlRequest): Promise<JobId> {
144
+ const url = `${this.basePath}/${this.apiVersion}/crawl`;
145
+
146
+ const requestOptions = {
147
+ 'method': 'POST',
148
+ 'headers': {
149
+ 'Content-Type': 'application/json',
150
+ 'Authorization': `Bearer ${this.apiKey}`
90
151
  },
152
+ 'body': JSON.stringify(crawlRequest),
91
153
  };
154
+
155
+ return await this.sendRequest(url, requestOptions);
156
+ }
157
+
158
+ public async getJob(jobID: string): Promise<Job> {
159
+ const url = `${this.basePath}/${this.apiVersion}/job/${jobID}`;
160
+ const requestOptions = {
161
+ 'method': 'GET',
162
+ 'headers': {
163
+ 'Content-Type': 'application/json',
164
+ 'Authorization': `Bearer ${this.apiKey}`,
165
+ "User-Agent": "WebcrawlerAPI-NodeJS-Client"
166
+ }
167
+ }
92
168
  const response = await fetch(url, requestOptions);
93
169
  if (response.ok) {
94
170
  return response.json();
@@ -106,6 +182,7 @@ export class WebcrawlerClient {
106
182
  }
107
183
  }
108
184
 
185
+
109
186
  private async sendRequest(url: string, requestOptions: any): Promise<any> {
110
187
  let response: Response;
111
188
  try {
package/dist/api.d.ts CHANGED
@@ -1,12 +1,15 @@
1
- import { ScrapeIDResponse, ScrapeRequest, ScrapeResponse } from "./model";
1
+ import { CrawlRequest, Job, JobId, ScrapeRequest, ScrapeResponse } from "./model";
2
2
  export declare class WebcrawlerClient {
3
3
  protected apiVersion: string;
4
4
  protected apiKey: string;
5
5
  protected basePath: string;
6
6
  constructor(apiKey: string, basePath?: string, apiVersion?: string);
7
- scrapeAsync(scrapeRequest: ScrapeRequest): Promise<ScrapeIDResponse>;
7
+ scrapeAsync(scrapeRequest: ScrapeRequest): Promise<JobId>;
8
8
  scrapeWithMeta(scrapeRequest: ScrapeRequest): Promise<ScrapeResponse>;
9
9
  scrape(scrapeRequest: ScrapeRequest): Promise<ScrapeResponse>;
10
10
  getScrapeResult(scrapeID: string): Promise<ScrapeResponse>;
11
+ crawl(crawlRequest: CrawlRequest): Promise<Job>;
12
+ crawlAsync(crawlRequest: CrawlRequest): Promise<JobId>;
13
+ getJob(jobID: string): Promise<Job>;
11
14
  private sendRequest;
12
15
  }
package/dist/api.js CHANGED
@@ -55,14 +55,14 @@ class WebcrawlerClient {
55
55
  },
56
56
  'body': JSON.stringify(scrapeRequest),
57
57
  };
58
- const scrapeIDResponse = yield this.sendRequest(url, requestOptions);
59
- if (scrapeIDResponse.id === '') {
58
+ const jobIdResponse = yield this.sendRequest(url, requestOptions);
59
+ if (jobIdResponse.id === '') {
60
60
  throw new Error("Failed to fetch job status");
61
61
  }
62
62
  let delayIntervalMs = initialPullDelayMs;
63
63
  for (let i = 0; i < MaxPullRetries; i++) {
64
64
  yield new Promise(resolve => setTimeout(resolve, delayIntervalMs));
65
- const scrapeResult = yield this.getScrapeResult(scrapeIDResponse.id);
65
+ const scrapeResult = yield this.getScrapeResult(jobIdResponse.id);
66
66
  if (scrapeRequest.debug) {
67
67
  console.log(`Scrape result: ${JSON.stringify(scrapeResult)}`);
68
68
  }
@@ -104,6 +104,71 @@ class WebcrawlerClient {
104
104
  }
105
105
  });
106
106
  }
107
+ crawl(crawlRequest) {
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ const url = `${this.basePath}/${this.apiVersion}/crawl`;
110
+ const requestOptions = {
111
+ 'method': 'POST',
112
+ 'headers': {
113
+ 'Content-Type': 'application/json',
114
+ 'Authorization': `Bearer ${this.apiKey}`
115
+ },
116
+ 'body': JSON.stringify(crawlRequest),
117
+ };
118
+ const jobIdResponse = yield this.sendRequest(url, requestOptions);
119
+ if (jobIdResponse.id === '') {
120
+ throw new Error("Failed to fetch job status");
121
+ }
122
+ let delayIntervalMs = initialPullDelayMs;
123
+ for (let i = 0; i < MaxPullRetries; i++) {
124
+ yield new Promise(resolve => setTimeout(resolve, delayIntervalMs));
125
+ const job = yield this.getJob(jobIdResponse.id);
126
+ if (job.status !== 'in_progress' && job.status !== 'new') {
127
+ return job;
128
+ }
129
+ if (job.recommended_pull_delay_ms > 0) {
130
+ delayIntervalMs = job.recommended_pull_delay_ms;
131
+ }
132
+ }
133
+ });
134
+ }
135
+ crawlAsync(crawlRequest) {
136
+ return __awaiter(this, void 0, void 0, function* () {
137
+ const url = `${this.basePath}/${this.apiVersion}/crawl`;
138
+ const requestOptions = {
139
+ 'method': 'POST',
140
+ 'headers': {
141
+ 'Content-Type': 'application/json',
142
+ 'Authorization': `Bearer ${this.apiKey}`
143
+ },
144
+ 'body': JSON.stringify(crawlRequest),
145
+ };
146
+ return yield this.sendRequest(url, requestOptions);
147
+ });
148
+ }
149
+ getJob(jobID) {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ const url = `${this.basePath}/${this.apiVersion}/job/${jobID}`;
152
+ const requestOptions = {
153
+ 'method': 'GET',
154
+ 'headers': {
155
+ 'Content-Type': 'application/json',
156
+ 'Authorization': `Bearer ${this.apiKey}`
157
+ }
158
+ };
159
+ const response = yield fetch(url, requestOptions);
160
+ if (response.ok) {
161
+ return response.json();
162
+ }
163
+ try {
164
+ const data = yield response.json();
165
+ throw new Error(`failed to fetch job status ${response.status} ${response.statusText}: ${JSON.stringify(data)}`);
166
+ }
167
+ catch (e) {
168
+ throw new Error(`failed to fetch job status ${response.status} ${response.statusText}`);
169
+ }
170
+ });
171
+ }
107
172
  sendRequest(url, requestOptions) {
108
173
  return __awaiter(this, void 0, void 0, function* () {
109
174
  let response;
package/dist/model.d.ts CHANGED
@@ -4,7 +4,7 @@ export interface ScrapeRequest {
4
4
  max_retries?: number;
5
5
  debug?: boolean;
6
6
  }
7
- export interface ScrapeIDResponse {
7
+ export interface JobId {
8
8
  id: string;
9
9
  }
10
10
  export interface ScrapeResponse {
@@ -16,3 +16,43 @@ export interface ScrapeResponse {
16
16
  structured_data: any;
17
17
  recommended_pull_delay_ms: number;
18
18
  }
19
+ export interface CrawlRequest {
20
+ url: string;
21
+ items_limit?: number;
22
+ scrape_type?: string;
23
+ whitelist_regexp?: string;
24
+ blacklist_regexp?: string;
25
+ allow_subdomains?: boolean;
26
+ webhook_url?: string;
27
+ }
28
+ export interface Job {
29
+ id: string;
30
+ org_id: string;
31
+ url: string;
32
+ status: string;
33
+ scrape_type: string;
34
+ whitelist_regexp: string;
35
+ blacklist_regexp: string;
36
+ allow_subdomains: boolean;
37
+ items_limit: number;
38
+ created_at: string;
39
+ finished_at: string;
40
+ updated_at: string;
41
+ webhook_url: string;
42
+ recommended_pull_delay_ms: number;
43
+ job_items: JobItem[];
44
+ }
45
+ export interface JobItem {
46
+ id: string;
47
+ job_id: string;
48
+ original_url: string;
49
+ page_status_code: number;
50
+ markdown_content_url: string;
51
+ status: string;
52
+ title: string;
53
+ last_error: string;
54
+ created_at: string;
55
+ updated_at: string;
56
+ cost: number;
57
+ referred_url: string;
58
+ }
package/model.ts CHANGED
@@ -5,7 +5,7 @@ export interface ScrapeRequest {
5
5
  debug?: boolean;
6
6
  }
7
7
 
8
- export interface ScrapeIDResponse {
8
+ export interface JobId {
9
9
  id: string;
10
10
  }
11
11
 
@@ -18,3 +18,46 @@ export interface ScrapeResponse {
18
18
  structured_data: any;
19
19
  recommended_pull_delay_ms: number;
20
20
  }
21
+
22
+ export interface CrawlRequest {
23
+ url: string;
24
+ items_limit?: number;
25
+ scrape_type?: string;
26
+ whitelist_regexp?: string;
27
+ blacklist_regexp?: string;
28
+ allow_subdomains?: boolean;
29
+ webhook_url?: string;
30
+ }
31
+
32
+ export interface Job {
33
+ id: string;
34
+ org_id: string;
35
+ url: string;
36
+ status: string;
37
+ scrape_type: string;
38
+ whitelist_regexp: string;
39
+ blacklist_regexp: string;
40
+ allow_subdomains: boolean;
41
+ items_limit: number;
42
+ created_at: string;
43
+ finished_at: string;
44
+ updated_at: string;
45
+ webhook_url: string;
46
+ recommended_pull_delay_ms: number;
47
+ job_items: JobItem[];
48
+ }
49
+
50
+ export interface JobItem {
51
+ id: string;
52
+ job_id: string;
53
+ original_url: string;
54
+ page_status_code: number;
55
+ markdown_content_url: string;
56
+ status: string;
57
+ title: string;
58
+ last_error: string;
59
+ created_at: string;
60
+ updated_at: string;
61
+ cost: number;
62
+ referred_url: string;
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcrawlerapi-js",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "JS client for WecrawlerAPI",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "@types/node": "^22.4.0",
18
18
  "typescript": "^5.5.4"
19
19
  },
20
- "author": "Andrew <support@webcrawlerapi.com>",
20
+ "author": "Andrew <sdk@webcrawlerapi.com>",
21
21
  "license": "MIT",
22
22
  "dependencies": {},
23
23
  "scripts": {