請求、響應和反爬蟲

本文分為如下部分

  • 引言
  • 反爬蟲之user-agent
  • cookies設置
  • response對象
  • HTTP狀態碼詳解

引言

學習爬蟲,更清楚地了解訪問網頁獲取內容的過程是非常有必要的。

我們平常瀏覽網頁的過程其實是這樣的:點擊一個網頁鏈接,瀏覽器會幫我們向這個網頁發送請求,該網站伺服器接到這個請求之後,判斷這個請求是否合理,合理則將網頁信息返回到我們瀏覽器中,返回的過程稱為響應;不合理則阻止這次訪問。發送請求和返回請求的過程可以想像成我知乎私信你一條消息,你看到了之後給我回了一條。

舉一個例子,在這個網頁中,右鍵-檢查,刷新頁面,選擇Network,可以看到下面這個界面

將Response Headers摺疊起來,下拉看到

上面的Response Headers和Request Headers分別是響應頭信息和請求頭信息

  • Request Headers是瀏覽器向網站發送請求時攜帶的信息,類似身份證,對面網站通過判斷這些信息來決定是否接受你的這次請求。這就好比我給你發私信時,你會看到我的昵稱和頭像,還有一些其他信息,你會根據這些來判斷是不是要回我的消息。
  • Response Headers則是網頁傳迴響應時攜帶的信息

這些信息又什麼用呢?或者說和我們爬蟲有什麼關係呢?這些都是在使用requests請求設置參數、提取內容時體現的

反爬蟲之user-agent

我們可以用下面這個命令對這個網站發送請求

