在上一篇Node 爬蟲入門已經介紹過最簡單的 Node 爬蟲實現,本文在原先的基礎上更進一步,探討一下如何繞過登入,爬取登入區內的資料
目錄
一、理論基礎
如何維持登入態
Http 作為一種無狀態的協議,客戶端和伺服器端之間不會保持長連線。在一個一個相互獨立的請求響應之間,伺服器如何識別哪些介面是來自同一個客戶端?聰明的你,很容易想到如下一種機制:
這種機制的核心在於會話id(sessionId):
- 當客戶端請求伺服器端的時候,服務端判斷該客戶端沒有傳入sessionId,好的,這傢伙是新來的,給它生成一個sessionId,存入記憶體,並把這個sessionId返回客戶端
- 客戶端拿到伺服器端的sessionId儲存在本地,下次請求的時候帶上這個seesionId,伺服器檢查記憶體是否存在這個sessionId(如果在之前的某個步驟,使用者訪問了登入介面,那麼此刻記憶體中已經以seesionId為key,使用者資料為value儲存在了記憶體中),伺服器就可以根據sessionId這個唯一標識,返回該客戶端對應的資料
- 無論是客戶端還是伺服器端丟失了這個sessionId都會導致前面的步驟重新來過,誰也不認識誰了,重新開始
首先客戶端通過sessionId和伺服器端建立一種關聯,然後使用者再通過客戶端與伺服器端建立一種關聯(sessionId與使用者資料的鍵值對),從而維持了登入態
瀏覽器是怎麼做的
實際上,瀏覽器是不是按照上述的機制設計的呢?還真是!
這其中瀏覽器做了哪些事情:
一、 瀏覽器在每一次http請求中,都會在http的請求頭中加上該請求地址域名對應的cookie(如果cookie沒有被使用者禁用的話),在上圖中,第一個次請求伺服器請求頭中同樣有cookie,只是cookie中還沒有sessionId
二、 瀏覽器根據伺服器響應頭中的Set-Cookie設定cookie,為此,伺服器會將生成的sessionId放入Set-cookie中
瀏覽器接收到Set-Cookie指令,就會以請求地址的域名為key設定本地cookie,一般情況下,伺服器在返回Set-cookie的時候,對sessionId的過期時間預設設定為瀏覽器關閉時失效,這就是瀏覽器從開啟到關閉就是一次會話的由來(有些網站還可以設定保持登入,設定cookie長時間不失效爾爾)
三、 當瀏覽器再次向後臺發起請求時,此時請求頭中的cookie已經包含了sessionId,如果在此之前使用者已經訪問過登入介面,那麼就已經可以根據sessionId來查詢到使用者資料了
口說無憑,下面就以簡書為例說明:
1). 首先用 Chrome 開啟簡書的登入頁面,在 Application 中找到 http://www.jianshu.com
下的所 Cookie,進入 Network 項中把 `Preserve log 勾選上(不然頁面發生了重定向之後將無法看到之前的 log)
2). 然後重新整理頁面,找到 Sign-in 介面,它的響應頭中有很多 Set-Cookie 有木有
3). 再去檢視 Cookie 的時候,Session-id 已經儲存好了,下次再去請求簡書的其它介面的時候(例如獲取驗證碼、登入),都會帶上這個 Session-id,登入後使用者的資訊也會跟 Session-id 關聯起來
二、node實現
我們需要模擬瀏覽器的工作方式,去爬去網站登入區內的資料
找了一個沒有驗證碼的網站進行試驗,有驗證碼的又要涉及到驗證碼識別(簡書的登入就不考慮了,驗證碼複雜程度感人),下節說明
訪問登入介面獲取cookie
// 瀏覽器請求報文頭部部分資訊
var browserMsg={
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
'Content-Type':'application/x-www-form-urlencoded'
};
//訪問登入介面獲取cookie
function getLoginCookie(userid, pwd) {
userid = userid.toUpperCase();
return new Promise(function(resolve, reject) {
superagent.post(url.login_url).set(browserMsg).send({
userid: userid,
pwd: pwd,
timezoneOffset: '0'
}).redirects(0).end(function (err, response) {
//獲取cookie
var cookie = response.headers["set-cookie"];
resolve(cookie);
});
});
}複製程式碼
需要現在 Chrome 下捕獲一次請求,獲取一些請求頭的資訊,因為伺服器可能會對這些請求頭資訊進行校驗。例如,在我實驗的網站上,起初我並沒有傳入 User-Agent,伺服器發現並不是來自伺服器的請求,返回了一串錯誤資訊,所以我之後設定 User-Agent,把自己偽裝成 Chrome瀏覽器了~~
Superagent是一個 client-side Http Request庫,使用它可以跟輕鬆的傳送請求,處理 Cookie(自己呼叫 Http.request 在操作 Header 欄位資料上就沒有這麼方便,獲得 Set-cookie 之後,還得自己拼裝成合適的格式 Cookie)。
redirects(0)
主要是設定不進行重定向
請求登入區內介面
function getData(cookie) {
return new Promise(function(resolve, reject) {
//傳入cookie
superagent.get(url.target_url).set("Cookie",cookie).set(browserMsg).end(function(err,res) {
var $ = cheerio.load(res.text);
resolve({
cookie: cookie,
doc: $
});
});
});
}複製程式碼
在上一步中拿到 Set-cookie 之後,傳入 getData
方法,在通過 Superagent 設定到請求當中(Set-cookie會格式化成 Cookie),就可以正常拿到登入去內的資料
在實際的場景中,未必會如此順利,因為不同的網站有不同的安全措施,例如:有些網站可能需要先請求一個 Token ,有些網站需要對引數進行加密處理,有些安全性更高的,還有防重放機制。在定向爬蟲中,這需要具體的去分析網站的處理機制,如果繞不過去,就適可而止吧~~
但是對付一般內容資訊類的網站還是夠用的
通過以上方式請求到的只是一段 Html 字串,這裡還是老辦法,使用 Cheerio 庫將字串載入,就可以拿到一個類似於 Jquery dom 的物件,就可以像 Jquery 一樣去操作 dom,這真的是一個神器,良心製作!
三、如果有驗證碼怎麼破
現在不需要輸驗證碼就可以登入的網站還有幾個?當然我們就不企圖去識別12306的驗證碼了,簡書這種良心之作驗證碼也不奢望了,像知乎這種 too young too simple 的驗證碼還是可以挑戰下的
如果非要識別這些複雜的驗證碼也不是沒有辦法,花錢買專業打碼軟體的服務(搶票黃牛就是這麼幹的),畢竟人家是專業的
Tesseract 是google開源的OCR識別工具,雖然跟node沒有什麼關係,但是可以用node來排程使用,具體使用方式:用node.js實現驗證碼簡單識別
然而即便是使用graphicsmagick來對圖片進行預處理,也不能保證有很高的識別率,為此還可以對tesseract進行訓練,參考:利用jTessBoxEditor工具進行Tesseract3.02.02樣本訓練,提高驗證碼識別率
能不能做到高識別率,就看人品了~~~
四、延伸
還有一種更簡單的方式去繞過登入態,就是使用PhantomJS,phantomjs是一個基於webkit開源的伺服器js api,可以認為它就是一個瀏覽器,只是你可以通過js指令碼來操控它。
由於其完全模擬瀏覽器的行為,所以你根本不需要關心set-cookie,cookie的事情,只需要模擬使用者的點選操作就可以了(當然如果有驗證碼,還是得去識別的)
這種方式也並非毫無缺點,完全模擬瀏覽器的行為,意味者不放過任何一個請求,需要載入可能你並不需要的js、css等靜態資源,需要點選多個頁面才能到達目的頁面,在效率上要比直接訪問target url要低
感興趣自行搜尋
phontomJS
五、總結
雖然說的是node爬蟲的登入,但是前面原理講了一大堆,目的是如果你想換一種語言實現,也可以遊刃有餘,還是那句話:理解原理很重要
乾貨不斷,期待您的關注~~