目录

记录一次 Playwright 模拟登录

目录

事情的起因是需要从一个比较有年代的网站上拿到数据存到自己的数据库里面,但是这个网站每次登陆的时候都需要做滑块验证,之前没有搞过相关操作,周末花时间尝试了一下之后感觉整个过程还是比较有意思的,简单记录一下。

首先需要说明的一点是,由于网站比较老,滑动验证和目前主流的方案不同。其大概的实现方式是,在原始图片上挖掉一个方形,将该区域的颜色变为灰色,之后把挖出来的图片提供给用户,用户可以点击图片自由拖动到目标位置。

针对这个情况,破解的思路也很简单,因为被挖掉的部分基本变成了纯色矩形,可以直接使用 opencv 进行图片匹配,找到目标位置。由于获取图片的方式是调用的 screenshot 函数,得到的图片会比较模糊,导致匹配的精度不是特别高。不过这个问题无伤大雅,如果某一次匹配失败了,可以直接刷新页面,重新进行匹配。

通过 opencv 确定缺口位置后,接下来的任务便是模拟鼠标的操作,将被扣下来的图片拖动到目标位置。这部分内容网上可以找到很多实现,我参考的链接是:https://www.cnblogs.com/carl-/p/15761861.html

经过简单实现之后,发现在进行拖动模拟时,我没办法看到鼠标的轨迹,导致我不能确定是匹配错误还是滑动部分出现了问题。尝试显示轨迹之后未果,我被迫开始查看网站的源码,寻求一丝机会。网站登陆部分的 JS 代码大概是下面的结构,脚本会先检查 checkok 变量,如果该变量为 1,就发起登录,而 checkok 是通过验证码函数修改的。

if (checkok != 1) {
    // 提示用户先拖动滑块
    return ;
}

AjaxPost('/login', payloads);

到这里,我开始想,能不能不进行滑块验证,直接把变量的值修改为 1,之后直接进行登录。经过查询之后,浏览器控制台终端就可以完成该任务,playwright 也同样支持:page.evaluate("window.checkok = 1")

完成这部分工作之后,我实现了剩余的点击操作,结果发现虽然变量的值已经被修改,登陆请求可以被正常发送,但是会返回一个验证码已过期的提示消息。果然,老一辈程序员的想法没那么简单。经过我的简单测试,发现只有在进行成功的滑块验证之后再登录,才会有正确的页面信息。也就是说,每个登录应该有其绑定的验证码。

结果是再次回到了破解验证码的问题上,可这就是我一开始没解决的问题。睡了一觉之后,我查看了一下验证码部分的代码,在其中发现了新的解决方案,其核心代码如下所示。从代码中可以发现,验证码的本质是提交了一个 POST 请求,把用户的拖动结果提交给服务器进行验证。

AjaxPost('/yzmcheck', { left: left, top: top }, function (result) {
    if (result.IsSucceed) {
        checkok = 1;
    }
    else {
        checkok = 0;
    }
    $("#textAre").html(result.Msg);
    });

也就是说,滑动并不重要,重要的是提交正确的数据。而我已经有了得到正确数据的代码,那接下来只要在这个页面手动发起一次 POST 请求把答案提交给服务器,就可以进行登录。于是乎,我得到了最终的认证代码:

def run(playwright: Playwright) -> None:
    target_url = ""

    context = browser.new_context()
    page = context.new_page()
    page.goto(target_url)

    page.locator("#yzm").screenshot(path="img.png")
    pos = getPos("img.png")
    
    # 发起验证码 POST 请求
    resp = page.request.post(
        url=target_url + "/yzmcheck",
        params={
            "left": pos[0],
            "top": pos[1],
            "json": True
        }
    )

    res = resp.json()
    if res['IsSucceed']:
        # 修改js变量 并登录
        page.evaluate("window.checkok = 1")
        page.get_by_role("button", name="登录").click()