很多网站登录登陆时都要用到滑块验证码,在某些场景例如使用爬虫爬取信息时常常受到阻碍,想着用opencv的模板匹配试试能不能实现模拟登陆。本来觉得网上资料多应该还蛮容易,但实际上手还是搞了蛮久,在这里记录一下整个流程,网站无所谓主要是要有滑动验证码:
环境
python 3.9, selenium和Opencv相关依赖,用于抓取图片的requests包,具体安装这里不多讲了,其中selenium用的火狐版本。
selenium登录网站
整体流程就是这个样子:访问网站->点击登录->输入账号密码->搞定滑块验证->登录网站,其中最大的难点是滑块验证码,但在此之前我们当然要先让selenium自动打开网站把账号密码输好,我们通过find_element()方法定位输入框之后执行操作,元素的各个属性F12就可以找到:
代码如下:
options = webdriver.FirefoxOptions()
driver = webdriver.Firefox(options=options)
driver.get('网址')
driver.find_element("link text", "登录").click()
name = driver.find_element("id", "name-input")
name.send_keys("账号######") # 输入账号
pw = driver.find_element("id", "password-input")
pw.send_keys("密码#########") # 输入密码
driver.find_element("id", "submit").click() # 提交
requests抓取验证码图片
为了做后续处理我们需要把滑块验证码相关图片抓到本地,网上关于滑块验证码这块很多都是用原图和有缺口的图对比来确定缺口位置的,但是我并没有找到原图,这里用到的是有缺口的背景图和滑块图,如下:
滑块图:
有缺口的背景图:
这里爬图是selenium定位之后用requests包爬的,注意验证码和登陆界面不在一个iframe里,selenium记得切到对应iframe才能定位到图片,代码如下:
driver.switch_to.frame('tcaptcha_iframe')
# 切换iframe
img = driver.find_element("id", "slideBg").get_attribute('src')
headers = {
'Accept': "application/json, text/plain, */*",
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
} # 请求头
r = requests.get(img, headers=headers)
with open('img.png', 'wb') as f:
f.write(r.content)
block = driver.find_element("id", "slideBlock").get_attribute('src')
r = requests.get(block, headers=headers)
with open('block.png', 'wb') as f:
f.write(r.content)
OpenCV识别缺口位置
接下来就是重点,如何确定缺口位置来定位滑动验证码该往哪滑。这里主要用到OpenCV的模板匹配。 首先对滑块也就是稍后匹配时用到的模板进行处理,这里主要就是把形状轮廓提取出来然后去掉多余的东西,先把原图变成灰度图:
tpl_gray = cv2.cvtColor(tpl, cv2.COLOR_BGR2GRAY)
可以看到边缘有一圈阴影部分,我们需要把周围这圈去掉,遍历找到黑色像素点把它变成和周围一样。
width, height = tpl_gray.shape
for h in range(height):
for w in range(width):
if tpl_gray[w, h] == 0:
tpl_gray[w, h] = 96
处理后变成了这样,然后把中间主体部分涂黑,也就是将图片二值化。
binary = cv2.inRange(tpl_gray, 96, 96) # 二值化
kernel = np.ones((8, 8), np.uint8)
template = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 去白色噪点
处理完毕得到理想的模板图:
接下来对带有缺口的背景图进行处理,这个过程稍微麻烦一点,不过思路还是比较清晰的,还是先转化成灰度图再二值化,这里有一个问题,不同验证码图片之间差距很大,有的颜色很显眼,有的却很清淡,比如下面这两差别太大了,这就导致在二值化的过程中很难有一个固定的参数。
这里我根据图片的平均灰度值设定了几个区间,对不同区间的验证码图片传入不同参数进行二值化:
def avg_mean(img):
mean_val, _, _, _ = cv2.mean(img)
print("平均灰度:", mean_val)
return mean_val
def match(img):
gauss = cv2.GaussianBlur(img, [5, 5], 0)
img_gray = cv2.cvtColor(gauss, cv2.COLOR_BGR2GRAY)
cv2.imshow("111", img_gray)
if avg_mean(img) > 140: # 二值化
ret, target = cv2.threshold(img_gray, 105, 255, cv2.THRESH_BINARY)
elif avg_mean(img) > 102:
ret, target = cv2.threshold(img_gray, 95, 255, cv2.THRESH_BINARY)
else:
ret, target = cv2.threshold(img_gray, 85, 255, cv2.THRESH_BINARY)
处理过的结果大概像这样:
效果还是不错的,清晰的凸显了缺口位置,最后把背景图和模板传入opencv的模板匹配方法,记录下匹配到的坐标即可。
result = cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
left_up = max_loc
print(left_up)
right_down = (left_up[0] + height, left_up[1] + width)
cv2.rectangle(img, left_up, right_down, (0, 0, 255), 2)
cv2.imshow('res', img)
这里框出来看看效果:
模拟拖动滑块
识别出位置之后就要算出滑块移动了多少距离,我们可以看到滑块初始状态距离边缘有26个像素:
同时抓下来的图片相比在网页中放大了一倍,所以真实滑动距离是:
(left_up - 26*2)/2
于是用selenium的actionchains模拟拖动滑块:
def drag_block(l):
drag = driver.find_element("id", "tcaptcha_drag_button")
ActionChains(driver).click_and_hold(on_element=drag).perform()
ActionChains(driver).move_to_element_with_offset(to_element=drag, xoffset=l, yoffset=0).perform()
ActionChains(driver).release().perform()
这样整个流程就搞定了,理论上这样简单粗暴的自动拖过去在很多时候会不奏效,还需要模拟人手动拖动,不过因为我做测试的时候直接就成功了,所以没写下去,整体思路大概是加速减速停几秒或者中间触发几个mouse_up(),mouse_down()事件。
脚本示例:
下面是测试时用到的脚本,selenium部分和主函数,拿某个CTF靶场做的测试,仅供参考,根据实际网站不同肯定得改改:文章来源:https://www.toymoban.com/news/detail-413511.html
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import block_loc
def drag_block(l):
drag = driver.find_element("id", "tcaptcha_drag_button")
ActionChains(driver).click_and_hold(on_element=drag).perform()
ActionChains(driver).move_to_element_with_offset(to_element=drag, xoffset=l, yoffset=0).perform()
ActionChains(driver).release().perform()
def login_in(username, password):
headers = {
'Accept': "application/json, text/plain, */*",
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
}
driver.get('网址')
driver.find_element("link text", "登录").click()
name = driver.find_element("id", "name-input")
name.send_keys(username)
pw = driver.find_element("id", "password-input")
pw.send_keys(password)
driver.find_element("id", "submit").click()
time.sleep(2)
driver.switch_to.frame('tcaptcha_iframe')
img = driver.find_element("id", "slideBg").get_attribute('src')
r = requests.get(img, headers=headers)
with open('img.png', 'wb') as f:
f.write(r.content)
block = driver.find_element("id", "slideBlock").get_attribute('src')
r = requests.get(block, headers=headers)
with open('block.png', 'wb') as f:
f.write(r.content)
if __name__ == '__main__':
options = webdriver.FirefoxOptions()
driver = webdriver.Firefox(options=options)
user = "##########"
pw = "############"
login_in(user, pw)
image = "img.png"
tpl = "block.png"
length = block_loc.match(image, tpl)
print(length)
drag_block(length)
Opencv部分:文章来源地址https://www.toymoban.com/news/detail-413511.html
import cv2
import numpy as np
def avg_mean(img):
mean_val, _, _, _ = cv2.mean(img)
print("平均灰度:", mean_val)
return mean_val
def match(image, temp):
img = cv2.imread(image)
tpl = cv2.imread(temp)
tpl_gray = cv2.cvtColor(tpl, cv2.COLOR_BGR2GRAY)
# cv2.imshow("111", tpl_gray)
width, height = tpl_gray.shape
for h in range(height):
for w in range(width):
if tpl_gray[w, h] == 0:
tpl_gray[w, h] = 96
binary = cv2.inRange(tpl_gray, 96, 96)
kernel = np.ones((8, 8), np.uint8)
template = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# cv2.imshow('tpl', template)
print(img.shape)
gauss = cv2.GaussianBlur(img, [5, 5], 0)
img_gray = cv2.cvtColor(gauss, cv2.COLOR_BGR2GRAY)
# cv2.imshow("111", img_gray)
if avg_mean(img) > 140:
ret, target = cv2.threshold(img_gray, 105, 255, cv2.THRESH_BINARY) # 二值化
elif avg_mean(img) > 102:
ret, target = cv2.threshold(img_gray, 95, 255, cv2.THRESH_BINARY) # 二值化
else:
ret, target = cv2.threshold(img_gray, 80, 255, cv2.THRESH_BINARY)
# cv2.imshow('target', target)
result = cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
left_up = max_loc
print(left_up)
right_down = (left_up[0] + height, left_up[1] + width)
cv2.rectangle(img, left_up, right_down, (0, 0, 255), 2)
# cv2.imshow('res', img)
length = (left_up[0] - 26*2)/2
return length
到了这里,关于selenium+opencv实现模拟登陆(滑块验证码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!