环境:Windows7 +Python3.6+Pycharm2017

目标:猫眼电影票房、汽车之家字体反爬的处理

---全部文章: 京东爬虫 <https://blog.csdn.net/xing851483876/article/details/80817578> 、
链家爬虫 <https://blog.csdn.net/xing851483876/article/details/81408281>、美团爬虫
<https://blog.csdn.net/xing851483876/article/details/81842329>、微信公众号爬虫
<https://blog.csdn.net/xing851483876/article/details/82493412>、字体反爬
<https://blog.csdn.net/xing851483876/article/details/82928607>、Django笔记
<https://blog.csdn.net/xing851483876/article/details/84836439>、阿里云部署
<https://blog.csdn.net/xing851483876/article/details/84306953>、vi\vim入门
<https://blog.csdn.net/xing851483876/article/details/84553945>----


前言:字体反爬,也是一种常见的反爬技术,例如猫眼电影票房,汽车之家,天眼查等网站。这些网站采用了自定义的字体文件,在浏览器上正常显示,但是爬虫抓取下来的数据要么就是乱码,要么就是变成其他字符。采用自定义字体文件是CSS3的新特性,详情参考
CSS3字体 <http://www.w3school.com.cn/css3/css3_font.asp> 。

一、猫眼电影

打开猫眼电影票房 https://piaofang.maoyan.com/?ver=normal
<https://piaofang.maoyan.com/?ver=normal> ,打开浏览器开发者模式,可以看到这些票房数据在HTML代码中是显示不了的。




点击上图右上角的Sources,把这个html文档下载下来,在编辑器打开,就可以看到这些方框对应的是一个个编码,这些编码是自定义的,所以用utf-8编码方式是显示不出来的。浏览器显示的时候因为采用了自定义的字体文件,所以显示正常。




我们来找一下这个字体文件 ,在html页面中搜索关键字:font-face,找到如下内容。一大串字符串,从base64后面开始一直到后面format前面的括号中的内容,应该是字体文件的内容。是经过了base64编码后的形式。把这一段字符串考出来,用base64解码后再保存成本地ttf文件(ttf是字体的一种类型)。



 处理代码如下,先解码,再保存成本地文件 zt02.ttf:
import base64
font_face='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'
b=base64.b64decode(font_face) with open('zt02.ttf','wb')as f: f.write(b)

接下来我们要查看和处理这个字体文件,这里要用到两个工具。一个是软件 FontCreator,可以直接打开ttf字体文件,查看每一个字符对应的编码。还有一个是python第三方库fontTools,借助这个库可以用python代码来操作ttf文件。

FontCreator安装:

安装包下载地址 :链接:https://pan.baidu.com/s/1zKIr7EcGlMSSF6e9Z6IZmw  提取码:d4gm 
(如果无效的话自己百度下)

安装好后,不用激活也能免费试用30天,Use Evaluation Version 。然后点击左上角打开文件,打开我们上面保存的zt02.ttf文件。



打开后看到如下界面,可以看到数字2 9 6上面的编码和我们之前html中看到的编码是一致的。



 



思路分析 

这里补充一点就是你每次访问加载的字体文件中的字符的编码可能是变化的,就是说网站有多套的字体文件。


既然编码是不固定的,那就不能用编码的一一对应关系来处理字体反爬。这里要用到上面说的三方库fontTools,利用fontTools可以获取每一个字符对象,这个对象你可以简单的理解为保存着这个字符的形状信息。而且编码可以作为这个对象的id,具有一一对应的关系。像猫眼电影,虽然字符的编码是变化的,但是字符的形状是不变的,也就是说这个对象是不变的。


基本思路:先下载一个字体文件保存到本地(比如叫01.ttf),人工的找出每一个数字对应的编码。当我们重新访问网页时,同样也可以把新的字体文件下载下来保存到本地ttf(比如叫02.ttf)。网页中的一个数字的编码比如为AAAA,如何确定AAAA对应的数字。我们先通过编码AAAA找到这个字符在02.ttf中的对象,并且把它和01.ttf中的对象逐个对比,直到找到相同的对象,然后获取这个对象在01.ttf中的编码,再通过编码确认是哪个数字。

具体实现:

先安装 fontTools,应该是直接  pip install fonttools 就可以

基本命令介绍:
from fontTools.ttLib import TTFont font=TTFont('01.ttf') #打开本地字体文件01.ttf
font.saveXML('01.xml') #将ttf文件转化成xml格式并保存到本地,主要是方便我们查看内部数据结构

