爬虫之验证码处理

验证码处理

一、 字符验证码

通过某个程序,计算机产生一个字符串,一般四位,包含数字、字母、中文

1、 难点

  1. 噪点
  2. 干扰线
  3. 重叠
  4. 颜色
  5. 变形

经过这么一些的操作之后,程序会生成一张图片,而我们要做的就是输入和图片里面的文本信息一致,才算通过校验

2、 图像处理

在数字世界中,有色彩模式这一算法,来表示各种颜色

比较常见的有RGB模式,HSL模式等

基础知识

  1. RGB通道

    一张彩色图片,包含着R、G、B三个通道,每一个通道用(0, 255)来表示,越接近0,表示该通道的色彩越淡

  2. 像素

    像素是图形单元的 简称,它是位图最小的完整单位。像素具有两种属性:一种是相对于位图图像中的其他像素来说,一个像素具有一个特定的位置;另一种是具有可以用位来度量的颜色深度

  3. 图像分辨率

    图像分辨率通常是指每英寸中像素的个数,用ppi表示。图像分辨率取决于显示图像的大小。分辨率与图像清晰程度成正比。分辨率越高,图像越清晰,当然产生的图形图像文件越大,在图形处理是所需计算机的内存较多,同时CPU处理的时间也就越长

使用PIllow库进行图像处理

3、 实例代码

识别古诗文网的图形验证码:https://so.gushiwen.cn/RandCode.ashx

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @author: A.L.Kun
# @file : demo01.py
# @time : 2022/4/25 15:22
from requests import get
from PIL import Image
import pytesser3  # 注意,要安装tesseract
from io import BytesIO  # 内存IO,将数据写入到内存中去,不会产生文件

img_url = "https://so.gushiwen.cn/RandCode.ashx"  # 生成验证码的网址
img_data = BytesIO(get(img_url).content)  # 里面传入二进制数据
img = Image.open(img_data)  # 打开验证码图片
img = img.convert("L")  # 将图片转换为灰度图
# 获取阈值阈值可以通过数组来获取,这里默认方便,默认阈值为175
avg = 175
# 二值化处理
w, h = img.size  # 获取图片的大小
pixes = img.load()  # 获取照片的像素
for x in range(w):
    for y in range(h):
        if pixes[x, y] < avg:
            pixes[x, y] = 0
        else:
            pixes[x, y] = 255
# 去除噪点
for x in range(1, w - 1):
    for y in range(1, h - 1):
        count = 0  # 统计周边摆设像素的个数
        if pixes[x, y - 1] > 245:  # 如果上面的像素的颜色为白色
            count += 1
        if pixes[x, y + 1] > 245:  # 如果下面的像素的颜色白色
            count += 1
        if pixes[x - 1, y] > 245:  # 如果左边的像素的颜色为白色
            count += 1
        if pixes[x + 1, y] > 245:  # 如果右边的像素的颜色为白色
            count += 1
        if pixes[x - 1, y - 1] > 245:  # 如果左上面的像素的颜色为白色
            count += 1
        if pixes[x - 1, y + 1] > 245:  # 如果左下面的像素的颜色为白色
            count += 1
        if pixes[x - 1, y + 1] > 245:  # 如果右下边的像素的颜色为白色
            count += 1
        if pixes[x + 1, y - 1] > 245:  # 如果右上边的像素的颜色为白色
            count += 1
        if count > 5:
            pixes[x, y] = 255  # 如果有超过5个像素点的颜色为白色,说明很可能是一个噪点

print(pytesser3.image_to_string(img))  # 使用该方法,将图片内容转换为文字
img.show()

注意,如果使用scrapy请求图片数据,通过response.body来获取图片的二进制数据

二、 滑块验证码

通过滑动图片进行拼图进行验证,可以通过selenium来实现

1、 难点

  1. 精灵图:

    CSS Sprites,CSS精灵图

    所谓精灵图就是把好多小的图片合并一张大图,以该大图的元素为背景图片的元素尺寸比较小,可以通过控制背景图片的位置,让元素上显示合适的图像

2、 实现示例

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @author: A.L.Kun
# @file : demo02.py
# @time : 2022/4/25 19:59
import re
from io import BytesIO
from PIL import Image
from requests_html import HTMLSession
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait  # 显性等待
from selenium.webdriver.support import expected_conditions as ec  # 提供预期判断的方法,指定等待元素
from selenium.webdriver.common.action_chains import ActionChains  # 导入动作链


def get_img(web, div_class):
    """获取素材图,以及每个div的前景图坐标"""
    imgs = web.find_elements(By.CLASS_NAME, div_class)  # 得到存放图片的标签
    img_url = None
    pos_lis = []
    for img in imgs:
        # 拿到每一个图片元素
        # print(img.get_attribute("style"))
        img_info = {}  # 每一张图片具体的坐标
        img_ = re.findall(r'background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;',
                          img.get_attribute("style"))  # 匹配到图片和每个图片的偏移量
        # 元组拆包
        img_url, img_info['x'], img_info['y'] = img_[0]
        # 将坐标转换为整型
        img_info["x"], img_info['y'] = int(img_info["x"]), int(img_info['y'])
        pos_lis.append(img_info)

    # 抓取素材图片
    # print(img_url, pos_lis)
    session = HTMLSession()
    resp = session.get(url=img_url).content
    img = BytesIO(resp)
    img = Image.open(img)
    # img.show()
    return img, pos_lis  # 返回图片和每个精灵图的偏移量


