0


自动化-滑块验证码 Java + Selenium + OpenCV

目录


前言

  

最近我在工作中接到了一个任务,需要编写一个网页自动化脚本。说实话,这是我第一次尝试编写这样的脚本。一些普通的操作,对我来说还算简单,能用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自动化,感觉自己就是个异类。


本文转载自: https://blog.csdn.net/ZYH_Victor/article/details/140308866
版权归原作者 ZYH_Victor 所有, 如有侵权,请联系我们删除。

“自动化-滑块验证码 Java + Selenium + OpenCV”的评论:

还没有评论