【日常】Geetest滑动验证码(三代canvas版)处理小结(以B站登录验证为例)

article/2025/9/10 15:46:03

问题描述

这个问题确实让我困扰了太长时间,今天花了半天时间,并没有找到非常完满的解决方案,只是在解决问题的过程中学会了一些其他知识,我最后还是要通过人工来判断大致的移动距离,然后根据误差做微调。大致做个总结了,并且认为网站如果真心愿意反爬虫,完全可以处理到让爬虫无懈可击。

今年四月份时B站的Geetest验证码大致情况可以通过https://blog.csdn.net/CY19980216/article/details/89074771中的知悉,当时检查元素可以发现验证图片是由一块块div包裹着的切片,并且标签中注明了该切片在原图中对应的坐标,这就对我们复原图片并寻找缺块位置带来了极大的便利(事实上可以通过抓包拿到有缺块的验证图片与无缺块的验证图片)。

但是现在的情况是下面这样的,验证码是一张canvas画布👇

 

虽然在页面源代码中并不能找到验证图片的链接,但是我们通过浏览器监听抓包可以发现有👇

其实抓包依然可以发现被切分并打乱的验证图片,并且在JS监听抓包中可以看到这个图片链接的响应👇

其实看到这里我是以为问题已经解决了,显然是通过JS请求,Geetest响应了图片链接URL,然后访问图片链接就可以拿到这张验证图片。

 

解决设想

首先在页面源代码中可以找到这段JS响应的请求URL,经过对页源中各个script标签中的检测发现上图返回了图片链接URL的JS请求的URL如下图所示(我将页源复制到Notepad++中了):

红框中的东西就是这个👇

https://api.geetest.com/get.php?is_next=true&type=slide3&gt=b6cc0fc51ec7995d8fd3c637af690de3&challenge=ac60b064d702e4f9cc4d728eeca30764&lang=zh-cn&https=false&protocol=https%3A%2F%2F&offline=false&product=embed&api_server=api.geetest.com&isPC=true&area=%23geetest-wrap&width=100%25&callback=geetest_1575204658911

 这显然是一个带有查询字符串的GET请求,它的查询字符串大致是这样的👇

 

要命的问题来了,可以看到这里有一个chanllenge的字段,事实上这个URL只能被访问一次(即浏览器访问的这一次),二次访问就会返回给你old challenge的响应(不信你把这段URL复制到浏览器里看看),challenge字段起到验证作用,且阅后即焚,是个一次性用品,也就是说这张图片的链接URL只能被请求一次。

于是问题在于如何在利用selenium爬虫时可以顺带监听浏览器本身的抓包情况,这个便需要用到一个叫作browsermobproxy的库,这个库在安装上有个坑点,首先pip install browsermobproxy后你还需要去Github上去把它的源码下下来,因为需要源码里面的一个bat文件用来操作java,这个库具体用法Firefox用户可以参考https://blog.csdn.net/m0_37618247/article/details/85066272,它可以协作selenium把浏览器的包以json格式保存下来,经过实验,确实是可以拿到了图片的链接的了。

但是我无法复原上面截图中的那张打乱的图片,于是我干脆决定直接在网页上进行canvas元素截图。

元素截图不是什么难事,元素定位可以定位到它的左上角坐标,再根据元素尺寸即可拿到元素的具体位置了。坑点在于截屏的尺寸往往与浏览器自身尺寸不一样,即实际操作时需要将selenium截屏后的图片进行放缩(我的放缩比例是1.25)

问题转化为如何寻找缺块,我确实没有能够找到很好的办法去处理缺块,先后尝试了多种方法(基于矩阵的运算,我先后测试了临近两列的列向量相似度变化曲线,缺块),另外提一句利用selenium执行JS脚本可以从canvas中抓取到图片👇

	JS = 'return document.getElementsByClassName("geetest_canvas_slice geetest_absolute")[0].toDataURL("image/png");'img_info = browser.execute_script(JS) 					 # 执行js文件得到带图片信息的图片数据img_base64 = img_info.split(',')[1]  					 # 拿到base64编码的图片信息img_bytes = base64.b64decode(img_base64) 				 # 转为bytes类型with open("1.png","wb") as f:f.write(img_bytes)

我看了其他人的方法都可以用该方法去找到原图与有缺块的图(事实上页面上有一个默认不展示display:none的canvas,它理论上包含着的是不含缺块的原图),但是这里我只能拿到如下的图👇

