![f6001165ea31f30380b04a1dde9fcdaa.png](https://i-blog.csdnimg.cn/blog_migrate/5377deb92c80e679b50cd1f9aa6b3c93.jpeg)
今天
要来说说滑动验证码了
大家应该都很熟悉
点击滑块然后移动到图片缺口进行验证
![bbc7d28a5c855e947eae9bd7c698a872.png](https://i-blog.csdnimg.cn/blog_migrate/7839100585c1d314f353f367b6eab1cd.jpeg)
现在越来越多的网站使用这样的验证方式
为的是增加验证码识别的难度
![03b2c11e04f62cab9d23e2d7d4908c9b.png](https://i-blog.csdnimg.cn/blog_migrate/7457174a3ff2262f89c4beb162e00d28.jpeg)
那么
对于这种验证码
应该怎么破呢
接下来就是
学习 python 的正确姿势
![9fd1b213d227e55d619b5e1bc64902aa.png](https://i-blog.csdnimg.cn/blog_migrate/117d2b8e24b2d3d15f1d7bb4f58ac284.jpeg)
打开 b 站的登录页面
https://passport.bilibili.com/login
![c843c53bcb0db46377c4eba459d569b5.png](https://i-blog.csdnimg.cn/blog_migrate/7a4508df12b0a5a31340493428e07f96.jpeg)
可以看到登录的时候需要进行滑块验证
按下 F12
进入 Network
看下我们将滑块移到缺口松开之后做了什么提交
![1d0f85c4322893082a2bf3f2a8a7451b.png](https://i-blog.csdnimg.cn/blog_migrate/321d3cfd8469bb63ee042e6c7cf10c1a.jpeg)
可以看到是一个 GET 请求
但是
这请求链接也太特么长了吧
就是比小帅b短了一点点
![1d5b75b94158925574306a5cb1592184.png](https://i-blog.csdnimg.cn/blog_migrate/d61f5f15392a049f0cdcce8933ddb719.jpeg)
我们来看看请求的参数是怎么样的
![be8b9c98671a8eda47a86842e1e13644.png](https://i-blog.csdnimg.cn/blog_migrate/9cdffa1e8cd0b6f9182e0e24b1c09963.jpeg)
哇靠
gt?
challenge?
w?
这些都是什么鬼参数
还加密了
完全下不了手啊
![e630ae37c4e0983d09ecb71a2780758a.png](https://i-blog.csdnimg.cn/blog_migrate/fbcd7298f76f0511904bbf8871c6b999.jpeg)
那么
本篇完
再见
peace
说
你是不是迷恋我??
![207139e26bcc13b0296192409f37e638.png](https://i-blog.csdnimg.cn/blog_migrate/391bad280f93be12943a6f86d7a1b59f.jpeg)
好吧
你居然滑到这里来了
说明你还是有点爱小帅b的
小帅b是那种遇到一点困难就放弃的人吗
显然不是
那么接下来才是真的
学习 python 的正确姿势
![9fd1b213d227e55d619b5e1bc64902aa.png](https://i-blog.csdnimg.cn/blog_migrate/117d2b8e24b2d3d15f1d7bb4f58ac284.jpeg)
既然以请求的方式不好弄
我们从它们的源代码入手
看看有什么突破口
![9856e7197c1e7c47955677dd42e749ce.gif](https://i-blog.csdnimg.cn/blog_migrate/582c54cb2af22bac4e01a74987950e32.gif)
回到 b 站的登录页
按下 F12
进入 Element
然后点击滑块出现了图片
定位一下
![01817b896a7e6728730297c1f83b5c2c.png](https://i-blog.csdnimg.cn/blog_migrate/e35999d6d8d50d1acaf8e6e514622526.jpeg)
发现有两个 a 标签
一个 class 是 gt_bg gt_show
一个 class 是 gt_fullbg gt_show
和小帅b想的一样
这个验证码应该是有两张图片
一张是完全的背景图片
一张是缺口的图片
那把这两张图片下载下来对比一下不就行了
打开 a 标签一看
![ef873137128a9562519337875beac415.png](https://i-blog.csdnimg.cn/blog_migrate/8e1bfb4a816113e7061f82cd5496a5d8.jpeg)
哇靠
一张图片被切割成很多小块
原来这张图片是拼出来的
我们看看原始图片是怎么样的
![60cdc5577de4dc885e1f866d69de5998.png](https://i-blog.csdnimg.cn/blog_migrate/662a739c7d89720a756d19a098a68109.jpeg)
什么乱七八糟的
再仔细看下源代码
原来是在同一张图片通过偏移量合成了一张完整的图片
background-position: -277px -58px;
厉害厉害
小帅b看了一下缺口的图片也是如此
![3ca0b07b60b7272d29d8fc8ace8e97b0.png](https://i-blog.csdnimg.cn/blog_migrate/da36efa778ac20524e399d57ba77d25d.jpeg)
到这里
我们的第一个思路就是
下载这两张原始图片
然后通过偏移量合成两张真正的图片
背景图
![536a8576c3e08244ab3963e3498b3a97.png](https://i-blog.csdnimg.cn/blog_migrate/afe2ee0047eb9b13e99edafb48d69844.jpeg)
↓变身
![876dff3e9ff6934e7d0ec4edf569f695.png](https://i-blog.csdnimg.cn/blog_migrate/cb087e23fe0cca98c8a8d309397c1934.jpeg)
缺口图
![cf404f385656735ada87801a75e3957f.png](https://i-blog.csdnimg.cn/blog_migrate/c77a82d3deabc732bbae4aaa2e0934b0.jpeg)
↓变身
![3e5b9adff4acfaa15bfadc1e9c8650ab.png](https://i-blog.csdnimg.cn/blog_migrate/1ecc466c3df423edf3d6b1638bff9382.jpeg)
那么怎么做呢?
因为我们还要模拟滑动滑块
所以呢
我们要用到 selenium
打开b站的登录页
然后等到那个滑块显示出来
# 获取滑块按钮
driver.get(url)
slider = WAIT.until(EC.element_to_be_clickable(
(By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show")))
接下来我们就获取页面的源码
driver.page_source
然后使用 bs 获取两张原始背景图片的 url
bs = BeautifulSoup(driver.page_source,'lxml')
# 找到背景图片和缺口图片的div
bg_div = bs.find_all(class_='gt_cut_bg_slice')
fullbg_div = bs.find_all(class_='gt_cut_fullbg_slice')
# 获取缺口背景图片url
bg_url = re.findall('background-image:surl("(.*?)")',bg_div[0].get('style'))
# 获取背景图片url
fullbg_url = re.findall('background-image:surl("(.*?)")',fullbg_div[0].get('style'))
拿到了图片地址之后
将图片下载下来
# 将图片格式存为 jpg 格式
bg_url = bg_url[0].replace('webp', 'jpg')
fullbg_url = fullbg_url[0].replace('webp', 'jpg')
# print(bg_url)
# print(fullbg_url)
# 下载图片
bg_image = requests.get(bg_url).content
fullbg_image = requests.get(fullbg_url).content
print('完成图片下载')
ok
![60cdc5577de4dc885e1f866d69de5998.png](https://i-blog.csdnimg.cn/blog_migrate/662a739c7d89720a756d19a098a68109.jpeg)
![236ff17a5ba24468536dbe28c53540e9.png](https://i-blog.csdnimg.cn/blog_migrate/bb2b14663673af103a630f01099f7e31.jpeg)
我们已经把两张原始图片下载下来了
![217103085f76c9c3d15d2841b906a9d4.gif](https://i-blog.csdnimg.cn/blog_migrate/b09dd14dd49dd49973fbe7ec8de208de.gif)
那么接下来就是要合成图片了
我们要根据图片的位置来合成
也就是源码中的 background-position
![bcd2a91c7d94c06ccab1fa35da13ff88.png](https://i-blog.csdnimg.cn/blog_migrate/66905368ede3654ce4947f0e9888c38d.jpeg)
获取每一个小图片的位置
我们可以通过字典的形式来表示这些位置
然后将数据放到列表中
# 存放每个合成缺口背景图片的位置
bg_location_list = []
# 存放每个合成背景图片的位置
fullbg_location_list = []
for bg in bg_div:
location = {}
location['x'] = int(re.findall('background-position:s(.*?)pxs(.*?)px;', bg.get('style'))[0][0])
location['y'] = int(re.findall('background-position:s(.*?)pxs(.*?)px;', bg.get('style'))[0][1])
bg_location_list.append(location)
for fullbg in fullbg_div:
location = {}
location['x'] = int(re.findall('background-position:s(.*?)pxs(.*?)px;', fullbg.get('style'))[0][0])
location['y'] = int(re.findall('background-position:s(.*?)pxs(.*?)px;', fullbg.get('style'))[0][1])
fullbg_location_list.append(location)
那么
现在我们已经有了原始图片
还知道了每个位置应该显示原始图片的什么部分
接下来我们就写一个方法
用来合成图片
# 写入图片
bg_image_file = BytesIO(bg_image)
fullbg_image_file = BytesIO(fullbg_image)
# 合成图片
bg_Image = mergy_Image(bg_image_file, bg_location_list)
fullbg_Image = mergy_Image(fullbg_image_file, fullbg_location_list)
那么问题又来了
怎么合成啊
我们再看看一开始分析的图片
![ef873137128a9562519337875beac415.png](https://i-blog.csdnimg.cn/blog_migrate/8e1bfb4a816113e7061f82cd5496a5d8.jpeg)
这里图片被分割成的每一个小图片的尺寸是
10 * 58
所以我们也要将我们刚刚下载的原始图片切割成相应的尺寸大小
而且
这张图片是由上半部分的小图片和下半部分的小图片合成的
所以我们定义两个 list 来装这些小图片
# 存放上下部分的各个小块
upper_half_list = []
down_half_list = []
然后将原始的图片切割好放进去
image = Image.open(image_file)
# 通过 y 的位置来判断是上半部分还是下半部分,然后切割
for location in location_list:
if location['y'] == -58:
# 间距为10,y:58-116
im = image.crop((abs(location['x']), 58, abs(location['x'])+10, 116))
upper_half_list.append(im)
if location['y'] == 0:
# 间距为10,y:0-58
im = image.crop((abs(location['x']), 0, abs(location['x']) + 10, 58))
down_half_list.append(im)
至此
我们这两个 list 就分别放好了各个切割的图片了
那么接下来就创建一张空白的图片
然后将小图片一张一张(间距为10)的粘贴到空白图片里
这样我们就可以得到一张合成好的图片了
哎
我真是个天才
![535b173c4ab01a5da9f39860186b2610.png](https://i-blog.csdnimg.cn/blog_migrate/31c3d7e1ff99ba99c52abf6bf9dd6e55.jpeg)
# 创建一张大小一样的图片
new_image = Image.new('RGB', (260, 116))
# 粘贴好上半部分 y坐标是从上到下(0-116)
offset = 0
for im in upper_half_list:
new_image.paste(im, (offset, 0))
offset += 10
# 粘贴好下半部分
offset = 0
for im in down_half_list:
new_image.paste(im, (offset, 58))
offset += 10
那么到现在
我们可以得到网页上显示的那两张图片了
一张完全的图片
![876dff3e9ff6934e7d0ec4edf569f695.png](https://i-blog.csdnimg.cn/blog_migrate/cb087e23fe0cca98c8a8d309397c1934.jpeg)
一张带缺口的图片
![3e5b9adff4acfaa15bfadc1e9c8650ab.png](https://i-blog.csdnimg.cn/blog_migrate/1ecc466c3df423edf3d6b1638bff9382.jpeg)
接下来我们就要通过对比这两张图
看看我们要滑动的距离是多远
# 合成图片
bg_Image = mergy_Image(bg_image_file, bg_location_list)
fullbg_Image = mergy_Image(fullbg_image_file, fullbg_location_list)
# bg_Image.show()
# fullbg_Image.show()
# 计算缺口偏移距离
distance = get_distance(bg_Image, fullbg_Image)
print('得到距离:%s' % str(distance))
可以通过图片的 RGB 来计算
我们设定一个阈值
如果 r、g、b 大于这个阈值
我们就返回距离
def get_distance(bg_Image, fullbg_Image):
#阈值
threshold = 200
print(bg_Image.size[0])
print(bg_Image.size[1])
for i in range(60, bg_Image.size[0]):
for j in range(bg_Image.size[1]):
bg_pix = bg_Image.getpixel((i, j))
fullbg_pix = fullbg_Image.getpixel((i, j))
r = abs(bg_pix[0] - fullbg_pix[0])
g = abs(bg_pix[1] - fullbg_pix[1])
b = abs(bg_pix[2] - fullbg_pix[2])
if r + g + b > threshold:
return i
现在
我们知道了关键的滑动距离了
激动人心的时刻到了
我们使用 selenium
拿到滑块的元素
然后根据这个距离拖动到缺口位置不就好了么
马上打开 selenium 的文档
看到了这个函数
![70c622291f41ebe75f123a202121506c.png](https://i-blog.csdnimg.cn/blog_migrate/405e788f8fadceeac80f5db62e056736.jpeg)
它可以使用左键点击元素
然后拖动到指定距离
最后释放鼠标左键
挖槽
正合我意
赶紧试一下
knob = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show")))
ActionChains(driver).drag_and_drop_by_offset(knob, distance, 0).perform()
运行一下试试看吧
![47e8ad18af93f9b79b8f766ece3b8342.gif](https://i-blog.csdnimg.cn/blog_migrate/4be066004bda23f1e0a4f2219b8a6f35.gif)
哇哦你妹哦~
妖怪吃了拼图了
![aad97063dfcc8c1ed88411b0d011ca89.png](https://i-blog.csdnimg.cn/blog_migrate/417a7c3b4f2c0b4acf31bd52a33950cf.jpeg)
看来直接拖拽是不行的
容易遇到妖怪
毕竟这太快了
就算加藤鹰也没那么快吧
小帅b试着拖完滑块让它睡一下再释放
ActionChains(driver).click_and_hold(knob).perform()
ActionChains(driver).move_by_offset(xoffset=distance, yoffset=0.1).perform()
time.sleep(0.5)
ActionChains(driver).release(knob).perform()
发现拼图还是特么的被妖怪吃了
![f4fbee8e592e847c354d6d6b360b2ad4.png](https://i-blog.csdnimg.cn/blog_migrate/856b02083e18384d50082979776aed0f.jpeg)
后来小帅b发现原来别人也遇到了这样的问题
然后又发现了
有个叫匀速直线运动的东西
什么 加速度
什么 v = v0 + at
什么 s = ½at²
哇
这不是高中的知识点么
瞬间想起小帅b高中的时候在最角落的课桌
此刻往右上方抬起头
45 度角
让我的眼泪划出一条美丽的弧线
![0c9f6cf22d10f5accc208836f393d479.png](https://i-blog.csdnimg.cn/blog_migrate/d7aa4b148747ec758c406bc979733f70.jpeg)
什么鬼
回到正题
我们可以使用它来构造一个运动路径
该加速时加速
该减速的时候减速
这样的话就更像人类在滑动滑块了
def get_path(distance):
result = []
current = 0
mid = distance * 4 / 5
t = 0.2
v = 0
while current < (distance - 10):
if current < mid:
a = 2
else:
a = -3
v0 = v
v = v0 + a * t
s = v0 * t + 0.5 * a * t * t
current += s
result.append(round(s))
return result
这次
我们使用这个轨迹来滑动
knob = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#gc-box > div > div.gt_slider > div.gt_slider_knob.gt_show")))
result = get_path(distance)
ActionChains(driver).click_and_hold(knob).perform()
for x in result:
ActionChains(driver).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5)
ActionChains(driver).release(knob).perform()
好了好了
我们再来运行一下吧
![72e40608c0e92d65e4058f7b0c7842c1.gif](https://i-blog.csdnimg.cn/blog_migrate/272b4230256df5f56fc9bbcf975cf69a.gif)
哈哈哈
cool
成功识别了哇
我不管
![8d319256cae2dc85ce80c191538cb847.png](https://i-blog.csdnimg.cn/blog_migrate/7f1a8e5b7bcfcd85a9aeb81f642c7db8.jpeg)
当然了
成功率不是 100%
可以多调戏它几次
ok
以上就是识别滑动验证码的具体过程了
对于其它大部分的滑动验证码
也是可以使用这招搞定的
由于篇幅有限
源代码我放在了这个公众号【学习python的正确姿势】后台了
你发送〔滑动〕两个字
就可以获取啦
那
这次本篇就真的完啦
听说你想约我?
peace
肯定会
![d1d881f8abec51b4b2730bcfdbc30000.png](https://i-blog.csdnimg.cn/blog_migrate/0ab3049ac03a0dd244cb33be96e74c2e.jpeg)
相关文章
(小帅b教你三招搞定模拟登录)
(小帅b教你轻松识别图片验证码)
点个在zan啊~~(破音)
![c7910c3b878928f0c0b2a3090aab9da4.gif](https://i-blog.csdnimg.cn/blog_migrate/3771cf9d74f1515f8b322c4a8fa080d4.gif)