字体反扒 ---汽车之家(文字)

上面讲的猫眼电影例子,是编码变化,但是字体形状不变,网上也有很多介绍的文章。而汽车之家的字体反爬,不仅是编码变化,而且是字体形状也有变化。就是说对象本身变化,不能再直接用比较对象的方法处理。网上搜也是基本没什么好的解决办法,有一种是用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