滑块验证码处理

46 篇文章 7 订阅


最近遇到了滑块验证问题,我看有纯抠js的,难度比较大,也没这个时间和精力去破解,想着还是用selenium自动化工具去解决吧,稍微研究了一下,用selenium + OpenCV还是比较靠谱的,selenium并不是python的专属,我查了一下java也有,参考

使用java + selenium + OpenCV破解网易易盾滑动验证码
没有用过selenium的可以看看我这篇文章:

selenium的使用

查了很多博客代码大多都失效了(我没有找到一个可以用的),下面是我自己用python的解决方案,描述尽量详尽。我就用网易易盾这个网站作为实验案例,因为我之前遇到的是嵌入式的滑动拼图,所以我这篇文章也是针对嵌入式的滑动拼图做演示,网站页面是这样的:
在这里插入图片描述

网址:网易易盾
下面说一下大体解决思路。

1. 进入网站,判断是否成功加载

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(“滑动拼图”) 即判断加载网页的标题是否包含“滑动拼图”字样,如图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwrqg2kN-1638251221693)(C:\Users\sumwhy\AppData\Roaming\Typora\typora-user-images\image-20211111105404103.png)]

WebDriverWait(driver, 5) 即5秒之内加载,比主动time.sleep更优。

2. 点击嵌入式,使网页向下滚动

点击嵌入式之后,是这样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhfKRJMz-1638251221694)(C:\Users\sumwhy\AppData\Roaming\Typora\typora-user-images\image-20211111105937314.png)]

如果不向下滑动就无法进行下面的滑动操作,所以这一步是必不可少的,

代码实现:

# 定位嵌入式图标
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个像素

3. 获取网页源代码,用正则取出验证码背景和验证码滑块图片的链接并保存到本地

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)
             

4.最最重要的一部分——识别图片滑块缺口位置,计算移动距离

网上有很多图片缺口识别的算法,但我试过感觉都不太尽如人意,成功率很低,最后找到一种简洁高效的方案,直接用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效果最好,这个要以网页实际测试结果为准。

5. 规划移动轨迹

​ 因为人们在滑动滑块的时候大多是先加速后减速,有时还会倒退,所以不可以做匀速直线运动,要做变速,变加速直线运动。
​ 用到物理知识:初速度: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

6. 开始移动滑块

定位到滑块元素,之后按下不松开开始在规划好的移动轨迹之内移动之前计算好的距离,到位后再松开:

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()

好了,到这里也该跟大家说再见了,如果本文对你有用,请给俺点个赞吧,虽然收到点赞我并不会获利,但这真的是对我的肯定与鼓舞。创作不易,可能你看一篇也就几分钟,我写一篇可能要几个小时甚至几天,这是真正的吃力不讨好。但就我个人来说,我还是觉得人总要有一点情怀的,作为一个技术追求者,应该具备基本的开源精神,我开始写技术博客有很大原因是受开源精神影响的,生活很难,不让生活磨灭我们的个性,是对自己精神价值的鼓舞,更是对自己情怀的一种坚守。

山野千里,只要在路上,内心就满是欢喜,继续坚持,继续加油啊!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰履踏青云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值