验证码识别(Python)

图形验证码识别

        图形验证码最早出现,也很常见,一般由4个字母或者数字组成。例如,中国知网注册页面采用的就是图形验证码,链接为http://my.cnki.net/elibregister/commonRegister.aspx,页面如下:

验证码识别(Python)_第1张图片

该注册页面表单最后一项就是图形验证码,必须正确输入图形验证码才可以完成注册。该图形验证码其实是一张图片,上面是经过变形的4个大写字母,并加入了一些干扰,可以点击此验证码图片和“换一张”更新验证码。 先下载两张图形验证码,如图:

 环境准备:

(1)Python 3.6.5;

(2)pillow,tesserocr;

(3)Tesseract-OCR:https://digi.bib.uni-mannheim.de/tesseract/,tesseract-ocr-w64-setup-v4.0.0.20181030.exe,安装时选择所有的语言包,tessdata目录拷贝到Python主目录。

先来识别第一张“W875”的图形验证码,程序很简单,如下:

import tesserocr
from PIL import Image as Img

image = Img.open('images/W875.jpg')
result = tesserocr.image_to_text(image)
print(result)

程序运行结果:

验证码识别(Python)_第2张图片

结果很正确,两行代码搞定!再使用该方法继续识别第二张验证码,程序类似,如下:

import tesserocr
from PIL import Image as Img

image = Img.open('images/PRUT.jpg')
result = tesserocr.image_to_text(image)
print(result)

程序运行结果:

验证码识别(Python)_第3张图片

程序运行结果比验证码多个一些字符。 识别存在错误也是正常的,从来没有百分之百的识别,只有接近百分之百的识别。识别错误的原因就是图片周围有一些干扰,下面我们去除干扰后进行识别。彩色验证码一般是由RGB三个值构成的,需要先将验证码转换成灰度图片(0~255),然后再二值化转换成黑白图片(0和1)。程序如下:

import tesserocr
from PIL import Image as Img

image = Img.open('images/PRUT.jpg')
image = image.convert('L')
threshold = 120
table = [0] * threshold + [1] * (256 - threshold)
image = image.point(table, '1')
image.save('images/BinarizedPRUT.jpg')
result = tesserocr.image_to_text(image)
print(result)

经过灰度化处理后的验证码,如下:

可以看到背景干扰已经处理掉,图片有些失真,但不影响识别效果,程序运行结果:

验证码识别(Python)_第4张图片


极验滑动验证码识别

        极验验证码官网链接https://www.geetest.com/,它是全球交互安全创领者,专注于提供验证安全的企业。主要验证方式是拖动滑块拼合图像。若图像完全拼合,则验证成功,否则需要重新验证。大家平时上网也经常会遇到。我们以国家企业信用信息公示系统为例,链接:http://www.sgs.gov.cn/notice/。打开页面,如图:

验证码识别(Python)_第5张图片

 说白了,计算机其实是模拟人的上网行为,模拟的越像越智能。下面来看,我们是如何来查询企业信用的,分几个步骤:

(1)打开浏览器,输入网址http://www.sgs.gov.cn/notice/,打开国家企业信用信息公示系统;

(2)再输入框中输入企业名称,比如“中国长城工业上海有限公司”,如图:

验证码识别(Python)_第6张图片

(3)企业名称输入没问题后,点击查询按钮,如图:

验证码识别(Python)_第7张图片

此时就出现了极验验证码指示图,这里还不是真正的极验验证码,它其实是极验验证码操作指导。看到图片上的小手指向滑块,忍不住会用鼠标点击滑块,触碰后,如图:

验证码识别(Python)_第8张图片

图片立即出现了一个缺口,缺口图片在缺口正左方。

(4)向右拖动滑块,慢慢与缺口重合,完成验证码拼合,成功拼合后,如图:

验证码识别(Python)_第9张图片

