参考资料
slider-captcha/slider_captcha.py at master · maxnoodles/slider-captcha (github.com)
GitHub - sml2h3/ddddocr: 带带弟弟 通用验证码识别OCR pypi版
实践代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import random
import time
import traceback
from contextlib import contextmanager
import cv2 # pip install opencv_python
import requests
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
@contextmanager
def selenium(driver):
try:
yield
except Exception as e:
traceback.print_exc()
driver.quit()
class SliderCaptcha:
def __init__(self):
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
self.start_time = time.strftime("%H_%M_%S")
print(self.start_time)
self.background_path = './slider_img/background_%s.png' % self.start_time
self.slider_path = './slider_img/slider_%s.png' % self.start_time
self.driver = webdriver.Chrome(chrome_options=options)
self.wait = WebDriverWait(self.driver, 10)
def login(self):
with selenium(self.driver):
login_url = 'http://dun.163.com/trial/jigsaw'
self.driver.maximize_window()
self.driver.get(url=login_url)
# 点击按钮,触发滑块
time.sleep(1)
locator = (By.CLASS_NAME, 'yidun_slider')
WebDriverWait(self.driver, 10, 1).until(
EC.element_to_be_clickable(locator))
self.driver.find_elements_by_class_name("yidun_slider")[0].click()
# self.driver.find_element_by_xpath(
# '//div[@class="yidun_slider"]'
# ).click()
# 获取背景图并保存
background = self.wait.until(
lambda x: x.find_element_by_xpath('//img[@class="yidun_bg-img"]')
).get_attribute('src')
with open(self.slider_path, 'wb') as f:
resp = requests.get(background)
f.write(resp.content)
# 获取滑块图并保存
slider = self.wait.until(
lambda x: x.find_element_by_xpath('//img[@class="yidun_jigsaw"]')
).get_attribute('src')
with open(self.background_path, 'wb') as f:
resp = requests.get(slider)
f.write(resp.content)
# 这里可以用 ddddocr 模块提高准确度
distance = self.findfic(target=self.background_path, template=self.slider_path)
print("匹配距离:", distance)
# 初始滑块距离边缘 4 px
trajectory = self.get_tracks(distance + 4)
print("模拟轨迹:", trajectory)
# 等待按钮可以点击
slider_element = self.wait.until(
EC.element_to_be_clickable(
(By.CLASS_NAME, 'yidun_jigsaw'))
)
# 添加行动链,向右并超出一部分
ActionChains(self.driver).click_and_hold(slider_element).perform()
for track in trajectory['plus']:
ActionChains(self.driver).move_by_offset(
xoffset=track,
yoffset=round(random.uniform(1.0, 3.0), 1)
).perform()
time.sleep(0.5)
# 添加行动链,返回超出的部分
for back_tracks in trajectory['reduce']:
ActionChains(self.driver).move_by_offset(
xoffset=back_tracks,
yoffset=round(random.uniform(1.0, 3.0), 1)
).perform()
# perform() 是执行行动链
for i in [-4, 4]:
ActionChains(self.driver).move_by_offset(
xoffset=i,
yoffset=0
).perform()
time.sleep(0.1)
# 释放在元素上按住的鼠标按钮。
ActionChains(self.driver).release().perform()
time.sleep(1)
def close(self):
self.driver.quit()
def findfic(self, target='background.png', template='slider.png'):
"""
:param target: 滑块背景图
:param template: 滑块图片路径
:return: 模板匹配距离
"""
target_rgb = cv2.imread(target)
target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
template_rgb = cv2.imread(template, 0)
# 使用相关性系数匹配, 结果越接近1 表示越匹配
# https://www.cnblogs.com/ssyfj/p/9271883.html
# target_gray 参数表示待搜索源图像,必须是8位整数或32位浮点。
# template_rgb 参数表示模板图像,必须不大于源图像并具有相同的数据类型。
# cv2.TM_CCOEFF_NORMED 参数表示计算匹配程度的方法。
# res 参数表示匹配结果图像,必须是单通道32位浮点。如果image的尺寸为W x H,templ的尺寸为w x h,则result的尺寸为(W-w+1)x(H-h+1)。
res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
# opencv 的函数 minMaxLoc:在给定的矩阵中寻找最大和最小值,并给出它们的位置
# minVal参数表示返回的最小值,如果不需要,则使用NULL。
# maxVal参数表示返回的最大值,如果不需要,则使用NULL。
# minLoc参数表示返回的最小位置的指针(在2D情况下); 如果不需要,则使用NULL。
# maxLoc参数表示返回的最大位置的指针(在2D情况下); 如果不需要,则使用NULL。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val, max_val, min_loc, max_loc)
# 因为滑块只需要 x 坐标的距离,返 回坐标元组的 [0] 即可
if abs(1 - min_val) <= abs(1 - max_val):
distance = min_loc[0]
else:
distance = max_loc[0]
return distance
def get_tracks(self, distance):
"""
:param distance: 缺口距离
:return: 轨迹
"""
# 分割加减速路径的阀值
value = round(random.uniform(0.55, 0.75), 2)
# 划过缺口 20 px
distance += 20
# 初始速度,初始计算周期, 累计滑动总距
v, t, sum = 0, 0.3, 0
# 轨迹记录
plus = []
# 将滑动记录分段,一段加速度,一段减速度
mid = distance * value
while sum < distance:
if sum < mid:
# 指定范围随机产生一个加速度
a = round(random.uniform(2.5, 3.5), 1)
else:
# 指定范围随机产生一个减速的加速度
a = -round(random.uniform(2.0, 3.0), 1)
s = v * t + 0.5 * a * (t ** 2)
v = v + a * t
sum += s
plus.append(round(s))
# end_s = sum - distance
# plus.append(round(-end_s))
# 手动制造回滑的轨迹累积20px
# reduce = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]
reduce = [-6, -4, -6, -4]
return {'plus': plus, 'reduce': reduce}
def run():
for i in range(1):
spider = SliderCaptcha()
spider.login()
spider.close()
if __name__ == '__main__':
if not os.path.exists("./slider_img"):
os.mkdir("./slider_img")
run()
Zhuoyuebiji ( 广东·深圳 )
🚩成长的时候,能帮有需要的你
我是 卓越笔记,软件测试工作者,热爱互联网,喜欢琢磨,遇到问题就一定要找到答案。我的博客主要记录学习中遇到的知识点和遇到的问题及问题的解决方法。欢迎同样热爱互联网的小伙伴们交换友链,一起探索互联网的世界 😊