网页自动化里最容易被忽略的一步:怎么判断"真的登录了" - 九宸智能
发布时间:
· 更新时间:
浏览器自动化里判断登录是否成功,我们改了四版:只看地址会误判,单页应用、重定向、否定特征、别打断用户各有坑。
做浏览器自动化时,登录通常由人来完成(扫码、短信验证码),脚本只需要在登录成功后把登录态保存下来。看起来简单,但"怎么判断已经登录成功"这一步,我们前后改了四版,每一版都是被真实页面教育出来的。 第一版:看地址有没有离开登录页 最直觉的写法:地址里不再含 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) 后,浏览器自己记住登录状态,登录态过期时重开窗口多半直接就是已登录,只需要刷新一下导出的登录态。 总结 判断"真的登录了",我们最后用的是三重条件: 另开标签页访问受保护页面,地址不是登录页 没有被重定向离开目标页 页面上没有登录按钮这类否定特征(等前端渲染完再看) 每一条都对应一次真实的误判。如果你也在做类似的自动化,希望能帮你少走几步弯路。