系列文章目录
第一篇: Java实现QQ登录
第二篇: Selenium QQ自动化登录
前言
前面几篇文章中介绍了在安全状态下,QQ登录的几种方法,但是只要是,该账号登录在新IP上,或用新账号登录本IP,都会使得登录流程转向非安全状态,即会有图片验证码,如下:
提示:以下是本篇文章正文内容,下面案例可供参考
一、登录流程
如我在第一篇文章中介绍那样:Java实现QQ登录,会多出几个请求,其中我们会看到又两个img的请求:
而这两个请求正是获得验证码图片和图片缺口的接口。
二、验证办法
1.滑动验证码
当我们拖动滑块并停止时,会向后台发送一个cap_union_new_verify的请求,这个request的data里,又这样一个关键的数据,ans
经过分析得知,这个ans的值,正式滑动验证码中缺口图片在原图中的位置,那么我们只要能获得这个缺口坐标,即可获得正确request data。
2.获取缺口坐标
我们使用OpenCV来处理图片
2.1 获得两张验证码的url
大图:
https://t.captcha.qq.com/hycdn?index=1&image=937349372142954496?aid=21000501&sess=s0QmzAR9jp0J_P9zQf6-BikkZVSLAm3IZz9JBiMAkRnqiQQ-ehtcopO3OYgJzF9ZLrA1B6phmWOrNtG1YEarWRA9wRp2gHvMW9lzGN5bZgFvIdgvL1ljAEcUq-ASo3WNABJPoMJP-UWSkRsimXX2wetiGFbUg7pMjUYGBPJAUxL8gCiyRnbKExWMrjyzVu4onNqzoxRU4_Ou9jexaVzeERJRo-WahiuPG2NyiohF5390zOVQ1tmDAmDg**&sid=607792806632235317&img_index=1&subsid=9
缺口图:
https://t.captcha.qq.com/hycdn?index=2&image=937349372142954496?aid=21000501&sess=s0QmzAR9jp0J_P9zQf6-BikkZVSLAm3IZz9JBiMAkRnqiQQ-ehtcopO3OYgJzF9ZLrA1B6phmWOrNtG1YEarWRA9wRp2gHvMW9lzGN5bZgFvIdgvL1ljAEcUq-ASo3WNABJPoMJP-UWSkRsimXX2wetiGFbUg7pMjUYGBPJAUxL8gCiyRnbKExWMrjyzVu4onNqzoxRU4_Ou9jexaVzeERJRo-WahiuPG2NyiohF5390zOVQ1tmDAmDg**&sid=607792806632235317&img_index=2&subsid=10
url中最重要的参数sess可从上一个请求:cap_union_prehandle中的response中获得。
验证码的图有时间限制,一段时间后会过期
2.2 处理图片获得x坐标
因为滑块是左右滑动的,所以y坐标固定,我们先滑动一下获得他的y坐标,本例中的y坐标是41。
使用下面程序:
// 加载opencv环境
public static String dllPath = "D:\\workcode\\txslider\\src\\main\\resources\\opencv\\opencv_java3413.dll";
/**
* 获取验证滑动距离
*
* @return
*/
public static int getTencentDistance(String bUrl, String sUrl, int top) {
System.load(dllPath);
File bFile = new File("D:\\cap_union_new_getcapbysig.jpg");
File sFile = new File("D:\\cap_union_new_getcapbysig.png");
try {
FileUtils.copyURLToFile(new URL(bUrl), bFile);
FileUtils.copyURLToFile(new URL(sUrl), sFile);
BufferedImage bgBI = ImageIO.read(bFile);
BufferedImage sBI = ImageIO.read(sFile);
// 裁剪
bgBI = bgBI.getSubimage(360, top, bgBI.getWidth() - 370, sBI.getHeight());
ImageIO.write(bgBI, "png", bFile);
Mat s_mat = Imgcodecs.imread(sFile.getPath());
Mat b_mat = Imgcodecs.imread(bFile.getPath());
// 转灰度图像
Mat s_newMat = new Mat();
Imgproc.cvtColor(s_mat, s_newMat, Imgproc.COLOR_BGR2GRAY);
// 二值化图像
binaryzation(s_newMat);
Imgcodecs.imwrite(sFile.getPath(), s_newMat);
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_SQDIFF); // 归一化平方差匹配法
// 归一化相关匹配法
Core.normalize(g_result, g_result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
Core.MinMaxLocResult mmlr = Core.minMaxLoc(g_result);
Point 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, 0, 0, 0));
return (int) ((matchLocation.x + s_mat.cols() + 360 - sBI.getWidth()));
} catch (Throwable e) {
e.printStackTrace();
return 0;
} finally {
bFile.delete();
sFile.delete();
}
}
/**
*
* @param mat
* 二值化图像
*/
public static void binaryzation(Mat mat) {
int BLACK = 0;
int WHITE = 255;
int ucThre = 0, ucThre_new = 127;
int nBack_count, nData_count;
int nBack_sum, nData_sum;
int nValue;
int i, j;
int width = mat.width(), height = mat.height();
// 寻找最佳的阙值
while (ucThre != ucThre_new) {
nBack_sum = nData_sum = 0;
nBack_count = nData_count = 0;
for (j = 0; j < height; ++j) {
for (i = 0; i < width; i++) {
nValue = (int) mat.get(j, i)[0];
if (nValue > ucThre_new) {
nBack_sum += nValue;
nBack_count++;
} else {
nData_sum += nValue;
nData_count++;
}
}
}
nBack_sum = nBack_sum / nBack_count;
nData_sum = nData_sum / nData_count;
ucThre = ucThre_new;
ucThre_new = (nBack_sum + nData_sum) / 2;
}
// 二值化处理
int nBlack = 0;
int nWhite = 0;
for (j = 0; j < height; ++j) {
for (i = 0; i < width; ++i) {
nValue = (int) mat.get(j, i)[0];
if (nValue > ucThre_new) {
mat.put(j, i, WHITE);
nWhite++;
} else {
mat.put(j, i, BLACK);
nBlack++;
}
}
}
// 确保白底黑字
if (nBlack > nWhite) {
for (j = 0; j < height; ++j) {
for (i = 0; i < width; ++i) {
nValue = (int) (mat.get(j, i)[0]);
if (nValue == 0) {
mat.put(j, i, WHITE);
} else {
mat.put(j, i, BLACK);
}
}
}
}
}
主要思想为:根据传入的y坐标和两个图片的url,将网络上的图片先保存在本地,然后对大图进行切图,减少一些计算量,然后二值化两张图,接着使用归一化相关匹配法,计算出切图后的x距离,最后加上原来的x距离,即可得到最终的x坐标。
2.3 调用结果
getTencentDistance("https://t.captcha.qq.com/hycdn?index=1&image=937349372142954496?aid=21000501&sess=s0QmzAR9jp0J_P9zQf6-BikkZVSLAm3IZz9JBiMAkRnqiQQ-ehtcopO3OYgJzF9ZLrA1B6phmWOrNtG1YEarWRA9wRp2gHvMW9lzGN5bZgFvIdgvL1ljAEcUq-ASo3WNABJPoMJP-UWSkRsimXX2wetiGFbUg7pMjUYGBPJAUxL8gCiyRnbKExWMrjyzVu4onNqzoxRU4_Ou9jexaVzeERJRo-WahiuPG2NyiohF5390zOVQ1tmDAmDg**&sid=607792806632235317&img_index=1&subsid=9",
"https://t.captcha.qq.com/hycdn?index=2&image=937349372142954496?aid=21000501&sess=s0QmzAR9jp0J_P9zQf6-BikkZVSLAm3IZz9JBiMAkRnqiQQ-ehtcopO3OYgJzF9ZLrA1B6phmWOrNtG1YEarWRA9wRp2gHvMW9lzGN5bZgFvIdgvL1ljAEcUq-ASo3WNABJPoMJP-UWSkRsimXX2wetiGFbUg7pMjUYGBPJAUxL8gCiyRnbKExWMrjyzVu4onNqzoxRU4_Ou9jexaVzeERJRo-WahiuPG2NyiohF5390zOVQ1tmDAmDg**&sid=607792806632235317&img_index=2&subsid=10",
41)
调用getTencentDistance方法,传入大图和缺口图的地址,并传入y坐标。
最终结果为:
而实际y坐标为
相差6个像素点,在可接受范围内。
总结
本文主要介绍了用opencv,来识别滑动验证码中,缺口图片相对背景图的x坐标,获得了x坐标位置,即可模拟手动滑动,完成验证码的突破,结合前面几篇文章,即可完成有验证码情况下的登录。
当然还有更方便的方式,即通过Selenium自动模仿人手动滑动的方法,这种方法将在下篇文章中介绍。