图形验证码最早出现,也很常见,一般由4个字母或者数字组成。例如,中国知网注册页面采用的就是图形验证码,链接为http://my.cnki.net/elibregister/commonRegister.aspx,页面如下:
该注册页面表单最后一项就是图形验证码,必须正确输入图形验证码才可以完成注册。该图形验证码其实是一张图片,上面是经过变形的4个大写字母,并加入了一些干扰,可以点击此验证码图片和“换一张”更新验证码。 先下载两张图形验证码,如图:
环境准备:
(1)Python 3.6.5;
(2)pillow,tesserocr;
(3)Tesseract-OCR:https://digi.bib.uni-mannheim.de/tesseract/,tesseract-ocr-w64-setup-v4.0.0.20181030.exe,安装时选择所有的语言包,tessdata目录拷贝到Python主目录。
先来识别第一张“W875”的图形验证码,程序很简单,如下:
import tesserocr
from PIL import Image as Img
image = Img.open('images/W875.jpg')
result = tesserocr.image_to_text(image)
print(result)
程序运行结果:
结果很正确,两行代码搞定!再使用该方法继续识别第二张验证码,程序类似,如下:
import tesserocr
from PIL import Image as Img
image = Img.open('images/PRUT.jpg')
result = tesserocr.image_to_text(image)
print(result)
程序运行结果:
程序运行结果比验证码多个一些字符。 识别存在错误也是正常的,从来没有百分之百的识别,只有接近百分之百的识别。识别错误的原因就是图片周围有一些干扰,下面我们去除干扰后进行识别。彩色验证码一般是由RGB三个值构成的,需要先将验证码转换成灰度图片(0~255),然后再二值化转换成黑白图片(0和1)。程序如下:
import tesserocr
from PIL import Image as Img
image = Img.open('images/PRUT.jpg')
image = image.convert('L')
threshold = 120
table = [0] * threshold + [1] * (256 - threshold)
image = image.point(table, '1')
image.save('images/BinarizedPRUT.jpg')
result = tesserocr.image_to_text(image)
print(result)
经过灰度化处理后的验证码,如下:
可以看到背景干扰已经处理掉,图片有些失真,但不影响识别效果,程序运行结果:
极验验证码官网链接https://www.geetest.com/,它是全球交互安全创领者,专注于提供验证安全的企业。主要验证方式是拖动滑块拼合图像。若图像完全拼合,则验证成功,否则需要重新验证。大家平时上网也经常会遇到。我们以国家企业信用信息公示系统为例,链接:http://www.sgs.gov.cn/notice/。打开页面,如图:
说白了,计算机其实是模拟人的上网行为,模拟的越像越智能。下面来看,我们是如何来查询企业信用的,分几个步骤:
(1)打开浏览器,输入网址http://www.sgs.gov.cn/notice/,打开国家企业信用信息公示系统;
(2)再输入框中输入企业名称,比如“中国长城工业上海有限公司”,如图:
(3)企业名称输入没问题后,点击查询按钮,如图:
此时就出现了极验验证码指示图,这里还不是真正的极验验证码,它其实是极验验证码操作指导。看到图片上的小手指向滑块,忍不住会用鼠标点击滑块,触碰后,如图:
图片立即出现了一个缺口,缺口图片在缺口正左方。
(4)向右拖动滑块,慢慢与缺口重合,完成验证码拼合,成功拼合后,如图:
这里我猜你拖动滑块的速度至少经历了三个阶段,刚刚点击滑块时,先是慢慢移动,然后会快速移动,快到缺口附近时,你又降下速度来慢慢移动。在程序中,我们会模拟移动速度变化,有两个方案,第一个,随机移动,以中间为界先快后慢,第二个,加减速移动,使用物理加速度构建加速和减速模型。后面我们采用第一种方法。
(5)点击第一条查询结果,打开“中国长城工业上海有限公司”企业信用,如图:
在银行贷款等业务中,企业信用信息是参考因素之一,如果每个企业来办理贷款,都要去查询势必繁琐,而且效率低下,如果能将这些企业信息采集下来保存,并用于构建企业立体画像,然后再用于贷款是非常方便的。下面我们来用程序模拟人的整个查询流程。
环境准备:
(1)Python 3.6.5;
(2)pillow,selenium;
(3)chromedriver:https://chromedriver.storage.googleapis.com/index.html,版本:70.0.3538.16,chromedriver.exe需要拷贝到Python/Scripts目录下;
(4)Chrome:版本 71.0.3578.98(正式版本) (32 位)。
完整程序如下:
import random
import time
import re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import requests
from io import BytesIO
class CrawlCoder(object):
def __init__(self):
self.browser = webdriver.Chrome()
self.browser.set_window_size(1366, 768)
self.wait = WebDriverWait(self.browser, 10, 0.5)
def query(self):
"""根据输入的企业名称查询企业信用信息"""
# 访问页面
url = 'http://www.sgs.gov.cn/notice/'
self.browser.get(url)
# 等待查询输入框出现
keyword_id = 'keyword'
self.wait.until(EC.element_to_be_clickable((By.ID, keyword_id)))
# 根据ID找到查询输入框,输入查询企业名称
keyword_content = '中国长城工业上海有限公司'
self.browser.find_element_by_id(keyword_id).send_keys(keyword_content)
# 等待查询按钮出现
button_id = 'buttonSearch'
self.wait.until(EC.element_to_be_clickable((By.ID, button_id)))
# 根据ID找到查询按钮,单击查询(此时等待验证码的滑块出现……)
button = self.browser.find_element_by_id(button_id)
button.click()
def verify(self):
"""验证极验验证码"""
self.query()
# 等待滑块按钮出现
slider_button_xpath = '//div[@class="gt_slider_knob gt_show"]'
self.wait.until(EC.element_to_be_clickable((By.XPATH, slider_button_xpath)))
# 根据xpath获取到滑块按钮,将鼠标移动到滑块按钮,等待1s
# 鼠标触碰滑块才会出现有缺口的验证码
slider_button = self.browser.find_element_by_xpath(slider_button_xpath)
ActionChains(self.browser).move_to_element(slider_button).perform()
time.sleep(1)
# 根据切片坐标拼接图片
cut_xpath = '//div[@class="gt_cut_bg_slice"]'
full_xpath = '//div[@class="gt_cut_fullbg_slice"]'
cut_image = self.get_code_image(cut_xpath, name='cut.jpg')
full_image = self.get_code_image(full_xpath, name='full.jpg')
# 根据两个图片计算偏移量
distance = self.get_offset(cut_image, full_image)
# 开始移动
self.move(slider_button, distance)
# 根据移动结果,进行重试
self.retry()
def retry(self):
"""重试策略"""
try:
error_xpath = '//div[@class="gt_ajax_tip gt_error"]'
self.wait.until(EC.element_to_be_clickable((By.XPATH, error_xpath)))
print("验证失败")
return
except TimeoutException as e:
print(e)
# 判断是否验证成功
s = self.browser.find_elements_by_xpath('//*[@id="wrap1"]/div[3]/div/div/p')
if len(s) == 0:
print("滑动解锁失败,继续尝试")
self.verify()
else:
print("滑动解锁成功")
time.sleep(1)
# 打开查询结果
page_xpath = '//*[@id="wrap1"]/div[3]/div/div/div[2]'
ss = self.browser.find_element_by_xpath(page_xpath).get_attribute("onclick")
self.browser.find_element_by_xpath(page_xpath).click()
print(ss)
def get_code_image(self, xpath, name):
"""获取验证码图片
:return 验证码图片
:params
xpath 验证码xpath
name 验证码图片保存名称
"""
# 获取验证码图片切片url和位置坐标列表
# 每个切片url相同
link = re.compile('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;')
image_url = None
location = []
elements = self.browser.find_elements_by_xpath(xpath)
for element in elements:
style = element.get_attribute("style")
groups = link.search(style)
image_url = groups[1]
x_pos = groups[2]
y_pos = groups[3]
location.append((int(x_pos), int(y_pos)))
# 拼接图片
response = requests.get(image_url)
file = BytesIO(response.content)
img = Image.open(file)
# 创建一张画布,x_offset主要为新画布使用
upper_offset = 0
down_offset = 0
canvas = Image.new("RGB", (img.width, img.height))
for pos in location:
x = pos[0]
y = pos[1]
if y == 0:
# y值==0的图片属于上半部分,高度58
image_crop = img.crop((abs(x), 0, abs(x) + 10, 58))
canvas.paste(image_crop, (upper_offset, 58))
upper_offset += image_crop.width
if y == -58:
# y值==-58的图片属于下半部分
image_crop = img.crop((abs(x), 58, abs(x) + 10, img.height))
canvas.paste(image_crop, (down_offset, 0))
down_offset += image_crop.width
canvas.save('images/'+name)
return canvas
# 计算距离
@staticmethod
def get_offset(cut_image, full_image):
"""计算缺口偏移量
:return x 偏移量
"""
for x in range(cut_image.width):
for y in range(cut_image.height):
cpx = cut_image.getpixel((x, y))
fpx = full_image.getpixel((x, y))
for i in range(3):
if abs(cpx[i] - fpx[i]) > 50:
img = cut_image.crop((x, y, x + 50, y + 40))
# 保存一下计算出来位置图片,看看是不是缺口部分
img.save("gap.jpg")
return x
# 开始移动
def move(self, slider_button, distance):
"""移动滑块"""
# 这里就是根据移动进行调试,计算出来的位置不是百分百正确的,加上一点偏移
distance -= slider_button.size.get('width') / 2
distance += 15
# 按下鼠标左键
ActionChains(self.browser).click_and_hold(slider_button).perform()
time.sleep(0.5)
while distance > 0:
if distance > 10:
# 如果距离大于10,就让他移动快一点
span = random.randint(5, 8)
else:
# 快到缺口了,就移动慢一点
span = random.randint(2, 3)
ActionChains(self.browser).move_by_offset(span, 0).perform()
distance -= span
time.sleep(random.randint(10, 50) / 100)
ActionChains(self.browser).move_by_offset(distance, 0).perform()
ActionChains(self.browser).release(on_element=slider_button).perform()
if __name__ == "__main__":
coder = CrawlCoder()
coder.verify()
按照上面我们分解的操作步骤不难理解程序,为了更好的理解程序,加入了很多单行注释,在Python编码规范中约定:单行注释尽量少用,主要是影响代码阅读速度;类的每个方法必须要注释,写明方法参数,返回值等。本程序比较简单,这里就省略了。坚信:程序被阅读的次数总会比书写的次数要多。程序运行过程,如图:
注意鼠标没动哦(在右上角)。整个过程自动完成。破解率接近100%。