网页自动化里最容易被忽略的一步:怎么判断"真的登录了" - 九宸智能

发布时间: · 更新时间:

浏览器自动化里判断登录是否成功,我们改了四版:只看地址会误判,单页应用、重定向、否定特征、别打断用户各有坑。

做浏览器自动化时,登录通常由人来完成(扫码、短信验证码),脚本只需要在登录成功后把登录态保存下来。看起来简单,但"怎么判断已经登录成功"这一步,我们前后改了四版,每一版都是被真实页面教育出来的。

第一版:看地址有没有离开登录页

最直觉的写法:地址里不再含 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()

还有两个配套的改动:

总结

判断"真的登录了",我们最后用的是三重条件:

  1. 另开标签页访问受保护页面,地址不是登录页
  2. 没有被重定向离开目标页
  3. 页面上没有登录按钮这类否定特征(等前端渲染完再看)

每一条都对应一次真实的误判。如果你也在做类似的自动化,希望能帮你少走几步弯路。