京东自动登录功能记录
这里并没有完成自动登录,只是作为记录,记录过程中用到的技术,以及问题吧。
最初的想法就是刷京豆,但是其实这条路的套路我并不是十分清楚,折腾了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先去官网下载
<dependency>
<groupId>opencv</groupId>
<artifactId>opencv</artifactId>
<version>4.5.3</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/opencv-453.jar</systemPath>
</dependency>
就是这个包放到项目引入,x64和x86是系统位数,文件夹下有个c++写的dll,里面封装了各种算法,让java调用,因为我的机器是64位的所以用x64
这个dll怎么用的,不配置的话调用是报错的,如何配置呢?加jvm启动参数-Djava.library.path=路径
,junit测试的时候加入
springboot启动类也需要加入
拼图验证码破解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有效期很长,自动登录实现还是挺麻烦的,没有太大的必要,还有就是自动签到领豆,网上有很多脚本可以直接用。