欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

selenium+java破解极验滑动验证码的示例代码

程序员文章站 2023-12-17 10:44:40
摘要 分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码。 人工验证的过程 1、打开威锋网注册页面 2、移动鼠标至小滑块,一张...

摘要

分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码。

人工验证的过程

1、打开威锋网注册页面

2、移动鼠标至小滑块,一张完整的图片会出现(如下图1)

selenium+java破解极验滑动验证码的示例代码

3、点击鼠标左键,图片中间会出现一个缺块(如下图2)

selenium+java破解极验滑动验证码的示例代码

4、移动小滑块正上方图案至缺块处

5、验证通过

selenium模拟验证的过程

  1. 加载威锋网注册页面
  2. 下载图片1和缺块图片2
  3. 根据两张图片的差异计算平移的距离x
  4. 模拟鼠标点击事件,点击小滑块向右移动x
  5. 验证通过
  6. 详细分析

1、打开chrome浏览器控制台,会发现图1所示的验证码图片并不是极验后台返回的原图。而是由多个div拼接而成(如下图3)

selenium+java破解极验滑动验证码的示例代码

通过图片显示div的style属性可知,极验后台把图片进行切割加错位处理。把素材图片切割成10 * 58大小的52张小图,再进行错位处理。在网页上显示的时候,再通过css的background-position属性对图片进行还原。以上的图1和图2都是经过了这种处理。在这种情况下,使用selenium模拟验证是需要对下载的验证码图片进行还原。如上图3的第一个div.gt_cut_fullbg_slice标签,它的大小为10px * 58px,其中style属性为background-image: url("http://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp"); background-position: -157px -58px;会把该属性对应url的图片进行一个平移操作,以左上角为参考,向左平移157px,向上平移58px,图片超出部分不会显示。所以上图1所示图片是由26 * 2个10px * 58px大小的div组成(如下图4)。每一个小方块的大小58 * 10

selenium+java破解极验滑动验证码的示例代码

2、下载图片并还原,上一步骤分析了图片具体的混淆逻辑,具体还原图片的代码实现如下,主要逻辑是把原图裁剪为52张小图,然后拼接成一张完整的图。

/**
 *还原图片
 * @param type
 */
private static void restoreimage(string type) throws ioexception {
  //把图片裁剪为2 * 26份
  for(int i = 0; i < 52; i++){
    cutpic(basepath + type +".jpg"
        ,basepath + "result/" + type + i + ".jpg", -movearray[i][0], -movearray[i][1], 10, 58);
  }
  //拼接图片
  string[] b = new string[26];
  for(int i = 0; i < 26; i++){
    b[i] = string.format(basepath + "result/" + type + "%d.jpg", i);
  }
  mergeimage(b, 1, basepath + "result/" + type + "result1.jpg");
  //拼接图片
  string[] c = new string[26];
  for(int i = 0; i < 26; i++){
    c[i] = string.format(basepath + "result/" + type + "%d.jpg", i + 26);
  }
  mergeimage(c, 1, basepath + "result/" + type + "result2.jpg");
  mergeimage(new string[]{basepath + "result/" + type + "result1.jpg",
      basepath + "result/" + type + "result2.jpg"}, 2, basepath + "result/" + type + "result3.jpg");
  //删除产生的中间图片
  for(int i = 0; i < 52; i++){
    new file(basepath + "result/" + type + i + ".jpg").deleteonexit();
  }
  new file(basepath + "result/" + type + "result1.jpg").deleteonexit();
  new file(basepath + "result/" + type + "result2.jpg").deleteonexit();
}

还原过程需要注意的是,后台返回错位的图片是312 * 116大小的。而网页上图片div的大小是260 * 116。

3、计算平移距离,遍历图片的每一个像素点,当两张图的r、g、b之差的和大于255,说明该点的差异过大,很有可能就是需要平移到该位置的那个点,代码如下。

