Class 18 - 1 图形验证码的识别
一、图形验证码的识别
- 先将验证码的图片保存到本。
- 打开开发者工具,找到验证码元素。验证码元素是一张图片,src 属性是 CheckCode.aspx。打开链接 http://my.cnki.net/elibregister/CheckCode.aspx,保存并命名为 code.jpg。
 
 
- 识别测试
- 新建一个项目,将验证码图片放到项目根目录下,用 tesserocr 库识别验证码,示例:
import tesserocr from PIL import Image image = Image.open('code.jpg') result = tesserocr.image_to_text(image) print(result)
这里新建了一个 Image 对象,调用 tesserocr 的 image_to_ text()方法。传入该 Image 对象 即可完成识别。
 - 
tesserocr 还有一个更加简单的方法,这个方法可直接将图片文件转为字符串,代码:
import tesserocr print(tesserocr.file_to_text('image.png'))
此方法的识别效果不如上一种方法好。
 
 - 新建一个项目,将验证码图片放到项目根目录下,用 tesserocr 库识别验证码,示例:
 - 
验证码处理
- 
换一个验证码带有多线条,命名为 code2.jpg。重新识别和实际结果有偏差。
 - 
对于有线条干扰情况,还需要做额外处理,如转灰度、二值化等操作。 可以利用 Image 对象的 convert()方法参数传人 L,即可将图片转化为灰度图像,示例:
image = image.convert('L') image.show()
传入1 即可将图片进行二值化处理:
image = image.convert('1') image.show()
还可以指定二值化的阈值,上面的方法采用的是默认阈(yù)值。以上方法采用默认阔值 127。不能直接转化原图,要将原图先转为灰度图像,再指定二值化阔值,代码:
import tesserocr from PIL import Image image = Image.open('code.jpg') image = image.convert('L') threshold = 80 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) image = image.point(table,'1') image.show()
变量 threshold 代表二值化阈值,阈值设置为 80。原来验证码中的线条已经去除,整个验证码将会变得黑向分明。这时再重新识别验证码,代码:
import tesserocr from PIL import Image image = Image.open('code2.jpg') image = image.convert('L') threshold = 127 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) image = image.point(table,'1') result = tesserocr.image_to_text(image) print(result)
针对一些有干扰的图片,做一些灰度和二值化处理,会提高图片识别的正确率。
 - 阈值:继续遍历图片。如果图片灰度值大于阈值说明是背景图片,将之颜色设置为白色,否则是文字,颜色设置为黑色
 - 
大概逻辑:
- 
(计次循环首(图片.取宽度(),x)
计次循环首(图片.取高度(),y)
‘遍历每一个像素点
‘取出遍历到的当前像素点颜色
颜色值=到字节集(图片.取某点颜色值(x-1,y-1))
‘计算灰度值
灰度值=(颜色值[1]+颜色值[2]+颜色值[3])/3
‘将灰度值放回图片
连续赋值(灰度值,颜色值[1],颜色值[2],颜色值[3])
图片.写某点颜色值(x-1,y-1,灰度值)
计次循环尾()
计次循环尾()
图片.取图片数据() 
 - 
 
 - 
 
二、极验滑动验证码的识别
- 识别思路
- 首先找到一个带有极验验证的网站,如极验官方后台,链接:https://account.geetest.com/login。
 
 - 
可以使用Selenium来完全模拟人的行为的方式来完成验证。(一般,验证需要三步:)
- 模拟点击验证按钮: 可以直接用Selenium 模拟点击按钮
 - 识别滑动缺口的位置:
 - 模拟拖动滑块
- 识别滑动缺口的位置的操作比较关键,需要用到图像相关处理方法
- 首先观察图像:缺口的四周边缘有明显的断裂边缘,边缘和边缘周围有明显的区别。可以实现一个边缘检测算法来找出缺口的位置。对于极验验证码来说,可以利用和原图对比检测的方式来识别缺口的位置,因为在没有滑动滑块之前, 缺口并没有呈现。
 - 
可以同时获取两张图片。设定一个对比阈值,然后遍历两张图片,找出相同位置像素 RGB 差距超过此阈值的像素点,那么此像素点的位置就是缺口的位置。
 
 - 模拟拖动滑块中需要走注意:极验验证码增加了机器轨迹识别,匀速移动、 随机速度移动等方法都不能通过验证,只有完全模拟人的移动轨迹才可以通过验证。 人的移动轨迹一般是先加速后减速,需要模拟这个过程才能成功。
 
 - 识别滑动缺口的位置的操作比较关键,需要用到图像相关处理方法
 
 - 初始化
- 选定链接:https://account.geetest.com/login,极验的管理后台登录页面。首先初始化一些配置,如 Selenium 对象的初始化及一些参数的配置,示例:
from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait EMAIL = 'test@test.com' PASSWORD = '123456' class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser,20) self.email = EMAIL self.password = PASSWORD
其中, EMAIL 和 PASSWORD 就是登录极验需要的用户名和密码,如果没有需先注册。
 
 - 选定链接:https://account.geetest.com/login,极验的管理后台登录页面。首先初始化一些配置,如 Selenium 对象的初始化及一些参数的配置,示例:
 - 
