# API batch et crawl : lire de nombreuses URLs ou un site entier

> API batch et crawl : lisez 100 URLs par job ou un site jusqu'à 10 000 pages, robots.txt respecté. Sans KYC, payez en crypto.

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

Deux endpoints lancent des jobs en arrière-plan : `/batch` pour une liste de 100 URLs maximum, `/crawl` pour un site web. Les deux renvoient un job ID ; vous récupérez les pages depuis `/jobs/{job_id}`, une page de résultats à la fois.

**Réponse rapide — Comment crawler un site web vers du Markdown avec WebReaderAPI ?**

Envoyez l'URL de départ à `POST https://webreaderapi.com/api/v1/crawl` avec une page `limit`. **WebReaderAPI** lit le site en arrière-plan, même domaine uniquement, en suivant son sitemap, robots.txt et Crawl-delay, jusqu'à 10 000 pages par job. Récupérez les pages Markdown du job, une page de résultats à la fois. Chaque page lue coûte 1 crédit (2 avec un navigateur), à partir de 0,07 $US/1K pages depuis octobre 2026. Sans KYC ; payez en crypto.

## Les jobs en un coup d'œil

| Attribut | Valeur |
| --- | --- |
| Batch | Jusqu'à 100 URLs par job batch : `POST https://webreaderapi.com/api/v1/batch` |
| Crawl | Crawl d'un seul site web par job, jusqu'à 10 000 pages, en suivant son sitemap, son robots.txt et son Crawl-delay : `POST https://webreaderapi.com/api/v1/crawl` |
| Résultats | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`, puis `?cursor=…` pour la page de résultats suivante |
| Facturation | Chaque page lue est facturée comme un appel `/read` ; les pages ignorées par robots.txt et les pages en échec ne coûtent rien |
| Périmètre d'un crawl | Un seul domaine ; les liens vers d'autres domaines sont listés mais non lus |
| Annuler un job | Non disponible en v1 : réglez `limit` sur le nombre maximum de pages que vous êtes prêt à payer |

## Lancer un job batch

`POST /api/v1/batch`

Lit jusqu'à 100 URLs en arrière-plan avec les mêmes options. Les options utilisent les noms et valeurs de [/read](https://webreaderapi.com/fr/docs/read/). La réponse est le job ID ; les valeurs sont illustratives.

Authentification : clé API dans le header `Authorization: Bearer API_KEY`.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `urls` | array of strings | Oui | De 1 à 100 URLs http ou https. Les doublons ne sont lus qu'une fois. |
| `options` | object | Non | Tout paramètre `/read` sauf `url` : `format`, `main_content`, `render`, `include_links`, `include_images`, `country`, `max_chars`, `timeout`. |

*Exemple: 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
  }
}
```

**Réponse** `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);
```

## Démarrer un job de crawl

`POST /api/v1/crawl`

Lit un site web à partir d'une URL de départ, en largeur d'abord. Le crawler reste sur le domaine de l'URL de départ, utilise le sitemap indiqué dans robots.txt ou trouvé à `/sitemap.xml`, et respecte robots.txt et Crawl-delay. Les valeurs sont illustratives.

Authentification : clé API dans le header `Authorization: Bearer API_KEY`.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `url` | string | Oui | La page de départ, comme la page d'accueil d'un site de documentation. |
| `limit` | integer | Non | Nombre maximal de pages à lire, jusqu'à 10 000. Le job s'arrête lorsqu'il atteint ce nombre. |
| `max_depth` | integer | Non | Nombre maximal de sauts de lien depuis l'URL de départ. |
| `include_paths` | array of strings | Non | Motifs de chemin simples à conserver, comme `/docs/*`. |
| `exclude_paths` | array of strings | Non | Motifs de chemin simples à ignorer, comme `/blog/tag/*`. |
| `options` | object | Non | Tout paramètre `/read` sauf `url`, appliqué à chaque page. |

