京东自动登录功能记录

这里并没有完成自动登录,只是作为记录,记录过程中用到的技术,以及问题吧。

最初的想法就是刷京豆,但是其实这条路的套路我并不是十分清楚,折腾了2天的登录,总结一下技术

首先就是模拟浏览器自动登录用selenium这个工具,这个工具支持各种语言调用nodejs、java、c# 等,这里是用java做的。

用这个框架配合google浏览器,模拟点击事件形式自动填充用户名和密码进行登录,但是想要操作浏览器需要
ChromeDriver, 注意驱动的版本要和浏览器的版本配套,将驱动加入到path变量中就可以直接使用了,如果单独运行驱动,可以进行远程调用
的方式进行使用。

// 1.创建webdriver驱动
// 远程调用
WebDriver driver1 = new RemoteWebDriver(new URL("http://localhost:9515"),DesiredCapabilities.chrome());
// path调用
WebDriver driver2 = new ChromeDriver();

例子

    /**
     * 注意:webdriver需要配置到path中
     */
    @SneakyThrows
    public void login() {
        String loginUrl = "https://passport.jd.com/uc/login";
        String username = "";
        String password = "";

        WebDriver driver = new RemoteWebDriver(new URL("http://localhost:9515"),
                DesiredCapabilities.chrome());
        driver.get(loginUrl);
        driver.findElement(By.className("login-tab-r")).click();
        driver.findElement(By.id("loginname")).sendKeys(username);
        driver.findElement(By.id("nloginpwd")).sendKeys(password);
        driver.findElement(By.id("loginsubmit")).click();
        sliderVerification(driver);
        this.cookies = new ArrayList<>(driver.manage().getCookies());
    }

在做登录的时候还有两个难点

  • 拼图滑动验证码
  • 短信验证码

拼图是每次都有的,但是短信的不一定,机器第一次登录可能会验证,没有具体研究,拼图的话找了网上的一些算法例子是用的opencv库做的。

京东会验证滑动轨迹,判定是机器的话是不过的,用的这个算法的通过率也非常低,短信验证码的就更不好弄了,其实登录获取的cookie时间很长,没有必要破解这玩意,直接登录复制cookie就行了,这里仅仅是为了做个记录。

opencv配置

opencv首先导入库pom.xml, 这是本地导入的方式,opencv先去官网下载

Releases - OpenCV

        <dependency>
            <groupId>opencv</groupId>
            <artifactId>opencv</artifactId>
            <version>4.5.3</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/lib/opencv-453.jar</systemPath>
        </dependency>

image.png

就是这个包放到项目引入,x64和x86是系统位数,文件夹下有个c++写的dll,里面封装了各种算法,让java调用,因为我的机器是64位的所以用x64

image.png

这个dll怎么用的,不配置的话调用是报错的,如何配置呢?加jvm启动参数-Djava.library.path=路径 ,junit测试的时候加入

image.png

springboot启动类也需要加入

image.png

拼图验证码破解demo

这里用了hutool的库

hutool: 🍬小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 (gitee.com)

登录的类

import com.easyxu.wool.common.util.SlidingPuzzleUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@Component
public class JDLoginApi {

    @Getter
    private List<Cookie> cookies = null;

    /**
     * 模拟人工移动
     *
     * @param driver
     * @param element 页面滑块
     * @param distance 需要移动距离
     * @throws InterruptedException
     */
    private 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);
        System.out.println(track);
        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(100) + randomTime);
            }
            Thread.sleep(200);
            actions.release(element).perform();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据距离获取滑动轨迹
     *
     * @param distance 需要移动的距离
     * @return
     */
    private List<Integer> getMoveTrack(int distance) {
        List<Integer> track = new ArrayList<>();// 移动轨迹
        Random random = new Random();
        int current = 0;// 已经移动的距离
        int mid =  distance * 4 / 6;// 减速阈值
        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.add(distance - current);
                break;
            }
            current += move;
        }
        return track;
    }

    /**
     * 滑块验证码过验证
     * @param driver
     * @throws InterruptedException
     */
    private void sliderVerification(WebDriver driver) throws InterruptedException {
        WebElement bigImgElement = driver.findElement(By.className("JDJRV-bigimg"));
        WebElement smallImgElement = driver.findElement(By.className("JDJRV-smallimg"));
        String bBase64 = bigImgElement.findElement(By.tagName("img")).getAttribute("src");
        String sBase64 = smallImgElement.findElement(By.tagName("img")).getAttribute("src");

        String folder = System.getProperty("java.io.tmpdir");

        String bTempPath = folder + "bimg.png";
        String sTempPath = folder + "simg.png";

        SlidingPuzzleUtil.saveImage(bBase64, bTempPath);
        SlidingPuzzleUtil.saveImage(sBase64, sTempPath);

        double match = SlidingPuzzleUtil.match(new File(bTempPath), new File(sTempPath));

        move(driver, smallImgElement, (int) Math.round(match) + 12);
    }



    /**
     * 注意:webdriver需要配置到path中
     */
    @SneakyThrows
    public void login() {
        String loginUrl = "https://passport.jd.com/uc/login";
        String username = "";
        String password = "";

        WebDriver driver = new RemoteWebDriver(new URL("http://localhost:9515"),
                DesiredCapabilities.chrome());
        driver.get(loginUrl);
        driver.findElement(By.className("login-tab-r")).click();
        driver.findElement(By.id("loginname")).sendKeys(username);
        driver.findElement(By.id("nloginpwd")).sendKeys(password);
        driver.findElement(By.id("loginsubmit")).click();
        sliderVerification(driver);
        this.cookies = new ArrayList<>(driver.manage().getCookies());
    }
}

