AIクローラーは通すbot対策、WAF2段の設計
GA4を開いたら、直近28日のセッションが1,834でした。前の28日は693です。2.6倍。よくやった、と思いました。
国別に割って、5秒で撤回しました。
前提: 見分け方は前編に書きました
GA4の国別レポートで、シンガポールが682セッション・エンゲージ6・平均滞在0.6秒。682のうち681が desktop / Chrome の一点に固まり、着地先は書籍ページを端から均等に踏む形でした。セッションの37%がこれです。
この現象の分解は前編のアクセス2.8倍の正体、クローラー3種の見分け方に書きました。GA4がUser-Agentを持っていないこと、Cloudflareのログで3種類(AI検索・名乗らないもの・攻撃スキャン)に割れること、AI系は全部UAに自分の名前を書いていること。見分け方はそちらです。この記事は、見分けたあとに何を設定するかの話になります。

セッションの棒とエンゲージの棒を重ねると、並び順が入れ替わります。セッションで数えるとシンガポールが1位、エンゲージで数えると日本とブラジルしか残りません。設定を決めるのに要る数字だけ、この先に置き直します。
止めたくないものが、すでに読者を連れてきている
ここで手が止まりました。私はLLMOの本を書いていて、サイト自体をその実装リファレンスにしています。AIに引用されるための前提は、AIにクロールされることです。
冒頭で触れた数字を、チャネル別に並べ直します。
| チャネル | セッション | エンゲージ | エンゲージ率 | 平均滞在 |
|---|---|---|---|---|
| Direct | 1,064 | 99 | 9.3% | 45.9秒 |
| Organic Search | 318 | 167 | 52.5% | 154.4秒 |
| Referral | 114 | 77 | 67.5% | 209.0秒 |
| AI Assistant | 50 | 19 | 38.0% | 124.3秒 |
AI経由は50セッションで、全体の2.7%にすぎません。誇張する数字ではありません。ただし内訳を開くと、ChatGPTから36(平均75.4秒)、Perplexityから13(平均226.8秒)、Geminiから1(554秒)。Perplexity経由の13セッションは、平均で3分47秒読んでいます。 Directの45.9秒と比べると、来る人数と読む深さは別の指標だと分かります。
この50が明日ゼロになっても、売上も順位も変わりません。困るのはその先です。クロールを止めた瞬間、AIの回答に自分のサイトが出る余地が消えます。50は今の数字で、止めれば伸びしろごと閉じます。
同じ日のCloudflareログには、10種のAIクローラーが来ていました。YouBot 87 / PerplexityBot 48 / Bytespider 36 / ExaSearchBot 27 / Claude-User 26 / OAI-SearchBot 24 / Amazonbot 21 / ChatGPT-User 19 / ClaudeBot 18 / MoonshotBot(Kimi)14。robots.txt では既に明示的に Allow しています。ここをWAFで落とすと、robots.txt に書いた許可は意味を持ちません。 robots.txt はクローラーへのお願いで、WAFはその手前で通信を切る場所だからです。
ちなみに一番リクエストを食っていたのはAIでも検索でもなく、SEO被リンク調査ボットでした。serpstatbot 単独で724。これは落としても私には何の損もありません。
国でもUser-Agentでも切れない
シンガポールが真っ黒なら、国で切ればいい。そう考えて米国の内訳を見たら、その手は使えないと分かりました。
| 米国の内訳 | セッション | エンゲージ | 平均滞在 |
|---|---|---|---|
| desktop / Chrome | 136 | 13 | 22.9秒 |
| mobile / Safari | 42 | 4 | 35.2秒 |
| desktop / Edge | 6 | 5 | 358.3秒 |
| desktop / Safari | 5 | 1 | 88.2秒 |
desktop / Chrome の136セッションは、シンガポールと同じ顔をしています。ところが同じ米国の desktop / Edge は、6セッション中5がエンゲージ、平均358秒。6分近く読んでいる読者です。米国を国ごと落とすと、この6セッションが消えます。中国も124セッション中エンゲージ8(6.5%)で、シンガポールと同型でした。国という軸は、真っ黒な国には効いて、混ざっている国には効きません。
名乗りのほうも同じです。シンガポール発のUser-Agentは Android・iPhone・Mac・Windows がバラバラに並び、Chromeのバージョンは106から149まで散っていました。毎回変えているので、文字列でマッチさせる方法が成立しません。3日分を割ると、SG総リクエスト385 / 369 / 490に対して、ブラウザを名乗るものが177 / 289 / 306。GA4に載っていたのはこの列です。JavaScriptを実行してタグを発火させている以上、単純なHTTPクライアントではなくヘッドレスブラウザです。
ここまでで、使える軸が3つとも潰れました。国は混ざる、User-Agentは偽装される、IPは無数にある。残っているのは「そのリクエストがブラウザとして本物かどうかを、リクエストの中身から判定する」という軸だけです。
Cloudflareの二段構成
Cloudflare側には、この判定がフィールドとして用意されています。
一段目: cf.client.bot
Cloudflareは「Verified Bots」というリストを持っていて、Googlebot、bingbot、GPTBot、ClaudeBot、PerplexityBotなどの正規クローラが登録されています。登録には、そのボットの運営者がCloudflareに申請して、逆引きDNSやIPレンジで名乗りが本物であることを検証される手続きがあります。
cf.client.bot は「このリクエストは検証済みボットである」を返す真偽値のフィールドです。User-Agentの文字列マッチと違い、偽装したUser-Agentは true になりません。GooglebotのUser-Agentを騙るリクエストは、IPが一致しないので弾かれます。
二段目: 残りにチャレンジを出す
WAFカスタムルールは上から順に評価されるので、順序で意図を表現します。
ルール1 [skip: 後続の全カスタムルール]
(cf.client.bot)
or (http.request.uri.path eq "/llms.txt")
or (http.user_agent contains "GPTBot")
or (http.user_agent contains "ClaudeBot")
or (http.user_agent contains "OAI-SearchBot")
or (http.user_agent contains "PerplexityBot")
or (http.user_agent contains "MoonshotBot")
...
ルール2 [managed_challenge]
(ip.src.country eq "SG") and (not cf.client.bot)

