selenium+java破解极验滑块验证码

最近在做自动化注册的程序,里面需要一个破解滑块验证码的工具。在网上搜了一些资料,最后以B站的登录验证为例展示出来。使用的技术就是selenium+java,jar包方面为了方便使用maven引入。
代码主要还是copy了网上的资料,测试上使用了bilibili的登录验证,成功率基本可以在80%左右。


<!-- maven dependencies config -->
<!-- maven pom.xml -->
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>3.11.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.codeborne/phantomjsdriver -->
    <dependency>
      <groupId>com.codeborne</groupId>
      <artifactId>phantomjsdriver</artifactId>
      <version>1.4.4</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
    <dependency>
      <groupId>org.jsoup</groupId>
      <artifactId>jsoup</artifactId>
      <version>1.7.2</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>
/**
 * 主程序类
 */

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.*;
import org.openqa.selenium.Point;
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.annotation.processing.SupportedSourceVersion;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.Rectangle;
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.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
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 int pieceNumber = 0; // 小图片数量
  private static String INDEX_URL = "https://passport.bilibili.com/login";//测试B站登录验证码
  private static WebDriver driver;
  private static int testTimes = 100; // 测试100次,成功率在80%左右
  private static int successTimes = 0;

  static {
    System.setProperty("webdriver.chrome.driver", "D://tools//chromedriver.exe");
    driver = new ChromeDriver();
  }

  public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < testTimes; i++) { // test 10 times
      try {
        invoke();
        System.out.println("测试"+(i+1)+"次,成功"+successTimes+"次,成功率为:"+ (((double)successTimes)/(double)(i+1))*100+"%" );
      } catch (IOException e) {
        e.printStackTrace();
      } catch (NullPointerException e){
        e.printStackTrace();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (StaleElementReferenceException e){
        e.printStackTrace();
      }
    }
    System.out.println("Finally,测试"+testTimes+"次,成功"+successTimes+"次,成功率为:"+ (((double)successTimes)/(double)testTimes)*100+"%");
    driver.quit();
  }


  private static void invoke() throws IOException, InterruptedException, StaleElementReferenceException{
    //设置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);
      String gtType = driver.findElement(gtTypeBy).getText();
      waitForLoad(driver, gtInfoBy);
      String gtInfo = driver.findElement(gtInfoBy).getText();//StaleElementReferenceException
      System.out.println(gtType + "---" + gtInfo);
      if(gtType.contains("验证通过")){
        successTimes++;
      }
      /**
       * 再来一次:
       * 验证失败:
       */
      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;// distance to move
    int moveX = new Random().nextInt(10) - 5;
    int moveY = 1;
    Actions actions = new Actions(driver);
    new Actions(driver).clickAndHold(element).perform();//click and hold the moveButton
    Thread.sleep(2000);//slow down
    actions.moveByOffset((xDis+moveX)/2,moveY).perform();
    Thread.sleep((int)(Math.random()*2000));
    actions.moveByOffset((xDis+moveX)/2,moveY).perform();//double move,to slow down the move and escape check
    Thread.sleep(500);
    actions.release(element).perform();
  }


  private static void printLocation(WebElement element) {
    Point point = element.getLocation();
    System.out.println("final:"+point.toString());//(632,360)
  }


  /**
   * 等待元素加载,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(); // pageSource即网页源代码
    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;
    pieceNumber = elements.size();
    moveArray = new int[pieceNumber][2];
    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 < pieceNumber; i++) {
      cutPic(basePath + type + ".jpg"
          , basePath + "result/" + type + i + ".jpg", -moveArray[i][0], -moveArray[i][1], 10, 58);
    }
    //拼接图片
    String[] b = new String[(int)pieceNumber/2];
    for (int i = 0; i < (int)pieceNumber/2; i++) {
      b[i] = String.format(basePath + "result/" + type + "%d.jpg", i);
    }
    mergeImage(b, 1, basePath + "result/" + type + "result1.jpg");
    //拼接图片
    String[] c = new String[(int)pieceNumber/2];
    for (int i = 0; i < (int)pieceNumber/2; i++) {
      c[i] = String.format(basePath + "result/" + type + "%d.jpg", i + (int)pieceNumber/2);
    }
    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 < pieceNumber; 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);
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_28379809/article/details/81210761