模拟点击
- 
实现第一步的操作,也就是模拟点击初始的验证按钮
- 
定义一个方法来获取这个按钮,利用显式等待的方法来实现,示例:
def get_geetest_button(self): "获取初始验证按钮:return:按钮对象" button = self.wait.unil(EC.element_to_be_clickable((By.CLASS_NAME,'geetest_radar_tip'))) return button
获取一个WebElement对象,调用它的click()方法即可模拟点击,示例:
#点击验证按钮 button = get_geetest_button() button.click()
完成第一步模拟点击验证按钮
 
 - 
 
 - 
 - 
识别缺口
- 
识别缺口的位置。
- 
首先获取前后两张比对图片,二者不一致的地方即为缺口 。获取不带缺口图片,利用 Selenium选取图片元素,得到其所在位置和宽高,然后获取整个网页的截图,图片裁切出来即可,代码:
def get_position(self): """获取验证码位置:return:验证码位置元组""" img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'geetest_canvas_img'))) time.sleep(2) location =img.location size = img.size top, bottom, left, right = location['y'],location['y']+size['weight'],location['x'],location['x']+size['width'] return(top, bottom, left, right) def get_geetest_image(self,name='captcha.png'): """获取验证码图片:return:图片对象""" top,bottom,left,right = self.get_position() print('验证码位置', top, bottom, left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) return captcha
这里 get_position()函数首先获取图片对象,获取它的位置和宽高,随后返回其左上角和右下角的坐标 get_geetest_image()方法获取网页截图,调用 crop()方法将图片裁切出来,返回的是 Image 对象
 - 接下来需要获取第二张图片,也就是带缺口的图片。要使得图片出现缺口,只需要点击下方的滑块即可。这个动作触发之后,图片中的缺口就会显现,代码:
def get_slider(self): """获取滑块:return:滑块对象""" slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'greetest_slider_button'))) return slider
利用 get_slider()方法获取滑块对象,调用 click()方法即可触发点击,缺口图片即可呈现,代码:
#点按呼出缺口 slider = self.get_slider() slider.click()
调用 get_geetest_image()方法将第二张图片获取下来即可。
 - 现在已经得到两张图片对象,分别赋值给变量 image1 和 image2。再对比图片获取缺口。这里遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据。如果二者 RGB 数据差距距在一定范围内,那就代表两个像素相同,继续比对下一个像素点 如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置,代码:
def is_pixel_equal(self, image1, image2, x, y): """判断两个像素是否相同 :param image1:图片1 :param image1:图片1 :param x:位置x :param y:位置y :return:像素是否相同""" #取两个图片的像素点 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(pixel1[2] -pixel2[2])<threshold: return True else: return False def get_gap(self, image1, image2): """获取缺口偏移量 :param image1:不带缺口图片 :param image2:带缺口图片 :return:""" left = 60 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
get_gap()方法即获取缺口位置的方法。此方法的参数是两张图片, 一张为带缺口图片,另一张为不带缺口图片。遍历两张图片的每个像素,利用 is_pixel_equal()方法判断两张图片同一位置的 像素是杏相同。比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold。如果绝对值均在阔值之 内,则代表像素点相同,继续遍历。否则代表不相同的像素点,即缺口的位置。
 - 滑块的位置会出现在左边位置,缺口会出现在与滑块同一水平线的位置,所以缺口一般会在滑块的右侧。如果要寻找缺口,直接从滑块右侧寻找即可。直接设置遍历的起始横坐标为 60,也就是从滑块的右侧开始识别,这样识别出的结果就是缺口的位置。
 
 - 
 
 - 
 - 模拟拖动
