&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Stan Wu 吳信典</title><link>https://blog.stanwu.org/tags/ai-agent/</link><description>拆解被包裝過的東西、數位自主權實踐、非典型理財觀</description><language>zh-TW</language><lastBuildDate>Tue, 07 Apr 2026 14:24:48 +0800</lastBuildDate><atom:link href="https://blog.stanwu.org/tags/ai-agent/feed.xml" rel="self" type="application/rss+xml"/><item><title>如何檢測 Cloudflare 是否阻擋外部 AI Agent：從需求、誤判到自建 bot.py</title><link>https://blog.stanwu.org/posts/detect-cloudflare-blocking-ai-agents/</link><pubDate>Tue, 07 Apr 2026 11:10:00 +0800</pubDate><guid>https://blog.stanwu.org/posts/detect-cloudflare-blocking-ai-agents/</guid><description>&lt;p&gt;當一個外部 AI 工具說「抓不到你的網站」時，直覺很容易把責任丟給 Cloudflare。尤其站上又開了 CDN、WAF、Bot 管理或其他安全機制時，第一反應通常是：是不是把外部 agent 都擋掉了？&lt;/p&gt;
&lt;p&gt;但這次實際排查後，答案並不是那麼簡單。問題的核心不是「網站有沒有被擋」，而是&lt;strong&gt;要先把外部工具自己的抓取失敗，和 Cloudflare 真正的封鎖區分開來&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="需求我真正想驗證的是什麼"&gt;需求：我真正想驗證的是什麼？&lt;/h2&gt;
&lt;p&gt;這次要確認的不是一般瀏覽器能不能開站，而是更具體的一件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stanwu.org&lt;/code&gt; 與 &lt;code&gt;blog.stanwu.org&lt;/code&gt; 是否會對外部 AI agent 或搜尋引擎 crawler 產生差別待遇？&lt;/li&gt;
&lt;li&gt;Cloudflare 是否對 &lt;code&gt;GPTBot&lt;/code&gt;、&lt;code&gt;ClaudeBot&lt;/code&gt;、&lt;code&gt;PerplexityBot&lt;/code&gt;、&lt;code&gt;Googlebot&lt;/code&gt; 這些常見 User-Agent 做了封鎖？&lt;/li&gt;
&lt;li&gt;如果某個外部工具分析失敗，問題到底出在網站、Cloudflare，還是工具自己的抓取流程？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這件事和 SEO / AEO 很有關係。&lt;/p&gt;
&lt;p&gt;因為如果主站其實沒有擋 bot，但外部工具只是偶爾抓不到，那解法就不是去亂改 Cloudflare 規則；反過來，如果 Cloudflare 真的對特定 bot 回 &lt;code&gt;403&lt;/code&gt; 或挑戰頁，那就必須正面處理。&lt;/p&gt;
&lt;h2 id="為什麼會有這個懷疑"&gt;為什麼會有這個懷疑？&lt;/h2&gt;
&lt;p&gt;起因很單純：外部 AI 工具對同一個網站給出了前後矛盾的結論。&lt;/p&gt;
&lt;p&gt;有些工具說主站已經是多頁品牌站，有雙語結構，也看得到 &lt;code&gt;llms.txt&lt;/code&gt;、&lt;code&gt;sitemap.xml&lt;/code&gt;；但另一些工具卻說很多頁抓不到，甚至把本來存在的頁面看成不存在，或把站點錯判成舊版單頁 landing page。&lt;/p&gt;
&lt;p&gt;這種情況通常有三種可能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;外部工具還在看舊快取。&lt;/li&gt;
&lt;li&gt;外部工具根本沒有穩定抓到 live HTML。&lt;/li&gt;
&lt;li&gt;Cloudflare 對某些真實 bot 或某些流量來源有限制。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果不把這三種可能拆開，後面的判斷就很容易失真。&lt;/p&gt;
&lt;h2 id="問題外部工具的失敗會被誤認成站點被擋"&gt;問題：外部工具的失敗，會被誤認成站點被擋&lt;/h2&gt;
&lt;p&gt;最大的問題在於，很多外部分析工具不會老老實實承認「我抓不到」。&lt;/p&gt;
&lt;p&gt;有些工具抓不到頁面，仍然會根據殘缺資料、舊搜尋索引或模糊印象硬下結論；最後你看到的不是網站現況，而是它自己抓取失敗後的推測。&lt;/p&gt;
&lt;p&gt;這樣的風險是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你可能會錯怪 Cloudflare。&lt;/li&gt;
&lt;li&gt;你可能會去改一堆其實沒問題的安全規則。&lt;/li&gt;
&lt;li&gt;你可能會因為誤判而破壞原本正常的 CDN / WAF 設定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以這次的排查方向很明確：&lt;strong&gt;不要再依賴外部工具的二手結論，直接做一個最小可驗證的檢測工具。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="解法設計用同一支小工具模擬多種-user-agent"&gt;解法設計：用同一支小工具模擬多種 User-Agent&lt;/h2&gt;
&lt;p&gt;最務實的做法不是上來就研究一堆 Cloudflare dashboard 設定，而是先回答一個簡單問題：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;當同一個 URL 被不同 User-Agent 存取時，站到底回了什麼？&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;因此我做了一支很小的 Python 工具 &lt;code&gt;bot.py&lt;/code&gt;，做的事情也很克制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接受一個 &lt;code&gt;--url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用不同的 User-Agent 去請求&lt;/li&gt;
&lt;li&gt;顯示 &lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;顯示 &lt;code&gt;final_url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;顯示幾個 Cloudflare 相關 response headers&lt;/li&gt;
&lt;li&gt;從 HTML 擷取 &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;截一小段 body snippet&lt;/li&gt;
&lt;li&gt;根據常見特徵判斷是否疑似遭到 Cloudflare challenge / block&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣的好處是，排查焦點非常集中，不需要先引入 headless browser、代理池或複雜外掛。&lt;/p&gt;
&lt;h2 id="botpy-的核心思路"&gt;bot.py 的核心思路&lt;/h2&gt;
&lt;p&gt;這支工具預設內建了幾組常見 User-Agent：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;browser_chrome&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;googlebot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gptbot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claudebot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perplexitybot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次測試時，它會對同一個 URL 重複做請求，然後輸出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;final_url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cloudflare_block_suspected&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server&lt;/code&gt;, &lt;code&gt;cf-ray&lt;/code&gt;, &lt;code&gt;cf-cache-status&lt;/code&gt;, &lt;code&gt;cf-mitigated&lt;/code&gt;, &lt;code&gt;content-type&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;body_snippet&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloudflare 疑似封鎖的判斷邏輯也故意保持簡單，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP status 是否是 &lt;code&gt;403&lt;/code&gt; / &lt;code&gt;429&lt;/code&gt; / &lt;code&gt;503&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;body 裡是否出現 &lt;code&gt;Attention Required&lt;/code&gt;、&lt;code&gt;challenge-platform&lt;/code&gt;、&lt;code&gt;sorry, you have been blocked&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;response header 是否有 &lt;code&gt;cf-mitigated&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些訊號未必完美，但已經足夠應付大多數「有沒有被擋」的第一輪判斷。&lt;/p&gt;
&lt;p&gt;公開 gist 在這裡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bot.py&lt;/code&gt;：&lt;a href="https://gist.github.com/stanwu/6b30392ec7412b0ee1496c28104a701e"&gt;https://gist.github.com/stanwu/6b30392ec7412b0ee1496c28104a701e&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用方式"&gt;使用方式&lt;/h2&gt;
&lt;p&gt;我在本機是這樣測的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/bot.py --url https://blog.example.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果只想測單一 agent，也可以指定：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/bot.py --url https://blog.example.com/ --agent googlebot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/bot.py --url https://blog.example.com/ --agent gptbot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/bot.py --url https://blog.example.com/ --agent perplexitybot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這裡刻意用 &lt;code&gt;example.com&lt;/code&gt; 形式展示，而不是把本機路徑、帳號或環境細節直接攤開。真正重要的是方法，不是機器上的私人上下文。&lt;/p&gt;
&lt;h2 id="實測結果主站與-blog-子網域都沒有被普遍擋掉"&gt;實測結果：主站與 blog 子網域都沒有被普遍擋掉&lt;/h2&gt;
&lt;p&gt;用這支工具對主站與 blog 子網域分別測試後，結果很清楚。&lt;/p&gt;
&lt;p&gt;不論是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;browser_chrome&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;googlebot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gptbot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claudebot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perplexitybot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在測試當下都拿到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;status: 200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;正確的 &lt;code&gt;final_url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;正常的 &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cloudflare_block_suspected: no&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而且 response headers 也能看到是經由 Cloudflare 回應，但沒有 challenge 或 block 的典型跡象。&lt;/p&gt;
&lt;p&gt;這代表至少在當下、對這些常見 User-Agent 而言：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stanwu.org&lt;/code&gt; 沒有被 Cloudflare 普遍封鎖。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blog.stanwu.org&lt;/code&gt; 也沒有被 Cloudflare 普遍封鎖。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="這個結果真正回答了什麼"&gt;這個結果真正回答了什麼？&lt;/h2&gt;
&lt;p&gt;它回答的不是「任何世界上所有 bot 都永遠不會被擋」，而是比較實際的三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;目前常見的搜尋與 AI bot UA 沒有被普遍阻擋。&lt;/li&gt;
&lt;li&gt;外部工具先前抓不到頁面，不足以直接推論成 Cloudflare 封鎖。&lt;/li&gt;
&lt;li&gt;如果某個分析工具仍然抓不到，很可能是它自己的抓取管線不穩，而不是站點本身拒絕它。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這個差異很重要。&lt;/p&gt;
&lt;p&gt;因為一旦確認 Cloudflare 沒有普遍封鎖，你後續就不用沿錯誤方向一直追安全規則，反而應該把注意力放回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;該工具是否真的支援 live fetch&lt;/li&gt;
&lt;li&gt;是否只在讀舊索引或快取&lt;/li&gt;
&lt;li&gt;是否沒有完整取得 HTML head&lt;/li&gt;
&lt;li&gt;是否有自己的 sandbox 限制&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="這次排查的關鍵經驗"&gt;這次排查的關鍵經驗&lt;/h2&gt;
&lt;h3 id="1-先做最小驗證再做大推論"&gt;1. 先做最小驗證，再做大推論&lt;/h3&gt;
&lt;p&gt;與其一開始就猜 Cloudflare、WAF、Bot Fight Mode、Rate Limiting、Robots 或 DNS，不如先做一個可以重現的最小檢測。&lt;/p&gt;
&lt;p&gt;只要你能回答「同一個 URL 對不同 UA 到底回什麼」，很多模糊推測會立刻消失。&lt;/p&gt;
&lt;h3 id="2-外部-ai-工具的失敗不等於網站失敗"&gt;2. 外部 AI 工具的失敗，不等於網站失敗&lt;/h3&gt;
&lt;p&gt;這次最值得記住的一點是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一個 AI 工具抓不到你的站，並不自動等於你的站有問題。&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;它可能只是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;沒有 live fetch 能力&lt;/li&gt;
&lt;li&gt;抓取流程不穩&lt;/li&gt;
&lt;li&gt;只拿到部分頁面&lt;/li&gt;
&lt;li&gt;受限於自己環境的 sandbox&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-小工具比複雜平台更適合做第一輪定位"&gt;3. 小工具比複雜平台更適合做第一輪定位&lt;/h3&gt;
&lt;p&gt;很多時候，真正有用的不是再換一個更大的分析平台，而是一支自己能看懂、能修改、能反覆執行的小工具。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bot.py&lt;/code&gt; 不複雜，但它把問題切得夠乾淨：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;這個 UA 能不能拿到 200？&lt;/li&gt;
&lt;li&gt;有沒有被 redirect？&lt;/li&gt;
&lt;li&gt;回來的是正常頁面還是 challenge 頁？&lt;/li&gt;
&lt;li&gt;標題對不對？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這種工具越小，越不容易把調查方向帶偏。&lt;/p&gt;
&lt;h2 id="我最後的結論"&gt;我最後的結論&lt;/h2&gt;
&lt;p&gt;這次的重點不只是做出一支檢測 bot，而是重新確認一件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當系統看起來有問題時，不要急著把責任交給最顯眼的那一層。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cloudflare 很常被當成第一嫌疑犯，這有時是對的，但有時只是因為它站在最前面。真正成熟的排查方式，是把：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;外部工具的不穩&lt;/li&gt;
&lt;li&gt;自家站點的設定&lt;/li&gt;
&lt;li&gt;中間 CDN / WAF 的行為&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一層一層拆開。&lt;/p&gt;
&lt;p&gt;這樣最後得到的，不只是「有沒有被擋」的答案，而是一個之後還能重複使用的檢測方法。&lt;/p&gt;
&lt;p&gt;如果你也在懷疑自己的站是不是被 Cloudflare 擋住某些 bot，我會建議先做一件很小但很關鍵的事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要先猜，先量。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;bot.py&lt;/code&gt; 就是這次我用來做第一輪量測的最小工具。&lt;/p&gt;</description></item></channel></rss>