scrapebadger 0.24.1 → 0.25.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/dist/{index-CIZUd1Zr.d.cts → index-ZD3sVCjc.d.cts} +24 -0
- package/dist/{index-CIZUd1Zr.d.ts → index-ZD3sVCjc.d.ts} +24 -0
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +96 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -3
- package/dist/index.mjs.map +1 -1
- package/dist/twitter/index.d.cts +1 -1
- package/dist/twitter/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -52,6 +52,22 @@ declare class BaseClient {
|
|
|
52
52
|
* Make an HTTP request to the API.
|
|
53
53
|
*/
|
|
54
54
|
request<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
55
|
+
/**
|
|
56
|
+
* POST and return the undecoded response body.
|
|
57
|
+
*
|
|
58
|
+
* For endpoints that answer with something other than JSON — currently
|
|
59
|
+
* `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
|
|
60
|
+
* itself. The normal path funnels a non-JSON response into
|
|
61
|
+
* `{ detail: await response.text() }`, which both loses the result and, for
|
|
62
|
+
* a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
|
|
63
|
+
*
|
|
64
|
+
* Returns the raw bytes plus the response headers.
|
|
65
|
+
*/
|
|
66
|
+
postBinary(path: string, options?: RequestOptions): Promise<{
|
|
67
|
+
bytes: Uint8Array;
|
|
68
|
+
headers: Headers;
|
|
69
|
+
status: number;
|
|
70
|
+
}>;
|
|
55
71
|
/**
|
|
56
72
|
* Make an HTTP request and return both data and rate limit headers.
|
|
57
73
|
*/
|
|
@@ -76,6 +92,14 @@ declare class BaseClient {
|
|
|
76
92
|
* Handle HTTP response and convert errors.
|
|
77
93
|
*/
|
|
78
94
|
private handleResponse;
|
|
95
|
+
/**
|
|
96
|
+
* Whether a failed request is worth another attempt.
|
|
97
|
+
*
|
|
98
|
+
* Retryable: transient server failures (500/502/503/504), request timeouts,
|
|
99
|
+
* rate limits, and raw network faults thrown by `fetch` itself. Everything
|
|
100
|
+
* else — auth, validation, not-found, conflict — is final.
|
|
101
|
+
*/
|
|
102
|
+
private static isRetryable;
|
|
79
103
|
/**
|
|
80
104
|
* Sleep for a given duration.
|
|
81
105
|
*/
|
|
@@ -52,6 +52,22 @@ declare class BaseClient {
|
|
|
52
52
|
* Make an HTTP request to the API.
|
|
53
53
|
*/
|
|
54
54
|
request<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
55
|
+
/**
|
|
56
|
+
* POST and return the undecoded response body.
|
|
57
|
+
*
|
|
58
|
+
* For endpoints that answer with something other than JSON — currently
|
|
59
|
+
* `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
|
|
60
|
+
* itself. The normal path funnels a non-JSON response into
|
|
61
|
+
* `{ detail: await response.text() }`, which both loses the result and, for
|
|
62
|
+
* a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
|
|
63
|
+
*
|
|
64
|
+
* Returns the raw bytes plus the response headers.
|
|
65
|
+
*/
|
|
66
|
+
postBinary(path: string, options?: RequestOptions): Promise<{
|
|
67
|
+
bytes: Uint8Array;
|
|
68
|
+
headers: Headers;
|
|
69
|
+
status: number;
|
|
70
|
+
}>;
|
|
55
71
|
/**
|
|
56
72
|
* Make an HTTP request and return both data and rate limit headers.
|
|
57
73
|
*/
|
|
@@ -76,6 +92,14 @@ declare class BaseClient {
|
|
|
76
92
|
* Handle HTTP response and convert errors.
|
|
77
93
|
*/
|
|
78
94
|
private handleResponse;
|
|
95
|
+
/**
|
|
96
|
+
* Whether a failed request is worth another attempt.
|
|
97
|
+
*
|
|
98
|
+
* Retryable: transient server failures (500/502/503/504), request timeouts,
|
|
99
|
+
* rate limits, and raw network faults thrown by `fetch` itself. Everything
|
|
100
|
+
* else — auth, validation, not-found, conflict — is final.
|
|
101
|
+
*/
|
|
102
|
+
private static isRetryable;
|
|
79
103
|
/**
|
|
80
104
|
* Sleep for a given duration.
|
|
81
105
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-
|
|
2
|
-
export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-
|
|
1
|
+
import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-ZD3sVCjc.cjs';
|
|
2
|
+
export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-ZD3sVCjc.cjs';
|
|
3
3
|
import 'node:events';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -65,6 +65,22 @@ interface ScrapeResult {
|
|
|
65
65
|
url: string;
|
|
66
66
|
status_code: number;
|
|
67
67
|
content: string | null;
|
|
68
|
+
/**
|
|
69
|
+
* Base64 body for a binary target (image/PDF/archive). Set instead of
|
|
70
|
+
* `content`, which is null there — binary bytes have no text form.
|
|
71
|
+
* Null when the body exceeded the 25 MB base64 ceiling.
|
|
72
|
+
*/
|
|
73
|
+
content_base64: string | null;
|
|
74
|
+
/** Whether the target returned a binary (non-text) body. */
|
|
75
|
+
is_binary: boolean;
|
|
76
|
+
/** The target's response Content-Type, as a bare media type. */
|
|
77
|
+
content_type: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Undecoded response body. Only set when `rawContent: true` — that mode
|
|
80
|
+
* returns the body itself rather than a JSON envelope. Write it straight to
|
|
81
|
+
* a file; do not decode it, the payload may be an image or a PDF.
|
|
82
|
+
*/
|
|
83
|
+
content_bytes?: Uint8Array;
|
|
68
84
|
format: string;
|
|
69
85
|
engine_used: string;
|
|
70
86
|
credits_used: number;
|
|
@@ -144,6 +160,16 @@ declare class WebClient {
|
|
|
144
160
|
* @returns The scrape result including content, metadata, and credit usage
|
|
145
161
|
*/
|
|
146
162
|
scrape(url: string, options?: ScrapeOptions): Promise<ScrapeResult>;
|
|
163
|
+
/**
|
|
164
|
+
* Run a `rawContent` scrape, whose response is not JSON.
|
|
165
|
+
*
|
|
166
|
+
* The normal path funnels a non-JSON response into `{ detail: text }`, so a
|
|
167
|
+
* raw scrape returned a result with no content — and for a binary target,
|
|
168
|
+
* `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
|
|
169
|
+
* body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
|
|
170
|
+
* server sends in this mode.
|
|
171
|
+
*/
|
|
172
|
+
private scrapeRaw;
|
|
147
173
|
/**
|
|
148
174
|
* Extract structured data from a web page using AI.
|
|
149
175
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-
|
|
2
|
-
export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-
|
|
1
|
+
import { B as BaseClient, T as TwitterClient, S as ScrapeBadgerConfig } from './index-ZD3sVCjc.js';
|
|
2
|
+
export { e as AccountRestrictedError, a3 as ApiResponse, $ as Article, A as AuthenticationError, aj as BillingLog, ak as BillingLogList, a2 as Broadcast, j as CommunitiesClient, K as Community, F as CommunityBanner, O as CommunityMember, a0 as CommunityNote, J as CommunityRule, q as CommunityTweetType, C as ConflictError, al as ConnectOptions, ab as ConnectedEvent, a8 as CreateMonitorParams, ah as DeliveryLog, ai as DeliveryLogList, ae as ErrorEvent, ao as FilterRuleCreate, at as FilterRuleDeliveryLog, au as FilterRuleDeliveryLogListResponse, ar as FilterRuleListResponse, aq as FilterRulePricingTier, av as FilterRulePricingTiersResponse, an as FilterRuleResponse, am as FilterRuleStatus, ap as FilterRuleUpdate, as as FilterRuleValidateResponse, G as GeoClient, l as GeoSearchOptions, H as Hashtag, I as InsufficientCreditsError, g as IteratorOptions, E as List, a4 as ListResponse, L as ListsClient, Y as Location, M as Media, a5 as MonitorStatus, N as NotFoundError, P as PaginatedResponse, f as PaginationOptions, ac as PingEvent, _ as Place, Z as PlaceTrends, s as Poll, r as PollOption, Q as QueryType, b as RateLimitError, R as ResolvedConfig, a as ScrapeBadgerError, c as ServerError, a1 as Space, o as SpacesClient, m as StreamClient, n as StreamEmitter, af as StreamEvent, ag as StreamEventType, a6 as StreamMonitor, a7 as StreamMonitorList, aa as StreamTweet, d as TimeoutError, X as Trend, p as TrendCategory, k as TrendsClient, x as Tweet, ad as TweetEvent, w as TweetPlace, i as TweetsClient, a9 as UpdateMonitorParams, t as Url, y as User, z as UserAbout, D as UserIds, u as UserMention, U as UsersClient, V as ValidationError, W as WebSocketStreamError, h as collectAll, v as verifyWebhookSignature } from './index-ZD3sVCjc.js';
|
|
3
3
|
import 'node:events';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -65,6 +65,22 @@ interface ScrapeResult {
|
|
|
65
65
|
url: string;
|
|
66
66
|
status_code: number;
|
|
67
67
|
content: string | null;
|
|
68
|
+
/**
|
|
69
|
+
* Base64 body for a binary target (image/PDF/archive). Set instead of
|
|
70
|
+
* `content`, which is null there — binary bytes have no text form.
|
|
71
|
+
* Null when the body exceeded the 25 MB base64 ceiling.
|
|
72
|
+
*/
|
|
73
|
+
content_base64: string | null;
|
|
74
|
+
/** Whether the target returned a binary (non-text) body. */
|
|
75
|
+
is_binary: boolean;
|
|
76
|
+
/** The target's response Content-Type, as a bare media type. */
|
|
77
|
+
content_type: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Undecoded response body. Only set when `rawContent: true` — that mode
|
|
80
|
+
* returns the body itself rather than a JSON envelope. Write it straight to
|
|
81
|
+
* a file; do not decode it, the payload may be an image or a PDF.
|
|
82
|
+
*/
|
|
83
|
+
content_bytes?: Uint8Array;
|
|
68
84
|
format: string;
|
|
69
85
|
engine_used: string;
|
|
70
86
|
credits_used: number;
|
|
@@ -144,6 +160,16 @@ declare class WebClient {
|
|
|
144
160
|
* @returns The scrape result including content, metadata, and credit usage
|
|
145
161
|
*/
|
|
146
162
|
scrape(url: string, options?: ScrapeOptions): Promise<ScrapeResult>;
|
|
163
|
+
/**
|
|
164
|
+
* Run a `rawContent` scrape, whose response is not JSON.
|
|
165
|
+
*
|
|
166
|
+
* The normal path funnels a non-JSON response into `{ detail: text }`, so a
|
|
167
|
+
* raw scrape returned a result with no content — and for a binary target,
|
|
168
|
+
* `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
|
|
169
|
+
* body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
|
|
170
|
+
* server sends in this mode.
|
|
171
|
+
*/
|
|
172
|
+
private scrapeRaw;
|
|
147
173
|
/**
|
|
148
174
|
* Extract structured data from a web page using AI.
|
|
149
175
|
*
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
9
9
|
var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
|
|
10
10
|
|
|
11
11
|
// src/internal/version.ts
|
|
12
|
-
var SDK_VERSION = "0.24.
|
|
12
|
+
var SDK_VERSION = "0.24.2";
|
|
13
13
|
|
|
14
14
|
// src/internal/exceptions.ts
|
|
15
15
|
var ScrapeBadgerError = class _ScrapeBadgerError extends Error {
|
|
@@ -124,7 +124,8 @@ var WebSocketStreamError = class _WebSocketStreamError extends ScrapeBadgerError
|
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
// src/internal/client.ts
|
|
127
|
-
var
|
|
127
|
+
var RETRYABLE_STATUS_CODES = [500, 502, 503, 504];
|
|
128
|
+
var BaseClient = class _BaseClient {
|
|
128
129
|
config;
|
|
129
130
|
constructor(config) {
|
|
130
131
|
this.config = config;
|
|
@@ -136,6 +137,39 @@ var BaseClient = class {
|
|
|
136
137
|
const { data } = await this.requestRaw(path, options);
|
|
137
138
|
return data;
|
|
138
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* POST and return the undecoded response body.
|
|
142
|
+
*
|
|
143
|
+
* For endpoints that answer with something other than JSON — currently
|
|
144
|
+
* `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
|
|
145
|
+
* itself. The normal path funnels a non-JSON response into
|
|
146
|
+
* `{ detail: await response.text() }`, which both loses the result and, for
|
|
147
|
+
* a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
|
|
148
|
+
*
|
|
149
|
+
* Returns the raw bytes plus the response headers.
|
|
150
|
+
*/
|
|
151
|
+
async postBinary(path, options = {}) {
|
|
152
|
+
const url = new URL(path, this.config.baseUrl);
|
|
153
|
+
const { body, headers = {} } = options;
|
|
154
|
+
const response = await this.fetchWithTimeout(url.toString(), {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
"X-API-Key": this.config.apiKey,
|
|
159
|
+
"User-Agent": `scrapebadger-node/${SDK_VERSION}`,
|
|
160
|
+
...headers
|
|
161
|
+
},
|
|
162
|
+
body: body ? JSON.stringify(body) : void 0
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
await this.handleResponse(response);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
169
|
+
headers: response.headers,
|
|
170
|
+
status: response.status
|
|
171
|
+
};
|
|
172
|
+
}
|
|
139
173
|
/**
|
|
140
174
|
* Make an HTTP request and return both data and rate limit headers.
|
|
141
175
|
*/
|
|
@@ -184,7 +218,7 @@ var BaseClient = class {
|
|
|
184
218
|
return { data, rateLimit };
|
|
185
219
|
} catch (error) {
|
|
186
220
|
lastError = error;
|
|
187
|
-
if (
|
|
221
|
+
if (!_BaseClient.isRetryable(error)) {
|
|
188
222
|
throw error;
|
|
189
223
|
}
|
|
190
224
|
if (attempt === this.config.maxRetries) {
|
|
@@ -311,6 +345,22 @@ var BaseClient = class {
|
|
|
311
345
|
throw new ScrapeBadgerError(message);
|
|
312
346
|
}
|
|
313
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Whether a failed request is worth another attempt.
|
|
350
|
+
*
|
|
351
|
+
* Retryable: transient server failures (500/502/503/504), request timeouts,
|
|
352
|
+
* rate limits, and raw network faults thrown by `fetch` itself. Everything
|
|
353
|
+
* else — auth, validation, not-found, conflict — is final.
|
|
354
|
+
*/
|
|
355
|
+
static isRetryable(error) {
|
|
356
|
+
if (error instanceof ServerError) {
|
|
357
|
+
return RETRYABLE_STATUS_CODES.includes(error.statusCode);
|
|
358
|
+
}
|
|
359
|
+
if (error instanceof RateLimitError || error instanceof TimeoutError) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
return !(error instanceof ScrapeBadgerError);
|
|
363
|
+
}
|
|
314
364
|
/**
|
|
315
365
|
* Sleep for a given duration.
|
|
316
366
|
*/
|
|
@@ -2464,11 +2514,54 @@ var WebClient = class {
|
|
|
2464
2514
|
if (options.aiPrompt !== void 0) body.ai_prompt = options.aiPrompt;
|
|
2465
2515
|
if (options.rawContent !== void 0) body.raw_content = options.rawContent;
|
|
2466
2516
|
if (options.skipBotDetection !== void 0) body.skip_bot_detection = options.skipBotDetection;
|
|
2517
|
+
if (options.rawContent) {
|
|
2518
|
+
return this.scrapeRaw(body);
|
|
2519
|
+
}
|
|
2467
2520
|
return this.client.request("/v1/web/scrape", {
|
|
2468
2521
|
method: "POST",
|
|
2469
2522
|
body
|
|
2470
2523
|
});
|
|
2471
2524
|
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Run a `rawContent` scrape, whose response is not JSON.
|
|
2527
|
+
*
|
|
2528
|
+
* The normal path funnels a non-JSON response into `{ detail: text }`, so a
|
|
2529
|
+
* raw scrape returned a result with no content — and for a binary target,
|
|
2530
|
+
* `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
|
|
2531
|
+
* body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
|
|
2532
|
+
* server sends in this mode.
|
|
2533
|
+
*/
|
|
2534
|
+
async scrapeRaw(body) {
|
|
2535
|
+
const { bytes, headers, status } = await this.client.postBinary("/v1/web/scrape", {
|
|
2536
|
+
body
|
|
2537
|
+
});
|
|
2538
|
+
const int = (name) => {
|
|
2539
|
+
const parsed = Number.parseInt(headers.get(name) ?? "", 10);
|
|
2540
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
2541
|
+
};
|
|
2542
|
+
const mediaType = ((headers.get("content-type") ?? "").split(";")[0] ?? "").trim().toLowerCase();
|
|
2543
|
+
const isText = mediaType.startsWith("text/") || ["application/json", "application/xml", "image/svg+xml"].includes(mediaType);
|
|
2544
|
+
return {
|
|
2545
|
+
success: headers.get("x-scrape-success") !== "0",
|
|
2546
|
+
url: headers.get("x-scrape-url") ?? (typeof body.url === "string" ? body.url : ""),
|
|
2547
|
+
status_code: int("x-scrape-status-code") || status,
|
|
2548
|
+
content: isText ? new TextDecoder().decode(bytes) : null,
|
|
2549
|
+
content_bytes: bytes,
|
|
2550
|
+
content_base64: null,
|
|
2551
|
+
is_binary: !isText,
|
|
2552
|
+
content_type: mediaType || null,
|
|
2553
|
+
format: headers.get("x-scrape-format") ?? "html",
|
|
2554
|
+
engine_used: headers.get("x-scrape-engine") ?? "",
|
|
2555
|
+
credits_used: int("x-credits-used"),
|
|
2556
|
+
duration_ms: int("x-scrape-duration-ms"),
|
|
2557
|
+
retries_used: int("x-scrape-retries"),
|
|
2558
|
+
content_length: int("x-scrape-content-length") || bytes.length,
|
|
2559
|
+
screenshot_url: null,
|
|
2560
|
+
video_url: null,
|
|
2561
|
+
headers: {},
|
|
2562
|
+
blocking_detected: false
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2472
2565
|
/**
|
|
2473
2566
|
* Extract structured data from a web page using AI.
|
|
2474
2567
|
*
|