- 完全模拟加速减速的过程也就是人拖动滑块的操作通过验证。前段滑块做匀加速运动,后段滑块做匀减速运动,利用加速度公式即可完成验证。
- 滑块滑动的加速度用 a 表示, 当前速度用 v 表示,初速用 v0 表示,位移用 x 表示,所需时间用t 表示,满足关系:
- x = v0*t + 0.5*a*t*t
 - v = v0 + a*t
 
 - 利用这两个公式可以构造轨迹移动算法,计算出先加速后减速的运动轨迹,代码:
def get_track(self, distance): """根据偏移量获取移动轨迹 :param distance:偏移量 :return: 移动轨迹""" #移动轨迹 track = [] #当前位移 current = 0 #减速阈值 mid = distance * 4 / 5 #计算间隔 t = 0.2 #初速度 v = 0 while current < distance: if current < mid: a = 2 else: a = -3 #初速度 V0 v0 = v v = v0 + a*t move = v0 *t +1/2 *a *t *t current += move #加入轨迹 track.append(round(move)) return track
定义 get_ rack()方法,传人的参数为移动的总距离,返回的是运动轨迹。运动轨迹用 track 表示,是一个列表,列表的每个元素代表每次移动多少距离。
- 
定义变量 mid,即减速的阔值,也就是加速到什么位置开始减速。这里 mid 值为 4/5,即模拟前 4/5 路程是加速过程,后 1/5 路程是减速过程。
 - 接着定义当前位移的距离变量 current,初始为 0,然后进入 while 循环,循环的条件是当前位移小于总距离。在循环里我们分段定义了加速度,其中加速过程的加速度定义为 2,减速过程的加速度定义为 -3。之后套用位移公式计算出某个时间段内的位移,将当前位移更新并记录到轨迹里即可。
 - 直到运动轨迹达到总距离时,循环终止。最后得到的 track 记录了每个时间间隔移动了多少位移, 这样滑块的运动轨迹就得到了。
 - 最后按照该运动轨迹拖动滑块即可,方法实现代码:
