使用java + selenium + OpenCV破解网易易盾滑动验证码
没有用过selenium的可以看看我这篇文章:
selenium的使用
查了很多博客代码大多都失效了(我没有找到一个可以用的),下面是我自己用python的解决方案,描述尽量详尽。我就用网易易盾这个网站作为实验案例,因为我之前遇到的是嵌入式的滑动拼图,所以我这篇文章也是针对嵌入式的滑动拼图做演示,网站页面是这样的:
网址:网易易盾
下面说一下大体解决思路。
url = 'https://dun.163.com/trial/jigsaw'
driver = webdriver.Chrome()
driver.get(url)
WebDriverWait(driver, 5).until(EC.title_contains("滑动拼图"))
driver.maximize_window() # 窗口最大化
EC.title_contains(“滑动拼图”) 即判断加载网页的标题是否包含“滑动拼图”字样,如图
WebDriverWait(driver, 5) 即5秒之内加载,比主动time.sleep更优。
点击嵌入式之后,是这样的
如果不向下滑动就无法进行下面的滑动操作,所以这一步是必不可少的,
代码实现:
# 定位嵌入式图标
em = driver.find_element_by_xpath('/html/body/main/div[1]/div/div[2]/div[2]/ul/li[2]')
em.click() # 点击嵌入式
driver.execute_script('window.scrollTo(0, 300)') # 浏览器向下滑动300个像素
html = driver.page_source # 获取网页源代码
# print(html)
bg_img = re.findall(r'alt="验证码背景".*?src="(.*?)"',html)[0]
hk_img = re.findall(r'alt="验证码滑块".*?src="(.*?)"',html)[0]
# 保存图片
with open('./images/bg.jpg','wb') as f:
f.write(requests.get(bg_img).content)
with open('./images/hk.png','wb') as f:
f.write(requests.get(hk_img).content)
网上有很多图片缺口识别的算法,但我试过感觉都不太尽如人意,成功率很低,最后找到一种简洁高效的方案,直接用cv2的模板匹配,成功率很高,大概90%以上,不敢保证百分百,虽然我试了十次都成功了。
def get_distance():
"""
获取移动距离
:return:
"""
# 读取背景图片和缺口图片
bg_img = cv2.imread('./images/bg.jpg') # 背景图片
tp_img = cv2.imread('./images/hk.png') # 缺口图片
# 识别图片边缘
bg_edge = cv2.Canny(bg_img, 100, 200)
tp_edge = cv2.Canny(tp_img, 100, 200)
# 转换图片格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
x = max_loc[0] # 滑块在验证图片的x坐标(左边)
# 绘制方框
th, tw = tp_pic.shape[:2]
tl = max_loc # 左上角点的坐标
print(tl)
br = (tl[0] + tw, tl[1] + th) # 右下角点的坐标
print(br)
print(br[0])
cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2) # 绘制矩形
cv2.imwrite('./images/out.jpg', bg_img) # 保存在本地
return int(br[0]) - 43
int(br[0]) - 43 就是最后滑块的移动距离了,br[0]是我们画红框的右下角x坐标,然后减去滑块的宽度和红框边线的宽度就是移动距离,
滑块的宽度我量的方形框40个像素,再加上红色矩形框的2个像素,理论上应该是要减42,但最后做测试尝试还是43效果最好,这个要以网页实际测试结果为准。
因为人们在滑动滑块的时候大多是先加速后减速,有时还会倒退,所以不可以做匀速直线运动,要做变速,变加速直线运动。
用到物理知识:初速度:v0、位移:x、时间:t、加速度:a,满足公式:x = v0 * t + 1/2 * a * t^2
加速度用到random模块,随机选择给定的加速度
def track(distance):
"""
规划移动的轨迹
:param distance:
:return:
"""
#匀速移动
# for i in range(distance):
# ActionChains(self.driver).move_by_offset(1, 0).perform()
# ActionChains(self.driver).move_by_offset(distance-5, 0).perform()
t = 0.1
speed = 0
current = 0
mid = 3 / 5 * distance
track_list = []
while current < distance:
if current < mid:
a = random.choice([1,2,3])
# a = 3
else:
a = random.choice([-1,-2,-3])
# a = -4
move_track = speed * t + 0.5 * a * t**2
track_list.append(round(move_track))
speed = speed + a*t
current += move_track
#模拟人类来回移动了一小段
end_track = [1,0]*10 +[0]*10+[-1,0]*10
track_list.extend(end_track)
offset = sum(track_list) - distance
#由于四舍五入带来的误差,这里需要补回来
if offset > 0:
track_list.extend(offset*[-1,0])
elif offset < 0:
track_list.extend(offset*[1,0])
return track_list
定位到滑块元素,之后按下不松开开始在规划好的移动轨迹之内移动之前计算好的距离,到位后再松开:
def slid_button(distance):
"""
根据缺口位置,移动滑块特定的距离distance
:param diatance:
:return:
"""
# 获取滑块元素
button = driver.find_element_by_xpath(
'/html/body/main/div[1]/div/div[2]/div[2]/div[1]/div[2]/div/div/div[2]/div[3]/div/div/div[2]/div[2]/span')
# /html/body/main/div[1]/div/div[2]/div[2]/div[1]/div[2]/div/div/div[2]/div[3]/div/div/div[2]/div[2]/span
ActionChains(driver).click_and_hold(button).perform()
time.sleep(0.5)
track_list = track(distance - 3)
# print(track_list)
for i in track_list:
ActionChains(driver).move_by_offset(i, 0).perform()
time.sleep(1)
ActionChains(driver).release().perform()
from selenium import webdriver
import re
import time
import random
import cv2
import requests
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver import ActionChains
chrome_options = Options()
chrome_options.add_argument('--headless') # 浏览器不提供可视化页面
# driver = webdriver.Chrome(chrome_options=chrome_options) # 测试用无头模式无效
url = 'https://dun.163.com/trial/jigsaw'
driver = webdriver.Chrome()
driver.get(url)
WebDriverWait(driver, 5).until(EC.title_contains("滑动拼图"))
driver.maximize_window()
# time.sleep(2)
em = driver.find_element_by_xpath('/html/body/main/div[1]/div/div[2]/div[2]/ul/li[2]')
em.click()
driver.execute_script('window.scrollTo(0, 300)')
time.sleep(1)
html = driver.page_source
# print(html)
bg_img = re.findall(r'alt="验证码背景".*?src="(.*?)"', html)[0]
hk_img = re.findall(r'alt="验证码滑块".*?src="(.*?)"', html)[0]
print(bg_img)
print(hk_img)
def save_img():
with open('./images/bg.jpg', 'wb') as f:
f.write(requests.get(bg_img).content)
f.close()
with open('./images/hk.png', 'wb') as f:
f.write(requests.get(hk_img).content)
f.close()
def get_distance():
"""
获取移动距离
:return:
"""
# 读取背景图片和缺口图片
bg_img = cv2.imread('./images/bg.jpg') # 背景图片
tp_img = cv2.imread('./images/hk.png') # 缺口图片
# 识别图片边缘
bg_edge = cv2.Canny(bg_img, 100, 200)
tp_edge = cv2.Canny(tp_img, 100, 200)
# 转换图片格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
x = max_loc[0] # 滑块在验证图片的x坐标(左边)
# 绘制方框
th, tw = tp_pic.shape[:2]
tl = max_loc # 左上角点的坐标
print(tl)
br = (tl[0] + tw, tl[1] + th) # 右下角点的坐标
print(br)
print(br[0])
cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2) # 绘制矩形
cv2.imwrite('./images/out.jpg', bg_img) # 保存在本地
return int(br[0]) - 43
def track(distance):
"""
规划移动的轨迹
:param distance:
:return:
"""
# 匀速移动
# for i in range(distance):
# ActionChains(self.driver).move_by_offset(1, 0).perform()
# ActionChains(self.driver).move_by_offset(distance-5, 0).perform()
t = 0.1
speed = 0
current = 0
mid = 3 / 5 * distance
track_list = []
while current < distance:
if current < mid:
a = random.choice([1, 2, 3])
# a = 3
else:
a = random.choice([-1, -2, -3])
# a = -4
move_track = speed * t + 0.5 * a * t ** 2
track_list.append(round(move_track))
speed = speed + a * t
current += move_track
# 模拟人类来回移动了一小段
end_track = [1, 0] * 10 + [0] * 10 + [-1, 0] * 10
track_list.extend(end_track)
offset = sum(track_list) - distance
# 由于四舍五入带来的误差,这里需要补回来
if offset > 0:
track_list.extend(offset * [-1, 0])
elif offset < 0:
track_list.extend(offset * [1, 0])
return track_list
def slid_button(distance):
"""
根据缺口位置,移动滑块特定的距离distance
:param diatance:
:return:
"""
# 获取滑块元素
button = driver.find_element_by_xpath(
'/html/body/main/div[1]/div/div[2]/div[2]/div[1]/div[2]/div/div/div[2]/div[3]/div/div/div[2]/div[2]/span')
# /html/body/main/div[1]/div/div[2]/div[2]/div[1]/div[2]/div/div/div[2]/div[3]/div/div/div[2]/div[2]/span
ActionChains(driver).click_and_hold(button).perform()
time.sleep(0.5)
track_list = track(distance - 3)
# print(track_list)
for i in track_list:
ActionChains(driver).move_by_offset(i, 0).perform()
time.sleep(1)
ActionChains(driver).release().perform()
if __name__ == '__main__':
save_img()
distance = get_distance()
slid_button(distance)
# driver.save_screenshot('./images/big.png')
time.sleep(5) # 看一下效果
driver.close()
好了,到这里也该跟大家说再见了,如果本文对你有用,请给俺点个赞吧,虽然收到点赞我并不会获利,但这真的是对我的肯定与鼓舞。创作不易,可能你看一篇也就几分钟,我写一篇可能要几个小时甚至几天,这是真正的吃力不讨好。但就我个人来说,我还是觉得人总要有一点情怀的,作为一个技术追求者,应该具备基本的开源精神,我开始写技术博客有很大原因是受开源精神影响的,生活很难,不让生活磨灭我们的个性,是对自己精神价值的鼓舞,更是对自己情怀的一种坚守。