一:图形验证码的识别
1.准备工作
①安装pytesseract pip install pytesseract
②在安装过程中存在坑,实际运行时提示路径不在指定位置,处理方式参考
https://blog.csdn.net/wang_hugh/article/details/80760940
2.识别测试
①图片中数字和字母清晰,无噪声
from PIL import Image
import pytesseract
image=Image.open(r'C:\Users\wcl\Desktop\image.png')
result=pytesseract.image_to_string(image)
print(result)
②验证码图片内存在干扰信息,需要转灰度、二值化、改变二值化的阈值等操作
将图片转化为灰度图像
image=image.convert('L')
将图片进行二值化处理,传入数字1
image=image.convert('1')
有干扰图片的处理
from PIL import Image
import pytesseract
image=Image.open(r'C:\Users\wcl\Desktop\image.png')
image=image.convert('L')
threshold=127
table=[]
for i in range(256):
if i
二:极验滑动验证码的识别
1.流程
包括分析识别思路、识别缺口位置、生成滑块拖动路径、模拟实现滑块拼合通过验证等步骤
2.以魅族网站为例
思路:
①模拟点击验证按钮
②识别滑动缺口的位置
?模拟拖动滑块
3.过程
①第一步比较简单,直接用selenium模拟点击按钮
②识别缺口的位置比较关键,需要用到图像相关的处理方法。
分为若干种情况:
a.有原图和有缺口的图
分别截图原图和有缺口的图,通过对比像素,找到两者之间的距离
b.只有有缺口的图
存在某种规律,比如每类滑动滑块滑动的间距一致,这类只需要将两者间距填入即可
c.只有有缺口的图2
两者间距不一致,存在变化,需要将图片切割,然后再合成,此类比较麻烦(不会)
4.详细介绍前两种验证码的识别
①只有有缺口的图1:滑动间距固定
from selenium import webdriver
import time
from selenium.webdriver.common.action_chains import ActionChains
#模拟拖动
def get_track(distance):
'''
根据偏移量获取移动轨迹
:param distance: 偏移量
:return: 移动轨迹
'''
#移动轨迹
track=[]
#当前位移
current=0
#减速阈值
mid=distance*4/5
#计算间隔
t=0.2
#初速度
v=0
while current跳转到需要输入账户和密码的界面
driver.find_element_by_xpath('/html/body/div[1]/div[1]/ul[1]/li[2]').click()
driver.find_element_by_xpath('//*[@id="username"]').send_keys('your username')
driver.find_element_by_xpath('//*[@id="password"]').send_keys('your password')
#输入账户和密码后,点击登陆豆瓣
driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/div[5]/a').click()
#有两种情况,一种不需要验证码直接成功进入主页,另一种是登陆过于频繁,需要输入验证码
time.sleep(2)
if '我的豆瓣' in driver.page_source:
return driver.page_source
else:
# 滑块位于iframe框架中
driver.switch_to.frame(0)
# 定位图片下面的滑动按钮
element = driver.find_element_by_xpath('//*[@id="tcaptcha_drag_thumb"]')
# 点击滑动按钮
ActionChains(driver).click_and_hold(on_element=element).perform()
# 拖动
for x in track:
ActionChains(driver).move_by_offset(xoffset=x, yoffset=0).perform()
# 松开按钮
time.sleep(0.5)
ActionChains(driver).release().perform()
time.sleep(2)
return driver.page_source
if __name__=='__main__':
#distance为实际滑动块和缺口之间的距离,根据实际情况而定
distance = 180
#track为使用加速运动拖动的位移
track=get_track(distance)
response=main(distance)
print(response)
②可以同时获取到原图和有缺口的图
a.初始化
选定连接为https://auth.geetest.com/login/,初始化一些配置,如Selenium对象的初始化及一些参数的配置
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
EMAIL="[email protected]"
PASSWORD="123456"
class GrackGeetest():
def __init__(self):
self.url='https://auth.geetest.com/login'
self.browser=webdriver.Chrome()
self.wait=WebDriverWait(self.browser,20)
self.email=EMAIL
self.password=PASSWORD
b.模拟点击
def get_geetest_button(self):
button=self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'geetest_radar_tip')))
return button
获取WebElement对象,调用它的click()方法即可模拟点击,
#点击验证按钮
button=self.get_geetest_button()
button.click()
c.识别缺口
首先获取前后两张比对图片,二者不一致的地方即为缺口。获取不带缺口的图片,利用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#{'x': 34, 'y': 462}图片左上角点的位置
size = img.size#{'height': 170, 'width': 170}图片的宽高
top, bottom, left, right = location['y'], location['y'] + size['height'], 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))
captcha.save(name)
return captcha
#知道固定图块的位置,即可将其从大图中裁剪出来
def get_screenshot(self):
"""
获取网页截图
:return: 截图对象
"""
screenshot = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
return screenshot
get_position()函数首先获取图片对象,获取它的位置和宽高,随后返回其左上角和右下角的坐标。get_geetest_image()方法获取网页截图,调用crop()方法将图片裁切出来,返回的是Image对象。
接下来需要获取第二张图片,就是带缺口的图片。要使得图片出现缺口,只需要点击下发给的滑块即可。这个动作触发之后,即可看到缺口图片。
def get_slider(self):
"""
获取滑块
:return: 滑块对象
"""
slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
return slider
现在可以得到两张图片,分别赋值给image1和image2。接下来对不图片获取缺口,遍历图片的每一个坐标点,获取两张图片对应像素点的RGB数据。如果二者的RGB数据差距在一定范围内,就代表两个像素相同,继续遍历下一个像素点。如果差距超过一定范围,则代表像素点不同,当前位置为缺口的位置
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_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,也就是在滑块的右侧开始识别,这样识别出来的结果就是缺口的位置。
d.模拟拖动
需要模拟真实的人手操作,前段滑块做匀加速运动,后段做匀减速运动。滑块滑动的加速度用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:
# 加速度为正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
直到运功轨迹达到总距离时,循环终止。最后得到的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()
传入参数为滑块对象和运动轨迹。首先调用ActionChains的click_and_hold()方法按住拖动底部滑块,遍历运动轨迹获取每小段位移距离,调用move_by_offset()方法移动此位移,最后调用release()方法松开鼠标。
整体代码:
#崔大神的代码,https://github.com/Python3WebSpider/CrackGeetest/blob/master/crack.py
import 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 = '[email protected]'
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()