4422 字
22 分钟
记一次Cloudflare五秒盾的研究

前言#

在当今的互联网世界中,数据是新的石油。无论是进行市场分析、学术研究还是聚合信息,网络爬虫(Web Scraper)都扮演着至关重要的角色。然而,在我们与海量数据亲密接触的道路上,常常会遇到一道坚固的“城墙”——Cloudflare。特别是其著名的“I’m Under Attack Mode”,也就是我们俗称的“五秒盾”,它像一位尽职的门卫,将绝大多数自动化程序拒之门外。

本文将记录一次深入研究并最终成功“绕过”Cloudflare五秒盾的完整过程。需要强调的是,本文旨在进行技术探讨和学习交流,旨在帮助开发者理解反爬虫机制的运作原理,以便在合规合法的前提下进行数据采集。请读者务必遵守目标网站的robots.txt协议,并以负责任、低频率的方式进行访问,避免对目标服务器造成不必要的负担。

第一章:初探城墙 —— 理解Cloudflare五秒盾的运作机制#

在尝试攻克一个难题之前,我们必须首先理解它。Cloudflare的五秒盾,其官方名称为“I’m Under Attack Mode”,是Cloudflare提供的一项高级安全功能,主要用于抵御DDoS(分布式拒绝服务)攻击。

当网站启用此模式时,所有访问者(无论是人类还是机器人)在首次访问时,都会看到一个加载页面,上面写着“Checking your browser before accessing [website]…”。这个过程通常持续五秒钟,其背后发生了一系列复杂的操作:

  1. JavaScript挑战:Cloudflare的服务器会向客户端(即你的浏览器)发送一段经过高度混淆的JavaScript代码。这段代码的设计目的就是为了验证客户端是否为一个“真实”的、具备完整功能的浏览器。
  2. 环境指纹收集:这段JavaScript代码会在客户端环境中执行,收集大量关于浏览器的“指纹”信息。这包括但不限于:User-Agent、屏幕分辨率、时区、安装的插件、字体支持、Canvas渲染能力等等。这些信息组合起来,可以形成一个高度独特的浏览器身份标识。
  3. 计算与验证:JavaScript代码会执行一系列复杂的数学运算。这些运算的设计对CPU有一定要求,旨在拖慢那些可能试图模拟执行环境的自动化工具。计算完成后,结果会连同浏览器指纹信息一起发送回Cloudflare的服务器进行验证。
  4. 下发“通行证”Cookie:如果服务器验证通过,确认访问者是一个合法的浏览器,它就会在HTTP响应中设置一个特殊的Cookie(例如 cf_clearance)。这个Cookie就像一个在有效期内的通行证。浏览器在后续的请求中只要携带这个Cookie,就可以直接访问网站内容,无需再次接受挑战。

传统的爬虫工具,如Python中的requests库,其本质是一个纯粹的HTTP客户端。它只能发送和接收HTTP报文,本身不具备执行JavaScript的能力。因此,当requests面对五秒盾时,它只能获取到那段用于挑战的JavaScript代码,而无法执行它、完成计算和验证,自然也就无法获取到关键的cf_clearance Cookie,从而陷入无限循环的挑战页面中。

理解了这一点,我们就明确了攻克五秒盾的核心方向:我们需要一个能够模拟真实浏览器行为,特别是能够完美执行JavaScript挑战的工具。

第二章:轻装上阵 —— 使用Cloudscraper应对免费版防护#

对于许多由Cloudflare免费版提供保护的网站,其防护强度相对基础。社区中也因此诞生了一些专门应对此类挑战的利器。在Python生态中,cloudscraper库便是其中的佼佼者。

cloudscraper可以看作是著名requests库的一个“超集”或“魔改版”。它继承了requests所有简单易用的API接口,同时在底层集成了一个JavaScript解释器(如PyExecJSjs2py)和一个专门用于解决Cloudflare挑战的逻辑引擎。

2.1 Cloudscraper 的工作原理浅析#

