在上期文章中,我们深入剖析了 TLS / JA3 传输层指纹的生成机制,并给出了基于 curl_cffi 与 tls-client 的伪装方案。然而,很多数据工程师在修补好 TLS 漏洞后,会发现在面对 Cloudflare Enterprise、Akamai 或 DataDome 等顶级风控系统时,仍有一定概率被识别并拦截。
这是因为现代风控系统已经将检测维度升级到了更复杂的协议层——HTTP/2 协议栈指纹(HTTP/2 Fingerprinting),以及针对渲染引擎的无头浏览器(Headless Browser)环境检测。
作为系列博客的第三期,本文将解析 HTTP/2 指纹的识别逻辑,并分享在 Playwright / Puppeteer 自动化测试工具中进行环境伪装的技术实践。
当客户端与服务端完成 TLS 握手并协商使用 HTTP/2 协议后,双方需要建立 Connection 并交换控制帧(Control Frames)。
HTTP/2 协议引入了多路复用(Multiplexing)、流优先级(Stream Priority)和头部压缩(HPACK)等特性。然而,不同浏览器与网络库在建立 HTTP/2 连接时,发送的初始设置帧(SETTINGS Frame)、窗口更新帧(WINDOW_UPDATE Frame)以及优先级帧(PRIORITY Frame)的顺序与参数取值存在显著差异。
SETTINGS_HEADER_TABLE_SIZE
SETTINGS_ENABLE_PUSH
SETTINGS_MAX_CONCURRENT_STREAMS
SETTINGS_INITIAL_WINDOW_SIZE
SETTINGS_MAX_FRAME_SIZE
SETTINGS_MAX_HEADER_LIST_SIZE
:method ➔ :authority ➔ :scheme ➔ :path。net/http2)的伪头顺序与 Chrome 不一致。如果你的请求在 TLS 握手层伪装成 Chrome 120,但在 HTTP/2 SETTINGS 帧中发送了 Python/Go 的默认参数,风控系统同样会在毫秒级内将其判定为异常流量。
对于带有复杂动态 JS 渲染、Behavior 行为轨迹打分(如鼠标轨迹、防刷验证)的目标页面,单纯的 HTTP 请求库往往难以完整执行页面逻辑。此时,使用 Puppeteer 或 Playwright 等无头浏览器(Headless Browser)成为必需。
然而,原生未经过改造的无头浏览器在风控引擎面前几乎是“赤裸”的。
navigator.webdriver:原生无头浏览器中该属性固定返回 true。
HeadlessChrome 关键字。
navigator.plugins 为空,缺失 window.chrome 对象。
Software Renderer 或 SwiftShader。
要在自动化渲染中避开指纹检测,需要结合 playwright-extra + stealth 插件,并在发起网络请求时注入纯净的住宅 IP(以 Novproxy 为例)。
Python
import asyncio
from playwright.async_api import async_playwright
# 代理配置 (使用 Novproxy 静态/动态住宅代理网关)
PROXY_SERVER = "http://gateway.novproxy.com:8080"
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
async def run_stealth_browser():
async with async_playwright() as p:
# 1. 启动 Chromium 实例,并注入掩盖 Headless 特征的启动参数
browser = await p.chromium.launch(
headless=True,
proxy={
"server": PROXY_SERVER,
"username": PROXY_USER,
"password": PROXY_PASS,
},
args=[
"--disable-blink-features=AutomationControlled", # 核心:移除自动化标记
"--disable-dev-shm-usage",
"--no-sandbox",
"--use-gl=desktop", # 启用硬件加速仿真,避免 SwiftShader 特征
]
)
# 2. 创建上下文并初始化真实浏览器环境参数
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
viewport={'width': 1920, 'height': 1080},
locale="en-US",
timezone_id="America/New_York" # 时区需与代理 IP 的实际地理位置对齐,避免时区冲突
)
page = await context.new_page()
# 3. 注入 JavaScript 脚本,在页面加载前修补 Context 对象
await page.add_init_script("""
// 抹去 navigator.webdriver
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// 补全 window.chrome 属性
window.chrome = {
runtime: {}
};
// 伪造语言列表
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en']
});
""")
# 访问测试节点查看渲染效果
await page.goto("https://bot.sannysoft.com/", wait_until="networkidle")
# 截图留存验证
await page.screenshot(path="stealth_result.png", full_page=True)
print("页面渲染完成,截图已保存。")
await browser.close()
if __name__ == "__main__":
asyncio.run(run_stealth_browser())一个成熟的企业级 Web 数据采集系统,在面对不同难度的目标网站时,应当具备分层路由调度的能力:
┌─────────────────────────┐
│ 任务调度中心 (Task) │
└────────────┬────────────┘
│
┌──────────────┴──────────────┐
│ 针对普通 API / 低风控目标 │ 针对高难度 / 复杂 JS 渲染目标
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ HTTP 库指纹伪装 │ │ 无头浏览器 Stealth │
│ (curl_cffi / Go) │ │ (Playwright / CDP) │
└──────────┬─────────┘ └──────────┬─────────┘
│ │
└──────────────┬──────────────┘
│ HTTP / Socks5
▼
┌──────────────────────────┐
│ Novproxy 代理网关 │
│ (动态住宅 / 静态 ISP) │
└────────────┬─────────────┘
▼
┌──────────────────────────┐
│ 目标网站 Web 风控 │
└──────────────────────────┘curl_cffi 这类经过 TLS/HTTP2 指纹修正的轻量级客户端,配合 Novproxy 动态住宅代理进行快速轮换,最大化吞吐效率与并发降低成本。
Timezone/Locale 的严丝合缝。
在下一期分享中,我们将把焦点转向后端基础设施,聊聊“如何在 Kubernetes (TKE) 环境下搭建高可用爬虫集群,并实现代理 IP 的智能健康检查与熔断策略”。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。