一、字符验证码



        识别此类验证码,首先需要找到验证码图片在网页HTML代码中的位置,然后将验证码下载,最后在通过OCR技术进行验证码的识别工作。



1、搭建OCR环境


        Tesseract-OCR是一个免费的、开源的OCR引擎,通过该引擎可以识别图片中的验证码,搭建OCR的具体步骤如下:

(1) 打开Tesseract-OCR下载地址(https://github.com/UB-Mannheim/tesseract/wiki),然后选择与自己操作系统匹配的版本

(2)安装方法

(3) 配置环境变量

lua 识别验证码 验证码识别教程_lua 识别验证码



1.1 安装tesserocr模块

pip install pytesseract



注意:



      如果使用的是Anaconda并在安装时出现了错误,执行如下命令:

conda install -c simonflueckiger tesserocr




如果这两种方法都有问题,可以下载.whl文件进行安装

下载地址: https://pypi.org/网站上搜索tesserocr

2、常用命令

tesseract --help 查看帮助
tesseract --list-langs 查看可识别的语言
tesseract 图片名称 返回的文件名 识别前需要先cd到图片的目录
tesseract 图片名称 返回的文件名 -l 需要识别的语言



        并不是所有的验证码都可以轻松识别出来,如下方图片,通过OCR技术识别后的验证码多了一个".",遇到这种情况,可以将彩色的验证码图片转换为灰度图片再测试。




lua 识别验证码 验证码识别教程_Image_02

import pytesseract
from PIL import Image
img = Image.open('./2.png')
# 将彩色图片转换为灰度图片
img = img.convert('L')
code = pytesseract.image_to_string(img)
print('验证码为:',code)



       接下来将转换为灰度后的验证码图片进行二值化处理,将验证码二值化处理后再次通过OCR进行识别



import pytesseract
from PIL import Image
img = Image.open('./2.png')
# 将彩色图片转换为灰度图片
img = img.convert('L')

t = 155 # 设置阈值
table = []
for i in range(256):
    if i < t:
        table.append(0)
    else:
        table.append(1)
# 将图片进行二进制化处理
img = img.point(table,'1')
img.save('code4.png')
# img.show() ## 显示处理后图片
code = pytesseract.image_to_string(img)
print('验证码为:',code)



说明:



       在识别以上具有干扰线的验证码图片时,可以做一些灰度和二进制处理,这样可以提高图片验证码的识别率,如果二进制处理后还是无法达到识别的精准性,可以适当的上下调节一下二值化操作中的阀值



3、第三方验证码识别



       虽然OCR可以识别验证码图片中的验证码信息,但是识别效率与准确度不高是OCR的缺点,所以使用第三方验证码识别平台是一个不错的选择




       验证码识别平台一般分为两种:分别是扫码平台和AI开发者平台。扫码平台主要是由在线人员进行验证码的识别工作,然后在较短的时间内返回结果。AI开发者平台主要是由人工智能来进行识别。比如百度AI




案例:第三方扫码平台中验证码识别过程



(1)打开扫码平台网页( http://www.chaojiying.com/ ):



注册之后联系平台客服人员,申请免费测试



账号注册好之后,找到开发文档下载python示例




二、滑块验证

1、获取滑动距离

  • 对于有缺口和没有缺口的两张图片,进行逐个像素的对比
  • 通过opencv的方式,拿着有缺口的小图片 去有缺口的背景图片找到对应的位置
  • 通过机器学习的方式计算缺口位置。

1.1 第一种:对比像素

写一个函数,对于有缺口和没有缺口的两张图片,进行像素对比,先找到两张图片进行做对比,获取到缺口的大致位置,再用selenium里面学习的行为链,进行滑动

1、通过修改 css样式得到两张图(完整图片 和有缺口的图片)

2、在获取坐标进行截图的时候,要注意自己的电脑的显示中的缩放与布局是不是100%

3、在本案例中,进行滑动时会对行为轨迹进行验证,所以最好推荐使用pyautogui


import random
import time

import pyautogui
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from PIL import Image
from selenium.webdriver.common.by import By
from io import BytesIO
# pip install pyautogui -i https://pypi.tuna.tsinghua.edu.cn/simple
import pyautogui


class FloatSlide():
    def __init__(self, username, password):
        self.url = 'https://www.geetest.com/demo/slide-float.html'
        self.username = username
        self.password = password

        self.driver = webdriver.Chrome()
        try:
            # 窗口最大化
            self.driver.maximize_window()
        except:
            pass

        self.wait = WebDriverWait(self.driver, 100)

    def crop_img(self, img_file):
        time.sleep(1)
        # 截全屏
        screen_shot = self.driver.get_screenshot_as_png()

        # 获取到验证码图片的位置
        img = self.driver.find_element(By.CLASS_NAME,'geetest_canvas_img')
        # location 获取的是定位到的元素的坐标
        location = img.location
        # print(img.location)
        # size 用于获取img的大小
        size = img.size
        # print(img.size)
        # 分别得到两个点的坐标
        x1, y1 = location['x'], location['y']
        x2, y2 = location['x'] + size['width'], location['y'] + size['height']

        new_screen_shot = Image.open(BytesIO(screen_shot))
        # 抠图
        captcha = new_screen_shot.crop((int(x1), int(y1), int(x2), int(y2)))
        # 把抠出来的图片保存到本地
        captcha.save(img_file)

        return captcha

    # 判断两个像素是否相同
    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素是否相同
        :param image1:图片一
        :param image2:图片二
        :param x:需要对比的坐标(x)
        :param y:需要对比的坐标(y)
        :return: 像素是否相同 相同返回True 不同返回False
        """
        # 获取两个图片的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        # 获取到两张图片对应像素点的RGB数据
        # 如果差距在一定范围之类 就代表两个像素相同 继续对比下一个像素点
        # 如果差距超过一定范围 则表示像素点不同 即为缺口位置
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold and abs(pixel1[3] - pixel2[3]) < threshold:
            return True
        return False

    # 获取缺口偏移量
    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 完整的图片
        :param image2: 带缺口的图片
        :return: 返回偏移量
        """
        left = 0
        # 遍历图片的每个坐标点
        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 get_track_and_move(self, distance):
        # 移动轨迹
        track = []
        # 当前位置
        current = 0
        """
        如果是匀速运动 极验很有可能会识别出它是程序的操作 因为人是没有办法做到完全匀速拖动的
        需要用到物理学里面的两个公式
        通过这两个公式可以构造轨迹移动算法 计算出先加速后减速的运动轨迹
        """
        # 减速阈值(大概到3/4的位置 开始减速)
        mid = distance * 3 / 4
        # 计算间隔
        t = 0.1
        # 初速度
        v = 0
        while current < distance:
            if current < mid:
                a = random.randint(2, 3)
            else:
                a = -random.randint(7, 8)
            # 初速度
            v0 = v
            # 当前速度
            v = v0 + a * t
            # 移动距离
            move = v0 * t + 0.5 * a * t * t
            track.append(round(move))  # 把计算得到的新的移动距离放入移动轨迹列表中
            current += move  # 更新当前位置
        return track

    # 移动函数
    def move_slide(self, offset_x, offset_y, left):
        """
        移动函数
        :param offset_x: 滑块的x轴坐标
        :param offset_y: 滑块的y轴坐标
        :param left: 需要移动的距离
        :return:
        """
        # pyautogui库操作鼠标指针
        pyautogui.moveTo(offset_x, offset_y,
                         duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
        pyautogui.mouseDown()
        offset_y += random.randint(9, 19)
        pyautogui.moveTo(offset_x + int(left * random.randint(15, 25) / 20), offset_y, duration=0.28)
        offset_y += random.randint(-9, 0)
        pyautogui.moveTo(offset_x + int(left * random.randint(17, 23) / 20), offset_y,
                         duration=random.randint(20, 31) / 100)
        offset_y += random.randint(0, 8)
        pyautogui.moveTo(offset_x + int(left * random.randint(19, 21) / 20), offset_y,
                         duration=random.randint(20, 40) / 100)
        offset_y += random.randint(-3, 3)
        pyautogui.moveTo(left + offset_x + random.randint(-3, 3), offset_y,
                         duration=0.5 + random.randint(-10, 10) / 100)
        offset_y += random.randint(-2, 2)
        pyautogui.moveTo(left + offset_x + random.randint(-2, 2), offset_y,
                         duration=0.5 + random.randint(-3, 3) / 100)
        pyautogui.mouseUp()
        time.sleep(3)

    def check_success(self):
        # 异常处理注意针对第一次运行
        try:
            con = self.driver.find_element(By.CLASS_NAME,'geetest_success_radar_tip_content').text
            if con == '验证成功':
                print('成功')
                return True
            else:
                print('失败')
                return False
        except:
            return False

    def run(self):
        while not self.check_success():
            self.driver.get(self.url)

            # 输入用户名
            username_input = self.driver.find_element(By.ID,'username')
            username_input.clear()
            username_input.send_keys(self.username)

            # 输入密码
            password_input = self.driver.find_element(By.ID,'password')
            password_input.clear()
            password_input.send_keys(self.password)

            # 用显示等待 判断点击按钮进行验证 是否处于可以点击的状态
            # 显示等待 再until里面设置等待条件
            check_btn = self.wait.until(
                EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))
            )
            if check_btn:
                check_btn.click()
                # 判断滑块是否出现 如果出现了 在class="geetest_radar_tip_content"的标签中的文本内容为
                # con = self.driver.find_element_by_class_name('geetest_radar_tip_content')
                check_text = self.wait.until(
                    EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_radar_tip_content'), "请完成验证")
                )
                if check_text:
                    # 如果是 请完成验证 就证明滑块已经加载完成
                    # 一旦确定滑块加载完成 我们就可以开始截图了
                    # print('123')
                    # 通过测试 我们发现 document.querySelectorAll("canvas") 索引标是从0开始的
                    # 执行js 修改css样式 获取完整的背景图片
                    self.driver.execute_script('document.querySelectorAll("canvas")[1].style="opacity: 1; display: none;"')
                    self.driver.execute_script('document.querySelectorAll("canvas")[2].style=""')
                    # 拿到完整的背景图片
                    intact_img = self.crop_img('intact_img.png')

                    # 获取到有缺口的图片
                    self.driver.execute_script('document.querySelectorAll("canvas")[2].style="opacity: 0; display: none;"')
                    # 拿到有缺口的图片
                    gap_img = self.crop_img('gap_img.png')

                    # 获取完图片之后 才能恢复样式
                    self.driver.execute_script('document.querySelectorAll("canvas")[1].style="opacity: 1; display: block;"')

                    # 进行对比 获取到缺口位置
                    distance = self.get_gap(intact_img, gap_img)
                    print(distance)
                    # 需要根据实际情况对偏移量做小的调整
                    distance -= 6

                    # 2.0版本的滑动
                    x, y = 1260,380
                    self.move_slide(x, y, distance)


if __name__ == '__main__':
    f = FloatSlide('xmg', '412304')
    f.run()

1.2、通过openCV识别缺口位置

如何使用端口号打开谷歌(知乎)

使用selenium最保险的方式,是使用端口号打开,减少被识别的风险

cmd => chrome.exe --remote-debugging-port=9222(要么cd到chrome的安装目录,要么把chrome的安装目录添加到环境变量),而且使用之前必须要确保所有之前开启的chrome都要关闭。

import random
import time
from urllib import request

import pyautogui
from selenium import webdriver
from selenium.webdriver.common.by import By
import cv2

#  87.0.4280.66
class Wangyi():
    def __init__(self):
        self.url = 'https://dun.163.com/trial/sense'
        option = webdriver.ChromeOptions()
        option.add_experimental_option('debuggerAddress', '127.0.0.1:9222')

        self.driver = webdriver.Chrome(options=option)
        # 窗口最大化处理
        try:
            self.driver.maximize_window()
        except:
            pass

    def identify_gap(self, bg_image, tp_image, out="new_image.png"):
        """
        通过cv2计算缺口位置
        :param bg_image: 有缺口的背景图片文件
        :param tp_image: 缺口小图文件图片文件
        :param out: 绘制缺口边框之后的图片
        :return: 返回缺口位置
        """
        # 读取背景图片和缺口图片
        bg_img = cv2.imread(bg_image)  # 背景图片
        tp_img = cv2.imread(tp_image)  # 缺口图片

        # 识别图片边缘
        bg_edge = cv2.Canny(bg_img, 100, 200)
        tp_edge = cv2.Canny(tp_img, 100, 200)

        # 转换图片格式
        bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
        tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)

        # 缺口匹配
        res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配

        # 绘制方框
        th, tw = tp_pic.shape[:2]
        tl = max_loc  # 左上角点的坐标
        br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标
        cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2)  # 绘制矩形
        cv2.imwrite(out, bg_img)  # 保存在本地

        # 返回缺口的X坐标
        return tl[0]

    # 移动函数
    def move_slide(self, offset_x, offset_y, left):
        """
        移动函数
        :param offset_x: 滑块的x轴坐标
        :param offset_y: 滑块的y轴坐标
        :param left: 需要移动的举例
        :return:
        """
        # pyautogui库操作鼠标指针
        pyautogui.moveTo(offset_x, offset_y,
                         duration=0.1 + random.uniform(0, 0.1 + random.randint(1, 100) / 100))
        pyautogui.mouseDown()
        offset_y += random.randint(9, 19)
        pyautogui.moveTo(offset_x + int(left * random.randint(15, 25) / 20), offset_y, duration=0.28)
        offset_y += random.randint(-9, 0)
        pyautogui.moveTo(offset_x + int(left * random.randint(17, 23) / 20), offset_y,
                         duration=random.randint(20, 31) / 100)
        offset_y += random.randint(0, 8)
        pyautogui.moveTo(offset_x + int(left * random.randint(19, 21) / 20), offset_y,
                         duration=random.randint(20, 40) / 100)
        offset_y += random.randint(-3, 3)
        pyautogui.moveTo(left + offset_x + random.randint(-3, 3), offset_y,
                         duration=0.5 + random.randint(-10, 10) / 100)
        offset_y += random.randint(-2, 2)
        pyautogui.moveTo(left + offset_x + random.randint(-2, 2), offset_y,
                         duration=0.5 + random.randint(-3, 3) / 100)
        pyautogui.mouseUp()
        time.sleep(3)

    def run(self):
        self.driver.get(self.url)
        time.sleep(1)
        # 切换验证模式
        self.driver.find_element(By.XPATH,'//ul[@class="tcapt-tabs__container"]/li[2]').click()

        self.driver.implicitly_wait(10)
        self.driver.find_element(By.CLASS_NAME,'yidun_intelli-tips').click()
        time.sleep(3)

        # 分别获取两张图片
        # get_attribute 获取定位到的标签里面的属性值
        img_url1 = self.driver.find_element(By.CLASS_NAME,'yidun_bg-img').get_attribute('src')
        img_url2 = self.driver.find_element(By.CLASS_NAME,'yidun_jigsaw').get_attribute('src')
        # print(img_url1, img_url2)
        # 保存图片
        request.urlretrieve(img_url1, 'img_url1.png')
        request.urlretrieve(img_url2, 'img_url2.png')

        # 识别缺口位置 获取偏移量
        distance = self.identify_gap('img_url1.png', 'img_url2.png')
        print(distance)

        # 滑动
        # 滑块的坐标(需要根据实际情况做调整)
        x, y = 1054, 740
        # distance需要根据实际情况做调整
        self.move_slide(x, y, distance)


