Documentation
¶
Overview ¶
Package sdo is the official Go SDK for the Scrape.do web-scraping API.
Example (Quickstart) ¶
Example_quickstart is the README quickstart, run against a local stand-in for the gateway that returns a page shorter than 200 bytes.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
gateway := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Scrape.do-Request-Cost", "5")
fmt.Fprint(w, "tiny page")
}))
defer gateway.Close()
client, err := sdo.New("YOUR_TOKEN", sdo.WithBaseURL(gateway.URL))
if err != nil {
panic(err)
}
res, err := client.Scrape(context.Background(), &sdo.ScrapeConfig{
URL: "https://example.com",
Render: true,
GeoCode: "us",
})
if err != nil {
panic(err)
}
cost, _ := res.Cost()
fmt.Println(res.StatusCode, cost)
text := res.Text()
fmt.Println(text[:min(200, len(text))]) // the first 200 bytes, or fewer
}
Output: 200 5 tiny page
Index ¶
- Constants
- func Bool(b bool) *bool
- func RedactSecrets(text string) string
- type APIError
- func (e *APIError) Cost() (int, bool)
- func (e *APIError) Error() string
- func (e *APIError) IsAuth() bool
- func (e *APIError) IsConcurrency() bool
- func (e *APIError) IsGateway() bool
- func (e *APIError) IsValidation() bool
- func (e *APIError) RemainingCredits() (int, bool)
- func (e *APIError) RetryAfter() (time.Duration, bool)
- type Account
- type AsyncJobs
- func (j *AsyncJobs) Cancel(ctx context.Context, jobID string) error
- func (j *AsyncJobs) Create(ctx context.Context, targets []string, options map[string]any) (*Job, error)
- func (j AsyncJobs) GoString() string
- func (j *AsyncJobs) List(ctx context.Context) ([]JobStatus, error)
- func (j *AsyncJobs) Me(ctx context.Context) (*Account, error)
- func (j *AsyncJobs) Result(ctx context.Context, jobID, taskID string) (*TaskResult, error)
- func (j *AsyncJobs) Status(ctx context.Context, jobID string) (*JobStatus, error)
- func (j AsyncJobs) String() string
- type Client
- func (c *Client) Do(ctx context.Context, config *ScrapeConfig, req *Request) (*Result, error)
- func (c Client) GoString() string
- func (c *Client) Jobs() *AsyncJobs
- func (c *Client) Plugin(ctx context.Context, route string, params map[string]any) (any, error)
- func (c *Client) PluginResult(ctx context.Context, route string, params map[string]any) (*Result, error)
- func (c *Client) Redact(text string) string
- func (c *Client) Scrape(ctx context.Context, config *ScrapeConfig) (*Result, error)
- func (c Client) String() string
- func (c *Client) Usage(ctx context.Context) (*Usage, error)
- type ConfigError
- type ErrorKind
- type Job
- type JobStatus
- type Option
- func WithAcceptEncoding(s string) Option
- func WithAllowInsecureHTTP() Option
- func WithAsyncBaseURL(u string) Option
- func WithBackoff(d time.Duration) Option
- func WithBaseURL(u string) Option
- func WithHTTPClient(h *http.Client) Option
- func WithMaxResponseBytes(n int64) Option
- func WithPreferBrotli() Option
- func WithRetries(n int) Option
- func WithTimeout(d time.Duration) Option
- func WithUserAgent(ua string) Option
- type Request
- type ResponseTooLargeError
- type Result
- func (r *Result) Bytes() []byte
- func (r *Result) ContentType() string
- func (r *Result) Cookies() string
- func (r *Result) Cost() (int, bool)
- func (r *Result) InitialStatusCode() (int, bool)
- func (r *Result) JSON(v any) error
- func (r *Result) RemainingCredits() (int, bool)
- func (r *Result) ResolvedURL() string
- func (r *Result) TargetRedirectedLocation() string
- func (r *Result) TargetURL() string
- func (r *Result) Text() string
- type ScrapeConfig
- type Task
- type TaskResult
- type TransportError
- type Usage
Examples ¶
Constants ¶
const Version = "0.1.0"
Version is the single source of truth for the installed SDK version. Its only use on the wire is the User-Agent (sdo-sdk-go/<Version>).
Variables ¶
This section is empty.
Functions ¶
func Bool ¶
Bool returns a pointer to b, for setting tri-state *bool fields such as ScrapeConfig.BlockResources from a literal (Go cannot take the address of one): BlockResources: sdo.Bool(false).
func RedactSecrets ¶
RedactSecrets masks credential-shaped values in text: token=, api_key= and access_token= values (in a URL or in prose), the same names as JSON string fields, and an X-Token header value.
It works by shape, so it also covers URLs the SDK never built (echoed by a proxy, or embedded in a *url.Error). Client.Redact additionally masks the client's own token wherever it appears.
Types ¶
type APIError ¶
type APIError struct {
// Message is the gateway's own explanation when its error body carries
// one, otherwise a default for the status. Credentials are redacted.
Message string
StatusCode int
Kind ErrorKind
// Billed reports whether the gateway charged for the response: the
// Scrape.do-Request-Cost header when it is readable (positive is billed,
// 0 is not), else the documented table (the gateway's own 400, 401, 429,
// 502 and 510 are not billed). Never for the async job API.
Billed bool
// IsRetryable reports whether the status is transient. The client retries
// such statuses itself; POST, PUT, PATCH and DELETE only on 429.
IsRetryable bool
// Body is the start of the response body as text (up to 64 KiB is read),
// redacted, truncated to 2048 characters.
Body string
// contains filtered or unexported fields
}
APIError is the single, normative gateway-origin error. Match it with errors.As(err, &apiErr) then inspect Kind. A successful scrape of a page that itself returned 404/410 is NOT an APIError — it is a *Result, and so is any response the gateway billed (a positive Scrape.do-Request-Cost: the target's 400, 401, 403, 422, ...) and any response the target itself produced under TransparentResponse. A 400 or 401 the gateway produced (a parameter it rejected, a refused token) carries no cost and is an APIError.
func (*APIError) Cost ¶
Cost returns the Scrape.do-Request-Cost header and whether it was present as a single non-negative integer, as Result.Cost reads it (never set for the async job API). An error's cost is 0: a positive cost makes the response a *Result.
func (*APIError) IsConcurrency ¶
func (*APIError) IsValidation ¶
func (*APIError) RemainingCredits ¶
RemainingCredits returns the Scrape.do-Remaining-Credits header of a 429 and whether it was present.
type Account ¶
type Account struct {
TotalConcurrency int `json:"TotalConcurrency"`
FreeConcurrency int `json:"FreeConcurrency"`
ActiveJobs int `json:"ActiveJobs"`
// AvailableCredits reads "AvailableCredits", falling back to the API's
// misspelled "AvaliableCredits".
AvailableCredits int `json:"AvailableCredits"`
}
Account is the caller's concurrency and credit snapshot.
func (*Account) UnmarshalJSON ¶
UnmarshalJSON decodes an account snapshot leniently.
type AsyncJobs ¶
type AsyncJobs struct {
// contains filtered or unexported fields
}
AsyncJobs wraps the q.scrape.do REST job surface. Every request authenticates via the X-Token header (never a token= query) and goes through the owning client's transport: its timeout, size cap, retry policy and redaction. Any non-2xx response, a redirect included, is an *APIError; a success body that is not valid JSON is a *TransportError.
func (*AsyncJobs) Create ¶
func (j *AsyncJobs) Create(ctx context.Context, targets []string, options map[string]any) (*Job, error)
Create submits a job for one or more target URLs. options is merged into the JSON body using the API's own PascalCase job-body keys (e.g. "Super", "GeoCode", "Render", "Output"); it is not ScrapeConfig query serialization. The body is canonical JSON. targets must be non-empty, and options may not set "Targets".
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the Scrape.do client. It is safe for concurrent use.
func New ¶
New constructs a Client.
Token precedence: the explicit argument, then SDO_TOKEN. Either is trimmed of ASCII whitespace ("" then means not given) and must otherwise be visible ASCII. The environment also supplies SDO_BASE_URL, SDO_ASYNC_BASE_URL and SDO_TIMEOUT (a decimal number of seconds, at least 0.001) when the matching option is not given. Invalid settings, from options or the environment, return a *ConfigError.
Example ¶
ExampleNew shows constructing a client. The token is read from the explicit argument, falling back to the SDO_TOKEN environment variable.
package main
import (
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
fmt.Println("config error:", err)
return
}
_ = client
}
Output:
Example (Options) ¶
ExampleNew_options configures timeouts, retries, and brotli negotiation.
package main
import (
"fmt"
"time"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New(
"your-token",
sdo.WithTimeout(30*time.Second),
sdo.WithRetries(3),
sdo.WithBackoff(250*time.Millisecond),
sdo.WithPreferBrotli(),
)
if err != nil {
fmt.Println("config error:", err)
return
}
_ = client
}
Output:
func (*Client) Do ¶
Do scrapes config.URL with the method, body and headers in req (nil is a plain GET).
Retries follow the method: GET, HEAD and OPTIONS retry a retryable status and a failure before anything was sent (DNS, connect, TLS); POST, PUT, PATCH and DELETE, which can have side effects on the target, retry only a 429 (the gateway refused before contacting the target). A failure after the request was sent (a read timeout, a dropped connection, a body that cannot be read or decoded) is never retried: the gateway may still run and bill it.
Example ¶
ExampleClient_Do sends a POST with a JSON body through the gateway. The body is canonical JSON; a Request with no Method and a body is a POST.
package main
import (
"context"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
res, err := client.Do(context.Background(),
&sdo.ScrapeConfig{URL: "https://httpbin.org/post", CustomHeaders: true},
&sdo.Request{
JSON: map[string]any{"query": "laptop", "page": 1},
Headers: map[string]string{"Accept": "application/json"},
})
if err != nil {
return
}
fmt.Println(res.StatusCode, res.Header.Values("Set-Cookie"))
}
Output:
func (*Client) Jobs ¶
Jobs returns the async (q.scrape.do) job API. It shares the client's transport: the same timeout, size cap, retry policy and redaction.
Example ¶
ExampleClient_Jobs submits an async job via the REST job API, polls it and reads a task's content. The options map uses the job API's own PascalCase body keys (e.g. Super, GeoCode).
package main
import (
"context"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
ctx := context.Background()
job, err := client.Jobs().Create(ctx, []string{"https://example.com"},
map[string]any{"Super": true, "GeoCode": "us"})
if err != nil {
return
}
status, err := client.Jobs().Status(ctx, job.JobID)
if err != nil || len(status.Tasks) == 0 {
return
}
task, err := client.Jobs().Result(ctx, job.JobID, status.Tasks[0].TaskID)
if err != nil {
return
}
// Binary targets arrive base64-encoded; ContentBytes decodes them.
body, err := task.ContentBytes()
if err != nil {
return
}
fmt.Println(task.Status, task.StatusCode, len(body))
}
Output:
func (*Client) Plugin ¶
Plugin calls a documented plugin endpoint generically. Returns parsed JSON (as any) or raw bytes for non-JSON plugins. PluginResult returns the same response with its status and headers (cost included).
route is the path under /plugin/, e.g. "google/shopping"; "amazon" (or "amazon/") is the Amazon raw-HTML plugin at /plugin/amazon/.
Example ¶
ExampleClient_Plugin calls a structured-data plugin endpoint. JSON responses come back as a decoded any; non-JSON plugins return raw []byte.
package main
import (
"context"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
out, err := client.Plugin(context.Background(), "google/shopping", map[string]any{
"q": "laptop",
"geoCode": "us",
})
if err != nil {
return
}
fmt.Printf("%T\n", out)
}
Output:
func (*Client) PluginResult ¶
func (c *Client) PluginResult(ctx context.Context, route string, params map[string]any) (*Result, error)
PluginResult calls a plugin like Plugin but returns the delivered response as a *Result: its status, its headers (Cost, RemainingCredits) and its body (Result.JSON parses it). Result.URL is "".
func (*Client) Redact ¶
Redact masks this client's token (literally and percent-encoded) and every credential shape RedactSecrets knows in text. Use it on anything derived from a response before it leaves your process (logs, an LLM's context).
func (*Client) Scrape ¶
Scrape fetches a URL with a GET and returns a *Result. It is Do(ctx, config, nil).
Example ¶
ExampleClient_Scrape performs a basic scrape and reads the decoded body.
package main
import (
"context"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
res, err := client.Scrape(context.Background(), &sdo.ScrapeConfig{
URL: "https://example.com",
})
if err != nil {
return
}
fmt.Println(res.StatusCode)
fmt.Println(res.Text())
}
Output:
Example (Render) ¶
ExampleClient_Scrape_render enables headless rendering with a geo target and a wait condition — the render-only fields require Render (or RenderMode) set.
package main
import (
"context"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
res, err := client.Scrape(context.Background(), &sdo.ScrapeConfig{
URL: "https://example.com",
Render: true,
GeoCode: "us",
WaitUntil: "networkidle2",
WaitSelector: "#app",
Output: "markdown",
})
if err != nil {
return
}
fmt.Println(res.Text())
}
Output:
func (Client) String ¶
String describes the client without its token, so logging a Client or a copy of one (with %v, %+v, %s or %q) cannot leak the credential.
func (*Client) Usage ¶
Usage returns the account's usage statistics (GET <base URL>/info, the token as a query parameter). It costs no credits.
The endpoint allows 10 requests per minute per IP address and answers 429 above that, so a retryable status is not retried here (a retry within seconds would only spend more of the same minute); a failure before the request was sent still is. A token the gateway does not know is answered with a 502 "authentication refused".
type ConfigError ¶
type ConfigError struct{ Message string }
ConfigError is returned for client-side misconfiguration (no HTTP call made), e.g. a missing token or mutually exclusive options.
func (*ConfigError) Error ¶
func (e *ConfigError) Error() string
type ErrorKind ¶
type ErrorKind int
ErrorKind classifies a gateway-origin API error. It maps to the shared cross-language taxonomy (Auth/Validation/Concurrency/Gateway).
There is no quota kind: the gateway has no 402 tier and returns 401 for an exhausted credit balance too, so that case is KindAuth and is told apart by the message rather than the status code.
type Job ¶
type Job struct {
JobID string `json:"JobID"`
Message string `json:"Message"`
TaskIDs []string `json:"TaskIDs"`
}
Job is the acknowledgement returned by Create.
func (*Job) UnmarshalJSON ¶
UnmarshalJSON decodes a job acknowledgement leniently.
type JobStatus ¶
type JobStatus struct {
JobID string `json:"JobID"`
TaskIDs []string `json:"TaskIDs"`
Status string `json:"Status"`
StartTime string `json:"StartTime"`
EndTime string `json:"EndTime"`
Canceled bool `json:"Canceled"`
Tasks []Task `json:"Tasks"`
}
JobStatus is the full state of a job and its tasks.
func (*JobStatus) UnmarshalJSON ¶
UnmarshalJSON decodes a job status leniently.
type Option ¶
type Option func(*clientConfig)
Option configures a Client.
func WithAcceptEncoding ¶
WithAcceptEncoding overrides the advertised Accept-Encoding (default "gzip, deflate, br"). The value must be printable ASCII.
func WithAllowInsecureHTTP ¶
func WithAllowInsecureHTTP() Option
WithAllowInsecureHTTP permits an http:// base URL on any host. Without it, plain http is accepted only for localhost, 127.0.0.1 and ::1, because the token travels with every request.
func WithAsyncBaseURL ¶
WithAsyncBaseURL sets the async job API base (default https://q.scrape.do, or SDO_ASYNC_BASE_URL). The same https rule as WithBaseURL applies.
func WithBackoff ¶
WithBackoff sets the base of the exponential backoff between retries (default 500ms). A Retry-After header takes precedence; waits are capped at 30s.
func WithBaseURL ¶
WithBaseURL sets the sync API base (default https://api.scrape.do, or the SDO_BASE_URL environment variable). It must be https, unless the host is localhost, 127.0.0.1 or ::1, or WithAllowInsecureHTTP is given. "" means not given.
func WithHTTPClient ¶
WithHTTPClient supplies the http.Client to send through (for proxies, mTLS or tests). The SDK uses a copy that never follows redirects, so a 3xx cannot carry the token elsewhere. Its Timeout is kept unless WithTimeout is given; when it is 0 the SDK's timeout applies.
func WithMaxResponseBytes ¶
WithMaxResponseBytes sets the ceiling for a decompressed response body (default 32 MiB). Exceeding it returns *ResponseTooLargeError instead of inflating the whole payload.
func WithPreferBrotli ¶
func WithPreferBrotli() Option
WithPreferBrotli advertises "br" alone so the gateway actually returns brotli.
func WithRetries ¶
WithRetries sets how many times a transient failure is retried (default 2). A negative count is treated as 0: the request is still sent once.
func WithTimeout ¶
WithTimeout sets the total deadline of one HTTP attempt: connect, headers and the whole body. The default is 70s (the gateway's 60s default timeout plus 10s), or SDO_TIMEOUT seconds. 0 disables it. When a ScrapeConfig sets the gateway Timeout (or a plugin call the "timeout" param), that request's attempts wait at least the gateway timeout plus 10s.
func WithUserAgent ¶
WithUserAgent replaces the User-Agent, for tools built on this SDK (the sdo CLI sends "sdo-cli/<version>"). The value must match ^sdo-(sdk-(python|js|go)|cli)/<semver>$; anything else makes New return a *ConfigError. The default is "sdo-sdk-go/<Version>".
Example ¶
ExampleWithUserAgent is for tools built on the SDK: the value must be a Scrape.do client identifier, or New returns a *ConfigError.
package main
import (
"errors"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
_, err := sdo.New("your-token", sdo.WithUserAgent("sdo-cli/1.0.0"))
fmt.Println(err)
_, err = sdo.New("your-token", sdo.WithUserAgent("my-bot/1.0"))
var cfgErr *sdo.ConfigError
fmt.Println(errors.As(err, &cfgErr))
}
Output: <nil> true
type Request ¶
type Request struct {
// Method is case-insensitive and one of GET, POST, PUT, PATCH, DELETE,
// HEAD or OPTIONS. Empty means GET, or POST when a body is given.
Method string
// JSON, when non-nil, is the body, in the SDK's canonical JSON encoding
// (sorted keys, no HTML escaping), sent with Content-Type:
// application/json unless Headers sets one.
JSON any
// Data, when non-nil, is the body, sent verbatim with no Content-Type.
// JSON and Data are mutually exclusive.
Data []byte
// Headers are sent with the request and win over ScrapeConfig.Headers.
// Names are case-insensitive; a value replaces the SDK's own header of
// the same name (User-Agent, Accept-Encoding). See ScrapeConfig.Headers for
// what is accepted.
Headers map[string]string
}
Request is the HTTP side of a scrape: the method, body and headers the gateway sends to the target. A nil *Request is a plain GET.
type ResponseTooLargeError ¶
type ResponseTooLargeError struct {
Limit int64
}
ResponseTooLargeError is returned when a response exceeds the client's size ceiling.
Transparent decompression is an attack surface: the scraped target controls the body, so a small compressed payload can inflate without bound. Reading and inflating are both capped and abort as soon as the ceiling is crossed, before the full buffer is materialized.
func (*ResponseTooLargeError) Error ¶
func (e *ResponseTooLargeError) Error() string
type Result ¶
type Result struct {
StatusCode int
// Header holds every response header, repeated ones included
// (Header.Values), as the gateway sent them.
Header http.Header
// URL is the scraped target (ScrapeConfig.URL); "" for a plugin result.
URL string
// contains filtered or unexported fields
}
Result is a successfully delivered scrape (or plugin) response. The body is already transparently decompressed. StatusCode is the *target's* status (a scrape of a 404 page is a successful Result with StatusCode == 404).
func (*Result) ContentType ¶
ContentType returns the response Content-Type.
func (*Result) Cookies ¶
Cookies returns the raw session cookies the gateway reported, or "" if absent.
func (*Result) Cost ¶
Cost returns the credits this request cost (Scrape.do-Request-Cost, the authoritative figure) and whether the header was present as a single non-negative integer of at most 2^53-1.
func (*Result) InitialStatusCode ¶
InitialStatusCode returns the target's first-hop status (before any gateway retry/redirect handling) and whether the header was present.
func (*Result) JSON ¶
JSON unmarshals the body into v. The body is UTF-8 (a leading BOM ignored, invalid bytes read as U+FFFD); NaN, Infinity and numbers beyond the range of a float64 are rejected. Any failure is a *TransportError, like every other SDK failure (and like the Python and JS SDKs); the decoder's error stays reachable through errors.As.
func (*Result) RemainingCredits ¶
RemainingCredits returns the remaining credits and whether the header was present as a single integer.
func (*Result) ResolvedURL ¶
ResolvedURL returns the final URL the gateway resolved to, or "" if absent.
func (*Result) TargetRedirectedLocation ¶
TargetRedirectedLocation returns the Location the target answered with when redirects were not followed (ScrapeConfig.DisableRedirection), from the Scrape.do-Target-Redirected-Location header, or "" if absent.
func (*Result) Text ¶
Text decodes the body per the WHATWG Encoding Standard: a BOM wins and is consumed; then the Content-Type charset (resolved through the WHATWG label table — an unknown label, or one that maps to "replacement", is ignored); JSON media types are UTF-8; HTML (or an unlabelled body) may name its charset in a <meta> within the first 1024 bytes; otherwise UTF-8. Invalid sequences become U+FFFD, one per maximal subpart, so the result is always valid UTF-8 and identical to what the Python and JS SDKs return.
type ScrapeConfig ¶
type ScrapeConfig struct {
// URL is the target: an absolute http or https URL. It is sent exactly as
// given.
URL string
// Render enables JS rendering (render=true).
Render bool
// RenderMode is the render enum: "true" or "false" (matched
// case-insensitively, sent lowercase). When set it wins over Render.
// "false" disables rendering and, like a false Render, is not sent.
// Render: true together with RenderMode "false" is a *ConfigError.
RenderMode string
SuperProxy bool // wire: super
Device string // desktop | mobile | tablet
Timeout int // 5000–120000 ms
// RetryTimeout is 5000–55000 ms and cannot be combined with rendering
// (the gateway rejects render with retryTimeout).
RetryTimeout int
DisableRetry bool
// geo
GeoCode string
// RegionalGeoCode routes through a random country of a region (europe,
// asia, africa, oceania, northamerica, southamerica). It requires
// SuperProxy and cannot be combined with GeoCode: the gateway rejects
// both, so they are a *ConfigError here.
RegionalGeoCode string
// ZipCode targets a postal code. It requires SuperProxy and GeoCode (the
// gateway silently ignores it without super, routing through datacenter
// proxies), so a ZipCode without them is a *ConfigError.
ZipCode string
// SessionID keeps the same proxy (IP) across requests: an integer
// 0-1000000 in decimal digits (at most 7 of them, the gateway's limit).
// Anything else is a *ConfigError. "" is unset; "0" is a valid id.
SessionID string
// SetCookies is the RAW cookie string ("name=value; name2=value2"). The
// gateway expects it URL-encoded and the SDK encodes it as a query value, so
// pre-encoding it yourself double-encodes and breaks it.
SetCookies string
// CustomHeaders, ExtraHeaders and ForwardHeaders choose how Headers reach
// the target; at most one may be set (the gateway rejects any two).
CustomHeaders bool
ExtraHeaders bool
ForwardHeaders bool
// Headers are sent with the request; Request.Headers win over them.
// Whether the gateway forwards them to the target is controlled by
// CustomHeaders / ExtraHeaders / ForwardHeaders. Names are
// case-insensitive and must be RFC 9110 tokens; values printable ASCII
// plus space and tab. Host, Content-Length, Transfer-Encoding and
// Connection cannot be set. Anything else is a *ConfigError before the
// request is sent.
Headers map[string]string
// render-only behaviour: each requires rendering (Render or RenderMode)
CustomWait int // 0–35000 ms
WaitSelector string // CSS selector
WaitUntil string // domcontentloaded | load | networkidle0 | networkidle2
// BlockResources is tri-state. nil leaves it unset and sends nothing, so
// the gateway applies its default of true (resources blocked). A *false
// is sent explicitly (blockResources=false) to load resources — omitting
// it would leave them wrongly blocked by that default. A *true is sent as
// blockResources=true and, being render-only, requires rendering; a *false
// does not. Use sdo.Bool to take the address of a literal.
BlockResources *bool
PlayWithBrowser []map[string]any
// ShowWebsocketRequests and ShowFrames add WebSocket messages and iframe
// content to the JSON response: each requires ReturnJSON as well.
ShowWebsocketRequests bool
ShowFrames bool
// screenshots (render-only, and each requires ReturnJSON: the image comes
// back base64-encoded inside the JSON response). At most one of the three
// may be set, and ParticularScreenshot cannot be combined with
// PlayWithBrowser (take the screenshot as a ScreenShot action instead).
Screenshot bool
FullScreenshot bool
ParticularScreenshot string // CSS selector
// Height and Width set the browser viewport in px (render-only).
Height int
Width int
// output / behaviour
Output string // raw | markdown
// ReturnJSON returns the network requests and page content as JSON. With
// rendering it carries the browser's XHR/fetch requests (and screenshots,
// frames, WebSocket messages when asked for); without rendering the gateway
// wraps the plain response in the same JSON envelope.
ReturnJSON bool
// PureCookies returns the target's original Set-Cookie header instead of
// the Scrape.do-Cookies header. The body is unaffected.
PureCookies bool
DisableRedirection bool
TransparentResponse bool
// misc
// ExtraParams is the escape hatch for gateway parameters without a typed
// field. Keys are sent after the typed params, sorted, with values
// verbatim. "token" and "url" are reserved (case-insensitively), and a key
// must be an ASCII letter followed by letters, digits, '_', '.' or '-'.
ExtraParams map[string]string
}
ScrapeConfig is the single, serializable request spec.
A zero value means "unset" and is omitted from the wire: false, 0, "" and an empty slice or map. Unset values are never validated. Use ExtraParams to send a literal the typed fields cannot express (for example key=false).
Example ¶
ExampleScrapeConfig demonstrates the request spec and error inspection. A gateway-origin failure is an *sdo.APIError whose Kind classifies the cause.
package main
import (
"context"
"errors"
"fmt"
sdo "github.com/scrape-do/sdo-go"
)
func main() {
client, err := sdo.New("your-token")
if err != nil {
return
}
cfg := &sdo.ScrapeConfig{
URL: "https://httpbin.org/json",
SuperProxy: true,
Device: "desktop",
}
if _, err := client.Scrape(context.Background(), cfg); err != nil {
var apiErr *sdo.APIError
// An exhausted credit balance arrives as an auth error: the gateway has
// no 402 tier, so the message is what tells the two apart.
if errors.As(err, &apiErr) && apiErr.IsAuth() {
fmt.Println("auth failed or out of monthly credits:", apiErr.Message)
}
}
}
Output:
type Task ¶
type Task struct {
TaskID string `json:"TaskID"`
URL string `json:"URL"`
Status string `json:"Status"`
}
Task is one target within a job.
func (*Task) UnmarshalJSON ¶
UnmarshalJSON decodes a task leniently.
type TaskResult ¶
type TaskResult struct {
TaskID string `json:"TaskID"`
JobID string `json:"JobID"`
URL string `json:"URL"`
// Status is one of queuing, queued, pending, rotating, success, error,
// canceled.
Status string `json:"Status"`
StartTime string `json:"StartTime"`
EndTime string `json:"EndTime"`
ExpiresAt string `json:"ExpiresAt"`
UpdateTime string `json:"UpdateTime"`
// Base64EncodedContent reports that Content is base64 (a binary target);
// ContentBytes decodes it.
Base64EncodedContent bool `json:"Base64EncodedContent"`
StatusCode int `json:"StatusCode"`
ResponseHeaders map[string]string `json:"ResponseHeaders"`
// ScrapeDo is the gateway's metadata map (the "Scrape.do" key).
ScrapeDo map[string]string `json:"Scrape.do"`
Content string `json:"Content"`
ErrorMessage string `json:"ErrorMessage"`
}
TaskResult is the delivered content of a single task. Absent or mistyped fields are zero. Times are the API's RFC 3339 strings.
func (*TaskResult) ContentBytes ¶
func (t *TaskResult) ContentBytes() ([]byte, error)
ContentBytes returns the task's content as bytes: base64-decoded when Base64EncodedContent is set, otherwise the UTF-8 bytes of Content. Invalid base64 is a *TransportError.
func (*TaskResult) UnmarshalJSON ¶
func (t *TaskResult) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes a task result leniently.
type TransportError ¶
TransportError is returned when no usable HTTP response arrived: DNS, TCP, TLS, timeouts, a body that broke off or could not be decompressed, or a success body that was not the JSON the API promised. Timeout reports whether the cause was a deadline (the client timeout or the context's).
Err holds the cause with credentials already redacted: the raw *url.Error Go produces embeds the request URL — and therefore the token — so wrapping it unchanged would leak through errors.Unwrap and %+v as well as Error().
func (*TransportError) Error ¶
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
type Usage ¶
type Usage struct {
// IsActive reports whether the subscription is active.
IsActive bool
// ConcurrentRequest is the plan's concurrent-request limit.
ConcurrentRequest int
// MaxMonthlyRequest is the plan's monthly request limit.
MaxMonthlyRequest int
// RemainingConcurrentRequest is how many more requests may run right now.
RemainingConcurrentRequest int
// RemainingMonthlyRequest is how many requests are left this month.
RemainingMonthlyRequest int
}
Usage is the account's usage snapshot from the /info endpoint. When the subscription is not active, the gateway reports every count as 0.