Playwright在Python 3.12下默认启动必被Cloudflare拦截,因Chromium v128+保留完整无头特征链(如navigator.webdriver=True、Canvas指纹固定等),须用playwright-stealth注入脚本重写运行时环境,并配合行为模拟(hover、滚动)、context隔离复用cf_clearance及IP轮换方可稳定绕过。

单靠 playwright.launch() 默认配置,在 Python 3.12 下几乎必然触发 Cloudflare 的 5 秒盾或 Turnstile 验证页——这不是版本兼容问题,而是自动化痕迹太重。
为什么 Python 3.12 + Playwright 默认启动必被拦截?
Python 版本本身不参与指纹生成,但 Playwright 在 3.12 环境下默认启用的 Chromium 构建(v128+)仍保留完整无头特征链:
-
navigator.webdriver值为True(Cloudflare 第一筛查项) -
WebGLRenderingContext.prototype.getParameter()返回空字符串或报错,暴露无 GPU 渲染环境 -
HTMLCanvasElement.prototype.toDataURL()输出高度可复现,Canvas 指纹哈希值固定 -
screen.availWidth/screen.availHeight锁死在 1280×720 或 1920×1080,缺乏真实设备分布随机性 - 所有页面共用同一
BrowserContext,cf_clearancecookie 无法隔离,多页并发时易失效
必须加 playwright-stealth,且调用时机不能错
这个插件不是“一键开关”,它通过注入脚本重写浏览器运行时环境。关键约束如下:
- 必须在
page.goto()之前调用stealth(page),否则注入失败 - 对每个
page单独调用,stealth()不作用于browser或context - 不能与
--disable-blink-features=AutomationControlled同时使用——后者会覆盖 stealth 对navigator.webdriver的undefined设置,反而更可疑 - 若用异步 API(
async_playwright),需确保await stealth(page)在await page.goto()前完成
示例片段:
from playwright.async_api import async_playwright
from playwright_stealth import stealth
<p>async def run():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = await context.new_page()
await stealth(page) # ← 必须在这里,且仅对 page 生效
await page.goto("<a href="https://www.php.cn/link/173e097f39844e299a745824e4c90e04">https://www.php.cn/link/173e097f39844e299a745824e4c90e04</a>")
</p>
绕过 Turnstile 还得补行为模拟,光有指纹不够
Cloudflare Turnstile 会静默监听交互信号。即使 stealth() 让指纹“像人”,没行为仍是机器人。必须加入以下三步:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 访问目标页前,先
await page.goto("about:blank"),await page.wait_for_timeout(1500)再跳转,避免直连触发速率警报 -
await page.hover("header")或任意非关键 DOM 元素,持续500–800ms(page.hover()自带自然延迟) -
await page.wait_for_load_state("networkidle")后,执行await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 2)"),触发视口内资源加载
注意:page.wait_for_load_state("networkidle") 在某些 Cloudflare 页面可能永不返回,建议改用 page.wait_for_function("window.cfTurnstile || true") 显式等待 Turnstile 脚本加载完毕。
context 隔离和 cf_clearance 复用是稳定性的命门
Cloudflare 的 cf_clearance cookie 有效期通常为 2–24 小时,但只对同一 BrowserContext 有效。频繁新建 context 会导致反复挑战。
- 用
context.storage_state(path="state.json")保存登录态和 clearance,下次启动时传入storage_state="state.json" - 避免在循环中重复
browser.new_context(),应复用 context 并用context.new_page()开新页 - 若需多账号或隔离环境,用不同
user_data_dir启动多个 browser 实例,而非共享 context - 不要手动读写
cf_clearancecookie——Playwright 会自动管理,手动设置易因 domain/path 不匹配而失效
真正容易被忽略的是:Cloudflare 会根据 TLS 指纹、TCP 栈行为等底层网络特征打分。即便指纹和行为全伪装成功,长期使用同一代理 IP 或数据中心出口 IP,仍可能在 2–3 小时后突然失效——这不是代码问题,是 IP 信誉坍塌。此时唯一解法是切换出口 IP,而非调参。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










