Python 100%解析svg-captcha验证码

news2024/11/18 13:41:18

前言

前段时间接到一个需求,登陆某一个网站,然后录入数据;本来以为是一个很简单的需求,结果遇到几个难点:

  1. 登陆的时候需要有验证码
  2. 验证码是一个请求路径,每请求一次验证码都不一样

本来一开始以为是常用的图片验证码,但是当查看页面源码是,并没有发现对应的验证码图片,而是发现了一个由svg标签组装的代码,如下:

<svg xmlns="http://www.w3.org/2000/svg" width="80" height="32" viewBox="0,0,80,32">
    <path fill="#111" d="M34.71 25.15L34.77 ......"/>
    <path fill="#222" d="M61.76 17.99L61.68 ......"/>
    <path fill="#222" d="M8.54 8.38L8.63 8.47L8.47 ......"/>
    <path d="M7 23 C57 24,22 9,75 17" stroke="#333" fill="none"/>
    <path fill="#333" d="M39.66 8.30L39.83 8.47L39.70......"/>
    <path d="M19 15 C53 9,43 29,62 27" stroke="#666" fill="none"/>
</svg>

上面代码对应验证码如下:

image

我对前端不怎么熟悉,所以在网上查,在github上看到了一个100%验证svg验证码的项目,但他这个是nodejs写的,我想用python写一个,于是学习了一下大佬的代码,并自己改了一个python版本的

大佬项目地址:svg-captcha-recognize

简单分析

首先查看大佬这串代码:

// 从svg中把几个字母的d内容取出来,同时把字母按照它们在svg中的顺序排列
const getLetters = (svg) => {
  let i = 0
  const letters = []
  while (i < svg.length - 1 && i !== -1) {
    const pathStart = svg.indexOf('<path', i)
    if (pathStart === -1) {
      break
    }
    let pathEnd = svg.indexOf('>', pathStart)
    if (pathEnd === -1) {
      pathEnd = svg.length
    } else {
      pathEnd++
    }

    // 太短的是噪点
    if (pathEnd - pathStart > 500) {
      const path = svg.substring(pathStart, pathEnd)
      const [, d] = path.match(/d="([^"]+)"/) || []
      if (d) {
        letters.push(d)
      }
    }

    i = pathEnd
  }

  // 给字母按照位置排序
  if (letters.length) {
    letters.sort((a, b) => {
      const [ax] = a.match(/\d+(\.\d*)?/)
      const [bx] = b.match(/\d+(\.\d*)?/)
      return parseFloat(ax) - parseFloat(bx)
    })
  }

  return letters
}

const utils = {
  getMoveY (path) {
    const [,,, moveY] = path.match(/M(\d+(\.\d*)?)\s+(\d+(\.\d*)?)/) || []
    return parseFloat(moveY)
  },

  getAllXY (path) {
    return (path.match(/(\d+(\.\d*)?)/g) || []).map(v => parseFloat(v))
  },

  getMinXY (path) {
    const xs = []
    const ys = []
    this.getAllXY(path).forEach((v, i) => {
      (i % 2 ? ys : xs).push(v)
    })
    return [
      Math.min(...xs),
      Math.min(...ys)
    ]
  },

  // 获取宽高
  getWH (path) {
    const xs = []
    const ys = []
    this.getAllXY(path).forEach((v, i) => {
      (i % 2 ? ys : xs).push(v)
    })
    const maxXY = [
      Math.max(...xs),
      Math.max(...ys)
    ]
    const minXY = [
      Math.min(...xs),
      Math.min(...ys)
    ]
    return [
      maxXY[0] - minXY[0],
      maxXY[1] - minXY[1]
    ]
  }
}

module.exports = {
  recognize (svg) {
    const letters = getLetters(svg)
    return letters.map(l => {
      if (lengthSameMap[l.length]) {
        return lengthSameMap[l.length](l)
      }
      const letters = lengthMap[l.length] || ['']
      if (!letters[0]) { // 这个值没有记录到
        console.log(`had not train : ${l}`)
      }
      return letters[0]
    }).join('')
  }
}

