一、字符验证码
识别此类验证码,首先需要找到验证码图片在网页HTML代码中的位置,然后将验证码下载,最后在通过OCR技术进行验证码的识别工作。
1、搭建OCR环境
Tesseract-OCR是一个免费的、开源的OCR引擎,通过该引擎可以识别图片中的验证码,搭建OCR的具体步骤如下:
(1) 打开Tesseract-OCR下载地址(https://github.com/UB-Mannheim/tesseract/wiki),然后选择与自己操作系统匹配的版本
(2)安装方法
(3) 配置环境变量
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技术识别后的验证码多了一个".",遇到这种情况,可以将彩色的验证码图片转换为灰度图片再测试。
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))