爬蟲高級內(nèi)容,Python如何應對字體反爬
前言
在編寫網(wǎng)頁爬蟲的時候,我們經(jīng)常會遇到各種反爬,比如js加密、css位置偏移、字體反爬等。最近在學習過程中,我碰到幾個網(wǎng)站,都是使用的字體反爬,其大致上可以分為兩類:一類是字體文件是靜態(tài)的,另一類字體文件是動態(tài)的變化的。今天,我們探究一下,在爬蟲中如何破解字體反爬。
一、如何尋找字體文件
字體文件大致上分兩種,一種是以.ttf結(jié)尾的,一種是以.woff結(jié)尾的,兩者相差不大,現(xiàn)在的網(wǎng)站運用的大多是woff文件。如何尋找網(wǎng)頁使用的字體文件,可以分兩類:
1.第一類 字體URL在網(wǎng)頁源代碼中
以aHR0cHM6Ly93d3cucmVucmVuY2hlLmNvbS9iai9lcnNob3VjaGUvP3Bsb2dfaWQ9NWI5NzA3MWI1YjVhNWZkYWRkOWZhZDEzMGE0MmJiMDM=網(wǎng)站為例:
打開網(wǎng)站后,明顯能看到,8被替換成了6。這種情況,我們可以嘗試在網(wǎng)頁源代碼中搜索“font-face”,查看字體URL。

2.第二類 字體URL不在網(wǎng)頁源代碼中,由服務器加載
以aHR0cHM6Ly93d3cuZ3VhemkuY29tL2J1eQ==網(wǎng)站為例:
這里可以看到車輛的行使里程數(shù)和首付均是字體反爬,按照第一種情況,我們直接在網(wǎng)頁源代碼中搜索“font-face”
可以看到,也是有woff文件和ttf文件,但這兩個文件都是“element-icons”的鏈接,打開(軟件:FontCreator)后也就是一堆圖標。
遇到這種的,可以打開瀏覽器Network,刷新后抓包。
可以看到這個請求返回的結(jié)果中有一個woff文件,打開后觀察:

這個正是我們要找的字體文件,當然這個網(wǎng)站要拿到字體URL需要js逆向verify-token這個參數(shù),這里不展開說。
二、如何應對靜態(tài)字體反爬
還是以aHR0cHM6Ly93d3cucmVucmVuY2hlLmNvbS9iai9lcnNob3VjaGUvP3Bsb2dfaWQ9NWI5NzA3MWI1YjVhNWZkYWRkOWZhZDEzMGE0MmJiMDM=網(wǎng)站為例,經(jīng)過觀察,他的字體是每天更換一次,暫且當作是靜態(tài)。
1.在網(wǎng)頁源代碼中搜索字體文件的URL,復制到瀏覽器中打開,下載字體文件。
2.用FontCreator打開文件,查看字體對應關(guān)系,手動建立對應關(guān)系字典。
#?字體對應關(guān)系
relation_table?=?{"zero":?"0",?"one":?"2",?"two":?"1",?"four":?"3",?"three":?"4",?"five":?"8",?"seven":?"7","nine":?"9",?"six":?"6",?"eight":?"5"}
這是我學習過程中自己手動建立的對應關(guān)系。
3.請求字體鏈接,獲取字體code和name的對應關(guān)系,然后遍歷,獲取網(wǎng)頁中反爬文字的真實文字。
def?woff_font(font_url):
????'''獲取字體真實對應關(guān)系'''
????newmap?=?{}
????resp?=?session.get(font_url)??#?請求字體鏈接
????woff_data?=?BytesIO(resp.content)
????font?=?TTFont(woff_data)??#?讀取woff數(shù)據(jù)
????cmap?=?font.getBestCmap()??#?獲取字體對應關(guān)系
????font.close()
????for?k,?v?in?cmap.items():
????????value?=?v
????????key?=?str(k?-?48)??#?獲取真實的key
????????try:
????????????get_real_data?=?relation_table[value]
????????except:
????????????get_real_data?=?''
????????if?get_real_data?!=?'':
????????????newmap[key]?=?get_real_data??#?將字體真實結(jié)果對應
????return?newmap
4.替換網(wǎng)頁中的反爬文字
如果反爬字體是10進制或16進制的建議直接替換網(wǎng)頁代碼中的字體,如果是數(shù)字的,建議逐項替換。替換的方式有用正則表達式的(這里不說,因為我正則寫的也不好),也有用列表推導式的,比如:
title?=?"".join(html.xpath('//*[@id="zhimaicar-detail-header-right"]/div[1]/h1/text()')).strip()??#?獲取原始標題
trans_title?=?"".join([i?if?not?i.isdigit()?else?font[i]?for?i?in?title])??#?替換錯誤字體,獲取真實標題
具體的用法,可以百度。
三、如何應對動態(tài)字體反爬
動態(tài)字體反爬,就是每請求一次(或一段時間后),字體的映射關(guān)系也會改變,因為第二個網(wǎng)站比較麻煩,這個還是以這個網(wǎng)站為例,將時間節(jié)點放大,把他看做是動態(tài)反爬的。
1.先下載字體文件
2.安裝fontTools
安裝后用
from?fontTools.ttLib?import?TTFont
font?=?TTFont(下載的字體文件)
font.saveXML(文件名.xml)
將字體文件另存為xml文件,打開文件后會看到:
‘cmap’里存放的是字體code和name的關(guān)系

