java+selenium實現(xiàn)滑塊驗證
背景
現(xiàn)在越來越多的網(wǎng)站都使用采用滑塊驗證來作為驗證機制,用于判斷用戶是否為人類而不是機器人。它需要用戶將滑塊拖動到指定位置來完成驗證。
網(wǎng)上上有很多python和node過滑塊的案例,但是java的特別少。
本篇文章一起來看下java怎么實現(xiàn)滑塊驗證。
思路
因為隱私問題,假設(shè)有一個網(wǎng)站 www.example.com, 打開后需要點擊,那么我們完整的登錄流程為:
- 打開網(wǎng)站www.example.com
- 點擊頁面右上角login
- 在彈出對話框輸入用戶名
- 點擊send code 發(fā)送郵箱驗證碼
- 彈出滑塊,拖動滑動滑塊到指定位置,松開鼠標(biāo)
- 查看郵箱驗證碼,并輸入
- 點擊登錄
- 獲取登錄后cookie中返回的token,判斷是否登錄成功
代碼
chromedriver下載地址
下載與自己瀏覽器對應(yīng)版本的chromedriverregistry.npmmirror.com/binary.html
最新版谷歌瀏覽器chromedriver下載地址googlechromelabs.github.io/chrome-for-testing/
maven增加如下依賴
<!-- selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.8.0</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.15.1</version> </dependency> <!-- 獲取郵箱驗證碼 --> <dependency> <groupId>javax.mail</groupId> <artifactId>javax.mail-api</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.6.2</version> </dependency> <!-- 工具類 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency>
滑塊驗證
- 先保存無缺口的圖片到本地,然后保存有缺的圖片到本地。
- 將兩張圖片轉(zhuǎn)換成RGB集合,比較兩張圖片像素點的RGB值是否相同。
- 只要RGB的集合大于一定的誤差閾值,則認(rèn)為該位置為缺口位置。
核心代碼如下:
/** * 比較兩張截圖,找出有缺口的驗證碼截圖中缺口所在位置 * 由于滑塊是x軸方向位移,因此只需要x軸的坐標(biāo)即可 * * @return 缺口起始點x坐標(biāo) * @throws Exception */ public int comparePicture() throws Exception { notchPicture = ImageIO.read(new File("有缺口.png")); fullPicture = ImageIO.read(new File("無缺口.png")); int width = notchPicture.getWidth(); int height = notchPicture.getHeight(); int pos = 70; // 小方塊的固定起始位置 // 橫向掃描 for (int i = pos; i < width; i++) { for (int j = 0; j < height - 10; j++) { if (!equalPixel(i, j)) { pos = i; return pos; } } } throw new Exception("未找到滑塊缺口"); }
/** * 比較兩張截圖上的當(dāng)前像素點的RGB值是否相同 * 只要滿足一定誤差閾值,便可認(rèn)為這兩個像素點是相同的 * * @param x 像素點的x坐標(biāo) * @param y 像素點的y坐標(biāo) * @return true/false */ public boolean equalPixel(int x, int y) { int rgbaBefore = notchPicture.getRGB(x, y); int rgbaAfter = fullPicture.getRGB(x, y); // 轉(zhuǎn)化成RGB集合 Color colBefore = new Color(rgbaBefore, true); Color colAfter = new Color(rgbaAfter, true); int threshold = 220; // RGB差值閾值 if (Math.abs(colBefore.getRed() - colAfter.getRed()) < threshold && Math.abs(colBefore.getGreen() - colAfter.getGreen()) < threshold && Math.abs(colBefore.getBlue() - colAfter.getBlue()) < threshold) { return true; } return false; }
移動滑塊代碼:
/** * 移動滑塊,實現(xiàn)驗證 * * @param moveTrace 滑塊的運動軌跡 * @throws Exception */ public void move(List<Integer> moveTrace) throws Exception { // 獲取滑塊對象 element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("div.dv_handler.dv_handler_bg")); // 按下滑塊 actions.clickAndHold(element).perform(); Iterator it = moveTrace.iterator(); while (it.hasNext()) { // 位移一次 int dis = (int) it.next(); moveWithoutWait(dis, 0); } // 模擬人的操作,超過區(qū)域 moveWithoutWait(5, 0); moveWithoutWait(-3, 0); moveWithoutWait(-2, 0); // 釋放滑塊 actions.release().perform(); Thread.sleep(500); } /** * 消除selenium中移動操作的卡頓感 * 這種卡頓感是因為selenium中自帶的moveByOffset是默認(rèn)有200ms的延時的 * 可參考:https://blog.csdn.net/fx9590/article/details/113096513 * * @param x x軸方向位移距離 * @param y y軸方向位移距離 */ public void moveWithoutWait(int x, int y) { PointerInput defaultMouse = new PointerInput(MOUSE, "default mouse"); actions.tick(defaultMouse.createPointerMove(Duration.ofMillis(5), PointerInput.Origin.pointer(), x, y)).perform(); }
為了防止每天頻繁登錄,可能會被封號。 我們還要實現(xiàn)每天只需要登錄一次,其余時間都是免登錄
// 每天重新登陸一次 File cookieFile = new File("example.cookie.txt" + DateUtil.today()); if (!cookieFile.exists()) { // 文件不存在則認(rèn)為是當(dāng)天首次登錄,清空緩存文件 FileUtil.del(tempDirect); }
/** * 保存cookie * @throws IOException */ private void saveCookie() throws IOException { File cookieFile = new File("example.cookie.txt" + DateUtil.today()); cookieFile.delete(); cookieFile.createNewFile(); FileWriter fileWriter = new FileWriter(cookieFile); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); for (Cookie cookie : driver.manage().getCookies()) { bufferedWriter.write((cookie.getName() + ";" + cookie.getValue() + ";" + cookie.getDomain() + ";" + cookie.getPath() + ";" + cookie.getExpiry() + ";" + cookie.isSecure())); bufferedWriter.newLine(); } bufferedWriter.flush(); bufferedWriter.close(); fileWriter.close(); }
/** * 讀取cookie加載到瀏覽器 * @throws IOException */ private void addCookie() throws IOException { File cookieFile = new File("example.cookie.txt" + DateUtil.today()); if (cookieFile.exists()) { FileReader fileReader = new FileReader(cookieFile); BufferedReader bufferedReader = new BufferedReader(fileReader); String line; while ((line = bufferedReader.readLine()) != null) { StringTokenizer stringTokenizer = new StringTokenizer(line, ";"); while (stringTokenizer.hasMoreTokens()) { String name = stringTokenizer.nextToken(); String value = stringTokenizer.nextToken(); String domain = stringTokenizer.nextToken(); String path = stringTokenizer.nextToken(); Date expiry = null; String dt; if (!(dt = stringTokenizer.nextToken()).equals("null")) { expiry = new Date(dt); } boolean isSecure = new Boolean(stringTokenizer.nextToken()).booleanValue(); Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure); driver.manage().addCookie(cookie); } } } }
完整代碼
package com.fandf.selenium; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.fandf.email.EmailService; import com.fandf.utils.SeleniumUtil; import lombok.extern.slf4j.Slf4j; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.PointerInput; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.time.Duration; import java.util.List; import java.util.*; import static org.openqa.selenium.interactions.PointerInput.Kind.MOUSE; /** * @author fandongfeng * @date 2023-12-10 13:48 **/ @Slf4j public class SliderAutomatic implements Closeable { private WebDriver driver; private Actions actions; private WebElement element; private JavascriptExecutor js; // 帶有缺口的驗證碼 private BufferedImage notchPicture; // 不帶有缺口的驗證碼 private BufferedImage fullPicture; // chromedriver地址 private final static String chromedriver = "C:\Users\Administrator\Desktop\chrome\chromedriver.exe"; // 瀏覽器緩存地址 private final static String tempDirect = "C:\Users\Administrator\Desktop\chrome\temp"; @Override public void close() { if (driver != null) { driver.quit(); } } public String login() throws Exception { log.info("開始登錄"); System.setProperty("webdriver.chrome.driver", chromedriver); // 每天重新登陸一次 File cookieFile = new File("example.cookie.txt" + DateUtil.today()); if (!cookieFile.exists()) { // 文件不存在則認(rèn)為是當(dāng)天首次登錄,清空緩存文件 FileUtil.del(tempDirect); } log.info("清除緩存成功"); ChromeOptions options = new ChromeOptions(); options.addArguments("--remote-allow-origins=*"); String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"; options.addArguments("--user-agent=" + userAgent); options.addArguments("--disable-gpu"); options.addArguments("--disable-dev-shm-usage"); options.addArguments("--no-sandbox"); options.addArguments("--single-process"); options.addArguments("--disable-setuid-sandbox"); // 啟用自動化擴展 options.setExperimentalOption("excludeSwitches", Arrays.asList("enable-automation")); options.addArguments("--disable-blink-features=AutomationControlled"); // 禁用瀏覽器的安全性 options.addArguments("--disable-web-security"); options.addArguments("--allow-running-insecure-content"); //禁用瀏覽器的同源策略 options.addArguments("--disable-features=IsolateOrigins,site-per-process"); options.addArguments("--user-data-dir=" + tempDirect); // 設(shè)置后臺靜默模式啟動瀏覽器 // options.addArguments("--headless=new"); log.info("設(shè)置請求頭完成"); driver = new ChromeDriver(options); driver.manage().window().maximize(); js = (JavascriptExecutor) driver; js.executeScript("window.scrollTo(1,100)"); actions = new Actions(driver); // 先訪問在在加載cookie 否則報錯 invalid cookie domain driver.get("www.example.com"); // 讀取cookie加載到瀏覽器 addCookie(); // 刷新頁面 driver.navigate().refresh(); if (!isLogin()) { log.info("開始登錄..."); // 登錄 loginExample(); // 登錄成功后先刷新 driver.navigate().refresh(); } else { log.info("免登錄成功..."); } String token = null; Set<Cookie> cookies = driver.manage().getCookies(); for (Cookie cookie : cookies) { log.info("cookie= {}", JSONUtil.toJsonStr(cookie)); if (cookie.getName().equals("Example-Token")) { // 登錄成功后會返回token log.info("Example-Token 的值為:" + cookie.getValue()); token = cookie.getValue(); } } // token存在則證明登錄成功 if (StrUtil.isNotBlank(token)) { saveCookie(); } return token; } /** * 讀取cookie加載到瀏覽器 * * @throws IOException */ private void addCookie() throws IOException { File cookieFile = new File("example.cookie.txt" + DateUtil.today()); if (cookieFile.exists()) { FileReader fileReader = new FileReader(cookieFile); BufferedReader bufferedReader = new BufferedReader(fileReader); String line; while ((line = bufferedReader.readLine()) != null) { StringTokenizer stringTokenizer = new StringTokenizer(line, ";"); while (stringTokenizer.hasMoreTokens()) { String name = stringTokenizer.nextToken(); String value = stringTokenizer.nextToken(); String domain = stringTokenizer.nextToken(); String path = stringTokenizer.nextToken(); Date expiry = null; String dt; if (!(dt = stringTokenizer.nextToken()).equals("null")) { expiry = new Date(dt); } boolean isSecure = new Boolean(stringTokenizer.nextToken()).booleanValue(); Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure); driver.manage().addCookie(cookie); } } } } /** * 保存cookie * * @throws IOException */ private void saveCookie() throws IOException { File cookieFile = new File("example.cookie.txt" + DateUtil.today()); cookieFile.delete(); cookieFile.createNewFile(); FileWriter fileWriter = new FileWriter(cookieFile); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); for (Cookie cookie : driver.manage().getCookies()) { bufferedWriter.write((cookie.getName() + ";" + cookie.getValue() + ";" + cookie.getDomain() + ";" + cookie.getPath() + ";" + cookie.getExpiry() + ";" + cookie.isSecure())); bufferedWriter.newLine(); } bufferedWriter.flush(); bufferedWriter.close(); fileWriter.close(); } private void loginExample() throws Exception { js.executeScript("window.scrollTo(1,100)"); // 調(diào)出驗證碼 WebElement element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("div.login")); element.click(); element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("input[name='username']")); element.clear(); element.sendKeys("123456789@163.com"); log.info("開始登錄郵箱123456789@163.com"); element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector(".SendCode.correct")); element.click(); log.info("點擊登錄發(fā)送郵件完成"); // 等待驗證碼出現(xiàn) SeleniumUtil.waitMostSeconds(driver, By.cssSelector(".drag-verify-container.veriftyItem")); // 等待5秒,網(wǎng)絡(luò)問題,等待圖片顯示出來 log.info("等待5秒,網(wǎng)絡(luò)問題,等待圖片顯示出來"); Thread.sleep(5000); // 保存驗證碼 saveCode(); int position = comparePicture(); log.info("滑塊位置:{}", position); move(Collections.singletonList(position)); log.info("移動滑塊位置成功,開始等待驗證碼"); // 等待10秒,收到郵件 Thread.sleep(20000); // 輸入郵箱驗證碼 String emailLoginCode = EmailService.getLoginCode(); element = SeleniumUtil.waitMostSeconds(driver, By.xpath("http://*[@id="app"]/div[1]/div/form/div[2]/div[2]/input")); element.clear(); element.sendKeys(emailLoginCode); log.info("郵箱驗證碼為:{}", emailLoginCode); // 點擊登錄 element = SeleniumUtil.waitMostSeconds(driver, By.xpath("http://*[@id="app"]/div[1]/div/form/div[3]/div/button")); element.click(); log.info("登陸成功了"); } /** * 移動滑塊,實現(xiàn)驗證 * * @param moveTrace 滑塊的運動軌跡 * @throws Exception */ private void move(List<Integer> moveTrace) throws Exception { // 獲取滑塊對象 element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("div.dv_handler.dv_handler_bg")); // 按下滑塊 actions.clickAndHold(element).perform(); Iterator it = moveTrace.iterator(); while (it.hasNext()) { // 位移一次 int dis = (int) it.next(); moveWithoutWait(dis, 0); } // 模擬人的操作,超過區(qū)域 moveWithoutWait(5, 0); moveWithoutWait(-3, 0); moveWithoutWait(-2, 0); // 釋放滑塊 actions.release().perform(); Thread.sleep(500); } /** * 消除selenium中移動操作的卡頓感 * 這種卡頓感是因為selenium中自帶的moveByOffset是默認(rèn)有200ms的延時的 * 可參考:https://blog.csdn.net/fx9590/article/details/113096513 * * @param x x軸方向位移距離 * @param y y軸方向位移距離 */ private void moveWithoutWait(int x, int y) { PointerInput defaultMouse = new PointerInput(MOUSE, "default mouse"); actions.tick(defaultMouse.createPointerMove(Duration.ofMillis(5), PointerInput.Origin.pointer(), x, y)).perform(); } /** * 比較兩張截圖,找出有缺口的驗證碼截圖中缺口所在位置 * 由于滑塊是x軸方向位移,因此只需要x軸的坐標(biāo)即可 * * @return 缺口起始點x坐標(biāo) * @throws Exception */ private int comparePicture() throws Exception { notchPicture = ImageIO.read(new File("有缺口.png")); fullPicture = ImageIO.read(new File("無缺口.png")); int width = notchPicture.getWidth(); int height = notchPicture.getHeight(); int pos = 70; // 小方塊的固定起始位置 // 橫向掃描 for (int i = pos; i < width; i++) { for (int j = 0; j < height - 10; j++) { if (!equalPixel(i, j)) { pos = i; return pos; } } } throw new Exception("未找到滑塊缺口"); } /** * 比較兩張截圖上的當(dāng)前像素點的RGB值是否相同 * 只要滿足一定誤差閾值,便可認(rèn)為這兩個像素點是相同的 * * @param x 像素點的x坐標(biāo) * @param y 像素點的y坐標(biāo) * @return true/false */ private boolean equalPixel(int x, int y) { int rgbaBefore = notchPicture.getRGB(x, y); int rgbaAfter = fullPicture.getRGB(x, y); // 轉(zhuǎn)化成RGB集合 Color colBefore = new Color(rgbaBefore, true); Color colAfter = new Color(rgbaAfter, true); int threshold = 220; // RGB差值閾值 if (Math.abs(colBefore.getRed() - colAfter.getRed()) < threshold && Math.abs(colBefore.getGreen() - colAfter.getGreen()) < threshold && Math.abs(colBefore.getBlue() - colAfter.getBlue()) < threshold) { return true; } return false; } /** * 獲取無缺口的驗證碼和帶有缺口的驗證碼 */ private void saveCode() { // 隱藏缺口 // 隱藏滑塊 js.executeScript("document.querySelectorAll('canvas')[0].style='display'"); js.executeScript("document.querySelectorAll('canvas')[1].hidden='true'"); WebElement element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("canvas.main-canvas")); File screen = element.getScreenshotAs(OutputType.FILE); //執(zhí)行屏幕截取 SeleniumUtil.savePng(screen, "無缺口"); log.info("保存無缺口的截圖完成"); // 獲取有缺口的截圖 // 隱藏滑塊 js.executeScript("document.querySelectorAll('canvas')[1].style='display'"); js.executeScript("document.querySelectorAll('canvas')[1].hidden='true'"); WebElement element = SeleniumUtil.waitMostSeconds(driver, By.cssSelector("canvas.main-canvas")); File screen = element.getScreenshotAs(OutputType.FILE); //執(zhí)行屏幕截取 SeleniumUtil.savePng(screen, "有缺口"); // 展示滑塊 js.executeScript("document.querySelectorAll('canvas')[1].style='display: block;'"); log.info("保存有缺口的截圖完成"); } /** * 通過頁面是否有l(wèi)ogin按鈕來判斷是否登錄 * T 登錄了, F 未登錄 */ private boolean isLogin() { return !SeleniumUtil.containElement(driver, By.cssSelector("div.login")); } }
package com.fandf.utils; import org.apache.commons.io.FileUtils; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import java.io.File; import java.io.IOException; import java.time.Duration; /** * @author fandongfeng * @date 2023/12/9 18:29 */ public class SeleniumUtil { /** * 判斷元素是否存在 * * @param driver 驅(qū)動 * @param by 元素定位方式 * @return 元素控件 */ public static boolean containElement(WebDriver driver, By by) { try { WebDriverWait AppiumDriverWait = new WebDriverWait(driver, Duration.ofSeconds(5)); AppiumDriverWait.until(ExpectedConditions .presenceOfElementLocated(by)); return true; } catch (Exception ignore) { } return false; } /** * Selenium方法等待元素出現(xiàn) * * @param driver 驅(qū)動 * @param by 元素定位方式 * @return 元素控件 */ public static WebElement waitMostSeconds(WebDriver driver, By by) { try { WebDriverWait AppiumDriverWait = new WebDriverWait(driver, Duration.ofSeconds(5)); return (WebElement) AppiumDriverWait.until(ExpectedConditions .presenceOfElementLocated(by)); } catch (Exception e) { e.printStackTrace(); } throw new NoSuchElementException("元素控件未出現(xiàn)"); } /** * 保存截圖的方法 * * @param screen 元素截圖 * @param name 截圖保存名字 */ public static void savePng(File screen, String name) { String screenShortName = name + ".png"; try { System.out.println("save screenshot"); FileUtils.copyFile(screen, new File(screenShortName)); } catch (IOException e) { System.out.println("save screenshot fail"); e.printStackTrace(); } finally { System.out.println("save screenshot finish"); } } }
到此這篇關(guān)于java+selenium實現(xiàn)滑塊驗證的文章就介紹到這了,更多相關(guān)java selenium滑塊驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中使用Session共享實現(xiàn)分布式部署的示例代碼
這篇文章主要介紹了SpringBoot中使用Session共享實現(xiàn)分布式部署的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07詳解mybatis-plus的 mapper.xml 路徑配置的坑
這篇文章主要介紹了詳解mybatis-plus的 mapper.xml 路徑配置的坑,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08SpringBoot?集成Resteasy實現(xiàn)RESTFul接口的詳細(xì)過程
這篇文章主要介紹了SpringBoot集成Resteasy實現(xiàn)RESTFul接口,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08圖數(shù)據(jù)庫NebulaGraph的Java 數(shù)據(jù)解析實踐與指導(dǎo)詳解
這篇文章主要介紹了圖數(shù)據(jù)庫NebulaGraph的Java 數(shù)據(jù)解析實踐與指導(dǎo)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Java結(jié)構(gòu)型設(shè)計模式之組合模式Composite Pattern詳解
組合模式,又叫部分整體模式,它創(chuàng)建了對象組的數(shù)據(jù)結(jié)構(gòu)組合模式使得用戶對單個對象和組合對象的訪問具有一致性。本文將通過示例為大家詳細(xì)介紹一下組合模式,需要的可以參考一下2022-11-11