毫无用处。。。

 

最后用PIL库中的ImageContrast方法进行图片对比度增强,可以使得图片中的缺块变为几乎纯黑,于是可以近似找到边界👇

但是这个方法并不对所有的图片都生效,比如下面这张👇

对比度增强后并不会出现黑团(不展示增强对比度后的图片了,实在是太惊悚了,我可怜的2233娘)。

所以最后我也懒得处理这个问题了,我就直接用肉眼看一下大概缺块的位置所占整张图片宽度的比例,然后手动输入给程序了。。。确实比较蠢,但也不想动脑子了。

最后代码大致是这样的👇

	def login_20191130(self,):											 # 用户登录(20190712更新)"""目前登录方式相对于20190408的区别在于一下几点:- 次序上是先输入用户名密码, 点击登录后才会出现验证码图片;- 验证码图片的元素结构变化, 没有小切片因为是一个canvas画布标签, 图片链接仍然可以找到, 但是不在页面源代码中, 抓包可得, 抓包还能抓到geetest的返回结果;- 滑动按钮并未改变, 因此看起来是极验自身升级了;"""def get_track(xoffset):											 # 获取一条路径tracks = []x = int(xoffset/2)											 # 先走一半(很关键,不走不给过)tracks.append(x)xoffset -= xwhile xoffset>=10:x = random.randint(5,9)tracks.append(x)xoffset -= xfor i in range(int(xoffset)): tracks.append(1)				 # 最后几步慢慢走return trackswhile True:browser = webdriver.Firefox()								 # 驱动火狐浏览器browser.maximize_window()									 # 窗口最大化browser.get(self.loginURL)									 # 访问登录页面interval = 2.												 # 初始化页面加载时间(如果页面没有加载成功,将无法获取到下面的滑动验证码按钮,林外我意外的发现有时候竟然不是滑动验证,而是验证图片四字母识别,个人感觉处理滑动验证更有意思)while True:													 # 由于可能未成功加载,使用循环确保加载成功browser.find_element_by_id("login-username").send_keys(self.username)browser.find_element_by_id("login-passwd").send_keys(self.password)time.sleep(interval)browser.find_element_by_xpath("//a[@class='btn btn-login']").click()xpath = "//canvas[@class='geetest_canvas_slice geetest_absolute']"#WebDriverWait(browser,15).until(lambda driver: driver.find_element_by_xpath(xpath).is_displayed())try:time.sleep(interval)								 # 等待加载canvas = browser.find_element_by_xpath(xpath)except:browser.refresh()									 # 刷新页面interval += .5										 # 每失败一次让interval增加0.5秒print("验证图片加载失败!页面等待时间更新为{}".format(interval))continuebrowser.save_screenshot("{}/screen.png".format(self.tempFolder))location = canvas.locationsize = canvas.sizeleft = location["x"]									 # 获取图片左边界top = location["y"]										 # 获取图片上边界right = left + size["width"]							 # 获取图片右边界bottom = top + size["height"]							 # 获取图片下边界image = Image.open("{}/screen.png".format(self.tempFolder))image = image.crop((									 # 缩放及对右下边界要多取一格像素, python向来都是包左不包右left*self.layout,top*self.layout,right*self.layout+1,bottom*self.layout+1))image.save("{}/canvas.png".format(self.tempFolder))		 # 保存图片breakwhile True:div = browser.find_element_by_class_name("geetest_slider_button")ActionChains(browser).click_and_hold(on_element=div).perform()ratio = input("请输入肉眼可见的比例: ")xoffset = 260*float(ratio)									 # 寻找缺块位置的横坐标tracks = get_track(xoffset)total = 0for track in tracks:print(track)total += trackActionChains(browser).move_by_offset(xoffset=track,yoffset=random.randint(-5,5)).perform()time.sleep(random.randint(50,100)/100)ActionChains(browser).move_by_offset(xoffset=5,yoffset=random.randint(-5,5)).perform()ActionChains(browser).move_by_offset(xoffset=-5,yoffset=random.randint(-5,5)).perform()time.sleep(0.5)ActionChains(browser).release(on_element=div).perform()time.sleep(3)html = browser.page_sourcetime.sleep(1.)soup = BeautifulSoup(html,"lxml")title = soup.find("title")if str(title.string[4])=="弹":print("登录失败!准备重新登录!")else:print("登录成功!")return browser