‘glyf’里存放的是字體形狀和name的關(guān)系
3.手動建立字體形狀和name的關(guān)系
只需手動建立一次。
f_font?=?TTFont("rrcttf3d6e374d48fb2cd14247257d4ae76674.woff")??#?讀取分析的字體文件
f_font_glyf?=?f_font['glyf']??#?獲取分析文件中的字體關(guān)系
#?建立基礎的字體和字體形狀的對應關(guān)系
base_font_map?=?{
????0:?f_font_glyf['zero'],
????1:?f_font_glyf['two'],
????2:?f_font_glyf['one'],
????3:?f_font_glyf['four'],
????4:?f_font_glyf['three'],
????5:?f_font_glyf['eight'],
????6:?f_font_glyf['six'],
????7:?f_font_glyf['seven'],
????8:?f_font_glyf['five'],
????9:?f_font_glyf['nine'],
}
4.請求網(wǎng)頁中的字體URL,獲取code和name,形狀和name的關(guān)系
resp?=?session.get(font_url)??#?請求字體鏈接
woff_data?=?BytesIO(resp.content)??#?保存字體數(shù)據(jù)
font?=?TTFont(woff_data)??#?讀取woff數(shù)據(jù)
glyf?=?font['glyf']??#?獲取請求到的字體形狀
code_name_map?=?font.getBestCmap()??#?獲取請求到的字體code和name的對應關(guān)系
font.close()
5.根據(jù)形狀相同,肯定字體相同的原則,獲取真實對應關(guān)系
for?code,?name?in?code_name_map.items():
????codestr?=?str(code?-?48)??#?根據(jù)分析結(jié)果需要減去48
????current_shape?=?glyf[name]??#?根據(jù)name獲取字體形狀
????for?number,?shape?in?base_font_map.items():??#?遍歷基礎字體形狀對應關(guān)系
????????if?shape?==?current_shape:??#?判斷,如果兩個字體形狀相等
????????????newmap[codestr]?=?str(number)??#?將字體編碼和字體添加到字典
6.完整代碼
def?woff_font(font_url):
????newmap?=?{}
????f_font?=?TTFont("rrcttf3d6e374d48fb2cd14247257d4ae76674.woff")??#?讀取分析的字體文件
????f_font_glyf?=?f_font['glyf']??#?獲取分析文件中的字體關(guān)系
????#?建立基礎的字體和字體形狀的對應關(guān)系
????base_font_map?=?{
????????0:?f_font_glyf['zero'],
????????1:?f_font_glyf['two'],
????????2:?f_font_glyf['one'],
????????3:?f_font_glyf['four'],
????????4:?f_font_glyf['three'],
????????5:?f_font_glyf['eight'],
????????6:?f_font_glyf['six'],
????????7:?f_font_glyf['seven'],
????????8:?f_font_glyf['five'],
????????9:?f_font_glyf['nine'],
????}
????resp?=?session.get(font_url)??#?請求字體鏈接
????woff_data?=?BytesIO(resp.content)??#?保存字體數(shù)據(jù)
????font?=?TTFont(woff_data)??#?讀取woff數(shù)據(jù)
????glyf?=?font['glyf']??#?獲取請求到的字體形狀
????code_name_map?=?font.getBestCmap()??#?獲取請求到的字體code和name的對應關(guān)系
????font.close()
????for?code,?name?in?code_name_map.items():
????????codestr?=?str(code?-?48)??#?根據(jù)分析結(jié)果需要減去48
????????current_shape?=?glyf[name]??#?根據(jù)name獲取字體形狀
????????for?number,?shape?in?base_font_map.items():??#?遍歷基礎字體形狀對應關(guān)系
????????????if?shape?==?current_shape:??#?判斷,如果兩個字體形狀相等
????????????????newmap[codestr]?=?str(number)??#?將字體編碼和字體添加到字典
????return?newmap
7.結(jié)果驗證
當我在寫這篇分享的時候,該網(wǎng)站的字體早已經(jīng)換了,但是建立關(guān)系的時候我還用的是之前的文件,可見返回的結(jié)果也是沒有問題的。



四、總結(jié)
靜態(tài)字體反爬不用多說,面對動態(tài)反爬,一開始我也有點懵,后來在翻閱了一些資料和別人的啟發(fā)后才弄明白,其實就是利用形狀一樣,字體肯定也一樣的對應關(guān)系去破解。這是我學習過程中的一點經(jīng)驗總結(jié),有不足之處還請各位指正,謝謝。
最后,推薦螞蟻老師的視頻套餐課程,包含爬蟲部分,給我很大的幫助:
