1. 程式人生 > >那些年,我爬過的北科(二)——爬蟲基礎之session登陸

那些年,我爬過的北科(二)——爬蟲基礎之session登陸

(注:由於現在域名全都要備案了,.tech 域名不讓備案,下面的nladuo.tech 統一更改為 nladuo.cn

說說HTTP請求:GET與POST

在上一節中,我們在不知道原理的條件下呼叫了requests.get方法下載了HTML頁面。在本節中,我們來說說什麼是HTTP請求和它的特點。

在HTTP請求中,主要有GET和POST兩種方式,其主要區別在於:

  • GET的資訊儲存在url中,比如說我們在上節看到的“?categoryId=1”。
  • 而POST的資訊則把資訊儲存在form中,比如說我們在輸入登陸使用者名稱密碼的時候,不會在網址中看到這些隱私資訊;當然,我們在上傳大檔案的時候,比如說上傳一個1個G的視訊,也不會把視訊的資訊放到url中。

另外,HTTP是一種短連線的協議,它基於TCP。為什麼這麼說呢,因為HTTP請求的過程其實就是:

首先,我們的瀏覽器通過tcp和遠端web伺服器的埠互相建立連線;然後傳送一個指令,比如說要獲取根連線(url為'/')的內容;再然後伺服器會根據客戶端(我們的瀏覽器)的請求返回相應的結果(如HTML文字、圖片等);客戶端得到了請求之後,就會將連線關閉掉,同時web伺服器也關閉和客戶端的連線;這樣,一次HTTP請求也就完成了。

那麼瀏覽器和web伺服器是如何傳送指令的呢?

在Chrome中檢視HTTP請求

下面以nladuo.cn/test.html來作為測試頁面,在輸入連結的同時開啟Chrome的開發者工具,調整到Network選項。

這個網頁只有一個請求,那就是它的HTML,返回“It Works”。下面,我們在請求列表中點選test.html這項,可以看到伺服器的相應頭Requests Headers和客戶端的請求頭Response Headers。

我們點選Requests Headers邊上的view source可以看到原始的請求頭。

在這裡面可以看到:

  • 請求了nladuo.cn上面的“/test.html”這個資源,使用了HTTP1.1版本的協議,
  • 請求的域名的Host為: nladuo.cn
  • 等等。。。

同理檢視Response Headers,

可以看到:

  • 使用了HTTP1.1版本的協議,返回碼為200 OK
  • 日期是啥
  • 伺服器是Apache,2.4.7(Ubuntu)版本
  • 等等。。。

下面我們來使用電腦自帶的tcp客戶端telnet來模擬傳送GET請求,看看一個GET請求的實際流程。

在電腦中使用telnet客戶端

對於mac或者linux使用者,可以不用進行任何配置,開啟終端輸入telnet即可使用。

對於Windows使用者,可以在此處檢視開啟telnet部分。

使用telnet模擬GET請求

對於網站來說,預設開放的是80埠,比如在Network請求中的General中可以雖然沒有配置,但訪問的是80埠。當然,你也可以輸入nladuo.cn:80/test.html進行訪問,不過一般都不會多此一舉。

所以我們要用tcp連結nladuo.cn的80埠,使用以下命令。

telnet nladuo.cn 80
複製程式碼

輸入之後,就可以看到nladuo.cn被解析成了ip:123.206.86.230(這裡換了個伺服器,所以ip變了,原為191.101.13.124)了,並建立起了tcp連線。在這時mac和linux使用者就可以準備輸入命令了;對於Windows系統需要首先摁下ctrl鍵 + ']' 鍵進入輸入模式,然後再按回車切換到顯式輸入模式後再輸入命令。

我們像在chrome的Network看到的一樣,首先我們要發一個GET請求訪問/test.html資源,並使用HTTP1.1版本的協議,然後再告訴伺服器我們訪問的Host是nladuo.cn。之後,我們還可以告訴伺服器User-Agent是啥,Accept-Encoding是啥,不過這些都不是最重要的,所以只要輸入前兩條即可。

GET /test.html HTTP/1.1(回車)
Host: nladuo.cn(回車+回車)
複製程式碼

在這裡,每輸入一條後輸入一個回車,在最後輸入兩個回車。在輸入兩個回車之後,等待片刻,就可以看到伺服器給我們返回的資訊了。

這裡可以看到像之前Chrome瀏覽器中看到的一樣,返回了200 OK、時間、伺服器資訊、等等....

在相應頭後面的兩個回車後面,可以看到返回的HTML資訊:“It Works”

到這裡,這次請求就已經結束了,再等待片刻,可以看到遠端伺服器關閉了tcp連結。一次HTTP請求也正式完成了。

模擬登陸

登陸過程發生了什麼?

通過上面的利用tcp模擬http請求的案例,我們知道了客戶端傳送一個請求頭資訊,伺服器返回一個相應頭資訊+HTML後,tcp連線就關閉了。但是我們日常使用網頁中登陸網頁之後,重新整理之後瀏覽器還能記得我們登陸的資訊,這是什麼原理呢?

下面以一個簡單的登陸頁面來學習一下登陸過程中都發生了什麼?登陸的連結地址為:nladuo.cn/crawler_les…。而登陸後的隱私連結地址為:nladuo.cn/crawler_les…

首先,我們先不登入,訪問一下隱私頁面,注意要首先開啟Network,再輸入連結nladuo.cn/crawler_les…

這裡可以看到網頁顯示出了無權檢視,並且Response Headers多了一個Set-Cookie欄位。當然,如果你沒按照我說的做,而是先輸入url,顯示出頁面,然後再開啟Network,重新整理一下頁面檢視的話,就會看到以下的結果。

在Response Headers的Set-Cookie不見了,而Requests Headers多了一個Cookie,這兩個的值還是一樣的,都是“PHPSESSID=9m8vgq9699fun79t3ks6ljrdh7”。

當然,這個時候不管再重新整理幾次頁面,Response Headers都沒有出現新的Set-Cookie了;而在第一次的之後所有請求,Request Headers都會帶著一個Cookie。比如我們這裡檢視登陸頁面,nladuo.cn/crawler_les…,也帶了這個Cookie。

這裡可以先帶著疑問,我們現在知道,Set-Cookie只執行一次,由伺服器返回;在此之後,瀏覽器會儲存Set-Cookie儲存的值,每次訪問這個域的內容都會帶著Set-Cookie的值,並把他放到Cookie欄位裡。

接下來,我們嘗試一下登陸,這裡,使用者名稱和密碼都預設為nladuo,我們只要在輸入框輸入nladuo即可。點選登陸後,我們可以看到這裡發了一個POST請求,顯示出了“登陸成功”。

相比GET而言,POST的請求頭中多了Content-Type和Content-Length兩個欄位,因為這次是把資料放到了Form Data中,而不是在url裡了。這裡Content-Length就是傳送位元組的長度,Content-Type則是傳送的型別,這裡會把表單中的欄位轉換成鍵值對,如uname:nladuo,passwd=nladuo轉換為uname=nladuo&passwd=nladuo,這樣正好也是26個字元。

當然,這裡,我們也可以用Telnet模擬POST請求,讀者可以看一下,這裡就不多說了。

下面,我們再看一下Cookie,可以發現這裡Cookie沒有變,還是 “PHPSESSID=9m8vgq9699fun79t3ks6ljrdh7”。

Cookie與Session

看到這裡,帶著疑問,可以引出Cookie和Session的概念了。

我們上面看到的Cookie是一種客戶端的技術,而Session則是服務端的技術。登陸的過程其實就是一個會話開始的時候,伺服器給客戶端一個ID,如上面看到的Set-Cookie,這其實就代表會話的開始,而當瀏覽器關閉後,一次會話也就結束了。

在一次會話中,使用者的狀態儲存在伺服器中,而客戶端是儲存一個會話ID,所以當我們在沒登陸的時候請求nladuo.cn/crawler_les…頁面時,伺服器會在快取資料庫裡的PHPSESSID這張表查詢id為9m8vgq9699fun79t3ks6ljrdh7的欄位,判斷使用者是否登陸了,然後根據查詢的結果來返回不同的頁面。

比如,我們在沒有登陸前,快取伺服器中PHPSESSID中9m8vgq9699fun79t3ks6ljrdh7的is_login欄位是NULL,所以伺服器查詢到is_login是空的,所以不給使用者看隱私頁面。當我們登陸的時候,如果使用者名稱密碼正確,伺服器就會給快取伺服器上的9m8vgq9699fun79t3ks6ljrdh7欄位的is_login設定為True,當用戶下次請求隱私頁面的時候,就可以看到正確的返回結果了。

使用Cookie模擬登陸

通過上面的學習,我們知道了如何進行登入,下面我們用程式碼模擬一下這個步驟:1.首先登陸,2.儲存Cookie,3.帶著Cookie請求隱私頁面

# 1. 先登陸
resp1 = requests.post("http://nladuo.cn/crawler_lesson2/do_login.php",
  data={
    "uname": "nladuo",
    "passwd": "nladuo"
})

# 2. 儲存伺服器傳回來的Cookie
print("Set-Cookie:", resp1.headers["set-cookie"])
cookie = resp1.headers["set-cookie"].split(";")[0]

# 3. 再通過cookie請求隱私頁面
resp = requests.get("http://nladuo.cn/crawler_lesson2/private.php",
  headers={
    "Cookie":  cookie  # 現用瀏覽器或者Telnet傳送Post請求登入, 把cookie粘到這裡
})
print(resp.content)
複製程式碼

執行程式碼,可以看到這裡登陸成功,返回了隱私頁面。

使用requests.session模擬登陸

不過,上面的方式手動管理Cookie總感覺有些麻煩,Requests庫給我提供了一個更方便的物件:requests.session,它可以像瀏覽器一樣記錄我們的會話中的Cookie。

import requests

# 1. 建立一個Session
session = requests.session()
# 2. 登陸
session.post("http://nladuo.cn/crawler_lesson2/do_login.php",
             data={"uname": "nladuo", "passwd": "nladuo"})
# 3. 訪問隱私頁面
resp = session.get("http://nladuo.cn/crawler_lesson2/private.php")
print(resp.content)
複製程式碼

現在,我們的程式碼中的流程就像人用瀏覽器的操作的過程一樣了,登陸一下,直接請求隱私頁面就好了。

執行程式碼後,可以看到同樣的正確結果。