滑块验证码是目前网络上使用最多的,也是体验相对来说比较好的一种验证码。但爬虫和反爬虫就像矛和盾一样的关系相互促进相互影响,互联网技术就是在这样的不断碰撞中向前发展。
结合我自己的个人工作经验,来聊聊滑块验证码,我们就拿京东登陆页面的滑块验证举例,进行详细分解学习。
滑块验证码样例
目标:通过算法找到需要滑动的滑块(下文一律叫切片区)距离背景目标区域(下文一律叫背景区)的距离,然后自动拖动完成拼接。
一、利用Chrome-F12的开发者工具来定位滑块验证码的请求地址:
1、在google浏览器中打开对应的网站,进入到滑块验证码页面
2、在验证码页面按F12,进入Network区
3、点击验证码右上角的换一张(图中标号为1),目的是捕获验证码的请求地址
4、在name区可以看到多个情况地址,找到其中的验证码请求地址,这里是g.html(图中标号为2)
5、在Headers表头可以看到对应此链接地址的情况地址,以及请求方式,这里是GET请求
(注:后期可以通过JS或者Java等模拟网站GET请求来获取验证码信息)
定位滑块验证码的请求地址
二、分析、查找"切片区"和"背景区"的对应图片数据信息:
1、点击开发者工具中的Response来查看请求的返回值
2、这里是一个JSON串格式,其中bg对应的值就是背景图片区域的base64字符串值,patch对应的值就是切片区base64字符串值.
切片区"和"背景区"数据信息
3、将这些base64字符串值转换成图片,我们看一下背景区和切片区字符串对应的具体图像:
(背景区)
(切片区)
//切片对应的base64String
String sliceImg="iVBORw0KGgoAAAANSUhEUgAAADIAAA.....";//内容太多省略,自己从浏览器中获取即可
//背景区对应的base64String
String bgImg="iVBORw0KGgoAAAANSUhE....";//内容太多省略,自己从浏览器中获取即可
//背景区
BufferedImage biBuffer=base64StringToImg(bgImg);
//切片区
BufferedImage sliceBuffer=base64StringToImg(sliceImg);
//将图片输出到本地查看
ImageIO.write(biBuffer,
"png", new File("E:\\bgImg.png"));
ImageIO.write(sliceBuffer,
"png", new File("E:\\sliceImg.png"));
/**
* base64字符串转存图片
* @param base64String base64字符串
* @return BufferedImage
*/
public static BufferedImage base64StringToImg(final String base64String) {
try {
BASE64Decoder decoder=new BASE64Decoder();
byte[] bytes=decoder.decodeBuffer(base64String);
ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
return ImageIO.read(bais);
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
三、(重点,核心)利用orc模板匹配算法进行匹配,查找最相似区域,也就是我们的期望的坐标点:
废话不多说,直接上代码:
import static com.googlecode.javacv.cpp.opencv_core.CV_32FC1;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMat;
import static com.googlecode.javacv.cpp.opencv_core.cvMinMaxLoc;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_TM_CCOEFF_NORMED;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvMatchTemplate;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import com.googlecode.javacv.cpp.opencv_core;
import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvSize;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_imgproc;
import sun.misc.BASE64Decoder;
public class Test {
public static void main(String[] args) throws IOException {
//切片对应的base64String
String sliceImg="iVBORw0KGgoAAAANSUhEUgAAADIAAA.....";//内容太多省略,自己从浏览器中获取即可
//背景区对应的base64String
String bgImg="iVBORw0KGgoAAAANSUhE....";//内容太多省略,自己从浏览器中获取即可
// 背景区
BufferedImage biBuffer=base64StringToImg(bgImg);
// 切片区
BufferedImage sliceBuffer=base64StringToImg(sliceImg);
// 由于切片矩形区域存在透明区域,所以预处理将透明区域变成白色,方便后面对图片二值化处理。
// (重点:如果这里不对透明区域预处理,切片预处理后将只有一种颜色导致匹配失败)
int white=new Color(255, 255, 255).getRGB();
for (int x=0; x < sliceBuffer.getWidth(); x++) {
for (int y=0; y < sliceBuffer.getHeight(); y++) {
if ((sliceBuffer.getRGB(x, y) >> 24)==0) {
sliceBuffer.setRGB(x, y, white);
}
}
}
IplImage sourceImage=IplImage.createFrom(biBuffer);
IplImage targetImage=IplImage.createFrom(sliceBuffer);
CvMat sourceMat=sourceImage.asCvMat();
CvMat targetMat=targetImage.asCvMat();
// 模板匹配算法,根据目标图片在背景图片中查找相似的区域
List<Rectangle> a=matchTemplateTest(sourceMat, targetMat);
// 取第一个值,也就是匹配到的最相识的区域,可以定位目标坐标
// 也是我们期望的坐标点
Rectangle rec=a.get(0);
// 下面是验证,将识别到的区域用红色矩形框标识出来,进行验证看是否正确
Graphics g=biBuffer.getGraphics();
// 画笔颜色
g.setColor(Color.RED);
// 矩形框(原点x坐标,原点y坐标,矩形的长,矩形的宽)
g.drawRect(rec.x, rec.y, rec.width, rec.height);
g.dispose();
//输出到本地,验证区域查找是否正确
FileOutputStream out=new FileOutputStream("d:\\checkImage.png");
ImageIO.write(biBuffer, "png", out);
}
/**
* 模板匹配算法,根据目标图片在背景图片中查找相似的区域
* @param sourceMat 背景区域图片数组矩阵
* @param targetMat 切片目标区域图片数组矩阵
* @return 坐标点集合
*/
public static List<Rectangle> matchTemplateTest(CvMat sourceMat, CvMat targetMat) {
List<Rectangle> rtn=new ArrayList<Rectangle>();
//对图象进行单通道、二值化处理
CvMat source=opencv_core.cvCreateMat(sourceMat.rows(), sourceMat.cols(), opencv_core.CV_8UC1);
CvMat target=opencv_core.cvCreateMat(targetMat.rows(), targetMat.cols(), opencv_core.CV_8UC1);
opencv_imgproc.cvCvtColor(sourceMat, source, opencv_imgproc.CV_BGR2GRAY);
opencv_imgproc.cvCvtColor(targetMat, target, opencv_imgproc.CV_BGR2GRAY);
CvSize targetSize=target.cvSize();
CvSize sourceSize=source.cvSize();
CvSize resultSize=new CvSize();
resultSize.width(sourceSize.width() - targetSize.width() + 1);
resultSize.height(sourceSize.height() - targetSize.height() + 1);
CvMat result=cvCreateMat(resultSize.height(), resultSize.width(), CV_32FC1);
//利用模板匹配算法进行查找
cvMatchTemplate(source, target, result, CV_TM_CCOEFF_NORMED);
opencv_core.CvPoint maxLoc=new opencv_core.CvPoint();
opencv_core.CvPoint minLoc=new opencv_core.CvPoint();
double[] minVal=new double[2];
double[] maxVal=new double[2];
//找出图片数据中最大值及最小值的数据
cvMinMaxLoc(result, minVal, maxVal, minLoc, maxLoc, null);
Rectangle rec=new Rectangle(maxLoc.x(), maxLoc.y(), target.cols(), target.rows());
//将查找到的坐标按最优值顺序放入数组
rtn.add(rec);
source.release();
target.release();
result.release();
opencv_core.cvReleaseMat(result);
opencv_core.cvReleaseMat(source);
opencv_core.cvReleaseMat(target);
source=null;
target=null;
result=null;
return rtn;
}
我们看一下识别到的结果区域(红色矩形标识就是有系统自动识别出来的)霸气不霸气:
系统自动识别出来的区域坐标
四、根据第三步得到的移动坐标点进行坐标移动(这太小菜了,就不大篇幅在这里啰嗦了,可以使用你知道的任何技术进行模拟坐标移动),我用autoit进行举例;
//autoit代码块
//移动鼠标指针。
MouseMove ( x, y [, 速度] )
//参数说明:
x:要移动到的目标位置的 X 坐标。
y:要移动到的目标位置的 Y 坐标。
速度:鼠标移动速度,可设数值范围在 1(最快)和 100(最慢)之间。若设置速度为 0 则立即移动鼠标到指定位置。默认速度为 10。
ue可以通过插件来实现滑动验证码。下面是几种方法:
可以使用第三方的滑动验证码库,例如'vue-verify-slide'、'vue-slide-verify'等,这些库已经实现了滑动验证码的逻辑,我们只需要将其作为插件引入即可。
优点:实现方便,无需自己编写逻辑代码。
缺点:依赖第三方库,如果第三方库更新不及时或存在漏洞,会影响到整个系统。
可以自己编写滑动验证码组件,实现自定义的UI和逻辑。
优点:可以自由定制UI和逻辑。
缺点:需要编写大量的逻辑代码,工作量较大。
下面是一个自己编写的滑动验证码组件的示例:
<template>
<div class="slider-verify">
<div class="slider-bar" :style="{left: thumbLeft}">
<div class="slider-thumb" @mousedown="onMouseDown"></div>
</div>
<div class="slider-mask"></div>
</div>
</template>
<script>
export default {
data() {
return {
isDragging: false, // 是否正在拖动滑块
thumbLeft: 0, // 滑块左边距
maxWidth: 280, // 滑块最大可移动距离
dragStartX: 0, // 开始拖动时鼠标的x坐标
dragOffsetX: 0, // 鼠标相对于滑块左边缘的偏移量
};
},
methods: {
onMouseDown(e) {
this.isDragging=true;
this.dragStartX=e.clientX;
this.dragOffsetX=e.offsetX;
},
onMouseMove(e) {
if (this.isDragging) {
const distance=e.clientX - this.dragStartX;
const thumbLeft=Math.min(Math.max(distance - this.dragOffsetX, 0), this.maxWidth);
this.thumbLeft=`${thumbLeft}px`;
}
},
onMouseUp(e) {
this.isDragging=false;
if (parseInt(this.thumbLeft)===this.maxWidth) {
this.$emit('success');
} else {
this.thumbLeft='0px';
}
},
},
mounted() {
window.addEventListener('mousemove', this.onMouseMove);
window.addEventListener('mouseup', this.onMouseUp);
},
beforeDestroy() {
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('mouseup', this.onMouseUp);
},
};
</script>
<style scoped>
.slider-verify {
position: relative;
width: 300px;
height: 40px;
border-radius: 20px;
overflow: hidden;
}
.slider-bar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #eee;
transition: all 0.3s ease-out;
}
.slider-thumb {
position: absolute;
left: 0;
top: 50%;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease-out;
}
.slider-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 20px;
}
</style>
可以使用canvas绘制滑动验证码,将滑块拖动的距离作为验证依据。
优点:可以自由定制UI和逻辑,滑动效果更流畅。
缺点:需要对canvas有一定的了解,对性能有一定的影响。
下面是一个使用canvas实现的滑动验证码的示例:
<template>
<div class="canvas-verify">
<canvas ref="canvas" :width="canvasWidth" :height="canvasHeight" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp"></canvas>
</div>
</template>
<script>
export default {
data() {
return {
isDragging: false, // 是否正在拖动滑块
thumbLeft: 0, // 滑块左边距
canvasWidth: 300, // canvas宽度
canvasHeight: 150, // canvas高度
maxWidth: 250, // 滑块最大可移动距离
dragStartX: 0, // 开始拖动时鼠标的x坐标
dragOffsetX: 0, // 鼠标相对于滑块左边缘的偏移量
canvasContext: null, // canvas context
imagePath: '', // 背景图路径
};
},
methods: {
onMouseDown(e) {
if (this.isDragging) {
return;
}
const rect=this.$refs.canvas.getBoundingClientRect();
this.isDragging=true;
this.dragStartX=e.clientX - rect.left;
this.dragOffsetX=this.dragStartX - this.thumbLeft;
},
onMouseMove(e) {
if (this.isDragging) {
const rect=this.$refs.canvas.getBoundingClientRect();
const distance=e.clientX - rect.left - this.dragOffsetX;
const thumbLeft=Math.min(Math.max(distance, 0), this.maxWidth);
this.thumbLeft=thumbLeft;
this.draw();
}
},
onMouseUp(e) {
if (this.isDragging) {
this.isDragging=false;
if (this.thumbLeft===this.maxWidth) {
this.$emit('success');
} else {
this.thumbLeft=0;
this.draw();
}
}
},
draw() {
this.canvasContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 绘制背景图
const image=new Image();
image.src=this.imagePath;
image.onload=()=> {
this.canvasContext.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
// 绘制滑块
this.canvasContext.fillStyle='#ccc';
this.canvasContext.fillRect(this.thumbLeft, 50, 50, 50);
};
},
},
mounted() {
// 获取canvas context
this.canvasContext=this.$refs.canvas.getContext('2d');
// 加载背景图
this.imagePath='https://picsum.photos/300/150/?random';
const image=new Image();
image.src=this.imagePath;
image.onload=()=> {
this.draw();
};
},
beforeUnmount() {
this.canvasContext=null;
},
};
</script>
<style scoped>
.canvas-verify {
position: relative;
}
</style>
这个示例中,滑块使用了一个矩形代替,颜色为灰色。使用canvas实现滑动验证码需要对canvas有一定的了解,同时对性能也有一定的影响。但是可以自由定制UI和逻辑,实现更灵活。
以上是三种常见的实现滑动验证码的方法,每种方法都有其优点和缺点。使用CSS实现最简单,但是不太安全;使用canvas实现最灵活,但是需要对canvas有一定的了解;使用第三方库可以更快速地实现,但是需要依赖第三方库。具体使用哪种方法应该根据实际情况选择,权衡各种因素。
本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。
作者:卡卡叮
PS:如有需要Python学习资料的小伙伴可以私信小编
这篇文章主要介绍了python模拟哔哩哔哩滑块登入验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
谷歌驱动
建议指定清华源下载速度会更快点
使用方法 :
pip3 install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/opencv-python/
谷歌驱动
谷歌驱动下载链接 :http://npm.taobao.org/mirrors/chromedriver/
本篇文章采用的是cv2的Canny边缘检测算法进行图像识别匹配。
Canny边缘检测算法参考链接:https://www.jb51.net/article/185336.htm
具体使用的是Canny的matchTemplate方法进行模糊匹配,匹配方法用CV_TM_CCOEFF_NORMED归一化相关系数匹配。得出的max_loc就是匹配出来的位置信息。从而达到位置的距离。
难点
由于图像采用放大的效果匹配出的距离偏大,难以把真实距离,并存在误差。
由于哔哩哔哩滑块验证进一步采取做了措施,如果滑动时间过短,会导致验证登入失败。所以我这里采用变速的方法,在相同时间内滑动不同的距离。
误差的存在是必不可少的,有时会导致验证失败,这都是正常现象。
1.实例化谷歌浏览器 ,并打开哔哩哔哩登入页面。
2.点击登陆,弹出滑动验证框。
3.全屏截图、后按照尺寸裁剪各两张。
5.模糊匹配两张图片,从而获取匹配结果以及位置信息 。
6.将位置信息与页面上的位移距离转化,并尽可能少的减少误差 。
7.变速的拖动滑块到指定位置,从而达到模拟登入。
库安装好后,然后填写配置区域后即可运行。
from PIL import Image
from time import sleep
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import cv2
import numpy as np
import math
############ 配置区域 #########
zh='' #账号
pwd='' #密码
# chromedriver的路径
chromedriver_path="C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe"
####### end #########
options=webdriver.ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument('--window-size=1020,720')
# options.add_argument('--start-maximized') # 浏览器窗口最大化
options.add_argument('--disable-gpu')
options.add_argument('--hide-scrollbars')
options.add_argument('test-type')
options.add_experimental_option("excludeSwitches", ["ignore-certificate-errors",
"enable-automation"]) # 设置为开发者模式
driver=webdriver.Chrome(options=options, executable_path=chromedriver_path)
driver.get('https://passport.bilibili.com/login')
# 登入
def login():
driver.find_element_by_id("login-username").send_keys(zh)
driver.find_element_by_id("login-passwd").send_keys(pwd)
driver.find_element_by_css_selector("#geetest-wrap > div > div.btn-box > a.btn.btn-login").click()
print("点击登入")
# 整个图,跟滑块整个图
def screen(screenXpath):
img=WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.XPATH, screenXpath))
)
driver.save_screenshot("allscreen.png") # 对整个浏览器页面进行截图
left=img.location['x']+160 #往右
top=img.location['y']+60 # 往下
right=img.location['x'] + img.size['width']+230 # 往左
bottom=img.location['y'] + img.size['height']+110 # 往上
im=Image.open('allscreen.png')
im=im.crop((left, top, right, bottom)) # 对浏览器截图进行裁剪
im.save('1.png')
print("截图完成1")
screen_two(screenXpath)
screen_th(screenXpath)
matchImg('3.png','2.png')
# 滑块部分图
def screen_two(screenXpath):
img=WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.XPATH, screenXpath))
)
left=img.location['x'] + 160
top=img.location['y'] + 80
right=img.location['x'] + img.size['width']-30
bottom=img.location['y'] + img.size['height'] + 90
im=Image.open('allscreen.png')
im=im.crop((left, top, right, bottom)) # 对浏览器截图进行裁剪
im.save('2.png')
print("截图完成2")
# 滑块剩余部分图
def screen_th(screenXpath):
img=WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.XPATH, screenXpath))
)
left=img.location['x'] + 220
top=img.location['y'] + 60
right=img.location['x'] + img.size['width']+230
bottom=img.location['y'] + img.size['height'] +110
im=Image.open('allscreen.png')
im=im.crop((left, top, right, bottom)) # 对浏览器截图进行裁剪
im.save('3.png')
print("截图完成3")
#图形匹配
def matchImg(imgPath1,imgPath2):
imgs=[]
#展示
sou_img1=cv2.imread(imgPath1)
sou_img2=cv2.imread(imgPath2)
# 最小阈值100,最大阈值500
img1=cv2.imread(imgPath1, 0)
blur1=cv2.GaussianBlur(img1, (3, 3), 0)
canny1=cv2.Canny(blur1, 100, 500)
cv2.imwrite('temp1.png', canny1)
img2=cv2.imread(imgPath2, 0)
blur2=cv2.GaussianBlur(img2, (3, 3), 0)
canny2=cv2.Canny(blur2, 100, 500)
cv2.imwrite('temp2.png', canny2)
target=cv2.imread('temp1.png')
template=cv2.imread('temp2.png')
# 调整大小
target_temp=cv2.resize(sou_img1, (350, 200))
target_temp=cv2.copyMakeBorder(target_temp, 5, 5, 5, 5, cv2.BORDER_CONSTANT, value=[255, 255, 255])
template_temp=cv2.resize(sou_img2, (200, 200))
template_temp=cv2.copyMakeBorder(template_temp, 5, 5, 5, 5, cv2.BORDER_CONSTANT, value=[255, 255, 255])
imgs.append(target_temp)
imgs.append(template_temp)
theight, twidth=template.shape[:2]
# 匹配跟拼图
result=cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED)
cv2.normalize( result, result, 0, 1, cv2.NORM_MINMAX, -1 )
min_val, max_val, min_loc, max_loc=cv2.minMaxLoc(result)
# 画圈
cv2.rectangle(target,max_loc,(max_loc[0]+twidth,max_loc[1]+theight),(0,0,255),2)
target_temp_n=cv2.resize(target, (350, 200))
target_temp_n=cv2.copyMakeBorder(target_temp_n, 5, 5, 5, 5, cv2.BORDER_CONSTANT, value=[255, 255, 255])
imgs.append(target_temp_n)
imstack=np.hstack(imgs)
cv2.imshow('windows'+str(max_loc), imstack)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 计算距离
print(max_loc)
dis=str(max_loc).split()[0].split('(')[1].split(',')[0]
x_dis=int(dis)+135
t(x_dis)
#拖动滑块
def t(distances):
draggable=driver.find_element_by_css_selector('div.geetest_slider.geetest_ready > div.geetest_slider_button')
ActionChains(driver).click_and_hold(draggable).perform() #抓住
print(driver.title)
num=getNum(distances)
sleep(3)
for distance in range(1,int(num)):
print('移动的步数: ',distance)
ActionChains(driver).move_by_offset(xoffset=distance, yoffset=0).perform()
sleep(0.25)
ActionChains(driver).release().perform() #松开
# 计算步数
def getNum(distances):
p=1+4*distances
x1=(-1 + math.sqrt(p)) / 2
x2=(-1 - math.sqrt(p)) / 2
print(x1,x2)
if x1>=0 and x2<0:
return x1+2
elif(x1<0 and x2>=0):
return x2+2
else:
return x1+2
def main():
login()
sleep(5)
screenXpath='/html/body/div[2]/div[2]/div[6]/div/div[1]/div[1]/div/a/div[1]/div/canvas[2]'
screen(screenXpath)
sleep(5)
if __name__=='__main__':
main()
有能力的可以研究一下思路,然后写出更好的解决办法。
*请认真填写需求信息,我们会在24小时内与您取得联系。