python爬虫神技(Python爬虫字体反爬处理)

来源:老王的小船

环境:Win7 Python3.6 Pycharm2017

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

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

w3school/css3/css3_font.asp

一、猫眼电影

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

python爬虫神技(Python爬虫字体反爬处理)(1)

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

python爬虫神技(Python爬虫字体反爬处理)(2)

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

python爬虫神技(Python爬虫字体反爬处理)(3)

处理代码如下,先解码,再保存成本地文件 zt02.ttf:

1import base64 2 3font_face='d09GRgABAAAAAAgoAAsAAAAAC7gAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7le/Y21hcAAAAYAAAAC/AAACTCb1coxnbHlmAAACQAAAA5YAAAQ0l9 jTWhlYWQAAAXYAAAALwAAADYS0muuaGhlYQAABggAAAAcAAAAJAeKAzlobXR4AAAGJAAAABIAAAAwGhwAAGxvY2EAAAY4AAAAGgAAABoGpAXQbWF4cAAABlQAAAAfAAAAIAEZADxuYW1lAAAGdAAAAVcAAAKFkAhoC3Bvc3QAAAfMAAAAWwAAAI/dSbWYeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk0mWcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGBwYKn54MOv812GIYdZhuAIUZgTJAQDcwAtSeJzFkj0Kg0AQhd qSYymSBnwCgEr76Ctvaew8QQ5QW5gY50qxxF/EGwFsTNvHZuAtsks38K8XWaGmQFwAGCSO7EA9YaCthdVtegmnEW38KB/w5WKjayMq6IOGr/1urxPhnSMpnCe WP/ZcsUI24d/eIw0xEn1ugyu40zDMrHnUg/MPW/1N92We7n6rkkW2GJZSzouVWFoGdcBwJ7isYX2F20nqB3ocsFHbNPBL0XQypwChgjgfPAFAowPt8GPegAeJxFk89v2mYcxt/XVHZKKCHDPwppAWOCDSTB8S8COIZCoM1PRgKEkJaGqKU0W9ssarq0jbaWbpPaaX9Ad5m0wy7VDr130rSetk5bDvsDJu262yr1EsFeA9l8sPS1Zb/P83meL4AAdP8GEiABBkBMpkgPKQB0oan7FgPY74ADUfTGA2XJgDEDzkKFxwmc8wdVRZMlD6RIO T8fJCHSpDz4xTJ0JL25bAuhpO8HSegKzoR23jw6fbcvp68VygrmhW2VmeSlVD4fuF7XR03VLc2NnQKD7vdj3Zufb74VfvZt WpaBkmlzbqK/lQZN3UA3u3d0hPAIBxilVihgWpiWm9QwnegBJNkThhh4TdQsB3HX7YOibEg4kCFVrUU0uwdvrgtwM2QmZFQWLeGyqVvB5XNKr6xIXzM9fnF/LW5s298uSyxKQEdvIscwYdZ/mPAQm8YBIAp nZPIcwbePINJpjJC1Lpg4/biFpBg1af3r54e6rvZ1Mrv3HhXRezCgix2abF875x/0hn0yFSp8U4WfCzvs37yy1BPpq5sqhoTfy9e UlM9bz6Y7T/kc6aRI/tFqsZ8H8n M/QqsAGljVVaF8qhMcRQ/aoHZzi8wf6nRqP75ogiPOmLxxTF69kOfW/cf2EUeIoMUNYVHyIgYI2nqAB4SLEuMByJ PWMoXL49clEzynxIdwes9vhGSpPnrFVHPFFKSNOqNJ26 LR19fD0z4uZyiEvWJdhclZMGZmRWnTafba6tUiPXM5febJb z 7faTBhpRzoygjVTPDk F 1dcS5mfGhOE4Jnp0R9kvuUTmpH wg77xgQn0dbAn2syXMpAJut86xL8nnWYg2fOmmuDh1zYqoIR9YcZ2xrcprx8mrmVuP1vKflTWVFvnOZ8LasXCvRJGK8w4442fX9Omp9rN7N3Zb14d1VfFqVLnzUQ5UlueX68MtHex18CJ8ldZCrULJzizeaaDKDzisnOy0zW0CUcd3qQnzWK3y7lA4/7DdO2DcFM/uBO/HBzk8BY7hf2EmnSSQx k6VYYuDJ3Cu0SV9Y57V0tZKNZMm1HLzW Yv3zXH1x/Hcx9uzxtDrXGb7eSXotcLd0o808/jG1qV1baZ2wux40FfgRDsCe3AGm2H G7VV0hx8ELEMu9ytlb3kOYfDZh 7Xrih52vFB2th4WFgEjbaCyulzXBav5Vq8itrC9U3L /uw61kQs6AfwHWE DCAAB4nGNgZGBgAOLaDQuN4/ltvjJwszCAwPWbk38j6P9vWBiYzgO5HAxMIFEAYxgM AB4nGNgZGBg1vmvwxDDwgACQJKRARXwAAAzYgHNeJxjYQCCFAYGJh3iMAA3jAI1AAAAAAAAAAwAVACOANQA8AEyAUwBkAG0AeYCGgAAeJxjYGRgYOBhMGBgZgABJiDmAkIGhv9gPgMADoMBVgB4nGWRu27CQBRExzzyAClCiZQmirRN0hDMQ6lQOiQoI1HQG7MGI7 0XpBIlw/Id UT0qXLJ6TPYK4bxyvvnjszd30lA7jGNxycnnu J3ZwwerENZzjQbhO/Um4QX4WbqKNF Ez6jPhFrp4FW7jBm 8wWlcshrjQ9hBB5/CNVzhS7hO/Ue4Qf4VbuLWaQqfoePcCbewcLrCbTw67y2lJkZ7Vq/U8qCCNLE93zMm1IZO6KfJUZrr9S7yTFmW50KbPEwTNXQHpTTTiTblbfl PbI2UIFJYzWlq6MoVZlJt9q37sbabNzvB6K7fhpzPMU1gYGGB8t9xXqJA/cAKRJqPfj0DFdI30hPSPXol6k5vTV2iIps1a3Wi KmnPqxVhjCxeBfasZUUiSrs XY82sjqpbp46yGPTFpKr2ak0RkhazwtlR86i42RVfGn93nCip5t5gh/gPYnXLBAHicbco7DoAgEIThHXyj3kVZECwxyl1s7Ew8vnFp/ZsvmQwpymn6b4BCgRIVajRo0UGjx4CR8DT3daZgw dhYja6XXTzJjI70Zokf/YsLnaVfXJG9JGJXhiiF2UA' 4 5b=base64.b64decode(font_face) 6with open('zt02.ttf','wb')as f: 7 f.write(b)

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

