1. 字体加密技术背景与应用场景
在当今互联网数据采集领域,字体加密已成为主流的反爬虫技术手段之一。这种技术通过自定义字体文件对网页中的关键内容进行视觉渲染,使得浏览器能正常显示文字,但爬虫直接获取的却是乱码或无效字符。我最早在2018年接触电商价格采集时就遇到过这种防御机制,当时花了两周时间才彻底破解其加密逻辑。
字体加密的核心原理是字体文件(通常是TTF或WOFF格式)中字符编码(Unicode)与实际显示字形(Glyph)之间的映射关系被故意打乱。例如网页显示"128"这个价格,实际HTML中可能是""这样的特殊字符,这些字符通过自定义字体渲染后才变成人类可读的数字。这种技术常见于:
- 电商平台商品价格展示
- 票务网站座位余量显示
- 房地产中介房源信息
- 招聘网站薪资数据
2. Python解析加密字体的技术方案
2.1 字体文件获取与分析
首先需要通过浏览器开发者工具(F12)定位到字体文件。以Chrome为例:
- 打开Network面板并筛选"font"类型资源
- 刷新页面观察加载的字体文件(通常为.woff/.ttf)
- 右键复制字体文件链接地址
使用Python下载字体文件的典型代码:
import requests font_url = "https://example.com/fonts/encrypted.woff" response = requests.get(font_url) with open("local_font.woff", "wb") as f: f.write(response.content)2.2 字体解析库选择与使用
推荐使用fontTools这个专业字体处理库:
pip install fontTools解析字体文件的代码示例:
from fontTools.ttLib import TTFont font = TTFont("local_font.woff") # 获取字符编码到字形名称的映射 cmap = font.getBestCmap() # 获取字形轮廓坐标信息 glyf = font["glyf"]2.3 字符映射关系破解
字体加密的核心就是打乱了Unicode编码与实际显示内容的对应关系。我们需要建立正确的映射表,常见方法有:
- 基准字符比对法:找到页面中已知的字符(如"价格:"文字),逆向推导其编码
# 示例:已知页面显示"价格:¥128"对应的加密字符是"abc" mapping = { "a": "¥", "b": "1", "c": "2", "d": "8" }- 字形特征分析法:通过解析字体轮廓坐标识别对应数字
def recognize_number(glyph): # 分析glyf[glyph_name].coordinates等特征 # 返回识别出的数字或字符3. 实战:电商价格解密案例
以某电商平台为例,完整解密流程:
3.1 页面分析阶段
- 检查网页源码发现价格显示为
<span class="price"></span> - 在CSS中找到字体引用:
@font-face{src:url(//static.example.com/font/price.woff)}
3.2 字体解密实现
import re from fontTools.ttLib import TTFont import requests from io import BytesIO def decrypt_price(page_html): # 提取字体URL font_url = re.search(r"src:url\((.*?\.woff)\)", page_html).group(1) if not font_url.startswith("http"): font_url = "https:" + font_url # 下载并加载字体 font_data = requests.get(font_url).content font = TTFont(BytesIO(font_data)) # 建立映射关系(需根据实际字体调整) cmap = font.getBestCmap() glyph_order = font.getGlyphOrder() number_map = { glyph_order[1]: "1", glyph_order[2]: "2", # ...其他数字映射 } # 解密页面中的价格 encrypted_price = re.search(r'<span class="price">(.*?)</span>', page_html).group(1) decrypted = "".join([number_map[char] for char in encrypted_price]) return decrypted3.3 动态字体应对策略
高级反爬系统会动态生成字体文件,每次请求的字符编码都不同。应对方案:
- 建立字体特征库,自动识别新字体的字符对应关系
- 使用OCR技术辅助识别关键字符
- 结合机器学习模型预测映射关系
4. 常见问题与解决方案
4.1 字体加载失败问题
- 现象:无法下载字体文件或字体解析出错
- 排查:
- 检查字体URL是否完整(可能需要补全域名)
- 验证请求头是否包含Referer等必要字段
- 测试直接浏览器访问字体URL
4.2 映射关系识别错误
- 现象:解密后的数字顺序或值不正确
- 解决方案:
- 人工验证基准字符(如页面中的"¥"符号)
- 对比多个商品的加密字符模式
- 保存字体样本用于后续分析
4.3 动态字体变化
- 现象:相同商品每次刷新显示不同加密字符
- 应对策略:
- 实现字体缓存机制
- 提取字体特征而非依赖固定编码
- 使用selenium等工具保持会话一致
关键提示:处理字体加密时务必注意请求频率,过于频繁的字体下载请求可能触发反爬机制。建议对已知字体进行本地缓存,平均每个字体文件只需下载解析一次。
5. 进阶技巧与优化建议
- 字体预处理脚本:将常用网站的字体映射关系提前分析并存储
FONT_DB = { "example.com": { "font_md5": "a1b2c3d4...", "mapping": {"uniE123": "5", ...} } }- 多线程字体下载:当页面引用多个字体文件时
from concurrent.futures import ThreadPoolExecutor def download_font(url): return requests.get(url).content with ThreadPoolExecutor() as executor: font_urls = ["url1", "url2", "url3"] results = list(executor.map(download_font, font_urls))- 字形可视化分析(调试用):
from matplotlib import pyplot as plt def plot_glyph(font, glyph_name): glyph = font["glyf"][glyph_name] coords = glyph.coordinates plt.plot(coords[::2], coords[1::2]) plt.show()在实际项目中,我发现电商平台通常会在重大促销活动前更新字体加密策略。建议建立字体版本监控机制,当检测到新字体出现时自动触发分析流程,而不是等到采集失败才发现问题。可以定期访问测试页面,对比字体文件的哈希值是否发生变化。