无人值守脚本为什么不能复用人的浏览器:一次「登录超时 40 秒」的根因链
华为云成长中心的每日任务脚本靠 Playwright 之外的另一条路:直接 CDP 驱动一个带持久化 profile 的 Chrome,登录成功就把三套 Cookie 收割下来写文件。它有一个自动重登分支——Cookie 判活失败就用账号密码变量重新走一遍登录页。某天这个分支开始连续失败,报的都是同一句话:
等待跳转超时(40s)—— 仍停在 passport.huaweicloud.com因为报错里同时出现了「已用账号密码变量重登」的字样,最容易得出的结论是变量没传进去、或者被截断了。实测把变量取出来量长度、找分隔符,再 dump 页面里输入框的 value,两个框填得一丝不差。变量是对的,结论要从这里往回推翻。
第一环:脚本复用了一个「存在但已经坏死」的浏览器
问题在启动分支的写法上:
if not cdp_up(): # 端口有人听着就算「浏览器可用」
launch_chrome(LOGIN_URL, headless=True)9333 这个调试端口是人机共用的——平时我拿它挂着浏览器看页面、手动排障。只要那个窗口还开着,cdp_up() 就返回真,脚本于是直接复用,完全不去管它里面是什么状态。而被复用的那个浏览器里,会话早已失效,登录页处于一种最骗人的状态:DOM 正常、表单能填、按钮能点中、什么都不发。既不报错,也不弹验证码,就是没有任何网络请求。脚本在里面轮询跳转,40 秒后报超时。整个故障和账号密码没有任何关系。
修法是把无人值守的登录挪到独立端口:
LOGIN_PORT = 9337 # 只给自动重登用,绝不和人的 9333 混
def _own_login_browser():
global PORT, CDP
PORT, CDP = LOGIN_PORT, "http://127.0.0.1:%d" % LOGIN_PORT第二环:端口能换,profile 绝对不能换
换端口之后最自然的下一步是「顺便换个干净的 user-data-dir,省得两个实例打架」。这个想法是错的,而且错得很贵。实测两次对比,同一份账号密码:
老 profile(C:\qinglong\hw_profile) -> 提交 -> 302 到 developer.huaweicloud.com/grow?ticket=ST-...
新 profile -> 提交 -> 需要验证您的身份
使用 181*******37 / 使用 qcx***@qq.com「这台设备可信」这件事是记在老 profile 的 Cookie 里的,不是记在账号上的。换全新 profile 等于把自己变成一个陌生设备,立刻触发二次验证——而二次验证需要真人收验证码,定时任务当场变死路。所以正确的边界是:只换端口,profile 继续共用。
第三环:共用 profile 意味着必须先腾开
同一个 user-data-dir 只能有一个 Chrome 实例。新端口起来时如果老实例还占着这个目录,新进程会静默失败或者把请求转交给已有实例,表现同样是「超时」。所以登录前必须把两个端口上的浏览器都关掉,并且真的等端口释放:
def _port_up(p):
try:
return requests.get("http://127.0.0.1:%d/json/version" % p, timeout=2)\
.json().get("webSocketDebuggerUrl")
except Exception:
return None
def _free_profile_browser():
keep = (PORT, CDP)
for p in (LOGIN_PORT, 9333): # 僵尸的 9337 和人用着的 9333 都要清
PORT, CDP = p, "http://127.0.0.1:%d" % p
try:
b = browser_ws()
cdp_call(b, "Browser.close")
b.close()
except Exception:
pass
PORT, CDP = keep
for _ in range(20):
if not _port_up(LOGIN_PORT) and not _port_up(9333):
return True
time.sleep(1)
return not _port_up(9333)代价要说清楚:这样做会在登录时关掉人正开着的浏览器。这是有意取舍——把「复用共享资源」换成「独占资源 + 每次从全新进程开始」,牺牲一点方便,换掉一整类玄学报错。改完之后同样的账号密码,无窗口模式一次通过,三套 Cookie 全判有效。
附:判活的接口要用写接口,别用看起来最轻的 GET
这次连带修掉的第二个问题。原先用 GET growth/bonus-beans 判登录态,半死的 Cookie 也能正常返回数字:
GET .../growth/bonus-beans -> {"beans":220} 看着完全正常
POST .../mission/query-mission-list-by-condition -> 403 not login用 GET 判,废 Cookie 会被报成「有效」,定时任务于是跳过重登、白跑一场。判据换成任务列表这个 POST,并且要求响应体里出现真正的结构键才算数:
r = s.post(MISSION_TEST_URL, json={"page_no": 1, "page_size": 5},
headers={"csrf": tok}, timeout=20)
ok = r.status_code == 200 and "QueryMissionDtoList" in (r.text or "")小结
这条根因链上没有一处是「账号密码错了」,但报错把它伪装成了凭证问题。三个可复用的结论:① 无人值守脚本要独占运行资源,复用共享资源时必须校验状态而不是校验存在;② 设备信任类凭证绑定 profile,换干净环境前先确认代价;③ 健康检查用最小的真实写接口,最轻的 GET 往往是最宽松的谎言。
- 点赞
- 收藏
- 关注作者
评论(0)