Risposta rapida
Come faccio il crawling di un sito web in Markdown con WebReaderAPI?
Invia l'URL di partenza a POST https://webreaderapi.com/api/v1/crawl con una pagina limit. WebReaderAPI legge il sito in background, solo nello stesso dominio, seguendo la sua sitemap, robots.txt e Crawl-delay, fino a 10.000 pagine per job. Raccogli le pagine Markdown dal job, una pagina di risultati alla volta. Ogni pagina letta costa 1 credito (2 con un browser), da 0,07 USD/1K pagine a partire da ottobre 2026. Senza KYC; paghi in criptovalute.
I job in breve
| Batch | Fino a 100 URL per job batch: POST https://webreaderapi.com/api/v1/batch |
|---|---|
| Crawl | Crawling di un sito web per job, fino a 10.000 pagine, seguendo la sua sitemap, il suo robots.txt e il suo Crawl-delay: POST https://webreaderapi.com/api/v1/crawl |
| Risultati | GET https://webreaderapi.com/api/v1/jobs/{job_id}, poi ?cursor=… per la pagina di risultati successiva |
| Fatturazione | Ogni pagina letta viene fatturata come una chiamata /read; le pagine saltate da robots.txt e le pagine fallite non costano nulla |
| Ambito di un crawl | Un dominio; i link ad altri domini vengono elencati ma non letti |
| Annullare un job | Non disponibile nella v1: imposta limit sul numero massimo di pagine che sei pronto a pagare |
Avviare un job batch
POST/api/v1/batch
Legge fino a 100 URL in background con le stesse opzioni. Le opzioni usano i nomi e i valori di /read. La risposta è il job ID; i valori sono illustrativi.
Autenticazione: chiave API nell'header Authorization: Bearer API_KEY.
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
urls | array of strings | Sì | Da 1 a 100 URL http o https. I duplicati vengono letti una volta sola. |
options | object | No | Qualsiasi parametro /read tranne url: format, main_content, render, include_links, include_images, country, max_chars, timeout. |
{
"urls": [
"https://example.com/blog/post-1",
"https://example.com/blog/post-2",
"https://example.org/news/launch"
],
"options": {
"format": "markdown",
"max_chars": 50000
}
}Risposta 202 Accepted
{
"job_id": "job_5c1e8a90d2"
}Esempi di codice
curl -s "https://webreaderapi.com/api/v1/batch" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}}'import os
import requests
resp = requests.post(
"https://webreaderapi.com/api/v1/batch",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
json={"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}},
timeout=30,
)
print(resp.json()["job_id"])const res = await fetch('https://webreaderapi.com/api/v1/batch', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ urls: ['https://example.com/blog/post-1', 'https://example.com/blog/post-2'], options: { format: 'markdown' } }),
});
console.log((await res.json()).job_id);Avvia un job di crawling
POST/api/v1/crawl
Legge un sito web da un URL di partenza, in ampiezza. Il crawler resta sul dominio dell'URL di partenza, usa la sitemap indicata in robots.txt o trovata su /sitemap.xml, e rispetta robots.txt e Crawl-delay. I valori sono a scopo illustrativo.
Autenticazione: chiave API nell'header Authorization: Bearer API_KEY.
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
url | string | Sì | La pagina di partenza, ad esempio la home page di un sito di documentazione. |
limit | integer | No | Numero massimo di pagine da leggere, fino a 10.000. Il job si ferma quando raggiunge questo numero. |
max_depth | integer | No | Numero massimo di passaggi di link dall'URL di partenza. |
include_paths | array of strings | No | Semplici pattern di percorso da mantenere, ad esempio /docs/*. |
exclude_paths | array of strings | No | Semplici pattern di percorso da saltare, ad esempio /blog/tag/*. |
options | object | No | Qualsiasi parametro /read tranne url, applicato a ogni pagina. |
{
"url": "https://docs.example.com/",
"limit": 500,
"max_depth": 3,
"include_paths": [
"/docs/*"
],
"exclude_paths": [
"/docs/archive/*"
],
"options": {
"format": "markdown"
}
}Risposta 202 Accepted
{
"job_id": "job_8b27f41c0e"
}Esempi di codice
curl -s "https://webreaderapi.com/api/v1/crawl" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]}'import os
import requests
resp = requests.post(
"https://webreaderapi.com/api/v1/crawl",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
json={"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]},
timeout=30,
)
print(resp.json()["job_id"])const res = await fetch('https://webreaderapi.com/api/v1/crawl', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'https://docs.example.com/', limit: 500, include_paths: ['/docs/*'] }),
});
console.log((await res.json()).job_id);Ottieni lo stato e i risultati del job
GET/api/v1/jobs/{job_id}
Restituisce lo stato del job e i risultati successivi pronti. Ogni elemento di results è una risposta /read completa oppure {url, error} per una pagina che non è stato possibile leggere. Quando next_cursor è presente, richiama con cursor impostato a quel valore; quando è assente, non ci sono ancora altri risultati pronti. I valori sono a scopo illustrativo.
Autenticazione: chiave API nell'header Authorization: Bearer API_KEY.
Parametri
| Nome | In | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
job_id | path | string | Sì | L'ID restituito da /batch o /crawl. |
cursor | query | string | No | Il valore next_cursor della chiamata precedente, per ottenere i risultati successivi. |
Risposta 200
{
"job_id": "job_8b27f41c0e",
"status": "running",
"results": [
{
"url": "https://docs.example.com/docs/install",
"final_url": "https://docs.example.com/docs/install",
"status_code": 200,
"rendered": false,
"credits_used": 1,
"title": "Install",
"markdown": "# Install\n\nRun the installer...",
"word_count": 418
},
{
"url": "https://docs.example.com/docs/private",
"error": {
"code": "blocked_by_robots",
"message": "robots.txt disallows this page for WebReaderAPI."
}
}
],
"next_cursor": "c_000002",
"credits_used": 1
}Esempi di codice
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-d cursor=c_000002import os
import requests
resp = requests.get(
"https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
params={"cursor": "c_000002"},
timeout=30,
)
job = resp.json()
print(job["status"], len(job["results"]), job.get("next_cursor"))const res = await fetch('https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e?cursor=c_000002', {
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
});
const job = await res.json();
console.log(job.status, job.results.length, job.next_cursor);Come faccio ad aspettare che un job finisca?
Fai polling su /jobs/{job_id} e conserva il cursore. Elabora i risultati che ricevi, poi richiama con cursor impostato a next_cursor. Quando next_cursor è assente e status è done, hai tutto; quando è assente e il job è ancora running, aspetta e richiedi di nuovo con lo stesso cursore.
Inizia a fare polling ogni 2 secondi e raddoppia l'attesa fino a 30 secondi finché non arriva nulla di nuovo. Un crawling che rispetta il Crawl-delay di un sito può richiedere un po', quindi un'attesa lunga è normale e non costa nulla.
import os
import pathlib
import re
import time
import requests
JOBS = "https://webreaderapi.com/api/v1/jobs"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}
def save_job(job_id, folder="pages"):
out = pathlib.Path(folder)
out.mkdir(exist_ok=True)
cursor, wait, saved = None, 2, 0
while True:
params = {"cursor": cursor} if cursor else {}
job = requests.get(f"{JOBS}/{job_id}", headers=HEADERS, params=params, timeout=30).json()
for item in job["results"]:
if "error" in item:
print("skipped", item["url"], item["error"]["code"])
continue
name = re.sub(r"[^a-z0-9]+", "-", item["final_url"].lower()).strip("-")[:120]
(out / f"{name}.md").write_text(item["markdown"], encoding="utf-8")
saved += 1
if job.get("next_cursor"):
cursor, wait = job["next_cursor"], 2
continue
if job["status"] == "done":
return saved
time.sleep(wait)
wait = min(wait * 2, 30)
if __name__ == "__main__":
print(save_job("job_8b27f41c0e"), "pages saved")
Quali pagine legge un crawling?
Un crawling parte dal tuo URL e si espande verso l'esterno un passaggio di link alla volta, in ampiezza, fino a max_depth. Legge anche la sitemap del sito, dall'indirizzo indicato in robots.txt o da /sitemap.xml, per trovare pagine verso cui puntano pochi link.
- Solo lo stesso dominio. I link ad altri domini sono elencati nei risultati quando richiedi
include_links, ma non vengono letti. - Filtri di percorso.
include_pathseexclude_pathsaccettano semplici pattern come/blog/*. - Una copia per pagina. Quando un sito dichiara un URL canonico, una pagina raggiunta tramite più indirizzi viene letta una volta sola.
- Un limite invalicabile. Il job termina quando ha letto
limitpagine.
Come rispetta il sito un crawling?
Ogni richiesta porta lo User-Agent WebReaderAPI/1.0 (+https://webreaderapi.com/bot/), e il crawler segue le regole di robots.txt per WebReaderAPI, o per tutti i robot quando nessuno lo nomina. Le pagine non consentite vengono saltate e compaiono in results con l'errore blocked_by_robots; non costano nulla.
Quando robots.txt imposta un Crawl-delay, il crawler attende quel tempo tra due richieste al sito. Se un sito dichiara Content-Signal: ai-input=no nel suo robots.txt, non leggiamo le sue pagine per te. Se un sito rifiuta la nostra richiesta, restituiamo la sua risposta: non risolviamo mai CAPTCHA, non accediamo agli account e non tentiamo di aggirare un blocco o un paywall.
Quanto costa un crawling?
Ogni pagina letta costa quanto una chiamata /read: 1 credito, 2 se la pagina ha richiesto un browser, più 10 con country. Le pagine saltate e quelle non riuscite sono gratuite. Il credits_used del job somma quanto è stato addebitato finora.
Un sito di documentazione di 500 pagine letto senza browser usa circa 500 crediti, che il pack 10.000 pagine (3,75 USD) copre molte volte. Poiché in v1 un job non può essere annullato, limit è il tuo tetto di spesa: impostalo al numero massimo di pagine che sei disposto a pagare.
Batch e crawling: domande frequenti
Devo usare /batch o molte chiamate /read?
Usa /batch quando hai già gli URL e non ti serve ogni risposta subito. Ti evita di gestire concorrenza e retry. Usa /read quando un agente ha bisogno di una pagina adesso.
Per quanto tempo vengono conservati i risultati di un job?
Raccoglili appena sono pronti. I risultati restano in attesa finché non li recuperi; una volta consegnati, non viene conservata alcuna copia del contenuto della pagina, quindi salva sul tuo lato ciò che ti serve.
Un crawling può seguire link verso altri siti?
No. Un crawling legge un solo dominio. Gli altri domini compaiono come link quando li richiedi, e puoi inviare quegli URL a /batch se vuoi che vengano letti.
Posso fermare un crawling già avviato?
Non in v1. Il job si ferma a limit pagine, quindi scegli un limite adeguato al tuo budget. Le pagine già lette restano fatturate; le pagine mai lette non costano nulla.