我们在做爬虫程序的时候,会遇到有些网站必须登录进去才能访问该站点内有价值的信息,而且这类站点一般会在登录的时候增加验证码的环节来阻碍爬虫程序的高频访问,其中有一类验证码就是滑块验证码,而且这类验证码对于爬虫程序员来说往往是不好处理的,在本篇文章就会介绍如何来破解这类验证码,为大家提供一种思路。
首先来介绍一下我们的主角,也就是我们要破解验证码的站点——极验验证码,这家公司是一家验证码服务的平台,他们自己的注册和登录接口就使用了滑块验证码的功能,本篇文章就是介绍如何来破解该站点的登录界面的滑块验证码,url为 https://auth.geetest.com/login/ ,另外该站点还提供了机器学习的方法来识别拖动滑块的轨迹,从而将判定为非人为的操作排除在外,如果拖动滑块的速度过快或者过慢或者匀速等等,都会被判定为机器操作,这就为我们的爬虫工作增加了难度,但是机制是人为定的,我们只有模拟的够逼真就可以通过对方的防御机制。
现在目标已经明确,那么就撸起袖子开始干吧
。
我们先来分析一下思路,为了解决这个问题要用到什么方法和工具。在登录表单中,有两个输入框,分别是邮箱和密码,一开始并没有出现验证码图片,需要填入邮箱之后再点击按钮才会出现验证码,
工具已经有了,再来想想思路吧,我们可以用selenium模拟输入邮箱,再点击按钮呼出验证码的图片,然后使用selenium将验证码图片保存姑且命名为image1吧,此时的验证码图片是一个完整的原图,我们需要点击一下图片下方的按钮,在短暂的震动之后图片会出现缺口,
此处应该有掌声……
接下来就要开始撸代码了。
我们用类来实现一系列功能代码,首先要配置需要的参数和实现初始化代码,我们的目的是获取验证码图片,只输入邮箱就可以了。
from faker import Faker
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
fk = Faker(locale='zh_CN')
email = fk.email() # 伪造email
driver_path = r'/home/python/Desktop/spider/chromedriver' # chromedriver所在路径
class GeetestLogin(object):
def __init__(self):
self.url = r'https://account.geetest.com/login'
self.browser = webdriver.Chrome(driver_path)
self.wait = WebDriverWait(self.browser, 10) # 显示等待
self.email = email
def __del__(self):
self.browser.close()
要获取验证码就需要输入邮箱,我们定义一个方法来封装这一步操作。
def input_email(self):
"""
打开网页输入邮箱
:return: None
"""
# 请求登录页面
self.browser.get(self.url)
# 显示等待10秒直到页面出现id为email的元素,并返回元素对象
input_email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
# 输入email
input_email.send_keys(self.email)
第二步就是该点击验证码按钮来呼出验证码。
def get_geetest_button(self):
"""
获取初始验证按钮,点击呼出验证码
:return:
"""
# 定位可点击的class为geetest_radar_tip的元素并返回元素
button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
button.click() # 点击按钮
第三步就是获取验证码图片所在的位置和宽高,然后获取整个网页的截图,通过验证码图片的位置元素将图片裁切出来。
1. 获取验证码的位置返回位置元素
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
2. 获取网页截图,返回截图对象
def get_screenshot(self):
"""
获取网页截图
:return: 截图对象
"""
screenshot = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
return screenshot
3. 获取验证码图片,保存图片,返回图片对象
def get_geetest_image(self, name='image.png'):
"""
获取验证码图片
:return: 图片对象
"""
top, bottom, left, right = self.get_position()
screenshot = self.get_screenshot()
captcha = screenshot.crop((left, top, right, bottom))
captcha.save(name)
return captcha
第四步就是获取滑块按钮并点击滑块呼出缺口,然后返回滑块对象。
def get_slider(self):
"""
获取滑块
:return: 滑块对象
"""
slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
slider.click()
return slider
第五步就是获取带缺口图片验证码对象并保存,这一步是重用以上第三步代码,已经封装,调用即可。
第六步就是对比不带缺口的验证码图片和带缺口的验证码图片从而获取偏移量,此步有需要注意的地方
def get_gap(self, image1, image2):
"""
获取缺口偏移量
:param image1: 不带缺口图片
:param image2: 带缺口图片
:return:
"""
left = 70 # 可根据像素微调(60-80)
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):
return i-6 # 减去大概6的偏移量(可微调)
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 / 7
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < distance:
if current < mid:
# 加速度为正2.2
a = 2.2
else:
# 加速度为负3.5
a = -3.5
# 初速度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 crawl(self):
# 输入邮箱
self.input_email()
# 点击按钮 呼出验证码
self.get_geetest_button()
# 获取验证码图片
image1 = self.get_geetest_image(name='image1.png')
# 点按呼出缺口
slider = self.get_slider()
# 获取带缺口的验证码图片
image2 = self.get_geetest_image(name='image2.png')
# 获取缺口位置
gap = self.get_gap(image1, image2)
print('gap:', gap)
# 减去缺口位移
# gap -= border
# 获取移动轨迹
track = self.get_track(gap)
# 拖动滑块
self.move_to_gap(slider, track)
try:
success = self.wait.until(
EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
except TimeoutException:
success = None
# print(success)
# 失败后重试
if not success:
self.crawl()
else:
self.login()
以上便完成了极验验证码登录界面的滑块验证码的破解,对于其他站点的滑块验证码的处理过程也可以参考此文章的流程。
完整代码,已分享到百度网盘,链接:https://pan.baidu.com/s/1oJklXeimpRWsi_jRzBNEEQ 密码:zec9








Mr.TR 发表于 2018-7-5 17:07
| 欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |