Sari la conținut

Documentație

Autentificare, fiecare endpoint cu parametrii lui, erori și limitele planurilor pentru API-ul scrape.land.

API de extragere a datelor

Dă-ne un URL și un set de câmpuri. Aducem pagina, aplicăm selectorii tăi CSS și returnăm JSON curat. Niciun browser de rulat, niciun cod de parsare, nicio rotație de IP de gestionat. URL-ul de bază pentru fiecare endpoint este https://scrape.land.

Autentificare

Trimite cheia ta API în antetul X-Api-Key la fiecare cerere:

X-Api-Key: pb_live_YOURKEY

Merge și un antet bearer Authorization, dacă se potrivește mai bine cu stack-ul tău:

Authorization: Bearer pb_live_YOURKEY

POST/v1/extract

Cererea canonică. Trimite un url și o hartă fields și primești înapoi un obiect data cu o cheie pentru fiecare câmp.

Parametri în corpul cererii
urlstringobligatoriu

Pagina de descărcat: un URL absolut http sau https.

fieldsobject

Nume de câmp → selector: un selector CSS, selector@attr, XPath sau un obiect. Vezi Cum se mapează câmpurile pe selectori. Trimite-l pe acesta sau prompt.

promptstring

Descrie câmpurile în cuvinte, în loc să scrii selectori. Planul Scale și peste. Vezi Extragere cu AI.

schemaobject

Cu prompt: un obiect JSON cheie → tip care fixează forma lui data.

extract_typestring

Auto-extrage un obiect normalizat pentru un tip de pagină cunoscut, fără prompt și fără schemă. Vezi Auto-extragere.

modelstringimplicit "fast"

Nivelul AI: fast, smart sau max.

structuredbooleanimplicit true

Cu prompt: false întoarce un răspuns în limbaj natural în answer, în loc de obiectul data.

groupbooleanimplicit false

Combină câmpurile de tip listă într-un obiect pe rând, în items.

Plus toate opțiunile de cerere.

Cerere
curl https://scrape.land/v1/extract \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example-product.com/item/42",
       "fields":{"title":"h1","price":".price"}}'
import requests

r = requests.post("https://scrape.land/v1/extract",
    headers={"X-Api-Key": "pb_live_YOURKEY"},
    json={"url": "https://example-product.com/item/42",
          "fields": {"title": "h1", "price": ".price"}},
    timeout=60)
print(r.json()["data"])
from scrapeland import ScrapelandClient

client = ScrapelandClient("pb_live_YOURKEY")   # or $SCRAPELAND_API_KEY
data = client.extract("https://example-product.com/item/42",
                      {"title": "h1", "price": ".price"})
print(data)
const res = await fetch("https://scrape.land/v1/extract", {
  method: "POST",
  headers: { "X-Api-Key": "pb_live_YOURKEY", "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://example-product.com/item/42",
    fields: { title: "h1", price: ".price" },
  }),
});
console.log((await res.json()).data);
package main

import ("bytes"; "encoding/json"; "fmt"; "net/http")

func main() {
    body, _ := json.Marshal(map[string]any{
        "url":    "https://example-product.com/item/42",
        "fields": map[string]string{"title": "h1", "price": ".price"},
    })
    req, _ := http.NewRequest("POST", "https://scrape.land/v1/extract", bytes.NewReader(body))
    req.Header.Set("X-Api-Key", "pb_live_YOURKEY")
    req.Header.Set("Content-Type", "application/json")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    var out map[string]any
    json.NewDecoder(resp.Body).Decode(&out)
    fmt.Println(out["data"])
}
$ch = curl_init("https://scrape.land/v1/extract");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => ["X-Api-Key: pb_live_YOURKEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => json_encode([
    "url" => "https://example-product.com/item/42",
    "fields" => ["title" => "h1", "price" => ".price"],
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true)["data"];
Răspuns
{
  "url": "https://example-product.com/item/42",
  "status": 200,
  "data": {
    "title": "Vintage desk lamp",
    "price": "49.00"
  }
}

Cum se mapează câmpurile la selectori

Fiecare intrare din fields ia una dintre aceste forme:

Unde funcționează XPath. XPath este suportat pentru câmpurile de nivel superior, ca prescurtare sau prin cheia xpath, iar un XPath care nu se compilează dă 400 cu numele câmpului. Containerul unui grup trebuie să fie CSS: {"xpath": ..., "fields": {...}} dă 400. În fields ale unui grup, XPath nu este suportat: un subcâmp XPath vine null în fiecare rând, așa că folosește CSS acolo.

Setează "group": true în cerere pentru a uni câmpurile de tip listă într-un obiect per rând, sub items, în loc de tablouri paralele pe care le potrivești singur. Rândurile sunt construite după cea mai lungă listă, iar un câmp fără valoare la acel index este null. Dacă listele au numere diferite de elemente, răspunsul te anunță, pentru că umplerea se face la final: dacă golul real era la mijloc, toate coloanele de după sunt decalate. Când rândurile pot avea goluri, preferă grupul imbricat de mai sus, care citește fiecare valoare din containerul propriu al rândului și nu se poate desincroniza.

Deci această hartă de câmpuri:

{
  "title": "h1",
  "price": ".price",
  "image": "img.gallery@src",
  "tags":  {"css": ".tag", "all": true}
}

produce acest obiect de date, câmp cu câmp:

CâmpSelector / obiectValoare returnată
title"h1"textul primului h1
price".price"textul primului .price
image"img.gallery@src"atributul src, ca șir
tags{"css":".tag","all":true}o listă cu textul fiecărui .tag

Când un câmp revine null

Un câmp este null fie pentru că pagina chiar nu are un astfel de element, fie pentru că selectorul în sine nu a putut fi folosit. Pentru tine sunt lucruri foarte diferite, așa că le deosebim: dacă un selector nu a putut fi compilat, răspunsul poartă un obiect field_errors care numește câmpul și motivul. Câmpul rămâne prezent în data ca null (o listă goală pentru un câmp cu "all": true), cererea rămâne un 200, iar celelalte câmpuri nu sunt afectate: un selector inutilizabil nu scufundă niciodată restul extragerii.

{
  "data": {"title": "Example Domain", "price": null, "tags": null},
  "field_errors": {
    "tags": "selector did not compile (unknown pseudoclass or pseudoelement :is) — this field is null because the selector could not be used, not because the page lacks the element"
  }
}

Lipsa cheii field_errors înseamnă că fiecare selector s-a compilat, deci un null acolo este un „nu se află pe pagină” real. XPath este validat din start și returnează 400.

