概要
WebReaderAPIのエラーコードの意味と、再試行すべきものはどれですか?
WebReaderAPIエラーは3つのグループに分かれ、いずれも課金されません。リクエストの問題(invalid_url、invalid_api_key、insufficient_credits)はお客様側で修正が必要です。サイトの決定(blocked_by_robots、blocked_by_site_policy、target_refused)は最終的なものです。一時的な障害(fetch_failed、fetch_timeout、concurrency_limit)はバックオフで再試行できます。クレジットは$0.07/1Kページから始まり、KYC不要の暗号資産で支払われます。
APIが返す可能性のあるエラーコードはどれですか?
本文は常に{"error": {"code", "message"}}で、拒否がサイトからのものである場合はtarget_statusが付加されます。
| HTTP | code | 原因 | 再試行しますか? |
|---|---|---|---|
| 400 | invalid_url | http または https ではない、長すぎる、またはプライベート・ローカル・クラウドメタデータのアドレスです | いいえ: URLを修正してください |
| 400 | invalid_parameter | パラメータの値がリストまたは範囲外です | いいえ: リクエストを修正してください |
| 401 | invalid_api_key | キーが未設定、誤り、または失効しています | いいえ: APIキーを確認してください |
| 402 | insufficient_credits | 残高にクレジットが残っていません | クレジットパック購入後 |
| 403 | blocked_by_robots | サイトの robots.txt が、WebReaderAPI またはすべてのロボットに対してこのページを許可していません | いいえ: 確定です |
| 403 | blocked_by_site_policy | サイトが Content-Signal: ai-input=no を宣言しています | いいえ: 確定です |
| 422 | unsupported_content | 読み取れない種類(動画、アーカイブ)または大きすぎるファイルです | いいえ |
| 429 | concurrency_limit | 同時に実行中のリクエストが多すぎます | はい、バックオフを付けて |
| 502 | target_refused | サイトが拒否しました: 403、429、チャレンジページ、またはログインウォールです。ステータスは target_status にあります | いいえ: 確定です |
| 502 | fetch_failed | DNS、TLS、接続エラー、またはサイトが 404 か 410 を返しました | 1~2回、バックオフを付けて |
| 504 | fetch_timeout | サイトが timeout 以内に応答しませんでした | はい、バックオフまたはより長いタイムアウトで |
robots.txt とサイトによる拒否が確定なのはなぜですか?
それはサイト所有者の判断であり、WebReaderAPI はそれに従うように作られているからです。当社はrobots.txtを尊重します。クローラーはWebReaderAPIとして名乗り、自身向けまたはすべてのロボット向けに書かれたルールに従い、サイトが禁止したページは読み取りません。拒否されたページは課金されません。サイトがrobots.txtでContent-Signal: ai-input=noを宣言している場合、そのページをあなたのために読み取ることはありません。
サイトが別の形でリクエストを拒否した場合、403、429、チャレンジページ、ログインフォームなどでは、target_refused とサイト自身のステータスが target_status に返ります。サイトが当社のリクエストを拒否した場合、その回答をそのまま返します。CAPTCHAを解いたり、アカウントにログインしたり、ブロックやペイウォールを回避しようとしたりすることは決してありません。同じ URL を1分後に再試行しても通常は同じ結果になるため、これらのコードは失敗ではなく結果として扱ってください。ログに記録して次に進みます。
ページが拒否された理由を確認するには?
サイトの /robots.txt を開き、WebReaderAPI を名指すグループ、またはすべてのロボットに適用される * グループを探してください。パスに一致する Disallow の行があれば blocked_by_robots の説明になります。Content-Signal: ai-input=no のような行は blocked_by_site_policy の説明になります。
robots.txt のコピーは最大24時間保持します。サイトがロボット向けにセクションを開放したばかりの場合、変更が反映されるまで最大1日かかることがあります。サイト所有者は、クローラーの動作と許可・ブロックの方法を クローラーページ で確認できます。
一時的な失敗はどのように再試行すべきですか?
concurrency_limit、fetch_timeout、そして1~2回だけ fetch_failed を再試行してください。新しい試行の前に待機し、待機時間を毎回2倍にし、並列ワーカーが同時に再試行しないよう少しランダムなジッターを加えます。4~5回試行したら停止してください。
concurrency_limit には、一度に送るリクエストを減らすのがより良い対処です: すべてのアカウントには同時に読み取れるページ数の上限があります。上限はダッシュボードに表示され、サポートで引き上げられます。fetch_timeout には、遅いサイトを再試行する前に timeout を最大60秒まで延ばしてください。失敗した試行は課金されないため、再試行に費用はかかりません。
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"))
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);
}
}
バッチやクロール内のエラーはどうなりますか?
1ページの失敗でジョブ全体が止まることはありません。そのページはresults内に{url, error}として、上記と同じコードとともに記録され、クレジットは消費されません。あるセクションでblocked_by_robotsに遭遇したクロールは、単にサイトの残りの部分を読み進めます。
ジョブ終了時にエラーコードを集計します。1つのサイトでtarget_refusedが多い場合は、そのサイトが自動リーダーを受け付けていないことを意味します。fetch_timeoutが多い場合はサイトが遅いことを意味し、ジョブオプションでtimeoutを長くすると改善する場合があります。
エラー: よくある質問
robots.txtでブロックされたページは課金されますか?
いいえ。1クレジット = ブラウザなしでページを1回読み取り。ページのJavaScriptレンダリングにブラウザが必要な場合は2クレジット。失敗したリクエスト、またはサイトのrobots.txtが拒否したリクエストは課金されません。このページに記載されている他のすべてのエラーも同様です。
依頼すれば、クローラーをブロックしているページも読めますか?
いいえ。私たちはサイト所有者の選択に従い、ポリシーはすべてのお客様に同じです。サイトを所有している場合は、robots.txtにWebReaderAPI用のルールを追加してください。方法はクローラーページに記載されています。
存在するページなのにfetch_failedが返るのはなぜですか?
サイトがダウンしていた、TLS接続の受け付けが遅かった、または自動クライアントに対して404を返していた可能性があります。バックオフを入れて1〜2回リトライしてください。それでも失敗し続ける場合は、ブラウザでアドレスを確認してください。
拒否された後、エージェントはユーザーに何と伝えるべきですか?
「このサイトはそのページの自動読み取りを許可していません」といったシンプルな内容で十分です。その後、モデルに他の情報源を使わせてください。エラーコードをツールの結果として返すだけで、ほとんどのモデルには十分です。