OpenCV 破解滑塊驗證碼 -Java 篇
驗證碼作為一種安全機制,可以有效防止暴力破解密碼、發(fā)帖、灌水、刷票等,大家在做 Web
自動化的時候應該有碰到驗證碼這個難題,一般我們可以和開發(fā)溝通請求他們的幫助:去掉驗證碼或者設置一個萬能驗證碼,而如果開發(fā)不幫忙我們該如何去解決呢?本篇文章以
Java 語言為例教你怎么破解驗證碼。
現(xiàn)在大多數(shù)網(wǎng)址會采用滑塊驗證碼的方式,下面以騰訊的滑塊驗證碼為例,先來看看破解的效果:

要破解滑塊驗證碼,我們一般采取的思路如下:
獲取滑塊到背景缺口圖的距離
通過 selenium 的 Actions 類完成滑動
關鍵點就在于如何獲取滑塊到背景缺口圖的距離,現(xiàn)在主流的方案是通過 OpenCV(Open Source Computer Vision
Library)開源計算機視覺庫來處理,openCV 有非常多圖像處理方法,其是通過 C/C++ 開發(fā)的,但是對外有提供 Java,Python
的接口,所以不管是 Python 還是 Java 我們都可以使用 openCV 進行圖像處理。
環(huán)境準備
Step1:下載 OpenCV
進入到官網(wǎng)https://opencv.org/releases/下載對應系統(tǒng)的 openCV 軟件包,之后解壓
Step2:配置環(huán)境變量
進入到 opencv -> build ->x64 ->vc15 ->bin 目錄,將路徑復制追加到 Path 環(huán)境變量中

Step3:Intellij 工程中添加 jar 包
Intellij 中選擇 File -> Project Structure -> Modules -> Dependencies
點擊 add -> JARS or directories... 選擇 D:\software\opencv\build\java\opencv-450.jar 文件

驗證碼破解實現(xiàn)
首先我們通過 selenium 進入到驗證碼頁面(以 QQ 空間為例)
//設置chromeDriver識別路徑
System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver.exe");
driver = new ChromeDriver();
driver.get("https://qzone.qq.com/");
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
//切換登錄頁面所在iframe
WebElement loginFrame = driver.findElement(By.id("login_frame"));
driver.switchTo().frame(loginFrame);
driver.findElement(By.id("switcher_plogin")).click();
driver.findElement(By.id("u")).sendKeys("362715381");
driver.findElement(By.id("p")).sendKeys("1234r2we");
driver.findElement(By.id("login_button")).click();
進入到驗證碼界面后,我們仔細觀察會發(fā)現(xiàn):滑塊和背景是兩張分開的圖片,src 屬性中保存的即為圖片 URL 地址,所以我們可以通過 URL 將兩者下載到本地

//切換到驗證碼所在的iframe
WebElement tcaptchaFrame = driver.findElement(By.id("tcaptcha_iframe"));
driver.switchTo().frame(tcaptchaFrame);
//定位滑塊圖片
WebElement slideBlock = driver.findElement(By.id("slideBlock"));
//定位驗證碼背景圖
WebElement slideBg = driver.findElement(By.id("slideBg"));
//獲取圖片Url鏈接
String slideBlockUrl = slideBlock.getAttribute("src");
String slideBgUrl = slideBg.getAttribute("src");
//下載對應圖片
System.out.println("圖片下載開始...");
downloadImg(slideBlockUrl, "slideBlock.png");
downloadImg(slideBgUrl, "slideBg.png");
關鍵點在于獲取滑塊到滑動背景缺口圖的橫向距離,這里通過 OpenCV 的模板匹配技術 matchTemplate
然后再通過 selenium 的 Actions 類完成滑動,在滑動的時候需要注意不能直接從開始點滑到終止點(有些網(wǎng)站會判定腳本操作),其中 getMoveTrack 用于獲取滑動軌跡,控制每階段的滑動速度
//獲取滑塊到滑動背景缺口圖的橫向距離
double slideDistance = getSlideDistance(System.getProperty("user.dir")+"\\slideBlock.png", System.getProperty("user.dir")+"\\slideBg.png");
Actions actions = new Actions(driver);
WebElement dragElement = driver.findElement(By.id("tcaptcha_drag_button"));
//獲取style屬性值,其中設置了滑塊初始偏離值? style=left: 23px;
//需要注意的是網(wǎng)頁前端圖片和本地圖片比例是不同的,需要進行換算
slideDistance = slideDistance * 280 / 680 - 23;
actions.clickAndHold(dragElement).perform();
//根據(jù)滑動距離生成滑動軌跡,約定規(guī)則:開始慢->中間快->最后慢
List<Integer> moveTrack = getMoveTrack(slideDistance);
for (Integer index : moveTrack) {
? ? //Thread.sleep(20);
? ? actions.moveByOffset(index, 0).perform();
}
actions.release().perform();
getSlideDistance 方法實現(xiàn)
首先對滑塊進行處理
1、灰度化
2、去除圖片黑邊
3、inRange 二值化轉黑白圖
效果如下:
代碼實現(xiàn):

代碼實現(xiàn)
// 加載OpenCV本地庫
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//對滑塊進行處理
Mat slideBlockMat = Imgcodecs.imread(slideBlockPicPath);
//1、灰度化圖片
Imgproc.cvtColor(slideBlockMat, slideBlockMat, Imgproc.COLOR_BGR2GRAY);
//2、去除周圍黑邊
for (int row = 0; row < slideBlockMat.height(); row++) {
? ? for (int col = 0; col < slideBlockMat.width(); col++) {
? ? ? ? if (slideBlockMat.get(row, col)[0] == 0) {
? ? ? ? ? ? slideBlockMat.put(row, col, 96);
? ? ? ? }
? ? }
}
//3、inRange二值化轉黑白圖
Core.inRange(slideBlockMat, Scalar.all(96), Scalar.all(96), slideBlockMat);
對滑動背景圖進行處理
1、灰度化
2、二值化轉黑白圖

代碼如下:
//對滑動背景圖進行處理
Mat slideBgMat = Imgcodecs.imread(slideBgPicPath);
//1、灰度化圖片
Imgproc.cvtColor(slideBgMat, slideBgMat, Imgproc.COLOR_BGR2GRAY);
//2、二值化
Imgproc.threshold(slideBgMat, slideBgMat, 127, 255, Imgproc.THRESH_BINARY);
Mat g_result = new Mat();
/*
* matchTemplate:在模板和輸入圖像之間尋找匹配,獲得匹配結果圖像
* result:保存匹配的結果矩陣
* TM_CCOEFF_NORMED標準相關匹配算法
*/
Imgproc.matchTemplate(slideBgMat, slideBlockMat, g_result, Imgproc.TM_CCOEFF_NORMED);
/* minMaxLoc:在給定的結果矩陣中尋找最大和最小值,并給出它們的位置
* maxLoc最大值
*/
Point matchLocation = Core.minMaxLoc(g_result).maxLoc;
//返回匹配點的橫向距離
return matchLocation.x;
需要注意的是運行時需要指定 library 路徑,不然會報如下錯誤
java.lang.UnsatisfiedLinkError: no opencv_java450 in java.library.path
選擇 Edit Configuration -> VM options 中添加:
-Djava.library.path=D:\software\opencv\build\java\x64
