解决在启用Fiddler的环境里,爬虫报requests.exceptions.SSLError的问题
错误原因
源自:https://www.zhihu.com/question/42104344/answer/158407685
感谢知乎老哥通俗易懂又深刻的解释!
解决办法:
1.在requests.get()里设置参数verify = FALSE,跳过验证环节
response = requests.get(url,verify = False)
但是这样会报一个很烦人的InsecureRequestWarning,所以需要加上下面的代码:
import urllib3
urllib3.disable_warnings()
这样就完美解决了。
2.理论上可以导出Fiddler的根证书,使用OpenSSL转换成.pem格式,然后设置verify的值为证书的路径,验证的时候就会去验证Fiddler的根证书。但我不知道为什么,我这样做了,却没有成功,报的错误是:
OSError: Could not find a suitable TLS CA certificate bundle,
invalid path: Bili_Index/new.pem
参考资料来源:
官方文档ssl-warnings
http与https代理中的差异及细节
HTTP:07---连接管理之(Connection首部
TLS详解
详解 HTTPS、TLS、SSL、HTTP区别和关系
python使用requests挂fiddler代理时提示SSLError,HTTPSConnectionPool
知乎-少年晓琦OliverCh的回答)
感谢 !
——前来debug的游客请止步——
因为下面全是毫无意义的废话。
可算是明白了一杯茶一包烟一个Bug调一天的感觉了。
但是作为一个第二天学习Python的小萌新,我不禁思考,第二天就遇到了如此严峻、如此惨绝人寰的问题,是不是应该早点苦海无涯回头是岸?
先来看看报错信息:
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证书验证失败:无法获取本地颁布的证书
在处理上述异常期间,又来了异常:
urllib3中的最大重试错误:重试访问url超过最大连接数:由SSLError引起(SSL证书错误[SSL证书验证失败])
requests中的SSLError:HTTPS连接池:重试访问url超过最大连接数:由SSLError引起(SSL证书错误[SSL证书验证失败])
(狗屁不通......)
尝试分析以上的错误信息:每个报错都写着,SSL证书验证失败,但是为什么开着Fiddler代理就会出现这个问题呢?
因为requests的根证书和Fiddler的根证书冲突了,并且requests的证书验证是默认开启的。
尝试解决办法1:关闭SSL证书验证。
response = requests.get(url,verify=False)
验证失败那就不验证了嘛。
尝试结果:我去,不仅没有解决,还甩了一个不安全警告。
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,
不安全请求警告:正在向主机发出未经验证的HTTPS请求。强烈建议增加证书验证。
另外这里还有个配套的不安全警告处理措施,就是禁用警告:
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目标。其中唯一的差别是,是否需要先向代理创建一个TLS(传输层协议)连接。你可以通过指定正确的代理方案来指定你所需要连接的代理。
问题又来了,我虽然把Fiddler当工具在用,但我真的不懂HTTP和HTTPS代理是什么,那就了解一下。
找到了一篇好文章:https://www.cnblogs.com/selol/p/5446965.html
看到上面的图我深受启发,所以Connection首部又是什么啊。
找到了一篇好文章:https://blog.csdn.net/qq_41453285/article/details/95162180
谜底揭开了。
然后我理解了这两句话:
所以服务器和客户端达成keep-alive共识的时候,代理层懵逼了,我是谁我在哪儿你俩想干啥,得,关连接吧。
接着博主的文章往下看:
哦哦哦原来如此,盲中继没有理解Proxy-Connection这个Conection首部的其他首部字段名,并且转发了这个字段。
接着看:
我好像理解了!感谢博主!
划重点:HTTPS代理两侧连接是同步的,要断一起断。
把目光转向之前没看完的官方文档:
HTTPS 代理+ HTTPS 目标
一个TLS-in-TSL 隧道(?)将被创建。一个初始的TLS连接将被创建给代理,然后发送一个HTTP连接来创建一个通往目标的TCP连接,最后创建第二个通往目标的TLS连接。你可以自定义ssl.SSLContext用于通过ProxyManager类的proxy_ssl_context参数进行代理TLS连接。
(狗屁不通X2)
我勉强理解一下,就是先创建一个初始TLS连接给代理,然后......算了我理解不了,先去搜搜看什么是TSL连接吧。
找到了一篇写得很好的文章:https://www.jianshu.com/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)
(我现在好饿......)
接下来,我把FiddlerRoot证书导出,用OpenSSL把它从.cer转换成.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")
好了,又一次失败的尝试。
不过也有惊喜哦,那就是关了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] 由于目标计算机积极拒绝,无法连接。')))
好家伙,竟然敢拒绝我。
这下连HTTPS连接都建立不了。
(算了,休息会儿,回来面向百度debug)
回来了。
积极百度了一下还是运行不了。
草,我是智障吗!
我都把Fiddler关了,还怪人家为什么连不上代理!!!
呜呜呜呜呜对不起我的错。
我这就把Fiddler打开。
哎,还是熟悉的SSLError。
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
这还能咋办呢,回头去看看官方文档吧,叹气。
对于HTTPS代理,我们还支持使用绝对URI将请求转发到HTTPS目的地,前提是use_forwarding_For_HTTPS参数设置为True。我们强烈建议您仅将此选项用于受信任的代理或公司代理,因为代理将完全可见您的请求。
这我设置的没问题哈,略过这条。
这一条是关于上面的InsecureRequestWarning:
所以官方文档还得多读,再翻翻看。
发现了关键字Certificate Verification:
上面提示HTTPS连接现在是默认验证了。
虽然可以通过设置cert_reqs = 'CERT_NONE'来拒绝证书验证,但是还是强烈建议顺其自然。
除非另外指定的urllib3将尝试加载默认系统证书商店,最值得信赖的的跨平台方法是使用certifi包,它提供Mozilla的根证书包。
既然官网都这么说了,那就整一个?
太慢了,真的太慢了,这下载速度。
继续官方文档:
一旦你拥有证书,你可以创建一个PoolManager,在发送请求的时候验证证书。
啊这......跟我好像关系不是很大嘛。
目前要解决的问题是:
certificate verify failed: unable to get local issuer certificate
可是指定证书给它,它还是报这个错误。
不活了。
草草草草草草可以了!
就是加个参数verify=FALSE!
能跑出来不过有个不安全警告!
之前没有跑出来是因为我一共写了两个get():
response = requests.get(url,verify=False)
img = requests.get(iurl,verify=False,timeout = 5).content
但是我刚刚只补了一个verify=False。
总之程序跑出来了,警告的话忽略就行了,去看看Fiddler抓到的包——
User-Agent:python-requests/2.24.0
Connection:keep-alive
长连接是没问题的。
好像还发现了什么奇怪的东西?
Host: ocsp.globalsign.com是什么啊......
验证证书的啊,明白了。
注意到下载完最后一张图片以后,Conection依然是Keep-Alive:
好像这样也行吧,程序能跑,Fiddler能抓包,不安全警告可以disable掉,就是没有证书验证的环节。
但是,全局设置不验证ssl证书——
ssl._create_default_https_context =ssl._create_unverified_context
也运行不出来,还是报相同的错误。
继续探索正常验证证书的办法。
无意间看到有老哥解释的原因:
https://www.zhihu.com/question/42104344/answer/158407685
这个解释真是清晰明了!!!
最后提到的,将fiddler中下载的证书在requests中的参数设置,这方法我用过,不行的啊。
不如再试试?
试了,报错报错报错报错......
破案了!!我写错路径了!!
原来写的绝对路径:
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的,所以这其实是解析路径的时候出了什么问题吧。
等等......如果其实证书是无效的呢?
我重新导出一下证书,再换成.pem格式。
reset再重来。
警告多得我很恐慌,总觉得自己在按川川办公室的核*弹按钮。
重新试了一下还是报相同的错误。
应该还是证书的问题吧,搜了一下,验证12306的证书的时候,也会报这个错误。而我刚刚注意到这里:
OSError: Could not find a suitable TLS CA certificate bundle,
invalid path: Bili_Index/new.pem
我死心了,全网没找到办法,stackoverflow上面有个问题很像但不是。
饿死了,吃饭去。