因需,需定期抓取某网站数据进行分析。

近期该网站HTTP POST获取的数据采用字体加密,以避免被简单的抓取。

具体而言,返回的关键数据被套上:#MI35nElclt_1721813502875otltag䓡䑱#FontTag

其中括号内#(MI35nElclt_1721813502875)otltag(䓡䑱)#FontTag 分别是字体文件名称和字体加密后的数据。

难点:

  1. 字体文件是动态的,无法“一次识别,一直使用”;
  2. 字体直接 OCR 识别,效率低、费用高、准确率低(存在生僻字)。

解决思路

分析字体文件后发现,所用字体文件中,字体并不多,仅 1k+个。

在综合采用 OCR 等思路后,最后得到基于 imagehash 的方案如下:

  1. OCR 识别:下载字体文件,采用WindowsPowerToys 对字体文件进行识别解析,并人工校正;
  2. 建立字典:采用footToolsPILttf字体文件中的每个字渲染为 64*64 的图片,并采用imagehash计算其hash值,并根据 OCR 识别结果,建立hash:char 字典;
  3. 字体文件解析:每次抓取数据后,针对字体文件的每个字符,计算其imagehash,并从hash:char 字典中找到相似度最高的 char 作为对应字符,得到字体文件unicode:char字典;
  4. 数据解析:根据字体文件unicode:char字典,解析返回的数据。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import imagehash
from fontTools.ttLib import TTFont
from PIL import Image, ImageDraw, ImageFont

def _calculate_imagehash(font_path, char, font_size=64):
font = ImageFont.truetype(font_path, font_size)
ascent, descent = font.getmetrics()
bbox = font.getbbox(char)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
# render the image
image = Image.new('L', (font_size, font_size), 255)
draw = ImageDraw.Draw(image)
# draw text
draw.text(((image.width-text_width)//2, (image.height-ascent-descent)//2), char, font=font, fill="black")
# hash the image
image_hash = imagehash.average_hash(image)
return image_hash

优劣分析

优势

  1. 可动态解析字体文件,无需畏惧网站动态改变字体文件所涵字体数量;
  2. 单个网站的多个页面,构建单个hash:char 即可,后续若有新字体加入,适当追加即可;
  3. 当字体存在细微修改,其所构建的hash计算相似度时依旧有效;
  4. 相较于 OCR 方式,准确性更有保障;

劣势

  1. 由于每次需要解析整个字体文件,耗时略长(1k字符/5s),不过在可接受范围;
  2. 当网站更新字体时,可能无法继续工作;

参考