目录
前言
最近我在工作中接到了一个任务,需要编写一个网页自动化脚本。说实话,这是我第一次尝试编写这样的脚本。一些普通的操作,对我来说还算简单,能用Selenium来解决。但在对滑块验证码进行自动化时,只用Selenium已经无法满足,最终结合了OpenCV,才解决了这个难题。自动化滑块验证的解决方案,网上有很多,但用Java来编程的很少,值得一记!
一、配置项目环境
1.安装和引入OpenCV库
直接参考我的另一篇文章即可,里面写的非常详细:在Java中使用OpenCV-CSDN博客
2.引入Selenium库
这里我们使用Maven导入,代码如下:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.21.0</version>
</dependency>
3.下载对应浏览器驱动
Web自动化需要用到浏览器驱动,我使用的是谷歌浏览器,因此需要下载谷歌浏览器的驱动程序。先查询浏览器的内核版本,知道内核版本后,就可以下载对应版本的内核了。
下载解压之后会得到一个chromedriver.exe,这就是我们需要用到的驱动程序。
下载地址:dreamshao/chromedriver (github.com)
下载地址:chromedriver.storage.googleapis.com/index.html(旧版本)
操作如下图:
二、开始编码
1.OpenCV部分-关建编码
做滑块验证自动化,最关键就是位置信息,只要我们能获取到相关的位置信息,我们就能使用selenium来将滑块移动到对应位置。为了获取这一位置,我们需要用到OpenCV,来对图片进行操作。原理便是比较背景图和滑块图的轮廓,找出相似的地方并获取坐标
在编写OpenCV相关代码前,我们需要加载OpenCV库。
//因为我们前面已经配置好,这里可以直接加载库文件
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
加载后,我们需要获取到滑块图已经背景图的图片,这一部分可以用selenium来操作。后面会封装成一个函数,所以这里暂时不管。上OpenCV代码!
读取背景图和滑块图
//背景图地址
String bgPath = "path/path/XXX.jpg";
//滑块图地址
String sdrPath = "path/path/XXX/jpg";
//使用Mat类分别读取背景图和滑块图
Mat bg = Imgcodecs.imread(bgPath);
Mat slider = Imgcodecs.imread(sdrPath);
将背景图和滑块图转换为灰度图 —— 重要步骤
// 新建Mat类,用于存放灰度图
Mat bg_gray = new Mat();
Mat sdr_gray = new Mat();
//将背景图转换为灰度图
Imgproc.cvtColor(bg, bg_gray, Imgproc.COLOR_BGR2GRAY);
//将滑块图转换为灰度图
Imgproc.cvtColor(slider, sdr_gray, Imgproc.COLOR_BGR2GRAY);
//保存灰度图
Imgcodecs.imwrite(".\\bg_gray.png", bg_gray);
Imgcodecs.imwrite(".\\sdr_gray.png", sdr_gray);
在将灰度图转化为轮廓图 —— 重要步骤
//新建两个Mat类来存放轮廓图
Mat bg_edg = new Mat();
Mat sdr_edg = new Mat();
//转成轮廓图。后面两个数值是阈值,阈值可能得调整,调到轮廓清晰即可
Imgproc.Canny(bg_gray, bg_edg, 20, 200);
Imgproc.Canny(sdr_gray, sdr_edg, 20, 200);
//保存轮廓图
Imgcodecs.imwrite(".\\bg_edg.png", bg_edg);
Imgcodecs.imwrite(".\\sdr_edg.png", sdr_edg);
上面两个步骤很重要,如果不做,匹配出来得结果会不准确。代码运行后效果如下:
背景图-原图
滑块图-原图
滑块图-灰度图
背景图-灰度图
背景图-轮廓图
滑块图-轮廓图
对轮廓图进行比对,筛选图轮廓相似的地方,并同方框标出来
//匹配轮廓,并将结果保存到res_img
Mat res_img = new Mat();
Imgproc.matchTemplate(bg_edg, sdr_edg, res_img, Imgproc.TM_CCOEFF_NORMED);
//获取匹配最大和最小的结果
Core.MinMaxLocResult minMaxLoc = Core.minMaxLoc(res_img);
//获取最小的匹配度
double minVal = minMaxLoc.minVal;
//获取最大的匹配度
double maxVal = minMaxLoc.maxVal;
//获取最小匹配度的坐标
Point minLoc = minMaxLoc.minLoc;
//获取最大匹配度的坐标
Point maxLoc = minMaxLoc.maxLoc;
// 获取模板图片(即滑块图)的宽度和高度
int tw = sdr_edg.cols();
int th = sdr_edg.rows();
// 左上角点的坐标
Point tl = maxLoc;
// 计算右下角点的坐标
Point br = new Point(tl.x + tw, tl.y + th);
// 在原背景图上绘制矩形
Imgproc.rectangle(bg, tl, br, new Scalar(0, 0, 255), 2);
// 保存图片
Imgcodecs.imwrite(".\\result.png", bg);
运行结果如下:
非常完美!缺口位置准确,现在只需要一些数学运算,我们就获取到缺口偏移量。
接下来便可以封装成一个函数了(注意图片的读取方式,例子是从http上读取图片),
代码如下:
public double getSilderOffset(String bgPath, String sdrPath) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//读取图片需要具体情况具体分析,这里的例子是从http上读取图片
//读取背景图
Mat bg = loadImageFromURL(bgPath);
//读取滑块图
Mat slider = loadImageFromURL(sdrPath);
// 检查图片是否成功读取
if (bg.empty()|| slider.empty()) {
System.out.println("Error: Image cannot be loaded!");
return 0;
}
//
// 转换为灰度图像
Mat bg_gray = new Mat();
Mat sdr_gray = new Mat();
Imgproc.cvtColor(bg, bg_gray, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(slider, sdr_gray, Imgproc.COLOR_BGR2GRAY);
//保存灰度图
// Imgcodecs.imwrite(".\\bg_gray.png", bg_gray);
// Imgcodecs.imwrite(".\\sdr_gray.png", sdr_gray);
//转换为轮廓图
Mat bg_edg = new Mat();
Mat sdr_edg = new Mat();
Imgproc.Canny(bg_gray, bg_edg, 20, 200); //后面两个数值是阈值,阈值可能得调整,调到轮廓清晰即可
Imgproc.Canny(sdr_gray, sdr_edg, 20, 200);
//保存轮廓图
// Imgcodecs.imwrite(".\\bg_edg.png", bg_edg);
// Imgcodecs.imwrite(".\\sdr_edg.png", sdr_edg);
//模板匹配
Mat res_img = new Mat();
Imgproc.matchTemplate(bg_edg, sdr_edg, res_img, Imgproc.TM_CCOEFF_NORMED);
//获取匹配最大和最小的结果
Core.MinMaxLocResult minMaxLoc = Core.minMaxLoc(res_img);
//获取最小的匹配度
double minVal = minMaxLoc.minVal;
//获取最大的匹配度
double maxVal = minMaxLoc.maxVal;
//获取最小匹配度的坐标
Point minLoc = minMaxLoc.minLoc;
//获取最大匹配度的坐标
Point maxLoc = minMaxLoc.maxLoc;
// 获取模板图片(即滑块图)的宽度和高度
int tw = sdr_edg.cols();
int th = sdr_edg.rows();
// 左上角点的坐标
Point tl = maxLoc;
// 计算右下角点的坐标
Point br = new Point(tl.x + tw, tl.y + th);
// 绘制矩形
Imgproc.rectangle(bg, tl, br, new Scalar(0, 0, 255), 2);
// 保存图片
Imgcodecs.imwrite(".\\result.png", bg);
//返回图片缺口的位置,平移滑块我们只需要x轴坐标,
// 这里缺口取中心点,或许会有些偏差,可自行调整
return tl.x+tw/2;
}
//该函数用于读取从网络上下载的图片
private Mat loadImageFromURL(String imageUrl) {
try {
URL url = new URL(imageUrl);
byte[] imageData = readAllBytes(url.openStream());
ByteBuffer buffer = ByteBuffer.wrap(imageData);
return Imgcodecs.imdecode(new Mat(1, imageData.length, Core.CV_8UC1, buffer), Imgcodecs.IMREAD_COLOR);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
//该函数用于从网络上下载图片
private byte[] readAllBytes(java.io.InputStream is) throws IOException {
byte[] buffer = new byte[is.available()];
int nRead;
int total = 0;
while ((nRead = is.read(buffer, total, buffer.length - total)) > 0) {
total += nRead;
if (total == buffer.length)
buffer = Arrays.copyOf(buffer, buffer.length * 2);
}
return Arrays.copyOf(buffer, total);
}
2.用Selenium平移滑块
从上面OpenCV的代码,我们已经获取到了滑块需要的偏移量,接下来就可以用Selenium来模拟滑块平移操作了,代码如下:
//获取背景图和滑块图的元素
WebElement bg_img= driver.findElement(By.xpath("//div[@id='xxxxx']"));
WebElement sdr_img= driver.findElement(By.xpath("//div[@id='xxxxx']"));
//获取背景图和滑块图SRC
String bgPath = bg_img_src;
String sdrParh = sdr_img_src;
//获取滑块按钮元素,记得更改选择器
WebElement slider= driver.findElement(By.xpath("//div[@id='xxxxx']"));
//获取偏移量,用上前面咱们封装的函数
double offset = sliderRobot.getSilderOffset(bgPath, sdrPath);
if (slider != null){
Actions actions = new Actions(driver);
//因为后面要转成int,这样直接计算偏差会比较大,实际使用得用些算法来减小误差
int totalSteps = 20;//分解为20步,可自己调,但不能不分解,否则验证无法通过
int stepSize = (int) (offset / totalSteps); // 每一步的宽度
// 点击滑块开始移动
actions.click(slider).perform();
for (int i = 1; i <= totalSteps; i++) {
actions.moveByOffset(stepSize, 0).perform(); // 移动一步
Thread.sleep(20); // 暂停20毫秒,控制移动速度
}
actions.release().perform();//释放
}
最终运行结果:
三、总结
至此,滑块验证码的自动化脚本就完成了,当然,只是一个简单的例子,真正使用的话得使用一些算法来减小偏移误差。哈哈哈哈,用Java来做web自动化,感觉自己就是个异类。
版权归原作者 ZYH_Victor 所有, 如有侵权,请联系我们删除。