# 批量与整站抓取 (crawl) API：读取多个 URL 或整个网站

> 批量与整站抓取 (crawl) API 参考：每个任务最多读取 100 个 URL，或整个网站最多 10,000 页，遵守 robots.txt 和 Crawl-delay。无需 KYC，加密货币支付。

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

两个 endpoint 启动后台任务：`/batch` 用于最多 100 个 URL 的列表，`/crawl` 用于一个网站。两者都返回 job ID；你从 `/jobs/{job_id}` 收集页面，一次一页结果。

**快速回答 — 如何使用 WebReaderAPI 将网站整站抓取 (crawl) 为 Markdown？**

将起始 URL 连同页面 `limit` 发送到 `POST https://webreaderapi.com/api/v1/crawl`。**WebReaderAPI** 在后台读取网站，仅限同一域名，遵循其 sitemap、robots.txt 和 Crawl-delay，每个任务最多 10,000 页。从任务中收集 Markdown 页面，一次一页结果。每次页面读取花费 1 积分（使用浏览器为 2），自 2026 年 10 月起从 US$0.07/千页 起。无需 KYC；加密货币支付。

## 任务一览

| 属性 | 值 |
| --- | --- |
| 批量 | 每个批量任务最多 100 个 URL：`POST https://webreaderapi.com/api/v1/batch` |
| 整站抓取 (crawl) | 每个任务抓取一个网站，最多 10,000 页，遵循其 sitemap、robots.txt 和 Crawl-delay：`POST https://webreaderapi.com/api/v1/crawl` |
| 结果 | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`，然后 `?cursor=…` 获取下一页结果 |
| 计费 | 每次页面读取按 `/read` 调用计费；被 robots.txt 跳过的页面和失败的页面不收费 |
| 整站抓取 (crawl) 范围 | 一个域名；指向其他域名的链接会列出但不读取 |
| 取消任务 | v1 中不可用：将 `limit` 设置为你准备支付的最大页数 |

## 启动批量任务

`POST /api/v1/batch`

在后台使用相同选项读取最多 100 个 URL。选项使用 [/read](https://webreaderapi.com/zh/docs/read/) 的名称和值。答案是 job ID；值仅为示例。

认证：将 API 密钥放在 `Authorization: Bearer API_KEY` 请求头中。

**请求体**

| 字段 | 类型 | 必填 | 描述 |
| --- | --- | --- | --- |
| `urls` | array of strings | 是 | 1 到 100 个 http 或 https URL。重复的只读取一次。 |
| `options` | object | 否 | 除 `url` 外的任意 `/read` 参数：`format`、`main_content`、`render`、`include_links`、`include_images`、`country`、`max_chars`、`timeout`。 |

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

**响应** `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);
```

## 启动一个整站抓取 (crawl) 任务

`POST /api/v1/crawl`

从一个起始 URL 开始，按广度优先读取一个网站。爬虫只会停留在起始 URL 的域名内，使用 robots.txt 中指定的站点地图或位于 `/sitemap.xml` 的站点地图，并遵守 robots.txt 和 Crawl-delay。以下数值仅为示例。

认证：将 API 密钥放在 `Authorization: Bearer API_KEY` 请求头中。

**请求体**

| 字段 | 类型 | 必填 | 描述 |
| --- | --- | --- | --- |
| `url` | string | 是 | 起始页面，例如文档站点的首页。 |
| `limit` | integer | 否 | 最多读取的页面数，上限为 10,000。任务达到该数量时停止。 |
| `max_depth` | integer | 否 | 距起始 URL 最多的链接步数。 |
| `include_paths` | array of strings | 否 | 要保留的简单路径模式，例如 `/docs/*`。 |
| `exclude_paths` | array of strings | 否 | 要跳过的简单路径模式，例如 `/blog/tag/*`。 |
| `options` | object | 否 | 除 `url` 外的任意 `/read` 参数，应用于每个页面。 |

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

**响应** `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);
```

## 获取任务状态和结果

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

返回任务状态以及已就绪的下一批结果。`results` 中的每一项要么是完整的 `/read` 响应，要么是 `{url, error}`，表示该页面无法读取。当出现 `next_cursor` 时，将其设为 `cursor` 再次调用；当它不存在时，表示暂无更多结果就绪。以下数值仅为示例。

认证：将 API 密钥放在 `Authorization: Bearer API_KEY` 请求头中。

**参数**

| 名称 | 位置 | 类型 | 必填 | 描述 |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | 是 | 由 `/batch` 或 `/crawl` 返回的 ID。 |
| `cursor` | query | string | 否 | 上一次调用返回的 `next_cursor` 值，用于获取后续结果。 |

**响应** `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);
```

## 如何等待任务完成？

轮询 `/jobs/{job_id}` 并保留 cursor。处理拿到的结果，然后将 `cursor` 设为 `next_cursor` 再次调用。当 `next_cursor` 不存在且 `status` 为 `done` 时，说明已获取全部结果；当它不存在且任务仍为 `running` 时，等待后用同一个 cursor 再次查询。

开始时间隔 2 秒轮询一次，在没有新结果到达时每次将等待时间翻倍，最多 30 秒。遵守站点 Crawl-delay 的整站抓取 (crawl) 可能需要较长时间，因此等待久一点是正常的，且不产生费用。

*Python：轮询任务并将每个页面保存为 .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")

```

## 整站抓取 (crawl) 会读取哪些页面？

整站抓取 (crawl) 从你的 URL 开始，按广度优先一次向外扩展一个链接步，最多到 `max_depth`。它还会读取站点的站点地图，地址来自 robots.txt 中给出的地址或 `/sitemap.xml`，以找到链接指向较少的页面。

- **仅限同一域名。** 指向其他域名的链接在你请求 `include_links` 时会列在结果中，但不会被读取。
- **路径过滤。** `include_paths` 和 `exclude_paths` 接受 `/blog/*` 之类的简单模式。
- **每页只读一份。** 当站点声明了 canonical URL 时，通过多个地址到达的同一页面只读取一次。
- **硬性停止。** 任务在读取 `limit` 个页面后结束。

## 整站抓取 (crawl) 如何遵守站点规则？

每个请求都带有 User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`，爬虫会遵循适用于 `WebReaderAPI` 的 robots.txt 规则；若没有为其命名的规则，则遵循用于所有爬虫的规则。被禁止的页面会被跳过，并以错误 `blocked_by_robots` 出现在 `results` 中；这些页面不计费。

当 robots.txt 设置了 `Crawl-delay` 时，爬虫在对该站点的两次请求之间会等待那么久。如果网站在其 robots.txt 中声明 Content-Signal: ai-input=no，我们不会为你读取其页面。如果网站拒绝我们的请求，我们会返回其答复：我们从不破解 CAPTCHA、不登录账户，也不试图绕过封锁或付费墙。

## 整站抓取 (crawl) 的费用是多少？

每个读取的页面都按一次 `/read` 调用计费：1 积分，若页面需要浏览器则为 2 积分，使用 `country` 时再加 10 积分。被跳过和失败的页面免费。任务的 `credits_used` 会累计截至目前已计费的金额。

一个 500 页的文档站点在不使用浏览器的情况下读取约消耗 500 积分，10,000 个网页 套餐（US$3.75）可以覆盖很多次。由于 v1 中任务无法取消，`limit` 就是你的支出上限：把它设为你能承受付费的最大页面数。

## 批处理与整站抓取：常见问题

### 应该用 /batch 还是多次调用 /read？

当你已经拥有 URL 且不需要立即拿到每个结果时，用 `/batch`。它免去了你管理并发和重试的麻烦。当 agent 需要立即获取一个页面时，用 `/read`。

### 任务结果会保留多久？

结果一就绪就尽快收集。结果会等待你获取；一旦交付，就不再保留页面内容的副本，所以请在你自己这边保存所需内容。

### 整站抓取 (crawl) 会跟随指向其他网站的链接吗？

不会。一次整站抓取 (crawl) 只读取一个域名。其他域名会在你请求时作为链接出现，如果你希望读取这些 URL，可以把它们发给 `/batch`。

### 可以停止已经开始的整站抓取 (crawl) 吗？

v1 中不行。任务会在 `limit` 个页面时停止，所以请选择与预算相符的上限。已读取的页面仍会计费；从未读取的页面不产生费用。

---

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