SlidingPuzzleUtil.java工具类

比网上的例子唯一的优势就是我给它简化了下,阅读稍微方便了一点。

import cn.hutool.core.img.ImgUtil;
import lombok.SneakyThrows;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * 滑动验证距离匹配工具类
 */
public class SlidingPuzzleUtil {

    /**
     * 将base64图片保存到文件
     *
     * @param imgBase64
     * @param path
     */
    public static void saveImage(String imgBase64, String path) {
        if (imgBase64.startsWith("data:")) {
            imgBase64 = imgBase64.substring(imgBase64.indexOf(",") + 1);
        }
        ImgUtil.write(ImgUtil.toImage(imgBase64), new File(path));
    }

    /**
     * 图片处理,处理灰度、阈值化
     *
     * @param file
     * @return
     */
    private static Mat dealWith(File file) {

        Mat mat = Imgcodecs.imread(file.getPath());

        // 转灰度图像
        Mat newMat = new Mat();

        Imgproc.cvtColor(mat, newMat, Imgproc.COLOR_BGR2GRAY);

        Imgcodecs.imwrite(file.getPath(), newMat);

        // 自适应阈值化
        Mat s_nMat = new Mat();
        Imgproc.adaptiveThreshold(newMat, s_nMat, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 7, -4);
        Imgcodecs.imwrite(file.getPath(), s_nMat);

        mat = Imgcodecs.imread(file.getPath());

        return mat;
    }


    /**
     * 透明区域变白
     *
     * @param image
     * @throws IOException
     */
    private static void setWhite(BufferedImage image) {
        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());
                    }
                }
            }
        }
    }

    /**
     * 匹配距离
     *
     * @param bFile 背景图片file
     * @param sFile 缺口图片file
     * @return
     */
    @SneakyThrows
    public static double match(File bFile, File sFile) {

        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        BufferedImage sImage = ImgUtil.read(sFile);

        setWhite(sImage);

        ImageIO.write(sImage, "png", sFile);

        Mat bMat = dealWith(bFile);
        Mat sMat = dealWith(sFile);


        int result_rows = bMat.rows() - sMat.rows() + 1;
        int result_cols = bMat.cols() - sMat.cols() + 1;
        Mat g_result = new Mat(result_rows, result_cols, CvType.CV_32FC1);
        // 相关系数匹配法
        Imgproc.matchTemplate(bMat, sMat, g_result, Imgproc.TM_CCOEFF);
        Core.normalize(g_result, g_result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
        // 此处使用maxLoc还是minLoc取决于使用的匹配算法
        Point matchLocation = Core.minMaxLoc(g_result).maxLoc;
        Imgproc.rectangle(bMat, matchLocation
                , new Point(matchLocation.x + sMat.cols(), matchLocation.y + sMat.rows()), new Scalar(0, 255, 0, 0));
//        Imgcodecs.imwrite("D:/dx.png", bMat);
        return ((matchLocation.x + sMat.cols() - sImage.getWidth()) * 3 / 4 - 8);
    }
}

总结

京东的cookie有效期很长,自动登录实现还是挺麻烦的,没有太大的必要,还有就是自动签到领豆,网上有很多脚本可以直接用。

posted @ 2021-09-25 20:33  Bug的梦魇  阅读(939)  评论(0编辑  收藏  举报