scrapebadger 0.24.2 → 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-jp8WyFsJ.d.cts → index-ZD3sVCjc.d.cts} +16 -0
- package/dist/{index-jp8WyFsJ.d.ts → index-ZD3sVCjc.d.ts} +16 -0
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +76 -0
- 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
package/dist/index.mjs
CHANGED
|
@@ -131,6 +131,39 @@ var BaseClient = class _BaseClient {
|
|
|
131
131
|
const { data } = await this.requestRaw(path, options);
|
|
132
132
|
return data;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* POST and return the undecoded response body.
|
|
136
|
+
*
|
|
137
|
+
* For endpoints that answer with something other than JSON — currently
|
|
138
|
+
* `/v1/web/scrape` with `raw_content: true`, which returns the scraped body
|
|
139
|
+
* itself. The normal path funnels a non-JSON response into
|
|
140
|
+
* `{ detail: await response.text() }`, which both loses the result and, for
|
|
141
|
+
* a binary payload, corrupts it: `text()` decodes bytes as UTF-8.
|
|
142
|
+
*
|
|
143
|
+
* Returns the raw bytes plus the response headers.
|
|
144
|
+
*/
|
|
145
|
+
async postBinary(path, options = {}) {
|
|
146
|
+
const url = new URL(path, this.config.baseUrl);
|
|
147
|
+
const { body, headers = {} } = options;
|
|
148
|
+
const response = await this.fetchWithTimeout(url.toString(), {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
"X-API-Key": this.config.apiKey,
|
|
153
|
+
"User-Agent": `scrapebadger-node/${SDK_VERSION}`,
|
|
154
|
+
...headers
|
|
155
|
+
},
|
|
156
|
+
body: body ? JSON.stringify(body) : void 0
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
await this.handleResponse(response);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
163
|
+
headers: response.headers,
|
|
164
|
+
status: response.status
|
|
165
|
+
};
|
|
166
|
+
}
|
|
134
167
|
/**
|
|
135
168
|
* Make an HTTP request and return both data and rate limit headers.
|
|
136
169
|
*/
|
|
@@ -2475,11 +2508,54 @@ var WebClient = class {
|
|
|
2475
2508
|
if (options.aiPrompt !== void 0) body.ai_prompt = options.aiPrompt;
|
|
2476
2509
|
if (options.rawContent !== void 0) body.raw_content = options.rawContent;
|
|
2477
2510
|
if (options.skipBotDetection !== void 0) body.skip_bot_detection = options.skipBotDetection;
|
|
2511
|
+
if (options.rawContent) {
|
|
2512
|
+
return this.scrapeRaw(body);
|
|
2513
|
+
}
|
|
2478
2514
|
return this.client.request("/v1/web/scrape", {
|
|
2479
2515
|
method: "POST",
|
|
2480
2516
|
body
|
|
2481
2517
|
});
|
|
2482
2518
|
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Run a `rawContent` scrape, whose response is not JSON.
|
|
2521
|
+
*
|
|
2522
|
+
* The normal path funnels a non-JSON response into `{ detail: text }`, so a
|
|
2523
|
+
* raw scrape returned a result with no content — and for a binary target,
|
|
2524
|
+
* `response.text()` decoded the bytes as UTF-8 and destroyed them. Read the
|
|
2525
|
+
* body as bytes and rebuild the metadata from the `X-Scrape-*` headers the
|
|
2526
|
+
* server sends in this mode.
|
|
2527
|
+
*/
|
|
2528
|
+
async scrapeRaw(body) {
|
|
2529
|
+
const { bytes, headers, status } = await this.client.postBinary("/v1/web/scrape", {
|
|
2530
|
+
body
|
|
2531
|
+
});
|
|
2532
|
+
const int = (name) => {
|
|
2533
|
+
const parsed = Number.parseInt(headers.get(name) ?? "", 10);
|
|
2534
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
2535
|
+
};
|
|
2536
|
+
const mediaType = ((headers.get("content-type") ?? "").split(";")[0] ?? "").trim().toLowerCase();
|
|
2537
|
+
const isText = mediaType.startsWith("text/") || ["application/json", "application/xml", "image/svg+xml"].includes(mediaType);
|
|
2538
|
+
return {
|
|
2539
|
+
success: headers.get("x-scrape-success") !== "0",
|
|
2540
|
+
url: headers.get("x-scrape-url") ?? (typeof body.url === "string" ? body.url : ""),
|
|
2541
|
+
status_code: int("x-scrape-status-code") || status,
|
|
2542
|
+
content: isText ? new TextDecoder().decode(bytes) : null,
|
|
2543
|
+
content_bytes: bytes,
|
|
2544
|
+
content_base64: null,
|
|
2545
|
+
is_binary: !isText,
|
|
2546
|
+
content_type: mediaType || null,
|
|
2547
|
+
format: headers.get("x-scrape-format") ?? "html",
|
|
2548
|
+
engine_used: headers.get("x-scrape-engine") ?? "",
|
|
2549
|
+
credits_used: int("x-credits-used"),
|
|
2550
|
+
duration_ms: int("x-scrape-duration-ms"),
|
|
2551
|
+
retries_used: int("x-scrape-retries"),
|
|
2552
|
+
content_length: int("x-scrape-content-length") || bytes.length,
|
|
2553
|
+
screenshot_url: null,
|
|
2554
|
+
video_url: null,
|
|
2555
|
+
headers: {},
|
|
2556
|
+
blocking_detected: false
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2483
2559
|
/**
|
|
2484
2560
|
* Extract structured data from a web page using AI.
|
|
2485
2561
|
*
|