上面讲的猫眼电影例子,是编码变化,但是字体形状不变,网上也有很多介绍的文章。而汽车之家的字体反爬,不仅是编码变化,而且是字体形状也有变化。就是说对象本身变化,不能再直接用比较对象的方法处理。网上搜也是基本没什么好的解决办法,有一种是用OCR识别,这个当然可以。下面介绍一种博主自己摸索的方法,简单试了下应该是ok的。
先看问题,打开汽车之家论坛的一篇文章,https://club.autohome.com.cn/bbs/thread/1f05b4da4448439b/76044817-1.html###
右键检查,可以看到文章中的某些字符是显示不了的,如下图。
查找字体文件,直接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中相同的对象并确认表示的字符。代码如下:
import requests from lxml import etree from fontTools.ttLib import TTFont #========================================获取汽车之家网页信息=========================================================== url='https://club.autohome.com.cn/bbs/thread/1f05b4da4448439b/76044817-1.html' headers={ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } response=requests.get(url,headers=headers) response.encoding='gb2312' html=response.text html=etree.HTML(html) content=html.xpath('//*[@id="F0"]/div[3]/div[2]/div[1]/div/div[36]/text()| //*[@id="F0"]/div[3]/div[2]/div[1]/div/div[36]/span/text()') #对新的网页信息进行处理 t=str(content).replace('\\','').replace(',','').replace("'",'').upper().replace('U','uni').replace('[','').replace(']','') print(t,len(t)) #===============================================运用新旧字体库对比来解析字体加密=========================================================================== #定义一个比较函数,比较两个列表的坐标信息是否相同 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=['uniEDED','uniED39','uniEC86','uniECD8','uniEC24','uniED65','uniEDB7','uniED03','uniED55','uniECA2', 'uniEDE2','uniEC40','uniED81','uniECCD','uniED1F','uniEC6C','uniECBD','uniEDFE','uniED4B','uniED9C','uniECE9' ,'uniEC36','uniEC87','uniEDC8','uniEC26','uniED66','uniECB3','uniED05','uniEC51','uniED92','uniEDE4' ,'uniED30','uniED82','uniECCF','uniEC1B','uniEC6D','uniEDAE','uniECFA'] word_list=['三','七','多','的','近','上','大','是','呢','更','着','矮','八','坏','五','四','十','小','地','高','和' ,'远','得','九','很','低','长','右','少','了','好','六','短','一','左','二','下','不'] font1=TTFont('qiche.ttf') #打开已经保存的字体文件 be_p1=[] #保存38个字符的(x,y)信息 for uni in u_list: p1=[] #保存一个字符的(x,y)信息 # 获取对象的x,y信息,返回的是一个GlyphCoordinates对象,可以当作列表操作,每个元素是(x,y)元组 p=font1['glyf'][uni].coordinates # p=font1['glyf'][i].flags #获取0、1值,实际用不到 for f in p: #把GlyphCoordinates对象改成一个列表 p1.append(f) be_p1.append(p1) # print(be_p1) #想要获取的数据的字体库 font2=TTFont('qiche1.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): #判断本地字体库中的字体编码是否在新的网页字体库中 if uni_list2[n2-1] in t: gg=uni_list2[n2-1] # 本地字体库中的字体编码 hh=word_list[n1-1] # print(gg,hh) #将新网页中的编码进行替换成文字 t=t.replace(gg,hh) #输出替换后的文本 print(t) # 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。
作者:Mars_DD
来源:CSDN
原文:http://www.javashuo.com/article/p-gvtafayp-up.html