1. 程式人生 > >Python爬蟲六:字型反爬處理(貓眼+汽車之家)-2018.10

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

環境:Windows7 +Python3.6+Pycharm2017

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

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

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

一、貓眼電影

點選上圖右上角的Sources,把這個html文件下載下來,在編輯器開啟,就可以看到這些方框對應的是一個個編碼,這些編碼是自定義的,所以用utf-8編碼方式是顯示不出來的。瀏覽器顯示的時候因為採用了自定義的字型檔案,所以顯示正常。

我們來找一下這個字型檔案 ,在html頁面中搜索關鍵字:font-face,找到如下內容。一大串字串,從base64後面開始一直到後面format前面的括號中的內容,應該是字型檔案的內容。是經過了base64編碼後的形式。把這一段字串考出來,用base64解碼後再儲存成本地ttf檔案(ttf是字型的一種型別)。

 處理程式碼如下,先解碼,再儲存成本地檔案 zt02.ttf:

import base64

font_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'

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的。

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

查詢字型檔案,直接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。

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

 參考文章: