requests關於Exceeded 30 redirects問題得出的結論,exceededredirects
首先說結論,發送requests請求必須帶上headers否則無法保持bs之間的會話。從而報上述的錯誤。
昨天一個朋友在耙梳頁時出現的一個問題,以及後續我對這個問題進行了簡單的測試。
先說出現的問題的簡單描述。
首先是使用urllib請求網頁:
#urllib.request發起的請求import urllib.requestresponse = urllib.request.urlopen("https://baike.baidu.com")html = response.read().decode('utf8')print(type(html))print(html)
結果正常顯示了百科的頁面資訊:
我們使用requests來請求這個https頁面
#requests發起的請求import requestshtml = requests.get('https://baike.baidu.com')print(type(html))print(html)
然後報錯了:
報錯是重新導向超過三十個,百度的結果是取消預設允許的重新導向。
到這裡我們得出第一條結論:
urllib和requests發送的請求預設會根據響應的location進行重新導向。
百度了一下,根據眾網友的一致推薦,我們關閉allow_redirects這個欄位。
看一看源碼裡預設是允許重新導向的。
關閉了重新導向以後,頁面不再跳轉。
#requests發起的請求,關閉重新導向import requestshtml = requests.get('https://baike.baidu.com', allow_redirects=False).textprint(type(html))print(html)
禁止了重新導向頁面必然不能顯示正常的百科首頁了,這裡我們得到的是302的跳轉頁面。
再次表明一下,百度裡總有一些人只解決當前一個問題而不說明解決思路,或者試出來的結果就放上來當作回答的行為是很不負責的。
這裡重新導向的問題根本不在於頁面跳轉了,而是頁面為什麼會多次跳轉。
我查到一篇關於請求亞馬遜超出重新導向限制的文章:http://www.it1352.com/330504.html。
簡單來說就是沒有與伺服器建立會話,頁面重新導向成了環形的死迴圈。即你的原始URL重新導向一個沒有新的URL B,其重新導向到C,它重新導向到B,等等。
文章的結尾提到加要求標頭來保持會話的持久性。
#requests發起的請求,添加要求標頭import requestsheaders = {"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).textprint(type(html))print(html)
請求的頁面應當是正確的,但是卻出現了如下亂碼:
本文的第二個結論也出來了:http頭部沒有編碼方式,requests預設使用自己的編碼方式。也是很任性,具體關於requests的亂碼行為的出現原因及解決方案,在這篇部落格有詳細介紹,可以看一下。https://www.cnblogs.com/billyzh/p/6148066.html。
#requests發起的請求,解決亂碼問題import requestsheaders = {"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 requestsheaders = {"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.requestrequest = urllib.request.Request("https://baike.baidu.com")print(request.headers)#{}print(request.get_header("User-agent"))#None
但實際上肯定是不能發送一個空的要求標頭的,所以我們抓包擷取發送的請求資訊。
urllib的回應標頭
#urllib請求時回應的回應標頭import urllib.requestrequest = urllib.request.urlopen("https://baike.baidu.com")print(request.headers)
urllib在請求的時候什麼也沒做,要求標頭也沒東西,然而伺服器對他溫柔以待,響應了正確的跳轉頁面。
#requests請求超出30次重新導向,暫時無法得到他的要求標頭import requestsh=requests.get('https://baike.baidu.com')print(h.request.headers)
同理回應標頭我也看不到。
#requests阻止重新導向他的要求標頭import requestsh=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 requestsh=requests.get('https://baike.baidu.com', allow_redirects=False).headersprint(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 requestsheaders = {"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 requestsheaders = {"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 requestsh=requests.get('https://baike.baidu.com', allow_redirects=False,verify=False)print(h.request.headers)
抓到的get的請求包:
# #requests帶上自己瀏覽器資訊的要求標頭import requestsheaders = {"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請求的。