解決在啟用Fiddler的環(huán)境里,爬蟲報requests.exceptions.SSLError的問題
錯誤原因

源自:https://www.zhihu.com/question/42104344/answer/158407685
感謝知乎老哥通俗易懂又深刻的解釋!
解決辦法:
1.在requests.get()里設(shè)置參數(shù)verify = FALSE,跳過驗證環(huán)節(jié)
response = requests.get(url,verify = False)
但是這樣會報一個很煩人的InsecureRequestWarning,所以需要加上下面的代碼:
import urllib3
urllib3.disable_warnings()
這樣就完美解決了。
2.理論上可以導(dǎo)出Fiddler的根證書,使用OpenSSL轉(zhuǎn)換成.pem格式,然后設(shè)置verify的值為證書的路徑,驗證的時候就會去驗證Fiddler的根證書。但我不知道為什么,我這樣做了,卻沒有成功,報的錯誤是:
OSError: Could not find a suitable TLS CA certificate bundle,
invalid path: Bili_Index/new.pem
參考資料來源:
官方文檔ssl-warnings
http與https代理中的差異及細(xì)節(jié)
HTTP:07---連接管理之(Connection首部
TLS詳解
詳解 HTTPS、TLS、SSL、HTTP區(qū)別和關(guān)系
python使用requests掛fiddler代理時提示SSLError,HTTPSConnectionPool
知乎-少年曉琦OliverCh的回答)
感謝 !
——前來debug的游客請止步——
因為下面全是毫無意義的廢話。
可算是明白了一杯茶一包煙一個Bug調(diào)一天的感覺了。
但是作為一個第二天學(xué)習(xí)Python的小萌新,我不禁思考,第二天就遇到了如此嚴(yán)峻、如此慘絕人寰的問題,是不是應(yīng)該早點苦海無涯回頭是岸?
先來看看報錯信息:
ssl.SSLCertVerificationError:[SSL:CERTIFICATE_VERIFY_FAILED]
certificate verify failed:unable to get local issuer certificate (_ssl.c:1056)
During handling of the above exception, another exception occurred:urllib3.exceptions.MaxRetryError:HTTPSConnectionPool(host='www.bilibili.com', port=443):
Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL:CERTIFICATE_VERIFY_FAIL
requests.exceptions.SSLError:HTTPSConnectionPool(host='www.bilibili.com', port=443): Max retries exceeded with url:/ (Caused by SSLError(SSLCertVerificationError
(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
簡單翻譯一下:
SSL證書錯誤:SSL證書驗證失?。簾o法獲取本地頒布的證書
在處理上述異常期間,又來了異常:
urllib3中的最大重試錯誤:重試訪問url超過最大連接數(shù):由SSLError引起(SSL證書錯誤[SSL證書驗證失敗])
requests中的SSLError:HTTPS連接池:重試訪問url超過最大連接數(shù):由SSLError引起(SSL證書錯誤[SSL證書驗證失敗])
(狗屁不通......)
嘗試分析以上的錯誤信息:每個報錯都寫著,SSL證書驗證失敗,但是為什么開著Fiddler代理就會出現(xiàn)這個問題呢?
因為requests的根證書和Fiddler的根證書沖突了,并且requests的證書驗證是默認(rèn)開啟的。
嘗試解決辦法1:關(guān)閉SSL證書驗證。
response = requests.get(url,verify=False)
驗證失敗那就不驗證了嘛。
嘗試結(jié)果:我去,不僅沒有解決,還甩了一個不安全警告。
InsecureRequestWarning:
Unverified HTTPS request is being made to host 'www.bilibili.com'.
Adding certificate verification is strongly advised.
See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning,
不安全請求警告:正在向主機(jī)發(fā)出未經(jīng)驗證的HTTPS請求。強(qiáng)烈建議增加證書驗證。
另外這里還有個配套的不安全警告處理措施,就是禁用警告:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
感覺不太合適的樣子。
既然甩了一個官方文檔的連接,那就去看看嘛。
追溯到官方的文檔:https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
在里面,我注意到這樣的信息:

HTTP和HTTPS代理
HTTP和HTTPS代理都支持HTTP和HTTPS目標(biāo)。其中唯一的差別是,是否需要先向代理創(chuàng)建一個TLS(傳輸層協(xié)議)連接。你可以通過指定正確的代理方案來指定你所需要連接的代理。
問題又來了,我雖然把Fiddler當(dāng)工具在用,但我真的不懂HTTP和HTTPS代理是什么,那就了解一下。
找到了一篇好文章:https://www.cnblogs.com/selol/p/5446965.html

看到上面的圖我深受啟發(fā),所以Connection首部又是什么啊。
找到了一篇好文章:https://blog.csdn.net/qq_41453285/article/details/95162180

謎底揭開了。
然后我理解了這兩句話:

所以服務(wù)器和客戶端達(dá)成keep-alive共識的時候,代理層懵逼了,我是誰我在哪兒你倆想干啥,得,關(guān)連接吧。
接著博主的文章往下看:

哦哦哦原來如此,盲中繼沒有理解Proxy-Connection這個Conection首部的其他首部字段名,并且轉(zhuǎn)發(fā)了這個字段。
接著看:



我好像理解了!感謝博主!
劃重點:HTTPS代理兩側(cè)連接是同步的,要斷一起斷。
把目光轉(zhuǎn)向之前沒看完的官方文檔:

HTTPS 代理+ HTTPS 目標(biāo)
一個TLS-in-TSL 隧道(?)將被創(chuàng)建。一個初始的TLS連接將被創(chuàng)建給代理,然后發(fā)送一個HTTP連接來創(chuàng)建一個通往目標(biāo)的TCP連接,最后創(chuàng)建第二個通往目標(biāo)的TLS連接。你可以自定義ssl.SSLContext用于通過ProxyManager類的proxy_ssl_context參數(shù)進(jìn)行代理TLS連接。
(狗屁不通X2)
我勉強(qiáng)理解一下,就是先創(chuàng)建一個初始TLS連接給代理,然后......算了我理解不了,先去搜搜看什么是TSL連接吧。
找到了一篇寫得很好的文章:http://www.itdecent.cn/p/1fc7130eb2c2
TLS握手過程:

看完了,還沒太理解,又找到了另一篇好文章:https://blog.csdn.net/chan70707/article/details/82932153
我好像懂得了什么:

回頭看看報錯信息:
ssl.SSLCertVerificationError:
[SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate
證書驗證失敗的原因是:沒有獲取到本地頒布的證書。
對啊,那我為什么不想辦法告訴它怎么獲取證書呢?
嘗試解決辦法2:指定SSL證書。
https://blog.csdn.net/qq_33958297/article/details/82291009
按照這篇文章里的步驟操作了一下,失敗了orz。
gProxies = {"http":"http://192.168.1.103:8888","https":"http://192.168.1.103:8888"}
cert = "D:\py文件\Bili_Index\\fiddlerroot.crt"
response = requests.get(url,proxies=gProxies,verify=cert)
(我現(xiàn)在好餓......)
接下來,我把FiddlerRoot證書導(dǎo)出,用OpenSSL把它從.cer轉(zhuǎn)換成.pem,然后把我的代碼改成了這個樣子:
response = requests.get(url,
proxies={"http": "http://127.0.0.1:8888",
"https":"http:127.0.0.1:8888"},
verify="D:\py文件\Bili_Index\\fdlroot.pem")
好了,又一次失敗的嘗試。
不過也有驚喜哦,那就是關(guān)了Fiddler,還附送了一個臉生的新錯誤哦:
requests.exceptions.ProxyError:
HTTPSConnectionPool(host='www.bilibili.com', port=443):
Max retries exceeded with url:
/ (Caused by ProxyError('Cannot connect to proxy.', NewConnectionError
('<urllib3.connection.HTTPSConnection object at 0x00000251156835C0>:
Failed to establish a new connection:
[WinError 10061] 由于目標(biāo)計算機(jī)積極拒絕,無法連接。')))
好家伙,竟然敢拒絕我。
這下連HTTPS連接都建立不了。
(算了,休息會兒,回來面向百度debug)
回來了。
積極百度了一下還是運行不了。
草,我是智障嗎!
我都把Fiddler關(guān)了,還怪人家為什么連不上代理!??!
嗚嗚嗚嗚嗚對不起我的錯。
我這就把Fiddler打開。
哎,還是熟悉的SSLError。
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
這還能咋辦呢,回頭去看看官方文檔吧,嘆氣。

對于HTTPS代理,我們還支持使用絕對URI將請求轉(zhuǎn)發(fā)到HTTPS目的地,前提是use_forwarding_For_HTTPS參數(shù)設(shè)置為True。我們強(qiáng)烈建議您僅將此選項用于受信任的代理或公司代理,因為代理將完全可見您的請求。
這我設(shè)置的沒問題哈,略過這條。
這一條是關(guān)于上面的InsecureRequestWarning:

所以官方文檔還得多讀,再翻翻看。
發(fā)現(xiàn)了關(guān)鍵字Certificate Verification:

上面提示HTTPS連接現(xiàn)在是默認(rèn)驗證了。
雖然可以通過設(shè)置cert_reqs = 'CERT_NONE'來拒絕證書驗證,但是還是強(qiáng)烈建議順其自然。
除非另外指定的urllib3將嘗試加載默認(rèn)系統(tǒng)證書商店,最值得信賴的的跨平臺方法是使用certifi包,它提供Mozilla的根證書包。
既然官網(wǎng)都這么說了,那就整一個?

太慢了,真的太慢了,這下載速度。
繼續(xù)官方文檔:
一旦你擁有證書,你可以創(chuàng)建一個PoolManager,在發(fā)送請求的時候驗證證書。

啊這......跟我好像關(guān)系不是很大嘛。
目前要解決的問題是:
certificate verify failed: unable to get local issuer certificate
可是指定證書給它,它還是報這個錯誤。
不活了。
草草草草草草可以了!
就是加個參數(shù)verify=FALSE!
能跑出來不過有個不安全警告!
之前沒有跑出來是因為我一共寫了兩個get():
response = requests.get(url,verify=False)
img = requests.get(iurl,verify=False,timeout = 5).content
但是我剛剛只補(bǔ)了一個verify=False。
總之程序跑出來了,警告的話忽略就行了,去看看Fiddler抓到的包——

User-Agent:python-requests/2.24.0
Connection:keep-alive
長連接是沒問題的。
好像還發(fā)現(xiàn)了什么奇怪的東西?

Host: ocsp.globalsign.com是什么啊......

驗證證書的啊,明白了。
注意到下載完最后一張圖片以后,Conection依然是Keep-Alive:

好像這樣也行吧,程序能跑,F(xiàn)iddler能抓包,不安全警告可以disable掉,就是沒有證書驗證的環(huán)節(jié)。
但是,全局設(shè)置不驗證ssl證書——
ssl._create_default_https_context =ssl._create_unverified_context
也運行不出來,還是報相同的錯誤。
繼續(xù)探索正常驗證證書的辦法。
無意間看到有老哥解釋的原因:

https://www.zhihu.com/question/42104344/answer/158407685
這個解釋真是清晰明了!?。?/p>
最后提到的,將fiddler中下載的證書在requests中的參數(shù)設(shè)置,這方法我用過,不行的啊。
不如再試試?
試了,報錯報錯報錯報錯......
破案了!!我寫錯路徑了??!
原來寫的絕對路徑:
verify=r"D:\py文件\Bili_Index\fdlroot.pem"
改成相對路徑以后:
verify=r"Bili_Index/fdlroot.pem"
報的錯誤信息變化了耶!

OSError: Could not find a suitable TLS CA certificate bundle,
invalid path: Bili_Index/fdlroot.pem
讓我來看看錯誤是什么...invalid path....好的哦。
可是路徑是我從pycharm里面右鍵copy path的,所以這其實是解析路徑的時候出了什么問題吧。
等等......如果其實證書是無效的呢?
我重新導(dǎo)出一下證書,再換成.pem格式。

reset再重來。



警告多得我很恐慌,總覺得自己在按川川辦公室的核*彈按鈕。

重新試了一下還是報相同的錯誤。
應(yīng)該還是證書的問題吧,搜了一下,驗證12306的證書的時候,也會報這個錯誤。而我剛剛注意到這里:

OSError: Could not find a suitable TLS CA certificate bundle,
invalid path: Bili_Index/new.pem
我死心了,全網(wǎng)沒找到辦法,stackoverflow上面有個問題很像但不是。
餓死了,吃飯去。