Python实战:5分钟搞定动态WOFF字体反爬,以某采购网为例(附完整代码)

张开发
2026/4/12 17:08:52 15 分钟阅读

分享文章

Python实战:5分钟搞定动态WOFF字体反爬,以某采购网为例(附完整代码)
Python实战5分钟搞定动态WOFF字体反爬以某采购网为例附完整代码字体反爬是爬虫开发中常见的障碍尤其当目标网站使用动态WOFF字体加密关键数据时。本文将带你快速突破这一技术壁垒通过一个政府采购网的实际案例手把手教你从零构建完整的解决方案。1. 动态WOFF字体反爬原理剖析动态WOFF字体反爬的核心在于字体映射的动态变化。与传统静态字体不同动态字体的字符编码与显示形态会定期更新导致常规OCR方案失效。其技术实现通常包含三个关键环节字体文件动态生成服务器每次返回的WOFF文件可能包含不同的字形定义CSS样式动态注入通过JavaScript实时修改font-face规则中的字体URL字符编码随机化相同文字在不同请求中可能对应不同的Unicode码点以某政府采购网为例其价格数据采用以下加密逻辑# 伪代码展示字体动态映射原理 def generate_dynamic_font(): base_unicode 0xE000 # 私有使用区起始码点 char_mapping { 0: random_unicode(base_unicode), 1: random_unicode(base_unicode1), # ...其他数字映射 } return create_woff_font(char_mapping)2. 快速定位字体文件实战中获取WOFF文件的三种高效方法CSS源码分析直接搜索font-face规则import re def extract_woff_url(html): pattern rurl\(?(https?://[^\)]?\.woff)?\) return re.search(pattern, html).group(1)网络请求监控使用浏览器开发者工具的Network面板过滤woff类型请求动态加载捕获对于AJAX加载的字体可通过Selenium拦截请求from selenium.webdriver import ChromeOptions options ChromeOptions() options.set_capability(goog:loggingPrefs, {performance: ALL}) driver webdriver.Chrome(optionsoptions)字体URL特征对比表类型示例稳定性静态URL/static/font.woff高动态参数/font?v123456中全动态路径/fonts/a1b2c3.woff低3. 字体解析实战代码使用fontTools库快速解析WOFF文件from fontTools.ttLib import TTFont import io def parse_woff(woff_data): font TTFont(io.BytesIO(woff_data)) cmap font[cmap].getBestCmap() glyphs font.getGlyphSet() mapping {} for code, name in cmap.items(): glyph glyphs[name] # 提取关键特征点 coords list(glyph.coordinates) mapping[hex(code)] { name: name, points: coords, contours: list(glyph.endPtsOfContours) } return mapping关键步骤解析将二进制WOFF数据加载到内存提取字符编码映射表(cmap)获取每个字符的字形轮廓数据生成特征点指纹用于字符识别4. 动态映射解决方案针对字体动态变化的三种应对策略4.1 实时指纹比对def match_character(target_glyph, known_glyphs): 通过轮廓特征匹配字符 min_diff float(inf) matched_char None for char, glyph in known_glyphs.items(): diff calculate_similarity(target_glyph[points], glyph[points]) if diff min_diff: min_diff diff matched_char char return matched_char4.2 机器学习分类使用预训练模型处理字形图像import tensorflow as tf model tf.keras.models.load_model(font_classifier.h5) def predict_char(glyph_image): img_array tf.keras.preprocessing.image.img_to_array(glyph_image) img_array tf.expand_dims(img_array, 0) predictions model.predict(img_array) return chr(predictions[0].argmax() 48) # ASCII数字转换4.3 缓存自动更新建立字体版本管理系统class FontCache: def __init__(self): self.versions {} def check_update(self, url): resp requests.head(url) etag resp.headers.get(ETag) if url not in self.versions or self.versions[url] ! etag: self.update_font(url) self.versions[url] etag5. 完整集成示例将上述模块整合到Scrapy爬虫中import scrapy from font_decoder import FontDecoder class GovPurchaseSpider(scrapy.Spider): name gov_purchase def __init__(self): self.font_decoder FontDecoder() def parse(self, response): # 提取字体URL woff_url response.css(style::text).re_first(rurl\(?(.*?\.woff)?\)) # 并发请求页面数据和字体文件 yield scrapy.Request(woff_url, callbackself.parse_font) yield scrapy.Request(self.detail_url, callbackself.parse_detail) def parse_font(self, response): self.font_decoder.update_font(response.body) def parse_detail(self, response): encrypted_prices response.css(.price::text).getall() decoded_prices [self.font_decoder.decode(p) for p in encrypted_prices] yield { title: response.css(h1::text).get(), prices: decoded_prices }性能优化技巧使用aiohttp实现异步字体下载对字形特征进行哈希缓存建立错误映射自动修复机制6. 常见问题排查字体加载异常try: font TTFont(BytesIO(woff_data)) except TTLibError as e: # 尝试修复损坏的WOFF头 fixed_data woff_data[:4] b\x00\x01\x00\x00 woff_data[8:] font TTFont(BytesIO(fixed_data))字符识别偏差检查FreeType库版本是否≥2.8确认图像二值化阈值设置合理验证特征点提取算法是否包含关键轮廓实际项目中我发现字体映射关系通常在每周一凌晨更新建议设置定时任务提前抓取新字体。对于特别复杂的字形可以结合CNN模型提升识别准确率。

更多文章