@scrapa/sdk 0.2.1 → 0.3.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 +17 -17
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/generated/contract.d.ts +21 -8
- package/dist/generated/contract.d.ts.map +1 -1
- package/dist/generated/contract.js +14 -6
- package/dist/generated/contract.js.map +1 -1
- package/dist/generated/operations.d.ts +45 -43
- package/dist/generated/operations.d.ts.map +1 -1
- package/dist/generated/operations.js +45 -43
- package/dist/generated/operations.js.map +1 -1
- package/dist/generated/params.d.ts +142 -100
- package/dist/generated/params.d.ts.map +1 -1
- package/dist/generated/retry.d.ts +2 -2
- package/dist/generated/retry.js +3 -3
- package/dist/generated/transport.js +2 -2
- package/package.json +2 -2
- package/src/client.ts +2 -2
- package/src/errors.ts +1 -1
- package/src/generated/contract.ts +25 -6
- package/src/generated/operations.ts +45 -43
- package/src/generated/params.ts +142 -100
- package/src/generated/retry.ts +3 -3
- package/src/generated/transport.ts +2 -2
package/src/client.ts
CHANGED
|
@@ -116,11 +116,11 @@ export class Scrapa extends GeneratedOperations {
|
|
|
116
116
|
const opts = typeof options === "string" ? { apiKey: options } : options;
|
|
117
117
|
if (!opts.apiKey)
|
|
118
118
|
throw new TypeError(
|
|
119
|
-
"scrapa: an API key is required
|
|
119
|
+
"scrapa: an API key is required. Create one at https://console.scrapa.app/keys",
|
|
120
120
|
);
|
|
121
121
|
if (typeof (opts.fetch ?? globalThis.fetch) !== "function")
|
|
122
122
|
throw new TypeError(
|
|
123
|
-
"scrapa: no global fetch
|
|
123
|
+
"scrapa: no global fetch. Use Node 20+, or pass `fetch` in the options.",
|
|
124
124
|
);
|
|
125
125
|
const baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
|
|
126
126
|
const host = new URL(baseUrl).host;
|
package/src/errors.ts
CHANGED
|
@@ -125,7 +125,7 @@ export function scrapaError(failure: Failure, host: string): ScrapaError {
|
|
|
125
125
|
status: 0,
|
|
126
126
|
mayHaveRun: method !== "GET",
|
|
127
127
|
message: failure.timedOut
|
|
128
|
-
? `scrapa: no answer within ${failure.timeoutMs}ms
|
|
128
|
+
? `scrapa: no answer within ${failure.timeoutMs}ms. The page may still have been read and charged.`
|
|
129
129
|
: `scrapa: could not reach ${host}.`,
|
|
130
130
|
cause: failure.cause,
|
|
131
131
|
});
|
|
@@ -44,6 +44,10 @@ export const ERROR_CODES = [
|
|
|
44
44
|
// or did not answer, or the managed pool has no exit in the place you asked for. Free,
|
|
45
45
|
// and no colour — the fix is in the request (the proxy, the credentials, a wider place).
|
|
46
46
|
"PROXY_REFUSED", // 422 — details {reason: "no_exit" | "refused" | "unreachable", place?, status?}
|
|
47
|
+
// The site sent a file bigger than `raw` hands back whole. Not 413, which tells an HTTP
|
|
48
|
+
// client its OWN request was too big. Free, and no colour, for PROXY_REFUSED's reason:
|
|
49
|
+
// the fix is in the request (another format, or download it yourself), not another road.
|
|
50
|
+
"FILE_TOO_LARGE", // 422 — details {limit}, in bytes
|
|
47
51
|
"RATE_LIMIT_EXCEEDED", // 429 — the per-minute burst bucket; Retry-After + details {limit}
|
|
48
52
|
"QUOTA_EXCEEDED", // 429 — a durable bound that WAITING clears; details {scope, limit, resetAt}
|
|
49
53
|
"INTERNAL_ERROR", // 500 — we broke it; the body is always generic, the cause stays server-side
|
|
@@ -71,6 +75,7 @@ export const ERROR_STATUS = {
|
|
|
71
75
|
UPSTREAM_BLOCKED: 422,
|
|
72
76
|
NAV_TIMEOUT: 422,
|
|
73
77
|
PROXY_REFUSED: 422,
|
|
78
|
+
FILE_TOO_LARGE: 422,
|
|
74
79
|
RATE_LIMIT_EXCEEDED: 429,
|
|
75
80
|
QUOTA_EXCEEDED: 429,
|
|
76
81
|
INTERNAL_ERROR: 500,
|
|
@@ -79,10 +84,11 @@ export const ERROR_STATUS = {
|
|
|
79
84
|
} as const satisfies Record<ErrorCode, number>;
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
|
-
* The three no-charge outcomes, as DESIGN.md names them.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
87
|
+
* The three no-charge outcomes, as DESIGN.md names them. Each code here maps to exactly one
|
|
88
|
+
* — this is the map the console reads to pick the hue and the word, and the SDK reads to
|
|
89
|
+
* tell a caller what to do next. A code absent from here gets no colour: the caller's own
|
|
90
|
+
* mistake (a bad body, a missing key), or a free refusal whose fix is in the request rather
|
|
91
|
+
* than the site (PROXY_REFUSED, FILE_TOO_LARGE).
|
|
86
92
|
*
|
|
87
93
|
* blocked → the site said no → try another engine, pool or country
|
|
88
94
|
* denied → our policy said no → change the URL; retrying cannot help
|
|
@@ -180,7 +186,8 @@ export type ResolvedEngine = Exclude<Engine, "auto">;
|
|
|
180
186
|
/** What a scrape can hand back. `markdown` is the default because it is what an LLM, a
|
|
181
187
|
* notebook and a human all read; `html` is the page as fetched; `text` is prose with the
|
|
182
188
|
* markup gone; `links` every absolute anchor in document order; `extract` is the page read
|
|
183
|
-
* into the fields the caller asked for, which is the one format a model writes
|
|
189
|
+
* into the fields the caller asked for, which is the one format a model writes; `raw` is the
|
|
190
|
+
* response's own bytes, the file a browser would save. */
|
|
184
191
|
export const SCRAPE_FORMATS = [
|
|
185
192
|
"markdown",
|
|
186
193
|
"html",
|
|
@@ -189,6 +196,7 @@ export const SCRAPE_FORMATS = [
|
|
|
189
196
|
"screenshot",
|
|
190
197
|
"pdf",
|
|
191
198
|
"extract",
|
|
199
|
+
"raw",
|
|
192
200
|
] as const;
|
|
193
201
|
|
|
194
202
|
export type ScrapeFormat = (typeof SCRAPE_FORMATS)[number];
|
|
@@ -210,7 +218,7 @@ export type SearchTopic = (typeof SEARCH_TOPICS)[number];
|
|
|
210
218
|
|
|
211
219
|
/** Which front answered a search. Not a caller's choice — the ladder picks, and says which
|
|
212
220
|
* one it was, because a result set's flavour follows its index. */
|
|
213
|
-
export const SEARCH_ENGINES = ["bing", "brave"] as const;
|
|
221
|
+
export const SEARCH_ENGINES = ["bing", "brave", "duckduckgo"] as const;
|
|
214
222
|
|
|
215
223
|
export type SearchEngine = (typeof SEARCH_ENGINES)[number];
|
|
216
224
|
|
|
@@ -277,6 +285,14 @@ export interface ScrapeMetadataDto {
|
|
|
277
285
|
needsJs: boolean;
|
|
278
286
|
}
|
|
279
287
|
|
|
288
|
+
/** A file, exactly as the site sent it. */
|
|
289
|
+
export interface ScrapeRawDto {
|
|
290
|
+
/** The bytes, base64-encoded. */
|
|
291
|
+
base64: string;
|
|
292
|
+
/** The response's `content-type`, media type only: `application/pdf`. */
|
|
293
|
+
contentType: string | null;
|
|
294
|
+
}
|
|
295
|
+
|
|
280
296
|
/** `POST /v1/scrape`'s answer. Only the requested formats are present; everything else is
|
|
281
297
|
* always there. Artifacts (`screenshot`, `pdf`) are base64 in v1. */
|
|
282
298
|
export interface ScrapeResultDto {
|
|
@@ -307,6 +323,9 @@ export interface ScrapeResultDto {
|
|
|
307
323
|
links?: string[];
|
|
308
324
|
screenshot?: string;
|
|
309
325
|
pdf?: string;
|
|
326
|
+
/** The response's own bytes, as a browser would save them (any compression undone), when
|
|
327
|
+
* `raw` was in `formats`. Never cut short: a file over the limit is refused instead. */
|
|
328
|
+
raw?: ScrapeRawDto;
|
|
310
329
|
/** The fields asked for in `extract`, read off the page by a model. Null when the model
|
|
311
330
|
* could not answer — that credit is handed back and the page still stands. Present only
|
|
312
331
|
* when `extract` was in `formats`. */
|
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
} from "./params.ts";
|
|
35
35
|
|
|
36
36
|
/** The version this client reports in `x-scrapa-client`. */
|
|
37
|
-
export const SDK_VERSION = "0.
|
|
37
|
+
export const SDK_VERSION = "0.3.0";
|
|
38
38
|
|
|
39
39
|
/** The hosted API. */
|
|
40
40
|
export const DEFAULT_BASE_URL = "https://api.scrapa.app/v1";
|
|
@@ -50,12 +50,13 @@ export abstract class GeneratedOperations {
|
|
|
50
50
|
* Scrape a page
|
|
51
51
|
*
|
|
52
52
|
* Read one web page and hand it back as markdown, HTML, plain text, its links, a screenshot, a
|
|
53
|
-
* PDF
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
53
|
+
* PDF, or the fields you ask for, read off the page by a model. `engine: "auto"` (the default)
|
|
54
|
+
* makes a plain HTTP request first and re-reads the page in a real Chromium when the body
|
|
55
|
+
* turns out to be a JavaScript shell, so you pay for a browser only when the page needed one.
|
|
56
|
+
* To send a search form or a JSON query, use `method: "POST"` with a `body`; to download a
|
|
57
|
+
* file exactly as the site sent it, ask for `formats: ["raw"]`. Every request leaves through a
|
|
58
|
+
* residential proxy, or through your own with `proxy: { pool: "custom", server }`. The answer
|
|
59
|
+
* says which engine replied, where the page finally was and what it cost.
|
|
59
60
|
*
|
|
60
61
|
* Credits: 1 credit with the fetch engine, 5 with the browser (a screenshot or a PDF is
|
|
61
62
|
* included, never a surcharge), 5 more when you ask for `extract`, plus managed-proxy
|
|
@@ -84,11 +85,11 @@ export abstract class GeneratedOperations {
|
|
|
84
85
|
* Price a scrape
|
|
85
86
|
*
|
|
86
87
|
* What the same request would cost, without making it. Free, and refused by the same guards a
|
|
87
|
-
* real call meets
|
|
88
|
+
* real call meets, so a quote is never a price for a call that would be denied. `maxCost` is
|
|
88
89
|
* the ceiling: for `auto` it is the browser's price, which is only charged if the page turns
|
|
89
90
|
* out to need one.
|
|
90
91
|
*
|
|
91
|
-
* Credits: 0 credits
|
|
92
|
+
* Credits: 0 credits. Pricing a call is free.
|
|
92
93
|
*
|
|
93
94
|
* `POST /v1/scrape/quote`
|
|
94
95
|
*/
|
|
@@ -111,12 +112,13 @@ export abstract class GeneratedOperations {
|
|
|
111
112
|
/**
|
|
112
113
|
* Search the web
|
|
113
114
|
*
|
|
114
|
-
* Ask the web a question and get back ranked results
|
|
115
|
-
* holds
|
|
116
|
-
* cache. `country` picks both the exit and the market, so `br` returns what
|
|
117
|
-
* would see. Narrow to your own list of hosts with `includeDomains`, ask
|
|
118
|
-
* when you want dated journalism, and add `answer: true` when you want a
|
|
119
|
-
* over the results instead of writing it yourself. To read a result, pass
|
|
115
|
+
* Ask the web a question and get back ranked results: title, URL and the snippet the index
|
|
116
|
+
* holds. They are read live off a real search front through a residential exit, never a
|
|
117
|
+
* reseller's cache. `country` picks both the exit and the market, so `br` returns what
|
|
118
|
+
* somebody in Brazil would see. Narrow to your own list of hosts with `includeDomains`, ask
|
|
119
|
+
* for `topic: "news"` when you want dated journalism, and add `answer: true` when you want a
|
|
120
|
+
* paragraph written over the results instead of writing it yourself. To read a result, pass
|
|
121
|
+
* its URL to `scrape`.
|
|
120
122
|
*
|
|
121
123
|
* Credits: 4 credits per 10 results, so a `limit` above 10 costs two rows, plus 1 for `answer:
|
|
122
124
|
* true`. A search every front refused, and anything we broke, cost nothing; a search that ran
|
|
@@ -146,8 +148,8 @@ export abstract class GeneratedOperations {
|
|
|
146
148
|
* Start a batch
|
|
147
149
|
*
|
|
148
150
|
* Hand us a list of URLs and stop holding a connection open. Every one is read with the
|
|
149
|
-
* options you pass here
|
|
150
|
-
* `scrape
|
|
151
|
+
* options you pass here. It uses the same engines, the same exit and the same meter as a
|
|
152
|
+
* single `scrape`, and a page the site blocked costs nothing here too. Nothing is charged
|
|
151
153
|
* when you submit: a batch spends as it goes, so use `quote` for the ceiling. Poll `get`
|
|
152
154
|
* for progress, or pass a `webhook` and we will call you instead. Results are readable for
|
|
153
155
|
* 24 hours.
|
|
@@ -179,9 +181,9 @@ export abstract class GeneratedOperations {
|
|
|
179
181
|
* What that submission would cost at most, without submitting it. `maxCost` prices every
|
|
180
182
|
* URL at the ceiling one page could reach, so the real bill comes in lower by whatever the
|
|
181
183
|
* web refuses for free and by every page that never needed a browser. Bandwidth is on top
|
|
182
|
-
* and cannot be quoted
|
|
184
|
+
* and cannot be quoted, because it depends on what the pages weigh.
|
|
183
185
|
*
|
|
184
|
-
* Credits: 0 credits
|
|
186
|
+
* Credits: 0 credits. Pricing a batch is free.
|
|
185
187
|
*
|
|
186
188
|
* `POST /v1/batches/quote`
|
|
187
189
|
*/
|
|
@@ -202,10 +204,10 @@ export abstract class GeneratedOperations {
|
|
|
202
204
|
/**
|
|
203
205
|
* List your batches
|
|
204
206
|
*
|
|
205
|
-
* Your most recent batches, newest first
|
|
206
|
-
* what it has cost. Results are not included; ask `get` for one batch's pages.
|
|
207
|
+
* Your most recent batches, newest first, with what each one is doing, how far it has got
|
|
208
|
+
* and what it has cost. Results are not included; ask `get` for one batch's pages.
|
|
207
209
|
*
|
|
208
|
-
* Credits: 0 credits
|
|
210
|
+
* Credits: 0 credits. Listing batches is free.
|
|
209
211
|
*
|
|
210
212
|
* `GET /v1/batches`
|
|
211
213
|
*/
|
|
@@ -218,13 +220,13 @@ export abstract class GeneratedOperations {
|
|
|
218
220
|
*
|
|
219
221
|
* Where a batch got to: whether it is still running, how many of its URLs now have a
|
|
220
222
|
* verdict, how many of those the web or our policy refused, how many were never attempted,
|
|
221
|
-
* and what it has spent so far. Ask for `items` to get the results themselves
|
|
223
|
+
* and what it has spent so far. Ask for `items` to get the results themselves: the pages
|
|
222
224
|
* and the refusals, in the order you submitted them, each one the same answer a single
|
|
223
|
-
* `scrape` would have given
|
|
224
|
-
* whose results have expired still answers with everything else, because somebody
|
|
225
|
-
* for those pages must never be told the batch does not exist.
|
|
225
|
+
* `scrape` would have given. You get up to 200 at a time, from wherever `from` says. A
|
|
226
|
+
* batch whose results have expired still answers with everything else, because somebody
|
|
227
|
+
* who paid for those pages must never be told the batch does not exist.
|
|
226
228
|
*
|
|
227
|
-
* Credits: 0 credits
|
|
229
|
+
* Credits: 0 credits. Reading a batch is free.
|
|
228
230
|
*
|
|
229
231
|
* `GET /v1/batches/{id}`
|
|
230
232
|
*/
|
|
@@ -235,12 +237,12 @@ export abstract class GeneratedOperations {
|
|
|
235
237
|
/**
|
|
236
238
|
* Cancel a batch
|
|
237
239
|
*
|
|
238
|
-
* Stop a batch. A URL already being read finishes and is charged
|
|
239
|
-
* already paying for
|
|
240
|
-
*
|
|
241
|
-
*
|
|
240
|
+
* Stop a batch. A URL already being read finishes and is charged, because it is a page we
|
|
241
|
+
* are already paying for. Every URL not yet started becomes `skipped`, which costs nothing
|
|
242
|
+
* and is not a verdict about the URL: submit those positions again whenever you like. Safe
|
|
243
|
+
* to repeat; a batch that has already ended answers with how it ended.
|
|
242
244
|
*
|
|
243
|
-
* Credits: 0 credits
|
|
245
|
+
* Credits: 0 credits. Cancelling is free, and it stops the spending.
|
|
244
246
|
*
|
|
245
247
|
* `DELETE /v1/batches/{id}`
|
|
246
248
|
*/
|
|
@@ -264,10 +266,10 @@ export abstract class GeneratedOperations {
|
|
|
264
266
|
* `puppeteer.connect({ browserWSEndpoint: connectUrl })` in Puppeteer. The browser is
|
|
265
267
|
* yours alone for the whole window, every byte it pulls leaves through the exit you
|
|
266
268
|
* picked, and the socket survives our deploys. The URL carries the session's token and is
|
|
267
|
-
* shown once
|
|
269
|
+
* shown once. It cannot be rebuilt from the id.
|
|
268
270
|
*
|
|
269
271
|
* Credits: 2 credits per minute, reserved for the whole window when the session starts and
|
|
270
|
-
* settled by the minutes actually used when it closes
|
|
272
|
+
* settled by the minutes actually used when it closes, so closing early gives the rest
|
|
271
273
|
* back, and a browser that never came up costs nothing at all. Managed-proxy bandwidth
|
|
272
274
|
* past the first megabyte is added at close; your own proxy is never metered.
|
|
273
275
|
*
|
|
@@ -291,10 +293,10 @@ export abstract class GeneratedOperations {
|
|
|
291
293
|
* Price a session
|
|
292
294
|
*
|
|
293
295
|
* What holding a browser for that long would cost, without holding one. `maxCost` is the
|
|
294
|
-
* whole window at the per-minute rate
|
|
295
|
-
* not use come back when the session closes.
|
|
296
|
+
* whole window at the per-minute rate. It is a ceiling, not a bill, because the minutes
|
|
297
|
+
* you do not use come back when the session closes.
|
|
296
298
|
*
|
|
297
|
-
* Credits: 0 credits
|
|
299
|
+
* Credits: 0 credits. Pricing a session is free.
|
|
298
300
|
*
|
|
299
301
|
* `POST /v1/sessions/quote`
|
|
300
302
|
*/
|
|
@@ -315,10 +317,10 @@ export abstract class GeneratedOperations {
|
|
|
315
317
|
/**
|
|
316
318
|
* List your sessions
|
|
317
319
|
*
|
|
318
|
-
* Your most recent sessions, newest first
|
|
320
|
+
* Your most recent sessions, newest first: the live ones and the ones that have ended,
|
|
319
321
|
* with what each of them cost.
|
|
320
322
|
*
|
|
321
|
-
* Credits: 0 credits
|
|
323
|
+
* Credits: 0 credits. Listing sessions is free.
|
|
322
324
|
*
|
|
323
325
|
* `GET /v1/sessions`
|
|
324
326
|
*/
|
|
@@ -331,10 +333,10 @@ export abstract class GeneratedOperations {
|
|
|
331
333
|
* Read one session
|
|
332
334
|
*
|
|
333
335
|
* Where a session got to: whether it is still live, when it expires, what it has pulled
|
|
334
|
-
* and
|
|
336
|
+
* and, once it has ended, what it finally cost. The connect URL is not repeated here; it
|
|
335
337
|
* is shown once, when the session starts.
|
|
336
338
|
*
|
|
337
|
-
* Credits: 0 credits
|
|
339
|
+
* Credits: 0 credits. Reading a session is free.
|
|
338
340
|
*
|
|
339
341
|
* `GET /v1/sessions/{id}`
|
|
340
342
|
*/
|
|
@@ -347,8 +349,8 @@ export abstract class GeneratedOperations {
|
|
|
347
349
|
* Close a session
|
|
348
350
|
*
|
|
349
351
|
* Stop the browser and settle the bill: the minutes you held it are kept and the rest of
|
|
350
|
-
* the reservation goes back. Safe to repeat
|
|
351
|
-
*
|
|
352
|
+
* the reservation goes back. Safe to repeat: a session that has already ended answers with
|
|
353
|
+
* how it ended.
|
|
352
354
|
*
|
|
353
355
|
* Credits: Closing is free. It is where the session's own bill lands: the unused minutes
|
|
354
356
|
* are refunded and the managed pool's bandwidth is charged.
|