为了账号安全,请及时绑定邮箱和手机立即绑定

前端 - 自动化极验解锁 - puppeteer篇

标签:
JavaScript

前因

某天, 公司一个测试找到我说: ‘大佬, 极验那东西你这配置过吗?’;

我: ‘?’;

我一个前端, 你竟然问我配置过极验没有?!

我: ‘没有, 没配过, 咋了’;

测试: ‘我做自动化测试时, 卡到极验了, 你看前端这能不能把他给关掉, 要不然我过不去登录这一步’;

我内心: ‘WDFK’;

你这…

想起N年前在 puppeteer 刚出的时候, 自己使用 puppeteer 写了一个小测试文件, 里面就有自动化解锁极验的功能, 现在找出来, 修修补补应该还能用

我: ‘一会儿有时间我给你搞个脚本’;

测试: ‘行, 那我先去找下 python 怎么调用js文件’;

翻出N年前的 puppeteer 代码, 装包运行… 报错

没办法, 改吧

开整

  1. 下载插件
puppeteer-core
复制代码
  1. 本机需要有 chrome 并能找到安装地址

  2. 创建 xxx.js 文件, 在文件中编写代码

  3. 运行方式

node xxx.js
复制代码

调起测试窗口

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    // 本机的 chrome 浏览器安装地址
    executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
    timeout: 3000,
    // 操作延迟
    slowMo: 10,
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-features=site-per-process', '--window-size=1400,900'],
  });
  // 新开标签页
  const page = await browser.newPage();
  // 打开指定网址
  await page.goto('https://www.geetest.com/demo/slide-popup.html');
  // 等待极验出现
  await page.waitForSelector(".geetest_holder")
  // 点击显示极验
  await page.click(".geetest_btn")
  await page.waitForTimeout(800);
  // 开始滑动
  await slider();
复制代码

b.gif

开始滑动

思路: 获取滑块按钮, 滑动到图片的阴影缺口处

  1. 获取滑块
async function slider() {
    // 等待canvas完成 并完成0.5s的移动动画 (验证出错也可为等待时间)
    await page.waitForSelector('.geetest_ready', {
      timeout: 0,
    });
    await page.waitForTimeout(500);
    // 获取canvas的左上角X坐标作为滑动的基坐标
    await page.waitForSelector('.geetest_canvas_bg');
    let canvasCoordinate = await page.$('.geetest_canvas_bg');
    let canvasBox = await canvasCoordinate.boundingBox();
    let canvasX = canvasBox.x;
    // 等待滑动按钮出现获取Y坐标
    await page.waitForSelector('.geetest_slider_button');
    let button = await page.$('.geetest_slider_button');
    let box = await button.boundingBox();
    let mouseY = Math.floor(box.y + box.height / 2);
}
复制代码
  1. 计算滑块需要滑动的距离
async function slider() {
    ...
    
    // 计算位移
    let moveDistance = await compare();
}

// 计算位移
// 逻辑: 通过比对在 极验dom中 获取的两个图片的像素差, 大致判断处阴影缺口处位置
async function compare() {
    //  获取canvas
    let moveDistance = await page.evaluate(() => {
      let fullbgs = document.querySelector('.geetest_canvas_fullbg');
      let bgs = document.querySelector('.geetest_canvas_bg');
      let bgsCtx = bgs.getContext('2d');
      let fullbgsCtx = fullbgs.getContext('2d');
      let canvasWidth = bgsCtx.canvas.width;
      let canvasHeight = bgsCtx.canvas.height;
      // 最大像素差(阀值)
      // let pixelsDifference = 100;
      let pixelsDifference = 70;
      // 第一个超过阀值的x坐标 最后一个超过阀值的x坐标
      let firstX, lastX;
      // 对比像素
      for (let i = 1, k = 1; i < canvasWidth; i++) {
        if (!firstX) {
          // 找到第一个超过阀值的X坐标后 Y轴停止循环
          for (let j = 1; j < canvasHeight; j++) {
            // 获取像素数据
            let bgsPx = bgsCtx.getImageData(i, j, 1, 1).data;
            let fullbgsPx = fullbgsCtx.getImageData(i, j, 1, 1).data;
            // 计算像素差 并判断是否超过阀值
            let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
            let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
            let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
            if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
              firstX = i;
              // 记录Y坐标
              k = j;
            }
          }
        } else {
          // 顺着X轴查找最后一个超过阀值的X坐标
          // K是第一个超过阀值的Y坐标
          // (会多一点循环时间 但是不用手动测量阴影块宽度)
          let bgsPx = bgsCtx.getImageData(i, k, 1, 1).data;
          let fullbgsPx = fullbgsCtx.getImageData(i, k, 1, 1).data;
          let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
          let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
          let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
          if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
            lastX = i;
          }
        }
      }
      // 滑动到阴影块中心的距离
      return firstX + (lastX - firstX) / 2;
    });
    return moveDistance;
}
复制代码
  1. 滑动验证
