网易滑动验证码破解

1.滑动验证码

前面介绍了利用 tesserocr 来识别简单的图形验证码,和利用openCV识别滑动验证码的缺口处的位置坐标,本篇文章就正式介绍滑动验证码的破解思路,它需要拖动拼合滑块才可以完成验证,相对图形验证码来说识别难度加大了不少。

2.破解流程

  • 获取小图片和带有缺口的图片
  • 识别缺口位置
  • 生成滑块拖动路径
  • 模拟实现滑块拼合通过验证

代码实例是破解网易易盾滑动验证码 ,下面是程序控制chrome浏览器,自动破解验证码成功的结果。

gif

3.保存图片到本地以便分析

根据chrome调试工具获取到小图片和待带口图片的名字,如图是待缺口图片的信息,可以看到它的CLASS_NAMEyidun_bg.i,小图片的分析方法和它一致。

img

获取图片的代码
def get_pic(self):
    time.sleep(2)
    target = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_bg-img')))
    template = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_jigsaw')))
    target_link = target.get_attribute('src')
    template_link = template.get_attribute('src')
    target_img = Image.open(BytesIO(requests.get(target_link).content))
    template_img = Image.open(BytesIO(requests.get(template_link).content))
    target_img.save('target.jpg')
    template_img.save('template.png')
    local_img = Image.open('target.jpg')
    size_loc = local_img.size
    self.zoom = 320 / int(size_loc[0)

先是获取到了两张图片对象,然后获取它们的网络地址,并将其下载和保存下来。

注意

    local_img = Image.open('target.jpg')
    size_loc = local_img.size  #获取下载好的图片的大小(以像素为单位)
    self.zoom = 320 / int(size_loc[0]) # 网页上的图片为320像素

考虑到代码可能会在不同电脑上运行,为防止因为屏幕分辨率不一样,导致位移距离出现偏差所以后面操纵浏览器要移动的位移是需要缩放的。要获得到这个缩放系数,是将目标文件在网页上的大小和下载下来后实际的大小做比对,做除法即可获得缩放系数。

4.识别缺口位置

原理和实践在[Opencv_Python]模板匹配里已经完整的介绍了,这里不做赘述了。

def match(self, target, template):
    img_rgb = cv2.imread(target)
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
    template = cv2.imread(template, 0)
    run = 1
    w, h = template.shape[::-1]
    print(w, h)
    res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
    run = 1

    # 使用二分法查找阈值的精确值
    L = 0
    R = 1
    while run < 20:
        run += 1
        threshold = (R + L) / 2
        print(threshold)
        if threshold < 0:
            print('Error')
            return None
        loc = np.where(res >= threshold)
        print(len(loc[1]))
        if len(loc[1]) > 1:
            L += (R - L) / 2
        elif len(loc[1]) == 1:
            print('目标区域起点x坐标为:%d' % loc[1][0])
            break
        elif len(loc[1]) < 1:
            R -= (R - L) / 2

5.计算移动轨迹

移动轨迹是要完全模拟人移动滑块时的状态,即先加速再减速还可以加入回退和抖动。前段滑块做匀加速运动,后段滑块做匀减速运动,利用物理学 的加速度公式 即可完成验证 。
滑块滑动 的加 速度用 a 来表示 , 当前速度用 v 表示 , 初速度用 VO 表示 ,位移用s 表示 ,所需 时间用 t 表示,它们 之间 满足如下关系 :

s = VO * t + 1/2 * a * t * t
V=VO+a*t

利用这两个公式可以构造轨迹移动算法 ,计算出先加速后减速的运动轨迹,代码实现如下所示 :

def get_tracks(self, distance):
    print(distance)
    distance += 20
    v = 0
    t = 0.2
    forward_tracks = []
    current = 0
    mid = distance * 3 / 5  #减速阀值
    while current < distance:
        if current < mid:
            a = 2  #加速度为+2
        else:
            a = -3  #加速度-3
        s = v * t + 0.5 * a * (t ** 2)
        v = v + a * t
        current += s
        forward_tracks.append(round(s))

    back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]
    return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks}

get_tracks()方法,传人的参数为要移动的总距离(我们通过上面的match函数已经计算出来了),它返回的是运动轨迹 。运动轨迹用 forward_track表示,它是一个列表,列表的每个元素代表每次移动多少距离 。
首先定义变量 mid ,即减速的阔值,也就是加速到什么位置开始减速 。 在这里 mid 值为 3/5 ,即模
拟前 3/5 路程是加速过程,后 2/5 路程是减速过程 。
接着定义当前位移的距离变量 current ,初始为 0 ,然后进入 while 循环,循环的条件是当前位移
小于总距离 。 在循环里我们分段定义了加速度,其中加速过程的加速度定义为 2 ,减速过程的加速度
定义为 3 。 之后套用位移公式计算出某个时间段内的位移,将当前位移更新并记录到轨迹里即可 。
直到运动轨迹达到总距离时,循环终止 。 最后得到的 forward_tracks 记录了每个时间间隔移动了多少位移,
这样滑块的运动轨迹就得到了 。同时为了模拟一个回退的过程也手工设置了back_tracks列表,用于指定回退的位移量。

模拟滑块拼合的过程

def crack_slider(self):
    self.open()
    target = 'target.jpg'
    template = 'template.png'
    self.get_pic()
    distance = self.match(target, template)
    tracks = self.get_tracks((distance + 7) * self.zoom)  # 对位移的缩放计算
    print(tracks)
    slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'yidun_slider')))
    ActionChains(self.driver).click_and_hold(slider).perform()

    for track in tracks['forward_tracks']:
        ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform()

    time.sleep(0.5)
    for back_tracks in tracks['back_tracks']:
        ActionChains(self.driver).move_by_offset(xoffset=back_tracks, yoffset=0).perform()

    ActionChains(self.driver).move_by_offset(xoffset=-4, yoffset=0).perform()
    ActionChains(self.driver).move_by_offset(xoffset=4, yoffset=0).perform()
    time.sleep(0.5)

    ActionChains(self.driver).release().perform(

调用 ActionChains 的 click and hold ()方法按住拖
动底部滑块,遍历运动轨迹获取每小段位移距离,调用 move_by_offset ()方法移动此位移,最后调用
release ()方法松开鼠标即可 。整个过程模拟的是先加速在减速,并超出了缺口的位置,再回退,并且回退的多了4个像素,然后又前进了4个像素,完全是为了模拟人的行为,因为现在的滑动验证码识别加入了
机器学习模型。如果我们只是简单的匀速且一次的拖动是肯定不行的。

6.整体代码

from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import cv2
import numpy as np
from io import BytesIO
import time, requests


class CrackSlider():
    """
    通过浏览器截图,识别验证码中缺口位置,获取需要滑动距离,并模仿人类行为破解滑动验证码
    """
    def __init__(self):
        self.url = 'http://dun.163.com/trial/jigsaw'
        self.driver = webdriver.Chrome()
        self.wait = WebDriverWait(self.driver, 20)
        self.zoom = 1

    def open(self):
        self.driver.get(self.url)

    def get_pic(self):
        time.sleep(2)
        target = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_bg-img')))
        template = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_jigsaw')))
        target_link = target.get_attribute('src')
        template_link = template.get_attribute('src')
        target_img = Image.open(BytesIO(requests.get(target_link).content))
        template_img = Image.open(BytesIO(requests.get(template_link).content))
        target_img.save('target.jpg')
        template_img.save('template.png')
        local_img = Image.open('target.jpg')
        size_loc = local_img.size
        self.zoom = 320 / int(size_loc[0])

    def get_tracks(self, distance):
        print(distance)
        distance += 20
        v = 0
        t = 0.2
        forward_tracks = []
        current = 0
        mid = distance * 3 / 5  #减速阀值
        while current < distance:
            if current < mid:
                a = 2  #加速度为+2
            else:
                a = -3  #加速度-3
            s  = v * t + 0.5 * a * (t ** 2)
            v = v + a * t
            current += s
            forward_tracks.append(round(s))

        back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]
        return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks}

    def match(self, target, template):
        img_rgb = cv2.imread(target)
        img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
        template = cv2.imread(template, 0)
        run = 1
        w, h = template.shape[::-1]
        print(w, h)
        res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
        run = 1

        # 使用二分法查找阈值的精确值
        L = 0
        R = 1
        while run < 20:
            run += 1
            threshold = (R + L) / 2
            print(threshold)
            if threshold < 0:
                print('Error')
                return None
            loc = np.where(res >= threshold)
            print(len(loc[1]))
            if len(loc[1]) > 1:
                L += (R - L) / 2
            elif len(loc[1]) == 1:
                print('目标区域起点x坐标为:%d' % loc[1][0])
                break
            elif len(loc[1]) < 1:
                R -= (R - L) / 2
        return loc[1][0]

def crack_slider(self):
    slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'yidun_slider')))
    ActionChains(self.driver).click_and_hold(slider).perform()

    for track in tracks['forward_tracks']:
        ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform()

    time.sleep(0.5)
    for back_tracks in tracks['back_tracks']:
        ActionChains(self.driver).move_by_offset(xoffset=back_tracks, yoffset=0).perform()

    ActionChains(self.driver).move_by_offset(xoffset=-4, yoffset=0).perform()
    ActionChains(self.driver).move_by_offset(xoffset=4, yoffset=0).perform()
    time.sleep(0.5)

    ActionChains(self.driver).release().perform()


if __name__ == '__main__':
    cs = CrackSlider()
    cs.open()
    target = 'target.jpg'
    template = 'template.png'
    cs.get_pic()
    distance = self.match(target, template)
    tracks = self.get_tracks((distance + 7) * cs.zoom)  # 对位移的缩放计算
    cs.crack_slider()

7.小结

滑动验证码的识别,基本思路是先用图像识别的知识(opencv的模板匹配功能)判断出要滑动的位移,再模拟人拖动滑块的过程(先加速,再减速,适当加入回退,和随机抖动)。下一篇专题文章,是目前,最为流行的点触验证码的破解。

关于作者

个人博客: https://yhch.xyz

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容