Réponse rapide
Comment convertir une page web en Markdown dans Node.js ?
Utilisez le fetch intégré de Node.js 18+ : appelez https://webreaderapi.com/api/v1/read?url=… avec votre clé dans l'en-tête Authorization et lisez markdown dans la réponse JSON. WebReaderAPI renvoie le contenu principal de la page avec son titre, son auteur et sa date. Une page coûte 1 crédit, 2 avec un navigateur, depuis 0,07 $US/1K pages en octobre 2026. Sans KYC ; payez en crypto.
De quoi ai-je besoin ?
Node.js 18 ou plus récent, pour le fetch global, et une clé API dans la variable d'environnement WEBREADER_API_KEY. Les exemples sont des modules ES : enregistrez-les avec l'extension .mjs, ou définissez "type": "module" dans votre package.json, pour que le await de haut niveau fonctionne.
Les commentaires JSDoc décrivent la forme de la réponse, donc des éditeurs comme VS Code vous offrent la complétion sans étape de build TypeScript.
/**
* @typedef {Object} Page
* @property {string} url
* @property {string} final_url
* @property {number} status_code
* @property {boolean} rendered
* @property {number} credits_used
* @property {string} [title]
* @property {string} [author]
* @property {string} [published_at]
* @property {string} [language]
* @property {string} [markdown]
* @property {number} word_count
*/
/** @returns {Promise<Page>} */
export async function readPage(url, options = {}) {
const params = new URLSearchParams({ url, ...options });
const res = await fetch(`https://webreaderapi.com/api/v1/read?${params}`, {
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
signal: AbortSignal.timeout(75_000),
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error.message), { code: body.error.code });
return body;
}
const page = await readPage('https://example.com/docs/getting-started', { max_chars: '20000' });
console.log(page.title, page.word_count, 'words,', page.credits_used, 'credit(s)');
Comment lire une liste d'URL sans atteindre la limite ?
Lancez un nombre fixe de workers qui prennent des URL dans une file partagée. C'est plus simple que Promise.all sur toute la liste, ce qui lancerait toutes les requêtes à la fois et atteindrait 429 concurrency_limit. Chaque compte a une limite du nombre de pages lues en simultané ; votre dashboard l'affiche, et le support peut l'augmenter.
Le pool ci-dessous garde les erreurs à côté des résultats, donc une page refusée n'arrête pas l'exécution. Les refus comme blocked_by_robots sont définitifs et gratuits ; comptez-les et passez à la suite.
import { readPage } from './read.mjs';
export async function readAll(urls, workers = 8) {
const queue = [...urls];
const results = [];
async function worker() {
while (queue.length) {
const url = queue.shift();
try {
const page = await readPage(url, { max_chars: '50000' });
results.push({ url, page });
} catch (err) {
results.push({ url, error: err.code ?? err.name });
}
}
}
await Promise.all(Array.from({ length: workers }, worker));
return results;
}
const urls = Array.from({ length: 30 }, (_, i) => `https://example.com/blog/post-${i + 1}`);
for (const r of await readAll(urls)) console.log(r.url, '->', r.error ?? r.page.title);
Comment suivre un job de crawl dans Node.js ?
Démarrez le job avec POST https://webreaderapi.com/api/v1/crawl, puis lisez https://webreaderapi.com/api/v1/jobs/{job_id} dans une boucle. Passez cursor à chaque fois que next_cursor revient ; quand il n'y a pas de curseur et que le statut est done, le job est terminé. Attendez entre les polls vides et doublez l'attente jusqu'à 30 secondes, car un crawl qui respecte le Crawl-delay d'un site peut être lent.
La page batch and crawl décrit comment le crawl choisit ses pages et comment limit plafonne ce qu'il lit.
const headers = {
Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`,
'Content-Type': 'application/json',
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const start = await fetch('https://webreaderapi.com/api/v1/crawl', {
method: 'POST',
headers,
body: JSON.stringify({ url: 'https://docs.example.com/', limit: 300, include_paths: ['/docs/*'] }),
});
const { job_id } = await start.json();
const pages = [];
let cursor = '';
let wait = 2000;
for (;;) {
const url = `https://webreaderapi.com/api/v1/jobs/${job_id}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ''}`;
const job = await (await fetch(url, { headers })).json();
pages.push(...job.results.filter((r) => !r.error));
if (job.next_cursor) {
cursor = job.next_cursor;
wait = 2000;
continue;
}
if (job.status === 'done') break;
await sleep(wait);
wait = Math.min(wait * 2, 30000);
}
console.log(pages.length, 'pages read, job', job_id);
Comment passer le résultat à un modèle ?
Envoyez le Markdown, pas le JSON entier. Le champ markdown est ce qu'un modèle doit lire ; les métadonnées sont mieux gardées pour les citations. Préfixez avec le titre et final_url, pour que le modèle puisse nommer sa source, et gardez max_chars à une taille que le contexte de votre modèle peut contenir, par exemple 20 000 caractères par page.
La page OpenAI function calling montre la boucle d'outil complète en Python ; la même fonction readPage ci-dessus peut servir de corps d'outil dans un agent Node.js.
Node.js : questions fréquentes
Est-ce que cela fonctionne dans Deno, Bun ou le navigateur ?
Deno et Bun prennent en charge le même fetch et les modules ES. N'appelez pas l'API depuis une page de navigateur : la clé serait visible par chaque visiteur. Appelez-la depuis votre serveur à la place.
Existe-t-il un package npm ?
Non. Les fonctions de cette page constituent l'intégration complète. WebReaderAPI est du HTTPS et du JSON simples, donc vous gardez le contrôle des retries, des timeouts et du logging.
Comment gérer 429 concurrency_limit ?
Réduisez le nombre de workers, ou réessayez après une courte attente qui double à chaque fois. Un 429 ne coûte rien, et la page errors propose une fonction de backoff prête à l'emploi.
Combien coûte la lecture de 10 000 pages ?
Environ 10 000 crédits pour des pages simples, davantage si certaines nécessitent un navigateur. 10 000 pages coûtent 3,75 $US ; Les crédits prépayés n'expirent jamais : les pages non utilisées restent sur votre compte jusqu'à ce que vous les utilisiez.