这里我猜你拖动滑块的速度至少经历了三个阶段,刚刚点击滑块时,先是慢慢移动,然后会快速移动,快到缺口附近时,你又降下速度来慢慢移动。在程序中,我们会模拟移动速度变化,有两个方案,第一个,随机移动,以中间为界先快后慢,第二个,加减速移动,使用物理加速度构建加速和减速模型。后面我们采用第一种方法。

(5)点击第一条查询结果,打开“中国长城工业上海有限公司”企业信用,如图:

验证码识别(Python)_第10张图片

在银行贷款等业务中,企业信用信息是参考因素之一,如果每个企业来办理贷款,都要去查询势必繁琐,而且效率低下,如果能将这些企业信息采集下来保存,并用于构建企业立体画像,然后再用于贷款是非常方便的。下面我们来用程序模拟人的整个查询流程。

环境准备:

(1)Python 3.6.5;

(2)pillow,selenium;

(3)chromedriver:https://chromedriver.storage.googleapis.com/index.html,版本:70.0.3538.16,chromedriver.exe需要拷贝到Python/Scripts目录下;

(4)Chrome:版本 71.0.3578.98(正式版本) (32 位)。

完整程序如下:

import random
import time
import 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
from io import BytesIO


class CrawlCoder(object):
    def __init__(self):
        self.browser = webdriver.Chrome()
        self.browser.set_window_size(1366, 768)
        self.wait = WebDriverWait(self.browser, 10, 0.5)

    def query(self):
        """根据输入的企业名称查询企业信用信息"""

        # 访问页面
        url = 'http://www.sgs.gov.cn/notice/'
        self.browser.get(url)
        # 等待查询输入框出现
        keyword_id = 'keyword'
        self.wait.until(EC.element_to_be_clickable((By.ID, keyword_id)))
        # 根据ID找到查询输入框,输入查询企业名称
        keyword_content = '中国长城工业上海有限公司'
        self.browser.find_element_by_id(keyword_id).send_keys(keyword_content)
        # 等待查询按钮出现
        button_id = 'buttonSearch'
        self.wait.until(EC.element_to_be_clickable((By.ID, button_id)))
        # 根据ID找到查询按钮,单击查询(此时等待验证码的滑块出现……)
        button = self.browser.find_element_by_id(button_id)
        button.click()

    def verify(self):
        """验证极验验证码"""
        
        self.query()
        # 等待滑块按钮出现
        slider_button_xpath = '//div[@class="gt_slider_knob gt_show"]'
        self.wait.until(EC.element_to_be_clickable((By.XPATH, slider_button_xpath)))
        # 根据xpath获取到滑块按钮,将鼠标移动到滑块按钮,等待1s
        # 鼠标触碰滑块才会出现有缺口的验证码
        slider_button = self.browser.find_element_by_xpath(slider_button_xpath)
        ActionChains(self.browser).move_to_element(slider_button).perform()
        time.sleep(1)

        # 根据切片坐标拼接图片
        cut_xpath = '//div[@class="gt_cut_bg_slice"]'
        full_xpath = '//div[@class="gt_cut_fullbg_slice"]'
        cut_image = self.get_code_image(cut_xpath, name='cut.jpg')
        full_image = self.get_code_image(full_xpath, name='full.jpg')

        # 根据两个图片计算偏移量
        distance = self.get_offset(cut_image, full_image)

        # 开始移动
        self.move(slider_button, distance)

        # 根据移动结果,进行重试
        self.retry()

    def retry(self):
        """重试策略"""

        try:
            error_xpath = '//div[@class="gt_ajax_tip gt_error"]'
            self.wait.until(EC.element_to_be_clickable((By.XPATH, error_xpath)))
            print("验证失败")
            return
        except TimeoutException as e:
            print(e)

        # 判断是否验证成功
        s = self.browser.find_elements_by_xpath('//*[@id="wrap1"]/div[3]/div/div/p')
        if len(s) == 0:
            print("滑动解锁失败,继续尝试")
            self.verify()
        else:
            print("滑动解锁成功")
            time.sleep(1)
            # 打开查询结果
            page_xpath = '//*[@id="wrap1"]/div[3]/div/div/div[2]'
            ss = self.browser.find_element_by_xpath(page_xpath).get_attribute("onclick")
            self.browser.find_element_by_xpath(page_xpath).click()
            print(ss)

    def get_code_image(self, xpath, name):
        """获取验证码图片

        :return 验证码图片
        :params
            xpath 验证码xpath
            name 验证码图片保存名称
        """
        # 获取验证码图片切片url和位置坐标列表
        # 每个切片url相同
        link = re.compile('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;')
        image_url = None
        location = []
        elements = self.browser.find_elements_by_xpath(xpath)
        for element in elements:
            style = element.get_attribute("style")
            groups = link.search(style)
            image_url = groups[1]
            x_pos = groups[2]
            y_pos = groups[3]
            location.append((int(x_pos), int(y_pos)))

        # 拼接图片
        response = requests.get(image_url)
        file = BytesIO(response.content)
        img = Image.open(file)
        # 创建一张画布,x_offset主要为新画布使用
        upper_offset = 0
        down_offset = 0
        canvas = Image.new("RGB", (img.width, img.height))
        for pos in location:
            x = pos[0]
            y = pos[1]
            if y == 0:
                # y值==0的图片属于上半部分,高度58
                image_crop = img.crop((abs(x), 0, abs(x) + 10, 58))
                canvas.paste(image_crop, (upper_offset, 58))
                upper_offset += image_crop.width
            if y == -58:
                # y值==-58的图片属于下半部分
                image_crop = img.crop((abs(x), 58, abs(x) + 10, img.height))
                canvas.paste(image_crop, (down_offset, 0))
                down_offset += image_crop.width
        canvas.save('images/'+name)
        return canvas

    # 计算距离
    @staticmethod
    def get_offset(cut_image, full_image):
        """计算缺口偏移量
        :return x 偏移量
        """
        for x in range(cut_image.width):
            for y in range(cut_image.height):
                cpx = cut_image.getpixel((x, y))
                fpx = full_image.getpixel((x, y))
                for i in range(3):
                    if abs(cpx[i] - fpx[i]) > 50:
                        img = cut_image.crop((x, y, x + 50, y + 40))
                        # 保存一下计算出来位置图片,看看是不是缺口部分
                        img.save("gap.jpg")
                        return x

    # 开始移动
    def move(self, slider_button, distance):
        """移动滑块"""
        # 这里就是根据移动进行调试,计算出来的位置不是百分百正确的,加上一点偏移
        distance -= slider_button.size.get('width') / 2
        distance += 15

        # 按下鼠标左键
        ActionChains(self.browser).click_and_hold(slider_button).perform()
        time.sleep(0.5)
        while distance > 0:
            if distance > 10:
                # 如果距离大于10,就让他移动快一点
                span = random.randint(5, 8)
            else:
                # 快到缺口了,就移动慢一点
                span = random.randint(2, 3)
            ActionChains(self.browser).move_by_offset(span, 0).perform()
            distance -= span
            time.sleep(random.randint(10, 50) / 100)

        ActionChains(self.browser).move_by_offset(distance, 0).perform()
        ActionChains(self.browser).release(on_element=slider_button).perform()


if __name__ == "__main__":
    coder = CrawlCoder()
    coder.verify()

按照上面我们分解的操作步骤不难理解程序,为了更好的理解程序,加入了很多单行注释,在Python编码规范中约定:单行注释尽量少用,主要是影响代码阅读速度;类的每个方法必须要注释,写明方法参数,返回值等。本程序比较简单,这里就省略了。坚信:程序被阅读的次数总会比书写的次数要多。程序运行过程,如图:

验证码识别(Python)_第11张图片

注意鼠标没动哦(在右上角)。整个过程自动完成。破解率接近100%。

 

 

你可能感兴趣的:(Python)