这是个类函数,原类在https://blog.csdn.net/CY19980216/article/details/89074771

总之这次经历之后我觉得只要WEB开发者愿意,绝对可以做到让你什么东西也拿不到。

推荐一篇别人做成功的https://blog.csdn.net/z434890/article/details/93631888,但是我用这个方法确实不管用,唉算了,没必要跟自己过不去。

分享学习,共同进步!


20191202更新

好吧,我承认是我蠢了,页面上其实有三个canvas,其中一个是默认不展示的(即原图),一个是展示的有缺块的图,一个是滑块的图,我之所以用browser.execute_script()拿到的只有滑块是我太蠢了,这样问题其实就转化为前一篇模拟登录里面的情况了,很容易就可以找到边界了:

重新附上代码

	def login_20191202(self,):"""页面上有三个canvas, 原图与缺块图都可以很容易拿到的;这三个canvas的class属性分别为:"geetest_canvas_fullbg geetest_fade geetest_absolute"	 # 无缺块的图片"geetest_canvas_slice geetest_absolute"					 # 滑块图片"geetest_canvas_bg geetest_absolute"					 # 有缺块的图片问题由此转化为20190408的情况;"""def download_verifying_picture(canvas_class,name):				 # 下载滑动验证图片(从canvas标签中转化得到)JS = 'return document.getElementsByClassName("{}")[0].toDataURL("image/png");'.format(canvas_class)image_data = browser.execute_script(JS) 					 # 执行js代码得到带图片信息的图片数据image_base64 = image_data.split(",")[1]  					 # 摘取base64编码的图片信息image_bytes = base64.b64decode(image_base64) 					 # 将base64代码转为bytes类型with open("{}\\{}\\{}.png".format(self.workspace,self.tempFolder,name),"wb") as f: f.write(image_bytes)def find_block_space(width=64,zoo=1.0,plot=True):				 # 寻找缺块位置(默认参数width为缺块的列宽像素,zoo这边用1.15基本上大概率能过了,但是我查了一下两个图片的属性应该是1.2,设为1.2应该要改常数项了)image1 = numpy.asarray(Image.open("{}\\{}\\1.png".format(self.workspace,self.tempFolder)))image2 = numpy.asarray(Image.open("{}\\{}\\2.png".format(self.workspace,self.tempFolder)))Xaxis,Yaxis,Zaxis = image1.shape							 # 获取图片三维信息(160×15×3)errors = []													 # 记录260列宽上每个列向量的非相似度值for i in range(Yaxis):total = 0for j in range(Zaxis):X = numpy.array([image1[:,i,j]]).astype("int64")Y = numpy.array([image2[:,i,j]]).astype("int64").TnormX = numpy.linalg.norm(X,2)normY = numpy.linalg.norm(Y,2)dotXY = numpy.dot(X,Y)[0,0]error = 1.- (dotXY/normX/normY)						 # 这里我选择累积RGB在(1-余弦相似度)上的值total += errorerrors.append(total)tempErrors = errors[:]tempErrors.sort(reverse=True)index = [errors.index(i) for i in tempErrors[:width]]		 # 计算排序后对应的索引排序(根据图像的结果来看应该前width的索引是至少近似连续的自然数)if plot:plt.plot([i for i in range(len(errors))],errors)plt.savefig("{}\\{}\\error.jpg".format(self.workspace,self.tempFolder))return min(index[:10])/zoo-10def get_track(xoffset):											 # 获取一条路径(给定需要移动的距离)tracks = []x = int(xoffset/2)											 # 先走一半(很关键,不走不给过)tracks.append(x)xoffset -= xwhile xoffset>=10:x = random.randint(5,9)tracks.append(x)xoffset -= xfor i in range(int(xoffset)): tracks.append(1)				 # 最后几步慢慢走return trackswhile True:browser = webdriver.Firefox()								 # 驱动火狐浏览器browser.maximize_window()									 # 窗口最大化browser.get(self.loginURL)									 # 访问登录页面interval = 2.												 # 初始化页面加载时间(如果页面没有加载成功,将无法获取到下面的滑动验证码按钮,林外我意外的发现有时候竟然不是滑动验证,而是验证图片四字母识别,个人感觉处理滑动验证更有意思)class_bg = "geetest_canvas_bg geetest_absolute"class_fullbg = "geetest_canvas_fullbg geetest_fade geetest_absolute"xpath_slice = "//canvas[@class='geetest_canvas_slice geetest_absolute']"xpath_bg = "//canvas[@class='{}']".format(class_bg)xpath_fullbg = "//canvas[@class='{}']".format(class_fullbg)while True:													 # 由于可能未成功加载,使用循环确保加载成功browser.find_element_by_id("login-username").send_keys(self.username)browser.find_element_by_id("login-passwd").send_keys(self.password)time.sleep(interval)browser.find_element_by_xpath("//a[@class='btn btn-login']").click()try:													 # 我担心加载不出来time.sleep(interval)								 # 等待加载canvas = browser.find_element_by_xpath(xpath_slice)breakexcept:													 # 验证图片canvas未加载出来browser.refresh()									 # 刷新页面interval += .5										 # 每失败一次让interval增加0.5秒print("验证图片加载失败!页面等待时间更新为{}".format(interval))continuehtml = browser.page_sourcesoup = BeautifulSoup(html,"lxml")download_verifying_picture(class_bg,1)					 # 有缺块download_verifying_picture(class_fullbg,2)				 # 无缺块xoffset = find_block_space()							 # 寻找缺块位置的横坐标tracks = get_track(xoffset)								 # 生成轨迹div = browser.find_element_by_class_name("geetest_slider_button")ActionChains(browser).click_and_hold(on_element=div).perform()total = 0for track in tracks:print(track)total += trackActionChains(browser).move_by_offset(xoffset=track,yoffset=random.randint(-5,5)).perform()time.sleep(random.randint(50,100)/100)ActionChains(browser).move_by_offset(xoffset=5,yoffset=random.randint(-5,5)).perform()ActionChains(browser).move_by_offset(xoffset=-5,yoffset=random.randint(-5,5)).perform()time.sleep(0.5)ActionChains(browser).release(on_element=div).perform()time.sleep(3)html = browser.page_sourcetime.sleep(1.)soup = BeautifulSoup(html,"lxml")title = soup.find("title")if str(title.string[4])=="弹":print("登录失败!准备重新登录!")browser.quit()else:print("登录成功!")return browser	

 


