参考资料
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()