无需 KYC — 无身份证、无自拍、无证件。加密货币支付:BTC, USDT, USDC, ETH, SOL, LTC
语言: 简体中文
获取 API 密钥

文档 · 错误

错误、robots.txt 拒绝和重试

每个错误都有 HTTP 状态码、稳定的 code 和可读的 message。它们都不会消耗积分。有些值得重试;来自站点的拒绝是最终结果,我们绝不会绕过它们。

最后更新:

快速回答

WebReaderAPI 错误码是什么意思,哪些应该重试?

WebReaderAPI 错误分为三组,且都不会计费。请求问题(invalid_url、invalid_api_key、insufficient_credits)需要你在自己这边修复。站点决定(blocked_by_robots、blocked_by_site_policy、target_refused)是最终结果。临时故障(fetch_failed、fetch_timeout、concurrency_limit)可以用退避策略重试。积分起步于 US$0.07/千页,使用加密货币支付,无需 KYC。

API 可以返回哪些错误码?

响应体始终是 {"error": {"code", "message"}},如果拒绝来自站点,还会包含 target_status。

HTTPcode原因重试?
400invalid_url不是 http 或 https、网址过长,或是私有、本地或云元数据地址不重试:修正 URL
400invalid_parameter某个参数的值超出了其可选列表或范围不重试:修正请求
401invalid_api_key密钥缺失、错误或已被撤销不重试:检查 API 密钥
402insufficient_credits你的余额已无可用积分购买积分包后可重试
403blocked_by_robots该站点的 robots.txt 禁止 WebReaderAPI 或所有机器人访问该页面不重试:最终结果
403blocked_by_site_policy站点声明了 Content-Signal: ai-input=no不重试:最终结果
422unsupported_content我们不读取的类型(视频、压缩包)或文件过大否
429concurrency_limit同一时间运行的请求过多可以,退避后重试
502target_refused站点拒绝:403、429、验证挑战页或登录墙;其状态码在 target_status 中不重试:最终结果
502fetch_failedDNS、TLS 或连接错误,或站点返回 404 或 410重试一两次,并退避
504fetch_timeout站点在 timeout 内未响应可以,退避后重试或延长超时

为什么 robots.txt 和站点拒绝属于最终结果?

因为这是站点所有者的决定,而 WebReaderAPI 的设计就是遵循它。我们遵守 robots.txt:我们的爬虫以 WebReaderAPI 标识自身,遵循专为其或为所有爬虫编写的规则,不读取网站禁止的页面。被拒绝的页面不扣费。如果网站在其 robots.txt 中声明 Content-Signal: ai-input=no,我们不会为你读取其页面。

当站点以其他方式拒绝请求,例如 403、429、验证挑战页或登录表单时,你会得到 target_refused,并在 target_status 中看到站点自己的状态码。如果网站拒绝我们的请求,我们会返回其答复:我们从不破解 CAPTCHA、不登录账户,也不试图绕过封锁或付费墙。一分钟后重试同一 URL 通常得到相同结果,因此请把这些状态码当作结果而非失败:记录后继续。

如何排查页面为什么被拒绝?

打开站点的 /robots.txt,查找指名 WebReaderAPI 的分组,或适用于所有机器人的 * 分组。匹配该路径的 Disallow 行可解释 blocked_by_robots。诸如 Content-Signal: ai-input=no 的行则可解释 blocked_by_site_policy。

我们会保留每份 robots.txt 的副本,最长 24 小时。如果站点刚刚向机器人开放某个版块,变更最多需要一天才能生效。站点所有者可在我们的爬虫页面上了解我们爬虫的行为方式,以及如何允许或屏蔽它。

临时性失败应如何重试?

只重试 concurrency_limit、fetch_timeout,以及重试一两次 fetch_failed。每次重试前先等待,并让等待时间每次翻倍,再加一点随机抖动,避免并行 worker 同时重试。重试 4 或 5 次后停止。

