赞
踩
目录
最近我在工作中接到了一个任务,需要编写一个网页自动化脚本。说实话,这是我第一次尝试编写这样的脚本。一些普通的操作,对我来说还算简单,能用Selenium来解决。但在对滑块验证码进行自动化时,只用Selenium已经无法满足,最终结合了OpenCV,才解决了这个难题。自动化滑块验证的解决方案,网上有很多,但用Java来编程的很少,值得一记!
直接参考我的另一篇文章即可,里面写的非常详细:在Java中使用OpenCV-CSDN博客
这里我们使用Maven导入,代码如下:
- <dependency>
- <groupId>org.seleniumhq.selenium</groupId>
- <artifactId>selenium-java</artifactId>
- <version>4.21.0</version>
- </dependency>
Web自动化需要用到浏览器驱动,我使用的是谷歌浏览器,因此需要下载谷歌浏览器的驱动程序。先查询浏览器的内核版本,知道内核版本后,就可以下载对应版本的内核了。
下载解压之后会得到一个chromedriver.exe,这就是我们需要用到的驱动程序。
下载地址:dreamshao/chromedriver (github.com)
下载地址:chromedriver.storage.googleapis.com/index.html(旧版本)
操作如下图:
做滑块验证自动化,最关键就是位置信息,只要我们能获取到相关的位置信息,我们就能使用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);
- }

从上面OpenCV的代码,我们已经获取到了滑块需要的偏移量,接下来就可以用Selenium来模拟滑块平移操作了,代码如下:
- //加载驱动
- driverPath="path/path/chromedriver.exe";
- System.setProperty("webdriver.chrome.driver",driverPath);
-
- //打开浏览器
- WebDriver driver = new ChromeDriver();
- //跳转URL
- driver.get("http://......")
-
- //获取背景图和滑块图的元素
- 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自动化,感觉自己就是个异类。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。