当我们使用cloudscraper发起一个请求时,它内部的流程大致如下:

  1. 首次请求:像requests一样,它首先向目标URL发送一个标准的GET请求。
  2. 挑战识别:它会检查服务器返回的响应。如果发现响应状态码是503 Service Unavailable403 Forbidden,并且响应体中包含了Cloudflare挑战页面的特征HTML和JavaScript,它就会激活内部的挑战解决模块。
  3. 挑战解析与执行:它会从HTML中提取出混淆的JavaScript挑战代码,并利用内置的JavaScript解释器来执行这段代码,模拟浏览器的计算过程。
  4. 二次请求与Cookie获取:完成计算后,它会将计算结果按照Cloudflare要求的格式,连同第一次请求中获得的一些必要参数,构造一个新的请求发送给服务器的验证URL。如果验证成功,服务器就会返回包含cf_clearance Cookie的响应。
  5. 最终请求cloudscraper会将获取到的cf_clearance Cookie保存在会话(Session)中,然后用这个会话再次请求最初的目标URL。这一次,由于携带了有效的“通行证”,请求将成功穿透五秒盾,获取到真实的网页内容。

2.2 实战演练:三行代码的优雅#

cloudscraper的魅力在于其极简的API设计。开发者几乎不需要改变原有的基于requests库的编程习惯。

第一步:安装

安装过程非常简单,通过pip即可完成:

Terminal window
pip install cloudscraper

请确保你的环境中已经安装了Node.js,因为cloudscraper可能需要调用它作为JavaScript的运行时环境。

第二步:基础使用

假设我们的目标网站是https://example-protected-by-cf.com,绕过其五秒盾只需要简单的三行代码:

import cloudscraper
# 1. 创建一个scraper实例,可以将其看作一个增强版的requests.Session对象
scraper = cloudscraper.create_scraper()
# 2. 使用scraper实例发起GET请求,其用法与requests.get完全一致
# 所有复杂的挑战处理过程都在这一步中自动完成
response = scraper.get('https://example-protected-by-cf.com')
# 3. 打印获取到的网页源代码
print(response.text)

这三行代码的背后,cloudscraper已经悄无声息地完成了上述的所有复杂交互。

2.3 进阶应用:结合解析库提取数据#

在实际的爬虫项目中,我们不仅要获取源码,还要从中提取结构化数据。cloudscraper可以无缝地与lxmlBeautifulSoup等优秀的HTML解析库结合使用。

我们以上文中提到的mv-voice.com的新闻页面为例,演示如何获取页面标题:

import cloudscraper
from lxml import html # 使用lxml库进行高效的XPath解析
# 目标URL
target_url = 'https://mv-voice.com/news/2021/05/04/mountain-view-whisman-students-sent-home-after-children-test-positive-for-covid-19'
# 1. 初始化scraper,并配置一个浏览器User-Agent,这有助于提高成功率
scraper = cloudscraper.create_scraper(
browser={
'browser': 'chrome',
'platform': 'windows',
'desktop': True
}
)
try:
# 2. 发起请求,获取响应
resp_text = scraper.get(target_url).text
# 3. 使用lxml的fromstring方法将HTML文本解析为可操作的元素树对象
tree = html.fromstring(resp_text)
# 4. 使用XPath表达式定位<h1>标签并提取其文本内容
# '//h1/text()' 意思是:在整个文档中查找所有的h1标签,并获取其直接的文本子节点。
# [0] 表示我们只取第一个匹配结果。
title = tree.xpath('//h1/text()')[0].strip()
# 5. 打印最终结果
print(f"成功获取标题: {title}")
except IndexError:
print("未能通过XPath找到标题,请检查XPath表达式或页面结构。")
except Exception as e:
print(f"发生错误: {e}")

运行以上代码,我们能清晰地看到,即使目标网站部署了Cloudflare的基础防护,我们依然能够成功获取并解析其内容,实现了“破盾”。cloudscraper的接口与requests保持高度一致,迁移成本极低,极大地简化了开发流程。

然而,cloudscraper并非万能。随着Cloudflare防护策略的不断升级,特别是对于付费版用户提供的更高级别的保护,单纯依赖JavaScript解释器模拟执行的方式,成功率会大大降低。此时,我们就需要请出更为强大的“重型武器”。

第三章:重装出击 —— FlareSolverr 与付费版防护的较量#

cloudscraper碰壁时,通常意味着我们遇到的Cloudflare防护级别更高。它可能采用了更复杂的浏览器指纹检测技术,或者部署了需要真实渲染环境的挑战(如Canvas指纹)。在这种情况下,模拟JavaScript执行已经不足以过关,我们必须动用一个真实的、完整的浏览器核心。

这正是 FlareSolverr 项目的用武之地。

3.1 FlareSolverr 架构解析:一个聪明的“浏览器代理”#

FlareSolverr 的核心思想非常巧妙:既然模拟浏览器很困难,那就直接用一个真实的浏览器来解决问题。

