反爬蟲偵測總覽:自動化程式是怎麼被認出來的

從 HTTPS 請求的六個階段拆解 Cloudflare Bot Management 的分層偵測邏輯,解釋為何偽裝 User-Agent 無效。

📚 系列導覽:這是「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?

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 層  行為層        頻率、資源載入模式

關鍵不對稱:
  真瀏覽器 → 各層天生一致,不需努力
  自動化程式 → 需更換整個網路堆疊才能一致

理解重點

  1. 時序決定一切:TLS 指紋在階段 3 產生,User-Agent 在階段 5 才出現。伺服器讀到你的宣稱時,已經握有更早採集且更難偽造的證據。

  2. 矛盾比單一訊號更有力:偵測系統的核心手段不是「這個訊號像不像機器人」,而是「這幾個獨立來源的訊號是否互相吻合」。

  3. 被動與主動的成本分野:被動訊號零成本所以總是執行;主動挑戰有延遲成本所以只在必要時發動。這個成本結構決定了整套系統的行為,也決定了真人為何無感。

  4. 無感不等於無防護:一般使用者瀏覽正常,是判斷成本被前移到連線階段的結果,而不是防護薄弱的證明。

  5. 分數只是欄位,規則才是行為:Bot Score 由 Cloudflare 計算,但要不要擋、怎麼擋,完全由各網站自己的規則決定。

快速記憶

方面 說明
是什麼 判斷請求來自真人或自動化程式的分層偵測系統
為什麼有效 證據來自不同軟體層,客戶端只能控制其中最弱的一層
怎麼判斷擋在哪層 看狀態碼與 cf-mitigated 標頭
最強的單一手段 交叉比對 TLS 指紋、HTTP/2 指紋、宣稱的 User-Agent
無效的常見做法 只修改 User-Agent 或其他 HTTP 標頭

學習路徑

1. 建立六層全景與請求時序地圖 ✓(本篇)
2. 理解 TLS 指紋為何無法用標頭修補 → 系列 02
3. 理解 HTTP/2 連線層的獨立證據   → 系列 03
4. 理解主動挑戰在檢驗什麼          → 系列 04

延伸閱讀


建立日期:2026-09-07 最後更新:2026-09-07

🔗相關文章