首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 Playwright 驱动真实 Chrome 批量采集网页版大模型回答:6 个踩坑记录

用 Playwright 驱动真实 Chrome 批量采集网页版大模型回答:6 个踩坑记录

原创
作者头像
用户2440424
发布于 2026-09-22 23:35:41
发布于 2026-09-22 23:35:41
1210
举报

我们需要定期统计一件事:同一批问题问豆包、DeepSeek、通义千问的网页版,回答里提到了谁、引用了哪些网站。API 版不联网,结果和用户在网页上看到的不一样,所以只能自动化操作网页版。

这篇记录从"能跑"到"能稳定跑一整晚"之间踩过的 6 个坑。环境是 Python 3.13 + Playwright,macOS 和 Windows 都要支持。

坑 1:无头浏览器几乎每次都触发验证码

最开始用 Playwright 自带的无头 Chromium,基本上打开页面就是滑块验证。无头浏览器的指纹特征太明显了。

换成的做法是:启动一个真实的、有界面的 Chrome,开远程调试端口,再用 Playwright 通过 CDP 连上去。

代码语言:javascript
复制
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \  --remote-debugging-port=9333 \  --remote-debugging-address=127.0.0.1 \  --user-data-dir="$HOME/.geo-executor/chrome-profile"
代码语言:javascript
复制
browser = await playwright.chromium.connect_over_cdp("http://127.0.0.1:9333")context = browser.contexts[0]page = context.pages[0] if context.pages else await context.new_page()

这里有两个细节必须注意:

  • 调试端口一定绑 127.0.0.1。 绑成 0.0.0.0,等于把这个浏览器的完整控制权开放给局域网里的所有人,包括浏览器里所有已登录的账号。
  • 用独立的 user-data-dir。 和日常使用的 Chrome 共用配置,一是互相干扰,二是脚本异常退出时可能损坏你平时的浏览器配置。

换成这种方式后,验证码基本不再出现。登录由人在这个 Chrome 里扫码完成,脚本不经手任何账号密码。

坑 2:一个浏览器连两个 Playwright 客户端,会互相卡死

调试时很容易出现:源码跑着一个实例,打包好的桌面版又启动了一个,两边都 connect_over_cdp 到同一个 Chrome。结果两个客户端相互阻塞,每一步操作都卡到超时,报错信息完全看不出真正原因——我们为此排查了很久。

解决办法是加一个进程级互斥锁,命令行版和桌面版共用同一把锁:

代码语言:javascript
复制
import fcntlclass SingleInstance:    def __init__(self, path):        self._path = path        self._fh = None    def acquire(self) -> bool:        self._fh = open(self._path, "w")        try:            fcntl.flock(self._fh, fcntl.LOCK_EX | fcntl.LOCK_NB)            return True        except OSError:            self._fh.close()            self._fh = None            return False

Windows 下换成 msvcrt.locking。拿不到锁就直接提示"本机已有执行器在运行",而不是硬连上去。

坑 3:connect_over_cdp 默认不超时

Chrome 没起来、端口被占用时,connect_over_cdp 会一直挂着。外面看到的现象是"程序启动了,但什么也不做",日志里也什么都没有。

所有远程调用都要包一层超时:

代码语言:javascript
复制
ATTACH_TIMEOUT_SECONDS = 60browser = await asyncio.wait_for(    playwright.chromium.connect_over_cdp(endpoint),    timeout=ATTACH_TIMEOUT_SECONDS,)

失败后按指数退避重试。页面级的操作(等回答生成、读取引用)也各自设超时,不依赖默认值。

坑 4:笔记本休眠会让单调时钟停走

一次长任务跑到一半,笔记本合盖休眠了 34 分钟。醒来后任务既没超时也没继续,就停在那里。

原因是 macOS 休眠期间 time.monotonic() 不前进,基于它算的"已经等了多久"永远到不了超时阈值。而服务端给任务的租约是按真实时间算的,早就过期了,整轮任务都被判失败。

处理方式是任务运行期间阻止系统休眠:

代码语言:javascript
复制
import ctypes, os, subprocess, sysif sys.platform == "darwin":    # 进程在就一直阻止休眠,进程退出后自动失效    subprocess.Popen(["caffeinate", "-dimsu", "-w", str(os.getpid())])elif sys.platform == "win32":    ES_CONTINUOUS, ES_SYSTEM_REQUIRED, ES_AWAYMODE_REQUIRED = 0x80000000, 0x00000001, 0x00000040    ctypes.windll.kernel32.SetThreadExecutionState(        ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED    )

服务端同时把这类超时归为"可重试",重新排队,而不是直接判失败。

坑 5:请求节奏本身就是风控信号

连续快速提问 45 次后,通义千问弹出了滑块验证。这说明问得太快、太规律,本身就会被识别。

后来加了三层控制:

  • 每个平台设不同的基础间隔(豆包 20 秒,DeepSeek、千问各 45 秒)
  • 间隔上叠加 ±40% 的随机抖动,避免固定周期
  • 每个账号设每日上限;一旦出现验证码,该账号冷却 15 分钟
代码语言:javascript
复制
import randomDEFAULT_INTERVALS = {"doubao": 20, "deepseek": 45, "tongyi": 45}INTERVAL_JITTER = 0.4def next_interval(platform: str) -> float:    base = DEFAULT_INTERVALS.get(platform, 45)    return base * (1 + random.uniform(-INTERVAL_JITTER, INTERVAL_JITTER))

需要强调:遇到验证码,正确的做法是停下来交给人处理,而不是想办法绕过去。 绕过验证码违反平台规则,也会让账号面临更严格的风控。

坑 6:多账号不能开多个客户端,只能一个循环轮询

豆包、DeepSeek、千问三个账号要同时跑,但坑 2 已经说明不能开多个客户端连同一个浏览器。

最后的做法是:一个事件循环里轮询多个会话,每个会话各自维护页面、间隔和当日配额:

代码语言:javascript
复制
async def run(self):    while not self._stop.is_set():        for session in self._sessions:            if not session.due():            # 还没到这个会话的下次执行时间                continue            if session.quota_exhausted():    # 今天的配额用完了                continue            await self._tick(session)        await asyncio.sleep(1)

三个平台的任务在时间上自然错开,既避开了客户端冲突,也顺带分散了请求节奏。

小结

这类"驱动真实浏览器做自动化"的任务,难点往往不在业务逻辑,而在这些地方:

  • 连真实浏览器,不用无头浏览器
  • 任何远程调用都要有超时
  • 注意宿主机休眠这类环境因素
  • 主动把请求节奏压下来,遇到验证码就交给人

本文由 AI 辅助生成,内容经作者审核并对其负责。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 坑 1:无头浏览器几乎每次都触发验证码
  • 坑 2:一个浏览器连两个 Playwright 客户端,会互相卡死
  • 坑 3:connect_over_cdp 默认不超时
  • 坑 4:笔记本休眠会让单调时钟停走
  • 坑 5:请求节奏本身就是风控信号
  • 坑 6:多账号不能开多个客户端,只能一个循环轮询
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档