图形验证码是最早出现的验证方式,现在依然很常见,一般由4位左右的字母或者数字组成。本章节使用的网站时https://captcha7.scrape.center/,这个网站的验证码相对来说比较平整,没有过多的干扰线和干扰点,文字也没有大幅度的变形和旋转,因此比较好作为案例进行分析,对于这类验证码,可以使用OCR技术识别。参考书籍依然是Python3网络爬虫开发实战(第三版)。
OCR,即Optical Character Recognition,中文叫做光学字符识别,是指使用电子设备(例如扫描仪和数码相机)检查打印再纸上的字符,通过检查暗、亮的模式确定字符形状,然后使用字符识别方法将形状转化位计算机文字。现在OCR技术已经广泛应用于生产活动中,如文档识别,证件识别,字幕识别,文档搜索等。当然用来识别本节所述的图形验证码也没有问题。
再本节的学习中需要导入tesserocr库,这个库的安装需要参考https://setup.scrape.center/tesserocr.另外,还需要安装Selenium、Pillow、Numpy和retrying库用来模拟登录、处理图像和重试操作,可以使用pip3工具安装这些库。 安装好这些库就可以开始了。
如果安装异常的话就换一个,可以参照我的,我用的库不是上面的,而是pytesseract,我觉得两者差别不大
这个网页使用JavaScript渲染出来的,所以我们进行爬取的时候使用selenium自动化测试工具。
from selenium import webdriver
from selenium.webdriver.common.by import By
from PIL import Image
from io import BytesIO
import time
def demo():
browser = webdriver.Chrome()
browser.get("https://captcha7.scrape.center")
time.sleep(3)
captcha = browser.find_element(By.CSS_SELECTOR,"#captcha")
image = Image.open(BytesIO(captcha.screenshot_as_png))
image.show()
if __name__ == "__main__":
demo()
这里使用了我很少见的BytesIO,这是一个类,它的功能是读取二进制数据流,而图片就是二进制数据流;还有就是captcha.screenshot_as_png这部分的功能就是将当前页面的内容捕获为一张图像,以bytes二进制数据保存;最后调用image的show方法来显式验证码的图像。
首先我们选用两张图片来进行测试,第一张是有换行和明显空格,第二张是一张验证码。
我们运行下面代码:
import pytesseract
from PIL import Image
image1 = Image.open("tesseract_tt1.png")
result1 = pytesseract.image_to_string(image1)
image2 = Image.open("tesseract_tt2.png")
result2 = pytesseract.image_to_string(image2)
print(result1, end= '')
print("=========")
print(result2, end= '')
Demons
Lin
Ss ZzTU
=========
2034
我们可以看到在输出SZTU这部分时候出现了SsZz这样大小写都输出的情况,这是因为pytesseract库在识别大小写字母时候很难准确识别出大小写,你可以采取其他办法来执行,这里就不列出来。
import pytesseract
from PIL import Image
image = Image.open("error.png")
result = pytesseract.image_to_string(image)
print(result, end= '')
04-8 d.
可以看到这个输出结果明显不是我们想要的,这是因为OCR识别技术是通过检查暗、亮的模式确定字符形状,不是我们想当然的用脑子来看。所以,我们需要做一些额外处理,把干扰信息去掉,我们观察发现,图片里哪些造成干扰的点,其颜色大多比文本的颜色更浅,因此可以通过颜色将干扰点去掉。首先将保存的图片转化为数组,看一下维度:
from PIL import Image
import numpy as np
image = Image.open("error.png")
print(np.array(image).shape)
print(image.mode)
(38, 112, 4)
RGBA
从结果上可以看出,这个图片其实是一个三维数组,38和112代表图片的高和宽,4则是每个像素点的表示向量,那为什么是4呢?因为最后一维是一个长度为4的数组分别表示R(红)G(绿)B(蓝)A(透明度),即一个像素点由4个数字表示。那为什么是RGBA而不是RGB或者其他的呢?因为image.mode是RGBA,即由透明通道的真彩色。
mode属性定义了图片的类型和像素的位宽,一共由9种类型:
为了方便处理,可以把RGBA转化位更简单的L,即把图片转化位灰度图像。往图片对象的convert方法中传入L即可,代码如下表示:
image = image.convert('L')
image.show()
我们选择把图片转化位灰度图像,然后根据阈值删除图片上的干扰点,成功识别出验证码,代码如下:
from PIL import Image
import numpy as np
image = Image.open("error.png")
image = image.convert('L')
threshold = 90
array = np.array(image)
array = np.where(array> threshold, 255, 0)
image = Image.fromarray((array.astype('uint8')))
# image.show()
result = pytesseract.image_to_string(image)
print(result)
这里先将变量threshold赋值位50.它代表灰度的阈值。接着将图片转化位Numpy数组,利用Numpy的where方法对数组进行筛选和处理,其中将灰度大于阈值的图片的像素设置为255表示白色,否则为0,表示黑色。
import time
import re
import pytesseract
from selenium import webdriver
from io import BytesIO
from PIL import Image
from retrying import retry
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
import numpy as np
def preprocess(image):
image = image.convert('L')
array = np.array(image)
array = np.where(array > 105, 255, 0)
image = Image.fromarray(array.astype('uint8'))
return image
@retry(stop_max_attempt_number=10, retry_on_result=lambda x: x is False)
def login():
browser.get('https://captcha7.scrape.center/')
browser.find_element(By.CSS_SELECTOR, '.username input[type="text"]').send_keys('admin')
browser.find_element(By.CSS_SELECTOR, '.password input[type="password"]').send_keys('admin')
captcha = browser.find_element(By.CSS_SELECTOR,'#captcha')
image = Image.open(BytesIO(captcha.screenshot_as_png))
image = preprocess(image)
image.show()
captcha = pytesseract.image_to_string(image)
print(captcha)
captcha = re.sub('[^A-Za-z0-9]', '', captcha)
browser.find_element(By.CSS_SELECTOR, '.captcha input[type="text"]').send_keys(captcha)
browser.find_element(By.CSS_SELECTOR, '.login').click()
try:
WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.XPATH, '//h2[contains(., "登录成功")]')))
time.sleep(5)
browser.close()
return True
except TimeoutException:
return False
if __name__ == '__main__':
browser = webdriver.Chrome()
login()
在使用 pytesseract
时,你可以使用以下参数:
lang
: 这个参数用于指定 OCR 使用的语言。默认为 ‘eng’,表示英文。如果你的验证码是英文的,那么你可以保持这个默认值。如果验证码是其他语言的,你需要指定相应的语言代码。例如,中文的语言代码是 ‘chi_sim’。config
: 这个参数用于指定 tesseract 的配置文件。你可以使用它来调整 OCR 的行为。例如,你可以设置 tesseract 只识别数字和大写字母。nice
: 这个参数用于指定 OCR 的质量。值的范围是 0-3,0 表示最快但质量最低,3 表示最慢但质量最高。默认值是 0。如果你的验证码很难识别,你可能需要将这个值设为 3。这些参数可以在调用 pytesseract.image_to_string
时通过关键字参数的方式指定。例如:
captcha = pytesseract.image_to_string(image, lang='chi_sim', config='--psm 10', nice=3)
另外,你也可以使用 pytesseract.image_to_data
函数,它比 image_to_string
更灵活。image_to_data
函数返回一个包含了 OCR 结果的数据结构,你可以从这个数据结构中提取你需要的信息。例如,你可以提取每个单词的置信度,然后只保留置信度高的单词。
还有其他的识别技巧可以学习,这里给出CSDN博客我觉得挺好的一篇:
借助Tesseract-OCR进行文本检测(1)
借助Tesseract-OCR进行文本检测(2)