Python破解BiliBili滑块验证码,完美避开人机识别

前言本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 。
作者:Mingyueyixi
PS:如有需要Python学习资料的小伙伴可以私信小编获取

Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
准备工作
  • B站登录页 https://passport.bilibili.com/login
  • python3
  • pip install selenium (webdriver框架)
  • pip install PIL (图片处理)
  • chrome driver:http://chromedriver.storage.googleapis.com/index.html
  • firefox driver:https://github.com/mozilla/geckodriver/releases
B站的滑块验证码如上 。
这类验证码可以使用 selenium 操作浏览器拖拽滑块来进行破解 , 难点两个 , 一个如何确定拖拽到的位置 , 另一个是避开人机识别(反爬虫) 。
Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
确定滑块验证码需要拖拽的位移距离【Python破解BiliBili滑块验证码,完美避开人机识别】有三种方式
  • 人工智能机器学习 , 确定滑块位置
  • 通过完整图片与缺失滑块的图片进行像素对比 , 确定滑块位置
  • 边缘检测算法 , 确定位置
各有优缺点 。人工智能机器学习 , 确定滑块位置 , 需要进行训练 , 比较麻烦 , 也可以看是否存在在线api可以调用 。以下介绍其他两种方式 。
对比完整图片与缺失滑块的图片
仅介绍 , 本文不进行实现 。对于B站来说 , 是准确率最高的方式(100%) , 但不能保证未来B站的滑块验证升级 , 导致不可用 。
B站的滑块验证模块 , 一共有三张图片:完整图、缺失滑块图、滑块图 , 都是由画布绘制出的 。类似于:
完整图:
Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
缺失滑块图:
Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
滑块图:
Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
HTML代码类似于:
<div class="geetest_canvas_img geetest_absolute" style="display: block;"><div class="geetest_slicebg geetest_absolute"> <canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260"></canvas> <canvas class="geetest_canvas_slice geetest_absolute" width="260" height="160"></canvas></div><canvas class="geetest_canvas_fullbg geetest_fade geetest_absolute" height="160" width="260" style="display: none;"></canvas></div>只需要通过selenium获取画布元素 , 执行js拿到画布像素 , 遍历完整图和缺失滑块图的像素 , 一旦获取到差异(需要允许少许像素误差) , 像素矩阵x轴方向即是滑块位置 。
另外由于滑块图距离画布坐标原点有距离 , 还需要减去这部分距离 。
最后使用 selenium 拖拽即可 。
边缘检测算法 , 确定位置滑块基本上是个方形 , 通过算法确定方形起始位置即可 。
Python破解BiliBili滑块验证码,完美避开人机识别

文章插图
 
介绍两种方式
  • 滑块是方形的 , 存在垂直与水平的边 , 该边在缺失滑块图中基本都是灰黑的 。遍历像素找到基本都是灰黑的边即可 。
  • 缺失滑块图中滑块位置是灰黑封闭的 。通过算法可以找到封闭区域 , 大小与滑块相近 , 即是滑块需要拖拽到的位置 。
第二种实现起来有些复杂 , 不进行实现了 。
下面是第一种实现方式(只实现了垂直边的检测 , 水平边检测原理一致) , 会存在检测不出或错误的情况 , 使用时需要换一张验证码 。也可能存在检测出的边是另一条(因为B站的滑块不是长方形 , 存在弧形边) , 那么需要减去滑块宽度
class VeriImageUtil():def __init__(self):self.defaultConfig = {"grayOffset": 20,"opaque": 1,"minVerticalLineCount": 30}self.config = copy.deepcopy(self.defaultConfig)def updateConfig(self, config):# temp = copy.deepcopy(config)for k in self.config:if k in config.keys():self.config[k] = config[k]def getMaxOffset(self, *args):# 计算偏移平均值最大的数av = sum(args) / len(args)maxOffset = 0for a in args:offset = abs(av - a)if offset > maxOffset:maxOffset = offsetreturn maxOffsetdef isGrayPx(self, r, g, b):# 是否是灰度像素点 , 允许波动offsetreturn self.getMaxOffset(r, g, b) < self.config["grayOffset"]def isDarkStyle(self, r, g, b):# 灰暗风格return r < 128 and g < 128 and b < 128def isOpaque(self, px):# 不透明return px[3] >= 255 * self.config["opaque"]def getVerticalLineOffsetX(self, bgImage):# bgImage = Image.open("./image/bg.png")# bgImage.im.mode = 'RGBA'bgBytes = bgImage.load()x = 0while x < bgImage.size[0]:y = 0# 点》》线 , 灰度线条数量verticalLineCount = 0while y < bgImage.size[1]:px = bgBytes[x, y]r = px[0]g = px[1]b = px[2]# alph = px[3]# print(px)if self.isDarkStyle(r, g, b) and self.isGrayPx(r, g, b) and self.isOpaque(px):verticalLineCount += 1else:verticalLineCount = 0y += 1continueif verticalLineCount >= self.config["minVerticalLineCount"]:# 连续多个像素都是灰度像素 , 直线# print(x, y)return xy += 1x += 1passif __name__ == '__main__':bgImage = Image.open("./image/bg.png")veriImageUtil = VeriImageUtil()# veriImageUtil.updateConfig({#"grayOffset": 20,#"opaque": 0.6,#"minVerticalLineCount": 10# })bgOffsetX = veriImageUtil.getVerticalLineOffsetX(bgImage)print("bgOffsetX:{} ".format(bgOffsetX))


推荐阅读