ほとんどのスクレイピングプロジェクトは、サイトを守っているものが何かを苦労しながら学んでいく。プロトタイプは月曜日には動いていたのに、火曜日にはチャレンジページが現れ、金曜日にはチームがヘッドレスブラウザを中心に作り直している。その多くは初日に知ることができたはずのものだ。ボット管理製品はサイトのレスポンスヘッダーやクッキーに目に見える痕跡を残しており、通常のリクエストを1回送るだけでそれを読み取ることができる。
私たちはまさにそれを行う簡単なルックアップツールを構築し、上位1,000ドメインのホームページに対して実行し、普通のHTTPクライアントに何が返されたかを記録した。本ガイドではその結果、コード、そしてプロジェクトを計画する際にその答えをどう使うかを共有する。
重要なポイント
- 1回のリクエストで多くのことが分かる。ベンダーのクッキーとヘッダーにより、読み込めた653件の上位サイトホームページのうち37%でボット防御またはエッジセキュリティのベンダーを特定でき、16%で実際に稼働中のボット管理製品を特定できた。
- Cloudflareが群を抜いて最も多く、ホームページの4分の1に存在し、Akamaiがそれに続いた。DataDome、AWS WAF、Imperva、HUMANの出現頻度ははるかに低く、DataDomeとHUMANは送信したすべてのリクエストをチャレンジまたはブロックした。
- Chromeを名乗る普通のHTTPクライアントは、ホームページの81%で応答を受け取り、10%でチャレンジされ、9%でブロックされた。ライブラリのデフォルトUser-Agentを使うと、ブロック率は16%に上昇した。
- ブラウザを装うことが裏目に出ることもある。ある大手小売業者の10か国の店舗サイトでは、正直なライブラリのUser-Agentには完全なホームページが返され、Chromeを名乗るリクエストには1〜2キロバイトのチャレンジページが返された。
- このルックアップは計画段階の入力として扱うべきである。何を予想すべきか、公式APIや許可を得るほうが良い手段かどうか、そしてプロジェクトの予算をどう組むべきかを教えてくれる。
1回のリクエストで分かること
ボット管理製品やエッジセキュリティ製品は、ウェブサイトの前面に立ちすべてのリクエストを検査することで機能する。そのために、それらの多くは訪問者のブラウザにクッキーを設定したり、レスポンスヘッダーを追加したりしており、その名前は安定していてしばしば文書化されている。
| ベンダー | 典型的な証拠 | 出典 |
|---|---|---|
| Cloudflare | 提供するすべてのものに付くcf-rayヘッダー、Bot ManagementまたはBot Fight Modeが有効な場合の__cf_bmクッキー、チャレンジページでのcf-mitigated: challenge | Cloudflareのドキュメント |
| Akamai | Akamai-GRNリクエスト番号ヘッダー、Bot Managerによる_abck、ak_bmsc、bm_szクッキー | ヘッダーについてはAkamaiのドキュメント、クッキーについてはサイトのクッキーポリシー |
| DataDome | datadomeクッキー、x-datadomeヘッダー | DataDomeのドキュメント |
| HUMAN | _px3、_pxhd、_pxvidクッキー | HUMANのドキュメント |
| Imperva | visid_incap_、incap_ses_、nlbi_クッキー | サイトのクッキーポリシー |
| AWS WAF | チャレンジ時のHTTP 202を伴うx-amzn-waf-action: challenge、aws-waf-tokenクッキー | AWSのドキュメント |
結果を読み解く際に重要な区別が2つある。コンテンツデリバリーネットワークの配下にあることと、ボット管理を稼働させていることは同じではない。cf-rayヘッダーはサイトがCloudflareを使用していることを意味するだけだが、__cf_bmクッキーはCloudflareのボット製品が訪問者を実際にスコアリングしていることを意味する。そして、シグネチャが見つからないことは何も証明しない。多くのサイトは独自の検出システムを運用しているか、最初のレスポンスには痕跡を残さない製品を使っている。
コード
以下のモジュールは1件のレスポンスを読み取り、検出したベンダーとその証拠、ボット管理製品が稼働中かどうか、そしてリクエストが得た結果(served、challenged、blocked)を返す。
import re
# Cookies that each vendor's bot or WAF product sets, and headers it adds. Cookies are the strongest evidence.
SIGNATURES = {
"Cloudflare": {"headers": ["cf-ray"], "cookies": [r"^__cf_bm$", r"^cf_clearance$"]},
"Akamai": {"headers": ["akamai-grn"], "cookies": [r"^_abck$", r"^ak_bmsc$", r"^bm_sz$", r"^bm_sv$"]},
"DataDome": {"headers": ["x-datadome"], "cookies": [r"^datadome$"]},
"HUMAN": {"headers": [], "cookies": [r"^_px(3|hd|vid|cvid|de)$"]},
"Imperva": {"headers": [], "cookies": [r"^visid_incap_\d+$", r"^incap_ses_", r"^nlbi_\d+$", r"^reese84$"]},
"AWS WAF": {"headers": ["x-amzn-waf-action"], "cookies": [r"^aws-waf-token$"]},
}
# Evidence of an active bot-management product, as opposed to only the CDN in front of the site.
BOT_PRODUCT_COOKIES = [r"^__cf_bm$", r"^cf_clearance$", r"^_abck$", r"^ak_bmsc$", r"^bm_sz$", r"^datadome$", r"^_px", r"^reese84$"]
def cookie_names(response):
"""Names of every cookie the response tried to set."""
return {c.split("=", 1)[0].strip() for c in response.raw.headers.getlist("Set-Cookie")}
def detect_stack(response):
"""Return {vendor: [evidence]} from one response's headers and cookies."""
headers = {k.lower() for k in response.headers}
cookies = cookie_names(response)
found = {}
for vendor, sig in SIGNATURES.items():
evidence = [f"header {h}" for h in sig["headers"] if h in headers]
evidence += [f"cookie {c}" for c in sorted(cookies) if any(re.match(p, c) for p in sig["cookies"])]
if evidence:
found[vendor] = evidence
server = response.headers.get("server", "").lower()
if server == "cloudflare":
found.setdefault("Cloudflare", []).append("server header")
if "akamaighost" in server:
found.setdefault("Akamai", []).append("server header")
return found
def bot_product_active(response):
"""True when a cookie shows a bot-management product, not just a CDN, handled the request."""
return any(re.match(p, c) for c in cookie_names(response) for p in BOT_PRODUCT_COOKIES)
def outcome(response):
"""Classify what a request got: served, challenged or blocked."""
body = response.text[:20000].lower()
if (response.headers.get("cf-mitigated", "").lower() == "challenge"
or response.headers.get("x-amzn-waf-action", "").lower() == "challenge"
or "captcha-delivery.com" in body):
return "challenged"
if response.status_code in (401, 403, 405, 429) or response.status_code >= 500 or "incapsula incident id" in body:
return "blocked"
return "served"
このコードはrequestsライブラリからのレスポンスに対して動作し、生のSet-Cookieヘッダーからクッキーを読み取るため、クライアントが通常保持しないクッキーも検出できる。ここでの「served」の意味に注意してほしい。これはチャレンジやブロックのシグナルが見つからなかったことを意味するに過ぎない。ページに実際のコンテンツが含まれていたことを証明するものではなく、そのギャップについては以下で改めて取り上げる。
上位1,000ドメインで分かったこと
2026年10月3日、Trancoランキングの上位1,000ドメインそれぞれのホームページに対し、Chrome User-Agent文字列を使った場合と、requestsライブラリのデフォルトを使った場合の両方で、ルーマニアの単一接続からリクエストを送信した。両方のリクエストは同じPython HTTPクライアントから送られており、JavaScriptを実行せず、ブラウザのネットワークフィンガープリントも持たない。653の異なるサイトがHTMLホームページを返した。残りはコンテンツネットワーク、APIホスト、ホームページを持たないその他のドメインだった。
| 検出されたベンダー | ホームページ数 | 稼働中のボット管理クッキーあり |
|---|---|---|
| Cloudflare | 162 (24.8%) | 79 |
| Akamai | 55 (8.4%) | 16 |
| AWS WAF | 14 (2.1%) | 0 |
| DataDome | 8 (1.2%) | 8 |
| HUMAN | 2 | 2 |
| Imperva | 2 | 0 |
| 上記のいずれか | 241 (36.9%) | 105 (16.1%) |
| 検出なし | 412 (63.1%) | 0 |
2つのベンダーが同時に検出されたホームページはわずか2件だった。AWS WAFの14サイトのうち10サイトは同一小売業者の国別店舗サイトだった。
リクエストが得た結果:
| リクエスト | Served | Challenged | Blocked |
|---|---|---|---|
| Chrome User-Agent、653ホームページ | 530 (81.2%) | 64 (9.8%) | 59 (9.0%) |
| ライブラリのデフォルトUser-Agent、650ホームページ | 500 (76.9%) | 49 (7.5%) | 101 (15.5%) |
そして、サイトを保護しているものごとに内訳を示す。両方のリクエストが完了したサイトのみを対象としている。
| 検出された防御 | サイト数 | Chrome User-Agent: challengedまたはblocked | ライブラリのデフォルト: challengedまたはblocked |
|---|---|---|---|
| Cloudflare | 162 | 54 | 63 |
| Akamai | 54 | 26 | 22 |
| DataDome | 7 | 7 | 7 |
| 検出なし | 411 | 19 | 53 |
結果が示すこと
上位ウェブのほとんどは普通のリクエストに応答する。 5つに4つのホームページが、Chromeを名乗る基本的なHTTPクライアントに対し、目に見えるチャレンジなしで応答した。ただし、ホームページはサイトの中で最も簡単なページである。検索結果や価格、チェックアウトのフローは通常、玄関口よりも厳重に保護されている。
単純なフィルタと本格的な製品では挙動が異なる。 検出可能なベンダーがないサイトでは、ライブラリのデフォルトUser-AgentがChrome文字列の約3倍の頻度でチャレンジまたはブロックされた(53回対19回)。これは単純なUser-Agentフィルタリングの特徴である。DataDomeを稼働させているサイトでは、両方のリクエストが毎回チャレンジまたはブロックされ、User-Agentによる違いはなかった。
偽りの身元は正直な身元より悪い結果を招くことがある。 その小売業者の10か国の店舗サイトは、Chromeを名乗るリクエストには1〜2キロバイトのチャレンジまたはプレースホルダーページで応答し、正直なライブラリのUser-Agentには678 KBから1.4 MBの完全なホームページで応答した。全10サイトで確認を繰り返したが、このパターンは変わらなかった。Chromeに見えない接続上でChromeのUser-Agent文字列が届くことは、TLSおよびHTTP/2フィンガープリンティングで説明した通り、それ自体がシグナルとなる。
ステータスの成功はページの成功を意味しない。 それらのプレースホルダーページのうち3件はHTTP 200で返され、チャレンジヘッダーもなかったため、ステータスのみのチェックではservedとしてカウントされてしまう。サイレント失敗率やサイトが偽のコンテンツやブロックされたコンテンツを返しているかどうかの見分け方で述べた通り、ステータスコードだけでなくコンテンツを検証すべきである。
ルックアップを使ってプロジェクトを計画する
ホームページだけでなく、実際に必要なページに対してルックアップを実行し、その答えに計画を形作らせよう。
| ルックアップが示すこと | 通常意味すること | 妥当な次のステップ |
|---|---|---|
| ベンダーなし、普通のリクエストがserved | そのページにはボット管理がほとんど、または全くない | 正直な識別情報と控えめなペースによる普通のHTTP、そして適切なヘッダー |
| CDNのみ、ボットクッキーなし | 稼働中のボットスコアリングのないエッジセキュリティ | 普通のHTTPだが、ボリュームが増えるにつれてチャレンジに注意する |
| ボット管理クッキーあり、リクエストはserved | 稼働中のスコアリングがこのリクエストを許可した | 規模が拡大するとチャレンジが発生することを予想し、ターゲットヘルススコアでモニタリングする |
| 最初のリクエストでchallenged | 自動化されたクライアントをフィルタリングするという意図的な判断 | まず公式APIやデータフィードを探し、次にヘッドレスブラウザが必要なタイミングに従ってブラウザが正当化されるかどうかを検討する |
| 最初のリクエストでblocked | 厳格なルール、多くの場合ネットワークや地域による | ページの裏にあるAPIを含め、データが別の方法で入手可能かどうか、またブロックが地域的なものかどうかを確認する |
このルックアップは意図についても何かを語る。稼働中のボット管理製品を運用しているサイトは、自動化されたアクセスを制御することを決定しており、それはrobots.txt、AIオプトアウト、予約シグナルで論じた通り、その利用規約やrobots.txtと合わせて検討する価値がある。多くのプロジェクトにとって、厳重に保護されたターゲットへの適切な対応は、エスカレーションではなくライセンス、パートナーシップ、または公式APIである。収集が適切である場合、保護されたサイトのためのエスカレーションラダーがコスト順に選択肢を説明している。
測定の限界
- ホームページのみ。 より深いページはしばしば異なる方法で保護されている。
- 1ネットワークから、それぞれ1回のリクエスト。 結果は国、ネットワークの評判、時間帯、リクエスト履歴によって異なる可能性がある。地域差については最も地域ブロックされている国で取り上げた。
- ブラウザなし。 多くのチャレンジは、JavaScriptを実行する実際のブラウザによって突破されるよう設計されており、私たちのクライアントは設計上それができなかった。
- シグネチャは見逃すことがある。 自前の検出システムや、最初のリクエストで痕跡を残さない製品は「検出なし」としてカウントされるため、実際に保護されているサイトの割合はより高い。
結論
1回の普通のリクエストで、スクレイピングプロジェクトを計画するために必要なことのほとんどが分かる。誰がサイトを守っているか、ボットスコアリングが稼働しているか、普通のクライアントがどう扱われるか。上位1,000ドメインでは、ほとんどのホームページが応答し、4分の1がCloudflareの配下にあり、6分の1が稼働中のボット管理製品を運用しており、最も厳格な製品は送信したすべてのリクエストをチャレンジまたはブロックした。
スクレイパーを書く前に、必要なページに対してこのルックアップを実行しよう。それがシンプルに進めてよいタイミング、ブラウザを計画すべきタイミング、そしてデータへの公式なルートを探すべきタイミングを教えてくれる。
出典と参考文献
- Cloudflare、Cloudflare cookiesおよびdetecting a challenge page response。
- Akamai、Global request number。
- DataDome、Cookies and stored data。
- HUMAN、Use of cookies and web storage。
- AWS、CAPTCHA and Challenge in AWS WAF。
- Tranco、list Q2K34。
- 2026年10月3日にShifterが上記のコードを使ってリクエストしたホームページ。