python实现滑块验证码破解

python实现滑块验证码破解

@TOC

本实验用到的python包

先把这些包安装好喔^ _ ^

import aircv as ac
import random
import six
import os,base64
import time, re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import requests
import io
from io import BytesIO
import cv2

本次要破解验证码的网站地址

http://www.pkulaw.cn/cluster_form.aspx?Db=chl&menu_item=law&EncodingName=&clust_param=0/XC02&keyword=&range=name&

我们为了快速得到验证码验证界面,在页框里输入21页(因为浏览20页就会弹出验证码),如下图
在这里插入图片描述

代码正篇

配置信息

def __init__(self):
    chrome_option = webdriver.ChromeOptions()
    self.driver = webdriver.Chrome(executable_path=r"F:\下载\chromedriver.exe", chrome_options=chrome_option)
    self.driver.set_window_size(1440, 900)

我们这次是使用selenium控制Chrome浏览器自动打开网站,实现自动拖动滑块完成验证码,所以先下载好chromedriver.exe,将webdriver配置好

想要深入了解selenium的原理,建议自行查阅资料哦。
这里给大家一个网址,方便大家快速了解:https://www.jianshu.com/p/1b63c5f3c98e

网页弹出验证码

下面一步是控制浏览器输入21页,让网页弹出验证码:

url="http://www.pkulaw.cn/cluster_form.aspx?Db=chl&menu_item=law&EncodingName=&clust_param=0/XC02&keyword=&range=name&"
self.driver.get(url)
input = WebDriverWait(self.driver,10) .until(
EC.presence_of_element_located((By.XPATH,'//*[@id="gopage1"]'))
)
input.send_keys('21')

button = WebDriverWait(self.driver,10).until(EC.element_to_be_clickable ((By.XPATH,'//a[@class="anniu-page"]')))

button.click()
time.sleep(5)#等待加载

想要输入21,就得先知道那个页框的位置,我们使用Chrome在那个页框位置右键,然后选择检查元素,就能得知html标签信息了
在这里插入图片描述)代码里的id就是对应这里的id,我们找到这个位置,给他send_keys,意思就是给他传一个值21,这就完成了输入,接着我们要找到确定键的标签位置,控制鼠标点击,方法类似。在这里插入图片描述

给页面加载一会,防止加载过慢
time.sleep(…)

下载验证码图片

这是我们得到的验证码,我们想办法把他保存到本地。
在这里插入图片描述
首先,我们右键这些图片,查看一下他们的html标签,如下图:
在这里插入图片描述
我们通过查看得知,验证码被分成了20张图片,上下部分各十张,id是指他们的图片序号,通过对比可以发现,图片得到url都是一样的,由此可以得知,这个url是整张图片的url,我们先把他们下载下来。

def get_image_url(self, xpath):
    #得到背景图片
    link = re.compile('background-image: url\("(.*?)"\); width: 30px; height: 100px; background-position: (.*?)px (.*?)px;')#严格按照格式包括空格
    elements = self.driver.find_elements_by_xpath(xpath)
    image_url = None
    location = list()
    for element in elements:

        style = element.get_attribute("style")
        groups = link.search(style)

        url = groups[1]
        x_pos = groups[2]
        y_pos = groups[3]

        location.append((int(x_pos), int(y_pos)))
        image_url = url
    return image_url, location

我们用正则表达式来方便的将他们图片的位置信息(background-position: (.?)px (.?)px;)保存下来,以便后续使用。
我们看一下下载的图片:
在这里插入图片描述

坐标分析

有没有和我一样很惊奇呢?我们把事情想得太简单了!(当时看到这个我人傻了)原来他的背景图片是乱序的,我们得将他们拼接成正常的图片...
我们通过分析,发现我们刚刚保存的位置信息原来就是他们在这张图片里的位置。(还好我们早有准备!)我们知道每张小图片的大小是(30px,100px)
分别对应宽,高。再看一下位置信息,可以看到它们的y坐标只有0px和-100px,这就是指他们位于图片的上半部分或者下半部分,看来还是很简单的嘛哈哈哈。那这么说x坐标就应该是0,30,60...可是我眉头一皱,事情并没有这么简单..
怎么这些x坐标有的是正数,有的是负数,有的负数还<-300?通过仔细研究对比,终于总结出了他们的规律,无非就是将原本都是正数的正常坐标和300这个数字进行了一些加减运算,正常的正数坐标是从左往右看为0,30,60等等,现在我们通过样例分析出来,得出了转化方式,见代码:
我们根据图片位置信息,进行运算,得到他正确的正数形式的坐标,再图上进行裁剪,按次序拼接在一张画布上,最终得到完整的图。(不明白的同学代入两个例子就懂了)

