Python爬蟲六:字體反爬處理(貓眼+汽車之家)-2018.10

環境:Windows7 +Python3.6+Pycharm2017

目標:貓眼電影票房、汽車之家字體反爬的處理

                  --------全部文章: 京東爬蟲 、鏈家爬蟲美團爬蟲微信公衆號爬蟲字體反爬---------

前言:字體反爬,也是一種常見的反爬技術,例如貓眼電影票房,汽車之家,天眼查等網站。這些網站採用了自定義的字體文件,在瀏覽器上正常顯示,但是爬蟲抓取下來的數據要麼就是亂碼,要麼就是變成其他字符。採用自定義字體文件是CSS3的新特性,詳情參考 CSS3字體

一、貓眼電影

打開貓眼電影票房 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###

右鍵檢查,可以看到文章中的某些字符是顯示不了的,如下圖。

查找字體文件,直接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。

--------全部文章: 京東爬蟲 、鏈家爬蟲美團爬蟲微信公衆號爬蟲字體反爬---------

 參考文章:

Python爬蟲雜記 - 字體文件反爬(二)

如何去除網頁噪聲提取數據(02) —— 汽車之家(字體反爬)

反擊“貓眼電影”網站的反爬蟲策略

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章