Cloudflareスクレイパーガイド:Scrapelessでページコンテンツを取得および検証する
Expert Network Defense Engineer
要約 (TL;DR):
- Cloudflare 向けスクレイパーは、取得後のコンテンツを必ず検証する必要がある。 HTTP リクエストが完了しても、アプリケーション側で使えるページデータが得られていない場合がある。
- Scrapeless Web Unlocker はレスポンス指向の手段である。 ワークフローで制御されたブラウザーセッションやページ操作が必要な場合は Agent Browser が適している。
- サービスのレスポンスとオリジンのレスポンスは別々の観測対象である。 API のヘッダーをターゲットサイトのヘッダーであるかのように扱わないこと。
- ソース固有の契約に基づいてレコードを受け入れる。 ページの同一性、期待されるコンテンツ、必須フィールド、および空の結果の意味を確認すること。
スクレイパーは、気付かないうちに商品ページの URL の下にチャレンジドキュメントを保存してしまうことがある。リクエストは完了し、パーサーはテキストを見つけ、結果のレコードも一見埋まっている。それでもなお、誤ったドキュメントを記述している可能性がある。
この Cloudflare スクレイパーガイドは、その「受け入れ境界」に焦点を当てる。ここでは Scrapeless Web Unlocker を使って HTML を取得し、Python 製の小さなバリデーターで、受け入れ可能なコンテンツとチャレンジ、あるいは不完全なレコードとを区別する。ローカル検証の例では説明用のページを明示的に使用しているが、認証が必要なターゲットを取得する場合は、自分自身のキーと許可されたソースが必要になる。
Cloudflare 向けスクレイパーが対処すべきこと
Cloudflare 向けスクレイパーは、許可されたターゲットコンテンツを取得し、異なるレスポンスを受け取ったときにそれを認識しなければならない。チャレンジ処理とデータ抽出は、この作業の別々の部分である。
Cloudflare は、想定していたリソースの代わりに挿入型のチャレンジページを返すことがある。その チャレンジページのレスポンスシグナル はオリジンヘッダー cf-mitigated: challenge を用い、チャレンジの Content-Type は text/html である。
このシグナルは、アプリケーションがオリジンレスポンスを観測できる場合に役立つ。マネージドな取得 API は、独自の JSON エンベロープとサービスヘッダーを返す場合がある。ターゲットのヘッダーを公開していないのであれば、API レスポンスにそれが含まれていないという事実だけでは、ターゲットがチャレンジを受けていないと断定できない。
利用可能なチャレンジシグナルと並行して、肯定的なコンテンツチェックを行うこと。期待される記事の見出しや商品識別子は、汎用的なフレーズが「ない」ことよりも、そのページが要求されたものであることを示す強い証拠になる。
HTTP の成功とコンテンツの成功は別物
HTTP の成功はプロトコル上の結果を示し、コンテンツの成功はレスポンスが収集タスクを満たしているかどうかを示す。HTTP レスポンスのセマンティクス は、あなたのプロダクトスキーマや記事の受け入れルールを定義していない。
サービスへのリクエスト、返却されたペイロード、抽出されたレコードを切り分けること。サービスレスポンスは JSON として正しくても、そのデータに不適切なページが含まれている場合がある。逆に、正当な検索ページがヒット 0 件であっても、それだけでブロックされているとは限らない。
| Layer | Question | Evidence to keep |
|---|---|---|
| API request | サービスはオペレーションを受理し、完了したか? | サービスのステータスとエンベロープ |
| Page identity | これは意図したページ、または許可された正準的な同等ページか? | 要求した URL と、利用可能な最終的な同一性 |
| Content | ページに必要なソース資料が含まれているか? | 見出し、マーカー、または補強となる文章 |
| Extraction | このタスクに必要なフィールドは有効か? | パース済みの値と検証結果 |
| Empty state | ソース自身が、レコードが存在しないことを示しているか? | ソース固有の「空の状態」を示す証拠 |
これらのレイヤーごとに異なる失敗理由を用いること。「レコードがない」という診断は、取得したドキュメントにそもそも要求されたページが含まれていない場合には不十分である。
Web Unlocker と Agent Browser の選択はオペレーション単位で行う
Web Unlocker は、ターゲット URL から出発し、コンテンツを取得するワークフローに適している。その レンダリング設定 により、ドキュメント化された jsRender フィールドを通じて HTML を要求できる。
Agent Browser は、ブラウザーセッションの制御、インタラクション、ページ状態をまたぐナビゲーションが必要なタスクに適している。アプリケーションが 1 回の取得レスポンスを消費するだけでなく、ページそのものを扱う必要がある場合にその手段を選択する。
各実装は、選択したプロダクトサーフェス内にとどめること。以下の例では Web Unlocker を使用している。チュートリアルの途中でブラウザーセッションを HTTP API に切り替えたり、すべての保護されたターゲットでの受け入れを保証したりはしない。
まずはソースでサポートされているアクセス方法から始めること。取得サービスは許可の付与ではなく、制限の背後にあるコンテンツを、クライアントが URL をリクエストできるというだけの理由で公開コレクションの対象とみなしてはならない。
前提条件とインストール
このリクエスト例には、Scrapeless の API キー、Web Unlocker へのアカウントアクセス、Python、および requests パッケージが必要です。バリデーターは Python の標準ライブラリのみを使用します。
SCRAPELESS_API_KEY を実行環境内で非公開に設定します。TARGET_URL には、想定される見出しと識別フィールドを事前に確認した公開またはその他の認可済みページを設定します。認証情報を出力したり、記事の出力レコード内に含めたりしないでください。
サービスを呼び出す前に、プロジェクトの環境に requests をインストールし、Web Unlocker クイックスタートを確認します。依存関係のバージョンは、プロジェクトのロックファイルまたは環境マニフェストに記録してください。ネットワーク処理にはサービスアカウントと許可された対象が前提条件です。この例では、認証されたキャプチャが行われているとは主張しません。
最小限のレンダリング HTML リクエストを送信する
現在の Web Unlocker のレンダリングリクエストは、v2 エンドポイントとネストされた jsRender オブジェクトを使用します。サービスのエンベロープと分離して返された HTML を保存し、両方を検査できるようにします。
注記: このリクエストには、実際の Scrapeless API キー、アカウントアクセス、および認可された
TARGET_URLが必要です。現在のリクエストドキュメントに照らして確認されていますが、この例では有料ターゲットに対して実行されていません。
python
import json
import os
from pathlib import Path
import requests
target = os.environ["TARGET_URL"]
response = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={
"actor": "unlocker.webunlocker",
"proxy": {"country": "ANY"},
"input": {
"url": target,
"jsRender": {
"enabled": True,
"response": {"type": "html"}
}
}
},
timeout=60
)
response.raise_for_status()
envelope = response.json()
html = envelope.get("data")
if envelope.get("code") != 200 or not isinstance(html, str):
raise ValueError("Expected a successful HTML envelope")
Path("page.html").write_text(html, encoding="utf-8")
print(json.dumps({"requested_url": target, "html_characters": len(html)}))
API エンベロープのチェックは、ドキュメント化されたレスポンス構造を確立します。これはまだ page.html がアプリケーションに必要なソースを含んでいることを確立するものではありません。記録された URL は要求された URL です。最終的な遷移先を確認せずに、それを final_url に改名してはいけません。
パース前にコンテンツ契約を定義する
コンテンツ契約は、ページを受け入れるために必要な最小限の証拠を定義します。記事であれば、意図された見出しとソース識別子を必要とするかもしれません。プロダクトタスクには、その商品のフィールドとバリアントのフィールドがそれぞれ必要になります。
契約は、実際に検査した対象ページから作成してください。推測した CSS クラスだけを成功条件にしないようにします。安定した識別子、文書化された構造化フィールド、耐久性のある URL パターンは、ソースが提供している場合に有用です。
任意フィールドをどのように表現するかを決めてください。ある記事ソースでは、著者の欠如が許容されることもありますが、商品識別子が欠けていると商品レコード全体が利用不能になる場合もあります。欠落したフィールドを創作したテキストで埋めるのではなく、その理由を記録してください。
Start Scraping with Scrapeless
Scrapeless でウェブスクレイピングと自動化ワークフローを強化しましょう!
今すぐサインアップして $5 分の無料クレジット を獲得 — クレジットカードは不要です。今すぐ Scrapeless ダッシュボードで無料クレジットを請求しましょう。
簡易なコンテンツ受け入れチェックを実行する
ローカルの受け入れチェックは、明示的なチャレンジシグナルを却下し、期待されるページの肯定的な証拠を要求するべきです。以下の完全なスクリプトは、例示的な HTML フィクスチャに対してこのルールを適用します。
見出しと data-record-id マーカーは、これらのフィクスチャに属するものです。任意の保護されたウェブサイトに対するセレクタとして公開されているものではありません。スクリプトは、バリデーション動作を確認するためにローカルで実行されました。その出力は、ライブの Cloudflare 取得結果ではありません。
python
import json
from html.parser import HTMLParser
class Signals(HTMLParser):
def __init__(self):
super().__init__()
self.heading = []
self.ids = []
self.in_heading = False
def handle_starttag(self, tag, attrs):
if tag == "h1":
self.in_heading = True
marker = dict(attrs).get("data-record-id")
if marker:
self.ids.append(marker)
def handle_endtag(self, tag):
if tag == "h1":
self.in_heading = False
def handle_data(self, data):
if self.in_heading:
self.heading.append(data)
def assess(html, origin_headers, expected_heading):
headers = {k.lower(): v for k, v in origin_headers.items()}
if headers.get("cf-mitigated") == "challenge":
return {"status": "quarantined", "reason": "origin_challenge"}
signals = Signals()
signals.feed(html)
heading = " ".join(" ".join(signals.heading).split())
if heading != expected_heading or not signals.ids:
return {"status": "rejected", "reason": "content_contract"}
return {"status": "accepted", "heading": heading, "ids": signals.ids}
# Illustrative fixtures; these are not fetched target pages.
fixtures = [
("article", '<h1>Public Article</h1><main data-record-id="demo-a"></main>', {}),
("challenge", '<h1>Challenge</h1>', {"cf-mitigated": "challenge"}),
("incomplete", '<h1>Public Article</h1>', {})
]
print(json.dumps({name: assess(html, headers, "Public Article")
for name, html, headers in fixtures}))
記事フィクスチャは受け入れられ、明示的なチャレンジフィクスチャは隔離され、識別子の欠けたフィクスチャは拒否されます。これは、示された入力に対するローカルブランチの動作を証明します。サービスキャプチャを評価する前に、実際のソースに合わせて契約を調整してください。
より複雑なフィールド選択については、この受け入れ/拒否の判断ロジックと抽出ロジックを分離してください。HTML 抽出チュートリアルでは、パースレイヤーについて説明しています。
空の結果と利用不能なコンテンツを区別する
有効な空結果には、ソースが空である状態を示す肯定的な証拠が必要です。セレクタ結果が空であるという事実だけでは、その証拠にはなりません。
検索ページでは、ドキュメント化された「結果なし」マーカーや、その他ソース固有の条件を確認してください。記事であれば、見出しが欠けている場合は、通常は空の記事というより、不完全または不適切なキャプチャです。これらの区別はレコード内に保持してください。
| 観察 | 有用な解釈 | 次の確認 |
|---|---|---|
| 明示的なオリジンチャレンジシグナル | チャレンジレスポンス | 取得パスと許可されたアクセスパス |
| 期待されるタイトルだが識別子が欠如 | 不完全なレコード | ソースマークアップと抽出契約 |
| 一致する要素なし | 未解決 | ページの正体、レンダリング、セレクタ |
| ソースの空状態が確認済み | 有効な空 | 空状態の証拠を保存 |
| 意図したソースと有効なフィールド | 受け入れ可能なコンテンツ | 下流での保存と分析 |
| すべての拒否された取得結果を Cloudflare ブロックとしてラベリングすることは避けてください。セレクターの変更、地域別リダイレクト、誤った開始 URL などによっても、同様にレコードが存在しない結果が生じる可能性があります。 |
出力と取得証拠の保持
受け入れられたレコードには、なぜそれが受け入れられたのかを説明できるだけの元データを保持する必要があります。要求された URL、利用可能な最終的な同定情報、取得時刻、抽出ルール、検証ステータス、および必要な値を保存してください。
観測結果を、それを生み出した活動と結び付けておくために、ソースプロビナンス を利用してください。拒否されたレコードについては、その理由を保持しつつ、その内容を成功した業務レコードとして転送しないようにします。
あなたのアプリケーションはこのスキーマの所有者です。サービスのレスポンスフィールドと、あなたの正規化済みレコードは別個の契約であるため、それらを同一視するのではなく、変換内容を文書化してください。
制限と責任ある収集
Cloudflare スクレーパーは、ソースが許可するアクセス範囲と、選択した取得経路の制約を尊重しなければなりません。このワークフローは、普遍的な成功率や、非公開ページへのアクセスを約束するものではありません。
収集を行う前に、ソースの利用規約と robots 排除ルール を確認してください。対象リストは有限に保ち、タスクに必要なデータの収集に限定しましょう。
まずは、許可された 1 つのターゲットから開始します。ホストあたり 3 ワーカー以下といった小さな同時実行数の上限は、この例におけるアプリケーションポリシーであり、Scrapeless サービスの制限ではありません。ソースの許可、受け入れられたコンテンツ、および運用コストが確認されてから、拡張を検討してください。
結論
有用な Cloudflare スクレーパーとは、ソースとフィールドがタスクのチェックを通過したレコードを返すものです。リクエストはあくまで取得ステップに過ぎません。
レスポンス指向の収集には Web Unlocker を使用し、サービスのペイロードを保持したうえで、抽出したフィールドを受け入れる前に、意図したページであることを検証してください。チャレンジ状態、不完全状態、妥当だが空の状態を区別して扱いましょう。
Web データの検証を始めますか?
Scrapeless を使って許可されたコンテンツチェックを構築し、受け入れたレコードを 現在の料金 と照らし合わせて評価してください。Telegram で抽出契約について議論することもできます。
FAQ
Q: Cloudflare で保護されたウェブサイトのスクレイピングは合法ですか?
保護技術そのものは、そのページを収集する許可を確立するものではありません。スクレイパーを使用する前に、ソースの利用規約、適用される要件、およびあなたの権限を確認してください。
Q: この Web Unlocker ワークフローには、別途設定されたプロキシが必要ですか?
ここで示したリクエストは、マネージドな取得経路と、そのドキュメント化された国別フィールドを使用しています。個別に割り当てられたプロキシ認証情報が必要になるのは、あなた自身のクライアントがスタンドアロンのプロキシ製品を利用する場合だけです。
Q: HTTP 200 応答は、スクレイピングの成功を証明しますか?
HTTP 200 応答は、要求したビジネスコンテンツが取得できたことを証明するものではありません。レコードを受け入れる前に、ページの同定情報、ペイロード、および必要なフィールドを確認してください。
Q: ワークフローはいつ Agent Browser を使用すべきですか?
タスクに制御されたブラウザセッションやページ状態との対話が必要な場合は、Agent Browser を使用してください。レスポンス指向のタスクであれば、単一のコンテンツレスポンスだけで十分なことが多くあります。
Q: ページセレクターがマッチしなくなったとき、何を変更すべきですか?
ソースのマークアップと必要なフィールドを再確認し、抽出契約を更新してください。セレクターの結果が得られない状態は、ページの同定情報とコンテンツを確認するまでは未解決のままにしておくべきです。
Q: この例では、どの程度の同時実行数を使用すべきですか?
有限なソース集合から始め、ホストあたり 3 ワーカー以下とすることを、この例の収集ポリシーとします。ソースの許可と観測された運用状況が、その後の拡張の是非を左右すべきです。
Q: このワークフローは AI エージェントなしでも実行できますか?
HTTP リクエストと Python による検証は、AI エージェントなしでも実行できます。決定的なチェックが完了した後で、エージェントが受け入れられたレコードを利用することも可能です。
Q: 要求された URL は正規 URL として保存すべきですか?
要求された URL は、最終的または正規のページ同定情報とは別に保存してください。正規値は、取得過程やソースコンテンツによって実際に確立された場合にのみ使用します。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



