🎯 カスタマイズ可能で検出回避型のクラウドブラウザ。自社開発のChromiumを搭載し、ウェブクローラーAIエージェント向けに設計されています。👉今すぐ試す
ブログに戻ります

クロードウェブスクレイピング:nullableステータスのテーブルをクリーンなJSONに変換する

Sophia Martinez
Sophia Martinez

Specialist in Anti-Bot Strategies

29-Jul-2026

TL;DR:

  • Claudeはnullableデータをクリーンに読み取ります。そして、ライブ実行でそれが証明されました。 レンダリングされたシーズン統計テーブルを与えられ、anthropic/claude-haiku-4.5は1回の呼び出しで25チームのシーズン記録を返し、1990-91 NHLシーズンでまだ存在しない統計に対しては推測値を与えるのではなく、nullを正しく書き込みました。
  • APIはテキスト入力、テキスト出力 — ネットワークには一切触れません。 レンダリング、セッション、アクセスの課題はフェッチレイヤーに属し、このガイドのものはScrapeless Universal Scraping APIを通じて1ページあたり1つのPOSTです。
  • JSONモードには、解析エラーを引き起こす前に知っておく価値のある本当の特異性があります。 OpenRouter経由のClaudeは、response_formatが設定されていても、構造化出力を```jsonフェンスにラップしてしまうことがあります — 以下の抽出スクリプトは、防御的にこれを削除します。
  • 現在の安価なレベルの命名の移動。 ライブOpenRouterモデルカタログでは、claude-haiku-4.5が古いclaude-3-haikuラインよりも能力において格段に上でありながら、適度なトークン単位のプレミアムがかかります;このガイドはメモリから名前を想定するのではなく、カタログを直接チェックしました。
  • Anthropicキーがまだない?同じリクエストがOpenRouterを通じて実行されます。 このガイドはanthropic/claude-haiku-4.5でライブに実行し、キャプチャした出力を示しています。
  • フェッチ側で無料で始めることができます。 app.scrapeless.comでScrapeless APIキーを作成してください。

Claudeはウェブサイトをスクレイピングできますか?

Claudeは解析しますが、取得はしません。ページテキストとスキーマを与えれば、クリーンで型付けされたレコードを返します — それには、フィールドが実際に欠如している場合を知るというより難しいケースも含まれますが、信じられそうなゼロを作り出すことはありません。特定のURLを取得し、ページを構築するJavaScriptを実行し、セッションを保持したり、スクレイピングジョブのボリュームでアクセスの課題をクリアすることはできません。すべての動作する「Claudeウェブスクレイピング」セットアップは、そのためのフェッチレイヤーとモデルを別々に組み合わせます。

このサイトの以前のガイド、Web Scraping with Claude AIでは、Claudeとスクレイパーを組み合わせる10の異なる方法を調査しています。このガイドはより狭く、具体的です:1つの実際のターゲット、1つのライブで実行された抽出、1つのスキーマ、実際のキャプチャされた出力。幅広い質問がどの言語モデルを解析に使用するかということであれば、LLMスクレイパーの説明がそのカテゴリーをカバーしています。

インストール

Anthropic SDKはネイティブパスをカバーし、openaiはOpenRouterパスをカバーし、requestsはフェッチレイヤーをカバーします:

bash Copy
pip install "anthropic==0.120.0" "openai==2.48.0" requests

設定

bash Copy
export ANTHROPIC_API_KEY="sk-ant-your_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"

時々欠落するフィールドを持つページをフェッチ

デモターゲットは公開スポーツ統計のサンドボックスです:NHLチームごとのシーズンごとの行、リーグが1990年代半ばまで追跡しなかった統計 — 延長戦での敗北 — を含んでいます。その時点以前の行はセルを空白に残し、データを生成するのかそれとも欠如していると報告するのかの真のテストとなります。1回のPOSTでUniversal Scraping APIがページを返し、レンダリングとプロキシルーティングはサーバー側で処理されます:

python Copy
# fetch_teams.py — Scrapelessを介してチーム統計ページを取得
import os

import requests

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={
        "actor": "unlocker.webunlocker",
        "input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
    },
    timeout=120,
)
resp.raise_for_status()
html = resp.json().get("data", "")
print(f"取得した文字数: {len(html):,} 文字")
print("チーム行:", html.count('<tr class="team">'))

with open("page.html", "w", encoding="utf-8") as f:
    f.write(html)

この実行では、サンドボックスの最初の結果ページに25のチーム行が印刷されます — これは、公に利用可能なウェブスクレイピング練習サイトによって維持されているライブターゲットであり、このサイトの他のLLM解析ガイドで使用される引用や書籍のサンドボックスとは異なります。

基本的な実装:Claudeを抽出器として

ネイティブメッセージAPIは、output_configを通じてJSONスキーマを直接受け取り、現在のAPIリファレンスでは旧式のプレフィルベースのJSONトリックの代替として文書化されています — 詳細はAnthropicの構造化出力ガイドを参照してください。現在の非レガシークラウドティアの最も安価なモデルはclaude-haiku-4-5です。ネイティブモデルIDはハイフンを使用しており、OpenRouterがリストするclaude-haiku-4.5スラグとは異なります。

注: このブロックにはクレジット付きのANTHROPIC_API_KEYが必要です — このガイドが前提としない唯一の条件です。次のセクションでは、OpenRouterを通じて同一の抽出をライブで実行し、出力をキャプチャします。

python Copy
# extract_claude.py — ネイティブクラウド抽出(ANTHROPIC_API_KEYが必要)
import json

from anthropic import Anthropic

client = Anthropic()  # 環境からANTHROPIC_API_KEYを読み込みます

page_html = open("page.html", encoding="utf-8").read()

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=6000,
    system="このテーブルからすべてのチームシーズン行を抽出してください。ot_lossesは、セルが空白のときにnullです。",
    messages=[{"role": "user", "content": page_html}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "teams": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "year": {"type": "integer"},
                                "wins": {"type": "integer"},
                                "losses": {"type": "integer"},
                                "ot_losses": {"type": ["integer", "null"]},
                                "win_pct": {"type": "number"},
                                "goals_for": {"type": "integer"},
                                "goals_against": {"type": "integer"},
                            },
                            "required": ["name", "year", "wins", "losses", "ot_losses", "win_pct", "goals_for", "goals_against"],
                            "additionalProperties": False,
                        },
                    }
                },
                "required": ["teams"],
                "additionalProperties": False,
            },
        }
    },
)

text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)
print(f"抽出されたチーム数: {len(data['teams'])} ")

output_config.formatは、応答がスキーマに対してJSONとして解析されることを保証します — フェンスストリッピングなし、プレフィルの回避策なし。

Anthropicキーがありませんか?OpenRouterを通して実行します

OpenRouterは、OpenAI互換のチャット補完機能の背後にクラウドを公開しています。これは、このガイドが実際に実行したバージョンで、フェッチと抽出を1つの自己完結型スクリプトで行いました:

python Copy
# extract_openrouter.py — 同じ抽出をOpenRouter経由で実行
import json
import os

import requests
from openai import OpenAI

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={
        "actor": "unlocker.webunlocker",
        "input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
    },
    timeout=120,
)
resp.raise_for_status()
page_html = resp.json().get("data", "")

client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])

completion = client.chat.completions.create(
    model="anthropic/claude-haiku-4.5",
    temperature=0,
    max_tokens=6000,
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": 'このテーブルからすべてのチームシーズン行を抽出してください。JSONのみで応答してください: '
            '{"teams":[{"name":str,"year":int,"wins":int,"losses":int,"ot_losses":int|null,'
            '"win_pct":number,"goals_for":int,"goals_against":int}]}. '
            "ot_lossesは、セルが空白のときにnullです。",
        },
        {"role": "user", "content": page_html},
    ],
)

raw = completion.choices[0].message.content.strip()
if raw.startswith("```"):
    raw = raw.split("```")[1]
    if raw.startswith("json"):
        raw = raw[4:]
