# Node.js 网页转 Markdown

> 在 Node.js 中用原生 fetch 把网页转成 Markdown：单页、一个小型并发池和一个整站抓取 (crawl) 轮询循环。低至 US$0.07/千页，无需 KYC，加密货币支付。

- URL: https://webreaderapi.com/zh/docs/node/
- 最后更新: 2026-10-22
- 发布者: WebReaderAPI (https://webreaderapi.com)

Node.js 18 及更高版本自带 `fetch`，因此你完全不需要任何依赖。三种方案：读取一个页面、用小型并发池读取列表，以及跟踪 crawl 任务直到完成。

**快速回答 — 如何在 Node.js 中将网页转换为 Markdown？**

使用 Node.js 18+ 内置的 `fetch`：在 `Authorization` header 中带上你的密钥调用 `https://webreaderapi.com/api/v1/read?url=…`，并从 JSON 响应中读取 `markdown`。**WebReaderAPI** 返回页面的正文，以及标题、作者和日期。单个页面消耗 1 积分，使用浏览器则消耗 2 积分，价格自 2026 年 10 月起为 US$0.07/千页。无需 KYC；加密货币支付。

## 我需要什么？

Node.js 18 或更高版本，以使用全局 `fetch`，并在 `WEBREADER_API_KEY` 环境变量中设置 API 密钥。示例为 ES modules：保存时使用 `.mjs` 扩展名，或在 `package.json` 中设置 `"type": "module"`，这样顶层的 `await` 才能正常工作。

JSDoc 注释描述了响应结构，因此 VS Code 等编辑器无需 TypeScript 构建步骤就能提供补全。

*read.mjs：单个页面*

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

```

## 如何读取 URL 列表而不触及限制？

运行固定数量的 worker，从共享队列中取出 URL。这比对整个列表使用 `Promise.all` 更简单，后者会同时发起所有请求并触及 `429 concurrency_limit`。每个账户都有同时读取页面数的上限；你的控制台会显示该上限，支持团队可将其提高。

下面的并发池会把错误与结果放在一起，因此一个被拒绝的页面不会中断整个运行。诸如 `blocked_by_robots` 这类拒绝是最终且免费的；记录下来然后继续。

*pool.mjs：URL 列表，每次 8 个*

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

```

## 如何在 Node.js 中跟踪 crawl 任务？

用 `POST https://webreaderapi.com/api/v1/crawl` 启动任务，然后在循环中读取 `https://webreaderapi.com/api/v1/jobs/{job_id}`。每当 `next_cursor` 返回时传入 `cursor`；当没有 cursor 且状态为 `done` 时，任务已完成。在空轮询之间等待，并将等待时间翻倍直至 30 秒，因为尊重网站 Crawl-delay 的 crawl 可能很慢。

[batch 与 crawl 页面](https://webreaderapi.com/zh/docs/crawl/) 描述了 crawl 如何选择页面，以及 `limit` 如何限制其读取内容。

*crawl.mjs：启动 crawl 并收集每个页面*

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

```

## 如何将结果传给模型？

发送 Markdown，而不是整个 JSON。模型应读取的是 `markdown` 字段；元数据最好留作引用。在其前面加上标题和 `final_url`，以便模型能标明来源，并将 `max_chars` 保持在模型上下文能容纳的大小，例如每页 20,000 个字符。

[OpenAI function-calling 页面](https://webreaderapi.com/zh/docs/openai/) 演示了 Python 中的完整工具循环；上面的同一个 `readPage` 函数可以作为 Node.js agent 中的工具主体。

## Node.js：常见问题

### 这在 Deno、Bun 或浏览器中能用吗？

Deno 和 Bun 支持相同的 `fetch` 和 ES modules。不要从浏览器页面调用 API：密钥会暴露给每个访客。请从你的服务器调用。

### 有 npm 包吗？

没有。本页面的函数就是完整的集成。WebReaderAPI 就是普通的 HTTPS 和 JSON，因此你可以掌控重试、超时和日志记录。

### 如何处理 429 concurrency_limit？

减少 worker 数量，或短暂等待后重试，且每次将等待时间翻倍。429 不消耗任何费用，[错误页面](https://webreaderapi.com/zh/docs/errors/) 提供了现成的退避函数。

### 读取 10,000 个页面需要多少费用？

普通页面约 10,000 积分，若部分页面需要浏览器则更多。10,000 个网页 花费 US$3.75；预付费积分永不过期：未使用的页面数会一直保留在你的账户中，直到你使用。

---

WebReaderAPI: WebReaderAPI 是一款面向 AI agents 的网页读取 API，可将任何公开网页转换为干净的 Markdown，用加密货币支付，无需上传身份证件。预付费积分低至 US$0.07/千页，永不过期，且遵守 robots.txt。无需 KYC — 无身份证、无自拍、无证件。加密货币支付。