它将自己封装成一个轻量级的HTTP代理服务器。你的爬虫程序不再直接请求目标网站,而是向FlareSolverr服务发送一个请求。FlareSolverr接收到你的请求后,会在内部启动一个由Seleniumundetected-chromedriver驱动的、无头(Headless)的Chrome浏览器实例。

undetected-chromedriver是一个专门为反检测而生的chromedriver补丁版本,它通过修改浏览器内核的特征参数,使得自动化控制的浏览器看起来与普通用户手动操作的浏览器几乎没有区别,极大地提高了规避检测的能力。

整个工作流程如下:

  1. 爬虫程序 向本地运行的FlareSolverr服务(默认端口8191)发送一个POST请求,请求体中包含了真正的目标URL和其他参数。
  2. FlareSolverr服务 接收到请求后,启动一个undetected-chromedriver控制的Chrome浏览器实例。
  3. 浏览器实例 访问真正的目标URL。
  4. 由于这是一个功能齐全的真实浏览器,它可以完美地执行Cloudflare下发的任何JavaScript挑战,渲染页面,通过所有的指纹检测。
  5. 当浏览器成功解决挑战并加载出最终的网页内容后,FlareSolverr会从浏览器中提取出最终的HTML源代码、成功过盾的Cookies(尤其是cf_clearance)以及浏览器当前的User-Agent。
  6. FlareSolverr服务 将这些数据打包成一个JSON对象,作为响应返回给你的爬虫程序。
  7. 爬虫程序 解析这个JSON响应,从中取出网页源代码进行处理。

这个方案的优点是成功率极高,几乎能够应对所有级别的Cloudflare浏览器挑战。但其缺点也同样明显:因为每次请求(在非会话模式下)都需要启动一个完整的浏览器实例,所以资源消耗(CPU和内存)巨大,且响应速度相比cloudscraper会慢上几个数量级。

3.2 部署与实战:Docker带来的便捷#

FlareSolverr官方强烈推荐使用Docker进行部署,因为Docker镜像已经打包好了所有复杂的依赖,包括特定版本的Chrome浏览器、chromedriver以及各种系统库,真正实现了一键启动。

第一步:启动FlareSolverr容器

只需一行命令,即可在后台启动FlareSolverr服务:

Terminal window
docker run -d \
--name=flaresolverr \
-p 8191:8191 \
-e LOG_LEVEL=info \
--restart unless-stopped \
ghcr.io/flaresolverr/flaresolverr:latest

让我们来分解一下这条命令的含义:

  • docker run -d: 在后台(detached mode)运行一个容器。
  • --name=flaresolverr: 为容器指定一个易于识别的名称flaresolverr
  • -p 8191:8191: 将宿主机的8191端口映射到容器的8191端口。这是FlareSolverr API服务的默认端口。
  • -e LOG_LEVEL=info: 设置容器内的环境变量LOG_LEVELinfo,用于控制日志输出的详细程度。调试时可以设置为debug
  • --restart unless-stopped: 设置容器的重启策略。除非手动停止,否则容器在退出后会自动重启,保证了服务的可用性。
  • ghcr.io/flaresolverr/flaresolverr:latest: 指定要使用的Docker镜像,这里我们使用GitHub容器镜像仓库中的最新版本。

命令执行后,FlareSolverr服务就已经在你的机器上静静等待指令了。

第二步:通过API调用FlareSolverr

现在,我们的爬虫代码需要进行一些改造。不再使用requests.get直接访问目标,而是向http://localhost:8191/v1发送一个POST请求。

以下是一个访问受付费版Cloudflare保护的网站(例如coinbase.com的某个页面)的示例:

import requests
import json
# FlareSolverr服务的API端点
flaresolverr_url = "http://localhost:8191/v1"
# 构造请求体(payload)
# 这是我们要传递给FlareSolverr的指令
payload = {
"cmd": "request.get", # 命令:执行一个GET请求
"url": "https://www.coinbase.com/ventures/content", # 真正的目标URL
"maxTimeout": 60000 # 最大超时时间,单位为毫秒
}
# 设置请求头,指明我们发送的是JSON数据
headers = {
'Content-Type': 'application/json'
}
print("正在通过FlareSolverr请求目标网站...")
# 发送POST请求到FlareSolverr服务
response = requests.post(flaresolverr_url, headers=headers, data=json.dumps(payload))
# 解析FlareSolverr返回的JSON数据
response_data = response.json()
# 检查请求是否成功
if response_data.get('status') == 'ok':
# 网页的HTML源代码嵌套在返回JSON的 solution.response 字段中
html_content = response_data['solution']['response']
print("成功获取网页内容!")
# 为了简洁,这里只打印内容的前500个字符
print(html_content[:500] + "...")
else:
print("FlareSolverr处理失败。")
print("错误信息:", response_data.get('message'))

