# Web page to Markdown in Python

> Converti pagine web in Markdown in Python con requests e httpx async, job batch e suddivisione RAG per heading, da 0,07 USD/1K pagine senza KYC.

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

Quattro ricette che coprono la maggior parte dei progetti Python: leggi una pagina con requests, leggi molte pagine in parallelo con httpx, passa una lista a un job batch e dividi il Markdown in chunk per un vector store.

**Risposta rapida — Come converto una pagina web in Markdown in Python?**

Chiama **WebReaderAPI** con `requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})` e la tua chiave nell'header `Authorization`; la risposta JSON contiene il contenuto principale della pagina in `markdown`. Per molte pagine, usa `httpx.AsyncClient` con un semaforo o un job batch. Una pagina costa 1 credito, da 0,07 USD/1K pagine a ottobre 2026. Senza KYC; paghi in criptovalute.

## Cosa devo installare?

Python 3.9 o successivo e un client HTTP. `requests` basta per gli script che leggono una pagina alla volta; `httpx` aggiunge il supporto async per molte pagine in parallelo. Non serve nessun nostro pacchetto: l'API è semplice HTTPS e JSON.

Tieni la chiave fuori dal codice. Leggila dalla variabile d'ambiente `WEBREADER_API_KEY`, come fa ogni esempio qui sotto, e dai a ogni progetto la sua chiave, così puoi revocarne una senza toccare le altre.

*Installazione*

```bash
pip install requests httpx
export WEBREADER_API_KEY="your-key"
```

## Come leggo una pagina con requests?

Invia `url` e le opzioni come parametri di query, solleva un'eccezione in caso di errore HTTP e leggi i campi che ti servono. L'esempio mantiene il contenuto principale, lo limita a 20.000 caratteri per un modello e stampa quanto è costata la chiamata.

*requests: una pagina*

```python
import os

import requests

API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


def read(url, **options):
    resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
    if resp.status_code != 200:
        error = resp.json()["error"]
        raise RuntimeError(error["code"] + ": " + error["message"])
    return resp.json()


page = read("https://example.com/docs/getting-started", max_chars=20000)
print(page["title"], page["word_count"], "words", page["credits_used"], "credit(s)")
print(page["markdown"][:500])

```

## Come leggo molte pagine contemporaneamente con httpx?

Usa un solo `httpx.AsyncClient` e un `asyncio.Semaphore` impostato pari o sotto il tuo limite di concorrenza. Ogni account ha un limite di pagine lette contemporaneamente; la tua dashboard lo mostra e il supporto può aumentarlo. Gli errori di una pagina non devono fermare le altre, quindi l'esempio li raccoglie accanto ai risultati invece di sollevare un'eccezione.

Se superi il limite, l'API risponde `429 concurrency_limit`; non viene addebitato nulla, ma la pagina non viene letta. Tenere il semaforo sotto il limite è più semplice che riprovare.

*httpx: fino a 10 pagine in volo*

```python
import asyncio
import os

import httpx

API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


async def read_one(client, sem, url):
    async with sem:
        resp = await client.get(API, params={"url": url, "max_chars": 50000})
    body = resp.json()
    if resp.status_code == 200:
        return url, body, None
    return url, None, body["error"]["code"]


async def read_many(urls, limit=10):
    sem = asyncio.Semaphore(limit)
    async with httpx.AsyncClient(headers=HEADERS, timeout=75) as client:
        return await asyncio.gather(*(read_one(client, sem, u) for u in urls))


if __name__ == "__main__":
    urls = [f"https://example.com/blog/post-{n}" for n in range(1, 21)]
    for url, page, error in asyncio.run(read_many(urls)):
        print(url, "->", error or page["title"])

```

## Quando dovrei usare invece un batch job?

