字体反爬本质是字符映射混淆,核心解法是提取字体文件并建立glyph id到文字的映射关系;需通过network面板定位woff/ttf文件,用fonttools解析cmap表获取unicode码位映射,再结合dom中私有区字符还原真实文本。

直接说结论:字体反爬本质是「字符映射混淆」,不是加密也不是渲染障碍,核心解法是提取字体文件 + 建立 glyph ID 到文字的映射关系,而不是硬抠 CSS 或模拟渲染。
怎么快速定位目标字体文件(.woff/.ttf)
网页中动态字体通常通过 @font-face 加载,但 URL 往往藏在 CSS 里或由 JS 拼接。别盯着页面源码找,直接看浏览器开发者工具的 Network 面板:
- 刷新页面,筛选类型为
font或扩展名为.woff、.woff2、.ttf的请求 - 注意字体 URL 是否带时间戳或随机参数(如
?v=123abc),这类参数通常可忽略,直接请求原始路径即可 - 如果字体 URL 是 JS 动态生成的(比如拼接了
window.location.hostname),抓包时用「Preserve log」并勾选「Disable cache」,确保不漏掉首次加载的字体请求
用 fontTools 解析 woff 文件并提取 cmap 表
Python 中最稳的方案是用 fontTools 库读取字体二进制结构,重点是 cmap 表——它定义了 Unicode 码位与字形(glyph)的映射。别用正则去“猜”字体内容,那是徒劳的。
- 安装:
pip install fonttools - 关键代码片段(只取核心逻辑):
from fontTools.ttLib import TTFont
font = TTFont("example.woff")
cmap = font.getBestCmap() # 返回 {unicode_code: glyph_name}
# 注意:cmap 键是 int 类型的 Unicode 码点,比如 0x1f600,不是字符串 "1f600"
print(cmap)
- 有些字体用的是自定义编码(比如把汉字映射到私有区 U+F900–UF9FF),此时
getBestCmap()可能返回空或乱码,要手动指定平台 ID:font["cmap"].tables[2].cmap(索引 2 通常是 Windows Unicode BMP 表) - 别依赖字体文件名判断编码方式,每次都要实测
cmap内容
如何把网页中乱码文字还原成真实文本
字体反爬页面里,文字实际是用 glyph 名称或 Unicode 私有区码位渲染的,你看到的“”或方块,是因为系统没装该字体,但 HTML 里存的是真实码点或 data 属性里的 glyph ID。
- 先确认网页中目标文字的 DOM 结构:常见模式是
<span class="num"></span>,其中那个小方块其实是 Unicode 私有区字符(如\ue606),不是乱码 - 用
element.text.strip().encode('utf-16', 'surrogatepass').decode('utf-16')把这种 \ue606 转成真实 Unicode 整数,再查cmap反推原字(注意:cmap 键是整数,不是字符串) - 更稳妥的做法是直接解析字体中每个 glyph 的轮廓(
glyf表),用图像比对或哈希匹配——但这只在 cmap 完全不可用时才启用,开销大且易失效 - 警惕字体更新:网站只要换一次字体文件,映射关系就全作废,必须重新提取
cmap并更新本地映射表
为什么 Puppeteer/Playwright 渲染截图不管用
因为字体反爬不是为了防截图,而是防文本提取。截图 OCR 成功率低、速度慢、易受字号/颜色/背景干扰,属于兜底手段,不是正解。
- 截图后 OCR 的错误率在中文场景下常超 15%,尤其遇到连笔、斜体或小字号数字时
- 字体文件体积通常不到 100KB,而启动一个浏览器实例至少多耗 300MB 内存和 1s+ 启动时间,性价比极低
- 真正该拦截的是字体加载后的 DOM 文本节点内容,不是渲染结果——所以优先走静态字体解析,不是渲染管道
字体反爬最麻烦的从来不是技术实现,而是映射关系的维护成本:同一个网站可能每周换一次字体文件,而 cmap 表一旦变动,所有靠硬编码 glyph 名称或 Unicode 偏移的逻辑都会静默出错。上线后必须加字体指纹校验和告警,不能只靠一次解析一劳永逸。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











