用python實現模擬登錄人人網
我決定從頭說起。懂的人可以快速略過前面理論看最后幾張圖。
web基礎知識
從OSI參考模型(從低到高:物理層,數據鏈路層,網絡層,傳輸層,會話層,表示層,應用層)來說,我們的互聯網屬于應用層。從TCP/IP參考模型(從低到高:物理層,數據鏈路層,網絡層,傳輸層,應用層)來說,也同樣如此。
互聯網上有各種各樣的資源,包括文本、圖片、音頻、視頻……
通常所見的Web模型需要包括兩部分:客戶端,服務器。個人電腦上的瀏覽器就是一種客戶端,而保存我們輸入的網址所有相關資源的就是服務器。
客戶端通過URL(Uniform Resource Locator,統一資源定位符)來鏈接至服務器。
http://www.abcd.com/page.html
通常一個URL如上所示,http是協議名(http、ftp、telnet……),www.abcd.com是頁面所在機器的DNS名,page.html是頁面文件的名字。
內部細節不表,總之我們在瀏覽器中輸入url,瀏覽器通過url與服務器經過復雜溝通協調建立聯系(TCP、UDP……),將數據從服務器發至瀏覽器,我們能夠通過瀏覽器看到最終拿到的資源。
通常我們看到的web頁面,是使用HTML的語言來編寫的。上面除了有之前提到的各種資源外,還有超鏈接。
Cookie
新的需求出現了:服務器需要能夠認識客戶端,比如客戶需要登錄、需要身份識別、需要權限識別、需要依據不同客戶加載不同內容……
怎么辦?當客戶端向服務器端請求一個web頁面時,服務器端除了提供此頁面外,還會提供一些附加信息,其中包括cookie,客戶端會將cookie存儲在客戶機器的本地磁盤上。
當瀏覽器向該服務器端發起請求時,瀏覽器會檢查它的cookie,確定所請求的目標域是否有存cookie,如果有,會將該cookie包含到請求消息(request)中。服務器得到cookie后,就可以進行識別操作了。
一個cookie可以保函至多5個域:Domain、Path、Content、Expires、Secure。
- Domain:指示cookie來自什么地方,即域名。每個域至多在客戶端存儲20個cookie。
- Path:服務器目錄結構中的一個路徑,表示服務器文件樹的哪些部分可能會用到該cookie。通常是/,表示整個文件樹。
- Content:采用"name = value"的形式,即鍵值對,熟悉python的話可以得知Dictionary使用相同結構。這是cookie內容存放之處。
- Expires:過期時間。如果此域不存在的話,瀏覽器在退出時會將cookie丟棄。否則會一直在客戶端保存到過期為止。所以如果服務器想將客戶端的cookie刪除,只需要再將它發送一次,并且選擇一個已經過去的時間作為Expires。
- Secure:指示瀏覽器只向安全的服務器(https等等)才返回該cookie。
到這里,我們知道了,后續每次連接人人網的時候,都需要一個正確的cookie來表明我們的身份。
靜態和動態Web文檔
按照之前提到的模型,客戶端向服務器端發出Request,服務器端返回Response,一次完成,不管傳輸的是HTML格式、還是XML格式、還是……這種都算是靜態Web文檔,過程非常簡單。
然而,社會在進步,需求在增加。越來越多的內容需要根據實際情況來生產,并且內容的生成既可以發生在服務器端,也可發生在客戶端。
-
服務器端動態Web頁面
用戶可以在客戶端提交表單(Form)給服務器端,服務器端需要根據表單內容來返回正確的Web頁面。
有兩種處理表單或類似Request的方法。
傳統方法是CGI系統,允許服務器與后端程序及腳本通信,通常用Perl或Python編寫。簡單的模型是:用戶,瀏覽器,服務器,CGI腳本,磁盤上的數據庫,以上幾個呈鏈型。
另外一種方法是在HTML頁面中嵌入少量腳本,讓服務器來執行這些腳本以便生成最終發送給客戶的頁面。通常用PHP、JSP、ASP等。簡單的模型是:用戶,瀏覽器,服務器,而PHP則作為一個模塊存在于服務器內。 -
客戶端動態Web頁面
然而CGI、PHP、JSP、ASP這種在部署在服務器端的腳本并不能夠對用戶的鼠標移動事件進行響應,或者直接與用戶交互。
現在經常會談到“互聯網服務,交互界面要友好”,想要達成這一目的,需要在客戶端能夠實時依據用戶的動作來處理,現在最流行的應該是Javascript了吧。簡單的模型依舊是:用戶,瀏覽器,服務器,只不過在瀏覽器多了一個Javascript的模塊。
這兩種動態web語言并沒有誰優誰劣的區分,只不過用途不同罷了。前者就是我們常說的后端,后者就是我們常說的前端。
HTTP——超文本傳輸協議
前面提到,URL的第一個部分是協議名,我們用的比較多的是http協議。
Request
客戶端到服務器的消息被稱作請求(Request),需要包括應用于資源的方法、資源的標識符和協議的版本號。
HTTP的Request包括幾種訪問方法:GET/HEAD/PUT/POST/DELETE/TRACE/CONNECT/OPTIONS。
-
GET方法請求最常用,直接以鏈接形式訪問,即鏈接中包含了所有參數。速度快,內容量小,不安全。
-
POST方法經常和GET進行比較,它是向服務器發送請求,要求服務器接受位于請求后的實體,依據實體內容處理后返回相關內容。速度慢,內容量大,安全。
However,不再以訛傳訛,GET和POST的真正區別在這篇博客中,作者查證HTTP協議后說:
在HTTP協議中,Method和Data(URL, Body, Header)是正交的兩個概念,也就是說,使用哪個Method與應用層的數據如何傳輸是沒有相互關系的。
HTTP沒有要求,如果Method是POST數據就要放在BODY中。也沒有要求,如果Method是GET,數據(參數)就一定要放在URL中而不能放在BODY中。
那么,網上流傳甚廣的這個說法是從何而來的呢?我在HTML標準中,找到了相似的描述。這和網上流傳的說法一致。但是這只是HTML標準對HTTP協議的用法的約定。怎么能當成GET和POST的區別呢?
之后,該博客還對其他幾個區別點進行了駁斥。所以,上面所說的是按照HTML標準,按照HTTP標準的話,GET方法請求服務器發送頁面,POST方法則是要寫入頁面(所以POST方法通常要有Content-Type和認證頭,通過認證頭來證明有權執行所請求的操作)。
Response
從服務器返回的消息稱作響應(Response),包括HTTP協議的版本號、請求的狀態(成功與否,等等)和文檔的MIME類型。
狀態行包括一個3位數字的狀態碼,如2xx表示成功,常見200表示請求成功;3xx表示進行了重定向;4xx表示客戶錯誤,常見403是禁止的頁面,404是頁面未找到;5xx是服務器錯誤。
消息頭
HTTP的頭域包括通用頭,請求頭,響應頭和實體頭四個部分。每個頭域由一個域名,冒號(:)和域值三部分組成。
- 通用頭域(即通用頭)
通用頭域包含請求和響應消息都支持的頭域。通用頭域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。 - 請求消息(請求頭)
請求消息的第一行為下面的格式:
Method Request-URI HTTP-Version
Host頭域指定請求資源的Intenet主機和端口號,必須表示請求url的原始服務器或網關的位置。HTTP/1.1請求必須包含主機頭域,否則系統會以400狀態碼返回。
Accept:告訴WEB服務器自己接受什么介質類型,/?表示任何類型,type/* 表示該類型下的所有子類型,type/sub-type。
Accept-Charset: 瀏覽器申明自己接收的字符集。
Authorization:當客戶端接收到來自WEB服務器的 WWW-Authenticate 響應時,用該頭部來回應自己的身份驗證信息給WEB服務器。
User-Agent頭域的內容包含發出請求的用戶信息。
Referer 頭域允許客戶端指定請求uri的源資源地址,這可以允許服務器生成回退鏈表,可用來登陸、優化cache等。如果請求的uri沒有自己的uri地址,Referer不能被發送。 - 響應消息(響應頭)
響應消息的第一行為下面的格式:
HTTP-Version Status-Code Reason-Phrase
HTTP -Version表示支持的HTTP版本,例如為HTTP/1.1。
Status- Code是一個三個數字的結果代碼。
Reason-Phrase給Status-Code提供一個簡單的文本描述。 - 實體消息(實體頭和實體)
請求消息和響應消息都可以包含實體信息,實體信息一般由實體頭域和實體組成。
整理思路,開始動手
- 我的目的:到人人網將我的各種信息都download到本地。
- 遇到問題:人人網需要用戶登錄。
- 解決辦法:只需要獲得服務器返回的cookie,以后每次訪問頁面時將此cookie一并發送至服務器就可以了。
- 遇到問題:我不會手工構建cookie,怎么獲取。
- 解決辦法:先手工模擬瀏覽器登錄人人網,獲取第一個cookie。
- 遇到問題:怎么模擬登錄。
- 解決辦法:向登錄頁面發送http登錄請求。
- 遇到問題:登錄頁面的具體URL是什么;http登錄請求需要包含哪些信息。
解決這兩個問題后,這篇博客就圓滿了。
登錄URL
需要進行抓包,我選用Chrome,因為我的Mac上只有這個。
其實在抓包之前我先看了下網頁的編碼,如圖,打開chrome的開發者工具(option+command+i),點擊Elements,然后點擊左上方的放大鏡圖標,再去網頁頁面上點擊“登錄”按鈕,發現了圖中的結構體。
我選中的那一行已經明確表示了,表單方法是“POST”,猜測action屬性就是提交表單的對象,也就是登錄URL。然而到底是不是呢?這只能算個猜測,具體是什么URL,用抓包去得到的結果比較有說服力。

點擊Network,將左上方的圓圈點擊變成紅色,這樣chrome就會將打開網頁的過程抓取下來。
然而這么做我并沒有找到登錄時的POST Request,我想了好久找了好多參考資料都沒寫是咋回事。但是當我使用win7下的chrome,以及IE時,均找到了對應的Request。
這是咋回事?經過兩者的對比,我發現在Mac下,chrome抓包結果少了很多,尤其是當我在登錄界面輸入用戶名,并切換焦點時,會生成一個“ShowCaptcha”的Post Request(圖中有),結果點擊“登錄”后就消失不見了。我猜測是由于在登錄的過程中頁面進行了跳轉,使得之前抓取的信息都被覆蓋掉了。仔細尋找發現了“Preserve log”,鼠標停在按鈕上的解釋是“Do not clear log on page reload/navigation”,選取后才一切正常了,結果如圖。
個人判斷,此按鈕在mac下才在界面上顯示,win下是默認勾選的。

找找為數不多的幾個POST Request,不難發現就是圖中的倒數第三個。點擊可以看到它的頭部信息。
獲得信息,Request URL:http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=20158113687
url最后寫uniqueTimestamp,說明它是有時間戳的,過段時間此數值就不一樣了。
在該界面的最下方可以找到Post Request的Form Data,由于可能泄露隱私,就不截圖了。觀察可發現其中有兩個關鍵參數:email,是我的登錄郵箱;password,是經過處理后的密碼。還有其他一些參數,盡量還原,但是夠用就好。

http登錄請求包
實際上如何構造http登錄請求包是個復雜的過程,若登錄的是taobao這類安全要求較高的網站,需要構造很多校驗內容。人人網倒是非常簡單,直接POST Request即可,并且發現人人并沒有校驗HTTP頭部。
這也算是最簡單的一種了吧。
并且請求包很靈活,每個網站的要求還不太一樣,所以如果以后需要解析其他請求包時,再單獨分析吧。希望不要太復雜。
首先聲明一個cookieJar的對象來保存cookie,然后使用urllib2的HTTPCookieProcessor來創建cookie處理器,通過此處理器來構建opener,并將此opener設置為urllib2的默認opener。
從此之后,只要使用此opener,就可以使用當前的cookie了。也可以直接使用self.opener,效果是一樣的,只不過這里是顯式調用,之前的install之后使用urlllib2看不到具體的opener罷了。在下面的第二章截圖中,兩種方式均有實例。

這里只是簡單的構造一個Request。可以看到我試用了兩個loginURL,兩者最后都返回了cookie。
并且我分別嘗試用這兩個cookie去打開http://www.renren.com?,發現都成功進入了我的賬戶!

最后提出一個問題:登錄URL的時間戳是咋回事?
目前測試前天的還可以用。分析一下,首先這個URL是自動生成的,然后和登錄相關,猜測是在前端編寫的。
很快就找到了疑似的js腳本。

打開,搜“uniqueTimestam”。

像我一樣不懂JS的人可以百度下“js Date”,查看函數說明。按照解釋,我截圖中“201582021961”的意思依次是:2015年,9月,星期三,0點,21秒,961毫秒(居然沒有Minutes!)
也可以直接在Chrome的開發者工具里面點擊“Console”,直接輸入截圖中的命令就可以獲得當前的取值。
同時,我們從這段js代碼中,又一次確認了登錄URL,整個登錄也的確是一個簡單的Request。其中的變量c存儲了POST方法要發送的data(我猜的,畢竟我不懂js)。但是怎么判斷這個時間戳是有效的我沒看出來,我把代碼中的時間戳改成2014年也依然有效。總之,如果之后登錄失敗了,我們可以選擇重新生成一個新的時間戳。
另附帶幾個chrome開發工具使用說明:
Network界面左上方的禁止圖標,點擊后可以清楚當前抓取的所有信息。
有時需要清除瀏覽器的Cache和Cookie,此時在Network右鍵即可看到選項。