Quando hai una lista di fino a 100 URL e non ti serve ogni pagina nel momento in cui è pronta. Un batch job gira dalla nostra parte, quindi il tuo script non tiene connessioni aperte e non gestisce logiche di retry. Avvialo con `POST https://webreaderapi.com/api/v1/batch`, poi raccogli i risultati del job con il ciclo a cursore mostrato sulla [pagina batch e crawl](https://webreaderapi.com/it/docs/crawl/).

Per un intero sito, avvia invece un crawl e lascia che segua la sitemap e i link; la stessa pagina ha un ciclo completo che scrive un file `.md` per pagina.

*Avviare un job batch*

```python
import os

import requests

urls = [f"https://example.com/changelog/{year}" for year in range(2020, 2027)]
resp = requests.post(
    "https://webreaderapi.com/api/v1/batch",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"urls": urls, "options": {"format": "markdown", "main_content": True}},
    timeout=30,
)
resp.raise_for_status()
print("job:", resp.json()["job_id"])

```

## Come divido il Markdown in chunk per il RAG?

Prima dividi per heading, poi per dimensione. Gli heading segnano dove cambiano gli argomenti, quindi un chunk che inizia con un heading è più facile da far combaciare per un retriever. Mantieni il titolo della pagina e il percorso degli heading nei metadata di ogni chunk: aiutano il modello a citare la sezione giusta.

La funzione qui sotto non richiede librerie aggiuntive. Taglia sugli heading da `#` a `###`, poi divide qualsiasi sezione lunga sulle righe vuote per restare sotto un limite di caratteri. Memorizza `final_url` con ogni chunk, così due indirizzi che portano alla stessa pagina non creano duplicati.

*Dividi il Markdown per heading*

```python
import re


def chunk_markdown(page, max_chars=2000):
    """Split a /read response into chunks with a heading path and the source URL."""
    chunks, path, buf = [], [], []

    def flush():
        text = "\n".join(buf).strip()
        while text:
            part = text[:max_chars]
            cut = part.rfind("\n\n")
            if len(text) > max_chars and cut > max_chars // 2:
                part = part[:cut]
            chunks.append({"text": part.strip(), "section": " > ".join(path), "title": page.get("title"), "source": page["final_url"]})
            text = text[len(part):].strip()
        buf.clear()

    for line in page["markdown"].splitlines():
        m = re.match(r"^(#{1,3}) (.+)", line)
        if m:
            flush()
            level = len(m.group(1))
            path[:] = path[: level - 1] + [m.group(2).strip()]
        buf.append(line)
    flush()
    return chunks

```

## Quanto costa un job Python?

Conta le pagine, non le richieste. 1 credito = 1 pagina letta senza browser; 2 crediti quando la pagina richiede un browser per eseguire JavaScript. Una richiesta che fallisce, o che il robots.txt di un sito rifiuta, non viene addebitata. Leggere 2.000 pagine di documentazione senza browser usa circa 2.000 crediti, che 10.000 pagine (3,75 USD) copre cinque volte. I crediti prepagati non scadono mai: le pagine non utilizzate restano sul tuo account finché non le usi, quindi una pipeline che gira una volta al mese usa solo ciò che legge.

## Python: domande frequenti

### Esiste un pacchetto Python ufficiale?

No. Il codice su questa pagina è tutto ciò che ti serve: WebReaderAPI è una semplice API HTTPS, e `requests` o `httpx` la parlano direttamente.

### Come gestisco una pagina che robots.txt rifiuta?

Controlla il codice di errore. `blocked_by_robots` e `target_refused` sono definitivi e gratuiti; registrali e continua. La [pagina degli errori](https://webreaderapi.com/it/docs/errors/) ha un helper di retry che ritenta solo i codici che possono cambiare.

### Posso usare l'API da un notebook Jupyter?

Sì. La ricetta `requests` funziona così com'è. In un notebook, `await read_many(urls)` sostituisce `asyncio.run`, perché il notebook ha già un event loop in esecuzione.

### Quale timeout dovrebbe usare il mio client?

Un po' più di quello dell'API: il default è 30 secondi e il massimo 60, quindi 75 secondi lato client lasciano spazio a un sito lento e alla risposta.

---

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.
