滑动验证码处理 java实现

关于滑动验证码的解决方法,网上已经有很多详细的描述了,但是绝大多数都是用的python,这边就扩充一下java,但是验证准确率不是100%,只是强调一下在解决中可能会出现的问题。

注意点

元素截取
  • 元素截图时,尽可能不要通过截取整个屏幕,然后截图子图的方式来截取验证码,这样会因为显示分辨率导致位置偏差存在,无法获取验证码的准确位置,可参考 https://blog.csdn.net/weixin_44578172/article/details/111387534
  • java中直接调用元素的getScreenshotAs即可,但需要选择合理的标签,否侧在执行js代码后会出现图片位移少量的问题。我这边就是选择的最外侧的div标签来进行保存的,而且选择的是先保存无缺口的验证码,然后通过js代码恢复后,再保存具有缺口的验证码
缺口判断
  • 扫描两张图片的起始位置会对缺口的判断有影响,可通过坐标尺或微信截图的方式,确定出方块的初始位置在哪里,即下面代码中getGap方法中的pos变量
  • 验证码缺口判断时,需注意RGB判断阈值,有些验证码中会有些虚假的阴影,并不是我们所要确定的验证码位置,因此,对于阈值的选择也十分重要,这会提高验证精度,即下面代码中equalPixel方法中的threshold
滑块移动
  • 滑块在移动的过程中不要一直加速,因为实际人在操作中,最后一点会稍微减点速的,在计算滑块运动轨迹的时候,最后一个位移距离很有可能会造成滑块超出缺口边界,因此,需要强制对最后一个位移距离进行修改,避免该情况发生,详情见trace方法,滑块的移动尽可能真实的模仿人为操作,来提高准确率
  • 滑块在移动时会有卡顿感,这是因为selenium中默认中的moveByOffset是有200ms的等待时间的,因此,我们可以根据源码重写该方法,使其等待时间为0ms,见moveWithouWait方法
  • 滑块的边界与验证码的边界具有一定的距离,需要将这个距离给考虑进来,可根据一定的测试或测量来选择合适的值,即main方法中的left-=7
  • 移动滑块的时候,一定要注意最后给释放掉

下面代码在实际中会出现验证码回拉的情况,但是这也是提高准确率的一种方式,但是目前来看,这种方法在理论上会出现无解的情况,即一直反复的拉来拉去。但在测试时,还并未出现该情况,但后续需要考虑优化。

/**
 * Description: 实现滑动验证码的验证
 * @date:2021/03/30 09:23
 * @author: lyf
 */
public class SildeCode {

    private WebDriver driver;

    private Actions actions;

    private WebElement element;

    private JavascriptExecutor js;

    private BufferedImage imgBefore;  // 带有缺口的验证码

    private BufferedImage imgAfter;  // 不带有缺口的验证码


    /**
     * 初始化操作
     */
    public void init() {
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("http://www.geetest.com/Register");
        js = (JavascriptExecutor) driver;
        js.executeScript("window.scrollTo(1,100)");
        actions = new Actions(driver);
    }


