字体反爬(一)

news2024/9/21 0:47:24

网址

http://xxfb.mwr.cn/sq_djdh.html?v=1.0
获取相关数据

解决

  1. F12 先找接口吧, 搜索一下表格的数据
    在这里插入图片描述
    在这里插入图片描述
    直接从表格中复制
    复制过来乱码,基本锁定有字体反爬处理
    在这里插入图片描述
  2. 先点进去看看
{
           "addvnm": "#GkcERlldm4_1725629424756otltag㯼㢴#FontTag",
           "alertValue": 0.0,
           "bsnm": "#GkcERlldm4_1725629424756otltag㱬㯼#FontTag",
           "createTime": "2024-09-06 15:40:21",
           "idNo": "#GkcERlldm4_1725629424756otltag㝺㝸㝴#FontTag",
           "importantSection": "#GkcERlldm4_1725629424756otltag㣸#FontTag",
           "lgtd": "",
           "lttd": "",
           "rvnm": "#GkcERlldm4_1725629424756otltag㰬㯼#FontTag",
           "stType": "#GkcERlldm4_1725629424756otltag㞪㞜㞲㞘㞪#FontTag",
           "stnm": "#GkcERlldm4_1725629424756otltag㩘㲘#FontTag",
           "tm": "2024-09-06 08:00:00",
           "z": "#GkcERlldm4_1725629424756otltag㝶㝺.㝶㝶#FontTag"
       },

定位一下表格信息内容,源码有
在这里插入图片描述
字段能对上,说明接口是这个,

  1. 寻找字体文件
    在这里插入图片描述
    一般.ttf 或者woff 文件,再结合
<td class="hidden-m" align="center" width="13%" style="font-family:cfg_GkcERlldm4_1725629424756">㱬㯼</td>

GkcERlldm4_1725629424756 并且返回的数据也是带有GkcERlldm4_1725629424756
应该是这个ttf文件
刷新一次
在这里插入图片描述
发现文件名变化了, 而且能在network中观察到, ttf是后出现的
而ttf的文件名字就在返回数据中

#GkcERlldm4_1725629424756otltag㰬㯼#FontTag

所以要解码 㰬㯼 为正常的汉字

思路:

  1. 先根据数据接口拿到数据
  2. 从数据中提取ttf文件的名字
  3. 根据ttf文件名字从ttf文件接口中获取ttf文件并保存
  4. 对数据进行提取把多余部分去除
def extract_data(obj):
    new_obj = {}
    for key, value in obj.items():
        
        part1 = None
        if isinstance(value, str) and "#FontTag" in value:
            # 方法1: 通过字符串查找和切片提取
            start_tag = "#"
            middle_tag = "otltag"
            end_tag = "#FontTag"

            # 提取第一个部分(mPgcfp7TXR_1725546532529)
            start_idx = value.find(start_tag) + len(start_tag)
            middle_idx = value.find(middle_tag)
            part1 = value[start_idx:middle_idx]

            # 提取第二个部分(㸫㫣)
            middle_idx += len(middle_tag)
            end_idx = value.find(end_tag)
            part2 = value[middle_idx:end_idx]

            # 输出结果
            # print("Part 1:", part1)  # 输出 mPgcfp7TXR_1725546532529
            # print("Part 2:", part2)  # 输出 㸫㫣
            new_obj[key] = part2

        else:
            new_obj[key] = value
    return new_obj, part1




response = requests.get('http://xxfb.mwr.cn/OTMpfiwozTexvsf/OTMnprjsvUahsv', cookies=cookies, headers=headers, verify=False)
datas = json.loads(response.text)['result']
print("=======数据获取成功======")
# print(datas)
ttf_url = ""
newDatas = []
for i, data in enumerate(datas):
    # ttf_status = True
    data, ttf_url = extract_data(data)
    newDatas.append(data)
  1. 用FontCreator打开分析真正字符与Name之间的对应关系
    由于动态变化先记录下

㷼㶌 对应 淮河
在这里插入图片描述
在这里插入图片描述
淮的Name是glyph639
河的Name是glygh583

  1. 将字体文件转换为xml,分析uncode编码与Name之间的关系
from fontTools.ttLib import TTFont


def ttf_to_xml(ttf_file, xml_file):
    # 打开TTF文件
    font = TTFont(ttf_file)

    # 保存为XML文件
    font.saveXML(xml_file)
    print(f"TTF file '{ttf_file}' has been successfully converted to XML as '{xml_file}'.")


# 示例:将 'example.ttf' 转换为 'example.xml'
ttf_to_xml('./Temp/8UJgMNCr5u_1725630929271.ttf', './Temp/example.xml')