def move_to_gap(self, slider,tracks): """拖动滑块到缺口处 :params slider:滑块 :params tracks:轨迹 :return:""" ActionChains(self,browser).click_and_hold(slider).perform() for x in tracks: ActionChains(self,browser).move_by_offset(xoffset=x,yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform()
传人的参数为滑块对象和运动轨迹。首先调用 ActionChains 的 click_and_ hold()方法按住拖动底部滑块,遍历运动轨迹获取每小段位移距离,调用 move_by_offset()方法移动此位移,最后调用 release()方法松开鼠标即可。
 
 - 
 
 - 滑块滑动的加速度用 a 表示, 当前速度用 v 表示,初速用 v0 表示,位移用 x 表示,所需时间用t 表示,满足关系:
 
 - 完全模拟加速减速的过程也就是人拖动滑块的操作通过验证。前段滑块做匀加速运动,后段滑块做匀减速运动,利用加速度公式即可完成验证。
 - 
完整代码(需适当修改参数):
View Codeimport time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC EMAIL = 'cqc@cuiqingcai.com' PASSWORD = '' BORDER = 6 INIT_LEFT = 60 class CrackGeetest(): def __init__(self): self.url = 'https://account.geetest.com/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 20) self.email = EMAIL self.password = PASSWORD def __del__(self): self.browser.close() def get_geetest_button(self): """ 获取初始验证按钮 :return: """ button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip'))) return button def get_position(self): """ 获取验证码位置 :return: 验证码位置元组 """ img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img'))) time.sleep(2) location = img.location size = img.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] return (top, bottom, left, right) def get_screenshot(self): """ 获取网页截图 :return: 截图对象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_slider(self): """ 获取滑块 :return: 滑块对象 """ slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button'))) return slider def get_geetest_image(self, name='captcha.png'): """ 获取验证码图片 :return: 图片对象 """ top, bottom, left, right = self.get_position() print('验证码位置', top, bottom, left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def open(self): """ 打开网页输入用户名密码 :return: None """ self.browser.get(self.url) email = self.wait.until(EC.presence_of_element_located((By.ID, 'email'))) password = self.wait.until(EC.presence_of_element_located((By.ID, 'password'))) email.send_keys(self.email) password.send_keys(self.password) def get_gap(self, image1, image2): """ 获取缺口偏移量 :param image1: 不带缺口图片 :param image2: 带缺口图片 :return: """ left = 60 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 is_pixel_equal(self, image1, image2, x, y): """ 判断两个像素是否相同 :param image1: 图片1 :param image2: 图片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 取两个图片的像素点 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 60 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def get_track(self, distance): """ 根据偏移量获取移动轨迹 :param distance: 偏移量 :return: 移动轨迹 """ # 移动轨迹 track = [] # 当前位移 current = 0 # 减速阈值 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正2 a = 2 else: # 加速度为负3 a = -3 # 初速度v0 v0 = v # 当前速度v = v0 + at v = v0 + a * t # 移动距离x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track def move_to_gap(self, slider, track): """ 拖动滑块到缺口处 :param slider: 滑块 :param track: 轨迹 :return: """ ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform() def login(self): """ 登录 :return: None """ submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn'))) submit.click() time.sleep(10) print('登录成功') def crack(self): # 输入用户名密码 self.open() # 点击验证按钮 button = self.get_geetest_button() button.click() # 获取验证码图片 image1 = self.get_geetest_image('captcha1.png') # 点按呼出缺口 slider = self.get_slider() slider.click() # 获取带缺口的验证码图片 image2 = self.get_geetest_image('captcha2.png') # 获取缺口位置 gap = self.get_gap(image1, image2) print('缺口位置', gap) # 减去缺口位移 gap -= BORDER # 获取移动轨迹 track = self.get_track(gap) print('滑动轨迹', track) # 拖动滑块 self.move_to_gap(slider, track) success = self.wait.until( EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功')) print(success) # 失败后重试 if not success: self.crack() else: self.login() if __name__ == '__main__': crack = CrackGeetest() crack.crack()
 
三、点触验证码的识别
- 利用在线付费平台(推荐超级鹰https://www.chaojiying.com)
- 获取API
- 在官网下载对应 Python API,链接:https://www.chaojiying.com/api-14.html。使用requests库来实现。
 - 修改后的python3版本的API:
import requests from hashlib import md5 class Chaojiying(object): def __init__(self, username, password, soft_id): self.username = username self.password = md5(password.encode('utf-8')).hexdigest() self.soft_id = soft_id self.base_params = { 'user':self.username, 'pass2':self.password, 'softid':self.soft_id, } self.headers = { 'Connection':'Keep_Alive', 'User-Agent':'Mozilla/4.0(compatible;MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def post_pic(self, im, codetype): """ im: 图片字节 codetype: 题目类型参考 http://www.chaojiying.com/price.html """ params = { 'codetype':codetype, } params.update(self.base_params) files = {'userfile':('ccc.jpg',im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def report_error(self,im_id): """ im_id:报错题目的图片ID """ params = { 'id':im_id, } params.update(self.base_params) r = requests.post('http://Upload.chaojiying.net/Upload/ReportError.php',data=params, headers= self.headers) return r.json()
这里定义一个chaojiying类,构造函数接收三个参数,分别是超级鹰用户名、密码以及软件ID,保存以备使用。最重要的一个方法:post_pic(),需要传入图片对象和验证码的代号,该方法会将图片对象和相关信息发给超级鹰后台进行识别,然后将识别成功的JSON返回
 
 - 初始化
- 首先初始化一些变量,如WebDriver、chaojiying对象等,代码实现如下:
EMAIL = 'Mack01' PASSWORD = '超级鹰密码' #超级鹰用户名、密码、软件ID、验证码类型 CHAOJIYING_USERNAME = 'Germey' CHAOJIYING_PASSWORD = '' CHAOJIYING_SOFT_ID = 893590 CHAOJIYING_KIND = 9102 class CrackTouClick(): def __init__(self): self.url = 'http://admin.touclick.com/login.html' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser,20) self.email = EMAIL self.password =PASSWORD self.chaojiying = Chaojiying(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)
 
 - 首先初始化一些变量,如WebDriver、chaojiying对象等,代码实现如下:
 - 
获取验证码
- 
完善表单,模拟点击呼出验证码,代码:
def open(self): """ 打开网页输入用户名密码 return: None """ self.browser.get(self.url) email =self.wait.until(EC.present_of_element_located((By.ID,'email'))) password = self.wait.until(EC.present_of_element_located((By.ID,'password'))) email.send_keys(self.email) password.send_keys(self.password) def get_touclick_button(self): """ 获取初始验证按钮 return: """ button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'touclick-hod-wrap'))) return button
open()方法负责填写表单,get_touclick_button()方法获取验证码按钮,之后触发点击即可。
接下来,类似极验验证码图像获取一样,获取验证码图片的位置和大小,从网页截图里截取相应 的验证码图片,代码实现如下所示:def get_touclick_element(self): """ 获取验证图片对象 return: 图片对象 """ element = self.wait.until(EC.present_of_element_located((By.CLASS_NAME,'touclick-pub-content'))) return element def get_position(self): """ 获取验证码位置 return: 验证码位置元组 """ element =self.get_touclick_element() time.sleep(2) location = element.location size = element.size top, bottom, left, right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width'] return (top, bottom, left, right) def get_screenshot(self): """ 获取网页截图 return: 截图对象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_touclick_image(self, name='captcha.png'): """ 获取验证码图片 return: 图片对象 """ top,bottom,left,right =self.get_position() print('验证码位置',top,bottom, left ,right) screenshot = self.get_screenshot() captcha = screenshot.crop((left,top,right,bottom)) return captcha
get_ touclick_image()方法即为从网页截图中截取对应的验证码图片,其中验证码图片的相对位 置坐标由 get position()方法返回得到。 最后我们得到的是 Image 对象
 
 - 
 - 