对于 concurrency_limit,更好的办法是减少同时发送的请求数:每个账户都有同时读取页面数的上限;你的控制台会显示该上限,支持团队可将其提高。对于 fetch_timeout,在重试慢站点前将 timeout 提高到最多 60 秒。重试不产生费用,因为失败的尝试不计费。

Python:只重试那些可能变化的错误
import os
import random
import time

import requests

API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}
RETRY = {"concurrency_limit", "fetch_timeout", "fetch_failed"}
FINAL = {"blocked_by_robots", "blocked_by_site_policy", "target_refused", "unsupported_content"}


def read_page(url, attempts=5, **options):
    wait = 1.0
    for attempt in range(1, attempts + 1):
        resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
        if resp.status_code == 200:
            return resp.json()
        error = resp.json().get("error", {})
        code = error.get("code", "unknown")
        if code in FINAL:
            return {"url": url, "skipped": code, "target_status": error.get("target_status")}
        if code not in RETRY or attempt == attempts or (code == "fetch_failed" and attempt >= 3):
            raise RuntimeError(f"{code}: {error.get('message')}")
        time.sleep(wait + random.uniform(0, wait / 2))
        wait = min(wait * 2, 30)
    raise RuntimeError("unreachable")


if __name__ == "__main__":
    print(read_page("https://example.com/blog/first-post", max_chars=20000).get("title"))
Node.js:同样的规则,配合指数退避
const RETRY = new Set(['concurrency_limit', 'fetch_timeout', 'fetch_failed']);
const FINAL = new Set(['blocked_by_robots', 'blocked_by_site_policy', 'target_refused', 'unsupported_content']);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function readPage(url, options = {}, attempts = 5) {
  let wait = 1000;
  for (let attempt = 1; attempt <= attempts; attempt++) {
    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}` },
    });
    const body = await res.json();
    if (res.ok) return body;
    const code = body.error?.code ?? 'unknown';
    if (FINAL.has(code)) return { url, skipped: code, target_status: body.error.target_status };
    if (!RETRY.has(code) || attempt === attempts) throw new Error(`${code}: ${body.error?.message}`);
    await sleep(wait + Math.random() * (wait / 2));
    wait = Math.min(wait * 2, 30000);
  }
}

批量或整站抓取 (crawl) 中的错误怎么办?

单个页面失败不会让整个任务停止。该页面会以 {url, error} 的形式出现在 results 中,并带有与上面相同的错误代码,且不产生任何费用。在一次整站抓取 (crawl) 中,如果某个分区遇到 blocked_by_robots,它会直接读取网站的其余部分。

任务结束时,统计错误代码。同一网站上出现大量 target_refused,说明该网站不接受自动化读取;出现大量 fetch_timeout,说明网站较慢,在任务选项中设置更长的 timeout 可能会有帮助。

错误:常见问题

被 robots.txt 阻止的页面会收费吗?

不会。1 积分 = 读取 1 个页面且无需浏览器;当页面需要浏览器渲染 JavaScript 时为 2 积分。失败的请求,或被网站 robots.txt 拒绝的请求,不扣费。本页上的其他所有错误同样如此。

如果我要求,你们能读取阻止你们爬虫的页面吗?

不能。我们尊重网站所有者的选择,且该政策对所有客户一致。如果你拥有该网站,请在 robots.txt 中为 WebReaderAPI 添加一条规则;爬虫页面说明了具体做法。

为什么一个存在的页面会返回 fetch_failed?

网站可能当时宕机、接受 TLS 连接缓慢,或对自动化客户端返回 404。请用退避策略重试一两次。如果持续失败,请在浏览器中检查该地址。

在被拒绝后,我的 agent 应该告诉用户什么?

简单说明即可,例如该网站不允许对该页面进行自动化读取。然后让模型使用其他来源。对大多数模型来说,把错误代码作为工具结果返回就足够了。

低至 US$0.07/千页 · 无需 KYC

获取 API 密钥