运行这段代码,你会看到,即使coinbase.com部署了强大的Cloudflare防护,我们依然能够通过FlareSolverr这个“中间人”成功获取到其页面源码。

3.3 性能优化:善用Session管理#

FlareSolverr的资源消耗问题是其主要痛点。为了缓解这个问题,它提供了Session(会话)管理功能。

默认情况下,每个request.get命令都会启动一个全新的浏览器实例,用完即毁。而通过创建Session,我们可以预先启动一个浏览器实例,并在多个请求之间复用它。这不仅大大减少了重复启动浏览器带来的性能开销,还能保持登录状态和Cookie,对于需要连续抓取同一个网站多个页面的场景至关重要。

使用Session的流程如下:

  1. 创建会话:发送session.create命令。
  2. 使用会话:在request.getrequest.post命令中,通过session参数指定要使用的会话ID。
  3. 销毁会话:任务完成后,发送session.destroy命令,彻底关闭浏览器实例并释放资源。

这是一个简单的示例:

# ... (前面的url, headers定义不变) ...
# 1. 创建一个Session
session_id = "my_scraper_session_1"
create_payload = {"cmd": "sessions.create", "session": session_id}
requests.post(flaresolverr_url, headers=headers, data=json.dumps(create_payload))
print(f"会话 '{session_id}' 已创建。")
# 2. 使用该Session发起多个请求
urls_to_scrape = ["https://www.coinbase.com/ventures", "https://www.coinbase.com/blog"]
for url in urls_to_scrape:
get_payload = {
"cmd": "request.get",
"url": url,
"session": session_id, # 指定使用哪个session
"maxTimeout": 60000
}
response = requests.post(flaresolverr_url, headers=headers, data=json.dumps(get_payload))
# ... 处理响应 ...
print(f"已使用会话抓取: {url}")
# 3. 任务结束,销毁Session
destroy_payload = {"cmd": "sessions.destroy", "session": session_id}
requests.post(flaresolverr_url, headers=headers, data=json.dumps(destroy_payload))
print(f"会话 '{session_id}' 已销毁。")

通过合理地使用Session机制,我们可以将FlareSolverr的性能发挥到极致,使其成为我们工具箱中应对高强度反爬虫网站的终极解决方案。

第四章:反思与展望 —— 永恒的攻防博弈#

这次对Cloudflare五秒盾的研究,从轻量级的cloudscraper到重量级的FlareSolverr,让我们深刻体会到了网络爬虫与反爬虫技术之间永无止境的“猫鼠游戏”。

  • 技术演进requests代表了HTTP协议的直接交互;cloudscraper通过模拟执行JavaScript迈出了一大步;而FlareSolverr则通过驱动真实浏览器,达到了当前规避技术的天花板。每一种工具的诞生,都是对现有反爬虫技术的一次“回应”。

  • 成本权衡:没有银弹。cloudscraper轻快但成功率有限;FlareSolverr强大但资源消耗巨大。在实际项目中,我们需要根据目标的防护强度、抓取频率和预算成本,灵活选择最合适的工具,甚至可以设计一套“降级”策略:优先尝试cloudscraper,失败后自动切换到FlareSolverr

  • 道德与法律边界:技术的探索不应逾越法律和道德的底线。作为开发者,我们必须始终保持敬畏之心。尊重网站的robots.txt规则,以合理的频率发起请求,主动在User-Agent中标识我们的爬虫身份,这些都是负责任的爬虫开发者应有的基本素养。滥用这些技术进行恶意攻击、数据盗窃等行为,不仅会损害网站利益,最终也会引火烧身。

展望未来,这场攻防博弈还将继续。Cloudflare和其它安全厂商会不断推出新的检测技术,例如基于AI分析用户行为模式、更深层次的硬件指纹识别等。而爬虫技术领域,也必将出现更智能、更高效的应对方案。

对于我们技术人而言,理解这场博弈背后的原理,掌握攻防两端的核心思想,不断学习和适应新的变化,才是立于不败之地的关键。希望本文的探索记录,能为您在这条道路上提供一份有价值的参考。

记一次Cloudflare五秒盾的研究
https://fuwari.vercel.app/posts/20250910/
作者
木木em哈哈
发布于
2025-09-10
许可协议
CC BY-NC-SA 4.0