用python實現模擬登入人人網

發表於2016-04-11

我決定從頭說起。懂的人可以快速略過前面理論看最後幾張圖。

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。

  1. Domain:指示cookie來自什麼地方,即域名。每個域至多在客戶端儲存20個cookie。
  2. Path:伺服器目錄結構中的一個路徑,表示伺服器檔案樹的哪些部分可能會用到該cookie。通常是/,表示整個檔案樹。
  3. Content:採用”name = value”的形式,即鍵值對,熟悉python的話可以得知Dictionary使用相同結構。這是cookie內容存放之處。
  4. Expires:過期時間。如果此域不存在的話,瀏覽器在退出時會將cookie丟棄。否則會一直在客戶端儲存到過期為止。所以如果伺服器想將客戶端的cookie刪除,只需要再將它傳送一次,並且選擇一個已經過去的時間作為Expires。
  5. Secure:指示瀏覽器只向安全的伺服器(https等等)才返回該cookie。

到這裡,我們知道了,後續每次連線人人網的時候,都需要一個正確的cookie來表明我們的身份。

靜態和動態Web文件

按照之前提到的模型,客戶端向伺服器端發出Request,伺服器端返回Response,一次完成,不管傳輸的是HTML格式、還是XML格式、還是……這種都算是靜態Web文件,過程非常簡單。

然而,社會在進步,需求在增加。越來越多的內容需要根據實際情況來生產,並且內容的生成既可以發生在伺服器端,也可發生在客戶端。

  1. 伺服器端動態Web頁面
    使用者可以在客戶端提交表單(Form)給伺服器端,伺服器端需要根據表單內容來返回正確的Web頁面。
    有兩種處理表單或類似Request的方法。
    傳統方法是CGI系統,允許伺服器與後端程式及指令碼通訊,通常用Perl或Python編寫。簡單的模型是:使用者,瀏覽器,伺服器,CGI指令碼,磁碟上的資料庫,以上幾個呈鏈型。
    另外一種方法是在HTML頁面中嵌入少量指令碼,讓伺服器來執行這些指令碼以便生成最終傳送給客戶的頁面。通常用PHP、JSP、ASP等。簡單的模型是:使用者,瀏覽器,伺服器,而PHP則作為一個模組存在於伺服器內。
  2. 客戶端動態Web頁面
    然而CGI、PHP、JSP、ASP這種在部署在伺服器端的指令碼並不能夠對使用者的滑鼠移動事件進行響應,或者直接與使用者互動。
    現在經常會談到“網際網路服務,互動介面要友好”,想要達成這一目的,需要在客戶端能夠實時依據使用者的動作來處理,現在最流行的應該是Javascript了吧。簡單的模型依舊是:使用者,瀏覽器,伺服器,只不過在瀏覽器多了一個Javascript的模組。

這兩種動態web語言並沒有誰優誰劣的區分,只不過用途不同罷了。前者就是我們常說的後端,後者就是我們常說的前端。

HTTP——超文字傳輸協議

前面提到,URL的第一個部分是協議名,我們用的比較多的是http協議。

Request

客戶端到伺服器的訊息被稱作請求(Request),需要包括應用於資源的方法、資源的識別符號和協議的版本號。

HTTP的Request包括幾種訪問方法:GET/HEAD/PUT/POST/DELETE/TRACE/CONNECT/OPTIONS。

  1. GET方法請求最常用,直接以連結形式訪問,即連結中包含了所有引數。速度快,內容量小,不安全。
  2. 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的頭域包括通用頭,請求頭,響應頭和實體頭四個部分。每個頭域由一個域名,冒號(:)和域值三部分組成。

  1. 通用頭域(即通用頭)
    通用頭域包含請求和響應訊息都支援的頭域。通用頭域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。
  2. 請求訊息(請求頭)
    請求訊息的第一行為下面的格式:
    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不能被髮送。
  3. 響應訊息(響應頭)
    響應訊息的第一行為下面的格式:
    HTTP-Version Status-Code Reason-Phrase
    HTTP -Version表示支援的HTTP版本,例如為HTTP/1.1。
    Status- Code是一個三個數字的結果程式碼。
    Reason-Phrase給Status-Code提供一個簡單的文字描述。
  4. 實體訊息(實體頭和實體)
    請求訊息和響應訊息都可以包含實體資訊,實體資訊一般由實體頭域和實體組成。

整理思路,開始動手

  1. 我的目的:到人人網將我的各種資訊都download到本地。
  2. 遇到問題:人人網需要使用者登入。
  3. 解決辦法:只需要獲得伺服器返回的cookie,以後每次訪問頁面時將此cookie一併傳送至伺服器就可以了。
  4. 遇到問題:我不會手工構建cookie,怎麼獲取。
  5. 解決辦法:先手工模擬瀏覽器登入人人網,獲取第一個cookie。
  6. 遇到問題:怎麼模擬登入。
  7. 解決辦法:向登入頁面傳送http登入請求。
  8. 遇到問題:登入頁面的具體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指令碼。

螢幕快照 2015-09-23 上午12.03.30.png

開啟,搜“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右鍵即可看到選項。


2016年3月28日更新:

人人網登入時需要進行輸入驗證碼,之前的方法不能用了。
思路是向伺服器獲取驗證碼,進行人工識別,然後將包含驗證碼的data去進行登入操作,成功後即可得到cookie。
參考:人人網登入指令碼(含登入失敗及驗證碼處理)
成功的標誌是得到的cookie中有一項的name為id,它所對應的value即為登入賬號的userID,此時登入成功。

另外,為了方便,將cookie儲存到本地,若此cookie無法使用,再使用上述的方法獲取新cookie。
首先在初始化時,使用:
cookielib.LWPCookieJar().load(filename,ignore_discard=True, ignore_expires=True)
將filename中的cookie鍵值對匯入變數中。
ignore_discard的意思是即使cookies將被丟棄也將它儲存下來,ignore_expires的意思是如果在該檔案中cookies已經存在,則覆蓋原檔案寫入。
如果登入失敗,則得到的url中有failCode,根據人人網的規則,等於512即為驗證碼未通過。

獲取成功後,需要將cookie儲存到該檔案中:
cookielib.LWPCookieJar().save(filename,ignore_discard=True, ignore_expires=True)

有一點需要注意的是,獲取驗證碼圖片的url為:
http://icode.renren.com/getcode.do?t=login&rnd=Math.random()
其中t不可以為web_login,若設定為此值,則意味著在之後的提交時相當於頁面重新整理了一次,傳送過去的驗證碼和當前重新整理過後的驗證碼已不相等,導致登入失敗。


最後的總程式碼:RenRenDownload

參考書目

《計算機網路(第4版)》 Andrew S. Tanenbaum著

相關文章