📚 系列導覽:這是「Cloudflare 反爬蟲機制」系列的第 1 篇,負責建立全景地圖。後續三篇分別深入被動指紋(TLS、HTTP/2)與主動挑戰。
# 筆記 主題 01 反爬蟲偵測總覽(本篇) 六層偵測全景、被動 vs 主動、Bot Score 概念 02 TLS 指紋:JA3 與 JA4 ClientHello 為何洩漏身分、兩代指紋演算法 03 HTTP/2 指紋與 Akamai Fingerprint 連線層參數指紋、三方交叉驗證 04 Managed Challenge 與 Bot Score 主動挑戰機制、PAT、Cookie 綁定
目錄
- 什麼是 Bot Management?
- 基礎補課:一個 HTTPS 請求的六個階段
- 六層偵測全景
- 核心心法:為什麼偽裝 User-Agent 沒用
- 被動訊號 vs 主動挑戰
- Bot Score 是什麼
- 從回應讀出對方的防禦配置
- 對比分析:各層的偽裝難度
- 常見問題
- 總結
什麼是 Bot Management?
Bot Management 是一套判斷「這個 HTTP 請求來自真人瀏覽器,還是自動化程式」的技術,並依判斷結果決定放行、挑戰或封鎖。
Cloudflare 的實作特別值得研究,原因是它站在一個罕見的位置:全球有相當比例的網站流量經過它的邊緣網路,因此它能同時看到「真人的請求長什麼樣」與「自動化程式的請求長什麼樣」,並用兩者的差異來訓練判斷模型。
為什麼需要理解偵測原理?
不理解原理的常見結果:
- 花好幾天調整 HTTP 標頭,但一次都沒成功過——因為判斷點根本不在標頭
- 看到
403就以為 IP 被封鎖,於是去換 IP,但問題其實是缺少 JavaScript 執行環境 - 加了
sleep降低請求頻率仍然被擋——因為被動指紋跟頻率無關 - 誤以為「網站沒有反爬蟲」,因為用瀏覽器開起來完全正常
理解原理之後:
- 能從回應狀態碼與標頭判斷自己撞到的是哪一層防禦
- 知道哪些偽裝在原理上就無效,不必浪費時間
- 能從防禦端的視角評估自己網站的防護配置
- 理解「使用者無感」與「防護強度」為什麼不衝突
核心特點
Bot Management = 被動指紋收集 + 主動環境挑戰 + 行為分析 + 評分決策
基礎補課:一個 HTTPS 請求的六個階段
要理解偵測邏輯,必須先知道一個 HTTPS 請求實際上經過哪些階段。這張時間軸是整個系列的地圖,後面每一種指紋技術都對應到其中一個階段。
以瀏覽器打開 https://example.com/page 為例:
階段 1:DNS 查詢
問:example.com 的 IP 是多少?
答:104.x.x.x(若網域在 Cloudflare 後面,拿到的是 Cloudflare 的 anycast IP)
↓
階段 2:TCP 三向交握
SYN → SYN-ACK → ACK
此時洩漏:TTL、MSS、TCP window size 等作業系統堆疊特徵
↓
階段 3:TLS 握手(關鍵!)
客戶端送出 ClientHello ── 明文,任何中間設備都讀得到
此時洩漏:支援的加密演算法清單、擴充欄位清單、以及它們的順序
↓ ← JA3 / JA4 指紋在這裡產生
階段 4:ALPN 協商完成,HTTP/2 連線初始化
客戶端送出 SETTINGS frame、WINDOW_UPDATE、PRIORITY frame
此時洩漏:連線層參數的組合與順序
↓ ← Akamai / HTTP/2 指紋在這裡產生
階段 5:HTTP 請求
GET /page HTTP/2
:method :authority :scheme :path
user-agent: Mozilla/5.0 ... ← User-Agent 第一次出現在這裡
↓
階段 6:回應處理與後續行為
執行 JavaScript、載入 CSS/JS/圖片、使用者互動
此時洩漏:瀏覽器環境真偽、請求節奏、資源載入模式
這張圖最重要的一件事
User-Agent 出現在階段 5,但 TLS 指紋在階段 3 就已經被記錄了。
也就是說,當伺服器讀到你宣稱「我是 Chrome」的那一刻,它手上已經握有兩份在更早階段採集、且你的 HTTP 函式庫無法控制的證據。這兩份證據來自 TLS 函式庫與 HTTP/2 實作,跟你寫在標頭裡的字串是完全獨立的來源。
比對這三份資料是否一致,是現代 Bot 偵測最有效的單一手段。這也是本系列反覆出現的主線。
六層偵測全景
把上面的階段對應到防禦層,可以得到六層偵測。由外而內,越前面的層越早執行、成本越低。
┌─────────────────────────────────────────────────────────┐
│ 第 1 層:網路層 (階段 1-2) │
│ IP 信譽分數、ASN 類型(機房/住宅/行動) │
│ TCP/IP 堆疊指紋(TTL、MSS、window size) │
├─────────────────────────────────────────────────────────┤
│ 第 2 層:TLS 層 (階段 3) │
│ JA3 / JA4 指紋 ── 見系列第 02 篇 │
├─────────────────────────────────────────────────────────┤
│ 第 3 層:HTTP/2 連線層 (階段 4) │
│ SETTINGS、WINDOW_UPDATE、PRIORITY、pseudo-header 順序 │
│ ── 見系列第 03 篇 │
├─────────────────────────────────────────────────────────┤
│ 第 4 層:HTTP 請求層 (階段 5) │
│ 標頭順序與大小寫、Client Hints 完整性、UA 一致性 │
├─────────────────────────────────────────────────────────┤
│ 第 5 層:瀏覽器執行層 (階段 6) │
│ JavaScript 挑戰、Canvas/WebGL、navigator 屬性 │
│ ── 見系列第 04 篇 │
├─────────────────────────────────────────────────────────┤
│ 第 6 層:行為層 (階段 6 之後) │
│ 請求頻率與節奏、路徑序列、是否載入子資源 │
└─────────────────────────────────────────────────────────┘
各層說明
第 1 層:網路層
IP 位址帶有歷史信譽。更關鍵的是 ASN(自治系統編號,可以理解為「這個 IP 屬於哪個組織」):來自 AWS、GCP 這類雲端機房的 IP 段,本身就是高風險訊號——一般使用者不會從資料中心瀏覽網頁。住宅寬頻與行動網路的 ASN 風險分數低得多。
同時,TCP 封包的一些欄位(存活時間 TTL、最大分段大小 MSS、視窗大小)會隨作業系統與網路堆疊而不同,這稱為被動作業系統指紋。若 TCP 層看起來像 Linux 伺服器,但 User-Agent 說是 Windows 上的 Chrome,就是一個矛盾點。
第 2 層:TLS 層
ClientHello 訊息裡的演算法清單與擴充欄位順序,由 TLS 函式庫的實作決定,不是使用者可以隨意設定的。Chrome 用 BoringSSL、Firefox 用 NSS、Python 用系統 OpenSSL、Go 用自己的 crypto/tls——每一種都留下不同的指紋。詳見系列第 02 篇。
第 3 層:HTTP/2 連線層
HTTP/2 在傳送任何請求之前,會先交換一組連線參數。送哪些參數、值是多少、順序如何,同樣由函式庫寫死。詳見系列第 03 篇。
第 4 層:HTTP 請求層
這是唯一可以「照抄」的一層,但也因此不再具有決定性。可觀察的包含:
- 標頭順序:真瀏覽器的標頭順序高度固定,手寫的 HTTP 客戶端常是字典序或程式碼插入序
- 標頭大小寫:HTTP/2 強制小寫,HTTP/1.1 不強制,寫法差異可辨識
- Client Hints 完整性:現代 Chrome 會送出
sec-ch-ua、sec-ch-ua-mobile、sec-ch-ua-platform等欄位;只送User-Agent卻聲稱是新版 Chrome,本身就矛盾 sec-fetch-*系列:sec-fetch-mode、sec-fetch-site、sec-fetch-dest描述請求的來源情境,真瀏覽器會依導覽情境自動填寫
第 5 層:瀏覽器執行層
要求客戶端執行一段 JavaScript,藉此檢查它是否真的具備瀏覽器環境。這是成本最高、但辨識力最強的一層。詳見系列第 04 篇。
第 6 層:行為層
即使前五層都通過,行為模式仍會暴露自動化:
- 只請求 HTML,完全不載入 CSS、JavaScript、圖片、字型(真瀏覽器一定會載入)
- 請求間隔精確一致(例如每次都恰好 2.000 秒)
- 路徑序列不符合真實導覽(直接命中深層頁面,沒有經過列表頁)
- 完全沒有
Referer鏈
核心心法:為什麼偽裝 User-Agent 沒用
這是最值得記住的一節。
常見的錯誤心智模型
很多人腦中的模型是這樣:
❌ 錯誤模型:
伺服器讀 User-Agent → 判斷是不是瀏覽器 → 決定放行或擋下
推論:把 User-Agent 改成 Chrome 就能通過
實際的模型
✅ 實際模型:
階段 3(TLS):記錄 JA4 指紋 → 「這是 OpenSSL,Python 生態」
↓
階段 4(HTTP/2):記錄連線參數 → 「這是某 h2 函式庫,不是瀏覽器」
↓
階段 5(HTTP):讀到 User-Agent → 「它說自己是 Chrome」
↓
比對:三份證據互相矛盾 → 高風險
修改 User-Agent 不但無效,反而使情況更糟:一個誠實回報自己是 Python 的請求,只是分數偏低;一個 TLS 指紋是 Python 卻聲稱自己是 Chrome 的請求,是明確的偽裝意圖,會被歸入更高風險等級。
為什麼這個設計如此有效
關鍵在於證據來自不同的軟體層,而這些層由不同的函式庫負責:
| 證據 | 由誰產生 | 你的程式碼能控制嗎 |
|---|---|---|
| TLS 指紋 | TLS 函式庫(OpenSSL / BoringSSL / NSS) | ❌ 需更換整個 TLS 實作 |
| HTTP/2 指紋 | HTTP/2 實作(nghttp2 / h2 / net-http2) | ❌ 需更換或深度改造函式庫 |
| HTTP 標頭 | 你的應用程式碼 | ✅ 隨意填寫 |
| JS 環境 | JavaScript 引擎與瀏覽器 API | ❌ 需真實瀏覽器 |
只有一列是可控的。而防禦方要求的是四列一致。
真實瀏覽器天生四列一致——因為它們本來就是同一個軟體的不同部分。這就是不對稱之處:瀏覽器不需要努力就一致,自動化程式需要更換整個網路堆疊才能一致。
被動訊號 vs 主動挑戰
Cloudflare 的偵測分成兩種性質,理解這個分野對判斷「自己撞到哪一層」很重要。
被動訊號(Passive Signals)
定義:不需要客戶端額外配合,在正常通訊過程中順便採集的資訊。
特點:
- 零延遲:資訊本來就要傳,採集不增加往返次數
- 無法拒絕:不送 ClientHello 就無法建立 TLS 連線
- 客戶端不知情:完全不影響使用者體驗
- 包含:第 1 至 4 層的所有內容
因為零成本,被動訊號會對每個請求執行。
主動挑戰(Active Challenges)
定義:伺服器不直接回應內容,而是先回傳一段需要客戶端運算或執行的東西,通過後才給內容。
特點:
- 有延遲成本:至少多一次往返,可能還有運算時間
- 可能影響使用者體驗:這是防禦方最在意的代價
- 辨識力最強:能檢驗被動訊號無法檢驗的東西(是否有 JS 引擎、GPU、真實瀏覽器 API)
- 對應第 5 層
因為有成本,主動挑戰只在被動訊號無法決定時才發動。
兩者的關係
請求進來
↓
採集被動訊號(零成本,總是執行)
↓
訊號足以判斷嗎?
├─ 明顯是真人 → 直接放行(不發挑戰) ← 絕大多數真人走這條
├─ 明顯是惡意 → 直接封鎖(不發挑戰) ← 低品質爬蟲走這條
└─ 不確定 → 發出主動挑戰 ← 只有這條會被使用者察覺
這個流程解釋了一個常讓人困惑的現象:同一個網站,用瀏覽器開毫無異常,用程式抓卻立刻 403。不是網站對你有偏見,而是兩者在被動訊號階段就被分流到不同的分支了。
Bot Score 是什麼
Cloudflare 把所有訊號彙整成一個 Bot Score,範圍 1 到 99。
分數級距
| 分數 | Cloudflare 的定義 | 含義 |
|---|---|---|
| 1 | Automated | 確定是自動化程式 |
| 2–29 | Likely automated | 很可能是自動化程式 |
| 30–99 | Likely human | 很可能是真人 |
| 0 或無值 | — | 未計算(方案未啟用或不適用) |
三個容易誤解的地方
1. 分數不是機率
分數 50 不代表「有 50% 機率是機器人」。這是一個經過校準的信心指標,級距的分界(1、29、30)比具體數值更有意義。
2. 分數低不等於會被擋
Bot Score 只是一個欄位,實際行為由網站管理者寫的規則決定。同樣分數為 3 的請求,在 A 網站被放行、在 B 網站被封鎖,取決於各自的 WAF 規則設定。
3. 有一類「分數低但被歡迎」的流量
Cloudflare 另外維護一個 Verified Bot 標記,用於已驗證的良性爬蟲(搜尋引擎爬蟲、監控服務等)。驗證方式通常是反向 DNS 查詢加上官方公布的 IP 範圍比對。這些爬蟲的 Bot Score 當然很低——它們確實是機器人——但規則上會被放行。
這帶出一個重要觀念:Bot 偵測的目的不是「擋掉所有機器人」,而是「區分被授權的機器人與未經授權的機器人」。這也是為什麼取得授權(API 金鑰、加入白名單)在技術上永遠是最穩定的路徑——它讓你從「要通過偵測」變成「不需要通過偵測」。
從回應讀出對方的防禦配置
面對一個未知網站,可以從回應本身判斷防禦層級。以下為代表性樣本(實際數值依站台而異)。
判斷是否經過 Cloudflare
server: cloudflare
cf-ray: 0123456789abcdef-XXX
cf-ray 是 Cloudflare 為每個請求產生的追蹤 ID,結尾三字母是處理該請求的資料中心代碼。出現這兩個標頭即表示流量經過 Cloudflare。
判斷擋你的是哪一種機制
| 觀察到的回應 | 含義 | 性質 |
|---|---|---|
403 + cf-mitigated: challenge |
被挑戰攔下,需執行挑戰 | 主動挑戰 |
403 + 回應內容為挑戰頁 |
同上,Managed Challenge | 主動挑戰 |
503 + 內含 cf-chl 相關腳本 |
舊式 JS Challenge | 主動挑戰 |
403 但無 cf-mitigated |
WAF 規則直接封鎖 | 規則封鎖 |
429 |
請求頻率超限 | 速率限制 |
200 但內容為空殼 |
內容由 JS 動態載入 | 非防禦,是前端架構 |
cf-mitigated 標頭是最有價值的單一線索:它明確區分了「被挑戰」與「被規則封鎖」。前者理論上可以通過,後者需要改變請求的其他特徵。
判斷指紋收集的積極程度
accept-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Model, ...
critical-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Model, ...
accept-ch 是伺服器向瀏覽器索取 Client Hints(客戶端提示)的標頭。基本的 Client Hints 只包含瀏覽器品牌與平台;若清單延伸到 CPU 架構(Arch)、位元數(Bitness)、裝置型號(Model)、完整版本清單(Full-Version-List),代表對方刻意把裝置資訊的熵值拉到最高——這是一個「有人調過設定」的訊號,而非預設值。
critical-ch 更進一步:它要求瀏覽器立刻重送請求並附上這些提示,不接受先給一次不完整的請求。
對比分析:各層的偽裝難度
| 層 | 偽裝難度 | 需要什麼 | 為什麼是這個難度 |
|---|---|---|---|
| 第 1 層 IP / ASN | 🟡 中 | 住宅代理服務 | 技術上簡單,但需持續付費,且代理品質差異大 |
| 第 1 層 TCP/IP 指紋 | 🔴 高 | 修改作業系統核心參數 | 需要系統層權限,且會影響整台機器的網路行為 |
| 第 2 層 TLS 指紋 | 🟡 中 | 可控制 ClientHello 的函式庫 | 需更換整個 TLS 實作,但已有現成工具 |
| 第 3 層 HTTP/2 指紋 | 🟠 中高 | 可控制 h2 連線層參數的函式庫 | 可用工具較少,且需與 TLS 層搭配一致 |
| 第 4 層 HTTP 標頭 | 🟢 低 | 照抄真瀏覽器的標頭與順序 | 純字串操作,但也因此不具決定性 |
| 第 5 層 JS 執行環境 | 🔴 高 | 真實瀏覽器 | 只有真瀏覽器有完整的 JS 引擎與圖形堆疊 |
| 第 6 層 行為 | 🟡 中 | 刻意設計請求節奏與資源載入 | 概念簡單,但要做得自然需要細心 |
這張表最重要的推論
唯一「低難度」的那一層,恰好是唯一不具決定性的那一層。
這不是巧合,而是防禦設計的結果:偵測系統刻意把權重放在客戶端難以控制的層次。理解這件事,就能明白為什麼「調標頭」這條路從一開始就注定失敗,而不是「還沒調對」。
常見問題
問題 1:為什麼用瀏覽器開很正常,用程式抓就被擋?
回答:因為兩者在被動訊號階段就被分流了。
真瀏覽器的 TLS 指紋、HTTP/2 參數、標頭組合、JS 環境天生一致,被動訊號足以判定為真人,於是走「直接放行」分支,連挑戰頁都不會出現。
自動化程式在階段 3 就留下了不一致的證據,被分流到「不確定」或「明顯自動化」分支。
這也說明「使用者無感」不代表網站沒有防護,恰恰相反——它代表防護做得夠精準,能在不打擾真人的前提下完成判斷。
問題 2:加上 sleep 降低請求頻率,為什麼還是被擋?
回答:因為頻率屬於第 6 層(行為層),而你很可能在第 2 或第 3 層就已經被判定了。
降頻只對「行為層觸發的速率限制」有效,典型症狀是 429。如果你看到的是 403 加 cf-mitigated: challenge,那是指紋與環境的問題,跟頻率完全無關——就算一天只發一個請求也一樣會被挑戰。
診斷原則:先看狀態碼與 cf-mitigated,確認自己在哪一層被擋,再決定要調整什麼。
問題 3:403 是不是代表 IP 被封鎖了?
回答:不一定,而且多數情況不是。
403+cf-mitigated: challenge→ 被挑戰攔下,IP 沒事,換 IP 也沒用403無cf-mitigated→ 可能是 WAF 規則命中(規則可能基於國家、路徑、User-Agent 等,不一定是 IP)- 真正的 IP 層封鎖通常表現為連線層失敗或
1006/1015這類 Cloudflare 錯誤碼頁面
在確認之前就去換 IP,是很常見的誤診。
問題 4:常見誤解整理
誤解 1:反爬蟲主要靠檢查 User-Agent 與 robots.txt。
正確理解:User-Agent 只是第 4 層的一個欄位,且是最容易偽造因此權重最低的訊號。robots.txt 完全不是技術防禦——它是給守規矩的爬蟲看的宣告,沒有任何強制力。
誤解 2:只要用 headless 瀏覽器就能通過所有偵測。
正確理解:真瀏覽器解決了第 2、3、5 層,這是很大的進展。但自動化控制本身會留下痕跡(例如 WebDriver 標準要求 navigator.webdriver 為 true),且第 1 層(機房 IP)與第 6 層(行為)仍然獨立運作。
誤解 3:Cloudflare 能藉指紋識別出「我是誰」。
正確理解:TLS 與 HTTP/2 指紋是群組識別碼,不是個人識別碼。同一版 Chrome 在同一作業系統上的所有使用者共享同一個指紋——這正是它的設計目的:判斷「這是什麼工具」,而不是「這是哪個人」。把指紋當成使用者 ID 是常見的誤解。
誤解 4:只要挑戰頁沒出現,就代表網站沒有 Bot Management。
正確理解:挑戰頁不出現是「被動訊號已足夠判定為真人」的結果,屬於系統正常運作,而非系統不存在。
總結
核心要點
偵測邏輯 = 分層採集 + 交叉比對 + 分數決策
六層偵測:
├─ 第 1 層 網路層 IP/ASN、TCP 指紋
├─ 第 2 層 TLS 層 JA3 / JA4 → 系列 02
├─ 第 3 層 HTTP/2 連線層 Akamai 指紋 → 系列 03
├─ 第 4 層 HTTP 請求層 標頭順序、Client Hints
├─ 第 5 層 瀏覽器執行層 JS 挑戰、Canvas → 系列 04
└─ 第 6 層 行為層 頻率、資源載入模式
關鍵不對稱:
真瀏覽器 → 各層天生一致,不需努力
自動化程式 → 需更換整個網路堆疊才能一致
理解重點
-
時序決定一切:TLS 指紋在階段 3 產生,
User-Agent在階段 5 才出現。伺服器讀到你的宣稱時,已經握有更早採集且更難偽造的證據。 -
矛盾比單一訊號更有力:偵測系統的核心手段不是「這個訊號像不像機器人」,而是「這幾個獨立來源的訊號是否互相吻合」。
-
被動與主動的成本分野:被動訊號零成本所以總是執行;主動挑戰有延遲成本所以只在必要時發動。這個成本結構決定了整套系統的行為,也決定了真人為何無感。
-
無感不等於無防護:一般使用者瀏覽正常,是判斷成本被前移到連線階段的結果,而不是防護薄弱的證明。
-
分數只是欄位,規則才是行為:Bot Score 由 Cloudflare 計算,但要不要擋、怎麼擋,完全由各網站自己的規則決定。
快速記憶
| 方面 | 說明 |
|---|---|
| 是什麼 | 判斷請求來自真人或自動化程式的分層偵測系統 |
| 為什麼有效 | 證據來自不同軟體層,客戶端只能控制其中最弱的一層 |
| 怎麼判斷擋在哪層 | 看狀態碼與 cf-mitigated 標頭 |
| 最強的單一手段 | 交叉比對 TLS 指紋、HTTP/2 指紋、宣稱的 User-Agent |
| 無效的常見做法 | 只修改 User-Agent 或其他 HTTP 標頭 |
學習路徑
1. 建立六層全景與請求時序地圖 ✓(本篇)
2. 理解 TLS 指紋為何無法用標頭修補 → 系列 02
3. 理解 HTTP/2 連線層的獨立證據 → 系列 03
4. 理解主動挑戰在檢驗什麼 → 系列 04
延伸閱讀
- TLS 指紋:JA3 與 JA4 完整指南 - 第 2 層的完整拆解
- HTTP/2 指紋與 Akamai Fingerprint - 第 3 層的完整拆解
- Managed Challenge 與 Bot Score 運作機制 - 第 5 層的完整拆解
- Cloudflare 開發者平台總覽 - Cloudflare 產品地圖
- SSL/TLS 憑證完全指南 - TLS 憑證與信任鏈基礎
- HTTP 與 HTTPS - HTTP 協定基礎
建立日期:2026-09-07 最後更新:2026-09-07