woff字体反反爬
2021-12-31 本文已影响0人
LCSan
背景某港务公司货箱查询,返回货箱信息是这样子的:
货箱号
![](https://img.haomeiwen.com/i9380086/943fd61e4993b645.png)
分析上下文报文,返现同时有一个woff字体请求,显然这里做了字体映射。这里问题变成了如何解析woff成文字。
找了一下woff格式字体资料,通俗的讲有字体id,字体Unicode码,字模信息。三者互有映射关系,其中的Unicode就是字,字模信息会经过渲染,转换成人眼看到的字。
重点就在于字模,理论上来讲一套字体文件,字模是固定的。不管Unicode怎么变,最终映射的字模应该是一样的,除非显示出来的一个字有多套字模(这么做开发成本跟时间成本上肯定得作出让步,因此这里暂不考虑)。即使一个字有多套字模,那这也是可以枚举出来的。
基于这点,只需要将一套完整的字模跟字的映射表做好,只要字模不变,就可以通过Unicode映射字模,再通过字模映射字符,得到最终结果。
- 先下载一个woff字体,解析导出字模跟Unicode信息。(注意:这里从cmap里面取Unicode,踢出了非字体的码。)
def CreateFontDict(woffPath, jPath):
font = TTFont(woffPath)
res = {}
for k in font.getBestCmap().values():
res[hashlib.md5(str(font["glyf"][k].coordinates).encode('utf-8')).hexdigest()] = k
with open(jPath, 'w', encoding='utf-8') as f:
f.write(json.dumps(res))
导出json格式文件后,人工将Unicode码替换成对应字符。
这个网站,现在查看字库映射,用来改json里面的value为映射字符:
http://blog.luckly-mjw.cn/tool-show/iconfont-preview/index.html
- 加载人工处理后的json映射文件
def loadFontDict(jPath):
global jMap
with open(jPath, encoding='utf-8') as f:
json_str = f.read()
jMap = json.loads(json_str)
- 加载字模文件,对文本进行解析。(注意:要将原始字符串里面要替换的字符串格式,转为json输出时value的格式)
def parseFont(woffPath, text, coding="utf-8"):
global jMap
font = TTFont(woffPath)
for k in font.getBestCmap().values():
if k.startswith("uni"):
r = jMap[hashlib.md5(str(font["glyf"][k].coordinates).encode('utf-8')).hexdigest()]
t = k.replace("uni", r"\u").lower()
t = t.encode(coding).decode("unicode_escape")
text = text.replace(t, r)
return text
完整代码:
from fontTools.ttLib import TTFont
import hashlib
import json
import re
jMap = None
def CreateFontDict(woffPath, jPath):
font = TTFont(woffPath)
res = {}
for k in font.getBestCmap().values():
res[hashlib.md5(str(font["glyf"][k].coordinates).encode(
'utf-8')).hexdigest()] = k
with open(jPath, 'w', encoding='utf-8') as f:
f.write(json.dumps(res))
def parseFont(woffPath, text, coding="utf-8"):
global jMap
text = _handleTxt(text)
font = TTFont(woffPath)
for k in font.getBestCmap().values():
if k.startswith("uni"):
r = jMap[hashlib.md5(str(font["glyf"][k].coordinates).encode('utf-8')).hexdigest()]
t = k.replace("uni", r"\u").lower()
t = t.encode(coding).decode("unicode_escape")
text = text.replace(t, r)
return text
def loadFontDict(jPath):
global jMap
with open(jPath, encoding='utf-8') as f:
json_str = f.read()
jMap = json.loads(json_str)
def _replace(reg, exp, come):
# re.MULTILINE | re.DOTALL
return re.sub(re.compile(reg, 8 | 16), (exp if exp.find('lambda') == -1 else eval(exp, globals(), locals())), come)
def _handleTxt(text):
text = _replace("&#x([0-9a-f]{4});", "lambda x: '\\\\u'+x.group(1)", text)
text = text.replace('"', r'\"')
text = '"' + text + '"'
text = json.loads(text, strict=False)
return text
if __name__ == "__main__":
# CreateFontDict(r"C:\Users\44413\Documents\UiBot\creator\Projects\抖音商家数据采集\res\aa.ttf",
# r"C:\Users\44413\Documents\UiBot\creator\Projects\抖音商家数据采集\res\data1.json")
# loadFontDict(r"C:\Users\44413\Documents\UiBot\creator\Projects\抖音商家数据采集\res\data.json")
# with open(r"C:\Users\44413\Desktop\新文件 2.txt", encoding='utf-8') as f:
# json_str = f.read()
# a = parseFont(
# r"C:\Users\44413\Downloads\c54f43be76094bd1ae121adfb18f3bcf-ebe761e0c99f4b289029c3634f3516e3.ttf", json_str)
# print(a)
# a = '''<div class="infocardBasic fl">
# <div class="clearfix">
# <em class="stonefont"></em>
# <span>|</span>
# <em class="stonefont">岁</em>
# <em class="stonefont">年</em>
# <em class="stonefont"></em>
# <span class="phoNum">1图</span>
# </div>
# </div>'''
a = '''<div class="clearfix"><em class="stonefont"></em><span>|</span><em class="stonefont">岁</em><span>|</span><!-- TODO --><em class="stonefont">-年</em><span>|</span><!-- TODO --><em class="stonefont">/</em></div>'''
loadFontDict(r"C:\Users\44413\Documents\UiBot\creator\Projects\抖音商家数据采集\res\data.json")
a = parseFont(r"C:\Users\44413\Documents\UiBot\creator\Projects\抖音商家数据采集\res\aa.woff",a)
print(a)
记录TTFont几个有用方法
# 取字模
font['glyf']['uniF378'].coordinates
# 十进制,unicode映射
font.getBestCmap()
# id,unicode映射
font.getGlyphOrder()