if __name__ == '__main__':
    w = Wangyi()
    w.run()

1.3 通过机器学习的方式识别缺口位置

百度智能云: http://ai.baidu.com/easydl/vision/ 

1、创建数据集

      - 导入(上传图片)

      - 查看与标注(添加标签 —>逐个对每张图片 框缺口,选择标签)

2、创建模型

3、训练模型(需要添加数据集)

4、发布 (校验:放一张有缺口的图片,看是否能识别出图片中的缺口)

5、发布完成后,在服务详情里面有一个API接口。先查看API文档

6、要启动服务 校验模型-》启动服务

7、进入到控制台

       接口赋权-》应用列表->创建应用-》填写应用名称和应用归属(个人)

结果:

{
    "log_id": 953123488105375010,
    "results": [
        {
            "location": {
                "height": 54,
                "left": 220,
                "top": 27,
                "width": 54
            },
            "name": "缺口",
            "score": 0.9996750354766846
        }
    ]
}

left 就是偏移量

在接口文档中拷贝如下代码,改成自己账号对应的token,appkey,secretkey以及训练的接口地址 

"""
EasyDL 物体检测 调用模型公有云API Python3实现
"""
import json
import base64
import requests

IMAGE_FILEPATH = "./image1.jpg"
PARAMS = {"threshold": 0.3}
MODEL_API_URL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/detection/quekou_study"
ACCESS_TOKEN = ""
API_KEY = "f5qkXxvYyHiNnaaz9UfB1FMN"
SECRET_KEY = "rnLLXkt7fMERY4Njkhnw2ZfNXMoGEVG0"
with open(IMAGE_FILEPATH, 'rb') as f:
    base64_data = base64.b64encode(f.read())
    base64_str = base64_data.decode('UTF8')
PARAMS["image"] = base64_str
if not ACCESS_TOKEN:
    auth_url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials"               "&client_id={}&client_secret={}".format(API_KEY, SECRET_KEY)
    auth_resp = requests.get(auth_url)
    auth_resp_json = auth_resp.json()
    ACCESS_TOKEN = auth_resp_json["access_token"]
else:
    print("2. 使用已有 ACCESS_TOKEN")
request_url = "{}?access_token={}".format(MODEL_API_URL, ACCESS_TOKEN)
response = requests.post(url=request_url, json=PARAMS)
response_json = response.json()
response_str = json.dumps(response_json, indent=4, ensure_ascii=False)
print("结果:{}".format(response_str))