使用node+puppeteer破解验证码

栏目: Node.js · 发布时间: 7年前

内容简介:之前有写过关于puppeteer的相关文章

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

puppeteer初探

puppeteer在开发过程中的实践

没想到你是这样的SSR

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

使用node+puppeteer破解验证码

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

使用node+puppeteer破解验证码

WTF,你让我爬个鸟啊.....

盯着滑块验证码瞅了两天,终于我得出一个结论 滑块验证码阻止了人类文明的进步

每天早上产品

笑眯眯来问我进度的时候,我的内心都是崩溃的

使用node+puppeteer破解验证码

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

使用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();

复制代码

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

使用node+puppeteer破解验证码
使用node+puppeteer破解验证码

这里面,需要注意以下几点

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

你以为这样就结束了

使用node+puppeteer破解验证码

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

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值,来减少比对范围
  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 = [];

    // 比对范围内的像素点
    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 };
}

复制代码

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

使用node+puppeteer破解验证码

此种方法存在的问题

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

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

示例代码均可在 github 查看

使用node+puppeteer破解验证码

以上所述就是小编给大家介绍的《使用node+puppeteer破解验证码》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Clean Architecture

Clean Architecture

Robert C. Martin / Prentice Hall / 2017-9-20 / USD 34.99

Practical Software Architecture Solutions from the Legendary Robert C. Martin (“Uncle Bob”) By applying universal rules of software architecture, you can dramatically improve developer producti......一起来看看 《Clean Architecture》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器