---
# System prepended metadata

title: 當 Python 爬蟲遇到 Cloudflare
tags: [crawler]

---

# 當 Python 爬蟲遇到 Cloudflare

> 這是一份**研究過程回顧**，聚焦於「為何失敗、如何選擇」，不著墨於程式碼與操作細節。請務必遵守目標網站條款與法規；本文僅供技術學習與經驗分享。

---

## 1. 動機與前提

最初的目標其實很單純：**測試 AI 是否能從零製作出一個能抓整本小說的爬蟲**。選了第一個目標站就遇到 Cloudflare 防護，導致一般爬蟲手法全面失靈。從此研究重心轉為：在**合規**前提下，理解並評估各種「可行與不可行」的路徑，包含是否一定要執行 JavaScript、指紋/行為層面的要求、與是否能在不破壞服務的情況下完成資料整理。

---

## 2. 遇到的問題

* **挑戰與錯誤碼**：反覆出現 Cloudflare 的 PAT/Turnstile 挑戰；`/ajax_novels/...` 取得章節清單時回 **403**；挑戰流程的中途請求回 **401**。
* **環境綁定**：放行憑證（如 `cf_clearance`）**常與 UA、IP、指紋**（字體、圖形、TLS、擴充等）綁定；一旦切換到不同環境（例如改成無頭或改用純 `requests`），很容易立即失效。
* **指紋差異**：無頭瀏覽器、打包的 Chromium、缺少使用者自有的字型/擴充或 TLS 行為，容易被識別為自動化而再次被挑戰。
* **行為偵測**：過快的切換、固定節奏、缺乏「暖機」與隨機延遲，也可能被視為異常行為。

---

## 3. 方案的調整與失敗原因

以下依時間順序，說明每一次調整時的**考量 → 觀察 → 失敗原因**。

### 3.1 `requests` 純抓

* **考量**：成本最低、實作最簡、希望直接讀 HTML 或 AJAX 端點。
* **觀察**：403/401 居多，章節 AJAX 端點被擋；頁面顯示「請稍候/Just a moment」。
* **為何失敗**：無 JS 執行、無有效 cookie/Referer、TLS/UA 指紋不符。

### 3.2 `cloudscraper` + `requests`

* **考量**：用現成庫處理 JS 挑戰，避免開瀏覽器。
* **觀察**：沒有成功過；現象與 3.1 類似（持續被挑戰/403/401）。
* **為何失敗**：互動式驗證無法自動完成；指紋/UA/IP 綁定導致憑證易失效。

### 3.3 Playwright + Bundled Chromium（無頭）

* **考量**：可執行 JS，全自動化。
* **觀察**：沒有成功過；現象與 3.1 類似（持續被挑戰/403/401）。
* **為何失敗**：無頭指紋與真人差異過大（UA、Navigator、Canvas/WebGL、`sec-ch-ua*`、TLS 等）。

### 3.4 Playwright + 單一 Context + 抖動延遲

* **考量**：同一個 session、降低速度與固定節奏，嘗試「像真人」。
* **觀察**：沒有成功過；現象與 3.1 類似（持續被挑戰/403/401）。
* **為何失敗**：**根因仍是指紋**，行為層面的擬真不足以掩蓋。

### 3.5 Playwright `channel=chrome`（叫出系統 Chrome）

* **考量**：換成系統安裝的 Chrome，期望指紋更接近真人。
* **觀察**：**沒有成功過**；實測皆被 Cloudflare **PAT** 擋下（挑戰循環、無法進入穩定抓取）。
* **為何失敗**：`channel=chrome` 啟動的實例**不是**附著在**既有使用者的 profile/context**，指紋/啟動特徵仍與真人使用情境不同，無法滿足站點的驗證要求。

---

## 4. 最終成功的方案

**Playwright 以 CDP 連線到「系統已安裝的 Chrome」，而且非無頭**。核心是「**同一個環境、不中斷**」。

* **同一個 context**：在你正在使用的 Chrome（或以固定 `user-data-dir` 啟動的 Chrome）完成一次人機驗證後，直接用 CDP attach 到該執行個體，**不要重開或切換到其他工具**。
* **非無頭**：保持可視模式，沿用真實的字型、擴充、TLS、UA 等指紋；無頭切換幾乎必失敗。
* **單頁循序**：在同一個分頁內完成「暖機 → 展開章節 → 逐章擷取」；每一步之間插入隨機延遲與偶爾長休息。
* **避免跨環境**：通過驗證後**不要切 requests** 或改無頭；憑證通常會失效。
* **必要時補 Referer**：列表展開的 AJAX 端點若仍 403，補上正確 Referer 再嘗試。

這個方案之所以穩定，是因為它**與真人瀏覽環境完全一致**，且全程維持同一份「身分」。

---

## 5. 不使用 Python 的替代方案

* **App 內 WebView**：把流程嵌入 App，由使用者在同一視窗完成驗證與展開，程式再讀取 DOM。指紋與真人一致、穩定度高，但需要產品整合與互動設計。
* **第三方代理 API**：把挑戰與風控外包給供應商，專案只接結果；需評估成本、法遵與 SLA。

---

## 6. 對 Cloudflare 阻擋爬蟲的結論

* **Python 現況**：就本研究與實測而言，**目前沒有**能在**無頭**或不依賴真實瀏覽器 context 的前提下，長期穩定通過 Cloudflare PAT/Turnstile 的做法。
* **有頭爬取的意義**：在 Cloudflare 防護下，必須以**有頭＋同一個真實瀏覽器 context** 才較穩；但這等同一般使用流程，且速度需嚴格控管，**已喪失傳統爬蟲（高速/批次/併發）的效益**。
* **熱門套件現況**：`requests`、`cloudscraper`、Playwright/Selenium 的 headless 模式在本案均**無法穩定繞過**限制；切換環境更容易讓 `cf_clearance` 失效。
* **實務可行路徑**：目前觀察最成功、最自然的方案是 **App 內 WebView**（同環境、真人互動）；其次是 **代理第三方 API**。

> 一句話總結：**在強防護網站面前，傳統「無頭爬蟲」不再實用；改以同環境的有頭流程（如 WebView）或走授權/第三方服務，才是現實解。**
