前言
做爬虫时,难免会遇到需要通过验证码才能访问网址进行爬取内容,所以需要额外掌握破解验证码的技巧才行。
本文将讲解另一个验证码示例来进行演示(滑动验证码)
上一篇写了: 破解验证码(1)数字英文验证码,可跳转到:https://blog.csdn.net/ytraister/article/details/106307610
实例
- 以腾讯防水墙滑动验证码为例,进行破解
python + selenium + cv2
- 验证码地址:https://open.captcha.qq.com/online.html
- 使用OpenCV库,需要额外安装:
pip install opencv-python
- 效果如下图:
》完整代码如下(含解析):
实际开发中,可以根据自己的需求进行改动
import numpy as np # 矩阵计算的函数库
import random
from selenium.webdriver import ActionChains # 控制鼠标操作
import time
from selenium import webdriver
from PIL import Image # Python图像库
import os
from selenium.webdriver.support.ui import WebDriverWait
import cv2 # 强大的图像处理和计算机视觉库
class Login(object):
def __init__(self):
# 初始化方法:url、driver(浏览器驱动)
# 如果是实际应用中,可在此处添加账号和密码
self.url = "https://open.captcha.qq.com/online.html"
self.driver = webdriver.Chrome()
@staticmethod
def show(name):
# 调用窗口显示图片,以下3步骤是必须的
cv2.imshow("Show", name) # 参数1:窗口名称;参数2:需要显示的图片
cv2.waitKey(0)
cv2.destroyAllWindows()
@staticmethod
def webdriverwait_send_keys(dri, element, value):
"""
显示等待 输入
:param dri: 浏览器驱动driver
:param element: 通过selenium获取到的html元素
:param value: 需要传入的值
"""
WebDriverWait(dri, 10, 5).until(lambda dr: element).send_keys(value)
@staticmethod
def webdriverwait_click(dri, element):
"""
显示等待 click
:param dri: 浏览器驱动driver
:param element: 通过selenium获取到的html元素
"""
WebDriverWait(dri, 10, 5).until(lambda dr: element).click()
# 当需要多次调用该显示等待函数时,可以使用匿名函数lambda来获取元素 lambda函数接收一个参数word(即上面的dr)。且在冒号和末尾圆括号之间的部分为函数的定义。
@staticmethod
def get_position(chunk, canves):
"""
判断缺口位置
:param chunk: 缺口图片(验证码中的大图)
:param canves: 验证码中的拼图
:return: 位置 x, y
"""
# cv2.imread()用于读取图片文件;参数1:图片路径,参数2:读取图片的形式(1表示彩色图片[默认],0表示灰度图片,-1表示原来的格式)
chunk = cv2.imread(chunk, 0) # 读取大图(灰化)
canves = cv2.imread(canves, 0) # 读取拼图(灰化)
# 二值化后的图片名称
slide_puzzle = "slide_puzzle.jpg"
slide_bg = "slide_bg.jpg"
# 将二值化后的图片进行保存
# cv2.imwrite()用于保存图片文件;参数1:保存的图像名称,参数2:需要保存的图像
cv2.imwrite(slide_bg, chunk) # 保存大图
cv2.imwrite(slide_puzzle, canves) # 保存拼图
chunk = cv2.imread(slide_bg) # 使用cv2.imread()读出来的是BGR数据格式
# cv2.cvtColor(p1, p2) 是颜色空间转换函数 参数1:需要转换的图片,参数2:转换成何种格式
# cv2.COLOR_BGR2RGB:将BGR格式转换成RGB格式 cv2.COLOR_BGR2GRAY:将BGR格式转换成灰度图片
chunk = cv2.cvtColor(chunk, cv2.COLOR_BGR2GRAY)
chunk = abs(255 - chunk) # abs用于返回数字的绝对值
cv2.imwrite(slide_bg, chunk) # 保存大图 此时大图缺口部分的颜色比较浅,图像较容易识别到该缺口部分
chunk = cv2.imread(slide_bg) # 读取大图
canves = cv2.imread(slide_puzzle) # 读取拼图
# 获取偏移量
result = cv2.matchTemplate(chunk, canves, cv2.TM_CCOEFF_NORMED) # cv2.matchTemplate()实现模板匹配(即用来在大图中找小图,也就是说在一副图像中寻找另外一张模板图像的位置)
# 返回的result是一个矩阵,是每个点的匹配结果
# Login.show(chunk) # 展示大图二值化后的效果
# result.argmax()返回的是矩阵中最大数值的下标; result.shape 返回的是(680, 390)即下载的图片对象的尺寸
y, x = np.unravel_index(result.argmax(), result.shape) # np.unravel_index()作用:获取一个/组 int类型的索引值在一个多维数组中的位置 计算出 大图 与 拼图 位移的距离
return x # 实际滑动的距离就是x轴的距离,因此只需要x的值即可
@staticmethod
def get_track(distance):
"""
模拟轨迹 假装是人在操作
匀变速运动基本公式:
①v=v0+at 速度公式
②s=v0t+(1/2)at² 位移公式
:param distance: 移动的距离
:return: track
"""
v = 0 # 初速度
t = 0.2 # 时间间隔0.2s
tracks = [] # 移动轨迹列表,列表内的每一个元素代表0.2s的位移
current = 0 # 当前的位移
mid = distance * 4 / 5 # 到达mid值开始减速(前4/5段加速 后1/5段减速)
distance += 10 # 先滑过一点,最后再反着滑动回来
while current < distance:
if current < mid:
# 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
a = random.randint(28, 30) # 加速运动
else:
a = -random.randint(12, 13) # 减速运动
v0 = v # 初速度
s = v0 * t + 0.5 * a * (t ** 2) # 0.2秒时间内的位移
current += s # 当前的位置
tracks.append(round(s)) # 添加到轨迹列表,round()为保留一位小数且该小数要进行四舍五入
v = v0 + a * t # 速度已经达到v,该速度作为下次的初速度
# 反着滑动到大概准确位置
for i in range(4):
tracks.append(-random.randint(3, 4))
return tracks
@staticmethod
def urllib_download(imgurl, imgsavepath):
"""
下载图片
:param imgurl: 需要下载图片的url
:param imgsavepath: 图片存放位置
"""
# python3中的urllib.request模块所提供的urlretrieve()函数。urlretrieve()方法可以直接将远程数据下载到本地
from urllib.request import urlretrieve
urlretrieve(imgurl, imgsavepath)
def after_quit(self):
"""
关闭浏览器
"""
self.driver.quit()
def login_main(self):
# ssl._create_default_https_context = ssl._create_unverified_context # 用于忽略网页的SSL认证,需要额外导入import ssl
driver = self.driver
driver.maximize_window()
driver.get(self.url)
# 获取到元素并实现点击
click_keyi_username = driver.find_element_by_xpath("//div[@class='wp-onb-tit']/a[text()='可疑用户']")
self.webdriverwait_click(driver, click_keyi_username)
login_button = driver.find_element_by_id('code')
self.webdriverwait_click(driver, login_button)
time.sleep(1)
driver.switch_to.frame(driver.find_element_by_id('tcaptcha_iframe')) # 用于获取到验证码iframe元素
time.sleep(0.5)
bk_block = driver.find_element_by_xpath('//img[@id="slideBg"]') # 获取验证码中的大图
web_image_width = bk_block.size # 获取大图的大小(高度、宽度)
web_image_width = web_image_width['width'] # 获取大图的宽度
# print(bk_block.location) # 该图片对象在弹出的验证码框中的位置,返回字典的格式,例如:{'x': 10, 'y': 61}
bk_block_x = bk_block.location['x'] # 获取该图片对象在验证码框中的位置(x轴)
slide_block = driver.find_element_by_xpath('//img[@id="slideBlock"]') # 获取验证码中的拼图
# print(bk_block.location) # 该图片对象在弹出的验证码框中的位置,返回字典的格式,例如:{'x': 36, 'y': 102}
slide_block_x = slide_block.location['x'] # 获取该图片对象在验证码框中的位置(x轴)
bk_block = driver.find_element_by_xpath('//img[@id="slideBg"]').get_attribute('src') # 获取验证码中的大图url
slide_block = driver.find_element_by_xpath('//img[@id="slideBlock"]').get_attribute('src') # 获取验证码中的拼图url
slid_ing = driver.find_element_by_xpath('//div[@id="tcaptcha_drag_thumb"]') # 获取滑块
os.makedirs('./image/', exist_ok=True) # os.makedirs():递归创建多层目录;其中参数exist_ok=True:表示在目标目录已存在的情况下不会抛出异常(即不会报错)
self.urllib_download(bk_block, './image/bkBlock.png') # 下载验证码中的大图
self.urllib_download(slide_block, './image/slideBlock.png') # 下载验证码中的拼图
time.sleep(0.5)
img_bkblock = Image.open('./image/bkBlock.png') # 因为下载的大图跟浏览器中显示的大图比例不一样,会影响到滑动的距离跟速度,所以计算出比例后面用到
real_width = img_bkblock.size[0] # 获取下载的验证码中大图的宽度:680
width_scale = float(real_width) / float(web_image_width) # 得出比例 [下载的大图大小为:680*390;浏览器显示的大图大小为:340.81*198.45(大概)]
position_x = self.get_position('./image/bkBlock.png', './image/slideBlock.png') # 获取到 大图 与 拼图 位移的距离 (实际滑动的距离就是x轴的距离)
real_position = position_x / width_scale # 将大图/比例,得到验证码框中大图与拼图实际的滑动距离
real_position = real_position - (slide_block_x - bk_block_x) # (slide_block_x - bk_block_x):即拼图到大图的左边距,所以减去左边距后才得到真正的滑动距离
track_list = self.get_track(real_position) # 调用get_track()方法,传入真实距离参数,得出移动轨迹
ActionChains(driver).click_and_hold(on_element=slid_ing).perform() # 找到滑块元素,点击鼠标左键,按住不放
time.sleep(0.2)
# 拖动元素
for track in track_list:
ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform() # 根据运动轨迹(x轴),进行拖动
time.sleep(0.002)
time.sleep(1)
ActionChains(driver).release(on_element=slid_ing).perform() # 释放鼠标
time.sleep(1)
print('登录成功')
self.after_quit()
if __name__ == '__main__':
login = Login()
login.login_main()
》参考文献:
https://blog.csdn.net/qq_38534107/article/details/89522481
https://blog.csdn.net/Tracy_LeBron/article/details/84567419