如何解決Requests的SSLError(轉)


add by zhj: 我使用方法2“更新系統的certificate”解決了問題

 

原文:https://www.jianshu.com/p/8deb13738d2c

這兩天在Linux上爬Google Play的app列表時,發現之前的腳本不能用了,總是報SSLError。花了一天的時間進行定位,雖然還是不明白為什么腳本之前能用,現在卻不能用,但總算找到了解決辦法。記錄一下解決思路和過程,以供他人遇到類似問題時參考。

問題

腳本是用Python 3.4寫的,用到了一個開源的庫play-scraper,調用其collectionAPI來獲取Google Play的Top App列表。該庫使用了requests作為客戶端來對Google Play進行操作。當腳本執行時,會報如下錯誤:certificate verify failed。

Traceback (most recent call last):
  File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/connectionpool.py", line 600, in urlopen chunked=chunked) File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/connectionpool.py", line 345, in _make_request self._validate_conn(conn) File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/connectionpool.py", line 844, in _validate_conn conn.connect() File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/connection.py", line 326, in connect ssl_context=context) File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/util/ssl_.py", line 325, in ssl_wrap_socket return context.wrap_socket(sock, server_hostname=server_hostname) File "/usr/local/lib/python3.4/ssl.py", line 365, in wrap_socket _context=self) File "/home/me/py3.4/lib/python3.4/site-packages/gevent/_ssl3.py", line 232, in __init__ raise x File "/home/me/py3.4/lib/python3.4/site-packages/gevent/_ssl3.py", line 228, in __init__ self.do_handshake() File "/home/me/py3.4/lib/python3.4/site-packages/gevent/_ssl3.py", line 545, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/me/py3.4/lib/python3.4/site-packages/requests/adapters.py", line 440, in send timeout=timeout File "/home/me/py3.4/lib/python3.4/site-packages/urllib3/connectionpool.py", line 630, in urlopen raise SSLError(e) urllib3.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600) 

定位過程

仔細分析Traceback,發現問題出在def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None)中。注意verify參數,默認為True。在play-scraper中也是將其設為True的,說明在SSL握手過程中要驗證certificate的。

Google了一下錯誤信息,大致有以下幾種解決方法:

1. 將verify設為False,不驗證certificate

參考:https://stackoverflow.com/a/30373147/2510797
簡單粗暴,但是有效。不報錯誤了,但總是有Insecure request的告警。對於有代碼潔癖的本人來說,這顯然是不能接受的,除非時間非常緊迫。繼續定位。

2. 更新系統的certificate。

參考:https://stackoverflow.com/a/24212501/2510797

sudo apt-get install ca-certificates

看了一下所用Linux系統的ca-certificates package,確實比較老了,但之前一直沒有問題。死馬當活馬醫試試吧,但問題依舊。

3. 指定系統certificate的路徑

參考: https://stackoverflow.com/a/16085737/2510797
Linux系統certificate的certificate路徑在/etc/ssl/certs。使用verify="/etc/ssl/certs"試試,發現確實不報錯誤了。但是這個方法的弊端也是顯而易見:play-scraper並沒有在API中提供傳入參數verify,必須要修改其代碼才行。不同的操作系統,其certificate存放的位置肯定不一樣,要是代碼支持跨平台,就需要判斷操作系統的類型,然后傳入相應的verify值。對於一個相對使用比較廣泛的requests庫來說,這么做顯然不太合理。

4. 使用certifi的certitificate路徑

參考:https://stackoverflow.com/a/35791445/2510797
看了一下requests的文檔,發現它使用了certifi package。然后再去看certifi的文檔,發現其certificate路徑有兩個:certifi.where()certifi.old_where()。快速瀏覽了一下requests的源碼,發現如果verify=True的話,所用的certificate就是certifi.where(),所以就試了一下old_where(),居然不報錯了。但看到certifi的文檔中建議盡量不要用old_where(),所以還是不甘心,繼續定位。

5. 安裝requests的security extras

參考:https://stackoverflow.com/a/39580231/2510797

pip install -U requests[security] 

注意后面的方括號,pip會安裝三個security相關的package:pyopenssl cryptography idna。
試了一下,果然有效,不再報錯。再去讀requests和urllib3的源碼,發現確實使用了pyopenssl。具體是怎么用的,還沒有來得及分析。
至此個人覺得比較好的解決方法基本成型:修改play-scraper的dependency,使用requests[security]來安裝那三個安全相關的包。
另外,系統的openssl版本太舊或太新也可能會造成問題。在目前最新版本的openssl上,該解決方法是有效的。

總結

使用開源軟件的好處是可以看實現源碼,花點時間讀源碼,調試定位,問題基本不難解決。但是文檔有可能不是那么完備,需要進行Google或仔細讀源碼。希望自己的分析思路對別人有所幫助吧。



作者:PythonDeveloper
鏈接:https://www.jianshu.com/p/8deb13738d2c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM