做浏览器自动化时,登录通常由人来完成(扫码、短信验证码),脚本只需要在登录成功后把登录态保存下来。看起来简单,但"怎么判断已经登录成功"这一步,我们前后改了四版,每一版都是被真实页面教育出来的。
第一版:看地址有没有离开登录页
最直觉的写法:地址里不再含 login、signin、passport,就算登录成功。
url = page.url.lower()if "example.com" in url and not any(k in url for k in ("login", "signin", "passport")): logged_in = True很快就出问题了。有的站点在登录流程中间,会先短暂跳到首页,再跳回去继续后续步骤。就在停在首页的那一瞬间,条件满足,脚本判定"登录成功",保存了一份还没登录的登录态,关掉窗口。真正的报错要等到后面发布时才出现,那时已经很难联想到是登录这一步出的问题。
教训:地址变化只能当"可能登录了"的信号,不能当结论。
第二版:打开一个必须登录才能访问的页面
判断方式改成:打开一个受保护的页面(比如"写文章"页),看会不会被弹回登录页。
但这一版又暴露了新问题:有的单页应用在未登录时,照样把整个后台框架渲染出来,地址一点都不变,只是右上角显示"登录"。只看地址,永远判定为已登录。
更麻烦的是,那次误判还把之前一份虽然旧、但能用的登录态,覆盖成了匿名状态。
第三版:加上"否定特征"
除了看地址,再检查页面上有没有"未登录"的证据,比如登录按钮:
for sel in meta["denied"]: # 比如 "a.login-link" if await probe.locator(sel).first.is_visible(): return False # 看得到登录按钮,就是没登录这里有个坑:选择器要选"登录按钮本身",不要选它的容器。 我们第一次选的是顶栏用户区的容器,结果登录之后这个容器还在(里面换成了头像),于是永远判定为未登录,校验标签页一遍遍弹出来。
另外,单页应用要等前端渲染完才知道登录状态,页面加载完立刻检查会误判。我们实测等 6 秒比较稳妥。
同时还要补一条规则:被重定向离开目标页,本身就等于没有权限。 有的站点对未登录访问受保护页的处理,不是跳登录页,而是送回网站首页——地址里没有任何 login 字样。所以要比对最终地址的路径,是否还是目标页:
from urllib.parse import urlparsewant = urlparse(verify_url).path.rstrip("/")if want and want not in urlparse(probe.url).path.rstrip("/"): return False # 被送走了第四版:别动用户正在操作的页面
第三版的校验逻辑是在用户正在登录的那个页面上跳转。结果用户输到一半,页面被脚本拉去校验页,发现没登录又跳回登录页——用户每次都被打断,永远登不进去。
校验必须在另开的标签页里做:
async def really_logged_in(context, meta) -> bool: probe = await context.new_page() # 另开标签页,不碰用户的页面 try: await probe.goto(meta["verify_url"], wait_until="domcontentloaded") await asyncio.sleep(6) # 等单页应用渲染出登录状态 url = probe.url.lower() if any(k in url for k in ("login", "signin", "passport")): return False want = urlparse(meta["verify_url"]).path.rstrip("/") if want and want not in urlparse(url).path.rstrip("/"): return False for sel in meta.get("denied", []): if await probe.locator(sel).first.is_visible(): return False return True finally: await probe.close()还有两个配套的改动:
- 校验通过就立刻结束轮询。 我们有一版忘了
break,登录成功后还在每 2 秒开一个校验页,持续好几分钟——对网站来说,这就是一串异常访问。 - 用持久化的浏览器配置目录。 以前每次都开全新会话,哪怕上次刚登过也要重新登录。改成
launch_persistent_context(profile_dir)后,浏览器自己记住登录状态,登录态过期时重开窗口多半直接就是已登录,只需要刷新一下导出的登录态。
总结
判断"真的登录了",我们最后用的是三重条件:
- 另开标签页访问受保护页面,地址不是登录页
- 没有被重定向离开目标页
- 页面上没有登录按钮这类否定特征(等前端渲染完再看)
每一条都对应一次真实的误判。如果你也在做类似的自动化,希望能帮你少走几步弯路。