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_YOURKEYMerge și un antet bearer Authorization, dacă se potrivește mai bine cu stack-ul tău:
Authorization: Bearer pb_live_YOURKEYPOST/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.
urlstringobligatoriuPagina de descărcat: un URL absolut http sau https.
fieldsobjectNume 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.
promptstringDescrie câmpurile în cuvinte, în loc să scrii selectori. Planul Scale și peste. Vezi Extragere cu AI.
schemaobjectCu prompt: un obiect JSON cheie → tip care fixează forma lui data.
extract_typestringAuto-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 trueCu prompt: false întoarce un răspuns în limbaj natural în answer, în loc de obiectul data.
groupbooleanimplicit falseCombină câmpurile de tip listă într-un obiect pe rând, în items.
Plus toate opțiunile de 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"];{
"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:
- Un selector CSS ca șir. Returnăm textul primului element care se potrivește.
"title": "h1"returnează textul din primulh1. - O prescurtare selector@attr pentru a citi un atribut.
"image": "img.gallery@src"returneazăsrc; folosește@textsau niciun parametru pentru text. - O prescurtare XPath: un șir al cărui prim caracter este
/sau(este tratat ca XPath, orice altceva ca CSS."title": "//h1/text()","link": "//a/@href","first": "(//p)[1]". - Un obiect cu
cssplus chei opționale:attr(citește un atribut în loc de text),all(returnează fiecare potrivire ca listă) sauxpath(o expresie XPath în loc decss, pentru câmpurile de nivel superior). Se pot combina. - Un grup: un obiect cu
cssși propriilefields.cssalege containerul care se repetă, iar câmpurile interioare sunt citite în fiecare container, deci primești o listă de obiecte în loc de tablouri paralele care se pot desincroniza. Folosește"."pentru containerul însuși."articles": {"css": ".card", "fields": {"title": "h3", "url": {"css": "a", "attr": "href"}}}întoarce[{"title": ..., "url": ...}, ...], iar un câmp lipsă dintr-un container estenullpe acel rând, fără să deplaseze rândurile următoare.
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âmp | Selector / obiect | Valoare 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.
- 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 precumsvg|circle. Tot ce ține de CSS3 funcționează: combinatori de descendent/copil/frate,:nth-child(),:not(), selectori de atribut inclusiv^= $= *=și indicatoruli. - 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.
Selector Ce potrivește Exemplu :contains(…)element al cărui text conține un subșir h1: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ții h1:matchesOwn(^Example):haschild(…)element cu un copil direct care se potrivește cu un selector p: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 literalh1,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.
countrystringȚara de ieșire, cod ISO, de ex. us. Cererea pleacă din acea țară.
sessionstringNumele unei sesiuni persistente. Reutilizează același IP de ieșire între apeluri (autentificări, coșuri).
protocolstringProtocolul de ieșire: http, https sau socks5, adică protocolul pe care îl vorbește proxy-ul de ieșire. Omite-l pentru oricare.
max_latencyintegerFolosește doar ieșiri care au răspuns mai repede de atâtea milisecunde.
renderbooleanimplicit falsetrue 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 falsetrue pentru a include anteturile de răspuns în rezultat.
send_headersobjectUn obiect cu anteturi de cerere suplimentare, trimise mai departe către țintă (User-Agent propriu, Authorization, …).
cookiesstringValoarea 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 falsetrue 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 falsetrue returnează în plus o listă links: fiecare a href rezolvat la URL absolut, deduplicat, doar http(s). Util pentru parcurgere și descoperire.
screenshotbooleanimplicit falsePentru /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"}
}'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)
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.
<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.
urlstringobligatoriuPagina 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.
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"}'{
"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.
urlsarrayobligatoriuPână la 20 de URL-uri absolute. Rezultatele vin în aceeași ordine.
fields / promptobject / stringOpț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.
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"}'{
"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.
qstringobligatoriuInterogarea de căutare.
countintegerimplicit 10Numărul de rezultate, de la 1 la 10.
Plus opțiunile de proxy country, session, protocol și max_latency.
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}'{
"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.
urlstringobligatoriuPagina de descărcat: un URL absolut http sau https.
querystringobligatoriuLa ce trebuie să fie relevante linkurile.
top_kintegerCâte linkuri ordonate să întoarcă, maximum 50.
modelstringimplicit "fast"Nivelul AI: fast, smart sau max.
Plus toate opțiunile de 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}'{
"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.
typestringobligatoriuOperația de rulat: fetch, extract, batch sau search.
webhook_urlstringUn URL https public la care trimitem rezultatul prin POST când jobul se termină.
Plus parametrii acelei operații, exact ca pe endpointul ei.
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.
categorystringobligatoriuUn 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.
locationstringUn 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 200Numă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 0De 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.
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"}'{
"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.
{
"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.
{
"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.
| Tool | Does | Same as |
|---|---|---|
fetch_page | Fetch a page as markdown (default), text or HTML through our rotating tunneling network. Arguments: url, format, render, country. | /v1/fetch |
extract_data | Structured JSON from a page, by CSS fields or a plain-English prompt (with optional model: fast, smart or max). | /v1/extract |
web_search | Organic web results for q (up to 10). | /v1/search |
find_local_businesses | Businesses 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.
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 scrapelandfrom 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"))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 requestgateway.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.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.
| Scop | Forma din numele de utilizator | Antet |
|---|---|---|
| Țara de ieșire | KEY-country-us | X-Proxy-Country: us |
| Sesiune persistentă (același IP) | KEY-session-abc123 | X-Proxy-Session: abc123 |
| Protocol | KEY-protocol-socks5 | X-Proxy-Protocol: socks5 |
| Anonimat (premium) | KEY-anonymity-elite | X-Proxy-Anonymity: elite |
| Latență maximă (ms) | KEY-maxlatency-3000 | X-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 IPimport { 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 IPp, _ := 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
| Status | Semnificație |
|---|---|
401 | Cheie API lipsă sau invalidă pe API-ul de extragere. Setează X-Api-Key. |
407 | Cheie API lipsă sau invalidă pe tunelul brut. Setează-o ca nume de utilizator al tunelului. |
402 | Cotă epuizată. Reîncarcă-ți soldul sau treci pe un plan superior. |
429 | Rate 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ă. |
403 | Destinaț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. |
413 | Corpul 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. |
502 | Niciun upstream funcțional nu s-a potrivit cu filtrele tale (încearcă mai puține constrângeri). |
504 | Cererea 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. |
503 | Suntem 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ă. |
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)"
}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.
| Plan | Incluse / lună | Rate limit | Răspuns maxim | Ieșiri prioritare | Extragere cu AI |
|---|---|---|---|---|---|
| Free | 1.000 | 5 cereri/s | 2 MB | nu (pool cu latență mare) | nu |
| Pool ($19) | necontorizat | 10 cereri/s | 2 MB | nu (ieșiri fără prioritate) | nu |
| Starter ($99) | 600.000 | 50 cereri/s | 2 MB | da | nu |
| Growth ($249) | 1.600.000 | 100 cereri/s | 5 MB | da | nu |
| Scale ($499) | 3.400.000 | 200 cereri/s | 10 MB | da | da |
| Business ($999) | 7.200.000 | 1.000 cereri/s | 25 MB | da | da |
| Business XL ($1,999) | 15.000.000 | 1.000 cereri/s | 25 MB | da | da |
| Business XXL ($3,499) | 30.000.000 | 1.000 cereri/s | 25 MB | da | da |
| Business Ultra ($9,999) | 100.000.000 | 1.000 cereri/s | 25 MB | da | da |
| Pay as you go | Vine î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
- Ținta returnează 429/403: reîncearcă. O cerere nouă se rotește spre un IP nou. Adaugă un mic backoff.
- 402 de la noi: cota sau soldul tău este epuizat. Treci pe un plan superior sau reîncarcă.
- 401/407 de la noi: cheia API lipsește sau este greșită (antet pentru API, nume de utilizator pentru tunel).
- 502 de la noi: niciun upstream nu s-a potrivit cu filtrele tale. Relaxează
country/maxlatencyși reîncearcă.
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).
Este legal?
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.