图形验证码的识别

目标

以知网的验证码为例,讲解利用 OCR 技术识别图形验证码的方法。

准备工作

识别图形验证码需要库 tesserocr。

 

Linux下的安装:

• Ubuntu、 Debian 和 Deepin

在 Ubuntu 、Debian 和 Deepin 系统下,安装命令如下:

sudo apt-get install -y tesseract-ocr libtesseract-dev libleptonica-dev

• CentOS 、 Red Hat

在 CentOS 和 Red Hat 系统下 ,安装命令如下 :

yum install -y tesseract

Ubuntu、Debian、Deepin {#ubuntu、debian、deepin}

git clone https://github.com/tesseract-ocr/tessdata.git

sudo mv tessdata/* /usr/share/tesseract-ocr/tessdata

CentOS、RedHat

git clone https://github.com/tesseract-ocr/tessdata.git

sudo mv tessdata/* /usr/share/tesseract/tessdata

这样就可以将下载下来的语言包全部安装了。

这时我们重新运行列出所有语言的命令:

tesseract --list-langs

接下来pip安装tesserocr

pip3 install tesserocr pillow

步骤

1.获取验证码

打开这个链接 http://my.cnki.net/elibregister/CheckCode.aspx ,就可以看到一个验证码,右键保存即可,将其命名为 code.jpg。

2.识别测试

import tesserocr

from PIL import Image

image = Image.open('/home/yufeng/Desktop/spider/images/code.jpg')

result = tesserocr.image_to_text(image)

print(result)

通过上面的代码结果显示即为验证码。

另外,tesserocr 还有一个更加简单的方法,这个方法可直接将图片文件转为字符串,代码如下所示 :

import tesserocr

print(tesserocr.file_to_text('image.png'))

不过, 此种方法的识别效果不如上一种方法好。

3.验证码处理

在有偏差的情况下,可以对图片进行转灰度,二值化等操作:

import tesserocr

from PIL import Image

image = Image.open('/home/yufeng/Desktop/spider/images/code.jpg')

image = image.convert('L')#传入参数L可以将图像转化为灰度图像

threshold = 127 #指定二值化阙值,默认阙值为127

table = []

for i in range(256):

    if i < threshold:

        table.append(0)

    else:

        table.append(1)



image = image.point(table, '1')#传入参数1将图片进行二值化处理

image.show()



result = tesserocr.image_to_text(image)

print(result)

 

极验滑动验证码的识别

目标

用程序来识别并通过极验验证码的验证,包括分析识别思路、识别缺 口位置、生成

滑块拖动路径、模拟实现滑块拼合通过验证等步骤。

准备工作

安装Selenium库,Chrome 浏览器,并配置 ChromeDriver。

步骤

1.了解极验验证码

极验验证码官网为: http://www.geetest.com/。 它是一个专注于提供验证安全的系统,主要验证方式是拖动滑块拼合图像 。 若图像完全拼合, 则验证成功,即表单成功提交 , 否则需要重新验证。

2.识别思路

 

首先,模拟点击验证按钮,然后识别活动缺口的位置,最后,模拟拖动滑块。

第一步,我们可以直接利用selienium模拟点击按钮。

第二步,需要用到图像的相关处理方法。实现一个边缘检测算法来找出缺口的位置,而对于这种极验验证码,我们可以利用和原图对比检测的方式来识别缺口的位置,因为在没有滑动滑块之前,缺口并没有呈现。我们可以同时获取两张图片。设定一个 对比阈值,然后遍历两张图片,找出相同位置像素RGB差距超过此阈值的像素点,那么此像素点的位置就是缺口的位置。

第三步,其中的坑比较多。极验验证码增加了机器轨迹识别,匀速运动,随机速度等方法都不能通过验证,只有完全模拟人的移动轨迹才可以通过验证。人的运动轨迹一般是先急加速再减速,我们需要模拟这个过程才能成功。

3.实现

有了思路,我们就可以开始用程序来实现它了。大的方面,主要包括这几个步骤。第一步,初始化,在这里我们先初始化 一些selenium的 配置及一些参数的配置。第二步,就是模拟点击了,这里主要是利用selenium模块模拟浏览器对网页进行操作。

第三步,就该识别缺口的位置了。首先获取前后两张图片,得到其所在位置和宽高,然后获取整个网页的截图,图片裁切下来即可。最后一步,模拟拖动,经过多次试验,得出一个结论,那就是完全模拟加速减速的过程通过了验证。前段作匀加速,后段作匀减速运动,利用物理学的加速度公式即可完成验证。

 

'''

    极验验证码特点:首先点击按钮进行智能验证,如果验证不通过,则会弹出滑动验证的窗口,拖动滑块拼合图像进行验证,之后生成三个加密

参数,通过表单提交到后台,后台还会进行一次验证。

识别验证需要三步:

1.模拟点击验证按钮

2.识别滑动缺口的位置

3.模拟拖动滑块

import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

EMAIL = 'test@qq.com'   #此处填写自己的账号
PASSWORD = ''           #此处填写自己的密码
BORDER = 6
INIT_LEFT = 60


class CrackGeetest():
    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.email = EMAIL
        self.password = PASSWORD
    
    def __del__(self):
        self.browser.close()
    
    def get_geetest_button(self):
        """
        获取初始验证按钮
        :return:
        """
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button
    
    def get_position(self):
        """
        获取验证码位置
        :return: 验证码位置元组
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)
    
    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot
    
    def get_slider(self):
        """
        获取滑块
        :return: 滑块对象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider
    
    def get_geetest_image(self, name='captcha.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha
    
    def open(self):
        """
        打开网页输入用户名密码
        :return: None
        """
        self.browser.get(self.url)
        email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
        email.send_keys(self.email)
        password.send_keys(self.password)
    
    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        left = 60
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left
    
    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False
    
    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0
        
        while current < distance:
            if current < mid:
                # 加速度为正2
                a = 2
            else:
                # 加速度为负3
                a = -3
            # 初速度v0
            v0 = v
            # 当前速度v = v0 + at
            v = v0 + a * t
            # 移动距离x = v0t + 1/2 * a * t^2
            move = v0 * t + 1 / 2 * a * t * t
            # 当前位移
            current += move
            # 加入轨迹
            track.append(round(move))
        return track
    
    def move_to_gap(self, slider, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()
    
    def login(self):
        """
        登录
        :return: None
        """
        submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
        submit.click()
        time.sleep(10)
        print('登录成功')
    
    def crack(self):
        # 输入用户名密码
        self.open()
        # 点击验证按钮
        button = self.get_geetest_button()
        button.click()
        # 获取验证码图片
        image1 = self.get_geetest_image('captcha1.png')
        # 点按呼出缺口
        slider = self.get_slider()
        slider.click()
        # 获取带缺口的验证码图片
        image2 = self.get_geetest_image('captcha2.png')
        # 获取缺口位置
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)
        # 减去缺口位移
        gap -= BORDER
        # 获取移动轨迹
        track = self.get_track(gap)
        print('滑动轨迹', track)
        # 拖动滑块
        self.move_to_gap(slider, track)
        
        success = self.wait.until(
            EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
        print(success)
        
        # 失败后重试
        if not success:
            self.crack()
        else:
            self.login()


if __name__ == '__main__':
    crack = CrackGeetest()
    crack.crack()

问题【识别未成功】:

【网络爬虫】验证码识别(图形验证码识别和极验验证码识别)【问题待解决】_网络爬虫