rebrowser_playwright 实测:一行 import 没改,Playwright 脚本绕过知乎反爬

rebrowser_playwright 实测:一行 import 没改,Playwright 脚本绕过知乎反爬

我遇到了什么问题

我在写一个多平台内容发布系统,其中知乎发布模块用 Playwright 做浏览器自动化。代码写完第一天就跑通了------登录、写回答、点发布,一切正常。

第二天再跑,知乎直接跳验证码。第三天连验证码都不给了,直接白屏。

同一个脚本、同一个 user_data_dir,什么都没变,就是突然不能用了。我排查了一整天,最后定位到一件事:知乎检测到了 Playwright 的自动化特征,触发了反爬。

如果你在用 Playwright 做任何国内网站的自动化,你大概率已经踩过或者即将踩这个坑。

我试过的方案

方案 A:加 stealth 插件

pip install playwright-stealth,然后在代码里加两行:

python 复制代码
from playwright_stealth import stealth_sync
stealth_sync(page)

结果:知乎有改善------从"直接白屏"变成了"偶尔能加载但频繁弹验证码"。Stealth 藏了 webdriver 标记,但 Chromium 源码里还有几十处指纹点,光靠 JS 注入藏不完。

这个方案我保留着作为辅助层,但单靠它不够。

方案 B:用 rebrowser_playwright 替换 playwright

rebrowser_playwright 是社区维护的 Playwright 分支,直接改了 Chromium 源码层的指纹点,不是 JS 打补丁。安装后 API 完全兼容------把 from playwright.sync_api 改成 from rebrowser_playwright.sync_api,其余代码不动。

python 复制代码
# 之前
from playwright.sync_api import sync_playwright

# 之后
from rebrowser_playwright.sync_api import sync_playwright

结果:同一套业务代码,只改了一行 import,知乎从"必弹验证码"变成"零拦截"。我连续跑了三周,没再触发过反爬。

核心代码

完整的知乎发布上下文启动代码(简化自真实项目):

python 复制代码
# zhihu_publisher.py --- launch_persistent_context 配置

_CHROME_ARGS = [
    "--disable-blink-features=AutomationControlled",
    "--disable-features=IsolateOrigins,site-per-process",
    "--no-sandbox",
    "--disable-gpu",
    "--disable-dev-shm-usage",
    "--disable-infobars",
    "--disable-breakpad",
    "--disable-component-extensions-with-background-pages",
]

_CONTEXT_SETTINGS = {
    "viewport": {"width": 1440, "height": 900},
    "timezone_id": "Asia/Shanghai",
    "locale": "zh-CN",
    "user_agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0.0.0 Safari/537.36"
    ),
}

def _launch_context(self, playwright):
    kwargs = {
        "user_data_dir": self.user_data_dir,   # 持久化登录态
        "headless": False,
        "args": _CHROME_ARGS,
        **_CONTEXT_SETTINGS,
    }
    context = playwright.chromium.launch_persistent_context(**kwargs)
    if self.storage_state.exists():
        state = json.loads(self.storage_state.read_text())
        context.add_cookies(state.get("cookies", []))
    return context

关键参数解释:

  • user_data_dir:持久化存储,登录态不丢。不用每次启动重新登录,也避免了新号频繁登录触发风控。这一行比任何 stealth 技巧都重要。
  • --disable-blink-features=AutomationControlled :关掉 Chromium 自动在 navigator.webdriver 上打的标记。普通 Playwright 即使设 headless=False,这个标记也在。
  • locale + timezone_id:模拟中国用户环境。国内网站对非中文 locale 的请求更敏感。
  • headless=False:有头模式。知乎对 headless Chromium 有专门的检测规则,即使是 rebrowser 也建议有头跑。

附加:每页注入的反检测脚本

rebrowser 解决了源码层,但有些检测点靠 JS 注入更灵活:

javascript 复制代码
// 注入到每个页面的反检测脚本
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
// 伪造 Chrome 插件列表(真实 Chrome 有 4-5 个内置插件)
Object.defineProperty(navigator, 'plugins', {
    get: () => {
        const plugins = [
            { name: 'Chrome PDF Plugin', ... },
            { name: 'Chrome PDF Viewer', ... },
            { name: 'Native Client', ... },
        ];
        Object.setPrototypeOf(plugins, PluginArray.prototype);
        return plugins;
    }
});
// 伪造硬件信息
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });

这段代码每次 page.add_init_script() 注入。rebrowser 已经把大部分指纹改了,但 navigator.pluginsnavigator.hardwareConcurrency 这两个点浏览器指纹差异很大,手动伪造更可控。

踩坑记录

坑 1:launch_persistent_contextuser_data_dir 路径用反斜杠

Windows 下路径写 C:\data\zhihu 会报错。Playwright 内部用 / 分隔路径,Windows 传 C:\\ 会解析异常。改成 C:/data/zhihuPath 对象转换:

python 复制代码
# 报错
user_data_dir = "C:\\Users\\admin\\.browser-data\\zhihu"

# 正常
user_data_dir = str(Path(".browser-data") / "zhihu")

这个坑折腾了我一个下午------报错信息是 Target closed,完全看不出来是路径问题。

坑 2:headless=True 加 rebrowser 还是会被知乎检测

我以为 rebrowser 改了源码层指纹,headless 也能过。结果页面加载后直接报 405 错误,或者无声崩掉------没有报错信息,但页面就是空白。查了才发现知乎对 headless 有单独的检测规则(Canvas 渲染差异、字体渲染差异),rebrowser 也没有完全消除这些差异。修复方法就是把 headless 改成 False------一行配置,问题消失。

坑 3:发布按钮 button:has-text("发布") 误匹配

知乎编辑器里有不止一个含"发布"二字的按钮------"发布回答""发布设置"。用 button:has-text("发布") 会匹配到两个,Playwright 点第一个(可能是"发布设置"而不是"发布回答")。结果是点了半天文章发不出去,页面也没有任何报错,就是不动。

修复方案:放弃 Playwright 选择器,用 JS 精准匹配按钮文本:

python 复制代码
# 坏
page.locator('button:has-text("发布")').click()

# 好
page.evaluate("""() => {
    var btns = document.querySelectorAll('button');
    for (var i = 0; i < btns.length; i++) {
        if (btns[i].textContent.includes('发布回答')) {
            btns[i].click();
        }
    }
}""")

JS 兜底比 Playwright 选择器稳定------不会被 CSS 伪装干扰,也不受元素可见性判断误差影响。

总结

用 Playwright 做国内网站自动化,核心就两条:

  1. rebrowser_playwright 替换原生 Playwright。import 改一行,API 不变,反爬通过率从 0 拉到 90%。
  2. launch_persistent_context + user_data_dir。持久化登录态,越像真人浏览器越不容易触发风控。

剩下的 stealth JS、Chrome args、环境伪装属于锦上添花------主菜是 rebrowser 和持久化上下文。

如果你有类似需求,可以先试着只改一行 import 看效果。多数情况下这一行就够了。


代码来源:项目 zhihu_publisher.py,完整代码可在项目仓库查看。 运行环境:Windows 11, Python 3.10, rebrowser_playwright, Chrome 124