识别验证码
- 
调用 Chaojiying 对象的 post_pic()方法,即可把图片发送给超级鹰后台,这里发送的图像是字节 流格式,代码实现如下所示:
image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') #识别验证码 result = self.chaojiying.post_pic(bytes_array.getvalue(),CHAOJIYING_KIND) print(result)
运行之后, result 变量就是超级鹰后台的识别结果。 运行需要等待几秒。
返回的结果是一个 JSON。 如果识别成功,典型的返回结果如下所示 :
{’ err_no’:0, 'err_str’: 'OK', 'pic_id’: ’6002001380949200001', 'pic_str' :'132,127|56, 77 ','md5': '1f8e1d4bef8b11484cb1f1f34299865b'}
其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以|分隔。接下来只需要将其解析,然后模拟点击,代码:def get_points(self, captcha_result): """ 解析识别结果 param captcha_result: 识别结果 return: 转化后的结果 """ groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations def touch_click_words(self,locations): """ 点击验证图片 param locations:点击位置 return: None """ for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(),location[0],location[1]).click().perform() time.sleep(1)
这里用 get_points()方法将识别结果变成列表的形式。touch_click_words()方法则通过调用 move_to_element_with_offset()方法依次传入解析后的坐标,点击即可。
这样就模拟完成坐标的点选,最后点击提交验证的按钮, 等待验证通过,再点击登录按钮即可成功登录。 
 - 
 
 - 获取API
 
四、微博宫格验证码的识别
- 
一般选用全图匹配的方式来进行识别。找到匹配的模板之后,就可以得到事先为模板定义的拖动顺序,然后模拟拖动即可。
 
- 获取模板
- 需要做一下准备工作,先保存所有的 24 张验证码全图。因为验证码是随机的,一共有 4!= 24 种。可以写一段程序来批量保存验证码图片,然后从中筛选出需要的图片,代码所示:
import time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC USERNAME = '' PASSWORD = '' class CrackWeiboSlide(): def __init__(self): self.url = 'https://passport.weibo.cn/signin/login' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser,20) self.username = USERNAME self.password = PASSWORD def __del__(self): self.browser.close() def open(self): """ 打开网页输入用户名密码并点击 return: None """ self.browser.get(self.url) username = self.wait.until(EC.presence_of_element_located((By.ID,'loginName'))) password = self.wait.until(EC.presence_of_element_located((By.ID,'loginPassword'))) submit = self.wait.until(EC.element_to_be_clickable((By.ID,'loginAction'))) username.send_keys(self.username) password.send_keys(self.password) submit.click() def get_position(self): """ 获取验证码位置 return: 验证码位置元组 """ global img try: img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'Patt-shadow'))) except TimeoutException: print('未出现验证码') self.open() time.sleep(2) location = img.location size = img.size top,bottom, left, right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width'] return (top, bottom, left, right) def get_screenshot(self): """ 获取网页截图 return: 截图对象 """ screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_image(self, name='captcha.png'): """ 获取验证码吗图片 return: 图片对象 """ top, bottom, left, right = self.get_position() print('验证码位置', top, bottom,left, right) screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def main(self): """ 批量获取验证码 return: 图片对象 """ count = 0 while True: self.open() self.get_image(str(count)+'.png') count +=1 if __name__ =='__main__': crack = CrackWeiboSlide() crack.main()
只需要挑选出不同的 24 张验证码图片并命名保存。 名称可以直接取作宫格的滑动的顺序,识别过程只需要遍历模板进行匹配。
 
 - 需要做一下准备工作,先保存所有的 24 张验证码全图。因为验证码是随机的,一共有 4!= 24 种。可以写一段程序来批量保存验证码图片,然后从中筛选出需要的图片,代码所示:
 - 模板匹配
