<li id="fw3su"></li>
  • <li id="fw3su"></li>
  • <div id="fw3su"><tr id="fw3su"></tr></div>
    <dl id="fw3su"></dl>
  • <div id="fw3su"><tr id="fw3su"></tr></div>
  • <sup id="fw3su"></sup>
    <progress id="fw3su"></progress><div id="fw3su"><tr id="fw3su"></tr></div><input id="fw3su"><ins id="fw3su"></ins></input>

    使用node+puppeteer破解验证码

    之前有写过关于puppeteer的相关文章

    puppeteer初探

    puppeteer在开发过程中的实践

    没想到你是这样的SSR

    前一段时间,LZ又接到一个需求,要爬取某快递公司网站的订单数据,起初觉得不就是爬一下数据嘛,虽然nodejs玩的不是特别溜,但爬一些简单数据还是难不倒我这种战五渣的。

    当我打开网站,输入数据,准备来一波页面结构分析的时候,突然间跳出来一个滑块验证码。卧槽......

    WTF,你让?#36951;?#20010;鸟啊.....

    盯着滑块验证码瞅了两天,终于?#19994;?#20986;一个结论 滑块验证码阻止了人类文明的进步

    每天早上产品

    笑眯眯来问我进度的时候,?#19994;?#20869;心都是崩溃的

    难受归难受,但业务还是要做的。最后,我想到了之前用puppeteer开发的模拟cas(单点登录)来解决我司某些应用在开发、测试环境自动登录的功能。现在我就以一种情况为例,来看下怎么用node+puppeteer高效的破解滑块验证码。

    之前有一兄弟在掘金上写过 用puppeteer破解滑块验证码 , 接下来我们就用一些另外的思路去破解

    这里我们也以前端网为例:

    const puppeteer = require("puppeteer");
    const fs = require("fs");
    const path = require("path");
    const pixels = require("image-pixels");
    const resemble = require("resemblejs");
    
    let page = null;
    const bgImg = path.resolve(__dirname, "bg.png");
    const fullbgImg = path.resolve(__dirname, "fullbg.png");
    
    async function run() {
      const browser = await puppeteer.launch({
        headless: false
      });
      page = await browser.newPage();
        
      // 打开前端网
      await page.goto("https://www.qdfuns.com/");
      await page.waitForSelector(".hand");
    
      await page.click("a[data-type=login]");
      const geetest_btn = ".geetest_btn";
      await page.waitForSelector(geetest_btn);
    
      await page.click(geetest_btn);
      await page.waitFor(1000);
        
      // 获取滑动距离
      async function getDistance() {
      
        // 获取canvas
        let { bg, fullbg } = await page.evaluate(() => {
          const fullbg = document.querySelector(".geetest_canvas_fullbg");
          const bg = document.querySelector(".geetest_canvas_bg");
          return {
            bg: bg.toDataURL(),
            fullbg: fullbg.toDataURL()
          };
        });
    
        bg = bg.replace(/^data:image\/\w+;base64,/, "");
        fullbg = fullbg.replace(/^data:image\/\w+;base64,/, "");
        var bgDataBuffer = new Buffer(bg, "base64");
        var fullbgDataBuffer = new Buffer(fullbg, "base64");
    
        fs.writeFileSync(bgImg, bgDataBuffer);
        fs.writeFileSync(fullbgImg, fullbgDataBuffer);
    
        // 通过resemble比较背景图和缺口图的不同
        resemble(bgImg)
          .compareTo(fullbgImg)
          .ignoreColors()
          .onComplete(async function(data) {
            fs.writeFileSync(path.resolve(__dirname, `diff.png`), data.getBuffer());
          });
    
        var { data } = await pixels(path.resolve(__dirname, `diff.png`), {
          cache: false
        });
        
        // 获取缺口距离左边的做小位置,即计为需要滑动的距离
        let arr = [];
        for (let i = 10; i < 150; i++) {
          for (let j = 80; j < 220; j++) {
            var p = 260 * i + j;
            p = p << 2;
            if (data[p] === 255 && data[p + 1] === 0 && data[p + 2] === 255) {
              arr.push(j);
              break;
            }
          }
        }
        return Math.min(...arr);
      }
    
      const distance = await getDistance();
    
      const button = await page.$(".geetest_slider_button");
      const box = await button.boundingBox();
      const axleX = Math.floor(box.x + box.width / 2);
      const axleY = Math.floor(box.y + box.height / 2);
    
      await btnSlider(distance);
      
      // 滑动滑块
      async function btnSlider(distance) {
        await page.mouse.move(axleX, axleY);
        await page.mouse.down();
        await page.waitFor(200);
        await page.mouse.move(box.x + distance / 4, axleY, { steps: 20 });
        await page.waitFor(200);
        await page.mouse.move(box.x + distance / 3, axleY, { steps: 18 });
        await page.waitFor(350);
        await page.mouse.move(box.x + distance / 2, axleY, { steps: 15 });
        await page.waitFor(400);
        await page.mouse.move(box.x + (distance / 3) * 2, axleY, { steps: 15 });
        await page.waitFor(350);
        await page.mouse.move(box.x + (distance / 4) * 3, axleY, { steps: 10 });
        await page.waitFor(350);
        await page.mouse.move(box.x + distance + 30, axleY, { steps: 10 });
        await page.waitFor(300);
        await page.mouse.up();
        await page.waitFor(1000);
    
       const text = await page.evaluate(() => {
          return document.querySelector(".geetest_result_box").innerText;
        });
        console.log(text);
        let step = 0;
        if (text) {
          // 如果失败重新获取滑块
          if (
            text.includes("怪物吃了拼图") ||
            text.includes("拖动滑块将悬浮图像正确拼合")
          ) {
            await page.waitFor(2000);
            await page.click(".geetest_refresh_1");
            await page.waitFor(1000);
            step = await getDistance();
            await btnSlider(step);
          } else if (text.includes("速度超过")) {
            console.log("success");
          }
        }
      }
    }
    run();
    
    复?#25340;?#30721;

    执行该程序,控制台输出如下(运气好的话,可能一次就过了,具体要看中间的处理过程怎么优化求解)

    这里面,需要注意以?#24405;?#28857;

    缺口图存在干扰缺口图,resemble在比对的时候需要会得到两个缺口,这里目前没有一个很好的办法来确定到底哪个缺口是我们所需要的(下面我们会提到一个针对该问题的方法来避免该干扰项)
    滑动的时候需要控制下滑动速度,具体怎么个滑动法,那就仁者见仁智者见智了
    

    你以为这样就结束了

    很多情况下滑块验证码并不会给我们完整的背景图,这时候我们该怎么有效的去定位缺口呢,在这里我们可以使用 gm 把我们的背景图片模糊以下,然后在用 resemblejs 去比对下两个图片,但是此时图片会有很多地方比对出不同,此时我们可以获取到小滑块图片距离父辈元素的位置,借此来减少像素比对?#27573;?这可以有效解决我们上面所提到的避免干扰项问题)

    const puppeteer = require("puppeteer");
    const fs = require("fs");
    const path = require("path");
    const pixels = require("image-pixels");
    const resemble = require("resemblejs");
    const gm = require("gm");
    
    let page = null;
    const bgImg = path.resolve(__dirname, "bg.png");
    const bgBlurImg = path.resolve(__dirname, "bgBlur.png");
    const bgDiffImg = path.resolve(__dirname, "bgDiff.png");
    
    async function run() {
      const browser = await puppeteer.launch({
        headless: false
      });
      page = await browser.newPage();
    
      await page.goto(
        "https://x.tongdun.cn/onlineExperience/slidingPuzzle?source=baidu&plan=%E5%8F%8D%E6%AC%BA%E8%AF%88&unit=%E6%99%BA%E8%83%BD%E9%AA%8C%E8%AF%81&keyword=%E6%99%BA%E8%83%BD%E9%AA%8C%E8%AF%81%E7%A0%81&e_creative=24659987438&e_adposition=cl1&e_keywordid=101045415224&e_keywordid2=101045415224&audience=236369"
      );
      await page.waitForSelector("#loginBtn");
    
      await page.click("#loginBtn");
      const slidetrigger = ".td-pop-slidetrigger";
      await page.waitForSelector(slidetrigger);
    
      await page.click(slidetrigger);
      await page.waitFor(1000);
      const slideIdentity = ".td-pop-slide-identity";
      await page.waitFor(slideIdentity);
    
      // 获取小滑块的top值,来减少比对?#27573;?  const top = await page.evaluate(() => {
        const identity = document.querySelector(".td-pop-slide-identity");
        return identity.offsetTop;
      });
    
      async function getDistance() {
        // 获取缺口图片
        let { bg } = await page.evaluate(() => {
          const bg = document.querySelector(".td-bg-img");
          return {
            bg: bg.toDataURL()
          };
        });
        bg = bg.replace(/^data:image\/\w+;base64,/, "");
        var bgDataBuffer = new Buffer(bg, "base64");
    
        fs.writeFileSync(bgImg, bgDataBuffer);
    
        // 图片模糊
        gm(bgImg)
          .blur(1)
          .write(bgBlurImg, function(err) {
            if (!err) console.log("done");
          });
    
        // 图片对比
        resemble(bgImg)
          .compareTo(bgBlurImg)
          .ignoreColors()
          .onComplete(async function(data) {
            fs.writeFileSync(bgDiffImg, data.getBuffer());
          });
    
        var { data } = await pixels(bgDiffImg, {
          cache: false
        });
        let arr = [];
    
        // 比对?#27573;?#20869;的像素点
        for (let i = top; i < top + 44; i++) {
          for (let j = 60; j < 320; j++) {
            var p = 320 * i + j;
            p = p << 2;
            if (data[p] === 255 && data[p + 1] === 0 && data[p + 2] === 255) {
              arr.push(j);
              break;
            }
          }
        }
        const { maxStr } = getMoreNum(arr);
        return Number(maxStr);
      }
    
      const distance = await getDistance();
      const button = await page.$(slidetrigger);
      const box = await button.boundingBox();
      const axleX = Math.floor(box.x + box.width / 2);
      const axleY = Math.floor(box.y + box.height / 2);
      console.log(distance, "distance");
      console.log(box.x + distance);
    
      await btnSlider(distance);
      async function btnSlider(distance) {
        await page.mouse.move(axleX, axleY);
        await page.mouse.down();
        await page.waitFor(200);
        await page.mouse.move(box.x + distance / 4, axleY, { steps: 20 });
        await page.waitFor(200);
        await page.mouse.move(box.x + distance / 3, axleY, { steps: 18 });
        await page.waitFor(350);
        await page.mouse.move(box.x + distance / 2, axleY, { steps: 15 });
        await page.waitFor(400);
        await page.mouse.move(box.x + (distance / 3) * 2, axleY, { steps: 15 });
        await page.waitFor(350);
        await page.mouse.move(box.x + (distance / 4) * 3, axleY, { steps: 10 });
        await page.waitFor(350);
        await page.mouse.move(box.x + distance + 20, axleY, { steps: 10 });
        await page.waitFor(300);
        await page.mouse.up();
        await page.waitFor(1000);
      }
    }
    
    run();
    
    function getMoreNum(arr) {
      var obj = {};
      var arr1 = [];
      for (var i = 0; i < arr.length; i++) {
        if (arr1.indexOf(arr[i]) == -1) {
          obj[arr[i]] = 1;
          arr1.push(arr[i]);
        } else {
          obj[arr[i]]++;
        }
      }
      var max = 0;
      var maxStr;
      for (var i in obj) {
        if (max < obj[i]) {
          max = obj[i];
          maxStr = i;
        }
      }
      return { max, maxStr };
    }
    
    复?#25340;?#30721;

    该示例没添加错误之后重滑逻辑

    此种方法存在的问题

    • 自己模糊化背景图片就行像素比较,成功率较低,需优化(亦可以通过比对的图片通过其灰度值来锁定区域)

    以上,我们介绍了两种方法来破解解决滑块验证码。此外,LZ还尝试了使用图片二值化方法来进行图片缺口的定位,该方法的成功?#35797;?#39640;于第二种方法,具体实现方法就不写了,读者可以自行探索哈。

    示例代码均可在 github 查看

    我来评几句
    登录后评论

    已发表评论数()

    相关站点

    +订阅
    ?#35753;?#25991;章
    11选五 4887王中王鉄算盘奖结果 竟彩竟彩计算器胜平负 3d试机号奖号和值走势图列表 河北快三形态 北京单场总进球 d历史试机号 25选5开奖号码表 北京单场半全场玩法 p3历史开奖 最大的高频彩合买平台 内蒙古快3开奖走势图 曾道人官方85188网站 快乐时时彩开奖号码查询 nba吧_百度贴吧 彩票浙江体彩十一选五开奖结果查询