請求、響應和反爬蟲
本文分為如下部分
- 引言
- 反爬蟲之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.text
和r.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編程
專欄目錄:目錄
爬蟲目錄:爬蟲系列目錄
版本說明:軟體及包版本說明
推薦閱讀: