selenium 无原图滑动验证码解决
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
最近在做电商爬虫, 电商爬虫呢 有一个很重要的反扒措施。 就是登录, 而登录最常见的就是滑动验证码。
滑动验证码又可以简单分为两种:
- 通过调整 js可以获取到验证码原图
这种方式 虽然我没做过, 思路还是比较简单的, 通过遍历比较两种图片的像素点, 得到不一样的地方 就可以确定缺口位置 - 没有办法得到原图
这种方式就比较麻烦了, 也是我写这篇文章的前提了。 当然我也是参考别人的代码, 写出来的,还不懂原理。
本片文章只给代码, 自己找网站实验区
1. selenium获取坐标元素
代码如下(示例):
import json
import time
# 图像处理标准库
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as Ec
from selenium.webdriver.support.wait import WebDriverWait
# 目录文件 在下面
from 目录文件 import 类
'''
无头模式可以成功
'''
class Login(object):
def __init__(self):
# self.display = Display(visible=0, size=(800, 800))
# self.display.start()
# 创建一个参数对象,用来控制chrome以无界面模式打开
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless') # # 浏览器不提供可视化页面
chrome_options.add_argument('--disable-gpu') # 禁用GPU加速,GPU加速可能会导致Chrome出现黑屏,且CPU占用率高达80%以上
chrome_options.add_argument('--no-sandbox')
# chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--disable-dev-shm-usage')
self.browser = webdriver.Chrome(chrome_options=chrome_options)
# self.browser = webdriver.Chrome()
self.wait = WebDriverWait(self.browser, 20)
self.url = '登录网址'
self.sli = Code()
# 重试次数
self.count = 6
def login(self):
# 请求网址
self.browser.get(self.url)
# 点击输入密码界面
login_status = self.wait.until(
Ec.presence_of_element_located((By.XPATH, '//div[@class="switch-type"]'))
)
login_status.click()
print('点击输入密码界面')
# time.sleep(1)
# 输入账号
username = self.wait.until(
Ec.element_to_be_clickable((By.XPATH, '//input[@class="zent-input"]'))
)
username.send_keys('账号')
print('账号已输入')
# time.sleep(1)
# 输入密码
password = self.wait.until(
Ec.element_to_be_clickable((By.XPATH, '//input[@name="password"]'))
)
password.send_keys('密码')
print('密码已输入')
# 登录框
submit = self.wait.until(
Ec.element_to_be_clickable((By.XPATH, '//button[@type="submit"]'))
)
submit.click()
print('点击登录按钮')
time.sleep(3)
def login_chk(self):
'''
滑动验证码解决
'''
k = 1
# while True:
while k < self.count:
# 获取滑动前页面的url网址
start_url = self.browser.current_url
# 1. 获取原图
bg_img = self.wait.until(
Ec.presence_of_element_located((By.XPATH, '//div[@class="bg"]/img'))
)
# 获取滑块链接
front_img = self.wait.until(
Ec.presence_of_element_located(
(By.XPATH, "//div[@class='block']/img")))
# 获取验证码滑动距离
distance = self.sli.get_element_slide_distance(front_img, bg_img)
print('滑动距离是', distance)
# 2. 乘缩放比例, -去 滑块前面的距离 下面给介绍
distance = distance * 280 / 560 - 25
print('实际滑动距离是', distance)
# 滑块对象
element = self.browser.find_element_by_xpath(
'//div[@class="slide-block"]')
# 滑动函数
self.sli.slide_verification(self.browser, element, distance)
# TODO 验证是否通过滑块
# 滑动之后的url链接
time.sleep(2)
end_url = self.browser.current_url
if start_url == end_url:
print('第%s次验证失败...' % k, '\n')
k = k + 1
else:
# 看是否能获取登陆成功后的网页内容
success = self.wait.until(
Ec.element_to_be_clickable(
(By.XPATH, '//*[@id="js-react-container"]/div/div/div[2]/div[2]/div[2]/div'))
)
# success = self.browser.find_element_by_xpath(
# '//*[@id="js-react-container"]/div/div/div[2]/div[2]/div[2]/div')
if success:
success.click()
print('登录成功')
self.get_cookies()
print(self.cookies)
else:
print('失败')
break
def get_cookies(self):
'''
登录成功后 保存账号的cookies
:return:
'''
cookies = self.browser.get_cookies()
self.cookies = ''
for cookie in cookies:
self.cookies += '{}={};'.format(cookie.get('name'), cookie.get('value'))
def run(self):
# # 1.输入账号密码
self.login()
# # 2. 解决滑动验证码
self.login_chk()
def __del__(self):
self.browser.close()
print('界面关闭')
# self.display.stop()
获取实际滑动距离
这是一张验证码底图:
560 * 316 是他的原始图片大小
280 * 158 是实际在页面上展示的大小
红线部分是需要减去的滑动距离, 通过画图或者截图工具得出
所以实际滑动距离是 原始滑动距离 * 缩放比例 - 前缀距离
2.验证码处理
不BB了,直接给代码,该安装的包安装上去
import os
import random
import time
import cv2
import numpy as np
import requests
from selenium.webdriver.common.action_chains import ActionChains
class Code():
'''
滑动验证码破解
'''
def __init__(self, slider_ele=None, background_ele=None, count=1, save_image=False):
'''
:param slider_ele:
:param background_ele:
:param count: 验证重试次数
:param save_image: 是否保存验证中产生的图片, 默认 不保存
'''
self.count = count
self.save_images = save_image
self.slider_ele = slider_ele
self.background_ele = background_ele
def get_slide_locus(self, distance):
distance += 8
v = 0
m = 0.3
# 保存0.3内的位移
tracks = []
current = 0
mid = distance * 4 / 5
while current <= distance:
if current < mid:
a = 2
else:
a = -3
v0 = v
s = v0 * m + 0.5 * a * (m ** 2)
current += s
tracks.append(round(s))
v = v0 + a * m
# 由于计算机计算的误差,导致模拟人类行为时,会出现分布移动总和大于真实距离,这里就把这个差添加到tracks中,也就是最后进行一步左移。
# tracks.append(-(sum(tracks) - distance * 0.5))
# tracks.append(10)
return tracks
def slide_verification(self, driver, slide_element, distance):
'''
:param driver: driver对象
:param slide_element: 滑块元祖
:type 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)
# # 滑动之后的yurl链接
# end_url = driver.current_url
# 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)
except Exception 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: 滑块节点参数
:param background_ele: 背景图的节点
:param correct:
:return:
'''
# 获取验证码的图片
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'
slider02 = 'slider02.jpg'
background01 = 'background01.jpg'
cv2.imwrite(slider01, slider_pic)
cv2.imwrite(background01, background_pic)
# 读取另存的滑块
slider_pic = cv2.imread(slider01)
# 进行色彩转化
slider_pic = cv2.cvtColor(slider_pic, cv2.COLOR_BGR2GRAY)
# 获取色差的绝对值
slider_pic = abs(255 - slider_pic)
# 保存图片
cv2.imwrite(slider02, slider_pic)
# 读取滑块
slider_pic = cv2.imread(slider02)
# 读取背景图
background_pic = cv2.imread(background01)
# 必脚两张图的重叠部分
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_images:
loc = [(left + correct, top + correct), (left + width - correct, top + height - correct)]
self.image_crop(background, loc)
else:
# 删除临时文件
os.remove(slider01)
os.remove(slider02)
os.remove(background01)
os.remove(background)
os.remove(slider)
# print('删除')
# os.remove(slider)
# 返回需要移动的位置距离
return left
def image_crop(self, image, loc):
cv2.rectangle(image, loc[0], loc[1], (7, 249, 151), 2)
cv2.imshow('Show', image)
# cv2.imshow('Show2', slider_pic)
cv2.waitKey(0)
cv2.destroyAllWindows()
总结
我也是参考别人的代码的出来的, 写文章之前 刚试验过 可以成功。