bufferedimage fullbi = imageio.read(new file(basepath + "result/" + full_image_name + "result3.jpg"));
  bufferedimage bgbi = imageio.read(new file(basepath + "result/" + bg_image_name + "result3.jpg"));
  for (int i = 0; i < bgbi.getwidth(); i++){
    for (int j = 0; j < bgbi.getheight(); j++) {
      int[] fullrgb = new int[3];
      fullrgb[0] = (fullbi.getrgb(i, j) & 0xff0000) >> 16;
      fullrgb[1] = (fullbi.getrgb(i, j) & 0xff00) >> 8;
      fullrgb[2] = (fullbi.getrgb(i, j) & 0xff);

      int[] bgrgb = new int[3];
      bgrgb[0] = (bgbi.getrgb(i, j) & 0xff0000) >> 16;
      bgrgb[1] = (bgbi.getrgb(i, j) & 0xff00) >> 8;
      bgrgb[2] = (bgbi.getrgb(i, j) & 0xff);
      if(difference(fullrgb, bgrgb) > 255){
        return i;
      }
    }
  }

4、模拟鼠标移动事件,这一步骤是最关键的步骤,极验验证码后台正是通过移动滑块的轨迹来判断是否为机器所为。整个移动轨迹的过程越随机越好,我这里提供一种成功率较高的移动算法,代码如下。

  public static void move(webdriver driver, webelement element, int distance) throws interruptedexception {
    int xdis = distance + 11;
    system.out.println("应平移距离:" + xdis);
    int movex = new random().nextint(8) - 5;
    int movey = 1;
    actions actions = new actions(driver);
    new actions(driver).clickandhold(element).perform();
    thread.sleep(200);
    printlocation(element);
    actions.movetoelement(element, movex, movey).perform();
    system.out.println(movex + "--" + movey);
    printlocation(element);
    for (int i = 0; i < 22; i++){
      int s = 10;
      if (i % 2 == 0){
        s = -10;
      }
      actions.movetoelement(element, s, 1).perform();
      printlocation(element);
      thread.sleep(new random().nextint(100) + 150);
    }

    system.out.println(xdis + "--" + 1);
    actions.movebyoffset(xdis, 1).perform();
    printlocation(element);
    thread.sleep(200);
    actions.release(element).perform();
  }

完整代码如下

package com.github.wycm;
import org.apache.commons.io.fileutils;
import org.jsoup.jsoup;
import org.jsoup.nodes.document;
import org.jsoup.nodes.element;
import org.jsoup.select.elements;
import org.openqa.selenium.by;
import org.openqa.selenium.point;
import org.openqa.selenium.webdriver;
import org.openqa.selenium.webelement;
import org.openqa.selenium.chrome.chromedriver;
import org.openqa.selenium.interactions.actions;
import org.openqa.selenium.support.ui.expectedcondition;
import org.openqa.selenium.support.ui.webdriverwait;
import javax.imageio.imageio;
import javax.imageio.imagereadparam;
import javax.imageio.imagereader;
import javax.imageio.stream.imageinputstream;
import java.awt.*;
import java.awt.image.bufferedimage;
import java.io.file;
import java.io.fileinputstream;
import java.io.ioexception;
import java.net.url;
import java.util.iterator;
import java.util.random;
import java.util.regex.matcher;
import java.util.regex.pattern;

public class geettestcrawler {
  private static string basepath = "src/main/resources/";
  private static string full_image_name = "full-image";
  private static string bg_image_name = "bg-image";
  private static int[][] movearray = new int[52][2];
  private static boolean movearrayinit = false;
  private static string index_url = "https://passport.feng.com/?r=user/register";
  private static webdriver driver;

  static {
    system.setproperty("webdriver.chrome.driver", "d:/dev/selenium/chromedriver_v2.30/chromedriver_win32/chromedriver.exe");
    if (!system.getproperty("os.name").tolowercase().contains("windows")){
      system.setproperty("webdriver.chrome.driver", "/users/wangyang/workspace/selenium/chromedriver_v2.30/chromedriver");
    }
    driver = new chromedriver();
  }

  public static void main(string[] args) throws interruptedexception {
    for (int i = 0; i < 10; i++){
      try {
        invoke();
      } catch (ioexception e) {
        e.printstacktrace();
      } catch (interruptedexception e) {
        e.printstacktrace();
      }
    }
    driver.quit();
  }
  private static void invoke() throws ioexception, interruptedexception {
    //设置input参数
    driver.get(index_url);

    //通过[class=gt_slider_knob gt_show]
    by movebtn = by.cssselector(".gt_slider_knob.gt_show");
    waitforload(driver, movebtn);
    webelement moveelemet = driver.findelement(movebtn);
    int i = 0;
    while (i++ < 15){
      int distance = getmovedistance(driver);
      move(driver, moveelemet, distance - 6);
      by gttypeby = by.cssselector(".gt_info_type");
      by gtinfoby = by.cssselector(".gt_info_content");
      waitforload(driver, gttypeby);
      waitforload(driver, gtinfoby);
      string gttype = driver.findelement(gttypeby).gettext();
      string gtinfo = driver.findelement(gtinfoby).gettext();
      system.out.println(gttype + "---" + gtinfo);
      /**
       * 再来一次:
       * 验证失败:
       */
      if(!gttype.equals("再来一次:") && !gttype.equals("验证失败:")){
        thread.sleep(4000);
        system.out.println(driver);
        break;
      }
      thread.sleep(4000);
    }
  }

  /**
   * 移动
   * @param driver
   * @param element
   * @param distance
   * @throws interruptedexception
   */
  public static void move(webdriver driver, webelement element, int distance) throws interruptedexception {
    int xdis = distance + 11;
    system.out.println("应平移距离:" + xdis);
    int movex = new random().nextint(8) - 5;
    int movey = 1;
    actions actions = new actions(driver);
    new actions(driver).clickandhold(element).perform();
    thread.sleep(200);
    printlocation(element);
    actions.movetoelement(element, movex, movey).perform();
    system.out.println(movex + "--" + movey);
    printlocation(element);
    for (int i = 0; i < 22; i++){
      int s = 10;
      if (i % 2 == 0){
        s = -10;
      }
      actions.movetoelement(element, s, 1).perform();
//      printlocation(element);
      thread.sleep(new random().nextint(100) + 150);
    }

    system.out.println(xdis + "--" + 1);
    actions.movebyoffset(xdis, 1).perform();
    printlocation(element);
    thread.sleep(200);
    actions.release(element).perform();
  }
  private static void printlocation(webelement element){
    point point = element.getlocation();
    system.out.println(point.tostring());
  }
  /**
   * 等待元素加载,10s超时
   * @param driver
   * @param by
   */
  public static void waitforload(final webdriver driver, final by by){
    new webdriverwait(driver, 10).until(new expectedcondition<boolean>() {
      public boolean apply(webdriver d) {
        webelement element = driver.findelement(by);
        if (element != null){
          return true;
        }
        return false;
      }
    });
  }

  /**
   * 计算需要平移的距离
   * @param driver
   * @return
   * @throws ioexception
   */
  public static int getmovedistance(webdriver driver) throws ioexception {
    string pagesource = driver.getpagesource();
    string fullimageurl = getfullimageurl(pagesource);
    fileutils.copyurltofile(new url(fullimageurl), new file(basepath + full_image_name + ".jpg"));
    string getbgimageurl = getbgimageurl(pagesource);
    fileutils.copyurltofile(new url(getbgimageurl), new file(basepath + bg_image_name + ".jpg"));
    initmovearray(driver);
    restoreimage(full_image_name);
    restoreimage(bg_image_name);
    bufferedimage fullbi = imageio.read(new file(basepath + "result/" + full_image_name + "result3.jpg"));
    bufferedimage bgbi = imageio.read(new file(basepath + "result/" + bg_image_name + "result3.jpg"));
    for (int i = 0; i < bgbi.getwidth(); i++){
      for (int j = 0; j < bgbi.getheight(); j++) {
        int[] fullrgb = new int[3];
        fullrgb[0] = (fullbi.getrgb(i, j) & 0xff0000) >> 16;
        fullrgb[1] = (fullbi.getrgb(i, j) & 0xff00) >> 8;
        fullrgb[2] = (fullbi.getrgb(i, j) & 0xff);

        int[] bgrgb = new int[3];
        bgrgb[0] = (bgbi.getrgb(i, j) & 0xff0000) >> 16;
        bgrgb[1] = (bgbi.getrgb(i, j) & 0xff00) >> 8;
        bgrgb[2] = (bgbi.getrgb(i, j) & 0xff);
        if(difference(fullrgb, bgrgb) > 255){
          return i;
        }
      }
    }
    throw new runtimeexception("未找到需要平移的位置");
  }
  private static int difference(int[] a, int[] b){
    return math.abs(a[0] - b[0]) + math.abs(a[1] - b[1]) + math.abs(a[2] - b[2]);
  }
  /**
   * 获取move数组
   * @param driver
   */
  private static void initmovearray(webdriver driver){
    if (movearrayinit){
      return;
    }
    document document = jsoup.parse(driver.getpagesource());
    elements elements = document.select("[class=gt_cut_bg gt_show]").first().children();
    int i = 0;
    for(element element : elements){
      pattern pattern = pattern.compile(".*background-position: (.*?)px (.*?)px.*");
      matcher matcher = pattern.matcher(element.tostring());
      if (matcher.find()){
        string width = matcher.group(1);
        string height = matcher.group(2);
        movearray[i][0] = integer.parseint(width);
        movearray[i++][1] = integer.parseint(height);
      } else {
        throw new runtimeexception("解析异常");
      }
    }
    movearrayinit = true;
  }
  /**
   *还原图片
   * @param type
   */
  private static void restoreimage(string type) throws ioexception {
    //把图片裁剪为2 * 26份
    for(int i = 0; i < 52; i++){
      cutpic(basepath + type +".jpg"
          ,basepath + "result/" + type + i + ".jpg", -movearray[i][0], -movearray[i][1], 10, 58);
    }
    //拼接图片
    string[] b = new string[26];
    for(int i = 0; i < 26; i++){
      b[i] = string.format(basepath + "result/" + type + "%d.jpg", i);
    }
    mergeimage(b, 1, basepath + "result/" + type + "result1.jpg");
    //拼接图片
    string[] c = new string[26];
    for(int i = 0; i < 26; i++){
      c[i] = string.format(basepath + "result/" + type + "%d.jpg", i + 26);
    }
    mergeimage(c, 1, basepath + "result/" + type + "result2.jpg");
    mergeimage(new string[]{basepath + "result/" + type + "result1.jpg",
        basepath + "result/" + type + "result2.jpg"}, 2, basepath + "result/" + type + "result3.jpg");
    //删除产生的中间图片
    for(int i = 0; i < 52; i++){
      new file(basepath + "result/" + type + i + ".jpg").deleteonexit();
    }
    new file(basepath + "result/" + type + "result1.jpg").deleteonexit();
    new file(basepath + "result/" + type + "result2.jpg").deleteonexit();
  }
  /**
   * 获取原始图url
   * @param pagesource
   * @return
   */
  private static string getfullimageurl(string pagesource){
    string url = null;
    document document = jsoup.parse(pagesource);
    string style = document.select("[class=gt_cut_fullbg_slice]").first().attr("style");
    pattern pattern = pattern.compile("url\\(\"(.*)\"\\)");
    matcher matcher = pattern.matcher(style);
    if (matcher.find()){
      url = matcher.group(1);
    }
    url = url.replace(".webp", ".jpg");
    system.out.println(url);
    return url;
  }
  /**
   * 获取带背景的url
   * @param pagesource
   * @return
   */
  private static string getbgimageurl(string pagesource){
    string url = null;
    document document = jsoup.parse(pagesource);
    string style = document.select(".gt_cut_bg_slice").first().attr("style");
    pattern pattern = pattern.compile("url\\(\"(.*)\"\\)");
    matcher matcher = pattern.matcher(style);
    if (matcher.find()){
      url = matcher.group(1);
    }
    url = url.replace(".webp", ".jpg");
    system.out.println(url);
    return url;
  }
  public static boolean cutpic(string srcfile, string outfile, int x, int y,
                 int width, int height) {
    fileinputstream is = null;
    imageinputstream iis = null;
    try {
      if (!new file(srcfile).exists()) {
        return false;
      }
      is = new fileinputstream(srcfile);
      string ext = srcfile.substring(srcfile.lastindexof(".") + 1);
      iterator<imagereader> it = imageio.getimagereadersbyformatname(ext);
      imagereader reader = it.next();
      iis = imageio.createimageinputstream(is);
      reader.setinput(iis, true);
      imagereadparam param = reader.getdefaultreadparam();
      rectangle rect = new rectangle(x, y, width, height);
      param.setsourceregion(rect);
      bufferedimage bi = reader.read(0, param);
      file tempoutfile = new file(outfile);
      if (!tempoutfile.exists()) {
        tempoutfile.mkdirs();
      }
      imageio.write(bi, ext, new file(outfile));
      return true;
    } catch (exception e) {
      e.printstacktrace();
      return false;
    } finally {
      try {
        if (is != null) {
          is.close();
        }
        if (iis != null) {
          iis.close();
        }
      } catch (ioexception e) {
        e.printstacktrace();
        return false;
      }
    }
  }
  /**
   * 图片拼接 (注意:必须两张图片长宽一致哦)
   * @param files 要拼接的文件列表
   * @param type 1横向拼接,2 纵向拼接
   * @param targetfile 输出文件
   */
  private static void mergeimage(string[] files, int type, string targetfile) {
    int length = files.length;
    file[] src = new file[length];
    bufferedimage[] images = new bufferedimage[length];
    int[][] imagearrays = new int[length][];
    for (int i = 0; i < length; i++) {
      try {
        src[i] = new file(files[i]);
        images[i] = imageio.read(src[i]);
      } catch (exception e) {
        throw new runtimeexception(e);
      }
      int width = images[i].getwidth();
      int height = images[i].getheight();
      imagearrays[i] = new int[width * height];
      imagearrays[i] = images[i].getrgb(0, 0, width, height, imagearrays[i], 0, width);
    }
    int newheight = 0;
    int newwidth = 0;
    for (int i = 0; i < images.length; i++) {
      // 横向
      if (type == 1) {
        newheight = newheight > images[i].getheight() ? newheight : images[i].getheight();
        newwidth += images[i].getwidth();
      } else if (type == 2) {// 纵向
        newwidth = newwidth > images[i].getwidth() ? newwidth : images[i].getwidth();
        newheight += images[i].getheight();
      }
    }
    if (type == 1 && newwidth < 1) {
      return;
    }
    if (type == 2 && newheight < 1) {
      return;
    }
    // 生成新图片
    try {
      bufferedimage imagenew = new bufferedimage(newwidth, newheight, bufferedimage.type_int_rgb);
      int height_i = 0;
      int width_i = 0;
      for (int i = 0; i < images.length; i++) {
        if (type == 1) {
          imagenew.setrgb(width_i, 0, images[i].getwidth(), newheight, imagearrays[i], 0,
              images[i].getwidth());
          width_i += images[i].getwidth();
        } else if (type == 2) {
          imagenew.setrgb(0, height_i, newwidth, images[i].getheight(), imagearrays[i], 0, newwidth);
          height_i += images[i].getheight();
        }
      }
      //输出想要的图片
      imageio.write(imagenew, targetfile.split("\\.")[1], new file(targetfile));

    } catch (exception e) {
      throw new runtimeexception(e);
    }
  }
}

pom文件依赖如下

  <dependency>
   <groupid>org.seleniumhq.selenium</groupid>
   <artifactid>selenium-server</artifactid>
   <version>3.0.1</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
  <dependency>
   <groupid>org.jsoup</groupid>
   <artifactid>jsoup</artifactid>
   <version>1.7.2</version>
  </dependency>

最后

完整代码已上传至github,地址:

附上一张滑动效果图

selenium+java破解极验滑动验证码的示例代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: