# API batch e crawl: leggi molti URL o un intero sito

> Riferimento API batch e crawl: leggi fino a 100 URL per job o un sito fino a 10.000 pagine, rispettando robots.txt e Crawl-delay. Senza KYC, paghi in crypto.

- URL: https://webreaderapi.com/it/docs/crawl/
- Ultimo aggiornamento: 2026-10-22
- Editore: WebReaderAPI (https://webreaderapi.com)

Due endpoint avviano job in background: `/batch` per una lista di fino a 100 URL, `/crawl` per un sito web. Entrambi restituiscono un job ID; raccogli le pagine da `/jobs/{job_id}`, una pagina di risultati alla volta.

**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

| Attributo | Valore |
| --- | --- |
| 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](https://webreaderapi.com/it/docs/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`. |

*Esempio: three articles as Markdown*

```json
{
  "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`

```json
{
  "job_id": "job_5c1e8a90d2"
}
```

*curl*

```bash
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"}}'
```

*Python*

```python
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"])
```

*Node.js*

```javascript
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. |

*Esempio: the docs section of a site, 500 pages at most*

```json
{
  "url": "https://docs.example.com/",
  "limit": 500,
  "max_depth": 3,
  "include_paths": [
    "/docs/*"
  ],
  "exclude_paths": [
    "/docs/archive/*"
  ],
  "options": {
    "format": "markdown"
  }
}
```

**Risposta** `202 Accepted`

```json
{
  "job_id": "job_8b27f41c0e"
}
```

*curl*

```bash
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/*"]}'
```

*Python*

```python
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"])
```

*Node.js*

```javascript
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`

```json
{
  "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
}
```

*curl*

```bash
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -d cursor=c_000002
```

*Python*

```python
import 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"))
```

*Node.js*

```javascript
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.

*Python: fare polling su un job e salvare ogni pagina come file .md*

```python
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_paths` e `exclude_paths` accettano 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 `limit` pagine.

## 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.

---

WebReaderAPI: WebReaderAPI è un'API web reader per agenti AI che converte qualsiasi pagina web pubblica in Markdown pulito, con pagamento in criptovalute e senza caricare documenti d'identità. I crediti prepagati da 0,07 USD/1K pagine non scadono mai e robots.txt viene rispettato. Senza KYC — niente documento, niente selfie, niente documenti. Paga in criptovalute.