Ce CSS acceptă selectorii noștri. Potrivirea selectorilor folosește Cascadia din Go, care implementează CSS3. Nu este un motor de browser și diferă în ambele sensuri: bine de știut înainte să lipești un selector din devtools.
  • Selectorii mai noi nu sunt încă acceptați și vor ajunge în field_errors: :is(), :where(), :has(> x) în forma relativă, :focus-within, selectori cu spațiu de nume precum svg|circle. Tot ce ține de CSS3 funcționează: combinatori de descendent/copil/frate, :nth-child(), :not(), selectori de atribut inclusiv ^= $= *= și indicatorul i.
  • Patru selectori funcționează aici și nu sunt acceptați de niciun browser. Sunt extensii Cascadia pentru potrivire pe text, lucru pe care CSS-ul în sine nu îl poate face: chiar utile, dar nu sunt CSS standard. Un selector care le folosește nu va funcționa într-un browser, în devtools sau în orice alt instrument de scraping, așa că tratează-le ca pe o comoditate a acestui API, nu ca pe ceva de standardizat.
    SelectorCe potriveșteExemplu
    :contains(…)element al cărui text conține un subșirh1:contains("Example")
    :matches(…)element al cărui text se potrivește cu o expresie regulatăp:matches(^Price:)
    :matchesOwn(…)la fel, dar doar textul propriu al elementului, ignorând descendențiih1:matchesOwn(^Example)
    :haschild(…)element cu un copil direct care se potrivește cu un selectorp:haschild(a)

    Atenție la tipul argumentelor, aici e greșeala ușoară: :matches() și :matchesOwn() primesc o expresie regulată peste text, nu sunt o altă scriere a lui :is(). :matches(h1,h2) se compilează și apoi nu potrivește nimic, pentru că el caută textul literal h1,h2. Pune între ghilimele orice argument care conține spații sau punctuație: :contains("Learn more"), nu :contains(Learn more).

Opțiuni de cerere

Funcționează pe orice endpoint care descarcă o pagină: /v1/extract, /v1/fetch, /v1/batch, /v1/rank și în joburi. Opțiunile de browser au nevoie de render.

Opțiuni
countrystring

Țara de ieșire, cod ISO, de ex. us. Cererea pleacă din acea țară.

sessionstring

Numele unei sesiuni persistente. Reutilizează același IP de ieșire între apeluri (autentificări, coșuri).

protocolstring

Protocolul de ieșire: http, https sau socks5, adică protocolul pe care îl vorbește proxy-ul de ieșire. Omite-l pentru oricare.

max_latencyinteger

Folosește doar ieșiri care au răspuns mai repede de atâtea milisecunde.

renderbooleanimplicit false

true pentru a rula pagina într-un browser real înainte de a o citi.

wait_forstring

În modul render, un selector CSS de așteptat înainte de citirea DOM-ului.

devicestringimplicit "desktop"

"mobile" randează ca pe un telefon (viewport tactil 390×844, 3×, UA de Safari mobil) pentru layoutul mobil; implicit este desktop 1280×800. Implică render.

block_resourcesbooleanimplicit false

În modul render, true sare peste imagini/fonturi/media pentru o randare mai rapidă și mai ușoară (DOM-ul nu este afectat, extragerea funcționează în continuare).

fingerprintbooleanimplicit false

În modul render, true prezintă o identitate de browser coerentă cu country (fus orar, locale, geolocație, navigator.languages, WebGL/canvas generice) astfel încât o randare arată ca un client real din acea regiune și declanșează mai puține blocaje anti-bot. O session o menține stabilă.

actionsarray

În modul render, pași scriptați înainte de captură: scroll, click, wait, wait_for, fill. Excelent pentru scroll infinit și conținut ascuns în spatele unui clic.

headersbooleanimplicit false

true pentru a include anteturile de răspuns în rezultat.

send_headersobject

Un obiect cu anteturi de cerere suplimentare, trimise mai departe către țintă (User-Agent propriu, Authorization, …).

cookiesstring

Valoarea unui antet Cookie de trimis, de ex. pentru a extrage în spatele unei autentificări.

method / bodystringimplicit "GET"

Metoda HTTP (implicit GET) și corpul cererii. Folosește POST/PUT/… pentru API-uri JSON sau pentru trimiterea de formulare. Ce nu e GET nu se reîncearcă niciodată și nu este compatibil cu render.

metadatabooleanimplicit false

true returnează în plus un obiect metadata: title, description, canonical, lang, etichete OpenGraph/Twitter și jsonld schema.org parsat. Excelent pentru previzualizări de linkuri și SEO (fără niciun selector).

linksbooleanimplicit false

true returnează în plus o listă links: fiecare a href rezolvat la URL absolut, deduplicat, doar http(s). Util pentru parcurgere și descoperire.

screenshotbooleanimplicit false

Pentru /v1/fetch: true returnează un PNG base64 al paginii randate (implică render). Adaugă full_page: true pentru toată înălțimea derulabilă.

Pagini JavaScript: render și wait_for

Dacă pagina își construiește conținutul cu JavaScript, adaugă "render": true. Pornim un browser real, lăsăm pagina să ruleze, apoi citim DOM-ul și aplicăm selectorii tăi. Combină-l cu "wait_for" setat pe un selector CSS, ca să așteptăm până apare acel element înainte de citire. Este modul sigur de a aștepta conținut care se încarcă târziu:

curl https://scrape.land/v1/extract \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example-product.com/item/42",
    "render": true,
    "wait_for": "h1.title",
    "fields": {"title": "h1.title", "price": ".price"}
  }'
Cât te costă o randare. Un fetch simplu descarcă un singur document HTML. O randare descarcă tot ce cere pagina (JavaScript, CSS, imagini, fonturi, tracking) deci este mai lentă și trage mult mai multe date: măsurat la 1–300× octeții aceleiași pagini aduse simplu, mediana în jur de 17×, cu magazine online pline de imagini la capătul de sus. Este prețuită pe măsură: o randare livrată costă 5 unități de cerere în loc de 1 cât costă un fetch simplu, 10 dacă ai cerut și un screenshot, și 1 (mai ieftin decât o randare simplă și la fel ca un fetch simplu) când trimiți block_resources. O randare care eșuează nu se facturează deloc. Dacă nu ai nevoie de un screenshot sau chiar de imagini, trimite "block_resources": true: imaginile, fonturile și media sunt sărite, DOM-ul pe care îl citesc selectorii tăi este identic, pe o pagină plină de imagini elimină o mare parte din transfer (măsurat pe 14 pagini: mediană 42%, cel mai bun 77% și în jur de 23% chiar și pe CDN-uri de imagini care servesc URL-uri fără extensie de fișier) și scade costul de la 5 unități la 1. Se aplică inclusiv împreună cu screenshot: resursele chiar sunt blocate, deci captura pe care o primești va fi fără imagini. Capacitatea de randare este și ea limitată: dacă toate sloturile de browser sunt ocupate primești un 503 cu Retry-After: 1, deci reîncearcă în loc să tratezi asta ca pe un eșec.

Extragere cu AI (descrie câmpurile în limbaj natural)

Cerință de plan: extragerea cu AI (un prompt, o schema sau orice preset extract_type) este disponibilă pe Scale și mai sus (Scale, Business, Business XL/XXL/Ultra). Pe Free, Pool, Starter și Growth acestea returnează 403 cu planul de care ai avea nevoie. Extragerea cu fields pe bază de selectori (CSS și XPath, mai jos) funcționează pe orice plan, inclusiv Free. Verifică GET /v1/capabilities cu cheia ta ca să vezi ce ai.