http://chatgpt.dhexx.cn/article/XIQB9bFi.shtml

相关文章

geetest极验空间推理验证码破解与研究

看了很多的破解滑动验证码,决定破解一下空间推理验证码。破解思路,通过分析接口请求,对图片物体进行定位分类,通过模拟请求破解验证码。 研究的网站为 https://www.geetest.com/show 一、极验请求分析 请求详细 一、register-s…

googletest简介

googletest是由谷歌的测试技术团队开发的测试框架,使用c实现,具有跨平台等特性。 好的测试框架 引用谷歌给出的文档,好的测试应当具备以下特征: 测试应该是独立的和可重复的。调试一个由于其他测试而成功或失败的测试是一件痛苦…

破解极验(geetest)验证码

最近在搞爬虫的时候在好几个网站都碰到了一种叫做geetest的滑动条验证码,一直没有太好的办法只能在触发这个验证码后发个报警去手动处理一下。http://www.geetest.com/exp_embed是他们官网的样例。 后来研究了下觉得要破解这个验证码有这么几个问题: 无法直接通过发送…

破解滑块验证码最新版(GEETEST 95%以上通过率)

一、滑块验证码简述 有爬虫,自然就有反爬虫,就像病毒和杀毒软件一样,有攻就有防,两者彼此推进发展。而目前最流行的反爬技术验证码,为了防止爬虫自动注册,批量生成垃圾账号,几乎所有网站的注册页…

极验GeeTest简单demo

概述 人机验证 3.0 解决方案(基于生物行为与人工智能) 2012 年极验将人机验证从1.0时代推动到了 2.0 时代。在 5 年时间中,超过千亿次数据学习与优化,极验利用三角防护理论和 AI 智能决策引擎,全面更新安全架构。2017 年,正式推出…

极验geetest的使用