先把字体文件转化成xml格式,以便打开查看里面的数据结构。打开xml文件可以看到类似html标签的结构。这里我们用到的标签是<GlyphOrder...>和<glyf...>




点开标签内部,<GlyphOrder...>内包含着所有编码信息,注意前两个是不是0-9的编码,需要去除。



<glyf...> 内包含着每一个字符对象<TTGlyph>,同样第一个和最后一个不是0-9的字符,需要祛除。 



 点开<TTGlyph>对象,里面的信息如下,是一些坐标点的信息,可以联想到这些点应该是描绘字体形状的,后面再讲。




 实现步骤:先在本地保存字体文件01.ttf,并手动确认编码和数字的对应关系,保存到字典中。然后重新访问网页的时候,把网页中新的字体文件也下载保存到本地02.ttf。对于02中的编码uni2,先获取uni2的对象obj2,与01中的每一个对象注逐一对比,直到找到相同的对象obj1,再根据obj1的编码,在字典中找到对应的数字。代码如下:
from fontTools.ttLib import TTFont font1=TTFont('01.ttf') #打开本地字体文件01.ttf
obj_list1=font1.getGlyphNames()[1:-1] #获取所有字符的对象,去除第一个和最后一个
uni_list1=font1.getGlyphOrder()[2:] #获取所有编码,去除前2个 #手动确认编码和数字之间的对应关系,保存到字典中
dict={'uniEA78': '8', 'uniF411': '2', 'uniE87C': '1', 'uniEAC3': '9',
'uniE9DA': '3', 'uniE06A': '4', 'uniE210': '0', 'uniED72': '7', 'uniECB8': '5',
'uniF2A9': '6'} font2=TTFont('02.ttf') #打开访问网页新获得的字体文件02.ttf
obj_list2=font2.getGlyphNames()[1:-1] uni_list2=font2.getGlyphOrder()[2:] for
uni2 in uni_list2: obj2=font2['glyf'][uni2] #获取编码uni2在02.ttf中对应的对象 for uni1 in
uni_list1: obj1=font1['glyf'][uni1] if obj1==obj2: print(uni2,dict[uni1])
#打印结果,编码uni2和对应的数字
 
-----------------------------------------------分割线------------------------------------------------

二、汽车之家


上面讲的猫眼电影例子,是编码变化,但是字体形状不变,网上也有很多介绍的文章。而汽车之家的字体反爬,不仅是编码变化,而且是字体形状也有变化。就是说对象本身变化,不能再直接用比较对象的方法处理。网上搜也是基本没什么好的解决办法,有一种是用OCR识别,这个当然可以。下面介绍一种博主自己摸索的方法,简单试了下应该是ok的。

先看问题,打开汽车之家论坛的一篇文章,
https://club.autohome.com.cn/bbs/thread/1f05b4da4448439b/76044817-1.html###
<https://club.autohome.com.cn/bbs/thread/1f05b4da4448439b/76044817-1.html#%23%23>

右键检查,可以看到文章中的某些字符是显示不了的,如下图。



查找字体文件,直接html中搜索font-face,如下图,直接把url复制到浏览器中就能下载字体文件



 再用FontCreator打开,一共有38个汉字采用了自定义字体。




 上面说到汽车之家的字体形状也是变动的。直接打开字体文件看看是哪些数据发生了改变。其实主要有两个信息,一个是x,y坐标信息,还有一个是对应点0、1值。下几个字体文件就可以发现,同一个字符在不同的字体文件中x,y坐标是变化的,0,1值不变,还有坐标的数量也不变。



 很容易可以联想到这些x,y坐标应该是用来描绘字体形状的,用pylab画个图可以看出,如下图。0,1估计是描述连线的参数。




 这里猜想就是x,y坐标的变化是基于一个标准值做一定幅度的随机加减,而且这个范围相对于坐标系来说不会太大。因为变化太大的话字体显示就会有较大的差异,一个网站上的字符显示应该是保持一致的。实际通过几个字体文件计算得出的差值变化在40以内。


总结一下,这里我们要比较两个对象表示的字符是否相同,不能通过直接对比对象是否相等判断,而是要具体比较对象的坐标来判断。首先判断坐标的数量是否相同,如果相同的话,再比较每一个坐标是否相同,只要x,y相差40以内,我们就认为这2个坐标是相同的。如果两个对象的所有坐标都相同,则认为这两个对象表示同一个字符。这里有个前提就是同一个字符在不同字体文件中包含的x,y坐标数量是不变的,且x,y波动是在一定范围内的。实际测试几个样本来看这两个前提是满足的。还有就是两个不同字符误判成同一字符的概率,首先这38个字符坐标有29种数量,相同坐标数量的就没几个,还有每个坐标都差40以内是比较难碰撞的。还有为什么不用0,1来判断,这个值不同字符也有相同的。