async function slider() {
    ...
    
    // 滑动验证
    await page.hover('.geetest_slider_button');
    // 简单模拟人工滑动速度和状态
    await page.mouse.down();
    await page.mouse.move(canvasX + moveDistance / 3, mouseY, { steps: 15 });
    await page.waitForTimeout(1 * 30);
    await page.mouse.move(canvasX + moveDistance / 2, mouseY, { steps: 20 });
    await page.waitForTimeout(2 * 50);
    await page.mouse.move(canvasX + moveDistance + 10, mouseY, { steps: 18 });
    await page.waitForTimeout(3 * 80);
    await page.mouse.move(canvasX + moveDistance / 1, mouseY, { steps: 60 });
    await page.waitForTimeout(4 * 30);
    await page.mouse.up();
    await page.waitForSelector('.geetest_success_radar_tip_content');
    // 是否验证成功
    let state = await page.evaluate(() => {
      return document.querySelector('.geetest_success_radar_tip_content').innerText;
    });
    if (state !== '验证成功') {
      return slider();
    }
}
复制代码

撒花完结

看下最终效果图

c.gif

完整代码

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
    timeout: 3000,
    slowMo: 10,
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-features=site-per-process', '--window-size=1400,900'],
  });
  // 新开标签页
  const page = await browser.newPage();
  // 打开指定网址
  await page.goto('https://www.geetest.com/demo/slide-popup.html');
  // 等待极验出现
  await page.waitForSelector(".geetest_holder")
  // 点击显示极验
  await page.click(".geetest_btn")
  await page.waitForTimeout(800);
  // 开始滑动
  await slider();
  async function slider() {
    // 等待canvas完成 并完成0.5s的移动动画 (验证出错也可为等待时间)
    await page.waitForSelector('.geetest_ready', {
      timeout: 0,
    });
    await page.waitForTimeout(500);
    // 获取canvas的左上角X坐标作为滑动的基坐标
    await page.waitForSelector('.geetest_canvas_bg');
    let canvasCoordinate = await page.$('.geetest_canvas_bg');
    let canvasBox = await canvasCoordinate.boundingBox();
    let canvasX = canvasBox.x;
    // 等待滑动按钮出现获取Y坐标
    await page.waitForSelector('.geetest_slider_button');
    let button = await page.$('.geetest_slider_button');
    let box = await button.boundingBox();
    let mouseY = Math.floor(box.y + box.height / 2);
    // 计算位移
    let moveDistance = await compare();
    // 滑动验证
    await page.hover('.geetest_slider_button');
    await page.mouse.down();
    await page.mouse.move(canvasX + moveDistance / 3, mouseY, { steps: 15 });
    await page.waitForTimeout(1 * 30);
    await page.mouse.move(canvasX + moveDistance / 2, mouseY, { steps: 20 });
    await page.waitForTimeout(2 * 50);
    await page.mouse.move(canvasX + moveDistance + 10, mouseY, { steps: 18 });
    await page.waitForTimeout(3 * 80);
    await page.mouse.move(canvasX + moveDistance / 1, mouseY, { steps: 60 });
    await page.waitForTimeout(4 * 30);
    await page.mouse.up();
    await page.waitForSelector('.geetest_success_radar_tip_content');
    // 是否验证成功
    let state = await page.evaluate(() => {
      return document.querySelector('.geetest_success_radar_tip_content').innerText;
    });
    if (state !== '验证成功') {
      return slider();
    }
  }
  // 计算位移
  async function compare() {
    //  获取canvas
    let moveDistance = await page.evaluate(() => {
      let fullbgs = document.querySelector('.geetest_canvas_fullbg');
      let bgs = document.querySelector('.geetest_canvas_bg');
      let bgsCtx = bgs.getContext('2d');
      let fullbgsCtx = fullbgs.getContext('2d');
      let canvasWidth = bgsCtx.canvas.width;
      let canvasHeight = bgsCtx.canvas.height;
      // 最大像素差(阀值)
      // let pixelsDifference = 100;
      let pixelsDifference = 70;
      // 第一个超过阀值的x坐标 最后一个超过阀值的x坐标
      let firstX, lastX;
      // 对比像素
      for (let i = 1, k = 1; i < canvasWidth; i++) {
        if (!firstX) {
          // 找到第一个超过阀值的X坐标后 Y轴停止循环
          for (let j = 1; j < canvasHeight; j++) {
            // 获取像素数据
            let bgsPx = bgsCtx.getImageData(i, j, 1, 1).data;
            let fullbgsPx = fullbgsCtx.getImageData(i, j, 1, 1).data;
            // 计算像素差 并判断是否超过阀值
            let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
            let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
            let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
            if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
              firstX = i;
              // 记录Y坐标
              k = j;
            }
          }
        } else {
          // 顺着X轴查找最后一个超过阀值的X坐标
          // K是第一个超过阀值的Y坐标
          // (会多一点循环时间 但是不用手动测量阴影块宽度)
          let bgsPx = bgsCtx.getImageData(i, k, 1, 1).data;
          let fullbgsPx = fullbgsCtx.getImageData(i, k, 1, 1).data;
          let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
          let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
          let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
          if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
            lastX = i;
          }
        }
      }
      // 滑动到阴影块中心的距离
      return firstX + (lastX - firstX) / 2;
    });
    return moveDistance;
  }
  // await browser.close()
})();
复制代码

说明

本地测试用例使用地址为极验官方文档中提供的demo地址, 如有使用问题可联系本人删除…

后果

测试找到的 python 调用 javascript 方式行不通, 没办法, 再给他整理一个 phthon 版本的吧, 自己装得比, 哭着也要装完… 虽然N年没用过 python 了 但是看看文档应该也能写 下次更新 python 版本

作者:yusf
链接:https://juejin.cn/post/6956800708389634079
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消