FontCreator安装:

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

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

python爬虫神技(Python爬虫字体反爬处理)(4)

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

python爬虫神技(Python爬虫字体反爬处理)(5)

python爬虫神技(Python爬虫字体反爬处理)(6)

思路分析

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

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

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

具体实现:先安装 fontTools,应该是直接 pip install fonttools 就可以

基本命令介绍:

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

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

python爬虫神技(Python爬虫字体反爬处理)(7)

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

python爬虫神技(Python爬虫字体反爬处理)(8)

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

python爬虫神技(Python爬虫字体反爬处理)(9)

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

python爬虫神技(Python爬虫字体反爬处理)(10)

实现步骤:先在本地保存字体文件01.ttf,并手动确认编码和数字的对应关系,保存到字典中。然后重新访问网页的时候,把网页中新的字体文件也下载保存到本地02.ttf。对于02中的编码uni2,先获取uni2的对象obj2,与01中的每一个对象注逐一对比,直到找到相同的对象obj1,再根据obj1的编码,在字典中找到对应的数字。代码如下:

1from fontTools.ttLib import TTFont 2 3 4font1=TTFont('01.ttf') #打开本地字体文件01.ttf 5obj_list1=font1.getGlyphNames()[1:-1] #获取所有字符的对象,去除第一个和最后一个 6uni_list1=font1.getGlyphOrder()[2:] #获取所有编码,去除前2个 7 #手动确认编码和数字之间的对应关系,保存到字典中 8dict={'uniEA78': '8', 'uniF411': '2', 'uniE87C': '1', 'uniEAC3': '9', 'uniE9DA': '3', 'uniE06A': '4', 'uniE210': '0', 'uniED72': '7', 'uniECB8': '5', 'uniF2A9': '6'} 9 10 11font2=TTFont('02.ttf') #打开访问网页新获得的字体文件02.ttf 12obj_list2=font2.getGlyphNames()[1:-1]#公众号@老王的小船 13uni_list2=font2.getGlyphOrder()[2:] 14 15for uni2 in uni_list2: 16 obj2=font2['glyf'][uni2] #获取编码uni2在02.ttf中对应的对象 17 for uni1 in uni_list1: 18 obj1=font1['glyf'][uni1] 19 if obj1==obj2: 20 print(uni2,dict[uni1]) #打印结果,编码uni2和对应的数字