ルール1がAIクローラーを先に逃がすので、ルール2の網には掛かりません。ルール2側にも not cf.client.bot を重ねて書いてあるのは冗長ですが、あとでルールの順序を入れ替えたときに事故らないための保険です。
ルール1にUser-Agentの文字列マッチを混ぜているのは、Verified Botsリストに載っていない新興のAIクローラーを拾うためです。MoonshotBot(Kimi)やExaSearchBotのように、実際に来ているのに検証済みリストにはまだ無い、というものがあります。文字列マッチは偽装できる穴です。ただしここでの目的は計測の汚れを取ることで、不正アクセスを止める話ではありません。穴のコストは小さいと判断しました。
/llms.txt を明示的に外しているのは、LLMO用のファイルがチャレンジに掛かったら本末転倒だからです。この漏れは実際にやりかねないので、先に書いておきます。実際に入れたルールでは /llms-full.txt /robots.txt /sitemap-index.xml も同じ扱いにしました。
入れたあとに壊れていないか確かめる
投入は本番反映なので、入れた直後に自分で叩きました。日本からの通常アクセスと、AIクローラーを名乗ったアクセスの両方です。
| 確認したもの | 結果 |
|---|---|
/ /ja/blog/ | 200 |
/llms.txt /robots.txt /sitemap-index.xml | 200 |
| UA = ClaudeBot / GPTBot / PerplexityBot | 200 |
| UA = Qwenbot(実際には1件も来ていないが先回りで許可) | 200 |
ルール1のUser-Agentマッチが効いているかは、この叩き方では検証済みボット判定と区別がつきません。区別したいなら、Cloudflareのセキュリティイベントでどちらのルールにマッチしたかを見ます。
料金: Freeプランの5本に収まる
プランで変わるのはルールの本数だけです。
| Free | Pro | Business | Enterprise | |
|---|---|---|---|---|
| カスタムルール数 | 5 | 20 | 100 | 1,000 |
この構成は2本なので、Freeの5本に収まります。実際にFreeプランのゾーンへAPIから投入して、skip と managed_challenge の2本が通ることを確認しました。 cf.client.bot も式として受け付けられます。このサイト自身もFreeプランです。
アクションのうちプラン制限があるものには、公式に注記があります。log はEnterpriseのみ、Blockのカスタムレスポンスは Pro 以上。Managed Challengeにはその注記がありません。チャレンジの実行回数に対する従量課金も、公式ドキュメントには見当たりませんでした。ただしこれは「無料と書いてある」のではなく「課金の記載がない」なので、そこは区別しておきます。
Managed Challenge が実際にやっていること
ここが本題です。私は最初、Managed Challengeを「怪しかったらCAPTCHAを出す機能」だと思っていました。公式ドキュメントを読んだら、3箇所間違っていました。
誤解1: 「怪しくなければ何も出ない」ではない
Managed Challengeはインタースティシャルなチャレンジページを必ず返します。目的URLの手前に全画面のゲートが挟まり、そこでブラウザ環境が評価されます。
The Challenge Page intercepts the visitor from getting to the destination URL by holding the request and evaluating the browser environment for automated signals, and serving a challenge.
「何も起きずに素通り」ではなく、「ゲートは必ず通るが、大半の人間は無操作で Successful になって先へ進む」が正確です。
WAFルールで選べるアクションは3つあります。
| アクション | 中身 |
|---|---|
| Non-Interactive Challenge | 注入されたJavaScriptをブラウザに処理させる。操作は不要。通常5秒未満 |
| Managed Challenge | ブラウザのシグナルを見て、Cloudflareがどのチャレンジを出すか動的に選ぶ |
| Interactive Challenge | 必ず操作を要求する |
公式は「特別な互換性の問題がない限り、Managed Challenge以外を使うな」と明記しています。動的に選ぶぶん、人間に無駄な操作をさせる回数が減るからです。
そして、この3つのどこにもCAPTCHAはありません。信号機を数えさせる画像パズルは、いまのWAFの選択肢に載っていないのです。Interactive Challengeの説明ですら「visitorがチャレンジと対話する必要がある」としか書かれておらず、CAPTCHAという語は出てきません。
Cloudflareは2022年に「CAPTCHAをやめる」と宣言しています。当時の公表値で、チャレンジの完了にかかる時間は平均32秒から1秒へ、離脱率は従来のCAPTCHA比で31%低いとされました。CAPTCHAの利用は1年で91%減り、Managed Challengeが返す解答のうちCAPTCHAは9%まで下がっていました。
ただしこの9%という数字は2022年時点のもので、同じ記事の「年内に1%未満へ」は当時の目標であって実測ではありません。私が確認できる現在の事実は、公式のアクション一覧にCAPTCHAが存在しないことのほうです。かつてあったLegacy CAPTCHAというアクションは、いまのリファレンスには載っていません。
読者にCAPTCHAが出るのではという不安は、ここで解けます。出す設定が用意されていません。
誤解2: Private Access Token は「チャレンジを飛ばす」ではない
iOS 16以降やmacOS Ventura以降の対応環境では、OSが「これは本物の端末である」と証明するトークン(Private Access Token)をネットワーク越しに提示できます。私はこれを「PATがあればチャレンジページを見ずに済む」と理解していました。違いました。
A PAT does not automatically solve a challenge or let a visitor bypass the Challenge Page. The visitor still encounters the Challenge Page regardless of whether they have a valid PAT.
PATがあってもチャレンジページには行きます。減るのは「必要な段数」です。トークンが取得できない環境では /cdn-cgi/challenge-platform/.../pat/... へのリクエストが401を返しますが、これは正常動作で、ブロックではありません。開発者ツールで401を見つけて原因だと勘違いしないように、と公式がわざわざ書いています。
誤解3: cf_clearance は単なる有効期限つきクッキーではない
チャレンジを通過すると cf_clearance クッキーが発行されます。既定の有効期間は30分(推奨15〜45分、Challenge Passageで変更可)。ここまでは想像どおりでした。
中身が2層になっているのは知りませんでした。
- Challenge clearance: チャレンジを解いたときに付与される。解いたチャレンジの強度に応じて3段階のレベルがある
- Precursor clearance: セッション中の挙動に基づいて継続的に更新される
レベルには階層があります。
| クリアランスのレベル | 何を素通りできるか |
|---|---|
| Interactive(高) | Interactive / Managed / Non-Interactive の全部 |
| Managed(中) | Managed と Non-Interactive |
| Non-Interactive(低) | Non-Interactive のみ |
そして重要なのが、Challenge clearance の有効期間は設定した時間だけ持つものの、Precursor がそのセッションを怪しいと判断した時点で無効になることです。「30分間は何をしても通し放題」ではありません。クッキーは発行された端末に紐づいていて、他のマシンにコピーしても使えません。
XHRについては、時計のずれを吸収するために検証時に1時間の猶予が足されます。短い有効期間を設定したページでXHRが壊れるのを防ぐためです。
効かない場合と、踏むと危ない罠
JavaScriptを実行できる相手には効き切らない
Non-Interactive Challengeの中身は「注入されたJavaScriptをブラウザに処理させる」です。ヘッドレスブラウザはJavaScriptを実行できます。だから通過することがあります。
私のサイトのシンガポール発トラフィックは、GA4のタグを発火させている以上、JavaScriptを実行しています。つまり最初から「JSを実行できる相手」です。ここは正直に見積もるべきところで、期待できるのは「消える」ではなく「減る」です。データセンターのIPレピュテーションとブラウザAPIの整合性の両方で減点されるので相当数は落ちますが、ゼロにはなりません。
AJAX / XHR は壊れる
チャレンジページはHTMLを1枚返すことでリクエストの流れを止めます。ブラウザがHTML以外のレスポンスを期待している場面――AJAXやfetch――では、この仕組みは機能しません。公式が明記しています。
This mechanism fails when the browser expects a non-HTML response, such as an AJAX or XHR (fetch) request.
APIエンドポイントやSPAを守りたい場合は、チャレンジを直接当てず、TurnstileのPre-clearanceを使います。先にHTMLページで人間確認を済ませてクッキーを発行しておき、API側はそのクッキーで通す形です。
私のサイトは静的サイトで、/rss.xml も /feed.xml も /atom.xml も存在しない(全部404)ので、この罠は踏みません。踏まないことを確認してから入れる、という順序が大事なところです。フィードを配信しているサイトが同じ設定を入れると、購読者のRSSリーダーが静かに壊れます。
チャレンジループ
Cloudflareの他のRules機能と併用すると、チャレンジが繰り返される状態になることがあります。公式がCautionとして挙げています。クッキーを無効にしているブラウザでも cf_clearance を保持できないので同じことが起きます。
国で切っても、混ざっている国は残る
冒頭の表に戻ります。シンガポールを落としても、米国のdesktop / Chrome 136セッション(エンゲージ13)は残ります。国という軸を選んだ時点で、この取りこぼしは構造的に決まっています。
だから対処は2本立てになります。WAFで来させないようにするのと、計測側を汚れに強い数え方にするのと。後者は具体的には、エンゲージセッション数で見るということです。冒頭の表がまさにそれで、682と6の差は国名を知らなくても一目で分かります。
WAFを入れる価値は「サーバー負荷とログの汚れが減る」ことにあって、「数字が正しくなる」ことではありません。数字を正しくするのは数え方の側の仕事です。
References
- Cloudflare challenges — チャレンジ全体の入口
- Interstitial Challenge Pages — 3つのアクションの定義とAJAX/XHRの制限
- Clearance —
cf_clearanceの2層構造とレベル階層 - Challenge Passage — 既定30分、推奨15〜45分
- Private Access Tokens (PAT) — 401が正常である理由
- Verified Bots — 検証済みボットの考え方
- Verified bot categories — カテゴリ一覧
cf.client.bot— フィールド定義- WAF custom rules — ルールの書き方とプラン別の本数上限(Free 5 / Pro 20 / Business 100 / Enterprise 1,000)
- Rules language: actions — アクション一覧。プラン限定のものはここに明記される
- Cloudflare は CAPTCHA をやめた — Managed Challenge 導入の背景
- iPhone と Mac で CAPTCHA をなくす — PATの技術背景
まとめ
- GA4のセッション増2.6倍の内実は、37%がデータセンター発の自動アクセスでした。682セッション中エンゲージ6、平均滞在0.6秒
- AIクローラーは同じ日に10種が来ていました。ClaudeBot、OAI-SearchBot、PerplexityBot、Kimi。ここを落とすとLLMOの前提が崩れます
- 国では切れません。米国は同じ国の中に「136セッションでエンゲージ13」と「6セッションでエンゲージ5、平均358秒」が同居しています
- User-Agentでも切れません。名乗りは毎回変わります
- 使える軸は
cf.client.bot(検証済みボットか)とチャレンジ(ブラウザとして本物か)の二段でした - Managed Challengeは「怪しければCAPTCHA」ではありません。ゲートは必ず通り、大半の人間は無操作で抜け、PATは段数を減らすだけで、
cf_clearanceはセッション挙動で継続評価されます - JavaScriptを実行できるヘッドレスには効き切りません。ゼロにはならず、減るだけです
- 数字を正しくするのは数え方の側の仕事です。エンゲージセッションで数えれば、国名を知らなくても分かります
- 費用はかかりません。カスタムルールはFreeプランで5本まで使え、この構成は2本です
- このルールは2026-09-09にkenimoto.devへ実際に投入しました。投入直後の疎通は確認済み、SGがどれだけ減ったかは2週間後に追記します
関連書籍 AIくさい文章から脱出する技術 AIくささ 消し方 · AIっぽい文章 特徴 · AI判定 誤検知 · 6モデル180サンプルを自分で測った全記録 (Kindle Unlimited 対象) 書籍ページを見る → この記事は役に立ちましたか?