import requestsr = requests.get(https://zhuanlan.zhihu.com/python-programming)

這是我們以前常做的事情,但是我們來看一下這次返回的結果是什麼樣的

>>> r.text<html><body><h1>500 Server Error</h1>
An internal server error occured.
</body></html>

它沒有包含我們想要的信息,這更像一個報錯。這是因為我們發送請求時攜帶的信息沒有通過檢驗,或者說對方網站把我們這個請求識別成了爬蟲。

我們可以來檢驗一下我們攜帶了什麼信息,看一下Request Headers

>>> r.request.headers{User-Agent: python-requests/2.18.4, Accept-Encoding: gzip, deflate, Accept: */*, Connection: keep-alive}

其中最重要的是User-Agent,顧名思義,就是指誰來代替我們訪問網頁的,這裡顯示是python中的requests庫發送的請求。我們從網站設計者的角度出發思考,網站設計是給人用的,人通過瀏覽器訪問是OK的,但是一個python庫來訪問是個什麼情況,肯定是爬蟲了。所以網站設計者只要制定這樣一條規則:檢查請求者的UA(User-Agent),如果不是瀏覽器,就拒絕其訪問。這樣就成功把我們的訪問禁止了,這就是最簡單的反爬蟲,禁UA

為什麼要反爬蟲呢?

  • 首先,網站肯定不希望自己的信息被隨便拿去使用,如果有人輕易拿到了知乎的所有文章信息,創建一個平台,並通過比知乎更好的演算法將合適的內容推薦給用戶,那麼知乎網站的很多用戶自然就會被吸引到那個新的平台上,這顯然是知乎不願意看到的
  • 第二,爬蟲是自動化程序,它一秒向網站發出千百次請求,如果有非常多的爬蟲在抓這個網站,那麼網站伺服器就會面臨比較大的壓力。所以反爬蟲的一個作用是設置壁壘,阻止大量小白隨意爬取;同時限制其他爬蟲的抓取速度,防止伺服器過載

同時,我們要注意一點,網站面向的對象是那些瀏覽網頁的人,網站需要照顧的是他們的用戶體驗,他們不需要考慮做爬蟲的人的用戶體驗,因為網站不是用來爬的,爬蟲和網站甚至是敵人的關係。所以網頁要盡量識別爬蟲的特徵,將確定為爬蟲的請求封殺掉。而如果是真正用瀏覽器的用戶則一定要確保他們不會被封,防止用戶體驗受損,這種關係其實是寧可放過一千也不誤殺一個。

接下來,我們言歸正傳。

python庫的UA被禁了,那麼我們就好奇,瀏覽器幫我們發請求時,它攜帶的UA是什麼呢?

我現在用的是chrome瀏覽器,上面截圖中請求頭信息沒有截全,補充如下

從這裡的UA能看出這是一個chrome瀏覽器發出的請求。

那麼我們會想,如果requests發送請求時能偽裝成chrome就可以繞過這道反爬蟲機制了,只要把代碼改成下面這樣

import requestsheaders = {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36}r = requests.get(https://zhuanlan.zhihu.com/python-programming, headers = headers)

上面的UA就是從瀏覽器里複製過來的。這時用r.text列印出來的就是網頁源代碼。

總結一下,通常拿到一個網頁,先不用加headers請求一次,如果沒有返回想要的結果,首先考慮加一個headers,很多時候就可以了,如果不可以再考慮其他的。

cookies設置

我們現在來請求知乎首頁,因為之前那個知乎頁面需要設置user-agent,所以這裡我們也加上

import requestsheaders = {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36}r = requests.get(https://www.zhihu.com/, headers = headers)r.status_code # 200,表示請求成功r.text # 網頁源代碼如下所示

可以看到這是一個等著我們登錄的界面,如果我要抓取我知乎的首頁內容,就需要登錄進我的賬號才能獲取。(之前那個網頁是不需要登錄就可以看的,而首頁是必須登錄賬號才可以看)

這時讀者可能會奇怪,為什麼我用瀏覽器輸入這個網址自動進入的就是我登錄完的頁面,而requests進入的就是待登錄頁面。那是因為瀏覽器記住了你的登錄狀態,當你曾經登錄過這個網站,它就創建了一個cookies,這個cookies存儲在你的瀏覽器中。當你下次打開瀏覽器的時候,瀏覽器就直接使用這個cookies,使你不需要重新輸入賬號密碼。我們可以從剛剛的截圖中看到

request headers 裡面有一項cookie,表示瀏覽器幫你請求網頁的時候攜帶了這個cookie,所以你就可以直接進入登錄過的頁面。同樣,如果requests攜帶這個cookies,也可以直接訪問到登錄後的數據。代碼如下

import requestsmycookie = # 將上圖中的cookie對應部分複製過來,以字元串形式傳入mycookie變數headers = {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36, cookie: mycookie}r = requests.get(https://www.zhihu.com/, headers = headers)r.status_code # 200,表示請求成功r.text

(因為我如果公布自己的cookie則我的賬戶就可以被別人操作了,所以這裡不寫在代碼中,讀者可以自己貼自己的cookie)

這時我們會發現拿到了首頁的文章信息。

關於cookie,讀者可以選擇application進入下面這個頁面

方框處右鍵-clear,然後你刷新這個頁面,會發現變成了這樣

也就是清除了cookie之後,網頁就需要重新登錄,這也更說明了登錄狀態是通過cookie保持的。

總結一下,即當你請求時發現對方要求你登錄之後才會把信息給你,你可以加上cookie就可以用登錄後的狀態來訪問網頁獲取信息了。我們不需要了解cookie是怎麼工作的,只要拿過來用就可以了。

response對象

上面代碼中我們對r這個變數提取了這兩個屬性

r.textr.status_code

下面我們來更詳細地介紹這個Response對象(即上面的r變數)。

Response對象,顧名思義,就是包含了網頁返回信息的對象,我們可以通過提取屬性的方式提取其中的信息。這些信息里有我們要提取的網頁源代碼,也有我們請求、響應的信息,有助於我們查看請求的狀態。常用的有下面這些

r.status_code # 提取請求狀態碼r.url # 當前請求的urlr.text # 網頁源代碼字元型r.content # 網頁源代碼bytes型# 兩種編碼r.encodingr.apparent_encoding

主要講下面兩點

1.r.status_code

這是HTTP狀態碼,表示請求的響應狀態。由三位數字組成,這三位數中最重要的是第一位數字

  • 2開頭的表示請求成功最常見的是200,這是最好的結果,請求沒有任何問題
  • 3開頭表示重定向,即你請求的URL可能是無效的,自動轉而去請求了另外一個URL,常見301
  • 4開頭表示客戶端錯誤,即我們這裡發的是錯的。比如常見的404表示鏈接是無效的
  • 5開頭表示伺服器錯誤,即那邊接收時發生了錯誤

更加詳細的狀態碼介紹可以參考維基百科

本文下一節會具體舉例什麼時候會出現什麼樣的狀態碼。

2.源代碼與編碼

r.textr.content都是網頁源代碼,前者是我們可以直接處理的字元串形式,而後者是二進位形式,他們之間的相互轉化是通過編碼與解碼完成的,二者之間相互轉化使用的字符集是r.encoding,而r.apparent_encoding是網頁真實的編碼方式。即下圖源代碼開頭標註的部分

在抓取網頁的時候,可能會出現這樣的情況:r.text的結果是一些看不懂的字元,即默認使用的字符集r.encoding是錯誤的,這樣我們就無法提取到正確的信息,此時我們就需要使用r.content使用正確的字符集r.apparent_encoding轉化成我們真正想要的字元。可以看下面這個例子

上面截圖是我在這篇文章中舉的一個例子,這是一個字元編碼的系列文章,涉及到了編碼的方方面面,對編碼不是很了解的可以通過專欄目錄查找這個系列文章學習,這裡就不贅述編碼方面的細節了。

除了上面列出的這些,以後可能還會遇到r.json(),當網頁源代碼是json格式的數據時,直接這樣就可以轉化成python對象,和json.loads功能類似。

另一個需要提起的是r.request,如果用r.headers則返回的是response的headers,如果我們想獲取發送請求時帶的headers,就要r.request.headers

HTTP狀態碼詳解

2開頭

2開頭表示請求成功,一般就是返回200,例子如下

>>> r = requests.get(https://bj.lianjia.com/ershoufang/)>>> r.status_code200

3開頭

3開頭的狀態碼錶示重定向,即你不能訪問當前訪問的鏈接獲取資源,它會自動轉到一個新的鏈接上去,即重新定位到一個新的位置。

1.scrapy的日誌例子

舉一個例子

https://bj.lianjia.com/ershoufanghttps://bj.lianjia.com/ershoufang/

這兩個鏈接中,下面這個鏈接是比較規範的,而如果我們請求時使用的是上面的鏈接,它會自動重定向到下面的鏈接上,比如用scrapy爬蟲時就出現過這樣的日誌信息(看from to 從哪個鏈接跳轉到哪個鏈接)

DEBUG: Redirecting (301) to <GET http://bj.lianjia.com/ershoufang/> from <GET https://bj.lianjia.com/ershoufang>[scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://bj.lianjia.com/ershoufang/> from <GET http://bj.lianjia.com/ershoufang/>[scrapy.core.engine] DEBUG: Crawled (200) <GET https://bj.lianjia.com/ershoufang/> (referer: None)

這個日誌信息表示,在抓取過程中,先請求的是

https://bj.lianjia.com/ershoufang

301重定向到

http://bj.lianjia.com/ershoufang/

再301重定向到

https://bj.lianjia.com/ershoufang/

此時返回200即正常請求到頁面。

重定向不影響我們獲取到數據,但是這樣多次請求會影響獲抓取速度,所以最好還是規範自己的URL,定位到真正資源的位置。

2.requests的例子

對於requests來說,它內部有自動重定向的設計,所以即使是不規範的URL,也會自動重定向然後返回200,看下面例子

>>> requests.get(https://bj.lianjia.com/ershoufang).status_code200

但是我們修改一個參數就可以知道它其實是重定向過的

# 輸入不規範的URL>>> r = requests.get(https://bj.lianjia.com/ershoufang, allow_redirects=False)>>> r.status_code301>>> r.text# 輸入規範的URL>>> r = requests.get(https://bj.lianjia.com/ershoufang/,allow_redirects=False)>>> r.status_code200

allow_redirects=False不允許重定向時,不規範的URL就會返回301,且不會自動重定向,無法獲得數據,而規範的URL就會正常請求。

3.r.history

其實我們也可以不使用allow_redirects參數就可以探知返回的200過程中是有301的,如下所示

>>> r = requests.get(https://bj.lianjia.com/ershoufang)>>> r.url # 請求的url和傳入的不一樣,已經被重定向過了https://bj.lianjia.com/ershoufang/>>> r.status_code200>>> r.history # 看請求過程中有沒有歷史請求,發現有一次301的重定向[<Response [301]>]>>> r.history[0].url # 提取可以看出這次被重定向的網址,就是我們輸入的URLhttps://bj.lianjia.com/ershoufang

4開頭

1.404

最常見的404即頁面不存在,比如這個https://stackoverflow.com/1111,輸入瀏覽器就會顯示page not found,其實就是404.

用requests庫請求也是如此

>>> r = requests.get(https://stackoverflow.com/1111)>>> r.status_code404

這種情況說明客戶端錯誤,即你輸入的鏈接是錯誤的,這個鏈接根本就不存在。

2.403

403表示伺服器理解你的請求,但是拒絕執行它,可能是識別出了你是爬蟲,它也完全可以返回一個404.

>>> r = requests.get(http://worldagnetwork.com/)>>> r.status_code403>>> headers = {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36}>>> r = requests.get(http://worldagnetwork.com/, headers = headers)>>> r.status_code200

上面代碼表示最開始禁止訪問,攜帶UA之後就允許正常訪問了。

5開頭

我們直接看下面這個例子

>>> r = requests.get(https://zhuanlan.zhihu.com/python-programming)>>> r.status_code500>>> headers = {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36}>>> r = requests.get(https://zhuanlan.zhihu.com/python-programming, headers = headers)>>> r.status_code200

5開頭的狀態碼錶示服務端發生錯誤,上面情況說明你發的請求被服務端拒絕了,從而無法獲得數據,返回500。這是知乎的反爬蟲限制的結果,如果帶上UA再訪問就可以正常請求,返回狀態碼也變成200.

我們可以看到,這種情況和剛剛403那種情況發生的原因是一樣的,但是返回的狀態碼卻不同,其實這是對方伺服器自己定義的。他不想讓你訪問它的網頁,有的說是我們服務端不想你訪問,就是5開頭,如果他說是你自己的請求有問題,那就返回4開頭的。

所以關鍵只在請求是否成功,不是200的狀態碼最好都去檢查一下是哪裡出了問題。

專欄信息

專欄主頁:python編程

專欄目錄:目錄

爬蟲目錄:爬蟲系列目錄

版本說明:軟體及包版本說明


推薦閱讀:

TAG:Python | 爬蟲計算機網路 | python爬蟲 |