Nu vrei să scrii și să întreții selectori? Trimite un prompt în loc de fields: aducem pagina (toți parametrii de mai sus se aplică în continuare), o convertim în Markdown, iar un model de limbaj returnează exact câmpurile pe care le-ai descris, ca JSON. Selectorii se strică atunci când se schimbă markupul unui site; un prompt se adaptează. Adaugă o schema opțională (un obiect JSON de key→tip) ca să fixezi forma exactă a răspunsului. Trimite model ca să alegi un nivel de putere. "fast" este implicit: mediană 1,5 s și alegerea potrivită pentru aproape orice extragere, fiindcă a scoate dintr-o pagină câmpuri care sunt scrise acolo este o sarcină de citire. "smart" are o mediană de circa 10 s și e mai bun când răspunsul trebuie dedus din pagină, nu doar citit, de pildă combinând o oră de început, o durată și un fus orar. "max" folosește cel mai puternic model al nostru (mediană de circa 3 s) și e pentru extragerile în care fiecare câmp trebuie să fie corect. Fiecare nivel se facturează de la un minim peste unitatea de preluare (fast 2, smart 4, max 25 de unități de cerere), iar o pagină foarte mare sau un răspuns lung se facturează mai mult, proporțional cu cât a costat efectiv apelul la model. Un nume necunoscut este un 400 care le enumeră pe cele valide. Dacă nivelul cerut nu este disponibil, răspundem pe celălalt în loc să eșuăm, facturăm nivelul care a servit efectiv și îți spunem returnând requested_model alături de model.

Pui o întrebare în loc să enumeri câmpuri? Un prompt precum "what's this business about?" este o întrebare, nu o listă de câmpuri, dar modelul tot trebuie să inventeze nume de chei pentru ea (business, category, competitors_mentioned…), iar tu trebuie să ghicești ce a ales. Trimite "structured": false și primești în schimb un răspuns în limbaj natural sub answer, fără nicio cheie data:

curl https://scrape.land/v1/extract \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://scrape.land/",
    "prompt": "what's this business about?",
    "structured": false
  }'
{
  "url": "https://scrape.land/",
  "status": 200,
  "extracted_by": "ai",
  "model": "fast",
  "answer": "scrape.land is a web data-extraction API: you send a URL and it returns clean structured data, handling proxy rotation, retries, geo-targeting and JavaScript rendering for you. Pricing is flat per 1,000 delivered requests."
}

data și answer nu apar niciodată împreună, deci un client tipizat ramifică după cea pe care a primit-o, în loc să facă type-switch pe un singur câmp. structured este implicit true, deci nimic din ce trimiți deja nu se schimbă. Costă exact aceleași unități de cerere ca extragerea structurată: este aceeași pagină și același apel de model. O schema sau un extract_type fixează o formă JSON și deci implică ieșire structurată; trimițându-le împreună cu "structured": false primești un 400 care numește ambele câmpuri, în loc să ghicim în tăcere ce ai vrut. Nu are niciun efect asupra extragerii cu fields pe bază de selectori, care nu ajunge niciodată la un model.

Ai nevoie de o listă? Cere "every product on the page" și primești lista înapoi sub data.items. Dacă pagina nu are text (o pagină doar-JS adusă fără render), primești un 422 care îți spune să reîncerci cu "render": true, în loc de un răspuns plin de valori nule.

CSS față de AI: AI-ul citește textul vizibil al paginii plus metadatele ei structurate. JSON-LD, OpenGraph, microdate și <time datetime> ajung la model, deci un preț exact, un cod de monedă sau o dată ISO de publicare sunt disponibile chiar și când pagina afișează doar "From $49" sau "3 days ago". Datele codificate cu totul altundeva (un rating într-un nume de clasă CSS) se scot totuși mai bine cu fields (CSS/XPath), care văd HTML-ul brut. Combină-le liber.
curl https://scrape.land/v1/extract \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://books.toscrape.com/",
    "prompt": "the page title, and the title and price (as a number) of the first book"
  }'
{
  "url": "https://books.toscrape.com/",
  "status": 200,
  "extracted_by": "ai",
  "model": "fast",
  "data": {
    "page_title": "Books to Scrape",
    "first_book_title": "A Light in the Attic",
    "first_book_price": 51.77
  }
}

Auto-extragere (un cuvânt în loc de schemă)

Auto-extragerea este tot extragere cu AI dedesubt, deci cere același plan Scale sau mai sus.

Pentru tipurile de pagină uzuale nu ai nevoie nici măcar de un prompt. Trimite extract_type și aplicăm noi un prompt și o schemă îngrijite, ca să primești înapoi un obiect normalizat: product, article, job, discussion, event, recipe, real_estate și profile. prompt-ul sau schema ta au întâietate dacă le trimiți și pe ele.

Preseturile rulează pe nivelul implicit fast și răspund de obicei în câteva secunde. Trimite "model": "smart" dacă o pagină este chiar ambiguă. Durează cam de două ori mai mult și costă mai multe unități de cerere. Cererile sincrone au un buget de 150 s; peste el primești un 504, deci trimite una lentă ca job asincron (POST /v1/jobs, buget de 5 minute).

curl https://scrape.land/v1/extract \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
    "extract_type": "product",
    "model": "fast"
  }'

Obținerea anteturilor de răspuns

Uneori ai nevoie de anteturile de răspuns, nu doar de corp: un Location de redirect, un Set-Cookie, un Content-Type. Adaugă "headers": true și includem un obiect headers alături de data. Numele anteturilor vin în forma canonică (Content-Type, Set-Cookie), iar un antet repetat este unit cu ", ". Obiectul conține și două anteturi ale noastre: X-Proxy-Country, țara de ieșire din care a plecat cererea, și X-Proxy-Tier, nivelul de ieșire care a transportat-o (premium sau pool). Niciunul nu este un antet al răspunsului HTTP al API-ului însuși.

{
  "url": "https://example-product.com/item/42",
  "status": 200,
  "data": {"title": "Vintage desk lamp"},
  "headers": {
    "Content-Type": "text/html; charset=utf-8",
    "Set-Cookie": "sid=abc123; Path=/; HttpOnly",
    "Cache-Control": "max-age=300",
    "X-Proxy-Country": "US",
    "X-Proxy-Tier": "premium"
  }
}

Mai multe endpointuri

POST/v1/fetch

Când vrei pagina întreagă în loc de câmpuri anume, folosește /v1/fetch. Setează format pe html (implicit), text (textul vizibil), markdown sau raw (octeți base64 + content_type, pentru PDF-uri/imagini/binare, până la 7 MiB). Se aplică aceleași opțiuni country, session, render, wait_for și headers.

Parametri în corpul cererii
urlstringobligatoriu

Pagina de descărcat: un URL absolut http sau https.

formatstringimplicit "html"

Pentru /v1/fetch: html (implicit), text, markdown (Markdown gata pentru LLM, vezi mai jos) sau raw, care returnează octeții exacți codificați base64 sub base64, împreună cu content_type, ca să poți trage un PDF, o imagine sau orice fișier binar prin proxy intact (plafonat la 7 MiB).

Plus toate opțiunile de cerere.

Cerere
curl https://scrape.land/v1/fetch \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example-product.com/item/42", "format": "html"}'
Răspuns
{
  "url": "https://example-product.com/item/42",
  "status": 200,
  "html": "<!doctype html><html>…"
}

Când pagina primită este o provocare anti-bot sau un refuz (de exemplu o pagină Cloudflare "Just a moment") în loc de conținut, răspunsul conține și "blocked": true și antetul X-Scrapeland-Blocked: 1, chiar dacă ținta a răspuns cu 200. Reîncearcă: răspunsurile blocate nu se facturează niciodată.

Markdown gata pentru LLM

