Python爬蟲抓取技術的門道

程式設計師啟航發表於2019-09-21

web是一個開放的平臺,這也奠定了web從90年代初誕生直至今日將近30年來蓬勃的發展。然而,正所謂成也蕭何敗也蕭何,開放的特性、搜尋引擎以及簡單易學的html、css技術使得web成為了網際網路領域裡最為流行和成熟的資訊傳播媒介;但如今作為商業化軟體,web這個平臺上的內容資訊的版權卻毫無保證,因為相比軟體客戶端而言,你的網頁中的內容可以被很低成本、很低的技術門檻實現出的一些抓取程式獲取到,這也就是這一系列文章將要探討的話題—— 網路爬蟲

有很多人認為web應當始終遵循開放的精神,呈現在頁面中的資訊應當毫無保留地分享給整個網際網路。然而我認為,在IT行業發展至今天,web已經不再是當年那個和pdf一爭高下的所謂 “超文字”資訊載體 了,它已經是以一種 輕量級客戶端軟體 的意識形態的存在了。而商業軟體發展到今天,web也不得不面對智慧財產權保護的問題,試想如果原創的高質量內容得不到保護,抄襲和盜版橫行網路世界,這其實對web生態的良性發展是不利的,也很難鼓勵更多的優質原創內容的生產。

未授權的爬蟲抓取程式是危害web原創內容生態的一大元凶,因此要保護網站的內容,首先就要考慮如何反爬蟲。學習過程中有不懂的可以加入我們的學習交流秋秋圈784中間758後面214,與你分享Python企業當下人才需求及怎麼從零基礎學習Python,和學習什麼內容。相關學習視訊資料、開發工具都有分享

從爬蟲的攻防角度來講

最簡單的爬蟲,是幾乎所有服務端、客戶端程式語言都支援的http請求,只要向目標頁面的url發起一個http get請求,即可獲得到瀏覽器載入這個頁面時的完整html文件,這被我們稱之為“同步頁”。

作為防守的一方,服務端可以根據http請求頭中的 User-Agent來檢查客戶端是否是一個合法的瀏覽器程式,亦或是一個指令碼編寫的抓取程式,從而決定是否將真實的頁面資訊內容下發給你。

這當然是最小兒科的防禦手段,爬蟲作為進攻的一方,完全可以偽造 User-Agent欄位,甚至,只要你願意,http的get方法裡, request header的 ReferrerCookie 等等所有欄位爬蟲都可以輕而易舉的偽造。

此時服務端可以利用瀏覽器http頭指紋,根據你宣告的自己的瀏覽器廠商和版本(來自 User-Agent ),來鑑別你的http header中的各個欄位是否符合該瀏覽器的特徵,如不符合則作為爬蟲程式對待。這個技術有一個典型的應用,就是 PhantomJS 1.x版本中,由於其底層呼叫了Qt框架的網路庫,因此http頭裡有明顯的Qt框架網路請求的特徵,可以被服務端直接識別並攔截。

除此之外,還有一種更加變態的服務端爬蟲檢測機制,就是對所有訪問頁面的http請求,在 http response 中種下一個 cookie token ,然後在這個頁面內非同步執行的一些ajax介面裡去校驗來訪請求是否含有cookie token,將token回傳回來則表明這是一個合法的瀏覽器來訪,否則說明剛剛被下發了那個token的使用者訪問了頁面html卻沒有訪問html內執行js後呼叫的ajax請求,很有可能是一個爬蟲程式。

如果你不攜帶token直接訪問一個介面,這也就意味著你沒請求過html頁面直接向本應由頁面內ajax訪問的介面發起了網路請求,這也顯然證明了你是一個可疑的爬蟲。知名電商網站 Amazon就是採用的這種防禦策略。

以上則是基於服務端校驗爬蟲程式,可以玩出的一些套路手段。

基於客戶端js執行時的檢測