def mosaic_image(self, image_url, location):

    resq = base64.b64decode(image_url[22::])
    file1 = open('1.jpg', 'wb')
    file1.write(resq)
    file1.close()
    with open("1.jpg", 'rb') as f:
        imageBin = f.read()
    # 很多时候,数据读写不一定是文件,也可以在内存中读写。
    # BytesIO实现了在内存中读写bytes,创建一个BytesIO,然后写入一些bytes:
    buf = io.BytesIO(imageBin)
    img = Image.open(buf)
    image_upper_lst = []
    image_down_lst = []
    count=1
    for pos in location:
        if pos[0]>0:
            if count <= 10:
                if pos[1] == 0:

                    image_upper_lst.append(img.crop((abs(pos[0]-300), 0, abs(pos[0]-300) + 30,100)))
                else:
                    image_upper_lst.append(img.crop((abs(pos[0] - 300), 100, abs(pos[0] - 300) + 30, 200)))
            else:
                if pos[1] == 0:

                    image_down_lst.append(img.crop((abs(pos[0]-300), 0, abs(pos[0]-300) + 30,100)))
                else:
                    image_down_lst.append(img.crop((abs(pos[0] - 300), 100, abs(pos[0] - 300) + 30, 200)))
        elif pos[0]<=-300:

            if count <= 10:
                if pos[1] == 0:

                    image_upper_lst.append(img.crop((abs(pos[0]+300), 0, abs(pos[0]+300) + 30,100)))
                else:
                    image_upper_lst.append(img.crop((abs(pos[0] +300), 100, abs(pos[0]+ 300) + 30, 200)))
            else:
                if pos[1] == 0:

                    image_down_lst.append(img.crop((abs(pos[0] + 300), 0, abs(pos[0] + 300) + 30, 100)))
                else:
                    image_down_lst.append(img.crop((abs(pos[0] + 300), 100, abs(pos[0] + 300) + 30, 200)))
        else:
            if count <= 10:
                if pos[1] == 0:

                    image_upper_lst.append(img.crop((abs(pos[0] ), 0, abs(pos[0] ) + 30, 100)))
                else:
                    image_upper_lst.append(img.crop((abs(pos[0] ), 100, abs(pos[0] ) + 30, 200)))
            else:
                if pos[1] == 0:

                    image_down_lst.append(img.crop((abs(pos[0] ), 0, abs(pos[0] ) + 30, 100)))
                else:
                    image_down_lst.append(img.crop((abs(pos[0] ), 100, abs(pos[0] ) + 30, 200)))
        count+=1
    x_offset = 0
    # 创建一张画布,x_offset主要为新画布使用
    new_img = Image.new("RGB", (300, 200))
    for img in image_upper_lst:
        new_img.paste(img, (x_offset, 0))
        x_offset += 30

    x_offset = 0
    for img in image_down_lst:
        new_img.paste(img, (x_offset, 100))
        x_offset += 30

    return new_img

找到滑块的位置

滚动条是无法直接用定位工具来定位的。selenium里面也没有直接的方法去控制滚动条,
这时候只能借助JS了,还好selenium提供了一个操作js的方法:
execute_script(),可以直接执行js的脚本。

self.driver.execute_script(
    "var x=document.getElementsByClassName('xy_img_bord')[0];"
    "x.style.display='block';"
)

接下来就是获得滑块图片

cut_fullimage_url = self.get_move_url('//div[@class="xy_img_bord"]')
resq = base64.b64decode(cut_fullimage_url[22::])

file1 = open('2.jpg', 'wb')
file1.write(resq)
file1.close()
def get_move_url(self, xpath):
    #得到滑块的位置
    link = re.compile(
        'background-image: url\("(.*?)"\); ')  # 严格按照格式包括空格
    elements = self.driver.find_elements_by_xpath(xpath)
    image_url = None

    for element in elements:
        style = element.get_attribute("style")
        groups = link.search(style)
        url = groups[1]
        image_url = url
    return image_url

匹配滑块图片和背景图片,找到滑块的位置

