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
| 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. 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. |
{
"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
{
"job_id": "job_5c1e8a90d2"
}Exemples de code
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);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. |
{
"url": "https://docs.example.com/",
"limit": 500,
"max_depth": 3,
"include_paths": [
"/docs/*"
],
"exclude_paths": [
"/docs/archive/*"
],
"options": {
"format": "markdown"
}
}Réponse 202 Accepted
{
"job_id": "job_8b27f41c0e"
}Exemples de code
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);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
{
"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
}Exemples de code
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);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.
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_pathsetexclude_pathsacceptent 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
limitpages.
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.