    /**
     * Selenium方法等待元素出现
     * @param driver 驱动
     * @param by     元素定位方式
     * @return 元素控件
     */
    public static WebElement WaitMostSeconds(WebDriver driver, By by) {
        try {
            WebDriverWait AppiumDriverWait = new WebDriverWait(driver, 5);
            return (WebElement) AppiumDriverWait.until(ExpectedConditions
                    .presenceOfElementLocated(by));
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new NoSuchElementException("元素控件未出现");
    }


    /**
     * 保存截图的方法
     * @param screen 元素截图
     * @param name   截图保存名字
     */
    public 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");
        }
    }

    /**
     * 获取无缺口的验证码和带有缺口的验证码
     */
    public void saveCode() {
        // 获取无缺口的截图
        js.executeScript("document.querySelectorAll('canvas')[2].style=''");
        element = WaitMostSeconds(driver, By.cssSelector("div.geetest_window"));
        File screen = element.getScreenshotAs(OutputType.FILE); //执行屏幕截取
        savePng(screen, "无缺口");

        // 获取有缺口的截图
        js.executeScript("document.querySelectorAll('canvas')[2].classList=[]");
        element = WaitMostSeconds(driver, By.cssSelector("div.geetest_window"));
        screen = element.getScreenshotAs(OutputType.FILE); //执行屏幕截取
        savePng(screen, "有缺口");
    }

    /**
     * 比较两张截图上的当前像素点的RGB值是否相同
     * 只要满足一定误差阈值,便可认为这两个像素点是相同的
     *
     * @param x 像素点的x坐标
     * @param y 像素点的y坐标
     * @return true/false
     */
    public boolean equalPixel(int x, int y) {
        int rgbaBefore = imgBefore.getRGB(x, y);
        int rgbaAfter = imgAfter.getRGB(x, y);
        // 转化成RGB集合
        Color colBefore = new Color(rgbaBefore, true);
        Color colAfter = new Color(rgbaAfter, true);
        int threshold = 80;   // 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;
    }


    /**
     * 比较两张截图,找出有缺口的验证码截图中缺口所在位置
     * 由于滑块是x轴方向位移,因此只需要x轴的坐标即可
     *
     * @return 缺口起始点x坐标
     * @throws Exception
     */
    public int getGap() throws Exception {
        imgBefore = ImageIO.read(new File("有缺口.png"));
        imgAfter = ImageIO.read(new File("无缺口.png"));
        int width = imgBefore.getWidth();
        int height = imgBefore.getHeight();
        int pos = 60;  // 小方块的固定起始位置
        // 横向扫描
        for (int i = pos; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (!equalPixel(i, j)) {
                    pos = i;
                    return pos;
                }
            }
        }
        throw new Exception("未找到滑块缺口");
    }


    /**
     * 计算滑块到达目标点的运行轨迹
     * 先加速,后减速
     * @param distance 目标距离
     * @return 运动轨迹
     */
    public List<Integer> trace(int distance) {
        java.util.List<Integer> moveTrace = new ArrayList<>();
        int current = 0;  // 当前位移
        int threshold = distance * 3 / 5; // 减速阈值
        double t = 0.2;   // 计算间隔
        double v = 0.0;     // 初速度
        double a;     // 加速度
        while (current < distance) {
            if (current < threshold) {
                a = 2;
            } else {
                a = -4;
            }
            // 位移计算公式
            double tmp = v;
            // 移动速度,会出现负值的情况,然后往反方向拉取
            v = tmp + a * t;
            int move = (int) (tmp * t + 0.5 * a * t * t);
            current += move;
            moveTrace.add(move);
        }
        // 考虑到最后一次会超出移动距离,将其强制修改回来,不允许超出
        int length = moveTrace.size();
        moveTrace.set(length - 1, moveTrace.get(length - 1) + (current > distance ? -(current - distance) : 0));
        return moveTrace;
    }

    /**
     * 消除selenium中移动操作的卡顿感
     * 这种卡顿感是因为selenium中自带的moveByOffset是默认有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(0), PointerInput.Origin.pointer(), x, y)).perform();
    }


    /**
     * 移动滑块,实现验证
     * @param moveTrace 滑块的运动轨迹
     * @throws Exception
     */
    public void move(List<Integer> moveTrace) throws Exception {
        // 获取滑块对象
        element = WaitMostSeconds(driver, By.cssSelector("div.geetest_slider_button"));
        // 按下滑块
        actions.clickAndHold(element).perform();
        Iterator it = moveTrace.iterator();
        while (it.hasNext()) {
            // 位移一次
            int dis = (int) it.next();
            moveWithoutWait(dis, 0);
        }
        // 模拟人的操作,超过区域
        moveWithoutWait(5, 0);
        moveWithoutWait(-3, 0);
        moveWithoutWait(-2, 0);
        // 释放滑块
        actions.release().perform();
        Thread.sleep(500);
    }


    /**
     * 调出验证码时的一些准备工作
     * @throws Exception
     */
    public void prepare() throws Exception {
        // 调出验证码
        element = WaitMostSeconds(driver, By.cssSelector("div.phone > input"));
        element.clear();
        element.sendKeys("12345678910");
        element = WaitMostSeconds(driver, By.cssSelector("div.sendCode"));
        element.click();

        // 等待验证码出现
        element = WaitMostSeconds(driver, By.cssSelector("a.geetest_close"));
        Thread.sleep(500);

        // 保存验证码
        saveCode();
    }


    public static void main(String[] args) throws Exception {
        SildeCode sc = new SildeCode();
        sc.init();
        sc.prepare();
        int left = sc.getGap();
        // 验证码的边界差值
        left -= 7;
        List<Integer> moveTrace = sc.trace(left);
        sc.move(moveTrace);
    }

}

在这里插入图片描述

好好学习,天天向上

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值