前言
本文詳細介紹了破解頂象滑動驗證碼的所有過程,包括破解思路,實現步驟還有測試結果,相信你看完之后也能輕松破解滑動驗證碼;
另外,其他類似驗證碼的破解步驟請參考:
《騰訊防水牆滑動拼圖驗證碼》
一丶解析驗證碼組成
從上面三張圖來看,頂象滑動拼圖驗證碼是由一個小的拼圖和一個大的背景圖組成,拼圖的形狀各式各樣,背景圖中有兩個陰影缺口:一個跟拼圖一樣的陰影缺口,還有一個形狀不確定大小也不確定的陰影缺口。
二丶分析出破解思路
- 首先根據這個驗證碼的組成,來分析一下我們人要做的事情:
按照正常的手動操作流程來看,我們需要看出背景圖中與拼圖對應的陰影缺口的位置,然后鼠標按住下方滑塊來把拼圖對正到缺口位置來完成驗證。
- 然后根據人要做的事情,來分析一下程序要做的事情:
根據分析得出下面幾個步驟:
1.獲取到兩張圖片(背景圖、拼圖)
2.處理圖片,得到陰影位置並計算滑動距離
3.根據滑動距離模擬滑動
三丶具體操作步驟
1丶查看網頁源碼並提取圖片信息
從上圖可以看到,拼圖的網絡地址存在<img>
標簽內,而背景圖是以canves
畫布的形式展現出來的。
拼圖我們可以通過selenium獲取到img標簽中的網絡地址,再通過請求獲取到圖片信息 。
背景圖可以參考《如何抓取canvas畫布中的圖片》來獲取圖片信息。
2丶處理圖片,計算滑動距離
這里由於拼圖的圖片格式是webp
不好處理,所以我們先將拼圖轉為png
格式。
需要導入jar包webp-imageio-core-0.1.0.jar
String sUrl = driver.findElement(By.className("dx_captcha_basic_sub-slider")).findElement(By.tagName("img")).getAttribute("src"); File f = new File("d://dximg.webp"); FileUtils.copyURLToFile(new URL(sUrl), f); ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next(); WebPReadParam readParam = new WebPReadParam(); readParam.setBypassFiltering(true); reader.setInput(new FileImageInputStream(f)); BufferedImage image = reader.read(0, readParam); ImageIO.write(image, "png", new File("d://dximg.png"));
接下來是圖片處理過程
1.根據拼圖在頁面的高度位置將背景圖裁剪,縮小匹配范圍
// 大圖裁剪 BufferedImage sBI = ImageIO.read(sFile); BufferedImage bgBI = ImageIO.read(bFile); bgBI = bgBI.getSubimage(0, top, bgBI.getWidth(), sBI.getHeight()); ImageIO.write(bgBI, "png", bFile);
2.將拼圖透明部分變為白色
setWhite(sBI); ImageIO.write(sBI, "png", sFile); /** * 透明區域變白 * * @param image * @param param * @throws IOException */ public void setWhite(BufferedImage image) throws IOException { if (image == null) { return; } else { int rgb; for (int i = 0; i < image.getWidth(); i++) { for (int j = 0; j < image.getHeight(); j++) { rgb = image.getRGB(i, j); int A = (rgb & 0xFF000000) >>> 24; if (A < 100) { image.setRGB(i, j, new Color(255, 255, 255).getRGB()); } } } } }
3.將兩張圖分別轉灰度圖像再進行自適應閾值化
Mat s_mat = Imgcodecs.imread(sFile.getPath()); Mat b_mat = Imgcodecs.imread(bFile.getPath()); // 轉灰度圖像 Mat s_newMat = new Mat(); Mat b_newMat = new Mat(); Imgproc.cvtColor(s_mat, s_newMat, Imgproc.COLOR_BGR2GRAY); Imgproc.cvtColor(b_mat, b_newMat, Imgproc.COLOR_BGR2GRAY); Imgcodecs.imwrite(sFile.getPath(), s_newMat); Imgcodecs.imwrite(bFile.getPath(), b_newMat); // 自適應閾值化 Mat s_nMat = new Mat(); Imgproc.adaptiveThreshold(s_newMat, s_nMat, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 7, -4); Imgcodecs.imwrite(sFile.getPath(), s_nMat); Mat b_nMat = new Mat(); Imgproc.adaptiveThreshold(b_newMat, b_nMat, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 7, -4); Imgcodecs.imwrite(bFile.getPath(), b_nMat); b_mat = Imgcodecs.imread(bFile.getPath()); s_mat = Imgcodecs.imread(sFile.getPath());
4.將處理過后的兩張圖進行模糊匹配
int result_rows = b_mat.rows() - s_mat.rows() + 1; int result_cols = b_mat.cols() - s_mat.cols() + 1; Mat g_result = new Mat(result_rows, result_cols, CvType.CV_32FC1); Imgproc.matchTemplate(b_mat, s_mat, g_result, Imgproc.TM_CCOEFF); // 相關系數匹配法 Core.normalize(g_result, g_result, 0, 1, Core.NORM_MINMAX, -1, new Mat()); Point matchLocation = new Point(); MinMaxLocResult mmlr = Core.minMaxLoc(g_result); matchLocation = mmlr.maxLoc; // 此處使用maxLoc還是minLoc取決於使用的匹配算法 Imgproc.rectangle(b_mat, matchLocation, new Point(matchLocation.x + s_mat.cols(), matchLocation.y + s_mat.rows()), new Scalar(0, 255, 0, 0)); Imgcodecs.imwrite("d:/dx.png", b_mat);
5.根據圖片再頁面顯示比例計算出需要滑動的距離
(matchLocation.x + s_mat.cols() - sBI.getWidth()) * 3 / 4 - 8)
3丶根據滑動距離模擬滑動
得到滑動距離直接控制滑塊滑動就可以了。
滑動代碼如下:
/** * 模擬人工移動 * * @param driver * @param element頁面滑塊 * @param distance需要移動距離 * @throws InterruptedException */ public static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException { int randomTime = 0; if (distance > 90) { randomTime = 250; } else if (distance > 80 && distance <= 90) { randomTime = 150; } List<Integer> track = getMoveTrack(distance - 2); int moveY = 1; try { Actions actions = new Actions(driver); actions.clickAndHold(element).perform(); Thread.sleep(200); for (int i = 0; i < track.size(); i++) { actions.moveByOffset(track.get(i), moveY).perform(); Thread.sleep(new Random().nextInt(300) + randomTime); } Thread.sleep(200); actions.release(element).perform(); } catch (Exception e) { e.printStackTrace(); } } /** * 根據距離獲取滑動軌跡 * * @param distance需要移動的距離 * @return */ public static List<Integer> getMoveTrack(int distance) { List<Integer> track = new ArrayList<>();// 移動軌跡 Random random = new Random(); int current = 0;// 已經移動的距離 int mid = (int) distance * 4 / 5;// 減速閾值 int a = 0; int move = 0;// 每次循環移動的距離 while (true) { a = random.nextInt(10); if (current <= mid) { move += a;// 不斷加速 } else { move -= a; } if ((current + move) < distance) { track.add(move); } else { track.