*Exemple: 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"
  }
}
```

**Réponse** `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);
```

## Obtenir le statut et les résultats d'un job

`GET /api/v1/jobs/{job_id}`

Renvoie le statut du job et les prochains résultats prêts. Chaque élément de `results` est soit une réponse `/read` complète, soit `{url, error}` pour une page qui n'a pas pu être lue. Quand `next_cursor` est présent, rappelez avec `cursor` défini à cette valeur ; quand il est absent, aucun autre résultat n'est encore prêt. Les valeurs sont illustratives.

Authentification : clé API dans le header `Authorization: Bearer API_KEY`.

**Paramètres**

| Nom | Emplacement | Type | Obligatoire | Description |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | Oui | L'ID renvoyé par `/batch` ou `/crawl`. |
| `cursor` | query | string | Non | La valeur `next_cursor` de l'appel précédent, pour obtenir les résultats suivants. |

**Réponse** `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);
```

## Comment attendre la fin d'un job ?

Interrogez `/jobs/{job_id}` et conservez le curseur. Traitez les résultats reçus, puis rappelez avec `cursor` défini à `next_cursor`. Quand `next_cursor` est absent et que `status` vaut `done`, vous avez tout ; quand il est absent et que le job est encore `running`, attendez et rappelez avec le même curseur.

Commencez à interroger toutes les 2 secondes et doublez l'attente jusqu'à 30 secondes tant que rien de nouveau n'arrive. Un crawl qui respecte le Crawl-delay d'un site peut prendre du temps : une longue attente est normale et ne coûte rien.

*Python : interroger un job et enregistrer chaque page dans un fichier .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")

```

## Quelles pages un crawl lit-il ?

Un crawl démarre à votre URL et progresse vers l'extérieur, un saut de lien à la fois, en largeur d'abord, jusqu'à `max_depth`. Il lit aussi le sitemap du site, à l'adresse indiquée dans robots.txt ou à `/sitemap.xml`, pour trouver les pages vers lesquelles peu de liens pointent.

- **Même domaine uniquement.** Les liens vers d'autres domaines sont listés dans les résultats quand vous demandez `include_links`, mais ils ne sont pas lus.
- **Filtres de chemin.** `include_paths` et `exclude_paths` acceptent des motifs simples comme `/blog/*`.
- **Une copie par page.** Quand un site déclare une URL canonique, une page atteinte via plusieurs adresses n'est lue qu'une fois.
- **Un arrêt net.** Le job se termine quand il a lu `limit` pages.

## Comment un crawl respecte-t-il le site ?

Chaque requête porte le User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`, et le crawler suit les règles robots.txt pour `WebReaderAPI`, ou pour tous les robots quand aucun ne le nomme. Les pages interdites sont ignorées et apparaissent dans `results` avec l'erreur `blocked_by_robots` ; elles ne coûtent rien.

Quand robots.txt définit un `Crawl-delay`, le crawler attend ce délai entre deux requêtes vers le site. Si un site déclare Content-Signal: ai-input=no dans son robots.txt, nous ne lisons pas ses pages pour vous. Si un site refuse notre requête, nous renvoyons sa réponse : nous ne résolvons jamais de CAPTCHA, ne nous connectons pas à des comptes et ne cherchons pas à contourner un blocage ou un paywall.

## Combien coûte un crawl ?

Chaque page lue coûte ce que coûterait un appel `/read` : 1 crédit, 2 si la page a nécessité un navigateur, plus 10 avec `country`. Les pages ignorées et en échec sont gratuites. Le champ `credits_used` du job totalise ce qui a été facturé jusqu'ici.

Un site de documentation de 500 pages lu sans navigateur utilise environ 500 crédits, que le pack 10 000 pages (3,75 $US) couvre largement. Comme un job ne peut pas être annulé en v1, `limit` est votre plafond de dépense : fixez-le au nombre maximal de pages que vous êtes prêt à payer.

## Batch et crawl : questions fréquentes

### Faut-il utiliser /batch ou plusieurs appels /read ?

Utilisez `/batch` quand vous avez déjà les URLs et n'avez pas besoin de chaque réponse immédiatement. Cela vous évite de gérer la concurrence et les tentatives. Utilisez `/read` quand un agent a besoin d'une page tout de suite.

### Combien de temps les résultats d'un job sont-ils conservés ?

Récupérez-les dès qu'ils sont prêts. Les résultats attendent que vous les récupériez ; une fois livrés, aucune copie du contenu de la page n'est conservée, stockez donc ce dont vous avez besoin de votre côté.

### Un crawl peut-il suivre des liens vers d'autres sites web ?

Non. Un crawl lit un seul domaine. Les autres domaines apparaissent comme liens quand vous les demandez, et vous pouvez envoyer ces URLs à `/batch` si vous voulez qu'elles soient lues.

### Puis-je arrêter un crawl déjà démarré ?

Pas en v1. Le job s'arrête à `limit` pages, choisissez donc une limite adaptée à votre budget. Les pages déjà lues restent facturées ; les pages jamais lues ne coûtent rien.

---

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.