現代瀏覽器賦予了JavaScript強大的能力,因此我們可以把頁面的所有核心內容都做成js非同步請求 ajax 獲取資料後渲染在頁面中的,這顯然提高了爬蟲抓取內容的門檻。依靠這種方式,我們把對抓取與反抓取的對抗戰場從服務端轉移到了客戶端瀏覽器中的js執行時,接下來說一說結合客戶端js執行時的爬蟲抓取技術。

剛剛談到的各種服務端校驗,對於普通的python、java語言編寫的http抓取程式而言,具有一定的技術門檻,畢竟一個web應用對於未授權抓取者而言是黑盒的,很多東西需要一點一點去嘗試,而花費大量人力物力開發好的一套抓取程式,web站作為防守一方只要輕易調整一些策略,攻擊者就需要再次花費同等的時間去修改爬蟲抓取邏輯。

此時就需要使用headless browser了,這是什麼技術呢?其實說白了就是,讓程式可以操作瀏覽器去訪問網頁,這樣編寫爬蟲的人可以通過呼叫瀏覽器暴露出來給程式呼叫的api去實現複雜的抓取業務邏輯。

其實近年來這已經不算是什麼新鮮的技術了,從前有基於webkit核心的 PhantomJS,基於Firefox瀏覽器核心的 SlimerJS,甚至基於IE核心的 trifleJS,有興趣可以看看 這裡這裡 是兩個headless browser的收集列表。

這些headless browser程式實現的原理其實是把開源的一些瀏覽器核心C++程式碼加以改造和封裝,實現一個簡易的無GUI介面渲染的browser程式。但這些專案普遍存在的問題是,由於他們的程式碼基於fork官方webkit等核心的某一個版本的主幹程式碼,因此無法跟進一些最新的css屬性和js語法,並且存在一些相容性的問題,不如真正的release版GUI瀏覽器執行得穩定。

這其中最為成熟、使用率最高的應該當屬 PhantonJS 了,對這種爬蟲的識別我之前曾寫過 一篇部落格,這裡不再贅述。PhantomJS存在諸多問題,因為是單程式模型,沒有必要的沙箱保護,瀏覽器核心的安全性較差。另外,該專案作者已經宣告停止維護此專案了。

如今Google Chrome團隊在Chrome 59 release版本中開放了 headless mode api,並開源了一個基於Node.js呼叫的 headless chromium dirver庫,我也為這個庫貢獻了一個 centos環境的部署依賴安裝列表

Headless Chrome可謂是Headless Browser中獨樹一幟的大殺器,由於其自身就是一個chrome瀏覽器,因此支援各種新的css渲染特性和js執行時語法。

基於這樣的手段,爬蟲作為進攻的一方可以繞過幾乎所有服務端校驗邏輯,但是這些爬蟲在客戶端的js執行時中依然存在著一些破綻,諸如:

基於plugin物件的檢查

if(navigator.plugins.length === 0) {
    console.log('It may be Chrome headless');
}

基於language的檢查

if(navigator.languages === '') {
    console.log('Chrome headless detected');
}

基於webgl的檢查

var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl');
var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
var vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
var renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
if(vendor == 'Brian Paul' && renderer == 'Mesa OffScreen') {
    console.log('Chrome headless detected');
}

基於瀏覽器hairline特性的檢查

if(!Modernizr['hairline']) {
    console.log('It may be Chrome headless');
}

基於錯誤img src屬性生成的img物件的檢查

var body = document.getElementsByTagName('body')[0];
var image = document.createElement('img');
image.src = 'http://iloveponeydotcom32188.jg';
image.setAttribute('id', 'fakeimage');
body.appendChild(image);
image.onerror = function(){
    if(image.width == 0 && image.height == 0) {
        console.log('Chrome headless detected');
    }
}

基於以上的一些瀏覽器特性的判斷,基本可以通殺市面上大多數 Headless Browser 程式。在這一層面上,實際上是將網頁抓取的門檻提高,要求編寫爬蟲程式的開發者不得不修改瀏覽器核心的C++程式碼,重新編譯一個瀏覽器,並且,以上幾點特徵是對瀏覽器核心的改動其實並不小,如果你曾嘗試過編譯 Blink核心Gecko核心你會明白這對於一個“指令碼小子”來說有多難~