def get_merge_image(img_, pos_lis):
    """剪裁并拼接图片"""
    pos_lis_upper = []  # 存储上部坐标信息
    pos_lis_lower = []  # 存储下部坐标信息
    """
    我们是通过pos_lis,把每一张图片剪裁下来,每一张图片,宽10高58
    得到第一张图片之后,我们让第二张图片在 x+10,y + 58 的位置,进行拼接
    """
    # 遍历每一个坐标点
    for pos in pos_lis:
        # 如果是上部坐标
        img_c = img_.crop(((abs(pos["x"])),
                           (abs(pos["y"])),
                           (abs(pos["x"]) + 10),
                           (abs(pos["y"]) + 58)
                           ))  # 对图片进行裁剪
        # img_c.show()
        # break
        if pos["y"] == -58:
            pos_lis_upper.append(img_c)
        else:
            pos_lis_lower.append(img_c)
    # 拼接图片,粘贴图片
    new_img = Image.new("RGB", (260, 116))  # 创建一个画布
    x_offset = 0  # 图片从原点进行编辑
    for i in range(len(pos_lis_upper)):
        new_img.paste(pos_lis_upper[i], (x_offset, 0))  # 上边的依次从左到右进行粘贴
        new_img.paste(pos_lis_lower[i], (x_offset, 58))  # 下边的依次从左到右进行粘贴
        x_offset += 10  # 图片的宽度为10
    new_img.show()
    return new_img


def is_similar(img_full, img_new, x, y):
    """判断两张图片是否相等,x,y 是指定点的坐标,使用对比像素"""
    full_pix = img_full.getpixel((x, y))  # 获取完整图片的像素
    new_pix = img_new.getpixel((x, y))  # 获取切口图片的像素
    # 遍历颜色通道
    for i in range(0, 3):
        if abs(full_pix[i] - new_pix[i]) >= 50:
            # 如果其中一个的颜色通道的值相差超过50
            return False
    return True


def get_diff_location(img_full, img_new):
    """比较图片区域"""
    for x in range(1, 259):
        for y in range(1, 115):
            if not is_similar(img_full, img_new, x, y):
                # 两个像素点的颜色不一致
                return x


if __name__ == '__main__':
    web = webdriver.Chrome()
    web.maximize_window()  # 窗口最大化
    web.get("http://www.cnbaowen.net/api/geetest/")
    # 等待滑块加载完再 开始获取图片
    get_knob_btn = WebDriverWait(web, 10).until(ec.element_to_be_clickable((By.CLASS_NAME, "gt_slider_knob")))  # 同时获取到该元素
    # 获取完整图片
    # 获取图片以及坐标
    img, pos_lis = get_img(web, "gt_cut_fullbg_slice")
    # 拼接图片
    img_full = get_merge_image(img, pos_lis)

    # 获取缺口图片
    # 获取图片及坐标
    img, pos_lis = get_img(web, "gt_cut_bg_slice")
    # 拼接图片
    img_new = get_merge_image(img, pos_lis)

    x = get_diff_location(img_full, img_new)  # 获取缺口左上角的坐标
    # 创建动作链
    action_chain = ActionChains(web)
    action_chain.click_and_hold(get_knob_btn)  # 点击并按住
    action_chain.pause(0.2)  # 停顿0.2秒
    action_chain.move_by_offset(x - 10, 0)  # 快到缺口的时候,停顿0.6秒
    action_chain.pause(0.6)
    action_chain.move_by_offset(10, 0)  # 移动到缺口处
    action_chain.release(get_knob_btn)  # 到达位置后松开
    action_chain.perform()  # 执行动作

三、 点触验证码

给定一组文字,给定一张照片,按照文字顺序点击图片的文字,来完成验证码

1、 问题

  1. 颜色不一致
  2. 字体镂空
  3. 背景图片干扰大

2、 解决方案

  1. 人工识别

    通过sleep,手动点击(成功率较高)

  2. 机器学习

    • 数学学得好
    • 数据分析、机器学习算法、神经网络
    • 海量的数据集
  3. 第三方工具

    这里我们选择超级鹰

3、 使用案例

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @author: A.L.Kun
# @file : demo03.py
# @time : 2022/4/26 9:00
import time

from PIL import Image
from chaojiying import Chaojiying_Client
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains

# 获取超级鹰的账户信息
username = input("请输入用户名:")
pwd = input("请输入密码:")
id = input("软件id:")
# 传入用户名,密码,软件id
chaojiying = Chaojiying_Client(username, pwd, id)  # 实例化超级鹰账户对象
url = "https://zc.yy.com/reg/udb/reg4udb.do?mode=udb&type=Mobile&appid=1&foreignMb=1&action=3&busiurl=https%3A%2F%2Faq.yy.com&fromadv=lgn&reqDomainList="
driver = Chrome()  # 创建一个驱动
driver.get(url)  # 访问url
driver.maximize_window()  # 使窗口最大化
element = WebDriverWait(driver, 20).until(ec.element_to_be_clickable((By.CLASS_NAME, "pw_main")))  # 显性等待
element.screenshot("pw_main.png")  # 对element元素进行截图
img = open("pw_main.png", "rb").read()  # 读取图片的字节数据
ret = chaojiying.PostPic(img, 9004)  # 获取图片识别结果
pos = [(int(j), int(k)) for j, k in
       [i.split(",") for i in ret.get("pic_str").split("|")]]  # 将数据转换为一个一个的坐标,同时使用get方法防止报错
for i in pos:
    # 根据返回的坐标,去执行点击事件
    ActionChains(driver).move_to_element_with_offset(element, i[0], i[1]).click().perform()  # 点击对应点的坐标
    
driver.find_element(By.CLASS_NAME, "pw_submit").click()  # 点击提交按钮
posted @ 2022-04-26 11:17  Kenny_LZK  阅读(321)  评论(0)    收藏  举报