res[3][0]是最佳匹配的横坐标

res=self.FindPic("cut.jpg", "2.jpg")

res=res[3][0]#x坐标
def FindPic(self,target, template):
    """
    找出图像中最佳匹配位置
    :param target: 目标即背景图
    :param template: 模板即需要找到的图
    :return: 返回最佳匹配及其最差匹配和对应的坐标
    """
    target_rgb = cv2.imread(target)
    target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
    template_rgb = cv2.imread(template, 0)
    res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
    value = cv2.minMaxLoc(res)
    return value

移动滑块

主要运用ActionChains方法

self.start_move(res)
def start_move(self, distance):
    element = self.driver.find_element_by_xpath('//div[@class="handler handler_bg"]')
    # 按下鼠标左键
    ActionChains(self.driver).click_and_hold(element).perform()
    time.sleep(0.5)
    while distance > 0:
        if distance > 10:
            # 如果距离大于10,就让他移动快一点
            span = random.randint(10,15)
        else:
            # 快到缺口了,就移动慢一点
            span = random.randint(2, 3)
        ActionChains(self.driver).move_by_offset(span, 0).perform()
        distance -= span
        time.sleep(random.randint(10, 50) / 100)

    ActionChains(self.driver).move_by_offset(distance, 1).perform()
    ActionChains(self.driver).release(on_element=element).perform()

以上就是全部过程,下面是完整代码:

完整代码