"format": "markdown" returnează pagina ca Markdown curat sub o cheie markdown, forma pe care pipeline-urile RAG și ferestrele de context ale LLM-urilor chiar o vor, ca să nu trimiți etichete HTML brute unui model și să le plătești ca tokeni. Elementele de șablon ale site-ului (nav, header, footer, aside, scripturi și stiluri) sunt scoase, titlurile, listele, linkurile, blocurile de cod și tabelele sunt păstrate, iar linkurile și imaginile relative sunt rezolvate la URL-uri absolute, ca un fragment să funcționeze și după ce este separat de pagina sursă. Se combină cu orice altă opțiune (country, session, render, wait_for, actions, fingerprint) și funcționează și în /v1/batch. Costă exact o unitate de cerere, la fel ca orice alt fetch; nu există niciun supliment pentru Markdown sau AI.

curl https://scrape.land/v1/fetch \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog/post", "format": "markdown", "render": true}'
# -> {"url":"...","status":200,"markdown":"# Post title\n\nBody text..."}

POST/v1/batch

Adu până la 20 de URL-uri într-un singur apel. Trimite o listă urls plus orice parametri comuni; primești o intrare results pentru fiecare URL, în ordinea de intrare: fiecare fie un rezultat obișnuit de fetch, fie un {"url","error"}. Fiecare URL se facturează ca o cerere livrată separată.

Extragere în masă: adaugă fields sau un prompt și fiecare URL este extras exact ca la /v1/extract (fiecare rezultat poartă un obiect data). Excelent pentru a transforma o listă de URL-uri în rânduri, într-un singur apel.

Parametri în corpul cererii
urlsarrayobligatoriu

Până la 20 de URL-uri absolute. Rezultatele vin în aceeași ordine.

fields / promptobject / string

Opțional. Fiecare URL este extras ca la /v1/extract, iar rezultatul lui conține data.

formatstringimplicit "html"

Fără câmpuri sau prompt: formatul fiecărei pagini, ca la /v1/fetch.

Plus toate opțiunile de cerere.

Cerere
curl https://scrape.land/v1/batch \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://a.example/", "https://b.example/"], "format": "text"}'
Răspuns
{
  "results": [
    {"url": "https://a.example/", "status": 200, "text": "…"},
    {"url": "https://b.example/", "error": "…"}
  ]
}

POST/v1/search

Rulează o căutare web și primești înapoi rezultatele organice (title, url-ul real de destinație și un snippet) fără să parsezi tu un SERP. Trimite {"q": "...", "count": 10} plus orice parametri de proxy comuni. Se facturează ca o singură cerere livrată. count este plafonat la 10, o singură pagină de rezultate de la motorul de căutare; ceri mai mult și tot 10 primești.

Parametri în corpul cererii
qstringobligatoriu

Interogarea de căutare.

countintegerimplicit 10

Numărul de rezultate, de la 1 la 10.

Plus opțiunile de proxy country, session, protocol și max_latency.

Cerere
curl https://scrape.land/v1/search \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "best web scraping api", "count": 5}'
Răspuns
{
  "query": "best web scraping api",
  "count": 2,
  "results": [
    {"title": "…", "url": "https://…", "snippet": "…"},
    {"title": "…", "url": "https://…", "snippet": "…"}
  ]
}

POST/v1/rank

Clasează linkurile de pe o pagină după cât de relevant este fiecare pentru un scop, ca să alegi ce sub-pagini să extragi în continuare în loc să parcurgi tot. Dă un url şi un query; noi aducem pagina, îi citim linkurile (cu tot cu textul ancorei), iar un LLM le returnează ordonate cu un score (0–1) şi un reason scurt. Este stateless: clasează linkurile pe care pagina le are deja şi nu le accesează. Adaugă top_k ca să limitezi lista, model ("fast"/"smart") şi orice parametri comuni de fetch. Dacă pasul AI eşuează, tot primeşti linkurile în ordinea din document, cu un ranking_error (codul tău nu se strică). Se taxează ca un fetch plus suprataxa AI; o clasare eşuată taxează doar fetch-ul.

Parametri în corpul cererii
urlstringobligatoriu

Pagina de descărcat: un URL absolut http sau https.

querystringobligatoriu

La ce trebuie să fie relevante linkurile.

top_kinteger

Câte linkuri ordonate să întoarcă, maximum 50.

modelstringimplicit "fast"

Nivelul AI: fast, smart sau max.

Plus toate opțiunile de cerere.

Cerere
curl https://scrape.land/v1/rank \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog", "query": "in-depth technical tutorials", "top_k": 10}'
Răspuns
{
  "url": "https://example.com/blog",
  "query": "in-depth technical tutorials",
  "status": 200,
  "total_links": 84,
  "ranked": [
    {"url": "https://example.com/blog/go-generics",
     "text": "A deep dive into Go generics",
     "score": 0.93, "reason": "long-form technical tutorial"}
  ],
  "ranked_count": 10,
  "extracted_by": "ai",
  "model": "fast"
}

POST/v1/jobs: async & webhooks

Pentru lucrări de durată (un batch mare, o randare lentă) rulează-le asincron: trimiți un job, primești imediat un id, apoi îl interoghezi sau primești rezultatul pe un webhook. Trimite parametrii obișnuiți ai operației plus type (fetch/extract/batch/search) și un webhook_url opțional.

Parametri în corpul cererii
typestringobligatoriu

Operația de rulat: fetch, extract, batch sau search.

webhook_urlstring

Un URL https public la care trimitem rezultatul prin POST când jobul se termină.

Plus parametrii acelei operații, exact ca pe endpointul ei.

Cerere
curl https://scrape.land/v1/jobs \
  -H "X-Api-Key: pb_live_YOURKEY" -H "Content-Type: application/json" \
  -d '{"type":"batch","urls":["https://a.example","https://b.example"],
       "fields":{"title":"h1"},"webhook_url":"https://your.app/hook"}'
# -> {"id":"job_ab12","status":"queued"}

# poll it:
curl https://scrape.land/v1/jobs/job_ab12 -H "X-Api-Key: pb_live_YOURKEY"
# -> {"id":"job_ab12","type":"batch","status":"done","result":{}}

# list recent jobs (newest first, no result blobs):
curl "https://scrape.land/v1/jobs?limit=20" -H "X-Api-Key: pb_live_YOURKEY"

Când un job se termină trimitem un POST cu {"id","status","result"} către webhook_url-ul tău, care trebuie să fie un URL https public (adresele interne și loopback sunt respinse). Joburile rulează până la câteva minute; fiecare este contorizat exact ca apelul sincron.

Verificarea webhookului. Fiecare livrare poartă un antet X-Scrapeland-Signature de forma t=<unix>,v1=<hex>, unde hexul este HMAC-SHA256("<t>.<raw body>") cu cheia secretului tău de webhook. Semnează octeții BRUȚI ai corpului (nu o copie reserializată), compară în timp constant și respinge orice are un t mai vechi de câteva minute: asta oprește o livrare capturată să fie reluată împotriva ta mai târziu. Cere secretul de la suport dacă folosești webhook_url.