data = json.loads(raw)
print(f"抽出されたチーム数: {len(data['teams'])} ")
print(json.dumps(data["teams"][0], ensure_ascii=False))

ライブ実行では、すべての25チームシーズンが取得され、最初の記録:

text Copy
抽出されたチーム数: 25 
{"name": "ボストン・ブルーインズ", "year": 1990, "wins": 44, "losses": 24, "ot_losses": null, "win_pct": 0.55, "goals_for": 299, "goals_against": 264}

ot_lossesは1990-91シーズンでnullに戻りました — この年はNHLがその統計を追跡し始める前のことです — 25行すべてが宣言された型にパースされ、ポストホックキャスティングはありませんでした。フェンスのストリッピングはここで重要です: それなしでは、json.loadsはこのモデルのOpenRouter出力で完全に失敗します、たとえresponse_formatが設定されていても。全体の呼び出しは: 13,365トークンです。

高度なパターン

  • アグリゲーターパスでのフェンス出力に対する防御。 ネイティブのoutput_config.formatパスはフェンスなしでスキーマ通りのJSONを強制します; OpenRouterのチャット補完パスはモデル間で同じ保証をしないので、どのモデルにルーティングしても、パース前に先頭の`ブロックをストリップしてください。
  • nullの可否を明示的に記述する。 スキーマ単体("ot_losses": {"type": ["integer", "null"]})とシステムプロンプトの一文が、モデルが0を発明する代わりに正しいnullを生成した要因です。いずれかを省略した場合は、フィールドを信頼する前に再テストしてください。
  • 1回の呼び出しにつき1ページを保持する。 ページの境界は自然なレコードの境界です; 数ページを1つのプロンプトにバッチすると、1チームの統計が終わり、次のチームが始まる場所がぼやけます。
  • スケールアップする前にトークンを測る。 上記の実行は25行のページに対して13,365トークンかかりました — 実際のページとフィールド数で掛け算してからページ容量にコミットしてください、請求書が届いてからではありません。

トラブルシューティング

  • json.loadsがOpenRouterレスポンスに対してJSONモードを設定しても失敗する。 先頭に`jsonフェンスがあるか確認し、それをパース前にストリップしてください — このガイドのライブ実行は正確にその修正を必要としました。
  • 時には欠落すべきフィールドが常に0またはプレースホルダーとして返ってくる。 スキーマとプロンプトでフィールドがnullableであり、何が条件でnullになるかを明示的に述べてください; モデルは、そう指示されない限り空白を埋めることがデフォルトです。
  • ページから数行だけが得られ、数十行がある場合。 まずは取得したHTMLのカウントを確認してください; 薄い取得は描画またはアクセスの問題であり、抽出プロンプトでは解決できません。
  • 出力が同一実行間で漂流する。 OpenRouterパスでtemperature=0を設定してください; 抽出は転写作業であり、ゼロ以上の温度は言い換えを招きます。

