1、编码、矢量图(字形)、字符
编码原理、字体、编辑字体的工具
编码 原理
- bit (比特):是由 0 或 1 构成的二进制位
- Byte (字节):1个字节由八个连续的二进制位,或二个16进制数表示
- 字符:是指计算机中使用的字母、数字、字和符号
- ASCII 编码:使用指定的7位或8位二进制数组合来表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。
- Unicode 编码:为世界上所有字符都分配了一个唯一的数字编号,这个编号范围从 0x000000 到 0x10FFFF(十六进制),有110多万,每个字符都有一个唯一的Unicode编号,这个编号一般写成16进制,在前面加上U+。例如:”爬“的Unicode是U+722C。它是一种规定,Unicode本身只规定了每个字符的数字编号是多少,并没有规定这个编号如何存储。
理论上可以直接把Unicode编号直接转换成二进制进行存储,而Unicode并不是这么操作,因为除了这种直接转换成二进制的方案外,还有其他方案,主要有UTF-8,UTF-16,UTF-32,gbk。(UTF-8、UTF-16、UTF-32……都是 Unicode编码 的一种实现。) - UTF-8 编码:对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
汉字的UTF-8编码(n字节)表示:
获取汉字unicode编号
转化为二进制数
根据UTF-8编码格式,将二进制数据填充到指定位置
将填充好的新二进制数据,转换成16进制1、获取汉字unicode编号 ord('爬') =》29228 hex(29228) =》'0x722c' 2、转化为二进制数 bin(29228) =》'0b111001000101100' 3、根据UTF-8编码格式,将二进制数据填充到指定位置 (29228>2048所以选3的: 1110xxxx 10xxxxxx 10xxxxxx) 11100111 10001000 10101100 4、将填充好的新二进制数据,转换成16进制 hex(int('11100111',2)) =》'0xe7' hex(int('10001000',2)) =》'0x88' hex(int('10101100',2)) =》'0xac' "爬".encode('utf-8') =》b'\xe7\x88\xac' #字节编码
不同的编码方式,字符的编码都不一样,字符的编码就是指字符有一个唯一的编号,通过这个编号可以找打这个唯一的字符。
unicode 编码目前可以包含世界上所有语言的所有字符。一个字的 Unicode 编号是固定的,但是在计算机上存储时的字节码,取决于编码方案的实现方式。不同的编码方式,字符的字节码不一样。
矢量图 ( 字形 glyphs )
矢量图 也就是 字符的 字形(glyphs)。
随便点一个 字符,这里点击 万 字,可以看到是由一个个坐标点连接而成的图形,这就是 万 字的矢量图,也就是 万 字的 字形图
矢量图(字形) 对应的 字符(字体)
字符(字体) 可以理解为通过 unicode 编号找到对应的 自定义图形(字形)。
假设这里要传递 字符 5 ,有两种方案,
- 方案 1:传递字符5的字节码:浏览器拿到字节码,转换成 unicode 编号,在没有指定字体文件的前提下,通过unicode编号在到系统自带字体中寻找字符矢量图,得到 ‘5’
- 方案 2:传递另一个繁体字的字节码,同时,传递一个自定义的字体。浏览器拿到繁体字的字节码,转换成 unicode 编码,到 css 指定的字体文件中查找,找到字符矢量图 ‘5’。这个就是字体反爬原理
正常对应关系:unicode编号 -->正常字符集 -->正常字符
使用自定义矢量图:①unicode编号 -->正常字符集 -->难懂的字
②nunicode编号 -->自定义字符集 -->正常字符
字体 文件
字体文件存储的是unicode编号和字符矢量图的对应关系。字体文件扩展名一般都是 woff ,人类无法直接阅读,可以把字体文件转成 xlm 文件,就可以看到一个字体文件由数个表(table)构成,字体的信息储存在表中,表中存储有字形的坐标点数据。
一个最基本的字体文件一定会包含以下的表:
- cmap: Character to glyph mapping
- head: Font header
- hhea: Horizontal header
- hmtx: Horizontal metrics
- maxp: Maximum profile
- name: Naming table
- OS/2: OS/2 and Windows specific metrics
- post: PostScript information:
使用TrueType曲线绘制的字体会包含如下的表:
- cvt: Control Value Table
- fpgm: Font program
- glyf: Glyph data
- loca: Index to location
- prep: CVT Program
- gasp: Grid-fitting/Scan-conversion (optional table)
使用PostScript曲线绘制的字体会包含如下的表:
- CFF: PostScript font program (compact font format)
- VORG: Vertical Origin (optional table)
使用SVG曲线绘制的字体会包含如下的表:
- SVG: The SVG (Scalable Vector Graphics) table
使用Bitmap图形构成的字体会包含如下的表:
- EBDT: Embedded bitmap data
- EBLC: Embedded bitmap location data
- EBSC: Embedded bitmap scaling data
- CBDT: Color bitmap data
- CBLC: Color bitmap location data
包含高级书法特性的字体会包含如下的表:
- BASE: Baseline data
- GDEF: Glyph definition data
- GPOS: Glyph positioning data
- GSUB: Glyph substitution data
- JSTF: Justification data
- MATH: Math layout data
包含其他特性的字体会包含如下的表:
- DSIG: Digital signature
- hdmx: Horizontal device metrics
- kern: Kerning
- LTSH: Linear threshold data
- PCLT: PCL 5 data
- VDMX: Vertical device metrics
- vhea: Vertical Metrics header
- vmtx: Vertical Metrics
- COLR: Color table
- CPAL: Color palette table
对字体的修改基本上是围绕着上方的表进行的。
字体编辑工具 FontCreator
FontCreator(专业的字体编辑软件) v15.0.0.2974 绿色版:https://www.zimupu.com/3780
Fontcreator V 15汉化版:https://www.52pojie.cn/thread-1839024-1-1.html
FontCreator 中文使用手册:https://www.maoken.com/docs/fontcreator14中文使用手册/开始/开始使用fontcreator
在线字体编辑:https://tophix.com/zh-cn/font-tools/font-editor
Python 解析字体
要解析字体文件并获取字符的Unicode映射关系,可以使用fontTools
这个Python库来完成,这是一个开源、跨平台的字体处理库,能够读取多种字体格式,包括TrueType (.ttf)、OpenType (.otf)、WOFF、WOFF2等,并可以访问字体文件中的数据表来获取 字形 (glyphs) 与Unicode码 的映射关系。
fontTools 介绍:https://pypi.org/project/fonttools/
文档、教程:https://fonttools.readthedocs.io/en/latest/index.html
github:https://github.com/fonttools/fonttools
fontTools 使用:https://blog.csdn.net/weixin_43411585/article/details/103484643
FontTools 是一套以 ttx 为核心的工具集,用于处理和编辑字体,由以下4个程序组成:
- TTX:ttx是FontTools的核心工具,用于将字体转换为xml文件,或者将xml文件转换回字体。示例:ttx yourfont.woff2 命令会在同一目录下生成一个与WOFF2文件同名的XML文件,但扩展名为.ttx。这里字体文件名为yourfont.woff2,则生成的XML文件将命名为yourfont.ttx。
这个XML文件包含字体的所有信息,你可以用任何文本编辑器打开它以查看其内容。 - pyftmerge:可将数个字体文件合并成为一个字体文件
- pyftsubset:可产生一个由字体的指定字符组成的子集
- pyftinspect:可显示字体文件的二进制组成信息
FontTools 的核心组件包括:
- PyFontTools: 提供了丰富的类和函数,用于直接操作字形、字典、表结构等,方便进行字体分析和编辑。
- ufo2ft: 支持将Unicode Font Object (UFO) 格式的字体转换成OpenType字体,并且可以使用滤镜扩展自定义字形生成过程。
- varLib: 用于处理OpenType变体字体(Variable Fonts),可以构建、提取或合并字体的变体轴。
Brotli 是一种通用无损压缩算法:https://github.com/google/brotli
前端性能优化之文件压缩gzip zopfli及brotli:https://juejin.cn/post/6966507461872189454
安装:pip install brotli
安装:pip install fonttools
2、字体反爬
网站的反爬措施有很多,例如:js反爬、ip反爬、css字体加密反爬、验证码反爬、滑动点击类验证反爬等等。世界上没有一个网站,能做到完美地反爬虫。如果页面希望能在用户面前正常展示,同时又不给爬虫机会,就必须要做到识别真人与机器人。因此工程师们做了各种尝试,这些策略大多采用于后端,也是目前比较常规单有效的手段,比如:
- User-Agent + Referer检测
- 账号及Cookie验证
- 验证码
- IP限制频次
而爬虫是可以无限逼近于真人的,比如:
- chrome headless或phantomjs来模拟浏览器环境
- tesseract识别验证码
- 代理IP淘宝就能买到
所以说100%的反爬虫策略?不存在的。更多的是体力活,是个难易程度的问题。不过作为前端工程师,可以增加一下游戏难度,设计出一些很(sang)有(xin)意(bing)思(kuang)的反爬虫策略。
前端与反爬虫
反击爬虫,前端工程师的脑洞可以有多大?
font-face 拼凑式。
示例:https://www.maoyan.com/films/78341
猫眼电影里,对于票房数据,展示的并不是纯粹的数字。页面使用了font-face定义了字符集,并通过unicode去映射展示。也就是说,除去图像识别,必须同时爬取字符集,才能识别出数字。并且,每次刷新页面,字符集的url都是有变化的,无疑更大难度地增加了爬取成本。
background 拼凑式
示例:美团
与font的策略类似,美团里用到的是background拼凑。数字其实是图片,根据不同的background偏移,显示出不同的字符。
并且不同页面,图片的字符排序也是有区别的。不过理论上只需生成0-9与小数点,为何有重复字符就不是很懂。
字符穿插式
示例:微信公众号文章
某些微信公众号的文章里,穿插了各种迷之字符,并且通过样式把这些字符隐藏掉。这种方式虽然令人震惊…但其实没有太大的识别与过滤难度,甚至可以做得更好,不过也算是一种脑洞吧。
伪元素隐藏式
示例:汽车之家
汽车之家里,把关键的厂商信息,做到了伪元素的content里。这也是一种思路:爬取网页,必须得解析css,需要拿到伪元素的content,这就提升了爬虫的难度。
元素定位覆盖式
示例:去哪儿
还有热爱数学的去哪儿,对于一个4位数字的机票价格,先用四个
i
标签渲染,再用两个b
标签去绝对定位偏移量,覆盖故意展示错误的i
标签,最后在视觉上形成正确的价格…这说明爬虫会解析css还不行,还得会做数学题。
iframe 异步加载式
例子:网易云音乐
网易云音乐页面一打开,html源码里几乎只有一个iframe,并且它的src是空白的:about:blank。接着js开始运行,把整个页面的框架异步塞到了iframe里面…
不过这个方式带来的难度并不大,只是在异步与iframe处理上绕了个弯(或者有其他原因,不完全是基于反爬虫考虑),无论你是用selenium还是phantom,都有API可以拿到iframe里面的content信息。
字符分割式
例子:全网代理IP
在一些展示代理IP信息的页面,对于IP的保护也是大费周折。他们会先把IP的数字与符号分割成dom节点,再在中间插入迷惑人的数字,如果爬虫不知道这个策略,还会以为自己成功拿到了数值;不过如果爬虫注意到,就很好解决了。
字符集替换式
例子:去哪儿移动端
同样会欺骗爬虫的还有去哪儿的移动版。html里明明写的3211,视觉上展示的却是1233。原来他们重新定义了字符集,3与1的顺序刚好调换得来的结果…
什么是 字体反爬
一些网站,如果网页浏览显示正常,但是用python爬取下来是乱码,或者 F12 开发者模式查看网页源代码也是乱码。这种一般是网站设置了字体反爬
字体反爬 又叫 "css 字体加密",是一种常见的反爬技术,网页开发者自己创造一种字体,因为在 字体(这里是字体文件) 中每个字符都有其 代号(字符的unicode编码),那么以后在网页中不会直接显示这个字符的最终的效果,而是显示他的代号,因此即使获取到了网页中的文本内容,也只是获取到文字的代号,而不是文字本身,从而达到防止爬虫爬取信息的目的。最早使用字体反爬技术的有58同城、汽车之家等等,现在很多网站或APP也使用字体反爬来增加一种反爬措施。
总结:字体反爬就是自定义字体反爬,通过调用自定义的ttf文件来渲染网页中的文字,而网页中的文字不再是文字,而是相应的字体编码,通过复制或者简单的采集是无法采集到编码后的文字内容!
字体反爬 原理
通过自定义字体来替换页面中某些数据。使用自定义字体文件是CSS3特性,可参考CSS3字体。
-
自定义字体文件:网站使用自定义的字体文件(如
.ttf
、.woff
等格式),这些字体文件将常规字符映射到随机或不常用的符号上。例如,字符'A'
可能被映射到看起来像'B'
的符号上,而字符'1'
可能显示为看起来像'9'
的符号。 -
字符映射混淆:通过这种方式,即使爬虫能够成功地从网页中提取到文本信息,也只会得到一串经过混淆的、毫无意义的字符序列。因为在没有访问到实际映射的字体文件的情况下,无法知道每个符号代表的真实字符。
-
映射表的动态变化:为了进一步增强防爬虫效果,服务器可能会定期更换字体文件和对应的字符映射关系,使得即使爬虫获取到了字体文件,也只能短暂时间内正确解析文本内容。
实现方式
-
替换网页文本的显示:网站通过CSS引入自定义字体,并确保网页渲染时文本按照这些自定义字体展示。用户在浏览器中看到的是正确的文本内容,因为浏览器能够解析自定义字体和字符映射。
-
随机化映射关系:动态生成字体文件和字符映射表,确保每次访问时映射关系都不同,或者对不同的用户展示不同的映射关系。
应对策略
- 手动识别:通过分析字体文件和映射关系,手动构建字符到实际文本的映射表,这需要一些字体处理方面的知识。
- OCR技术:使用光学字符识别技术,将文本重现为图像,然后通过OCR技术解析图像中的文本。这种方法不直接依赖于字符与符号之间的映射关系,但可能存在一定的误差。
手动实现 字体反爬
- 自定义web-font实现数据防采集:https://blog.csdn.net/fdipzone/article/details/68166388
- 前端使用字体文件(.ttf)来阻止爬虫:https://blog.csdn.net/qq_37540004/article/details/78864713
web-font 是CSS3 中的一种标记 @font-face,在@font-face声明里,你可以声明一种字体,指定这种字体字体库文件从网络某个地址下载。在HTML中通过 @font-face 来使用自定义字体。
具体写法如下:
@font-face {
font-family: '字体名称';
src: url('http://www.example.com/字体名称.eot'); /* IE9 Compat Modes */
src: url('http://www.example.com/字体名称.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('http://www.example.com/字体名称.ttf') format('truetype'), /* Safari, Android, iOS */
url('http://www.example.com/字体名称.woff') format('woff'), /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('http://www.example.com/字体名称.svg?#字体名称') format('svg'); /* Legacy iOS */
}
当网页数据需要使用特别的字体来修饰时,我们就可以使用web-font。因为使用web-font会自动从网络中加载字体,并不需要用户本机有安装这个字体。
防采集原理:
使用 web-font 可以从网络加载字体,因此就可以自己创建一套字体,设置自定义的字符映射关系表。例如设置 0xaaa 是映射字符1,0xbbb是映射字符2,以此类推。当需要显示字符1时,网页的源码只会是0xaaa,被采集的也只会是0xaaa,并不是1,使采集者采集不到正确的数据。而对于正常访问的用户则没有影响。
3、字体反爬通杀:ocr (图像识别)
解决流程:
- 获取字体文件 woff 路径,并下载。
- 读取字体文件中字符的 "矢量图(字形)" 即坐标,然后画 png 图,首次生成的png图需要将背景色改为白色,方便识别
- 调用 OCR 识别字体
- 人工检核,打开字体图片,然后与解析结果对比,并更改识别错误的结果。
OCR 工具
ddddocr 图像识别:pip install ddddocr
百度 OCR 识别 (普通接口每天有50000次免费,高精度的有500次免费,但不支持并发):https://ai.baidu.com/tech/ocr_others/webimage
Umi-OCR
命令行:https://github.com/hiroi-sora/Umi-OCR/blob/main/docs/README_CLI.md
HTTP接口:https://github.com/hiroi-sora/Umi-OCR/blob/main/docs/README_HTTP.mdcnocr 图像识别:pip install cnocr
使用Python批量转换SVG文件为PNG或PDF文件:pip install nocairosvg
import os
import requests
import numpy as np
from cnocr import CnOcr
from PIL import ImageFont, Image, ImageDraw
from fontTools.ttLib import TTFont
from io import BytesIO
def font_to_img(code_list, filename, ignore_flag=True, score=0.95):
normal_dict = {}
be_padding_dict = {}
ocr = CnOcr()
"""
将字体画成图片
code_list: 加密字符列表
filename: 字体文件
ignore_flag:是否忽视 sorce 返回结果
score: 识别准确率默认 95%以上
"""
for char_list in code_list:
char_code = char_list.encode().decode()
img_size = 1024
img = Image.new('1', (img_size, img_size), 255)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(filename, int(img_size * 0.7))
x, y = draw.textsize(char_code, font=font)
draw.text(((img_size - x) // 2, (img_size - y) // 2), char_code, font=font, fill=0)
# 将单通道 转为 三通道
img = img.convert("RGB")
# word = ocr.ocr_for_single_line("%s.jpg" % mame_ocr)
word = ocr.ocr_for_single_line(np.array(img))
if word["score"] >= score:
# 处理重复名字
# img.save("%s_%s.jpg" % (char_code, word["text"]))
normal_dict[char_code] = word["text"]
else:
be_padding_dict[char_code] = word
img.save("%s_%s_be_padding.jpg" % (char_code, word["text"]))
if ignore_flag:
normal_dict[char_code] = word["text"]
return normal_dict, be_padding_dict
def ttf_parse(url, ttf_name):
"""
根据url 获取返回值
"""
response = requests.get(url)
font_parse = TTFont(BytesIO(response.content))
font_parse.save(ttf_name)
m_dict = font_parse.getBestCmap()
unicode_list = []
for key, _ in m_dict.items():
unicode_list.append(key)
# 获取需要判断的字符
char_list = [chr(ch_unicode) for ch_unicode in unicode_list]
normal_dict, error_dict = font_to_img(char_list, ttf_name)
print(normal_dict)
print(error_dict)
# 删除字体文件
os.remove(ttf_name)
字体格式转换器:https://www.fontke.com/tool/convfont/
unicode 的三种表现形式:&#、&#x、\u
&#、&#x、\u 都可以用来表示一串 unicode 编码。
4、字体反爬 案例
猫眼
示例 网页:https://www.maoyan.com/board/1
打开页面可以正常看到 数字。但是通过页面源码发现是乱码。
<span class="stonefont">.</span> 使用了自定义的stonefont字体,在网页中查找stonefont,很快有了发现,这就是标准的 @font-face 定义方法。
且每次访问,字体文件访问地址都会随机变化。
直接点击 woff 文件的 url 地址可以将woff字体文件下载到本地。然后使用 FontCeator ( Fontcreator V 15汉化版:https://www.52pojie.cn/thread-1839024-1-1.html ) 打开字体文件,可以看到对应关系如下:
把字体文件写到本地文件中
import re
import requests
from fontTools.ttLib import TTFont
def download_html(url=None):
headers = {
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
}
response = requests.request("GET", url, headers=headers)
return response
def save_font_xml(font_file_path=None):
font = TTFont(font_file_path)
font.saveXML(f'{font_file_path}.xml')
def main():
url = 'https://maoyan.com/board/1'
response = download_html(url)
resp_text = response.text
if '猫眼验证中心' in resp_text:
print("被检测出来, 刷新页面手动验证后, 然后再次运行")
return
font_list = re.findall(r'\,url\("(.*?\.woff)', resp_text)
font_url = f'https:{font_list[0]}'
font_file_name = font_url.split('/')[-1]
print(font_url)
response = download_html(font_url)
font_content = response.content
with open(font_file_name, 'wb') as f:
f.write(font_content)
save_font_xml(f'./{font_file_name}')
if __name__ == '__main__':
main()
pass
使用 fontTools 来读取字体文件并保存为 xml 格式文件。打开 xml 文件,可以看到每一个编码都对应一个 TTGlyph 对象,从各种 x y 坐标可以猜测它应该是用来绘制一个字的。
现在复制任意一个对象,然后用 matplotlib 根据坐标画个图试试看。
import re
import matplotlib.pyplot as plt
point_list = """
<TTGlyph name="uniE83F" xMin="0" yMin="-24" xMax="727" yMax="1000">
<contour>
<pt x="258" y="536" on="1"/>
<pt x="106" y="592" on="0"/>
<pt x="106" y="741" on="1"/>
<pt x="106" y="853" on="0"/>
<pt x="263" y="1000" on="0"/>
<pt x="526" y="1000" on="0"/>
<pt x="686" y="844" on="0"/>
<pt x="686" y="738" on="1"/>
<pt x="686" y="592" on="0"/>
<pt x="538" y="536" on="1"/>
<pt x="630" y="505" on="0"/>
<pt x="727" y="372" on="0"/>
<pt x="727" y="280" on="1"/>
<pt x="727" y="150" on="0"/>
<pt x="545" y="-24" on="0"/>
<pt x="248" y="-24" on="0"/>
<pt x="157" y="63" on="1"/>
<pt x="67" y="147" on="0"/>
<pt x="67" y="283" on="1"/>
<pt x="67" y="381" on="0"/>
<pt x="165" y="512" on="0"/>
</contour>
<contour>
<pt x="232" y="745" on="1"/>
<pt x="232" y="672" on="0"/>
<pt x="325" y="584" on="0"/>
<pt x="398" y="584" on="1"/>
<pt x="465" y="584" on="0"/>
<pt x="514" y="629" on="1"/>
<pt x="560" y="672" on="0"/>
<pt x="560" y="804" on="0"/>
<pt x="466" y="897" on="0"/>
<pt x="396" y="897" on="1"/>
<pt x="323" y="897" on="0"/>
<pt x="232" y="808" on="0"/>
</contour>
<contour>
<pt x="193" y="283" on="1"/>
<pt x="193" y="228" on="0"/>
<pt x="241" y="132" on="0"/>
<pt x="293" y="104" on="1"/>
<pt x="340" y="77" on="0"/>
<pt x="398" y="77" on="1"/>
<pt x="442" y="77" on="0"/>
<pt x="514" y="106" on="0"/>
<pt x="543" y="134" on="1"/>
<pt x="601" y="189" on="0"/>
<pt x="601" y="368" on="0"/>
<pt x="483" y="483" on="0"/>
<pt x="393" y="483" on="1"/>
<pt x="307" y="483" on="0"/>
<pt x="193" y="370" on="0"/>
</contour>
<instructions/>
</TTGlyph>
"""
x = [int(i) for i in re.findall(r'<pt x="(.*?)"', point_list)]
y = [int(i) for i in re.findall(r'y="(.*?)"', point_list)]
print(x)
print(y)
plt.plot(x, y)
plt.show()
执行结果
可以看到 uniE83F 对应的就是数字 8,那么其它的编码也是这个道理。现在可以使用识别图片验证码的方式来识别数字,这样做效率虽然不高,但是确实字体反爬的通杀方法
如果加载字体文件不变,则可以打开 xml 获取 unicode 编码,然后通过绘制图的方式或者通过 fontCreator软件打开字体文件,获取每个unicode代表的数字,最后把得到映射关系保存到字典中
这样爬虫在获取到数据后,通过 unicode 到字典中获取到正确的数字即可。
原来:unicode编码 <-----> 字体形状编码(字形编码固定不变) <-----> 对应字符编码
现在:unicode编码 <-----> 字体形状编码(字形编码变化) <-----> 对应字符编码
现在猫眼改版,每次请求得到的字体文件可能相同,有可能不同。
解决方法:每次请求时,把字体文件保存下来,把字体文件和 映射的字典做关联,如果请求的字体文件已经做过关联,则直接用关联的映射字典,如果字体文件没有关联,则新建字体文件和映射字典的关联即可。
下面是使用fontTools.ttLib获取的单个字符的字形数据。
<TTGlyph name="uniE183" xMin="0" yMin="-12" xMax="516" yMax="706">
<contour>
<pt x="134" y="195" on="1"/>
<pt x="144" y="126" on="0"/>
<pt x="217" y="60" on="0"/>
<pt x="271" y="60" on="1"/>
<pt x="335" y="60" on="0"/>
<pt x="423" y="158" on="0"/>
<pt x="423" y="311" on="0"/>
<pt x="337" y="397" on="0"/>
<pt x="270" y="397" on="1"/>
<pt x="227" y="397" on="0"/>
<pt x="160" y="359" on="0"/>
<pt x="140" y="328" on="1"/>
<pt x="57" y="338" on="1"/>
<pt x="126" y="706" on="1"/>
<pt x="482" y="706" on="1"/>
<pt x="482" y="622" on="1"/>
<pt x="197" y="622" on="1"/>
<pt x="158" y="430" on="1"/>
<pt x="190" y="452" on="0"/>
<pt x="258" y="475" on="0"/>
<pt x="293" y="475" on="1"/>
<pt x="387" y="475" on="0"/>
<pt x="516" y="346" on="0"/>
<pt x="516" y="243" on="1"/>
<pt x="516" y="147" on="0"/>
<pt x="459" y="75" on="1"/>
<pt x="390" y="-12" on="0"/>
<pt x="271" y="-12" on="1"/>
<pt x="173" y="-12" on="0"/>
<pt x="112" y="42" on="1"/>
<pt x="50" y="98" on="0"/>
<pt x="42" y="188" on="1"/>
</contour>
<instructions/>
</TTGlyph>
使用下面语句可以获取顺序的字符编码值,
# 解析字体库font文件
# 用一个base文件提前解析出文件的编码规律
##############################################################################
# 访问字体的 url ,下载 字体文件 并 保存,这里保存文件名为 base.woff
base_font = TTFont('base.woff') # 解析字体库font文件
# 使用 "FontCreator字体查看软件" 查看字体的对应关系,然后设置对应关系
base_num_list = ['.', '3', '5', '1', '2', '7', '0', '6', '9', '8', '4']
base_unicode_list = [
'x', 'uniE64B', 'uniE183', 'uniED06', 'uniE1AC', 'uniEA2D',
'uniEBF8', 'uniE831', 'uniF654', 'uniF25B', 'uniE3EB'
]
"""
1. 字库对应的字形顺序不变,映射的 unicode 编码改变。
只需要找一次对应关系即可。
2. 字库对应的字形顺序改变,映射的 unicode 编码也改变。
需要找两次对应关系:
第一次可以当基准对应关系,找到 字形 和 unicode 的对应关系
第二次时,因为字形的数据都相同,可以找到字形的数据和第一次做基准的做对比,
因为字形数据相同,可以找到第一次对应的字形所对应的第二次的 unicode 对应关系
"""
##############################################################################
# 猫眼 属于 字形 顺序改变,unicode 编码也改变
mao_yan_font = TTFont('maoyan.woff')
mao_yan_unicode_list = mao_yan_font['cmap'].tables[0].ttFont.getGlyphOrder()
mao_yan_num_list = []
for i in range(1, 12):
mao_yan_glyph = mao_yan_font['glyf'][mao_yan_unicode_list[i]]
for j in range(11):
base_glyph = base_font['glyf'][base_unicode_list[j]]
if mao_yan_glyph == base_glyph:
mao_yan_num_list.append(base_num_list[j])
break
pass
天眼查
案例:https://blog.csdn.net/cf313995/article/details/82987298
汽车之家
案例:https://blog.csdn.net/jia666666/article/details/108974149
起点中文网
:https://blog.csdn.net/llllllkkkkkooooo/article/details/108430930
:https://blog.csdn.net/xiaoduantongxue/article/details/117754751
大众点评
:https://cloud.tencent.com/developer/article/2014152
:https://juejin.cn/post/6970933428145356831
实习僧 招聘网站
:https://blog.csdn.net/pdcfighting/article/details/121550898
优志愿
:https://www.youzy.cn/tzy/search/colleges/collegeList