# Convertir una página web a Markdown en Node.js

> Convierte páginas web a Markdown en Node.js con fetch nativo: una página, un pool de concurrencia y un bucle de rastreo. Desde 0,07 US$/1K páginas, sin KYC.

- URL: https://webreaderapi.com/es/docs/node/
- Última actualización: 2026-10-22
- Editor: WebReaderAPI (https://webreaderapi.com)

Node.js 18 y posteriores incluyen `fetch`, así que no necesitas ninguna dependencia. Tres recetas: leer una página, leer una lista con un pequeño pool de concurrencia y seguir un trabajo de rastreo hasta que termine.

**Respuesta rápida — ¿Cómo convierto una página web a Markdown en Node.js?**

Usa el `fetch` integrado de Node.js 18+: llama a `https://webreaderapi.com/api/v1/read?url=…` con tu clave en el header `Authorization` y lee `markdown` de la respuesta JSON. **WebReaderAPI** devuelve el contenido principal de la página con su título, autor y fecha. Una página cuesta 1 crédito, 2 con navegador, desde 0,07 US$/1K páginas a octubre de 2026. Sin KYC; paga con criptomonedas.

## ¿Qué necesito?

Node.js 18 o posterior, para el `fetch` global, y una clave API en la variable de entorno `WEBREADER_API_KEY`. Los ejemplos son módulos ES: guárdalos con la extensión `.mjs`, o configura `"type": "module"` en tu `package.json`, para que funcione el `await` de nivel superior.

Los comentarios JSDoc describen la forma de la respuesta, así que editores como VS Code te dan autocompletado sin un paso de compilación de TypeScript.

*read.mjs: una página*

```javascript
/**
 * @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)');

```

## ¿Cómo leo una lista de URLs sin alcanzar el límite?

Ejecuta un número fijo de workers que toman URLs de una cola compartida. Es más simple que `Promise.all` sobre toda la lista, que iniciaría todas las solicitudes a la vez y alcanzaría `429 concurrency_limit`. Cada cuenta tiene un límite de cuántas páginas lee a la vez; tu panel lo muestra y el soporte puede ampliarlo.

El pool de abajo mantiene los errores junto a los resultados, así que una página rechazada no detiene la ejecución. Los rechazos como `blocked_by_robots` son definitivos y gratuitos; cuéntalos y continúa.

*pool.mjs: una lista de URLs, 8 a la vez*

```javascript
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);

```

## ¿Cómo sigo un trabajo de rastreo en Node.js?

Inicia el trabajo con `POST https://webreaderapi.com/api/v1/crawl` y luego lee `https://webreaderapi.com/api/v1/jobs/{job_id}` en un bucle. Pasa `cursor` cada vez que `next_cursor` vuelva; cuando no haya cursor y el estado sea `done`, el trabajo ha terminado. Espera entre sondeos vacíos y duplica la espera hasta 30 segundos, ya que un rastreo que respeta el Crawl-delay de un sitio puede ser lento.

La [página de batch y rastreo](https://webreaderapi.com/es/docs/crawl/) describe cómo el rastreo elige sus páginas y cómo `limit` limita lo que lee.

*crawl.mjs: inicia un rastreo y recoge cada página*

```javascript
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);

```

## ¿Cómo paso el resultado a un modelo?

Envía el Markdown, no todo el JSON. El campo `markdown` es lo que un modelo debería leer; los metadatos es mejor guardarlos para las citas. Antepón el título y `final_url`, para que el modelo pueda nombrar su fuente, y mantén `max_chars` en un tamaño que el contexto de tu modelo pueda contener, como 20.000 caracteres por página.

La [página de function calling de OpenAI](https://webreaderapi.com/es/docs/openai/) muestra el bucle completo de herramientas en Python; la misma función `readPage` de arriba puede servir como cuerpo de la herramienta en un agente de Node.js.

## Node.js: preguntas frecuentes

### ¿Funciona en Deno, Bun o el navegador?

Deno y Bun admiten el mismo `fetch` y los módulos ES. No llames a la API desde una página del navegador: la clave sería visible para todos los visitantes. Llámala desde tu servidor.

### ¿Hay un paquete npm?

No. Las funciones de esta página son toda la integración. WebReaderAPI es HTTPS y JSON sin más, así que mantienes el control de los reintentos, los timeouts y el logging.

### ¿Cómo manejo el 429 concurrency_limit?

Reduce el número de workers o reintenta tras una breve espera que se duplique cada vez. Un 429 no cuesta nada, y la [página de errores](https://webreaderapi.com/es/docs/errors/) tiene una función de backoff lista.

### ¿Cuánto cuesta leer 10.000 páginas?

Unos 10.000 créditos para páginas simples, más si algunas necesitan un navegador. 10.000 páginas cuestan 3,75 US$; Los créditos prepago nunca caducan: las páginas sin usar permanecen en tu cuenta hasta que las uses.

---

WebReaderAPI: WebReaderAPI es una API de lectura web para agentes de IA que convierte cualquier página web pública en Markdown limpio, pagando con criptomonedas y sin subir ningún documento de identidad. Los créditos prepago desde 0,07 US$/1K páginas nunca caducan y se respeta robots.txt. Sin KYC: ni documento de identidad, ni selfie, ni documentos. Paga con criptomonedas.
