Selenium selenium+PIL+tesseract 实现 web 简单验证码识别

zhizhizhi2017 · 2018年05月02日 · 最后由 book 回复于 2018年08月14日 · 5545 次阅读
本帖已被设为精华帖!

识别图片的验证码

目前只是用来好玩的,因为适用范围太窄了。比如只是数字验证码识别率高;验证码颜色一复杂,这个脚本也需要调,难度更高。

一、效果图


识别6次,成功5次,第3次识别失败

二、设计思路

1.获取整个网页的截图
2.获取验证码的截图
3.对验证码图片处理,只保留其中的数字
4.用工具识别验证码图,转为文本
5.登录

三、代码

这个代码只适用于我的测试网站。自己的网站需要自己修改rgb值。

# coding:utf-8
from selenium import webdriver
from PIL import Image
import os
import time

def get_captcha(driver,captcha_id='kaptchaImage',full_screen_img_path='c:/web.png',
captcha_img_path='c:/captcha.png',captcha_final_path='c:/captcha_final.png',
txt_path='c:/captcha.txt'):
'''
自动获取验证码
:param driver:
:param captcha_id:网页中验证码图片的id
:param full_screen_img_path:整个网页截屏保存的路径
:param captcha_img_path:验证码图片的路径
:param captcha_final_path:最终处理的验证码的路径
:param txt_path:保存验证码的txt文本路径
:return:验证码 or fail
'''

# 浏览器界面截图
driver.save_screenshot(full_screen_img_path)
#找到验证码图片,得到它的坐标
element = driver.find_element_by_id(captcha_id)
left = element.location['x']
top = element.location['y']
right = element.location['x'] + element.size['width']
bottom = element.location['y'] + element.size['height']
left, top, right, bottom = int(left), int(top), int(right), int(bottom)
img = Image.open(full_screen_img_path)
img = img.crop((left, top, right, bottom))
#得到验证码图片
img.save(captcha_img_path)
#打开验证码图片
img = Image.open(captcha_img_path)
# 新建一张图片(大小和原图大小相同,背景颜色为255白色)
img_new = Image.new('P', img.size, 255)
for x in range(img.size[1]):
for y in range(img.size[0]):
# 遍历图片的xy坐标像素点颜色
pix = img.getpixel((y, x))
# print(pix)
#自己调色,r=0g=0b>0为蓝色
if (pix[0] < 120 and pix[1] < 120 and pix[2] > 200) or (pix[0] < 40 and pix[1] < 40 and pix[2] > 100):
#把遍历的结果放到新图片上,0为透明度,不透明
img_new.putpixel((y, x), 0)
img_new.save(captcha_final_path, format = 'png')

#通过tesseract工具解析验证码图片,生成文本
# cmd = 'tesseract '+captcha_final_path+' '+txt_path[0:-4]
cmd = 'tesseract '+captcha_final_path+' '+txt_path[0:-4] + ' -psm 7 digits'
os.system(cmd)

#读取txt文件里面的验证码
with open(txt_path, 'r') as f:
#去掉左右空格
t = f.read().strip()
#去掉中间空格
if ' ' in t:
t = t.replace(' ', '')
#如果是数字且长度为4,就返回数字,如果不是就返回 fail
if t.isdigit() and len(t) == 4:
return t
else:
return 'fail'

def log_in_eshop(driver,username,password):
'''
通过URL判定是否登录成功,比判定元素快,如果网速较慢可能会判定失败
:param driver:
:param username: 账号
:param password: 密码
:return:
'''
driver.find_element_by_name("userName").clear()
driver.find_element_by_name("userName").send_keys(username)
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys(password)
code = get_captcha(driver)
driver.find_element_by_name("kaptcha").clear()
driver.find_element_by_name('kaptcha').send_keys(code)
time.sleep(2)
driver.find_element_by_class_name("loginBt").click()
time.sleep(3)
#统计识别次数
n = 0
#识别5次验证码,如果5次都没识别出来,测试fail
while driver.current_url == '你的登录网站' and n < 5:
driver.find_element_by_name("userName").clear()
driver.find_element_by_name("userName").send_keys(username)
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys(password)
code = get_captcha(driver)
driver.find_element_by_name("kaptcha").clear()
driver.find_element_by_name('kaptcha').send_keys(code)
time.sleep(1)
driver.find_element_by_class_name("loginBt").click()
time.sleep(4)
n=n+1
if n==5:
print(u'输入验证码失败')
return False
else:
return True

if __name__ == '__main__':
url = '你的登录网站'
driver = webdriver.Chrome()
driver.maximize_window()
driver.get(url)
time.sleep(2)
usename = '你的账号'
password = '你的密码'
log_in_eshop(driver, usename, password)

四、一些问题

1.pix[0],pix[1],pix[2] 是什么意思?
pix[0],pix[1],pix[2]分别对应r,g,b三种颜色。

2.pix[0],pix[1],pix[2]怎么取值的?
比如 if (pix[0] < 120 and pix[1] < 120 and pix[2] > 200) or (pix[0] < 40 and pix[1] < 40 and pix[2] > 100): 这个是什么意思?
以下图为例

这张图片中,验证码的数字主要为3种颜色,蓝色、蓝白色、以及干扰线的黑色。
现在我们要获取这3种颜色的rgb值。
首先打开QQ,用老马给你的截图键,对验证码图片截图,鼠标放到蓝色上面,然后你就看到蓝色的rgb值。蓝白色和黑色同理。


不同的地方肉眼,看起来是一个颜色,实际上rgb会有所偏差。
所以代码没有直接写成

if (pix[0] = 4 and pix[1] = 4 and pix[2] = 236) or (pix[0] = 108 and pix[1] = 108 and pix[2] = 206) or (pix[0] = 5 and pix[1] = 7 and pix[2] = 128):

而是写成了

if (pix[0] < 120 and pix[1] < 120 and pix[2] > 200) or (pix[0] < 40 and pix[1] < 40 and pix[2] > 100):

处理后的结果:

验证码图片处理参考资料:https://www.shiyanlou.com/courses/364/labs/1165/document

3.tesseract是什么?
tesseract是一款开源的ocr工具。更多资料可以网上搜索下。
网上下载这样一个工具,安装,配置环境变量,然后使用基础语法就可以识别图片了。
基础语法:tesseract imagename outputbase
Usage:tesseract imagename outputbase [-l lang] [-psm pagesegmode] [configfile...]
pagesegmode values are:
0 = Orientation and script detection (OSD) only.
1 = Automatic page segmentation with OSD.
2 = Automatic page segmentation, but no OSD, or OCR
3 = Fully automatic page segmentation, but no OSD. (Default)
4 = Assume a single column of text of variable sizes.
5 = Assume a single uniform block of vertically aligned text.
6 = Assume a single uniform block of text.
7 = Treat the image as a single text line.
8 = Treat the image as a single word.
9 = Treat the image as a single word in a circle.
10 = Treat the image as a single character.
-l lang and/or -psm pagesegmode must occur before anyconfigfile.

代码中是使用的:

cmd = 'tesseract '+captcha_final_path+' '+txt_path[0:-4] + ' -psm 7 digits'

这条命令是使用模式7,读取/tessdata/configs/digits 文件,digits文件我改了下,里面全是数字,那么ocr工具只会匹配数字

参考资料:https://blog.csdn.net/xiaochunyong/article/details/7193744
https://github.com/tesseract-ocr/tesseract/wiki/Command-Line-Usage

共收到 20 条回复 时间 点赞

文章不错。麻烦博主能不能换个头像,考虑一下老年人的感受,这东西看的头晕啊!!!

取验证码截图那是一种可推荐的方法;

seveniruby 将本帖设为了精华贴 05月02日 20:51
yyym 回复

😂 现在头像不动了

验证码图片获取,有的时候需要加上header,cookie,克服 CSRF

import requests
# 获取的是百度搜索页面上图标
r=requests.get('http://www.baidu.com/img/bd_logo1.png?where=super')

import io
i=io.BytesIO()
i.write(r.content)

from PIL import Image
image=Image.open(i)
image.save('captchaCode.png')

匿名 · #6 · 2018年05月03日
仅楼主可见
mixure 回复

嗯,如果是使用request请求来获取验证码图片的话,大多数网站因为反爬机制都需要加上这些参数。
通过selenium可以不用这些参数。

学习了,以前用过PIL做图像上传。
仔细想一下,背景色应该是比较独立的一种颜色,可否考虑把四个角的RGB取出来,超过3组相同就预计是背景颜色。
然后过滤掉取出文字色和干扰色, 如果二者不一样可以粗略根据像素点数量分离,如果一样……就比较难搞了

mypchas6fans 回复

是的,如果背景颜色很杂,或者验证码本身颜色很杂那么这个处理过程就会麻烦很多。
即使加一个小小的验证码随机旋转,也会加大处理难度。

我直接调百度的图片识别接口其实还可以😂

不是专门测试验证码的,应该把验证功能关了,把重点放在测试上,而不是搞验证识别

其实可以利用pytesseract库进行验证码识别,不需要对验证码背景颜色重新写代码处理

image = Image.open('D:\Python_ATX\image\code1.png')
tessdata_dir_config ='--tessdata-dir "D:\Tesseract-OCR\tessdata"'
code = pytesseract.image_to_string(image,config=tessdata_dir_config)
print(code)

楼主不妨试试

这种图 去燥加中级滤波可 处理起来没那么麻烦也可以提升识别率
总结的不错,建议楼主可以在图像领域继续深挖
tesseract 一些深度学习的东西加上它本身一些训练库 可以继续研究一下

huanzhijin 回复

是的,我们测试时会设置一个万能验证码。这个只是闲暇时自己捣鼓的一个脚本。

testly 回复

当然会的,图片处理和识别很有意思。

xxoomyoppo 回复

啊,这个应该只是对简单的验证不需要做图片处理吧。如果难度很大的也不需要处理的话,那就太厉害了。

让开发搞个白名单绕过去才是最好的解决方案。

zhizhizhi2017 关闭了讨论 05月26日 16:19
zhizhizhi2017 重新开启了讨论 05月26日 16:19

如果你测的网站的验证码被你识别了 你可以直接提个bug,不用测试了😂

dongdong 回复

小公司,没人攻击,先将就着用。😄
等公司成长起来了,这个肯定要改的。

总是把1识别成7,好心塞

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册