d
搜索cmap会发现

<map code="0x3900" name="glyph00001"/>

name和code有一个对应
我们找到glyph00639
在这里插入图片描述

<map code="0x3dfc" name="glyph00639"/><!-- CJK UNIFIED IDEOGRAPH-3DFC -->

在这里插入图片描述
0x3dfc -> 㷼
0x3dfc ->name : glyph00639 -> 淮

 <GlyphOrder>
    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
    <GlyphID id="0" name="glyph00000"/>
    <GlyphID id="1" name="glyph00001"/>
    <GlyphID id="2" name="glyph00002"/>
    <GlyphID id="3" name="glyph00003"/>
    <GlyphID id="4" name="glyph00004"/>
    <GlyphID id="5" name="glyph00005"/>
    <GlyphID id="6" name="glyph00006"/>
    <GlyphID id="7" name="glyph00007"/>
    <GlyphID id="8" name="glyph00008"/>

id和name也有对应关系, 有时候会用到这里用不到

和前面记录的一致
所以大概就是
乱码的Unicode-----Unicode码------码对应的name----[name对应的id]
7. 建立乱码与真正字符之间的映射关系
包括:
乱码与Name
根据Name,绘图OCR识别,返回真正字符

所以要从ttf文件中提取两者之间的关系,

def get_char_list_from_ttf(font_file):
    """ 给定font_file,获取它的中文字符 """
    font = TTFont(font_file)
    res = []
    for cmap_table in font['cmap'].tables:
        # if cmap_table.platformID == 3:
            # print(f"Windows Platform, Encoding ID: {cmap_table.encodingID}")
            # 打印当前 cmap 表中的字符映射
        for unicode_val, glyph_name in cmap_table.cmap.items():
            char = chr(unicode_val)
            # print(f"Unicode: {hex(unicode_val)}, Character: {char}, Glyph: {glyph_name}")
            res.append([hex(unicode_val), char, glyph_name])

    return res

输出:[[‘0x42d0’, ‘䋐’, ‘glyph01117’],]

在这里插入图片描述
但是怎么映射为正常字符呢?
因为这里的unicode和并不是正常汉字一一对应,

渲染+OCR文字识别
参考博文 ttf解析
直接copy
功能是给定义一个
乱码的字符对应的glyphName和对应的ttf文件,返回绘制并OCR识别的文字字符

import ddddocr


