首先說結論,發送requests請求必須帶上headers否則無法保持bs之間的會話。從而報上述的錯誤。
昨天一個朋友在爬網頁時出現的一個問題,以及后續我對這個問題進行了簡單的測試。
先說出現的問題的簡單描述。
首先是使用urllib請求網頁:
#urllib.request發起的請求 import urllib.request response = urllib.request.urlopen("https://baike.baidu.com") html = response.read().decode('utf8') print(type(html)) print(html)
結果正常顯示了百科的頁面信息:

我們使用requests來請求這個https頁面
#requests發起的請求 import requests html = requests.get('https://baike.baidu.com') print(type(html)) print(html)
然后報錯了:

報錯是重定向超過三十個,百度的結果是取消默認允許的重定向。
到這里我們得出第一條結論:
urllib和requests發送的請求默認會根據響應的location進行重定向。
百度了一下,根據眾網友的一致推薦,我們關閉allow_redirects這個字段。
看一看源碼里默認是允許重定向的。

關閉了重定向以后,頁面不再跳轉。
#requests發起的請求,關閉重定向 import requests html = requests.get('https://baike.baidu.com', allow_redirects=False).text print(type(html)) print(html)
禁止了重定向頁面必然不能顯示正常的百科主頁了,這里我們得到的是302的跳轉頁面。

再次表明一下,百度里總有一些人只解決當前一個問題而不說明解決思路,或者試出來的結果就放上來當作回答的行為是很不負責的。
這里重定向的問題根本不在於頁面跳轉了,而是頁面為什么會多次跳轉。
我查到一篇關於請求亞馬遜超出重定向限制的文章:http://www.it1352.com/330504.html。
簡單來說就是沒有與服務器建立會話,頁面重定向成了環形的死循環。即你的原始URL重定向一個沒有新的URL B,其重定向到C,它重定向到B,等等。
文章的結尾提到加請求頭來保持會話的持久性。
#requests發起的請求,添加請求頭 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} html = requests.get('https://baike.baidu.com', headers=headers).text print(type(html)) print(html)
請求的頁面應當是正確的,但是卻出現了如下亂碼:

本文的第二個結論也出來了:http頭部沒有編碼方式,requests默認使用自己的編碼方式。也是很任性,具體關於requests的亂碼行為的出現原因及解決方案,在這篇博客有詳細介紹,可以看一下。https://www.cnblogs.com/billyzh/p/6148066.html。
#requests發起的請求,解決亂碼問題 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} html = requests.get('https://baike.baidu.com', headers=headers).content.decode('utf8') print(type(html)) print(html)
此時頁面顯示無異常,正確顯示百科的地址。

#requests發起的請求,加上重定向禁止 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} html = requests.get('https://baike.baidu.com', headers=headers, allow_redirects=False).content.decode('utf8') print(type(html)) print(html)

結果沒有影響,所以前面提到的解決重定向問題解決方案,多數人提到的禁止重定向根本無效,根本在於保持會話,防止重定向進入死循環。
本文結論三:多Google少百度(只針對技術性問題)。
到這里我們到底在模擬發送請求時請求頭帶了哪些東西導致的出現上面的問題呢?只能一步步分析請求頭的信息。
#urllib請求時發送的請求頭 import urllib.request request = urllib.request.Request("https://baike.baidu.com") print(request.headers)#{} print(request.get_header("User-agent"))#None

但實際上肯定是不能發送一個空的請求頭的,所以我們抓包獲取發送的請求信息。

urllib的響應頭
#urllib請求時回應的響應頭 import urllib.request request = urllib.request.urlopen("https://baike.baidu.com") print(request.headers)

urllib在請求的時候什么也沒做,請求頭也沒東西,然而服務器對他溫柔以待,響應了正確的跳轉頁面。
#requests請求超出30次重定向,暫時無法得到他的請求頭 import requests h=requests.get('https://baike.baidu.com') print(h.request.headers)
同理響應頭我也看不到。
#requests阻止重定向他的請求頭 import requests h=requests.get('https://baike.baidu.com', allow_redirects=False) print(h.request.headers)
{'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
User-Agent表示了自己是python解釋器的請求。
#requests阻止重定向他的響應頭 import requests h=requests.get('https://baike.baidu.com', allow_redirects=False).headers print(h)
{'Connection': 'keep-alive', 'Content-Length': '154', 'Content-Type': 'text/html', 'Date': 'Wed, 31 Jan 2018 04:07:32 GMT', 'Location': 'https://baike.baidu.com/error.html?status=403&uri=/', 'P3p': 'CP=" OTI DSP COR IVA OUR IND COM "', 'Server': 'Apache', 'Set-Cookie': 'BAIDUID=C827DBDDF50E38C0C10F649F1DAAA462:FG=1; expires=Thu, 31-Jan-19 04:07:32 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1'}
連接是keep alive,有location顯示重定向地址。
#requests帶上自己瀏覽器信息的請求頭 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} h=requests.get('https://baike.baidu.com', allow_redirects=False,headers=headers) print(h.request.headers)
{'User-Agent': 'User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
#requests帶上自己瀏覽器信息的請求頭,默認允許重定向 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} h=requests.get('https://baike.baidu.com',headers=headers) print(h.request.headers)
與上面一樣,再次驗證阻不阻止頁面重定向不是解決問題的關鍵點。
根據上面的測試,我有一個大膽的猜測,urllib請求會被服務器接受並響應了setcookie字段,有了cookie創建一個會話最后保證了重定向的正常請求到一個最終的頁面,但是requests不加請求頭並不會被服務器返回setcookie,產生環形的重定向,最終無法定位到跳轉的頁面,而加上請求頭User-Agent字段,那么服務器默認會建立會話保證跳轉到正常的頁面。
補充一點,結論是不加請求頭,requests無法保證與服務器之間的會話,每次連接服務器都被當作一條新請求直接讓他跳轉,不存在重定向環路的問題。
# #requests禁止跳轉的請求頭 import requests h=requests.get('https://baike.baidu.com', allow_redirects=False,verify=False) print(h.request.headers)
抓到的get的請求包:

# #requests帶上自己瀏覽器信息的請求頭 import requests headers = {"User-Agent" : "User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} h=requests.get('https://baike.baidu.com', allow_redirects=False,headers=headers,verify=False) print(h.request.headers)

所以使用requests記得一定加上請求頭信息。
希望各位大神如果一不小心看完這篇文章請指出我說的不對的地方,或者哪些方面理解的還不夠深刻。謝謝。
測試的時候沒考慮太多,其實可以通過http://httpbin.org來查看請求響應信息更加直觀方便,這個網址是專門用來測試http請求的。