# -*- coding: utf-8 -*-
import aircv as ac
import random
import six
import os,base64
import time, re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import requests
import io
from io import BytesIO
import cv2
class qjh(object):
    def __init__(self):
        chrome_option = webdriver.ChromeOptions()
        self.driver = webdriver.Chrome(executable_path=r"F:\下载\chromedriver.exe", chrome_options=chrome_option)
        self.driver.set_window_size(1440, 900)
    def core(self):

        url="http://www.pkulaw.cn/cluster_form.aspx?Db=chl&menu_item=law&EncodingName=&clust_param=0/XC02&keyword=&range=name&"
        self.driver.get(url)
        input = WebDriverWait(self.driver,10) .until(
        EC.presence_of_element_located((By.XPATH,'//*[@id="gopage1"]'))
        )
        input.send_keys('21')

        button = WebDriverWait(self.driver,10).until(EC.element_to_be_clickable ((By.XPATH,'//a[@class="anniu-page"]')))

        button.click()
        time.sleep(5)#等待加载
        cut_image_url, cut_location = self.get_image_url('//div[@class="cut_bg"]')
        cut_image = self.mosaic_image(cut_image_url, cut_location)
        cut_image.save("cut.jpg")

        self.driver.execute_script(
            "var x=document.getElementsByClassName('xy_img_bord')[0];"
            "x.style.display='block';"
        )
        cut_fullimage_url = self.get_move_url('//div[@class="xy_img_bord"]')
        resq = base64.b64decode(cut_fullimage_url[22::])

        file1 = open('2.jpg', 'wb')
        file1.write(resq)
        file1.close()


        res=self.FindPic("cut.jpg", "2.jpg")

        res=res[3][0]#x坐标

        self.start_move(res)





    def FindPic(self,target, template):
        """
        找出图像中最佳匹配位置
        :param target: 目标即背景图
        :param template: 模板即需要找到的图
        :return: 返回最佳匹配及其最差匹配和对应的坐标
        """
        target_rgb = cv2.imread(target)
        target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
        template_rgb = cv2.imread(template, 0)
        res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
        value = cv2.minMaxLoc(res)
        return value


    def get_move_url(self, xpath):
        #得到滑块的位置
        link = re.compile(
            'background-image: url\("(.*?)"\); ')  # 严格按照格式包括空格
        elements = self.driver.find_elements_by_xpath(xpath)
        image_url = None

        for element in elements:
            style = element.get_attribute("style")
            groups = link.search(style)
            url = groups[1]
            image_url = url
        return image_url


    def get_image_url(self, xpath):
        #得到背景图片
        link = re.compile('background-image: url\("(.*?)"\); width: 30px; height: 100px; background-position: (.*?)px (.*?)px;')#严格按照格式包括空格
        elements = self.driver.find_elements_by_xpath(xpath)
        image_url = None
        location = list()
        for element in elements:

            style = element.get_attribute("style")
            groups = link.search(style)

            url = groups[1]
            x_pos = groups[2]
            y_pos = groups[3]

            location.append((int(x_pos), int(y_pos)))
            image_url = url
        return image_url, location

    # 拼接图片
    def mosaic_image(self, image_url, location):

        resq = base64.b64decode(image_url[22::])
        file1 = open('1.jpg', 'wb')
        file1.write(resq)
        file1.close()
        with open("1.jpg", 'rb') as f:
            imageBin = f.read()
        # 很多时候,数据读写不一定是文件,也可以在内存中读写。
        # BytesIO实现了在内存中读写bytes,创建一个BytesIO,然后写入一些bytes:
        buf = io.BytesIO(imageBin)
        img = Image.open(buf)
        image_upper_lst = []
        image_down_lst = []
        count=1
        for pos in location:
            if pos[0]>0:
                if count <= 10:
                    if pos[1] == 0:

                        image_upper_lst.append(img.crop((abs(pos[0]-300), 0, abs(pos[0]-300) + 30,100)))
                    else:
                        image_upper_lst.append(img.crop((abs(pos[0] - 300), 100, abs(pos[0] - 300) + 30, 200)))
                else:
                    if pos[1] == 0:

                        image_down_lst.append(img.crop((abs(pos[0]-300), 0, abs(pos[0]-300) + 30,100)))
                    else:
                        image_down_lst.append(img.crop((abs(pos[0] - 300), 100, abs(pos[0] - 300) + 30, 200)))
            elif pos[0]<=-300:

                if count <= 10:
                    if pos[1] == 0:

                        image_upper_lst.append(img.crop((abs(pos[0]+300), 0, abs(pos[0]+300) + 30,100)))
                    else:
                        image_upper_lst.append(img.crop((abs(pos[0] +300), 100, abs(pos[0]+ 300) + 30, 200)))
                else:
                    if pos[1] == 0:

                        image_down_lst.append(img.crop((abs(pos[0] + 300), 0, abs(pos[0] + 300) + 30, 100)))
                    else:
                        image_down_lst.append(img.crop((abs(pos[0] + 300), 100, abs(pos[0] + 300) + 30, 200)))
            else:
                if count <= 10:
                    if pos[1] == 0:

                        image_upper_lst.append(img.crop((abs(pos[0] ), 0, abs(pos[0] ) + 30, 100)))
                    else:
                        image_upper_lst.append(img.crop((abs(pos[0] ), 100, abs(pos[0] ) + 30, 200)))
                else:
                    if pos[1] == 0:

                        image_down_lst.append(img.crop((abs(pos[0] ), 0, abs(pos[0] ) + 30, 100)))
                    else:
                        image_down_lst.append(img.crop((abs(pos[0] ), 100, abs(pos[0] ) + 30, 200)))
            count+=1
        x_offset = 0
        # 创建一张画布,x_offset主要为新画布使用
        new_img = Image.new("RGB", (300, 200))
        for img in image_upper_lst:
            new_img.paste(img, (x_offset, 0))
            x_offset += 30

        x_offset = 0
        for img in image_down_lst:
            new_img.paste(img, (x_offset, 100))
            x_offset += 30

        return new_img
    def start_move(self, distance):
        element = self.driver.find_element_by_xpath('//div[@class="handler handler_bg"]')
        # 按下鼠标左键
        ActionChains(self.driver).click_and_hold(element).perform()
        time.sleep(0.5)
        while distance > 0:
            if distance > 10:
                # 如果距离大于10,就让他移动快一点
                span = random.randint(10,15)
            else:
                # 快到缺口了,就移动慢一点
                span = random.randint(2, 3)
            ActionChains(self.driver).move_by_offset(span, 0).perform()
            distance -= span
            time.sleep(random.randint(10, 50) / 100)

        ActionChains(self.driver).move_by_offset(distance, 1).perform()
        ActionChains(self.driver).release(on_element=element).perform()
if __name__ == "__main__":
    h = qjh()
    h.core()






结果

最后得出的结果我也是没想到的,识别率非常之高,而且几乎没有一点偏移,算是第一次破解验证码的成功经历吧,可能还是滑块验证码比较简单,以后有机会还会破解其他形式的验证码。

全部评论

相关推荐

10-24 13:36
门头沟学院 Java
Zzzzoooo:更新:今天下午有hr联系我去不去客户端,拒了
点赞 评论 收藏
分享
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务