通过查看这串代码,我们可以分析出以下几步:

  1. 通过getLetters()方法获取到svg标签中所有path标签中的d属性值
  2. 较短的则是干扰线,直接剔除
  3. 获取d属性的长度
  4. 然后记录这个长度以及对应的数字
  5. 在这种情况下,从【a-Z】【1-9】中一定会有相同数字对应不用结果的,所以,大佬通过不同的宽高来区分

如果是在svg默认字体的情况下,我们可以得到以下结果:

const lengthMap = {
  986: ['I', 'l'],
  998: ['1'],
  1068: ['I', 'l'],
  1081: ['1'],
  1082: ['v'],
  1130: ['Y'],
  1134: ['Y'],
  1172: ['v'],
  1224: ['Y'],
  1274: ['L', 'y'],
  1298: ['V'],
  1311: ['V'],
  1360: ['i'],
  1380: ['L', 'y'],
  1406: ['V'],
  1473: ['i'],
  1478: ['T']
  ......
}

一般情况下不会是默认字体的,所以需要我们自己灵活获取

如果实在需要使用svg-captcha,请一定准备多套字体,并且经常更换,英文字体还是很容易找到的。

使用Python解析

将使用同上面一样的步骤解析,由于我不是很懂前端,所以也没怎么去研究svg-captcha到底是个啥,以及它的一些规则

这个方式有一个缺点就是:需要自己录入数据,如果没有根据输入的数据自己生成svg图片的时候,就需要自己一个一个获取指定svg验证码,以及自己判断完之后,从而进行记录

1、获取svg中所有path标签中的d属性值

import pymysql
from xml.dom import minidom
import re
import os

# 读取当前文件下的 image.scg 文件 并获取所有path标签
def get_path_elements(self, svg_file_path):
    doc = minidom.parse(svg_file_path)
    path_elements = doc.getElementsByTagName('path')
    return path_elements

# 把获取到的path标签通过坐标从左到右排序好
def sort_paths_by_left_position(self, path_elements):
    paths_without_stroke = []
    for path in path_elements:
        if not self.has_stroke_attribute(path):
            paths_without_stroke.append(path)
            sorted_paths = sorted(paths_without_stroke, key=lambda path: self.get_path_element_left_position(path))
            return sorted_paths

# 获取svg中各个path标签中对应的数量,也就是key值
def get_value(self, code):
    svg_file_path = 'image.svg'
    path_elements = self.get_path_elements(svg_file_path)
    sorted_paths = self.sort_paths_by_left_position(path_elements)
    # 打印排序后的路径顺序
    code_list = []
    for index, path in enumerate(sorted_paths):
        d = path.getAttribute('d')
        path_data = d.split(" ")
        key_value = (len(path_data), code[index])
        code_list.append(key_value)  

2、较短的线直接剔除

我是通过判断他是否有stroke这个属性,从而区分的

# 判断path标签中是否存在stroke属性
def has_stroke_attribute(self, path_element):
    return path_element.hasAttribute('stroke')

3、长度相同的值通过宽高来区分

大佬的第三步和第4步,我已经在第一步给出,大佬是通过所有长度,我是通过空格分割之后统计的长度,在我的场景下,这样的处理,重复率会降低,大家可根据自己的实际情况调整

# 通过d属性获取值得宽高
def get_path_dimensions(self, d):
    coordinates = re.findall(r'[-+]?\d*\.?\d+', d)
    x_values = [float(x) for x in coordinates[::2]]
    y_values = [float(y) for y in coordinates[1::2]]
    min_x = min(x_values)
    max_x = max(x_values)
    min_y = min(y_values)
    max_y = max(y_values)
    width = round(max_x - min_x, 2)
    height = round(max_y - min_y, 2)
    return width, height

4、通过宽高的差值来判断值是什么

因为就算是相同的字母,也会出现宽高细微上的差别,所以只能通过谁最接近,就取谁,所幸的是,我相同数字的字母,宽高区别大,所以能精确的获取验证码的结果,大家可根据自己的实际情况调整

				closest_value = None
                closest_diff = float('inf')
                for item in row_list:
                    width_diff = abs(float(item['width']) - width)
                    height_diff = abs(float(item['height']) - height)
                    total_diff = width_diff + height_diff
                    if total_diff < closest_diff:
                        closest_diff = total_diff
                        closest_value = item["value"]

