【Python爬虫实战】彻底解决 Cloudflare 403 盾与图片防盗链:手把手教你用 curl_cffi 伪装 TLS 指纹
本文介绍了如何通过模拟TLS指纹解决爬虫请求被Cloudflare等防火墙拦截的问题。文章指出传统修改User-Agent的方法已失效,因为WAF会检测TLS握手过程中的ClientHello包特征。解决方案是使用curl_cffi库模拟浏览器TLS指纹,并设计了一个"双模智能下载器":针对高防域名使用curl_cffi模拟Chrome指纹,普通CDN则用httpx加速下载,同
前言
做爬虫的同学肯定都经历过这样的绝望:
代码写得天衣无缝,逻辑跑得通,但在请求某个网站(尤其是海外站点)时,服务器直接返回 403 Forbidden,或者返回一个写着 "Just a moment..." 的 Cloudflare 质询页面。
这时候,无论你怎么修改 User-Agent,甚至换代理 IP,依然会被拦截。为什么?因为现在的 WAF(Web应用防火墙)已经不只看 HTTP Header 了,它们开始校验 TLS 指纹(JA3/JA4 Fingerprint)。
本文将结合我最近的一个实战项目(爬取某电商数据平台),分享如何设计一个**“双模智能下载器”**,既能通过 curl_cffi 完美绕过 Cloudflare 盾,又能保持对普通 CDN 图片的高速下载,同时解决图片防盗链(Referer)问题。
一、 为什么会被 403?揭秘 TLS 指纹识别
1. 什么是 TLS 指纹?
当我们使用 Python 的 requests 或 httpx 库发起 HTTPS 请求时,客户端和服务器在建立加密连接(TLS 握手)的过程中,会发送一个 Client Hello 包。这个包里包含了加密套件列表、TLS 版本、扩展字段顺序等信息。
-
浏览器(Chrome/Edge)发送的特征是固定的。
-
Python 脚本(requests/urllib)发送的特征也是固定的,但与浏览器完全不同。
2. WAF 的逻辑
Cloudflare 等防火墙会检测这个特征。如果它发现请求头里写着 User-Agent: Chrome/120,但 TLS 指纹却是 python-requests 的特征,它就会判定为**“伪造身份的机器人”**,直接拦截。
这就是为什么你只是换个 UA 没用的原因——你的底裤(TLS指纹)暴露了你。
二、 解决方案:curl_cffi
为了解决这个问题,我们需要使用 curl_cffi 库。它是 curl-impersonate 的 Python 绑定,底层修改了 curl 的代码,使其能够完美模拟主流浏览器(Chrome, Safari, Edge)的 TLS 指纹。
安装:
code Bash
downloadcontent_copy
expand_less
pip install curl_cffi
三、 实战:构建“双模智能”图片下载器
在实际项目中,我们面临两个需求:
-
高防域名(如 findqc.com):有 Cloudflare 盾,必须用 curl_cffi 模拟指纹。
-
普通域名(如 alicdn.com):普通的 CDN 图片,防盗链较弱,但追求速度。
为了兼顾通过率和性能,我们设计了一个**“双客户端”**架构:根据 URL 的域名,动态切换下载引擎。
1. 初始化:准备两把“枪”
我们在类初始化时,同时建立 httpx 客户端(用于普通任务)和 curl_cffi 客户端(用于攻坚任务)。
code Python
downloadcontent_copy
expand_less
import httpx
from curl_cffi.requests import AsyncSession
class ImageDownloader:
def __init__(self):
# 1. 普通 HTTP 客户端 (httpx)
# 优点:轻量、原生异步支持好、速度快
# 缺点:无法通过强校验的 WAF
self.httpx_client = httpx.AsyncClient(
verify=False,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."
}
)
# 2. 特种作战客户端 (curl_cffi)
# 优点:完美模拟浏览器 TLS 指纹,通过 Cloudflare
# 缺点:底层是 C 绑定,开销稍大
self.cffi_client = AsyncSession(
impersonate="chrome120", # 关键:模拟 Chrome 120
timeout=30,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
# 预设一些浏览器标准头
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}
)
2. 核心逻辑:智能路由与防盗链处理
下载图片时,我们需要做两件事:
-
判断域名:决定用哪个客户端。
-
设置 Referer:解决图片防盗链(Hotlink Protection)。很多服务器会检查请求来源,如果 Referer 为空或不对,也会返回 403。
code Python
downloadcontent_copy
expand_less
from urllib.parse import urlparse
async def download_image(self, url: str):
# 解析域名
parsed = urlparse(url)
# === 策略判断 ===
use_cffi = False
headers = {}
# 针对特定高防域名,开启 CFFI 模式,并设置对应的 Referer
if "findqc.com" in parsed.netloc:
use_cffi = True
headers["Referer"] = "https://findqc.com/"
# 针对普通 CDN 域名,使用普通模式,但也要设置 Referer 防盗链
elif "alicdn.com" in parsed.netloc:
headers["Referer"] = "https://www.alibaba.com/"
# === 发送请求 ===
try:
if use_cffi:
# 使用模拟浏览器指纹的客户端
print(f"正在使用 curl_cffi 下载: {url}")
response = await self.cffi_client.get(url, headers=headers)
else:
# 使用普通客户端
print(f"正在使用 httpx 下载: {url}")
response = await self.httpx_client.get(url, headers=headers)
if response.status_code == 200:
# 保存图片...
with open("image.jpg", "wb") as f:
f.write(response.content)
return True
else:
print(f"下载失败: {response.status_code}")
return False
except Exception as e:
print(f"异常: {e}")
return False
四、 关键技术点总结
1. impersonate="chrome120"
这是 curl_cffi 的核心参数。它告诉底层库:“请按照 Chrome 版本 120 的标准,去构造 TLS Client Hello 包”。这包括加密算法的排列顺序、TLS 扩展字段等,让 WAF 认为这就是一个真实的浏览器。
2. User-Agent 与 TLS 指纹的一致性
千万不要犯这个低级错误:用 curl_cffi 模拟了 Chrome 的指纹,却在 Header 里写自己是 Firefox。
这种“精神分裂”的行为会立刻被 WAF 识别。确保 impersonate 的版本和 headers['User-Agent'] 保持一致。
3. Referer 防盗链
图片返回 403 不一定是因为 IP 被封,很有可能是触发了防盗链。
-
现象:浏览器能看图,代码下载不了。
-
解决:将 Referer 头设置为图片所在的网页地址,或者该图片的主域名首页(例如 https://www.google.com或目标网站首页)。
五、 完整代码示例
将上述逻辑整合,我们可以得到一个健壮的异步图片下载器类:
code Python
downloadcontent_copy
expand_less
import asyncio
import httpx
from curl_cffi.requests import AsyncSession
from urllib.parse import urlparse
from pathlib import Path
class SmartImageDownloader:
def __init__(self):
# 普通客户端
self.httpx_client = httpx.AsyncClient(
headers={"User-Agent": "Mozilla/5.0 ..."}
)
# 高防客户端
self.cffi_client = AsyncSession(
impersonate="chrome120",
headers={"User-Agent": "Mozilla/5.0 ..."}
)
async def close(self):
await self.httpx_client.aclose()
self.cffi_client.close()
async def download(self, url, save_path):
parsed = urlparse(url)
# 智能路由逻辑
if "findqc.com" in parsed.netloc:
client = self.cffi_client
headers = {"Referer": "https://findqc.com/"}
else:
client = self.httpx_client
headers = {}
if "alicdn" in parsed.netloc:
headers["Referer"] = "https://taobao.com/"
try:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
with open(save_path, 'wb') as f:
f.write(resp.content)
print(f"下载成功: {save_path}")
else:
print(f"下载失败 {resp.status_code}: {url}")
except Exception as e:
print(f"下载异常: {e}")
# 使用示例
async def main():
downloader = SmartImageDownloader()
# 这是一个高防图片
await downloader.download("https://findqc.com/api/test.jpg", "test1.jpg")
# 这是一个普通图片
await downloader.download("https://img.alicdn.com/test.jpg", "test2.jpg")
await downloader.close()
if __name__ == "__main__":
asyncio.run(main())
结语
爬虫与反爬虫是一场永恒的博弈。对于 Cloudflare 这种级别的对手,单纯修改 HTTP 报文已经不够了,必须深入到底层协议栈。通过 curl_cffi 模拟 TLS 指纹 加上 正确的 Referer 策略,我们可以通过 90% 以上的常见 WAF 防护。
希望这篇文章能帮到正在为此头秃的你!如果有问题,欢迎在评论区交流。
本文首发于 CSDN,转载请注明出处。
更多推荐


所有评论(0)