import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    ts, sig = parts.get("t", ""), parts.get("v1", "")
    if not ts or not sig or abs(time.time() - int(ts)) > tolerance:
        return False
    expected = hmac.new(secret.encode(),
                        ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

POST/v1/places

Afaceri locale de un anumit tip într-un anumit loc, sub formă de rânduri: nume, adresă, site, telefon, email și program. O listă de lead-uri fără să scrii un scraper. Trimite o category și o location (sau un bbox), păstrează opțional doar afacerile care au sau nu au site, telefon ori email și primești potrivirile ordonate după cele mai bune date de contact: un rând cu site și telefon trece înaintea unuia fără niciunul, apoi după nume.

Parametri în corpul cererii
categorystringobligatoriu

Un id de categorie, de exemplu dentist, restaurant, plumber sau it_company. Lista completă este în GET /v1/capabilities, la entitlements.places.categories; un id necunoscut dă 400.

locationstring

Un loc în care să cauți, de exemplu "Austin, TX" sau "Lyon". Trimite acest câmp sau bbox.

bboxarray

[south, west, north, east] în grade, în loc de location. Cel mult aproximativ 1,2 grade pătrate (o zonă metropolitană mare); o zonă mai mare, sau o locație care se traduce într-una, dă 400.

website / phone / emailstringimplicit "any"

Fiecare acceptă any, yes (doar afacerile care au unul listat) sau no (doar cele care nu au). "website": "no" îți dă o listă de afaceri fără site.

limitintegerimplicit 200

Numărul maxim de rânduri returnate, până la 200 (maximul, și ce primești dacă îl omiți). Pe planul Free este plafonat la 10.

offsetintegerimplicit 0

De unde începe pagina: 0 (implicit) pentru prima pagină, apoi next_offset din răspunsul anterior. Ignorat pe planul Free, care întoarce doar prima pagină.

results conține rândurile. Fiecare rând are name, category, lat, lon și osm_url, plus oricare dintre address, city, website, profile (o pagină socială, de rezervări sau de director listată în locul unui site propriu), phone, email și opening_hours are harta pentru el: un câmp fără date este omis, nu trimis gol. count este numărul de rânduri returnate, matched câte s-au potrivit filtrelor înainte de limită, units cât a facturat apelul, iar bbox zona căutată.

Toate afacerile. Un apel întoarce până la 200. Pentru toate, apelează din nou cu offset setat la next_offset din răspunsul anterior, până când este null; paginile vin din aceeași listă sortată, deci nu se suprapun și nu sar rânduri. O zonă întoarce cel mult 3.000 de afaceri dintr-o categorie; când are mai multe, răspunsul spune "truncated": true, iar căutarea pe zone mai mici (un cartier sau un bbox) le aduce pe toate.

Facturare și planuri. 1 unitate de cerere la fiecare 10 rânduri returnate, minimum 1 (un răspuns gol a rulat totuși o interogare), pe același contor ca orice alt apel. Orice plan îl poate apela. Pe Free un apel este o previzualizare: primele 10 potriviri, iar când s-au potrivit mai multe răspunsul spune asta cu "preview": true și un preview_note. Planurile plătite primesc până la 200 de rânduri per apel. Dacă serviciul de date cartografice este ocupat primești 502 și nu se facturează nimic.

Date și atribuire. Rândurile provin din OpenStreetMap, © contribuitorii OpenStreetMap, sub licența ODbL. Fiecare răspuns poartă această mențiune în attribution, iar fiecare rând trimite la sursa sa prin osm_url. Dacă publici sau distribui datele, trebuie să menționezi OpenStreetMap, așa cum cere licența. Răspunsurile sunt păstrate în cache până la 24 de ore.

Cerere
curl https://scrape.land/v1/places \
  -H "X-Api-Key: pb_live_YOURKEY" \
  -H "Content-Type: application/json" \
  -d '{"category": "dentist", "location": "Austin, TX", "website": "yes"}'
Răspuns
{
  "attribution": "© OpenStreetMap contributors, ODbL (openstreetmap.org/copyright)",
  "bbox": [30.0985133, -97.9367663, 30.5166255, -97.5605288],
  "category": "dentist",
  "count": 10,
  "matched": 79,
  "preview": true,
  "preview_note": "Free plan: the first 10 matches. Every paid plan returns up to 200 per call.",
  "results": [
    {"name": "Aloha Dental", "category": "dentist",
     "address": "1614 East Riverside Drive, 78741", "city": "Austin",
     "lat": 30.245304, "lon": -97.730394,
     "website": "https://www.aloha-dental.com/", "phone": "+1-512-707-7300",
     "email": "riverside@aloha-dental.com",
     "opening_hours": "Mo 08:00-17:00; Tu 09:00-18:00; We 08:00-17:00; Th 09:00-18:00; Fr 08:00-17:00",
     "osm_url": "https://www.openstreetmap.org/node/3856445942"},
    {"name": "Austin Dental", "category": "dentist",
     "address": "2206 West Parmer Ln, 78727", "city": "Austin",
     "lat": 30.417672, "lon": -97.698822,
     "website": "https://www.lifetimedental.com/", "phone": "+1-512-982-4006",
     "opening_hours": "Mo-Fr 07:30-17:00",
     "osm_url": "https://www.openstreetmap.org/way/383188243"}
  ],
  "units": 1
}

GET/v1/capabilities

Un endpoint cacheabil, ca să poți detecta din cod ce acceptă o instalare înainte de a trimite o cerere: formats și features disponibile, dacă render (și subopțiunile) și extragerea cu AI sunt pornite, models și extract_types ale AI-ului, endpoints-urile active și limits. Ambele SDK-uri îl expun ca capabilities().

Funcționează și fără cheie, dar trimite-ți X-Api-Key și răspunsul este adaptat contului tău. Extragerea cu AI depinde de plan (vezi Extragere cu AI), deci un apel fără cheie îți poate spune doar ce acceptă instalarea, în timp ce unul cu cheie îți spune ce poți apela tu: ai.enabled este atunci adevărat doar dacă planul tău îl include, iar extract_type/extract_types dispar odată cu el. ai.configured raportează instalarea, ai.min_plan numește planul de care ai avea nevoie, iar ai.plan_scoped îți spune dacă răspunsul a fost personalizat. Detectează pe ai.enabled și nu vei primi niciodată un da urmat de un 403.

Dimensiunea răspunsului. limits.max_response_bytes este cel mai mare răspuns unic pe care îl returnăm și depinde de planul tău (Free, Pool și Starter 2 MB, Growth 5 MB, Scale 10 MB, Business 25 MB). Paginile reale sunt departe de această valoare (răspunsul median pe care îl servim are cam 40 KB, iar 99% sunt sub 1,2 MB), deci prinde doar un video, o arhivă sau un export de date. Un răspuns peste limită este refuzat în întregime, cu un 413 care numește dimensiunea, limita și planul tău; nu îți dăm niciodată o pagină trunchiată, pentru că o pagină care și-a pierdut coada în tăcere se facturează ca reușită și se parsează ca date corupte. limits.max_response_bytes_scope îți spune al cui număr ai primit: "key" când ai trimis un X-Api-Key (limita planului tău) sau "lowest_plan" când nu ai trimis, adică pragul minim pe care îl primește orice cont. Așa că e mereu sigur pentru dimensionare, iar prezentarea unei chei nu poate decât să îl ridice.

Răspuns
{
  "formats": ["html", "text", "markdown", "raw"],
  "render": {"enabled": true, "screenshot": true, "actions": true,
             "device": true, "block_resources": true, "fingerprint": true},
  "ai": {"enabled": true, "configured": true, "min_plan": "scale",
         "plan_scoped": true, "models": ["fast", "smart", "max"], "default": "fast"},
  "extract_types": ["product", "article", "job", "discussion",
                    "event", "recipe", "real_estate", "profile"],
  "endpoints": ["/v1/fetch", "/v1/extract", "/v1/rank", "/v1/batch",
                "/v1/search", "/v1/jobs", "/v1/places", "/v1/account",
                "/v1/capabilities", "/mcp"],
  "limits": {"batch_max_urls": 20, "links_max": 2000,
             "search_max_count": 10, "raw_max_bytes": 7340032,
             "max_response_bytes": 5242880,
             "max_response_bytes_scope": "key"}
}

GET/v1/account

Vezi din cod planul cheii tale, cota rămasă, creditul preplătit, rate limit-ul și bugetul per cheie (de ex. înainte de un job mare), în loc să deschizi panoul. Raportează întotdeauna doar contul tău. Ambele SDK-uri îl expun ca account().

plan este codul planului, nu numele afișat: free, pool, starter, pro (planul Growth), scale, business, business_xl, business_xxl sau business_ultra. Aceleași coduri apar ca plan și required_plan în refuzuri și în GET /v1/capabilities.

Răspuns
{
  "plan": "scale",
  "included_requests_remaining": 2871400,
  "prepaid_credit_cents": 0,
  "rate_limit_rps": 200,
  "key_budget": {"max_requests": 0, "requests_used": 1234, "unlimited": true}
}

MCP server (Claude, Cursor and other AI clients)

scrape.land is also a remote Model Context Protocol server, so an AI assistant can fetch pages, extract data, search the web and find local businesses as tools. Endpoint: https://scrape.land/mcp (Streamable HTTP, JSON-RPC over POST). Authenticate with your API key as Authorization: Bearer YOUR_KEY. Each tool call runs the matching endpoint on your account, so it is billed, rate limited and plan gated exactly like the API.

ToolDoesSame as
fetch_pageFetch a page as markdown (default), text or HTML through our rotating tunneling network. Arguments: url, format, render, country./v1/fetch
extract_dataStructured JSON from a page, by CSS fields or a plain-English prompt (with optional model: fast, smart or max)./v1/extract
web_searchOrganic web results for q (up to 10)./v1/search
find_local_businessesBusinesses of a category in a location, with optional website, phone and email filters (any, yes or no), a limit and an offset to page through every result./v1/places

Adaugă-l în instrumentul tău AI. Înlocuiește YOUR_KEY cu o cheie API din panou; butoanele de instalare îl adaugă cu acest substituent, așa că deschide apoi setările serverului și lipește cheia ta.

Add to Cursor Add to Kiro

Claude (claude.ai, Desktop, mobil): Settings, Connectors, Add custom connector. Numește-l scrape.land, setează URL-ul la https://scrape.land/mcp și adaugă antetul de cerere Authorization cu valoarea Bearer YOUR_KEY.

Claude Code:

claude mcp add --transport http scrape-land https://scrape.land/mcp --header "Authorization: Bearer YOUR_KEY"

Cursor, configurația Claude Desktop, Cline și alți clienți care folosesc o configurație JSON mcpServers:

{
  "mcpServers": {
    "scrape-land": {
      "type": "http",
      "url": "https://scrape.land/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

VS Code (GitHub Copilot), în .vscode/mcp.json; VS Code cere cheia o singură dată și o păstrează în siguranță:

{
  "inputs": [
    { "type": "promptString", "id": "scrapeland-key", "description": "scrape.land API key", "password": true }
  ],
  "servers": {
    "scrape-land": {
      "type": "http",
      "url": "https://scrape.land/mcp",
      "headers": { "Authorization": "Bearer ${input:scrapeland-key}" }
    }
  }
}

Gemini CLI, în ~/.gemini/settings.json:

{
  "mcpServers": {
    "scrape-land": {
      "httpUrl": "https://scrape.land/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

Windsurf, în mcp_config.json:

{
  "mcpServers": {
    "scrape-land": {
      "serverUrl": "https://scrape.land/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

Zed, în settings.json:

{
  "context_servers": {
    "scrape-land": {
      "url": "https://scrape.land/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

Agentul tău, OpenAI Agents SDK (Python):

from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

async with MCPServerStreamableHttp(params={
    "url": "https://scrape.land/mcp",
    "headers": {"Authorization": "Bearer YOUR_KEY"},
}) as scrapeland:
    agent = Agent(name="researcher", mcp_servers=[scrapeland])
    result = await Runner.run(agent, "Find three dentists in Austin without a website")
    print(result.final_output)

Agentul tău, Claude API (MCP connector, antet beta anthropic-beta: mcp-client-2025-11-20):

{
  "model": "claude-sonnet-5",
  "max_tokens": 2048,
  "messages": [{ "role": "user", "content": "Summarise https://example.com" }],
  "mcp_servers": [{
    "type": "url",
    "url": "https://scrape.land/mcp",
    "name": "scrape-land",
    "authorization_token": "YOUR_KEY"
  }],
  "tools": [{ "type": "mcp_toolset", "mcp_server_name": "scrape-land" }]
}

SDK Python și migrarea de la Zyte

Clientul nostru Python înglobează atât API-ul de extragere, cât și tunelul brut, și include un adaptor drop-in pentru clientul Zyte API. Ești deja pe Zyte? Schimbă importul și cheia: formele cererii și ale răspunsului se potrivesc.

pip install scrapeland
from scrapeland import ScrapelandClient

client = ScrapelandClient("pb_live_YOURKEY")          # or $SCRAPELAND_API_KEY

# structured extraction:
res = client.extract("https://example-product.com/item/42", {"title": "h1"})

# raw tunnel fetch through a fresh exit IP:
r = client.get("https://api.ipify.org", country="us", session="job42")

# or hand the tunnel mapping to your existing requests/httpx code:
import requests
requests.get("https://api.ipify.org", proxies=client.proxies(country="de"))
Drop-in pentru Zyte: from scrapeland.zyte import ZyteAPI. url, httpResponseBody (base64), httpResponseHeaders, customHttpRequestHeaders, geolocation→țara de ieșire, sessionContext→sesiune persistentă și iter()/AsyncZyteAPI se mapează toate. browserHtml/screenshot/auto-extragerea nu sunt disponibile prin tunelul brut: folosește în schimb API-ul de extragere cu render.

Tunel brut (avansat)

Majoritatea oamenilor vor date structurate și ar trebui să folosească API-ul de extragere de mai sus. Dacă în schimb ai nevoie chiar tu de acces HTTP brut la pagină, tunelul brut este un forward proxy: îndreaptă orice client HTTP spre el cu cheia ta API pe post de nume de utilizator, și fiecare cerere iese dintr-un IP nou, cu controale opționale de țară, sesiune și protocol. Gazdă: gateway.scrape.land:8080.

curl -x http://pb_live_YOURKEY:@gateway.scrape.land:8080 https://api.ipify.org
# -> an exit IP, different on each request
Port blocat? Același proxy răspunde și pe gateway.scrape.land:443, pe care rețelele corporative, universitare și de hotel îl filtrează rar. Comportament și facturare identice (diferă doar portul). Păstrează schema http://: cheia ta călătorește tot în numele de utilizator, exact ca pe 8080.
Fiecare cerere se rotește spre un IP de ieșire nou dacă nu setezi o sesiune persistentă. Browserele nu pot seta un proxy per cerere, deci apelează tunelul dintr-un backend.
Cum se facturează tunelul. O unitate de cerere per tunel CONNECT, adică ce deschide clientul tău pentru fiecare țintă https://, și una per cerere pentru țintele http:// simple. Un tunel care nu transmite nimic de la țintă nu se facturează. Un browser (Playwright, Puppeteer) deschide un tunel separat pentru fiecare host de la care se încarcă pagina, adesea mai multe per host, așa că o încărcare de pagină prin tunel înseamnă de obicei mai multe unități. Pentru pagini randate întregi, render din API-ul de extragere se facturează fix cu 5 unități (1 cu block_resources).

Parametri de control

Adaugă perechi -nume-valoare la cheia din numele de utilizator, sau trimite anteturi de control X-Proxy-*. Te conectezi mereu la gateway ca la un proxy HTTP (schema http://); protocol alege ce tip de proxy de ieșire duce cererea mai departe: http, https sau socks5.

ScopForma din numele de utilizatorAntet
Țara de ieșireKEY-country-usX-Proxy-Country: us
Sesiune persistentă (același IP)KEY-session-abc123X-Proxy-Session: abc123
ProtocolKEY-protocol-socks5X-Proxy-Protocol: socks5
Anonimat (premium)KEY-anonymity-eliteX-Proxy-Anonymity: elite
Latență maximă (ms)KEY-maxlatency-3000X-Proxy-Max-Latency: 3000

Generator de URL pentru tunel

Alege-ți opțiunile și copiază comanda gata de rulat. Înlocuiește YOURKEY cu o cheie din panoul tău.

Folosirea tunelului din cod

import requests

KEY = "pb_live_YOURKEY"
proxy = f"http://{KEY}-country-us:@gateway.scrape.land:8080"
r = requests.get("https://api.ipify.org?format=json",
                 proxies={"http": proxy, "https": proxy}, timeout=30)
print(r.json())   # {'ip': '...'}  a US exit IP
import { ProxyAgent, fetch } from "undici";   // npm i undici

const KEY = "pb_live_YOURKEY";
// The key is the proxy username and the password is empty. undici only builds
// Proxy-Authorization from a URL that has both, so pass it as a token.
const dispatcher = new ProxyAgent({
  uri: "http://gateway.scrape.land:8080",
  token: "Basic " + Buffer.from(`${KEY}-country-gb:`).toString("base64"),
});
// Node's fetch ignores an `agent` option: the tunnel goes in `dispatcher`.
const res = await fetch("https://api.ipify.org?format=json", { dispatcher });
console.log(await res.json());   // { ip: '...' }  a GB exit IP
p, _ := url.Parse("http://pb_live_YOURKEY-country-us:@gateway.scrape.land:8080")
client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(p)}}
resp, _ := client.Get("https://api.ipify.org")
import { chromium } from "playwright";

const browser = await chromium.launch({
  proxy: {
    server: "http://gateway.scrape.land:8080",
    username: "pb_live_YOURKEY-country-us",
    password: "",
  },
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org");
# settings.py
DOWNLOADER_MIDDLEWARES = {
    "scrapeland.scrapy.ScrapelandMiddleware": 740,
}
SCRAPELAND_API_KEY = "pb_live_YOURKEY"
SCRAPELAND_COUNTRY = "us"            # optional default for every request

# per-request override:
yield scrapy.Request(url, meta={"scrapeland": {"country": "de", "session": "job42"}})

Erori

StatusSemnificație
401Cheie API lipsă sau invalidă pe API-ul de extragere. Setează X-Api-Key.
407Cheie API lipsă sau invalidă pe tunelul brut. Setează-o ca nume de utilizator al tunelului.
402Cotă epuizată. Reîncarcă-ți soldul sau treci pe un plan superior.
429Rate limit-ul planului a fost atins: limita de cereri pe secundă a planului tău (vezi Limitele planului) este folosită integral, la fel pentru ținte http:// și https://. code-ul din JSON este rate-limited, iar un antet Retry-After spune când să reîncerci. Nu se facturează.
403Destinație nepermisă, cod destination-blocked: adresele de loopback, private, link-local (inclusiv metadata cloud) și alte adrese interne sunt refuzate pe toate endpoint-urile și pe tunel, atât pentru http cât și pentru https, și nu se facturează. De asemenea: extracție AI sau randare în browser cerute pe un plan care nu le include. Câmpul code spune care.
413Corpul cererii, sau răspunsul, depășește limita de dimensiune a planului tău. Mesajul numește dimensiunea, limita și planul; vezi limits.max_response_bytes.
502Niciun upstream funcțional nu s-a potrivit cu filtrele tale (încearcă mai puține constrângeri).
504Cererea a depășit bugetul de timp (45 s pentru o cerere sincronă, 150 s când include extragere cu AI). Reîncearcă, simplific-o (mai puține redirecționări sau actions, "model": "fast") sau trimite-o la POST /v1/jobs, care are 5 minute.
503Suntem la capacitate pentru moment (cel mai des toate sloturile de browser sunt ocupate cu un render). Reîncearcă după antetul Retry-After, nu este o cerere eșuată și nu se facturează.
Orice refuz cauzat de planul tău răspunde la fel, ca să scrii o singură ramură în loc să potrivești propoziții în engleză. Pe API-ul de extragere, corpul JSON poartă un code stabil, planul pe care ești și ce ai de făcut:
{
  "error": "AI extraction (prompt, schema, extract_type) is not included on the \"free\" plan (it needs \"scale\" or above). Upgrade on the Plans page of your dashboard, or use CSS/XPath \"fields\" extraction, which every plan includes.",
  "code": "plan-upgrade-required",
  "plan": "free",
  "required_plan": "scale",
  "fallback": "use CSS/XPath \"fields\" extraction, which every plan includes",
  "capability": "AI extraction (prompt, schema, extract_type)"
}
Codurile sunt plan-upgrade-required (403), quota-exhausted (402), key-budget-exhausted (402), rate-limited (429), response-too-large (413) și email-unverified (403: nicio cerere nu este servită până la confirmarea adresei de email a contului, indiferent de plan). required_plan apare doar când o achiziție chiar ridică limita: plafonul de la format=raw, de pildă, este același pe orice plan, deci acolo este omis, în loc să fluture un upgrade care nu schimbă nimic. fallback este mereu prezent. Tunelul brut este un proxy HTTP și răspunde în text/plain (un corp JSON ar ajunge în interiorul tunelului tău), deci poartă același cod în antetul de răspuns X-Scrapeland-Error. Același vocabular pe ambele suprafețe: entitlements.refusal_codes din GET /v1/capabilities le enumeră pe cele legate de plan (toate codurile de mai sus, în afară de email-unverified).

Statusurile site-ului țintă (200/403/404 ale site-ului însuși) sunt transmise neschimbate. Țara de ieșire nu este un antet al răspunsului API-ului însuși: trimite "headers": true și o găsești ca X-Proxy-Country în obiectul headers. Pe tunelul brut, X-Proxy-Country apare pe răspunsul la CONNECT pentru ținte https:// și pe răspunsul propriu-zis pentru ținte http://.

Limitele planului

Patru lucruri se schimbă odată cu planul: cât de repede ai voie să trimiți cereri, cât de mare poate fi un singur răspuns, dacă extragerea cu AI este disponibilă și dacă primești ieșiri prioritare. Se aplică identic pe API-ul de extragere și pe tunelul brut. GET /v1/capabilities cu cheia ta returnează drepturile tale sub entitlements, iar GET /v1/account cifrele tale.

Ieșirile prioritare sunt o diferență de rutare, nu o coadă, nu există niciun rând în fața căruia să stai. Pe un plan plătit, cererii tale i se oferă întâi ieșirile ISP/datacenter premium, apoi o rezervă mai adâncă de proxy-uri din pool verificate constant, iar la final o ultimă încercare de livrare, ca o cerere plătită să nu eșueze din lipsă de ieșire. Pe planul Free fiecare cerere este servită dintr-un pool public cu latență mare, exact la ce e bun nivelul gratuit: prototipare și testare. Aceeași rotație, țintire geografică, sesiuni și randare, pe adrese care sunt mai lente și mai des blocate de țintele dificile. Măsurat pe traficul nostru din producție, pool-ul gratuit a avut o mediană de 1.015 ms față de 453 ms la premium, și o coadă lungă (p99 32 s față de 4,5 s) pe care un pool public o va avea mereu. Sunt observații asupra unui pool volatil, al unor terți, nu un nivel de serviciu la care îl obligăm.
PlanIncluse / lunăRate limitRăspuns maximIeșiri prioritareExtragere cu AI
Free1.0005 cereri/s2 MBnu (pool cu latență mare)nu
Pool ($19)necontorizat10 cereri/s2 MBnu (ieșiri fără prioritate)nu
Starter ($99)600.00050 cereri/s2 MBdanu
Growth ($249)1.600.000100 cereri/s5 MBdanu
Scale ($499)3.400.000200 cereri/s10 MBdada
Business ($999)7.200.0001.000 cereri/s25 MBdada
Business XL ($1,999)15.000.0001.000 cereri/s25 MBdada
Business XXL ($3,499)30.000.0001.000 cereri/s25 MBdada
Business Ultra ($9,999)100.000.0001.000 cereri/s25 MBdada
Pay as you goVine în 2027. Nu se vinde ca plan astăzi: începe pe Free sau alege un abonament. Reîncărcările preplătite sunt deja disponibile pe orice plan.

Pool costă $19/lună ($190/an) și nu este contorizat: nu are un număr lunar de cereri, iar rate limit-ul de 10 cereri/s este singurul plafon. Rulează pe ieșiri fără prioritate, nu include extragere cu AI și nici randare în browser, dar include instrumentele de generare de lead-uri (POST /v1/places).

Rate limit. Cereri pe secundă susținute, aplicate per cont pe toate cheile pe care le deții (o limită per cheie n-ar fi deloc o limită: cheile se creează gratis); o cheie individuală poate fi limitată și mai jos. Rafalele scurte peste limită sunt tolerate. Peste ea primești un 429 cu un antet Retry-After: încetinește și continuă, nu se pierde nimic și nu se facturează nimic. Rata fiecărui plan este dimensionată astfel încât o rulare la maximum pe toată alocația lunară să dureze ore, nu minute.

Răspuns maxim. Cel mai mare răspuns unic pe care îl returnăm, expus ca limits.max_response_bytes în GET /v1/capabilities. Paginile reale sunt departe de această valoare (răspunsul median pe care îl servim are cam 40 KB, iar 99% sunt sub 1,2 MB) deci prinde doar un video, o arhivă sau un export de date. Orice depășește este refuzat în întregime, cu un 413 care numește dimensiunea, limita și planul tău. Nu returnăm niciodată o pagină trunchiată, iar un răspuns refuzat nu se facturează.

Facturarea nu depășește niciodată ce ai plătit. Cererile incluse se consumă primele. După ele, cererile trag din creditul preplătit, la $0,20 / 1.000: soldul scade și se oprește la zero, moment în care cererile returnează 402 în loc să continue. Nu există factură ulterioară și nicio cale de a acumula o cheltuială pe care nu ai autorizat-o dinainte.

Ghiduri

Sesiuni rotative vs. persistente

Implicit, fiecare cerere iese dintr-un IP nou (excelent pentru a distribui încărcarea și a evita rate limit-urile). Când ai nevoie de același IP între cereri (autentificări, coșuri, fluxuri în mai mulți pași) adaugă un parametru session (sau -session-NUME pe tunel); fixăm acea sesiune pe un singur IP de ieșire pentru vreo 10 minute, apoi se rotește. Folosește un nume de sesiune unic pentru fiecare utilizator logic.

Gestionarea rate limit-urilor și a erorilor

import requests, time

KEY = "pb_live_YOURKEY"
def get(url, tries=4):
    for i in range(tries):
        proxy = f"http://{KEY}:@gateway.scrape.land:8080"   # fresh IP each try
        try:
            r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
            if r.status_code < 400:
                return r
        except requests.RequestException:
            pass
        time.sleep(2 ** i)        # exponential backoff
    raise RuntimeError("all retries failed")

Întrebări frecvente

Există un plan gratuit?

Da: 1.000 de cereri, fără card. Cel mai ieftin plan plătit este Pool, la $19/lună (fără contorizare, la 10 cereri/s, fără extragere cu AI și fără randare). Planurile contorizate încep de la $99/lună (600k cereri = $0,165 / 1k), iar tariful se îmbunătățește pe măsură ce crește nivelul: $0,139 / 1k pe Business ($999 / 7,2M) și $0,100 / 1k pe Business Ultra ($9.999 / 100M). Vezi Limitele planului pentru scara completă.

Cum sunt facturat?

Per 1.000 de cereri livrate. Dacă un proxy este blocat sau returnează 403/429, reîncercăm cu alt IP și nu îl facturăm niciodată. Plătești doar când pagina revine.

Cererile incluse în planul tău se consumă primele. După ele, cererile ies din creditul preplătit, la $0,20 / 1.000: reîncarci în avans, soldul scade și se oprește la zero. Nu există factură ulterioară și nicio cale de a acumula o notă pe care nu ai autorizat-o. Fără abonament, creditul preplătit costă tot $0,20 / 1.000. Pay-as-you-go ca plan separat vine în 2027; astăzi nu se vinde.

Pot alege o anumită țară?

Da. Pe API-ul de extragere adaugă "country": "us" în corpul cererii. Pe tunelul brut adaugă -country-us (orice cod ISO) la cheia ta. Vezi generatorul de URL pentru tunel.

Cum extrag pagini care au nevoie de JavaScript?

Adaugă "render": true la cererea ta de extragere și "wait_for" cu un selector CSS dacă tot conținutul se încarcă târziu. Vezi render și wait_for.

Oferiți IP-uri rezidențiale?

Astăzi pool-ul este format din IP-uri rotative de tip datacenter/ISP, autovalidate continuu. Pool-urile rezidențiale/mobile sunt pe foaia de parcurs (planuri premium).

Acestea sunt instrumente standard; tu ești responsabil să le folosești în mod legal și să respecți termenii site-urilor de destinație. Vezi Termenii noștri.