项目中会遇到 滑块验证的需求: 前端vue里 1.新建/utils/gt3.js "v0.4.8 Geetest Inc.";(function (window) {"use strict";if (typeof window undefined) {throw new Error(Geetest requires browser environment);}var document window.do…

爬虫进阶教程:极验(GEETEST)验证码破解教程

摘要: 爬虫最大的敌人之一是什么?没错,验证码!Geetest作为提供验证码服务的行家,市场占有率还是蛮高的。遇到Geetest提供的滑动验证码怎么破?授人予鱼不如授人予渔,接下来就为大家呈现本教程的精彩内容。 一…

【已解决】安卓手机的GeeTest文件夹是什么

网上关于安卓系统手机的GeeTest目录是什么的文章和帖子,绝大部分打着GeeTest的标题,内容都是牛头不对马嘴,答非所问,没一个能解释清楚。 我刚刚找到了正式的答复如下: Android 手机上的“geetest”目录与名为“极验”的…

正定二次型与半正定二次型

对于实二次型其中A是实对称的,下列条件等价: 正定的 (1)是正定的. (2)它的正惯性指数p等于n. (3)有可逆实矩阵C,使得其中 (4)实对称矩阵A是正…

怎么对document.write写出来的内容调整对齐方式_【求职技巧】给少数人:硅谷BAT级别的简历这么写...

我什么都不会”,“我什么都没干”,“这个项目很水”,这是我在帮别人修改简历时听到的最多的几句话。难道你真的什么都不会吗?真的什么都没干吗?真的很水吗?其实很多情况下,是这样的。 但是很水就放弃治疗了吗?不会的。放下无谓的抱怨和遗憾,好好梳理自己,认真编…

c语言八皇后问题经典算法,经典算法之八皇后问题

八皇后问题是一个古老而又著名的问题,是学习回溯算法的一个经典案例。今天我们就一起来探究一下吧! 时间退回到1848年,国际西洋棋棋手马克斯贝瑟尔提出了这样的一个问题, 在88格的国际象棋上摆放八个皇后,使其不能互相…

从八皇后问题思考回溯法

一、八皇后问题 八皇后是经典的回溯法问题,题目是说将八个皇后,放到88的国际象棋棋盘中中,使得任意两个皇后都不能在同一行、同一列以及同一条对角线上。下图是一个四皇后的搜索示意图。 八皇后问题可以通过暴力法求解,代码也很…

八皇后问题(Python)

一.问题简介 八皇后问题: 如何能在 8*8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了到达此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。 二.几种思路和方法 1.回溯法递归思想 如图所…

八皇后问题详解(四种解法)

所有源码都在github上(https://github.com/seasonyao/eight_queen_question) 如果你去百度百科八皇后这个问题,你会发现人家也是历史上有头有脸的一个问题,最后一句“计算机发明后就有一万种方式解决这个问题”读起来也让程序猿们很快活。闲话少说,开始阐述我的思路: 最…

八皇后问题

八皇后问题 八皇后问题(英文:Eight queens),是由国际西洋棋棋手马克斯贝瑟尔于1848年提出的问题,是回溯算法的典型案例。 问题表述为:在88格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或…

八皇后问题(适合初学者的写法)C语言

什么是八皇后问题: 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于1848年提出:在88格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能…

八皇后问题,秒懂递归回溯(有图详解|c语言)

目录 👸🏻前言 👸🏻题目介绍 👸🏻引入: 👸🏻解决思路: 👸🏻理论存在,实践开始! 👸&#x1f…

利用ngrok实现域名映射局域网ip

前言 相信很多开发者都有这样的需求,需要让外网访问你本地的服务器,方便调试本地代码,或者让别人体验到自己做的应用。那么这时,我们需要做的就是将我们本地的端口映射到一个外网的端口上,也就是内网穿透。常见的解决…

python调用手机摄像头,实现实时调用摄像头,需要你的电脑和手机在同一个局域网内

1、android手机上安装一款APP:IP摄像头,app的图片如上图 2.调用代码如下 import cv2cv2.namedWindow("camera", 1) # 开启ip摄像头 video "http://admin:admin10.0.0.32:8081/" # 此处后的ipv4 地址需要改为app提供的地址 cap c…

02、处于不同局域网下的Socket通信(网络部分理论知识)

目录 一、服务器 1、服务器的种类和功能 2、服务器的操作系统 3、IIs、Apache、Tomcat 4、云服务器 弹性云服务器(Elastic Cloud Server,ECS) 云服务器安全组 二、OSI七层模型与TCP/IP五层模型 三、外网、内网、公网、私网 内网穿透…