识别滑动拼图验证码的新策略
背景
关于滑动拼图验证码,网上识别的一般流程为先获取切割后的验证码背景图,如图1,然后再找到坐标拼接为完整的图片,之后比较出缺口图片与不带缺口图片之间像素不同的地方,从而计算出缺口位置,最后移动滑块。这里关键点在于找到不带缺口的图片和根据坐标拼接正确图片,对于有些网站,完成这两步还是有一定难度的,这里介绍一种可以略过这两步的方法。
原理
大家都知道,滑动拼图验证码一般由一张可移动的小图和带缺口的背景图组成,其中为了方便观察,缺口位置会做的比周围图片更暗或者更亮。根据这个现象,对缺口图片进行二值化处理,如果缺口位置比较亮,如图2(为了消除小图的干扰,先把小图切割掉了),阈值可以设置为255,然后识别图片中较亮的轮廓,如果没有识别出这样的轮廓,则减小阈值,处理后的结果类似图3;反之,如果缺口位置比较暗,阈值可以设置为0,后面的操作与上述较亮的情况一致。这样总能识别出含缺口位置的轮廓,理想情况下就能获得缺口边界的坐标,后面就是常规的拖动滑块移动操作了。
代码及测试
以下操作使用的是天安保险登录中的滑动拼图验证码。代码如下:
import cv2
import numpy as np
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
import random
SCRIPT = 'Object.defineProperty(navigator,"webdriver",{get:()=>undefined})'
def getTrack(gap):
# 生成滑动轨迹
track = []
gap = min(gap) + 70
# 当前位移
current = 0
# 减速阈值
mid = gap * 4 / 5 # 前4/5段加速 后1/5段减速
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < gap:
if current < mid:
a = 3 # 加速度为+3
else:
a = -3 # 加速度为-3
# 初速度v0
v0 = v
# 当前速度
v = v0 + a * t
# 移动距离
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
# 处理图片
def handle(path, threshold):
img = Image.open(path)
img = img.convert('L')
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
bim = img.point(table, '1')
bim.save('./imgs/handled.png')
def contou():
# 加载图片
img = cv2.imread('./imgs/handled.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 所有轮廓x坐标的列表
x_list = []
for i in range(len(contours)):
M = cv2.moments(contours[i])
if M["m00"] != 0:
center_x = int(M["m10"] / M["m00"])
center_y = int(M["m01"] / M["m00"])
x_list.append(center_x)
else:
pass
return x_list
def getImg():
# 设置二值化阈值
threshold = 255
# 天安保险登录网址
url = 'https://tianaw.95505.cn/tacpc/#/login'
phone_num = '15537123105'
pwd = '111111111'
browser = webdriver.Firefox()
browser.execute_script(SCRIPT)
wait = WebDriverWait(browser, 10)
browser.get(url)
# 切换到账号登录标签
account = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '.tabForm > button:nth-child(3)')))
account.click()
input_phone = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#app > nz-content > app-login > nz-content > div > div > div > app-log-form > div > form > nz-form-item:nth-child(1) > nz-form-control > div > span > nz-input-group > span > input')))
# 输入手机号
input_phone.send_keys(phone_num)
input_pwd = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#app > nz-content > app-login > nz-content > div > div > div > app-log-form > div > form > nz-form-item:nth-child(2) > nz-form-control > div > span > nz-input-group > span > input')))
# 输入密码
input_pwd.send_keys(pwd)
# 点击同意协议
wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '.ant-checkbox-input'))).click()
click_btn = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#app > nz-content > app-login > nz-content > div > div > div > app-log-form > div > form > nz-form-item:nth-child(4) > nz-form-control > div > span > button')))
click_btn.click()
# 获取验证码图片
canvas = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#captchasli > canvas:nth-child(1)')))
canvas.screenshot('./imgs/1.png')
btn = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '.sliderIcon')))
cropImg()
# 判断在该threshold下是否识别出轮廓,如果没有增大或减小阈值
while True:
handle('./imgs/croped.png', threshold)
gap = contou()
if not gap:
threshold -= 3
continue
else:
break
# 获取滑块轨迹
track = getTrack(gap)
# 移动滑块
ActionChains(browser).click_and_hold(btn).perform()
# print(track)
for x in track:
y = random.uniform(-3, 3)
ActionChains(browser).move_by_offset(xoffset=x, yoffset=y).perform()
ActionChains(browser).release(btn).perform()
sleep(1.5)
browser.close()
def cropImg():
# 剪切图片,去除小图对轮廓识别的影响
base_crop = 70
img1 = Image.open('./imgs/1.png')
box = (base_crop, 0, img1.size[0], img1.size[1])
img = img1.crop(box)
img.save('./imgs/croped.png')
if __name__ == "__main__":
getImg()
整个代码的实现逻辑为:先用selenium访问登录界面,输入账号密码后调出拼图验证码,截取验证码图片,再把包含小滑块的图片部分切割掉,之后处理图片,识别轮廓,根据识别出的坐标生成移动轨迹,最后移动滑块到缺口位置,其中借鉴了一些网友的代码。
下面这张是测试的动图:
一共测试了6次,成功了4次,失败了两次。
另外也测试了缺口位置较暗的情况,验证码如图4,结果就不再演示了。
结语
以上只做学习交流使用,感兴趣的同学欢迎尝试,也欢迎各位提出宝贵意见,另转载请注明出处。
本文地址:https://blog.csdn.net/a97d1f4b2/article/details/107188556
下一篇: jps 进程状态工具