共2个文件。
"""
web自动化的环境安装:
1、selenium:
安装:pip install selenium
2、安装chromedriver:
案例需求:解决web自动化过程中遇到的滑动验证码验证的问题:
解决方案:自动识别滑动距离,进行滑动验证
"""
import time
from selenium import webdriver
from py0723_slide.slideVerfication import SlideVerificationCode
# 第一步:打开qq空间登录页面:
# 1.1启动浏览器
driver = webdriver.Chrome()
driver.implicitly_wait(10)
# 1.2访问qq空间登录页面
driver.get("https://qzone.qq.com/")
# 1.3点击账号密码登陆按钮
# 1.3.1切换到登录iframe中
driver.switch_to.frame("login_frame")
# 1.3.2点击打开账号密码登录
driver.find_element_by_id("switcher_plogin").click()
# 第二步:输入账号密码,点击登录
# 2.1定位账号输入框,输入账号
driver.find_element_by_id('u').send_keys("350978786")
# 2.2定位密码输入框,输入密码
driver.find_element_by_id('p').send_keys("12131311")
# 2.3 点击登录
driver.find_element_by_id('login_button').click()
# 第三步:进行滑动验证
# 3.1定位验证码所在的iframe,并进行切换
v_frame = driver.find_element_by_id('tcaptcha_iframe')
driver.switch_to.frame(v_frame)
# 3.2获取验证码滑块图元素
sli_ele = driver.find_element_by_id('slideBlock')
# 3.3获取验证码背景图的元素
bg_ele = driver.find_element_by_id('slideBg')
# 3.4 识别滑块需要滑动的距离
# 3.4.1识别背景缺口位置
sv = SlideVerificationCode()
distance = sv.get_element_slide_distance(sli_ele,bg_ele )
# 3.4.2 根据页面的缩放比列调整滑动距离
dis = (distance * 280/680)-30
# 3.5 获取滑块按钮
sli_btn =driver.find_element_by_id('tcaptcha_drag_thumb')
# 3.6拖动滑块进行验证
sv.slide_verification(driver,sli_btn,dis)
# 关闭浏览
time.sleep(15)
driver.close()
2.slideVerfication.py>
"""
本模块专门用来处理滑动验证码的问题,
"""
from selenium.webdriver import ActionChains
import random, time, os
import cv2
from PIL import Image as Im
import numpy as np
import requests
class SlideVerificationCode():
"""滑动验证码破解"""
def __init__(self, slider_ele=None, background_ele=None, count=1, save_image=False):
"""
:param count: 验证重试的次数,默认为5次
:param save_image: 是否保存验证过程中的图片,默认不保存
"""
self.count = count
self.save_image = save_image
self.slider_ele = slider_ele
self.background_ele = background_ele
def slide_verification(self, driver, slide_element, distance):
"""
:param driver: driver对象
:type driver:webdriver.Chrome
:param slide_element: 滑块的元组
:type slider_ele: WebElement
:param distance: 滑动的距离
:type: int
:return:
"""
# 获取滑动前页面的url地址
start_url = driver.current_url
print("需要滑动的距离为:", distance)
# 根据滑动距离生成滑动轨迹
locus = self.get_slide_locus(distance)
print("生成的滑动轨迹为:{},轨迹的距离之和为{}".format(locus, distance))
# 按下鼠标左键
ActionChains(driver).click_and_hold(slide_element).perform()
time.sleep(0.5)
# 遍历轨迹进行滑动
for loc in locus:
time.sleep(0.01)
ActionChains(driver).move_by_offset(loc, random.randint(-5, 5)).perform()
ActionChains(driver).context_click(slide_element)
# 释放鼠标
ActionChains(driver).release(on_element=slide_element).perform()
# 判读是否验证通过,未通过的情况下重新滑动
time.sleep(2)
# 滑动之后再次获取url地址
end_url = driver.current_url
# 滑动失败的情况下,重试count次
if start_url == end_url and self.count > 0:
print("第{}次验证失败,开启重试".format(6 - self.count))
self.count -= 1
self.slide_verification(driver, slide_element, distance)
def onload_save_img(self, url, filename="image.png"):
"""
下载图片保存
:param url:图片地址
:param filename: 保存的图片名
:return:
"""
try:
response = requests.get(url=url)
except(requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError)as e:
print("图片下载失败")
raise e
else:
with open(filename, "wb") as f:
f.write(response.content)
def get_element_slide_distance(self, slider_ele, background_ele, correct=0):
"""
根据传入滑块,和背景的节点,计算滑块的距离
该方法只能计算 滑块和背景图都是一张完整图片的场景,
如果是通过多张小图拼接起来的背景图,该方法不适用,后续会补充一个专门针对处理该场景的方法
:param slider_ele: 滑块图片的节点
:type slider_ele: WebElement
:param background_ele: 背景图的节点
:type background_ele:WebElement
:param correct:滑块缺口截图的修正值,默认为0,调试截图是否正确的情况下才会用
:type: int
:return: 背景图缺口位置的X轴坐标位置(缺口图片左边界位置)
"""
# 获取验证码的图片
slider_url = slider_ele.get_attribute("src")
background_url = background_ele.get_attribute("src")
# 下载验证码背景图,滑动图片
slider = "slider.jpg"
background = "background.jpg"
self.onload_save_img(slider_url, slider)
self.onload_save_img(background_url, background)
# 读取进行色度图片,转换为numpy中的数组类型数据,
slider_pic = cv2.imread(slider, 0)
background_pic = cv2.imread(background, 0)
# 获取缺口图数组的形状 -->缺口图的宽和高
width, height = slider_pic.shape[::-1]
# 将处理之后的图片另存
slider01 = "slider01.jpg"
background_01 = "background01.jpg"
cv2.imwrite(background_01, background_pic)
cv2.imwrite(slider01, slider_pic)
# 读取另存的滑块图
slider_pic = cv2.imread(slider01)
# 进行色彩转换
slider_pic = cv2.cvtColor(slider_pic, cv2.COLOR_BGR2GRAY)
# 获取色差的绝对值
slider_pic = abs(255 - slider_pic)
# 保存图片
cv2.imwrite(slider01, slider_pic)
# 读取滑块
slider_pic = cv2.imread(slider01)
# 读取背景图
background_pic = cv2.imread(background_01)
# 比较两张图的重叠区域
result = cv2.matchTemplate(slider_pic, background_pic, cv2.TM_CCOEFF_NORMED)
# 通过数组运算,获取图片的缺口位置
top, left = np.unravel_index(result.argmax(), result.shape)
# 背景图中的图片缺口坐标位置
print("当前滑块的缺口位置:", (left, top, left + width, top + height))
# 判读是否需求保存识别过程中的截图文件
if self.save_image:
# 截图滑块保存
# 进行坐标修正
loc = (left + correct, top + correct, left + width - correct, top + height - correct)
self.image_crop(background, loc)
else:
# 删除识别过程中保存的临时文件
os.remove(slider01)
os.remove(background_01)
os.remove(slider)
os.remove(background)
# 返回需要移动的位置距离
return left
def get_image_slide_dictance(self, slider_image, background_image, correct=0):
"""
根据传入滑块,和背景的图片,计算滑块的距离
该方法只能计算 滑块和背景图都是一张完整图片的场景,
如果是通过多张小图拼接起来的背景图,该方法不适用,后续会补充一个专门针对处理该场景的方法
:param slider_iamge: 滑块图的图片
:type slider_image: str
:param background_image: 背景图的图片
:type background_image: str
:param correct:滑块缺口截图的修正值,默认为0,调试截图是否正确的情况下才会用
:type: int
:return: 背景图缺口位置的X轴坐标位置(缺口图片左边界位置)
"""
# 读取进行色度图片,转换为numpy中的数组类型数据,
slider_pic = cv2.imread(slider_image, 0)
background_pic = cv2.imread(background_image, 0)
# 获取缺口图数组的形状 -->缺口图的宽和高
width, height = slider_pic.shape[::-1]
# 将处理之后的图片另存
slider01 = "slider01.jpg"
background_01 = "background01.jpg"
cv2.imwrite(background_01, background_pic)
cv2.imwrite(slider01, slider_pic)
# 读取另存的滑块图
slider_pic = cv2.imread(slider01)
# 进行色彩转换
slider_pic = cv2.cvtColor(slider_pic, cv2.COLOR_BGR2GRAY)
# 获取色差的绝对值
slider_pic = abs(255 - slider_pic)
# 保存图片
cv2.imwrite(slider01, slider_pic)
# 读取滑块
slider_pic = cv2.imread(slider01)
# 读取背景图
background_pic = cv2.imread(background_01)
# 比较两张图的重叠区域
result = cv2.matchTemplate(slider_pic, background_pic, cv2.TM_CCOEFF_NORMED)
# 获取图片的缺口位置
top, left = np.unravel_index(result.argmax(), result.shape)
# 背景图中的图片缺口坐标位置
print("当前滑块的缺口位置:", (left, top, left + width, top + height))
# 判读是否需求保存识别过程中的截图文件
if self.save_image:
# 截图滑块保存
# 进行坐标修正
loc = (left + correct, top + correct, left + width - correct, top + height - correct)
self.image_crop(background_image, loc)
else:
# 删除识别过程中保存的临时文件
os.remove(slider01)
os.remove(background_01)
# 返回需要移动的位置距离
return left
@classmethod
def get_slide_locus(self, distance):
"""
根据移动坐标位置构造移动轨迹,前期移动慢,中期块,后期慢
:param distance:移动距离
:type:int
:return:移动轨迹
:rtype:list
"""
remaining_dist = distance
locus = []
while remaining_dist > 0:
ratio = remaining_dist / distance
if ratio < 0.2:
# 开始阶段移动较慢
span = random.randint(2, 8)
elif ratio > 0.8:
# 结束阶段移动较慢
span = random.randint(5, 8)
else:
# 中间部分移动快
span = random.randint(10, 16)
locus.append(span)
remaining_dist -= span
return locus
def image_crop(self, image, location, new_name="new_image.png"):
"""
对图片的指定位置进行截图
:param image: 被截取图片的坐标位置
:param location:需要截图的坐标位置:(left,top,right,button)
:type location: tuple
:return:
"""
# 打开图片
image = Im.open(image)
# 切割图片
imagecrop = image.crop(location)
# 保存图片
imagecrop.save(new_name)