# Page web en Markdown en Python

> Convertissez des pages web en Markdown en Python avec requests, httpx en async, batch jobs et découpage RAG. Depuis 0,07 $US/1K pages, sans KYC.

- URL: https://webreaderapi.com/fr/docs/python/
- Dernière mise à jour: 2026-10-22
- Éditeur: WebReaderAPI (https://webreaderapi.com)

Quatre recettes qui couvrent la plupart des projets Python : lire une page avec requests, lire plusieurs pages en parallèle avec httpx, confier une liste à un batch job, et découper le Markdown en chunks pour un vector store.

**Réponse rapide — Comment convertir une page web en Markdown en Python ?**

Appelez **WebReaderAPI** avec `requests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})` et votre clé dans le header `Authorization` ; la réponse JSON contient le contenu principal de la page dans `markdown`. Pour plusieurs pages, utilisez `httpx.AsyncClient` avec un sémaphore ou un batch job. Une page coûte 1 crédit, à partir de 0,07 $US/1K pages en octobre 2026. Sans KYC ; payez en crypto.

## De quoi ai-je besoin d'installer ?

Python 3.9 ou ultérieur et un client HTTP. `requests` suffit pour les scripts qui lisent une page à la fois ; `httpx` ajoute le support async pour de nombreuses pages en parallèle. Aucun package de notre part n'est nécessaire : l'API est du HTTPS et du JSON simples.

Gardez votre clé hors du code. Lisez-la depuis la variable d'environnement `WEBREADER_API_KEY`, comme dans chaque exemple ci-dessous, et donnez à chaque projet sa propre clé pour pouvoir en révoquer une sans toucher aux autres.

*Installation*

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

## Comment lire une page avec requests ?

Envoyez `url` et les options en paramètres de requête, levez une exception en cas d'erreur HTTP et lisez les champs dont vous avez besoin. L'exemple conserve le contenu principal, le limite à 20 000 caractères pour un modèle et affiche ce que l'appel a coûté.

*requests : une page*

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

```

## Comment lire plusieurs pages à la fois avec httpx ?

Utilisez un seul `httpx.AsyncClient` et un `asyncio.Semaphore` réglé au niveau de votre limite de concurrence ou en dessous. Chaque compte a une limite du nombre de pages lues en simultané ; votre dashboard l'affiche, et le support peut l'augmenter. Les erreurs d'une page ne doivent pas arrêter les autres, donc l'exemple les collecte à côté des résultats au lieu de lever une exception.

Si vous dépassez la limite, l'API répond `429 concurrency_limit` ; rien n'est facturé, mais la page n'est pas lue. Garder le sémaphore sous la limite est plus simple que de réessayer.

*httpx : jusqu'à 10 pages en vol*

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

```

## Quand dois-je plutôt utiliser un batch job ?

Lorsque vous avez une liste pouvant aller jusqu'à 100 URL et que vous n'avez pas besoin de chaque page dès qu'elle est prête. Un job par lots s'exécute de notre côté, donc votre script ne garde aucune connexion ouverte et ne gère aucune logique de réessai. Lancez-le avec `POST https://webreaderapi.com/api/v1/batch`, puis récupérez les résultats du job avec la boucle de curseur présentée sur la [page batch et crawl](https://webreaderapi.com/fr/docs/crawl/).

Pour un site entier, lancez plutôt un crawl et laissez-le suivre le sitemap et les liens ; la même page contient une boucle complète qui écrit un fichier `.md` par page.

*Lancer 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"])

```

## Comment découper le Markdown en chunks pour le RAG ?

Découpez d'abord sur les titres, puis sur la taille. Les titres marquent les changements de sujet, donc un chunk qui commence à un titre est plus facile à faire correspondre pour un retriever. Conservez le titre de la page et le chemin des titres dans les métadonnées de chaque chunk : ils aident le modèle à citer la bonne section.

La fonction ci-dessous ne nécessite aucune bibliothèque supplémentaire. Elle coupe au niveau des titres `#` à `###`, puis découpe toute section longue sur les lignes vides pour rester sous un budget de caractères. Stockez `final_url` avec chaque chunk, afin que deux adresses menant à la même page ne créent pas de doublons.

*Découper le Markdown par titres*

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

```

## Combien coûte un job Python ?

Comptez les pages, pas les requêtes. 1 crédit = 1 page lue sans navigateur ; 2 crédits quand la page nécessite un navigateur pour exécuter JavaScript. Une requête qui échoue, ou qu'un robots.txt de site refuse, n'est pas facturée. Lire 2 000 pages de documentation sans navigateur utilise environ 2 000 crédits, ce que 10 000 pages (3,75 $US) couvre cinq fois. Les crédits prépayés n'expirent jamais : les pages non utilisées restent sur votre compte jusqu'à ce que vous les utilisiez, donc un pipeline qui s'exécute une fois par mois n'utilise que ce qu'il lit.

## Python : questions fréquentes

### Existe-t-il un package Python officiel ?

Non. Le code de cette page est tout ce dont vous avez besoin : WebReaderAPI est une API HTTPS simple, et `requests` ou `httpx` la gèrent directement.

### Comment gérer une page que robots.txt refuse ?

Vérifiez le code d'erreur. `blocked_by_robots` et `target_refused` sont définitifs et gratuits ; enregistrez-les et continuez. La [page errors](https://webreaderapi.com/fr/docs/errors/) contient un assistant de réessai qui ne relance que les codes susceptibles de changer.

### Puis-je utiliser l'API depuis un notebook Jupyter ?

Oui. La recette `requests` fonctionne telle quelle. Dans un notebook, `await read_many(urls)` remplace `asyncio.run`, car le notebook exécute déjà une boucle d'événements.

### Quel timeout mon client doit-il utiliser ?

Un peu plus que celui de l'API : la valeur par défaut est 30 secondes et le maximum 60, donc 75 secondes côté client laissent de la marge pour un site lent et la réponse.

---

WebReaderAPI: WebReaderAPI est une API de lecture de pages web pour agents IA qui transforme toute page web publique en Markdown propre, payable en crypto sans envoi de pièce d'identité. Les crédits prépayés à partir de 0,07 $US/1K pages n'expirent jamais, et robots.txt est respecté. Sans KYC — pas de pièce d'identité, pas de selfie, pas de documents. Payez en crypto.