from fontTools.ttLib.ttFont import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.path import Path
import matplotlib._color_data as mcd
def ttf_to_char(font_file, glyph_set_name="glyph00583", temp_png_path="./"):
    # %matplotlib inline
    #加载字体
    font = TTFont(font_file)
    #7.1 生成PNG图片
    #7.1.1 第一步提取绘制命令语句
    #获取包含字形名称和字形对象的--字形集对象glyphset
    glyphset = font.getGlyphSet()
    # print(glyphset.keys())
    #获取pen的基类
    pen = SVGPathPen(glyphset)

    #查找"马"的字形对象
    glyph = glyphset[glyph_set_name]
    #绘制"马"的字形对象
    glyph.draw(pen)
    #提取"马"的绘制语句
    commands = pen._commands
    # print(commands)
    total_commands = []
    command = []
    for i in commands:
        #每一个命令语句
        if  i == 'Z':
            #以闭合路径指令Z区分不同轮廓线
            command.append(i)
            total_commands.append(command)
            command = []
        else:
            command.append(i)
    #从'head'表中提取所有字形的边界框
    xMin = font['head'].xMin
    yMin = font['head'].yMin
    xMax = font['head'].xMax
    yMax = font['head'].yMax
    # print("所有字形的边界框: xMin = {}, xMax = {}, yMin = {}, yMax = {}".format(xMin, xMax, yMin, yMax))
    #所有字形的边界框: xMin = -12, xMax = 264, yMin = -47, yMax = 220
    preX = 0.0
    preY = 0.0
    #笔的起始位置
    startX = 0.0
    startY = 0.0
    #所有轮廓点
    total_verts = []
    #所有指令
    total_codes = []
    #转换命令
    for i in total_commands:
        #每一条轮廓线
        verts = []
        codes = []
        for command in i:
            #每一条轮廓线中的每一个命令
            code = command[0] #第一个字符是指令
            vert = command[1:].split(' ') #其余字符是坐标点,以空格分隔
            # M = 路径起始 - 参数 - 起始点坐标 (x y)+
            if code == 'M':
                codes.append(Path.MOVETO)  #转换指令
                verts.append((float(vert[0]), float(vert[1])))  #提取x和y坐标
                #保存笔的起始位置
                startX = float(vert[0])
                startY = float(vert[1])
                #保存笔的当前位置(由于是起笔,所以当前位置就是起始位置)
                preX = float(vert[0])
                preY = float(vert[1])
            # Q = 绘制二次贝塞尔曲线 - 参数 - 曲线控制点和终点坐标(x1 y1 x y)+
            elif code == 'Q':
                codes.append(Path.CURVE3)  #转换指令
                verts.append((float(vert[0]), float(vert[1]))) #提取曲线控制点坐标
                codes.append(Path.CURVE3) #转换指令
                verts.append((float(vert[2]), float(vert[3]))) #提取曲线终点坐标
                #保存笔的当前位置--曲线终点坐标x和y
                preX = float(vert[2])
                preY = float(vert[3])
            # C = 绘制三次贝塞尔曲线 - 参数 - 曲线控制点1,控制点2和终点坐标(x1 y1 x2 y2 x y)+
            elif code == 'C':
                codes.append(Path.CURVE4)  #转换指令
                verts.append((float(vert[0]), float(vert[1]))) #提取曲线控制点1坐标
                codes.append(Path.CURVE4) #转换指令
                verts.append((float(vert[2]), float(vert[3]))) #提取曲线控制点2坐标
                codes.append(Path.CURVE4) #转换指令
                verts.append((float(vert[4]), float(vert[5]))) #提取曲线终点坐标
                #保存笔的当前位置--曲线终点坐标x和y
                preX = float(vert[4])
                preY = float(vert[5])
            # L = 绘制直线 - 参数 - 直线终点(x, y)+
            elif code == 'L':
                codes.append(Path.LINETO)  #转换指令
                verts.append((float(vert[0]), float(vert[1]))) #提取直线终点坐标
                #保存笔的当前位置--直线终点坐标x和y
                preX = float(vert[0])
                preY = float(vert[1])
            # V = 绘制垂直线 - 参数 - 直线y坐标 (y)+
            elif code == 'V':
                #由于是垂直线,x坐标不变,提取y坐标
                x = preX
                y = float(vert[0])
                codes.append(Path.LINETO)  #转换指令
                verts.append((x, y)) #提取直线终点坐标
                #保存笔的当前位置--直线终点坐标x和y
                preX = x
                preY = y
            # H = 绘制水平线 - 参数 - 直线x坐标 (x)+
            elif code == 'H':
                #由于是水平线,y坐标不变,提取x坐标
                x = float(vert[0])
                y = preY
                codes.append(Path.LINETO)  #转换指令
                verts.append((x, y)) #提取直线终点坐标
                #保存笔的当前位置--直线终点坐标x和y
                preX = x
                preY = y
            # Z = 路径结束,无参数
            elif code == 'Z':
                codes.append(Path.CLOSEPOLY)  #转换指令
                verts.append((startX, startY)) #终点坐标就是路径起点坐标
                #保存笔的当前位置--起点坐标x和y
                preX = startX
                preY = startY
            #有一些语句指令为空,当作直线处理
            else:
                codes.append(Path.LINETO)  #转换指令
                verts.append((float(vert[0]), float(vert[1]))) #提取直线终点坐标
                #保存笔的当前位置--直线终点坐标x和y
                preX = float(vert[0])
                preY = float(vert[1])
        #整合所有指令和坐标
        total_verts.append(verts)
        total_codes.append(codes)
    color_list = list(mcd.CSS4_COLORS)
    #获取所有的轮廓坐标点
    total_x = []
    total_y = []
    for contour in total_verts:
        #每一条轮廓曲线
        x = []
        y = []
        for i in contour:
            #轮廓线上每一个点的坐标(x,y)
            x.append(i[0])
            y.append(i[1])
        total_x.append(x)
        total_y.append(y)
    #创建画布窗口
    fig, ax = plt.subplots()
    #按照'head'表中所有字形的边界框设定x和y轴上下限
    ax.set_xlim(xMin, xMax)
    ax.set_ylim(yMin, yMax)
    #设置画布1:1显示
    ax.set_aspect(1)
    #添加网格线
    # ax.grid(alpha=0.8,linestyle='--')
    #画图
    # print(f"{glyph_set_name}======绘制图片=======")
    for i in range(len(total_codes)):
        #(1)绘制轮廓线
        #定义路径
        path = Path(total_verts[i], total_codes[i])
        #创建形状,无填充,边缘线颜色为color_list中的颜色,边缘线宽度为2
        patch = patches.PathPatch(path, facecolor = 'none', edgecolor = color_list[i+10], lw=2)
        #将形状添到图中
        ax.add_patch(patch)
        #(2)绘制轮廓点--黑色,点大小为10
        # ax.scatter(total_x[i], total_y[i], color='black',s=10)
    #保存图片
    temp_path = f"{temp_png_path}/temp.png"
    plt.savefig(temp_path)
    # print(f"{glyph_set_name}======保存图片=======")

    with open(temp_path, "rb") as f:
        content = f.read()
    # print(f"{glyph_set_name}=====DdddOcr开始识别文字=======")
    dddd = ddddocr.DdddOcr(show_ad=False)
    text = dddd.classification(content)
    return text