-----------分割线-------------

二、汽车之家

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

先看问题,打开汽车之家论坛的一篇文章,club.autohome/bbs/thread/1f05b4da4448439b/76044817-1.html###

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

python爬虫神技(Python爬虫字体反爬处理)(11)

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

python爬虫神技(Python爬虫字体反爬处理)(12)

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

python爬虫神技(Python爬虫字体反爬处理)(13)

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

python爬虫神技(Python爬虫字体反爬处理)(14)

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

python爬虫神技(Python爬虫字体反爬处理)(15)

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

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

具体实现:首先还是要在本地下载一个字体文件01.ttf,并手动确认好编码和字符的对应关系。然后重新访问网站时新加载的字体文件也下载到本地02.ttf,然后还是和上面的一样找到01中相同的对象并确认表示的字符。代码如下:

1from fontTools.ttLib import TTFont 2 3 4def comp(l1,l2): #定义一个比较函数,比较两个列表的坐标信息是否相同 5 if len(l1)!=len(l2): 6 return False 7 else: 8 mark=1 9 for i in range(len(l1)): 10 if abs(l1[i][0]-l2[i][0])<40 and abs(l1[i][1]-l2[i][1])<40: 11 pass 12 else: 13 mark=0 14 break 15 return mark 16 17#手动确定一组编码和字符的对应关系 18u_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'] 19word_list=['少','大','二','四','和','右','下','左','矮','十','得','远','很','九','的','长','坏','八','多','着','小','上','高','近','六','短','了','七','地','不','更','低','是','三','呢','一','好','五'] #公众号@老王的小船 20 21 22font1=TTFont('01.ttf') 23be_p1=[] #保存38个字符的(x,y)信息 24for uni in u_list: 25 p1 = [] #保存一个字符的(x,y)信息 26 p=font1['glyf'][uni].coordinates #获取对象的x,y信息,返回的是一个GlyphCoordinates对象,可以当作列表操作,每个元素是(x,y)元组 27 # p=font1['glyf'][i].flags #获取0、1值,实际用不到 28 for f in p: #把GlyphCoordinates对象改成一个列表 29 p1.append(f) 30 be_p1.append(p1) 31 32 33font2=TTFont('02.ttf') 34uni_list2=font2.getGlyphOrder()[1:] 35on_p1=[] 36for i in uni_list2: 37 pp1 = [] 38 p=font2['glyf'][i].coordinates 39 for f in p: 40 pp1.append(f) 41 on_p1.append(pp1) 42 43 44n2=0 45x_list=[] 46for d in on_p1: 47 n2 =1 48 n1=0 49 for a in be_p1: 50 n1 =1 51 if comp(a,d): 52 print(uni_list2[n2-1],word_list[n1-1]) 53 x_list.append(word_list[n1-1]) 54#分行打印出来,方便和FontCreator中进行比较确认 55print(x_list[:16]) 56print(x_list[16:32]) 57print(x_list[-6:])

运行结果:根据01找出02中编码和字符的对应关系,再用FontCreator打开02进行对比确认,结果OK 。

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

最后,我自己是一名从事了多年开发的Python老程序员,辞职目前在做自己的Python私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的Python学习干货,可以送给每一位喜欢Python的小伙伴,想要获取的可以关注我的头条号并在后台私信我:01,即可免费获取。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页