- 
调用 get_image()方法,得到验证码图片对象。然后,对验证码图片对象进行模板匹配,定义如下方法:
from os import listdir def detect_image(self,image): """ 匹配图片 param image: 图片 return: 拖动顺序 """ for template_name in listdir(TEMPLATES_FOLDER): print('正在匹配', template_name) template = Image.open(TEMPLATES_FOLDER + template_name) if self.same_image(image, template): #返回顺序 numbers = [int(number) for number in list(template_name.split('.')[0])] print('拖动顺序',numbers) return numbers
TEMPLATES_FOLDER 就是模板所在的文件夹。 通过 listdir()方法获取所有模板的文件名称,然后对其进行遍历,通过 same_image()方法对验证码和模板进行比对。 如果匹配成功,就将匹配到的模板文件名转换为列表。例:模板文件 3124.png匹配到,返回结果为[3,1,2,4].
 - 
对比的方法实现如下:
def is_pixel_equal(self, image1, image2, x, y): """ 判断两个像素是否相同 :param image1: 图片1 :param image2: 图片2 :param x: 位置x :param y: 位置y :return: 像素是否相同 """ # 获取两个图片的像素点 pixel1 = image1.load()[x, y] pixel2 = image2.load()[x, y] threshold = 20 if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs( pixel1[2] - pixel2[2]) < threshold: return True else: return False def same_image(self, image, template): """ 识别相似验证码 param image: 待识别验证码 param template: 模板 return: """ # 相识度阈值 threshold = 0.99 count = 0 for x in range(image.width): for y in range(image.height): # 判断像素是否相同 if self.is_pixel_equal(image, template, x, y): count += 1 result = float(count) / (image.width * image.height) if result > threshold: print('功能匹配') return True return False
在这里比对图片也利用了遍历像素的方法。 same_image()方法接收两个参数,image 为待检测的验证码图片对象,template 是模板对象。由于二者大小是完全一致的,所以在这里遍历了图片的所有像素点。比对二者同一位置的像素点,如果像素点相同,计数就加 1。最后计算相同的像素点占总像素的比例。如果该比例超过一定阈值,那就判定图片完全相同,则匹配成功。这里阔值设定为 0.99, 即如果二者有 0.99 以上的相似比,则代表匹配成功。 依次匹配 24 个模板。 如果验证码图片正常,我们总能找到一个匹配的模板, 这样就可以得到宫格的滑动顺序了。
 
 - 
 - 
模拟拖动
- 
根据滑动顺序拖动鼠标,连接各个宫格,方法实现如下所示:
def move(self, numbers): """ 根据顺序拖动 """ #获得四个按点 circles = self.browser.find_elements_by_css_selector('.patt-wrap. patt-circ') dx = dy =0 for index in range(4): circle = circles[numbers[index]-1] #如果是第一次循环 if index == 0: # 点击第一个按点 ActionChains(self.browser)\.move_to_element_with_offset(circle, circle.size['width']/2, circle.size['height']/2 )\.click_and_hold().perform() else: #小幅移动次数 times = 30 #拖动 for i in range(times): ActionChains(self.browser).move_by_offset(dx/times, dy/times).perform() time.sleep(1/times) # 如果是最后一次循环 if index == 3: #松开鼠标 ActionChains(slef.browser).release().perform() else: #计算下一次偏移 dx = circles[numbers[index +1]-1].location['x'] - circle.location['x'] dy = circles[numbers[index +1]-1].location['y'] - circle.location['y']
这里方法接收的参数就是宫格的点按顺序,如 [3,1,2,4]。首先利用 find_elements_by_css_selector()方法获取到 4 个宫格元素,它是一个列表形式,每个元素代表一个宫格。接下来遍历宫格的点按顺序,做一系列对应操作。
其中如果当前遍历的是第一个宫格,那就直接鼠标点击并保持动作,否则移动到下一个宫格。如果当前遍历的是最后一个宫格,那就松开鼠标,如果不是最后一个宫格,则计算移动到下一个宫格的偏移盘。
通过 4 次循环,便可以成功操作浏览器完成宫格验证码的拖拽填充,松开鼠标之后即可识别成功。 鼠标会慢慢从起始位置移动到终止位置。 最后一个宫格松开之后,验证码的识别便完成了。
 
 - 
 
                    
                

                
            
        
浙公网安备 33010602011771号