KYC不要 — 身分証もセルフィーも書類も不要。暗号資産で支払い:BTC, USDT, USDC, ETH, SOL, LTC
言語: 日本語
APIキーを取得

ドキュメント · Python

PythonでWebページをMarkdownに変換

ほとんどのPythonプロジェクトをカバーする4つのレシピ:requestsで1ページを読み取る、httpxで複数ページを並行して読み取る、リストをバッチジョブに渡す、Markdownをベクトルストア用にチャンク分割する。

最終更新:

概要

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文字で上限を設定し、呼び出しにかかったコストを出力します。

requests:1ページ
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を返します。課金はされませんが、ページは読み取られません。セマフォを上限以下に保つほうが、リトライするより簡単です。

httpx:最大10ページを同時実行
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つのアドレスが重複を作らないようにします。

見出しでMarkdownをチャンク化する
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秒にすると遅いサイトとレスポンスのための余裕ができます。

$0.07/1Kページから · KYC不要

APIキーを取得