+
+
近些阵子知乎上线了针对专栏中盐选文章的反爬系统,随后该系统也被运用在知乎回答页面中的盐选文章上,具体表现为爬取的文章内容中出现大量的错乱词汇。而在本篇文章中我们将一步步带领各位解开这些乱码,在这个过程中我们将对字体反爬有更深入的认识,并学到运用字体反爬时需要注意的问题。
+
一、知乎反爬效果 来自知乎回答不被爱是一种什么样的感受? - 知乎
+
+
如图所示,在页面源码中出现了大量乱码,例如(原字,错字):
+
+中 -> 在
+是 -> 时
+上 -> 大
+
+
这些乱码使得文章可读性大大下降,那么乱码是怎么产生的?又如何解决这个问题呢?
+
二、找寻乱码真凶 观察上述现象,页面源码中的字,在被显示到页面后,居然变成了正确的字。因此我们初步推断知乎在该页面运用了字体反爬。
+
接下来我们打开 F12 -> Network 页面,选择 Font,观察知乎加载的字体。
+
+
右键选择 Open in new tab 将字体保存下来。
+
+
将字体后缀名改为 .ttf 并打开。
+
+
左:正常字体 右:反爬字体
+
+
与正常字体对比,我们下载的字体明显替换了部分字体,这便是知乎用于反爬的字体了。接下来我们将分析这个字体并给出应对方案。
+
三、致命缺陷 字体反爬的根本原理是替换原本的字为一个新字,再用字体将新字渲染为原字,这样对程序而言就只见到新字而不是旧字了,而用户看到的还是原本的内容。因此只要找到新字与原字间的对应关系便可解决该反爬。而要找到这个对应关系,抓住字体中各个字形的特征是必不可少的一环。
+
我们打开 FontDrop! 加载字体,向下翻,观察字形的特征。
+
+
我们发现字形的 Glyph 为 uni662F 而 Unicode 为65F6,接下来我们试着查询这两个十六进制数对应的字:
+
1 2 3 4 glyph = "\u662F" unicode = "\u65F6" print (glyph, unicode)
+
+
正好,上文提到,「是」在源码中被替换为了「时」。知乎在反爬字体中保留了原字与新字的对应关系,为我们提供了一个极为便捷的捷径,避免了对字形笔画的具体分析,这也是其字体反爬系统的致命缺陷。
+
至此,字形的特征与对应关系都被我们分析出了,接下来我们将编写程序从字体中提取对应关系。
+
四、提取对应关系 要提取各个字间的对应关系,首先我们需要安装 fontTools 。
+
+
+
用 ttLib.TTFont(filename)
打开字体:
+
1 2 3 from fontTools import ttLib font = ttLib.TTFont(input ("Input font filename: " ))
+
+
初始化一个存储对应关系的字典:
+
+
+
遍历字形,获得其 Glyph 与 Unicode,并写入字典(注意这里的Glyph对应的字可能不是标准的字,比如是康熙部首,因此我们要对其标准化):
+
1 2 3 4 5 6 7 8 from unicodedata import normalize cmap = font.getBestCmap()for x in cmap.items(): zhihu_dict[chr (x[0 ])] = normalize("NFKC" , chr (int (x[1 ][3 :], 16 )))print (zhihu_dict)
+
+
(这里的 cmap 是一个 dict,是字形的 {Unicode: Glyph})
+
接下来,我们将使用得到的对应关系将带乱码的文章转为正常文章。
+
五、去除乱码 这段代码很简单,不作解释。
+
1 2 3 raw_content = "在间那块奶酪夹心,时饼干被的喜爱了灵魂。" new_content = raw_content.translate(str .maketrans(zhihu_dict))print (new_content)
+
+
六、全部代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from fontTools import ttLibfrom unicodedata import normalize font = ttLib.TTFont("DynamicFonts30.ttf" ) zhihu_dict = {} cmap = font.getBestCmap()for x in cmap.items(): zhihu_dict[chr (x[0 ])] = normalize("NFKC" , chr (int (x[1 ][3 :], 16 )))print (zhihu_dict) raw_content = "在间那块奶酪夹心,时饼干被的喜爱了灵魂。" new_content = raw_content.translate(str .maketrans(zhihu_dict))print (new_content)
+
+
上面字体文件名记得换成你自己下载的字体文件名
+
注
+
+
+
+