selenium+java破解极验滑动验证码的示例代码
转自: https://www.jianshu.com/p/1466f1ba3275
selenium+java破解极验滑动验证码
摘要
分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码。
人工验证的过程
- 打开威锋网注册页面(https://passport.feng.com/?r=user/register)
- 移动鼠标至小滑块,一张完整的图片会出现(如下图1)
- 点击鼠标左键,图片中间会出现一个缺块(如下图2)
- 移动小滑块正上方图案至缺块处
- 验证通过
selenium模拟验证的过程
- 加载威锋网注册页面(https://passport.feng.com/?r=user/register)
- 下载图片1和缺块图片2
- 根据两张图片的差异计算平移的距离x
- 模拟鼠标点击事件,点击小滑块向右移动x
- 验证通过
详细分析
- 打开chrome浏览器控制台,会发现图1所示的验证码图片并不是极验后台返回的原图。而是由多个div拼接而成(如下图3)
通过图片显示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 - 下载图片并还原,上一步骤分析了图片具体的混淆逻辑,具体还原图片的代码实现如下,主要逻辑是把原图裁剪为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();
} 作者:卧颜沉默
链接:https://www.jianshu.com/p/1466f1ba3275
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。还原过程需要注意的是,后台返回错位的图片是312 * 116大小的。而网页上图片div的大小是260 * 116。
- 计算平移距离,遍历图片的每一个像素点,当两张图的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;
}
}
} 作者:卧颜沉默
链接:https://www.jianshu.com/p/1466f1ba3275
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - 模拟鼠标移动事件,这一步骤是最关键的步骤,极验验证码后台正是通过移动滑块的轨迹来判断是否为机器所为。整个移动轨迹的过程越随机越好,我这里提供一种成功率较高的移动算法,代码如下。
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();
} 作者:卧颜沉默
链接:https://www.jianshu.com/p/1466f1ba3275
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - 完整代码如下
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);
}
}
} 作者:卧颜沉默
链接:https://www.jianshu.com/p/1466f1ba3275
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - 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> 作者:卧颜沉默
链接:https://www.jianshu.com/p/1466f1ba3275
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最后
- 完整代码已上传至github,地址:https://github.com/wycm/selenium-geetest-crack
- 附上一张滑动效果图
selenium+java破解极验滑动验证码的示例代码的更多相关文章
- selenium+java破解极验滑动验证码
摘要 分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码. 人工验证的过程 打开威锋网注册页面(https://passport.feng.com/?r=user/r ...
- Python 破解极验滑动验证码
Python 破解极验滑动验证码 测试开发社区 1周前 阅读目录 极验滑动验证码 实现 位移移动需要的基础知识 对比两张图片,找出缺口 获得图片 按照位移移动 详细代码 回到顶部 极验滑动验证码 以 ...
- Python——破解极验滑动验证码
极验滑动验证码 以上图片是最典型的要属于极验滑动认证了,极验官网:http://www.geetest.com/. 现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7 月全球已有十六万家 ...
- 破解极验(geetest)验证码
破解极验(geetest)验证码 这是两年前的帖子: http://www.v2ex.com/t/138479 一个月前的破解程序,我没用过 asp.net ,不知道是不是真的破解了, demo ...
- python验证码识别(2)极验滑动验证码识别
目录 一:极验滑动验证码简介 二:极验滑动验证码识别思路 三:极验验证码识别 一:极验滑动验证码简介 近些年来出现了一些新型验证码,不想旧的验证码对人类不友好,但是这种验证码对于代码来说识别难度上 ...
- thinkphp整合系列之极验滑动验证码
对于建站的筒子们来说:垃圾广告真是让人深恶痛绝:为了清净:搞个难以识别的验证码吧:又被用户各种吐槽:直到后来出现了极验这个滑动的验证码:这真是一个体验好安全高的方案:官网:http://www.gee ...
- vue_drf之实现极验滑动验证码
一.需求 1,场景 我们在很多登录和注册场景里,为了避免某些恶意攻击程序,我们会添加一些验证码,也就是行为验证,让我们相信现在是一个人在交互,而不是一段爬虫程序.现在市面上用的比较多的,比较流行的是极 ...
- selenium处理极验滑动验证码
要爬取一个网站遇到了极验的验证码,这周都在想着怎么破解这个,网上搜了好多知乎上看到有人问了这问题https://www.zhihu.com/question/28833985,我按照这思路去大概实现了 ...
- luffy之多条件登录与极验滑动验证码
多条件登录 JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确. 我们可以通过修改Django认证系统的认证后端( ...
随机推荐
- CSS3实现多样的边框效果
半透明边框 实现效果: 实现代码: <div> 你能看到半透明的边框吗? </div> div { /* 关键代码 */ border: 10px solid rgba(255 ...
- 每天几分钟跟小猫学前端之node系列:用node实现最简单的爬虫
先来段求分小视频: https://www.iesdouyin.com/share/video/6550631947750608142/?region=CN&mid=6550632036246 ...
- 初识JAVA——方法声明和调用
class TempConverter{ public static void main(String[]args) { changeTemp("132"); } //定义花摄 ...
- 面向对象,更适合JavaScript
面向对象程序设计是软件开发中一个很庞大很复杂的话题,它并不是仅仅学会类.继承.封装.多态这些面向对象编程语法元素就表示掌握的,这些语法元素只是实现面向对象程序的工具, 就像砖块.水泥能搭建小屋,也能造 ...
- (转)Go语言并发模型:使用 context
转载自:https://segmentfault.com/a/1190000006744213 context golang 简介 在 Go http包的Server中,每一个请求在都有一个对应的 g ...
- 无效类字符串:ProgID: Excel.Application
网上发现的方案是改注册表,其实用不着那么麻烦,找2种excel文件:xlsx和xls,把默认打开方式都换成你机器上有的程序就行,比如WPS Office的WPS 表格
- 下载网易云VIP音乐
有偿帮助.联系方式在个人信息里.
- EntityFramework 6.x和EntityFramework Core插入数据探讨
前言 一直以来对EF和EF Core都是单独分开来写,从未以比较的形式来讲解,如果您既用过EF 6.x也用过用EF Core是否有了解过EF和EF Core在插入数据时和返回主键有何异同呢?本篇博客是 ...
- Ubuntu下vim中文乱码
在linux中,用vim打开包含中文的文件时,有可能出现乱码 下面的vim配置方法亲测有效 1. 找到你的vimrc文件,也有可能是.vimrc,我的服务器是vimrc,我改的是 有的说建议不要改全局 ...
- 航遇项目react踩坑
1.iconfont应用: a.正常用法如下 <span className='iconfont' > iconfont的代码,例如: </span> b.react不能动态 ...