最后

DdddOcr 用pyinstaller打包存在的问题解决方法:
存在的问题xxx.onnx 找不到
把ddddocr包下面的

common.onnx
common_old.onnx
common_det.onnx

复制到和要打包那个脚本统一目录下
在这里插入图片描述
在这之前先打包一次,生成.spec文件,
在修改

datas=[('./common.onnx','ddddocr'),('./common_old.onnx','ddddocr'), ('./common_det.onnx','ddddocr')],

在这里插入图片描述
最后删除build, dist文件夹
重新打包
命令

pyinstaller main.spec

就OK了

我用的anaconda, 如果需要打包最好新建一个虚拟环境,安装好项目对应的包

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

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

相关文章

Linux 技巧汇编

10个重要的Linux ps命令实战 显示所有当前进程 根据用户过滤进程 通过cpu和内存使用来过滤进程 通过进程名和PID过滤 根据线程来过滤进程 树形显示进程 显示安全信息 格式化输出root用户&#xff08;真实的或有效的UID&#xff09;创建的进程 使用PS实时监控进程状态 …

泛型列表相关知识

集合 C#中集合是指在system.Collection下的类型&#xff0c;他们大多数是通过实现此命名空间下的接口来实现的。 C#集合是来维护一组对象的数据结构&#xff0c;与数组不同&#xff0c;集合包含更多的功能。如&#xff1a;自动添加元素到指定位置&#xff0c;排序等。 泛型集…

企业级WEB应用服务器---TOMACT

一、WEB技术介绍 1.1 Http和B/S结构 操作系统一般都有子进程系统&#xff0c;使用多进程就可以充分利用硬件资源&#xff0c;提高效率。在前面的学习中我们了解到进程中可以有多个线程&#xff0c;且每一个线程都可以被CPU调度执行&#xff0c;这样就可以让程序并行执行。一台…

深入浅出孪生神经网络,高效训练模型

大家好&#xff0c;在深度学习领域&#xff0c;神经网络几乎能处理各种任务&#xff0c;但通常需要依赖于海量数据来达到最佳效果。然而&#xff0c;对于像面部识别和签名验证这类任务&#xff0c;我们不可能总是有大量的数据可用。由此产生了一种新型的神经网络架构&#xff0…

【自考zt】【数据结构】【22.04】

一、单选 二、填空 三、解答 四、算法阅读 五、算法设计

【Flutter】解决第一次运行项目很慢(gradle需要下载依赖)

配置gradle默认下载路径 默认下C盘谁顶得住 配置环境变量 名称: GRADLE_USER_HOME 值: D:\Develop\gradle 自己创建一个 下边是重点 配置gradle远端下载地址 后边版本号自己换 https://mirrors.cloud.tencent.com/gradle/ https://mirrors.cloud.tencent.com/gradle/gradl…

Matlab 一维层状声子晶体振动传输特性

一维声子晶体的传递矩阵法是一种用于研究声波在一维周期性结构中传播的方法。这种方法基于‌波动方程和周期性边界条件&#xff0c;通过计算声波在不同介质中的传播特性&#xff0c;进而分析声子晶体的带隙结构。传递矩阵法可以有效地预测声波在一维声子晶体中的传播行为&#…

利用AI大语言模型和Langchain开发智能车算法训练知识库(上篇)

今天小李哥将介绍亚马逊云科技的Jupyter Notebook机器学习托管服务Amazon SageMaker上&#xff0c;通过AI大语言模型、向量知识库和LangChain Agent&#xff0c;创建用于AI 智能车模型训练的RAG问答知识库。整个项目的架构图如下&#xff1a; 本系列共分为上下两篇。在上篇内容…

Java中的强引用、软引用、弱引用和虚引用于JVM的垃圾回收机制

参考资料 https://juejin.cn/post/7123853933801373733 在 Java 中&#xff0c;引用类型分为四种&#xff1a;强引用&#xff08;Strong Reference&#xff09;、软引用&#xff08;Soft Reference&#xff09;、弱引用&#xff08;Weak Reference&#xff09;和虚引用&#xf…

