字体反爬本质是未映射unicode码点,需通过network面板定位字体文件,用glyph轮廓哈希比对实现稳定映射,而非依赖易变的码点或ocr。

字体反爬不是“解码失败”,而是你拿到的文本根本没经过字体映射——浏览器渲染时用 @font-face 把 U+E801 映射成“5”,而你的 requests 只拿到原始 U+E801,不加载字体就永远显示为空白或方块。
怎么定位真实字体文件地址?别只盯 HTML 源码
很多网站把 @font-face 写在 JS 里、动态拼接 URL,甚至用 base64 内联。直接 requests.get() 抓 HTML 几乎拿不到有效链接。
- 打开浏览器开发者工具 → Network → 切到 Font 标签页 → 刷新页面,找状态码为
200的.woff或.woff2请求 - 右键该请求 → Copy → Copy link address,注意检查
Referer头是否被校验(常见于 autohome、大众点评等站) - 如果字体是 base64 内联的(
src: url("data:application/octet-stream;base64,xxx")),直接提取双引号内 base64 字符串,用base64.b64decode()解码为 bytes 流 - 相对路径必须和页面
<base href="...">或当前 URL 拼接,否则requests.get()会 404
用 fontTools 解析 woff/woff2 时为什么总报错?
TTFont 不是万能加载器:它能读大部分 .woff,但对 .woff2 常抛出 Unsupported sfnt version。这不是你代码写错了,是格式不兼容。
- 先试
TTFont("x.woff");若报错,说明可能是woff2,需先用woff2otf或fonttools的woff2模块解包:pip install woff2otf,再转成 ttf/woff - 别依赖
ttf['glyf']提取轮廓——它存的是矢量路径,解析开是上千个pt坐标,效率低且易受字形微调干扰 - 真正该看的是
ttf['cmap'].getBestCmap():它返回{unicode_codepoint: glyph_name},比如{0xe801: 'uniE801'} - 若返回空 dict,说明该字体用了非标准平台 ID,强制指定:
ttf['cmap'].tables[0].cmap或遍历tables找platformID == 3 and encodingID == 1
为什么静态映射字典第二天就失效?
网站每次生成新字体时,常做两件事:一是把相同字形分配给不同 Unicode 码点(如“3”这次是 U+E902,下次变 U+E71A);二是打乱字形顺序,让 getGlyphOrder() 返回列表不稳定。靠码点直连必然翻车。
- 可靠做法是提取字形轮廓哈希(glyph outline hash):用
ttf['glyf'][glyph_name]获取字形对象,序列化其coordinates和flags后算 md5 - 准备一份“基准字体”(比如从历史缓存里挑一个已知映射正确的 woff),提取所有数字/汉字的 glyph hash → 文字字典,如
{'a1b2c3...': '5', 'd4e5f6...': '8'} - 每次新字体来,对每个 glyph 计算同样 hash,查基准字典得文字 —— 这种方式抗重排、抗码点漂移
- 注意:部分字体对同一字符生成多个 glyph(ligature 或 variation),要统一取
ttf.getBestCmap().values()中出现过的 glyph name
绕过字体反爬最省事的方案是什么?
不是硬刚字体解析,而是换数据源。很多站点 PC 端用字体反爬,但 H5 或 APP 接口返回明文 JSON —— 因为移动端更重性能,不敢加渲染层开销。
- 用手机 UA 发起请求:
headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) ...'},常能绕过字体逻辑,直接拿到数字 - 抓包对比:在移动端浏览器(或 Chrome DevTools → Toggle device toolbar)访问同页面,看 Network 里 xhr/fetch 请求返回的是否为结构化数据
- 若接口带 sign 或 timestamp,优先分析 JS 中加密逻辑(比逆向字体简单得多),而不是死磕 woff
- 慎用 Selenium 截图 OCR:对少量字段可行,但遇到密集数字(如价格列表)、模糊字体或加噪点时,准确率断崖下跌,维护成本远高于 glyph hash 方案
字体反爬真正的复杂点不在解析,而在“映射关系是否稳定”。一旦你发现同一个页面两次请求返回的 woff 文件 md5 不同,或者 getGlyphOrder() 顺序乱序,就该放弃码点直连,立刻转向 glyph 轮廓哈希比对——这是目前实战中失效周期最长、适配性最强的路子。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