更進一步,我們還可以基於瀏覽器的 UserAgent 欄位描述的瀏覽器品牌、版本型號資訊,對js執行時、DOM和BOM的各個原生物件的屬性及方法進行檢驗,觀察其特徵是否符合該版本的瀏覽器所應具備的特徵。

這種方式被稱為 瀏覽器指紋檢查 技術,依託於大型web站對各型號瀏覽器api資訊的收集。而作為編寫爬蟲程式的進攻一方,則可以在 Headless Browser 執行時裡預注入一些js邏輯,偽造瀏覽器的特徵。

另外,在研究瀏覽器端利用js api進行 Robots Browser Detect 時,我們發現了一個有趣的小技巧,你可以把一個預注入的js函式,偽裝成一個 Native Function,來看看下面程式碼:

var fakeAlert = (function(){}).bind(null);
console.log(window.alert.toString()); // function alert() { [native code] }
console.log(fakeAlert.toString()); // function () { [native code] }

爬蟲進攻方可能會預注入一些js方法,把原生的一些api外面包裝一層proxy function作為hook,然後再用這個假的js api去覆蓋原生api。如果防禦者在對此做檢查判斷時是基於把函式 toString之後對 [native code]的檢查,那麼就會被繞過。所以需要更嚴格的檢查,因為 bind(null)偽造的方法,在 toString之後是不帶函式名的,因此你需要在 toString之後檢查函式名是否為空。

這個技巧有什麼用呢?這裡延伸一下,反抓取的防禦者有一種 Robot Detect的辦法是在js執行時主動丟擲一個 alert,文案可以寫一些與業務邏輯相關的,正常的使用者點確定按鈕時必定會有一個1s甚至更長的延時,由於瀏覽器裡 alert會阻塞js程式碼執行(實際上在 v8裡他會把這個 isolate上下文以類似程式掛起的方式暫停執行),所以爬蟲程式作為攻擊者可以選擇以上面的技巧在頁面所有js執行以前預注入一段js程式碼,把 alertpromptconfirm等彈窗方法全部hook偽造。如果防禦者在彈窗程式碼之前先檢驗下自己呼叫的 alert方法還是不是原生的,這條路就被封死了。

反爬蟲的銀彈

目前的反抓取、機器人檢查手段,最可靠的還是 驗證碼技術。但驗證碼並不意味著一定要強迫使用者輸入一連串字母數字,也有很多基於使用者滑鼠、觸屏(移動端)等行為的 行為驗證技術,這其中最為成熟的當屬Google reCAPTCHA,基於機器學習的方式對使用者與爬蟲進行區分。

基於以上諸多對使用者與爬蟲的識別區分技術,網站的防禦方最終要做的是 封禁ip地址或是對這個ip的來訪使用者施以高強度的驗證碼策略。這樣一來,進攻方不得不購買ip代理池來抓取網站資訊內容,否則單個ip地址很容易被封導致無法抓取。抓取與反抓取的門檻被提高到了 ip代理池經濟費用的層面。學習過程中有不懂的可以加入我們的學習交流秋秋圈784中間758後面214,與你分享Python企業當下人才需求及怎麼從零基礎學習Python,和學習什麼內容。相關學習視訊資料、開發工具都有分享

機器人協議

除此之外,在爬蟲抓取技術領域還有一個“白道”的手段,叫做 robots協議。 AllowDisallow宣告瞭對各個UA爬蟲的抓取授權。

不過,這只是一個君子協議,雖具有法律效益,但只能夠限制那些商業搜尋引擎的蜘蛛程式,你無法對那些“野爬愛好者”加以限制。

寫在最後

對網頁內容的抓取與反制,註定是一個魔高一尺道高一丈的貓鼠遊戲,你永遠不可能以某一種技術徹底封死爬蟲程式的路,你能做的只是提高攻擊者的抓取成本,並對於未授權的抓取行為做到較為精確的獲悉。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913713/viewspace-2657831/,如需轉載,請註明出處,否則將追究法律責任。

相關文章