0×01 前言
前兩天在百家號上看到一篇名為《反擊爬蟲,前端工程師的腦洞可以有多大?》的文章,文章從多方面結合實際情況列舉了包括貓眼電影、美團、去哪兒等大型電商網站的反爬蟲機制。的確,如文章所說,對於一張網頁,我們往往希望它是結構良好,內容清晰的,這樣搜索引擎才能准確地認知它;而反過來,又有一些情景,我們不希望內容能被輕易獲取,比方說電商網站的交易額,高等學校網站的題目等。因為這些內容,往往是一個產品的生命線,必須做到有效地保護。這就是爬蟲與反爬蟲這一話題的由來。本文就以做的較好的“貓眼電影”網站為例,搞定他的反爬蟲機制,輕松爬去我們想要的數據!
0×02 常見反爬蟲
從功能上來講,爬蟲一般分為數據采集,處理,儲存三個部分。而作為程序員的我們只關心數據采集部分,處理什么的還是交給那些數據分析師去搞吧。
一般來說,大多數網站會從三個方面反爬蟲:用戶請求的Headers,用戶行為,網站目錄和數據加載方式。前兩種比較容易遇到,大多數網站都從這些角度來反爬蟲,而第三種則相對比較特殊,一些應用ajax的網站會采用,這樣無疑會增大了爬蟲爬取的難度。
然而,這三種反爬蟲策略則早已有應對的方法和策略。如果遇到了從用戶請求的Headers反爬蟲機制,可以直接在爬蟲中添加Headers,將瀏覽器的User-Agent復制到爬蟲的Headers中;或者將Referer值修改為目標網站域名。對於檢測Headers的反爬蟲,在爬蟲中修改或者添加Headers就能很好的繞過。對於基於用戶行為的反爬蟲其實就是通過限制同一IP短時間內多次訪問同一頁面,應對策略也是很粗暴——使用IP代理,可以專門寫一個爬蟲,爬取網上公開的代理ip,檢測后全部保存起來。有了大量代理ip后可以每請求幾次更換一個ip,即可繞過這種反爬蟲機制。對於最后一種動態頁面反爬蟲機制來講,selenium+phantomJS框架能夠讓你在無界面的瀏覽器中模擬加載網頁的動態請求,畢竟selenium可是自動化滲透的神器。
0×03 貓眼反爬蟲介紹
介紹完常見的反爬蟲機制,我們回過頭看看我們今天的主角:貓眼電影的反爬蟲是什么樣的。
對於每日的電影院票價這一重要數據,源代碼中展示的並不是純粹的數字。而是在頁面使用了font-face定義了字符集,並通過unicode去映射展示。簡單介紹下這種新型的web-fongt反爬蟲機制:使用web-font可以從網絡加載字體,因此我們可以自己創建一套字體,設置自定義的字符映射關系表。例如設置0xefab是映射字符1,0xeba2是映射字符2,以此類推。當需要顯示字符1時,網頁的源碼只會是0xefab,被采集的也只會是 0xefab,並不是1:
因此采集者采集不到正確的票價數據:
采集者只能獲取到類似的數據,並不能知道””映射的字符是什么,實現了數據防采集。而對於正常訪問的用戶則沒有影響,因為瀏覽器會加載css中的font字體為我們渲染好,實時顯示在網頁中。也就是說,除去圖像識別,必須同時爬取字符集,才能識別出數字。
查看貓眼的網站源文件正是如此:
所有的票價信息都是由動態font字體“加密”后得到的。既然知道了原理,我們就繼續發掘,通過分析網站HTML結構,我們發現網站每次渲染票價的font字體都可以在網頁的script標簽中被找到:
字體是由base64加密后存儲在網頁中的,於是乎,上python:
#將base64加密的font文件解密轉存本地 font = re.findall(r"src: url\(data:application/font-woff;charset=utf-8;base64,(.*?)\) format",response_all)[0] fontdata=base64.b64decode(font) file=open('/home/jason/workspace/1.ttf','wb') file.write(fontdata) file.close()
我們在爬取時將font文件解密后存儲在本地存儲為ttf文件,留做備用。
前文提到過這種web-font定義了字符集,要通過unicode去映射展示,所以,我們要構建ttf字體文件中unicode映射出來的字符字典:
python代碼:
import fontforge def tff2Unicode():#將字體映射為unicode列表 filename = '/home/jason/workspace/1.ttf' fnt = fontforge.open(filename) for i in fnt.glyphs(): print i.unicode
我們猜測映射關系如下:
還記得嘛,第三張圖我們爬取到的數據是“綉春刀·修羅戰場 341189 2017-07-20 6號廳 2D 國語 11:10 ”,我們將“&#”替換成“0”后對應上表得出的票價不是剛好是“29”嘛!
python代碼:
tmp_dic={}
ttf_list = []
def creatTmpDic():#創建映射字典 tmp_dic={} ttf_list = [] num_list = [-1,-1,0,1,2,3,4,5,6,7,8,9] filename = '/home/jason/workspace/1.ttf' fnt = fontforge.open(filename) ttf_list = [] for i in fnt.glyphs(): ttf_list.append(i.unicode) tmp_dic = dict(zip(ttf_list,num_list))#構建字典 return tmp_dic,ttf_list def tff2price(para = ";",tmp_dic={},ttf_list = []):#將爬取的字符映射為字典中的數字 tmp_return = "" for j in para.split(";"): if j != "": ss = j.replace("&#","0") for g in ttf_list: if (hex(g) == ss): tmp_return+=str(tmp_dic[g]) return tmp_return
好的,到此,我們已經可以說已經完成了對票價“加密”數據的破解啦~還是有點小小的成就感呢!但是,這里面還是有個很坑的地方:開發者已經想到采集者可以通過分析,知道每一個映射代表的意思,從而進行采集后轉換處理,所以我們每次訪問都是隨機得到一種字體,而且開發者還定期更新一批字體文件和映射表用來加大采集的難度,所以我們在采集的過程中不得不每采集一個頁面就更新一次本地的該網頁的web-font字體,無疑會大大增加爬蟲的爬取成本和爬取效率,所以從一定意義上確實實現了反爬蟲。
參考文獻:
http://blog.csdn.net/fdipzone/article/details/68166388
https://baijiahao.baidu.com/s?id=1572788572555517&wfr=spider&for=pc
https://zhuanlan.zhihu.com/p/20520370?columnSlug=python-hacker