puppeteer能破解验证码吗???于是,正好就拿前端网来试试 (纯粹出于学习)

基本的流程

    1. 打开前端网,点击登录。
    1. 填写账号,密码。
    1. 点解验证按钮,通过滑动验证,最后成功登陆。

      代码实现

      run.js

      ```javascript const puppeteer = require(‘puppeteer’); const devices = require(‘puppeteer/DeviceDescriptors’); const iPhone = devices[‘iPhone 6 Plus’]; let timeout = function (delay) { return new Promise((resolve, reject) => {
      1. setTimeout(() => {
      2. try {
      3. resolve(1)
      4. } catch (e) {
      5. reject(0)
      6. }
      7. }, delay);
      }) } let page = null let btn_position = null let times = 0 // 执行重新滑动的次数 const distanceError = [-10,2,3,5] // 距离误差 async function run() { const browser = await puppeteer.launch({ headless:false //这里我设置成false主要是为了让大家看到效果,设置为true就不会打开浏览器 }); page = await browser.newPage(); // 1.打开前端网 await page.emulate(iPhone); await page.goto(‘https://www.qdfuns.com/‘); await timeout(1000);

    // 2.打开登录页面 page.click(‘a[data-type=login]’) await timeout(1000); // 3.输入账号密码 page.type(‘input[data-type=email]’,’你的账号’) await timeout(500); page.type(‘input[placeholder=密码]’,’你的密码’) await timeout(1000);

    // 4.点击验证 page.click(‘.geetest_radar_tip’) await timeout(1000); btn_position = await getBtnPosition(); // 5.滑动 drag(null) } /**

    • 计算按钮需要滑动的距离
    • */ async function calculateDistance() { const distance = await page.evaluate(() => { // 比较像素,找到缺口的大概位置 function compare(document) { const ctx1 = document.querySelector(‘.geetest_canvas_fullbg’); // 完成图片 const ctx2 = document.querySelector(‘.geetest_canvas_bg’); // 带缺口图片 const pixelDifference = 30; // 像素差 let res = []; // 保存像素差较大的x坐标 // 对比像素 for(let i=57;i<260;i++){
      1. for(let j=1;j<160;j++) {
      2. const imgData1 = ctx1.getContext("2d").getImageData(1*i,1*j,1,1)
      3. const imgData2 = ctx2.getContext("2d").getImageData(1*i,1*j,1,1)
      4. const data1 = imgData1.data;
      5. const data2 = imgData2.data;
      6. const res1=Math.abs(data1[0]-data2[0]);
      7. const res2=Math.abs(data1[1]-data2[1]);
      8. const res3=Math.abs(data1[2]-data2[2]);
      9. if(!(res1 < pixelDifference && res2 < pixelDifference && res3 < pixelDifference)) {
      10. if(!res.includes(i)) {
      11. res.push(i);
      12. }
      13. }
      14. }
      } // 返回像素差最大值跟最小值,经过调试最小值往左小7像素,最大值往左54像素 return {min:res[0]-7,max:res[res.length-1]-54} } return compare(document) }) return distance; } /**
    • 计算滑块位置 / async function getBtnPosition() { const btn_position = await page.evaluate(() => { const {clientWidth,clientHeight} = document.querySelector(‘.geetest_popup_ghost’) return {btn_left:clientWidth/2-104,btn_top:clientHeight/2+59} }) return btn_position; } /*
    • 尝试滑动按钮
    • @param distance 滑动距离
    • */
      async function tryValidation(distance) { //将距离拆分成两段,模拟正常人的行为 const distance1 = distance - 10 const distance2 = 10 page.mouse.click(btn_position.btn_left,btn_position.btn_top,{delay:2000}) page.mouse.down(btn_position.btn_left,btn_position.btn_top) page.mouse.move(btn_position.btn_left+distance1,btn_position.btn_top,{steps:30}) await timeout(800); page.mouse.move(btn_position.btn_left+distance1+distance2,btn_position.btn_top,{steps:20}) await timeout(800); page.mouse.up() await timeout(4000);

    // 判断是否验证成功 const isSuccess = await page.evaluate(() => { return document.querySelector(‘.geetest_success_radar_tip_content’) && document.querySelector(‘.geetest_success_radar_tip_content’).innerHTML }) await timeout(1000); // 判断是否需要重新计算距离 const reDistance = await page.evaluate(() => { return document.querySelector(‘.geetest_result_content’) && document.querySelector(‘.geetest_result_content’).innerHTML }) await timeout(1000); return {isSuccess:isSuccess===’验证成功’,reDistance:reDistance.includes(‘怪物吃了拼图’)} } /**

    • 拖动滑块
    • @param distance 滑动距离
    • */ async function drag(distance) { distance = distance || await calculateDistance(); const result = await tryValidation(distance.min) if(result.isSuccess) { await timeout(1000); //登录 console.log(‘验证成功’) page.click(‘#modal-member-login button’) }else if(result.reDistance) { console.log(‘重新计算滑距离录,重新滑动’) times = 0 await drag(null) } else { if(distanceError[times]){ times ++ console.log(‘重新滑动’) await drag({min:distance.max,max:distance.max+distanceError[times]}) } else { console.log(‘滑动失败’) times = 0 run() } } } run() 复制代码
      1. <a name="uxdGX"></a>
      2. ### **package.json**
      3. ```javascript
      4. {
      5. "name": "demo",
      6. "version": "1.0.0",
      7. "dependencies": {
      8. "puppeteer": "^1.0.0"
      9. }
      10. }
      11. 复制代码

      运行

      1. 将这个两个文件保存到文件夹下面,终端切换到当前路径下

      2. npm i

      3. 补上前端网的账号,密码

      4. node run

      演示

      下图演示可以分为四步:

    1. 打开登陆页面,输入事先写好的 账号密码
    1. 第一次拖动滑块提示“ 被怪兽吃了”,所以重新计算了新的图片的缺口距离。
    1. 第二,三次拖动提示 “没正确合拼”,所以重新拖动。
    1. 验证成功,登录

(请将鼠标放到gif上查看演示效果,或者请拖到新窗口打开gif)
pupeteer破解滑块验证码 - 图1

说明

1. 滑动验证有三个canvas,其中只需要 classname为 ‘geetest_canvas_fullbg’ 以及 ‘geetest_canvas_bg’ 的进行像素差对比。ps: 前者是完整图片,后者是带缺口的图片。

pupeteer破解滑块验证码 - 图2

2. 每个带缺口的图片都有一块误导的阴影,所以对比像素差的时候,计算出的距离分别是误导阴影以及缺口的。因此,滑动距离的取值,我取‘{min:res[0]-7,max:res[res.length-1]-54}’。当缺口比误导阴影靠左时,min(距离最小值)值就是滑动距离,否则就是max(距离最大值)减去滑块宽度

pupeteer破解滑块验证码 - 图3

3. 滑动结果分三种情况:验证成功被吃了失败“被吃了”会重新请求图片,所以重新计算了距离再滑动;“失败”则重新滑动,如果执行4次依然失败,则重新run整个流程。

整个流程大概就是这样,有兴趣的朋友可以交流交流