简单说明一下:
测试网址: https://accounts.douban.com/passport/login (豆瓣登陆页)
类型: 滑块验证码
基本思路: 下载图片到本地 => 使用CV获取边缘值并计算距离 => 模拟用户拖动
一、模拟点击
1.点击至验证页
首先测试环境:
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from get_pic_distance import get_distance
import time
path = r"D:\python310\chromedriver.exe"
browser = webdriver.Chrome(path)
browser.get('https://accounts.douban.com/passport/login')
首先分析首页:
可得步骤:
①选择密码登陆 => ②输入账号 => ③输入密码 => ④ 点击登陆
分别找到对应控件的id(或name或xpath)
即:
browser.find_element(By.CLASS_NAME, 'account-tab-account').click()
# 登陆用户名和密码
browser.find_element(By.ID, 'username').send_keys('1111111111')
browser.find_element(By.ID, 'password').send_keys('1111111111')
# 点击登陆
browser.find_element(By.CLASS_NAME, 'btn.btn-account.btn-active').click()
2.保存图片
图片这里直接找url是找不到的,因为有一个iframe,需要先进到iframe中,再查找图片的url.
# 找到内嵌的iframe 并进入
iframe = browser.find_element(By.ID, 'tcaptcha_iframe_dy')
browser.switch_to.frame(iframe)
url = browser.find_element(By.ID, 'slideBg').get_attribute("style")
# 正则提取链接
url = 'https://t.captcha.qq.com'+re.findall('url\("(.*?)"\);', url, re.S)[0]
# 下载图片到本地
with open('verify.jpg', 'wb') as f:
f.write(requests.get(url).content)
这样就获取到了图片
二、CV识别缺口
1. 边缘识别
提取边缘值:① 高斯滤波降噪 ② 图像二值化 ③ 提取边缘值
# 高斯滤波
blurred = cv.GaussianBlur(image, (5, 5), 0)
# 图像二值化
canny = cv.Canny(blurred, 200, 400)
# 提取边缘轮廓 参数说明 分别为: 二值图像, 只检测最外围轮廓, 仅保存轮廓的拐点信息
contours, hierarchy = cv.findContours(canny, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
二值化效果图:
2.计算距离
这个要复杂些,主要是判断边缘值的形状和方向(moments)
然后根据这些信息对边缘图像进行筛选(一般是根据图像的周长和面积来筛选)(可能有其他边缘值干扰)
最后选择图像的最小外接矩形的最小X值来作为距离
即:
for i, contour in enumerate(contours):
M = cv.moments(contour) # 计算边缘值的形状和方向 主要是判断方向
print(M)
if M['m00'] == 0:
cx = 0
else:
cx, cy = M['m10'] / M['m00'], M['m01'] / M['m00']
print()
# 面积在5000-70000之间 且 周长在 300-390 依次来筛选在指定范围大小的矩形
if 5000 < cv.contourArea(contour) < 7000 and 300 < cv.arcLength(contour, True) < 390: # 计算轮廓的面积
if cx < 240:
continue
x, y, w, h = cv.boundingRect(contour) # 最小外接矩形
# cv.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2) 显示外接矩形
return x
return 0
矩形效果图:
返回结果:250
三、模拟用户拖动
1. 距离分散
对于稍微严格点的网页验证,会识别用户滑动的轨迹,这时就需要模拟用户的滑动轨迹(代码从网上参考,来源忘了,这里直接贴代码了)
def get_track(distance):
track = []
current = 0
mid = distance * 3 / 4
t = 0.2
v = 0
while current < distance:
if current < mid:
a = 2
else:
a = -3
v0 = v
v = v0 + a * t
move = v0 * t + 1 / 2 * a * t * t
current += move
track.append(round(move))
return track
运算结果:[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4]
2. 拖动
最后就是滑动滑块了,先找到滑块的位置:
先获取滑块属性,再定义滑块
btn = browser.find_element(By.CLASS_NAME, 'tc-fg-item.tc-slider-normal') # 获取滑块
ActionChains(browser).click_and_hold(btn).perform() # 定义滑动 调用函数缓慢滑动滑块
按照生成的距离列表,滑动滑块:
for x in get_track(dis):
ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
# 释放滑块
ActionChains(browser).release().perform()
最后补充一点:
这个方式不能应对所有的滑块情况,所以如果识别距离为0,这个时候要刷新图片,方法如法炮制,点击刷新即可。
最终代码:
import re
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from get_pic_distance import get_distance
import time
path = r"D:\python310\chromedriver.exe"
browser = webdriver.Chrome(path)
browser.get('https://accounts.douban.com/passport/login')
def get_track(distance):
track = []
current = 0
mid = distance * 3 / 4
t = 0.2
v = 0
while current < distance:
if current < mid:
a = 2
else:
a = -3
v0 = v
v = v0 + a * t
move = v0 * t + 1 / 2 * a * t * t
current += move
track.append(round(move))
return track
# 滑块验证
def move_verify():
error_num = 0
dis = 0
while error_num <= 3: # 有一种形状的拼图暂获取不到 如果遇到则刷新
print('下载图片...')
# browser.switch_to.default_content()
iframe = browser.find_element(By.ID, 'tcaptcha_iframe_dy') # 找到“嵌套”的iframe
browser.switch_to.frame(iframe)
url = browser.find_element(By.ID, 'slideBg').get_attribute("style")
# 正则提取链接
url = 'https://t.captcha.qq.com'+re.findall('url\("(.*?)"\);', url, re.S)[0]
# 下载图片到本地
with open('verify.jpg', 'wb') as f:
f.write(requests.get(url).content)
print('开始计算距离...')
dis = get_distance('verify.jpg') # 如果返回为空 则需要重新获取图片
if dis != 0:
print(dis)
dis = round(dis * 341 / 680, 2) - 27.5 - 12 # -12微调
print(f'实际距离:{dis}')
break
else:
print('返回了0')
browser.find_element(By.ID, 'reload').click() # 未识别到距离 则重新获取验证
error_num += 1
btn = browser.find_element(By.CLASS_NAME, 'tc-fg-item.tc-slider-normal') # 获取滑块
ActionChains(browser).click_and_hold(btn).perform() # 定义滑动 调用函数缓慢滑动滑块
for x in get_track(dis):
ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
ActionChains(browser).release().perform()
print('结束')
time.sleep(600)
if __name__ == '__main__':
# 切换为密码登陆
# account-tab-account
browser.find_element(By.CLASS_NAME, 'account-tab-account').click()
# 登陆用户名和密码
browser.find_element(By.ID, 'username').send_keys('1111111111')
browser.find_element(By.ID, 'password').send_keys('1111111111')
time.sleep(3)
# 点击登陆
browser.find_element(By.CLASS_NAME, 'btn.btn-account.btn-active').click()
time.sleep(2)
# time.sleep(2)
move_verify()
最终效果展示: