怎么用字体混淆防止网站文字被采集?
字体混淆用 Unicode 私有区码位配合自定义字体的 cmap 映射,使页面显示正常而 HTML/接口返回乱码;需每次请求生成新映射和字体并打乱字形顺序,无法完全阻止采集,但可将零成本爬取抬高到需逆向字体的中等成本。
字体混淆的核心做法:把页面里的真实字符替换成 Unicode 私有区(PUA,码位范围 U+E000–U+F8FF)的假码位,再用 @font-face 加载一份自定义字体,把这些假码位渲染成正常字形。爬虫抓到的 HTML 里只有  这类乱码,要还原就得先去下载字体、解析 cmap 表(字体内部「字符码位 → 字形」的映射表)。结论:字体混淆不能 100% 阻止采集,但能把「正则一提就拿到价格数字」的零成本爬取,抬升到需要逆向字体的中等成本。
字体混淆为什么能让爬虫抓到乱码?
原理是「文本层」和「显示层」被拆开。HTML 源码里写的是 PUA 码位,浏览器加载自定义字体后,cmap 表把 U+E1A3 指向「4」的字形轮廓,屏幕上就显示成 4。
于是原始 HTML、接口返回、页面复制出来的文字全是乱码,而人眼看到的内容完全正常。爬虫用 re.findall(r'\d+', html) 提取数字会直接失败,因为它拿到的不是 ASCII 数字。
字体混淆具体怎么做?
用 Python 的 fontTools 库重写 cmap 表,5 步可以跑通。
第一步,装依赖:pip install fonttools brotli(brotli 是导出 woff2 必需的)。
第二步,生成随机映射并重建 cmap:
import random
from fontTools.ttLib import TTFont
CHARS = "0123456789"
font = TTFont("src.ttf")
glyphmap = font.getBestCmap() # {unicode: glyphName}
puas = random.sample(range(0xE000, 0xF000), len(CHARS))
new_map = {p: glyphmap[ord(c)] for p, c in zip(puas, CHARS)}
for t in font["cmap"].tables:
t.cmap = new_map
font.flavor = "woff2"
font.save("obf-7f3a.woff2")
第三步,前端声明字体并应用:
@font-face{
font-family:"obf-7f3a";
src:url("/f/7f3a.woff2") format("woff2");
font-display:block;
}
.price{font-family:"obf-7f3a"}
第四步,模板输出 PUA 码位:<span class="price"></span>,映射对应「42」。
第五步,每次请求重新跑一遍脚本,字体文件名带随机 hash,响应头设 Cache-Control: no-store,或最多 max-age=60。
注意两点:一是只保留用到的字形做子集化(pyftsubset 可把 3MB 的中文字体压到 20KB 以内);二是字体的 glyph 顺序也要打乱,否则按字形顺序推断仍然可行。
每次请求都要换一套映射吗?
结论:要,固定映射的字体混淆等于没做。
如果全站用同一份字体,爬虫只要下载一次 woff2、解析一次 cmap,就能建好对照表,之后所有页面畅通无阻,逆向成本约 20 行代码。
真正有效的是动态字体:每次页面请求生成新的 PUA 映射和新字体文件。这样爬虫必须先抓字体、再解析 cmap、再回填文本,而且不能复用,请求量一大就撑不住。
再进一步是字形轮廓扰动:把数字笔画的坐标在 1000 单位的 em 网格里偏移 1–2 个单位,人眼无感,但基于字形模板匹配的还原方案会失效,必须上 OCR,成本再翻一档。
字体混淆有哪些坑和副作用?
最大的坑是伤 SEO 和无障碍。
正文内容不要混淆。Googlebot 虽然会下载字体并按渲染结果取文本,但把标题、段落全做成 PUA 码位,属于不确定性风险,不值得。建议只混淆价格、电话、库存、销量、会员码这类结构化字段。
用户复制粘贴会得到乱码,屏幕阅读器也会读出错误内容。缓解办法是给混淆元素加 aria-label="42元",并在复制事件里用 JS 把 PUA 码位替换回真实字符。
还要做兜底:字体加载失败时页面会显示方块,用 font-display:block 配合超时降级,或服务端同时做频率限制(同一 IP 1 分钟超过 60 次请求直接拦截)和 UA 校验。字体混淆只是众多手段里的一层,别把它当成唯一防线。
爬虫怎么破字体混淆?
结论:单靠字体混淆挡不住有针对性的人,只能挡住批量脚本。
破解路径很固定:下载 woff2 → 用 fontTools 读 font.getBestCmap() → 拿到 PUA 到 glyphName 的映射 → 把 glyph 渲染成图片或提取轮廓坐标 → 与标准字库逐字比对,相似度最高的即为真值。整套流程开源方案已经成型。
所以评估防护效果的标准不是「能不能破」,而是「破解要花多少时间和算力」。动态映射 + 轮廓扰动 + 短缓存 + 频率限制四件套叠起来,脚本爬取的成本会从分钟级升到小时级甚至更高。
总结:字体混淆靠 PUA 码位加自定义 cmap 表,让 HTML 文本与视觉内容分离;落地要点是 fontTools 重建 cmap、每次请求换映射、字体文件短缓存、只混淆关键字段并做好复制还原和 SEO 兜底。它拦不住定向逆向,但能把采集成本显著抬高。
转载请注明出处,版权归原作者所有。