5G移动网络运维实验(训)室解决方案

随着第五代移动通信技术&#xff08;5G&#xff09;的快速普及和工业互联网的迅猛发展&#xff0c;全球制造业正面临着前所未有的深刻变革。5G技术凭借其超高的传输速率、极低的延迟以及大规模的连接能力&#xff0c;为工业自动化、智能制造等领域带来了革命性的技术支持。为了…

【免费分享】GIS开发面试题(流程+自我介绍+基础篇+Openlayermapbox)

本篇文章针对GIS应届生就业方向及面试困惑问题进行了收集整理&#xff0c;并列出了关于GIS开发面试中常见的问题&#xff08;含答案&#xff09;。 “ 包括以下内容 前言 简介 面试之前 面试流程 自我介绍-AI 基础篇 1、GIS八股文基础篇 2、Openlayers图形绘制 3、倾…

2-1 opencv实战进阶系列 阈值编辑器

目录 一、不说废话&#xff0c;先上现象 二、前言 三、方法详解 四、贴出完整代码 一、不说废话&#xff0c;先上现象 二、前言 对图像的处理中&#xff0c;设置合适的掩膜、寻找多边形、颜色追踪等方法都需要预先设置好颜色的上阈值和下阈值&#xff0c;来从原图中分割出…

蔚来发布新财报,亏损收窄,营收同比增长98.9%!

KlipC报道&#xff1a;9月5日&#xff0c;蔚来发布2024年二季度财报&#xff0c;财报显示&#xff0c;营收174.5亿元&#xff0c;同比增长98.9%&#xff0c;环比增长76.1%&#xff1b;交付量5.74万台&#xff0c;同比增长143.9%&#xff0c;环比增长90.9%&#xff1b;营收和交付…

Yolov5实现目标检测——调用官方权重进行检测

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 安装源码 开源网址&#xff1a;GitHub - ultralytics/yolov5: YOLOv5 &#x1f680; in PyTorch > ONNX > CoreML > TFLite ​ 二 安装所需环境 安装环境依赖包&#xff0c;进入项目…

探索Mem0:下一代人工智能与机器学习内存管理基础设施(二)Mem0+Ollama 部署运行

探索Mem0:下一代人工智能与机器学习内存管理基础设施(二) Mem 0(发音为“mem-zero”)通过智能记忆层增强AI助手和代理,实现个性化的AI交互。Mem 0会记住用户偏好,适应个人需求,并随着时间的推移不断改进,使其成为客户支持聊天机器人,AI助手和自治系统的理想选择。 …

[Mdp] lc198. 打家劫舍(记忆化搜索+dp)

文章目录 1. 题目来源2. 题目解析 1. 题目来源 链接&#xff1a;198. 打家劫舍 前置&#xff1a; [每日一题] 146. 打家劫舍(数组、动态规划、巧妙解法) 2. 题目解析 记忆化搜索可以处理&#xff0c;是自顶向下进行枚举的&#xff0c;属于 递归。 动态规划&#xff0c;属于…

Nuxt3服务端渲染项目简单搭建

目录 1.准备阶段 2.创建项目 3.安装需要的模块 1&#xff09;安装ArcoDesign/ElementPlus 2&#xff09;安装tailwindcss 4.目录结构 5.网站TDK设置 本篇文章相关的官方网站&#xff1a; 1.nuxt3&#xff1a;Nuxt 中文站 - 直观的Web框架 Nuxt3文档 Nuxt 2.vue3&#xf…

fpga系列 HDL:Vivado 安装usb cable驱动

安装usb cable驱动 安装cable_drivers:在data\xicom\cable_drivers\nt64\dlc10_win7目录 安装digilent

数据分析-12-多个时间序列数据的时间戳对齐以及不同的方式补点

参考python时间序列数据的对齐和数据库的分批查询 1 问题场景与分析 1.1 场景 在医院的ICU里,须要持续观察病人的各项生命指标。这些指标的采集频率每每是不一样的(例如有些指标隔几秒采集一个,有些几个小时采集一个,有些一天采集一个),并且有些是按期的,有些是不按期的…

SpringMVC基于注解使用:上传下载

01-文件下载 基于servlet api的文件下载 注意一点content-disposition是以文件下载的方式打开意思是客户端地址栏不会改变&#xff0c; 如果注销了那句话就会跳转到下载图片的图片里面去&#xff0c;就在网页中显示了 基于spring ResponseEntity的文件下载 不支持缓冲区 一次…