結論

クロードの構造化出力パスは、型付きJSONを返す際の推測を排除します — ネイティブAPIのoutput_config.formatはプレフィルのトリックを完全にスキップし、より許容的なOpenRouterチャット補完も、応答がマークダウンのフェンスから守られた後、正しくnullフィールドを持つ25のチームシーズン行をリアルページから引き出しました。クロードが提供しないのはページ自体です: 各ページごとにサーバーサイドのPOSTを1回実行し、レンダリングとアクセスのために構築されたフェッチレイヤーを通じて、レコードが最初に抽出可能になるのです。

クロードにリアルページを供給する準備はできましたか?

ここでのフェッチレイヤーは、ユニバーサルスクレイピングAPIです — プランとリクエストボリュームは料金ページにあり、すべてのunlocker.webunlockerパラメータは開発者ドキュメントにあります。無料プランでキーを作成し、app.scrapeless.comの2つのスクリプトがそのまま実行されます。

FAQ

Q: クロードは自分でウェブサイトをスクレイプできますか?

いいえ。クロードAPIは言語モデルのエンドポイントであり、提供されたテキストから抽出しますが、HTTPリクエストを発行したり、JavaScriptをレンダリングしたり、セッションを保持したりすることはできません。すべての稼働するセットアップは、忠実なページコンテンツを返すフェッチレイヤーと組み合わせています。

Q: ウェブスクレイピング抽出に使用すべきクロードモデルはどれですか?

ネイティブAPIのclaude-haiku-4-5(OpenRouterではclaude-haiku-4.5) — このガイドのライブ実行ではこれを使用し、正しくnullフィールドを持つ25のレコードをすべて返しました。現在最も安価な非レガシーのクロードティアです; 古いclaude-3-haiku名は以前の世代で、まだリストされていますが、現在の推奨ではありません。

Q: なぜ私のクロードJSON出力はJSONモードをオンにしてもパースに失敗するのですか?

OpenRouterアグリゲーターパスでは、クロードは```jsonマークダウンフェンスで出力をラップすることができます、たとえresponse_formatjson_objectで設定されていても。パースを呼び出す前に先頭のフェンスをストリップするか、ネイティブAPIのoutput_config.formatを使用してください。これは、その失敗モードなしでスキーマ通りのJSONを強制します。
Q: クロードは時々ソースページから欠落しているフィールドをどのように処理しますか?

正しく、スキーマとプロンプトがそう示しているときに。 このガイドのスキーマは ot_losses["integer", "null"] とマークしており、システムプロンプトは null 条件を示しています。 実行結果は、統計が存在しないシーズンに対して値を捏造するのではなく null を返しました。

Q: クロードを使ったスクレイピングは合法ですか?

抽出レイヤーは収集ルールを変更しません。 公開ページのみを取得し、サイトの利用規約と ロボット排除プロトコル によって標準化されたロボットディレクティブを尊重し、ボリュームの制限を守り、適用法の下で個人データを処理してください — さらに、モデル側の Anthropic の利用ポリシーに従ってください。

Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。

最も人気のある記事

カタログ