具体实现:首先还是要在本地下载一个字体文件01.ttf,并手动确认好编码和字符的对应关系。然后重新访问网站时新加载的字体文件也下载到本地02.ttf,然后还是和上面的一样找到01中相同的对象并确认表示的字符。代码如下:
from fontTools.ttLib import TTFont def comp(l1,l2): #定义一个比较函数,比较两个列表的坐标信息是否相同
if len(l1)!=len(l2): return False else: mark=1 for i in range(len(l1)): if
abs(l1[i][0]-l2[i][0])<40 and abs(l1[i][1]-l2[i][1])<40: pass else: mark=0
break return mark #手动确定一组编码和字符的对应关系 u_list=['uniEC1A', 'uniEC25', 'uniEC34',
'uniEC36', 'uniEC3F', 'uniEC50', 'uniEC6A', 'uniEC6C', 'uniEC86', 'uniEC98',
'uniECA0', 'uniECB2', 'uniECBC', 'uniECCC', 'uniECCE', 'uniECE8', 'uniECE9',
'uniECF9', 'uniED02', 'uniED04', 'uniED15', 'uniED1E', 'uniED2F', 'uniED49',
'uniED4B', 'uniED54', 'uniED65', 'uniED77', 'uniED7F', 'uniED81', 'uniED91',
'uniED9B', 'uniEDAD', 'uniEDC7', 'uniEDC8', 'uniEDE1', 'uniEDE3', 'uniEDFD']
word_list=['少','大','二','四','和','右','下','左','矮','十','得','远','很','九','的','长','坏','八','多','着','小','上','高','近','六','短','了','七','地','不','更','低','是','三','呢','一','好','五']
#公众号@老王的小船 font1=TTFont('01.ttf') be_p1=[] #保存38个字符的(x,y)信息 for uni in u_list:
p1 = [] #保存一个字符的(x,y)信息 p=font1['glyf'][uni].coordinates
#获取对象的x,y信息,返回的是一个GlyphCoordinates对象,可以当作列表操作,每个元素是(x,y)元组 #
p=font1['glyf'][i].flags #获取0、1值,实际用不到 for f in p: #把GlyphCoordinates对象改成一个列表
p1.append(f) be_p1.append(p1) font2=TTFont('02.ttf')
uni_list2=font2.getGlyphOrder()[1:] on_p1=[] for i in uni_list2: pp1 = []
p=font2['glyf'][i].coordinates for f in p: pp1.append(f) on_p1.append(pp1) n2=0
x_list=[] for d in on_p1: n2+=1 n1=0 for a in be_p1: n1+=1 if comp(a,d):
print(uni_list2[n2-1],word_list[n1-1]) x_list.append(word_list[n1-1])
#分行打印出来,方便和FontCreator中进行比较确认 print(x_list[:16]) print(x_list[16:32])
print(x_list[-6:])
 运行结果:根据01找出02中编码和字符的对应关系,再用FontCreator打开02进行对比确认,结果OK 。

PS:博主实际测试了几个字体文件,都是OK的,但是也不保证全都OK。





---全部文章: 京东爬虫 <https://blog.csdn.net/xing851483876/article/details/80817578> 、
链家爬虫 <https://blog.csdn.net/xing851483876/article/details/81408281>、美团爬虫
<https://blog.csdn.net/xing851483876/article/details/81842329>、微信公众号爬虫
<https://blog.csdn.net/xing851483876/article/details/82493412>、字体反爬
<https://blog.csdn.net/xing851483876/article/details/82928607>、Django笔记
<https://blog.csdn.net/xing851483876/article/details/84836439>、阿里云部署
<https://blog.csdn.net/xing851483876/article/details/84306953>、vi\vim入门
<https://blog.csdn.net/xing851483876/article/details/84553945>----

 参考文章:

Python爬虫杂记 - 字体文件反爬(二) <https://www.jianshu.com/p/0e2e1aa6d270>

如何去除网页噪声提取数据(02) —— 汽车之家(字体反爬)
<https://blog.csdn.net/zwq912318834/article/details/80268149>

反击“猫眼电影”网站的反爬虫策略 <https://www.cnblogs.com/h2zZhou/p/7248261.html>