完整代码

在code文件里

[ svg-captcha-recognize-python]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探究 CoreData 使用索引(Index)机制加速查表究竟如何实现?

问题现象 在  App 的开发中,CoreData 到底能不能用索引机制(Index)来加速查表?如果可以,又该如何创建和使用索引呢? 这是一个连  官方文档都模棱两可,Stackoverflow 里诸多大神都闪烁其词的话题。 在本篇博文中,您将学到如下内容: 什么是 CoreData 索引(Index…

SpringBoot + Ant Design Vue实现数据导出功能

SpringBoot Ant Design Vue实现数据导出功能 一、需求二、前端代码实现2.1 显示实现2.2 代码逻辑 三、后端代码实现3.1 实体类3.2 接收参数和打印模板3.3 正式的逻辑3.4 Contorller 一、需求 以xlsx格式导出所选表格中的内容要求进行分级设置表头颜色。 二、前端代码实现 2…

20230524 taro+vue3+webpack5+pdfjs时打包pdfjs进不来的问题

关闭taro的terser就可以了 terser:{enable:false }

UE中创建异步任务编辑器工具(Editor Utility Tasks)

在UE中我们往往需要执行一些编辑器下的异步任务&#xff0c;例如批量生成AO贴图、批量合并静态模型等&#xff0c;又不想阻碍主线程&#xff0c;因此可以使用Editor Utility Tasks直接创建UE编辑器下的异步任务。 如果你不太了解UE编辑器工具&#xff0c;可以参考这篇文章&…

Spring Boot 中自定义数据校验注解

Spring Boot 中自定义数据校验注解 在 Spring Boot 中&#xff0c;我们可以使用 JSR-303 数据校验规范来校验表单数据的合法性。JSR-303 提供了一些常用的数据校验注解&#xff0c;例如 NotNull、NotBlank、Size 等。但是&#xff0c;在实际开发中&#xff0c;我们可能需要自定…

2023年6月24日(星期六):骑行明郎

2023年6月24日(星期六)&#xff1a;骑行明郎&#xff0c;早8:30到9:00&#xff0c; 大观公园门囗集合&#xff0c;9:30点准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点: 大观公园门囗集合&#xff0c;家住南&#xff0c;东&#xff0c…

(二叉树) 100. 相同的树 ——【Leetcode每日一题】

❓100. 相同的树 难度&#xff1a;简单 给你两棵二叉树的根节点 p 和 q&#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q …

使用代理ip做网页抓取需要注意什么

现在&#xff0c;很多公司为达成目标&#xff0c;都需要抓取大量数据。企业需要根据数据来作出重大决定&#xff0c;因此掌握准确信息至关重要。互联网上有许多宝贵的公共数据。问题是如何轻松采集这些数据&#xff0c;而无需让团队整天手动复制粘贴所需信息?网页抓取的定义越…

Qt学习11:Dialog对话框操作总结

文章目录 QDialogQDialogButtonBoxQMessageBoxQFileDialogQFontDialogQColorDialogQInputDialogQProgressDialog 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 QDialog Qt中使用QDialog来实现对话框&#xff0c;QDialog继承自QWidget&#xff0c;对话框分为**三种**&…

尿的唰唰和笑的哈哈

很多人说看不懂&#xff0c;不知道哪个是真哪个是假。我说都是真的。不同心不同理。全球并不同炎凉。窦唯有句歌词&#xff1a;天堂地狱皆在人间。何勇有句歌词&#xff1a;有人减肥&#xff0c;有人饿死没粮。&#xff08;1&#xff09;产业我过去说过顶天立地。立地&#xff…

专利背后的故事 | 一种异常信息检测方法和装置

Part01 专利发明的初衷 用户和实体行为分析&#xff08;UEBA&#xff09;在2018年入选Gartner为安全团队建议的十大新项目。UEBA近几年一直受到国内安全厂商的热捧。但是对于UEBA的理解&#xff0c;以及具体落实的产品方案&#xff0c;各厂商虽然明显不同&#xff0c;但在对账…

Go应用性能优化的8个最佳实践,快速提升资源利用效率!

作者&#xff5c;Ifedayo Adesiyan 翻译&#xff5c;Seal软件 链接&#xff5c;https://earthly.dev/blog/optimize-golang-for-kubernetes/ 优化服务器负载对于确保运行在 Kubernetes 上的 Golang 应用程序的高性能和可扩展性至关重要。随着企业越来越多地采用容器化的方式和 …

HOOPS Native Platform 2023 cRACK

将高级 3D 工作流程添加到桌面和移动应用程序 HOOPS 原生平台集成了三种用于桌面和移动应用程序开发的先进 HOOPS 技术&#xff0c;包括高性能图形 SDK、CAD 数据访问工具包和 3D 数据发布 API。 ​ ​ 构建 3D 原生应用 借助桌面和移动设备上的 HOOPS 原生平台&#xff0c;快…

一个初级程序员该在哪接项目练手?

作为一个初级程序员&#xff0c;想要通过兼职接单赚钱&#xff0c;离不开项目练手。但不得不说&#xff0c;初级程序员想要通过接私活获取收入还是相对比较困难的&#xff0c;如果对接私活比较感兴趣的朋友&#xff0c;可以参考这条路径&#xff1a; 在GitHub上学习大佬的项目…

【WebLogic】WebLogic 10.3.6.0部署应用包后报错

问题背景&#xff1a; WebLogic 10.3.6.0部署应用包后出现报错【posted content exceeds max post size】&#xff0c;此报错会导致应用部署的目标服务实例无法成功启动。 报错信息截图如下所示&#xff1a; 根据报错信息&#xff0c;查询相关MOS文档&#xff0c;发现问题原因是…

网络能成为AI加速器吗

网络能成为AI加速器吗 摘要 人工神经网络&#xff08;NNs&#xff09;在许多服务和应用中扮演越来越重要的角色&#xff0c;并对计算基础设施的工作负载做出了重要贡献。在用于延迟敏感的服务时&#xff0c;NNs通常由CPU处理&#xff0c;因为使用外部专用硬件加速器会效率低下…

Magisk hide/Denylist 核心原理分析 ROOT隐藏的实现浅论

前言 当手机安装magisk后&#xff0c;全局的挂载空间会受到变更&#xff0c;magisk给我们挂载上了一个su二进制&#xff0c;这就是我们能够访问到su命令的原因 无论是Magisk hide还是Denylist&#xff0c;我们都可以将它们的工作分成两个部分&#xff0c;第一个部分是如何监控…

vue2中引入天地图及相关配置

前言 项目中需要引入特殊用途的地图&#xff0c;发现天地图比高德地图、百度地图要更符合需求&#xff0c;于是看了看天地图。 正文 vue2项目中如何引入天地图并对相关的配置进行修改使用呢&#xff1f;官方给的4.0版本的使用说明。 引入&#xff1a; 进入到public/index.html中…

使用逻辑回归LogisticRegression来对我们自己的数据excel或者csv数据进行分类--------python程序代码,可直接运行

文章目录 一、逻辑回归LogisticRegression是什么&#xff1f;二、逻辑回归LogisticRegression进行分类的具体步骤二、逻辑回归LogisticRegression进行二分类的详细代码三、逻辑回归LogisticRegression的广泛用途总结 一、逻辑回归LogisticRegression是什么&#xff1f; 逻辑回…

小白白也能学会的 PyQt 教程 —— QRadioButton 介绍以及基本使用

文章目录 一、QRadioButton快速入门1. QRadioButton简介2. QRadioButton快速上手 二、响应单选按钮点击事件1、信号和槽机制&#xff1a;2、创建槽函数来响应单选按钮点击&#xff1a;3、示例&#xff1a;执行特定操作或显示相关内容&#xff1a; 三、单选按钮的常用功能和属性…