概要
PythonでWebページをMarkdownに変換するにはどうすればよいですか?
WebReaderAPIをrequests.get("https://webreaderapi.com/api/v1/read", params={"url": ...})とAuthorizationヘッダーのキーとともに呼び出します。JSONの応答にはページの本文がmarkdownで入っています。複数ページには、セマフォ付きのhttpx.AsyncClientまたはバッチジョブを使います。1ページ1クレジットで、2026年10月時点で$0.07/1Kページから利用できます。KYC不要、暗号資産で支払い。
何をインストールする必要がありますか?
Python 3.9以降とHTTPクライアントが1つ必要です。1ページずつ読み取るスクリプトにはrequestsで十分です。httpxは多数のページを並行処理する非同期サポートを追加します。当社のパッケージは不要です:APIはプレーンなHTTPSとJSONです。
キーはコードに含めないでください。以下のすべての例と同様に、WEBREADER_API_KEY環境変数から読み取り、プロジェクトごとに専用のキーを発行すれば、他のキーに触れずに1つだけを失効できます。
pip install requests httpx
export WEBREADER_API_KEY="your-key"requestsで1ページを読み取るにはどうすればよいですか?
urlと任意のオプションをクエリパラメータとして送信し、HTTPエラーで例外を発生させ、必要なフィールドを読み取ります。この例では本文を保持し、モデル用に20,000文字で上限を設定し、呼び出しにかかったコストを出力します。
import os
import requests
API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}
def read(url, **options):
resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
if resp.status_code != 200:
error = resp.json()["error"]
raise RuntimeError(error["code"] + ": " + error["message"])
return resp.json()
page = read("https://example.com/docs/getting-started", max_chars=20000)
print(page["title"], page["word_count"], "words", page["credits_used"], "credit(s)")
print(page["markdown"][:500])
httpxで複数ページを一度に読み取るにはどうすればよいですか?
httpx.AsyncClientを1つと、同時実行数の上限以下に設定したasyncio.Semaphoreを使います。すべてのアカウントには同時に読み取れるページ数の上限があります。上限はダッシュボードに表示され、サポートで引き上げられます。1ページのエラーで他のページが止まるべきではないため、この例では例外を発生させる代わりに結果と並べてエラーを収集します。
上限を超えると、APIは429 concurrency_limitを返します。課金はされませんが、ページは読み取られません。セマフォを上限以下に保つほうが、リトライするより簡単です。
import asyncio
import os
import httpx
API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}
async def read_one(client, sem, url):
async with sem:
resp = await client.get(API, params={"url": url, "max_chars": 50000})
body = resp.json()
if resp.status_code == 200:
return url, body, None
return url, None, body["error"]["code"]
async def read_many(urls, limit=10):
sem = asyncio.Semaphore(limit)
async with httpx.AsyncClient(headers=HEADERS, timeout=75) as client:
return await asyncio.gather(*(read_one(client, sem, u) for u in urls))
if __name__ == "__main__":
urls = [f"https://example.com/blog/post-{n}" for n in range(1, 21)]
for url, page, error in asyncio.run(read_many(urls)):
print(url, "->", error or page["title"])
代わりにバッチジョブはいつ使うべきですか?
最大100件のURLリストがあり、各ページを準備ができた瞬間に取得する必要がない場合です。バッチジョブは当社側で実行されるため、あなたのスクリプトは接続を開いたままにせず、リトライ処理も行いません。POST https://webreaderapi.com/api/v1/batchで開始し、バッチとクロールのページに示すカーソルループでジョブから結果を収集します。
サイト全体の場合は、代わりにクロールを開始し、sitemapとリンクをたどらせます。同じページに、ページごとに1つの.mdファイルを書き出す完全なループがあります。
import os
import requests
urls = [f"https://example.com/changelog/{year}" for year in range(2020, 2027)]
resp = requests.post(
"https://webreaderapi.com/api/v1/batch",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
json={"urls": urls, "options": {"format": "markdown", "main_content": True}},
timeout=30,
)
resp.raise_for_status()
print("job:", resp.json()["job_id"])
RAG用にMarkdownをチャンクに分割するにはどうすればよいですか?
まず見出しで分割し、次にサイズで分割します。見出しはトピックが変わる位置を示すため、見出しから始まるチャンクはレトリーバーがマッチしやすくなります。各チャンクのメタデータにページタイトルと見出しパスを保持してください。モデルが正しいセクションを引用するのに役立ちます。
以下の関数は追加のライブラリを必要としません。#から###の見出しで分割し、その後、長いセクションを空行で分割して文字数予算内に収めます。各チャンクとともにfinal_urlを保存し、同じページに至る2つのアドレスが重複を作らないようにします。
import re
def chunk_markdown(page, max_chars=2000):
"""Split a /read response into chunks with a heading path and the source URL."""
chunks, path, buf = [], [], []
def flush():
text = "\n".join(buf).strip()
while text:
part = text[:max_chars]
cut = part.rfind("\n\n")
if len(text) > max_chars and cut > max_chars // 2:
part = part[:cut]
chunks.append({"text": part.strip(), "section": " > ".join(path), "title": page.get("title"), "source": page["final_url"]})
text = text[len(part):].strip()
buf.clear()
for line in page["markdown"].splitlines():
m = re.match(r"^(#{1,3}) (.+)", line)
if m:
flush()
level = len(m.group(1))
path[:] = path[: level - 1] + [m.group(2).strip()]
buf.append(line)
flush()
return chunks
Pythonジョブのコストはいくらですか?
リクエストではなくページを数えます。1クレジット = ブラウザなしでページを1回読み取り。ページのJavaScriptレンダリングにブラウザが必要な場合は2クレジット。失敗したリクエスト、またはサイトのrobots.txtが拒否したリクエストは課金されません。ブラウザを使わずに2,000件のドキュメントページを読むと約2,000クレジットを使用し、これは10,000ページ($3.75)で5回分以上をカバーします。プリペイドクレジットに有効期限はありません。未使用のページは使うまでアカウントに残りますため、月に1回実行するパイプラインは読んだ分だけを使用します。
Python:よくある質問
公式のPythonパッケージはありますか?
いいえ。このページのコードだけで十分です。WebReaderAPIはプレーンなHTTPS APIであり、requestsやhttpxはそれを直接扱えます。
robots.txtが拒否するページはどう処理すればよいですか?
エラーコードを確認してください。blocked_by_robotsとtarget_refusedは最終的で無料です。記録して続行してください。エラーページには、変化しうるコードのみをリトライするリトライヘルパーがあります。
JupyterノートブックからAPIを使えますか?
はい。requestsのレシピはそのまま動作します。ノートブックでは、イベントループがすでに実行されているため、await read_many(urls)がasyncio.runの代わりになります。
クライアントのタイムアウトはどれを使うべきですか?
API自体より少し長めにしてください。デフォルトは30秒、最大は60秒なので、クライアント側で75秒にすると遅いサイトとレスポンスのための余裕ができます。