【Python】DAY02学习日记,记一次惨绝人寰的debug

解决在启用Fiddler的环境里,爬虫报requests.exceptions.SSLError的问题

错误原因

【Python】DAY02学习日记,记一次惨绝人寰的debug

源自: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
在里面,我注意到这样的信息:

【Python】DAY02学习日记,记一次惨绝人寰的debug

HTTP和HTTPS代理
HTTP和HTTPS代理都支持HTTP和HTTPS目标。其中唯一的差别是,是否需要先向代理创建一个TLS(传输层协议)连接。你可以通过指定正确的代理方案来指定你所需要连接的代理。

问题又来了,我虽然把Fiddler当工具在用,但我真的不懂HTTP和HTTPS代理是什么,那就了解一下。
找到了一篇好文章:https://www.cnblogs.com/selol/p/5446965.html

【Python】DAY02学习日记,记一次惨绝人寰的debug

看到上面的图我深受启发,所以Connection首部又是什么啊。
找到了一篇好文章:https://blog.****.net/qq_41453285/article/details/95162180

【Python】DAY02学习日记,记一次惨绝人寰的debug

谜底揭开了。

然后我理解了这两句话:
【Python】DAY02学习日记,记一次惨绝人寰的debug
所以服务器和客户端达成keep-alive共识的时候,代理层懵逼了,我是谁我在哪儿你俩想干啥,得,关连接吧。

接着博主的文章往下看:

【Python】DAY02学习日记,记一次惨绝人寰的debug

哦哦哦原来如此,盲中继没有理解Proxy-Connection这个Conection首部的其他首部字段名,并且转发了这个字段。

接着看:

【Python】DAY02学习日记,记一次惨绝人寰的debug

【Python】DAY02学习日记,记一次惨绝人寰的debug

【Python】DAY02学习日记,记一次惨绝人寰的debug

我好像理解了!感谢博主!

划重点:HTTPS代理两侧连接是同步的,要断一起断。

把目光转向之前没看完的官方文档:

【Python】DAY02学习日记,记一次惨绝人寰的debug

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握手过程:

【Python】DAY02学习日记,记一次惨绝人寰的debug

看完了,还没太理解,又找到了另一篇好文章:https://blog.****.net/chan70707/article/details/82932153

我好像懂得了什么:
【Python】DAY02学习日记,记一次惨绝人寰的debug

回头看看报错信息:

ssl.SSLCertVerificationError: 
[SSL: CERTIFICATE_VERIFY_FAILED]
 certificate verify failed: unable to get local issuer certificate 

证书验证失败的原因是:没有获取到本地颁布的证书。

对啊,那我为什么不想办法告诉它怎么获取证书呢?

尝试解决办法2:指定SSL证书。
https://blog.****.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] 

这还能咋办呢,回头去看看官方文档吧,叹气。

【Python】DAY02学习日记,记一次惨绝人寰的debug

对于HTTPS代理,我们还支持使用绝对URI将请求转发到HTTPS目的地,前提是use_forwarding_For_HTTPS参数设置为True。我们强烈建议您仅将此选项用于受信任的代理或公司代理,因为代理将完全可见您的请求。

这我设置的没问题哈,略过这条。

这一条是关于上面的InsecureRequestWarning:
【Python】DAY02学习日记,记一次惨绝人寰的debug

所以官方文档还得多读,再翻翻看。

发现了关键字Certificate Verification:
【Python】DAY02学习日记,记一次惨绝人寰的debug

上面提示HTTPS连接现在是默认验证了。
虽然可以通过设置cert_reqs = 'CERT_NONE'来拒绝证书验证,但是还是强烈建议顺其自然。
除非另外指定的urllib3将尝试加载默认系统证书商店,最值得信赖的的跨平台方法是使用certifi包,它提供Mozilla的根证书包。

既然官网都这么说了,那就整一个?
【Python】DAY02学习日记,记一次惨绝人寰的debug

太慢了,真的太慢了,这下载速度。

继续官方文档:
一旦你拥有证书,你可以创建一个PoolManager,在发送请求的时候验证证书。

【Python】DAY02学习日记,记一次惨绝人寰的debug

啊这......跟我好像关系不是很大嘛。
目前要解决的问题是:
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抓到的包——

【Python】DAY02学习日记,记一次惨绝人寰的debug

User-Agent:python-requests/2.24.0
Connection:keep-alive
长连接是没问题的。

好像还发现了什么奇怪的东西?
【Python】DAY02学习日记,记一次惨绝人寰的debug

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

【Python】DAY02学习日记,记一次惨绝人寰的debug

验证证书的啊,明白了。

注意到下载完最后一张图片以后,Conection依然是Keep-Alive:

【Python】DAY02学习日记,记一次惨绝人寰的debug

好像这样也行吧,程序能跑,Fiddler能抓包,不安全警告可以disable掉,就是没有证书验证的环节。

但是,全局设置不验证ssl证书——

ssl._create_default_https_context =ssl._create_unverified_context

也运行不出来,还是报相同的错误。

继续探索正常验证证书的办法。

无意间看到有老哥解释的原因:

【Python】DAY02学习日记,记一次惨绝人寰的debug

https://www.zhihu.com/question/42104344/answer/158407685

这个解释真是清晰明了!!!

最后提到的,将fiddler中下载的证书在requests中的参数设置,这方法我用过,不行的啊。
不如再试试?

试了,报错报错报错报错......

破案了!!我写错路径了!!

原来写的绝对路径:

verify=r"D:\py文件\Bili_Index\fdlroot.pem"

改成相对路径以后:

verify=r"Bili_Index/fdlroot.pem"

报的错误信息变化了耶!

【Python】DAY02学习日记,记一次惨绝人寰的debug

OSError: Could not find a suitable TLS CA certificate bundle, 
invalid path: Bili_Index/fdlroot.pem

让我来看看错误是什么...invalid path....好的哦。
可是路径是我从pycharm里面右键copy path的,所以这其实是解析路径的时候出了什么问题吧。

等等......如果其实证书是无效的呢?

我重新导出一下证书,再换成.pem格式。

【Python】DAY02学习日记,记一次惨绝人寰的debug

reset再重来。

【Python】DAY02学习日记,记一次惨绝人寰的debug

【Python】DAY02学习日记,记一次惨绝人寰的debug

【Python】DAY02学习日记,记一次惨绝人寰的debug

警告多得我很恐慌,总觉得自己在按川川办公室的核*弹按钮。

【Python】DAY02学习日记,记一次惨绝人寰的debug

重新试了一下还是报相同的错误。

应该还是证书的问题吧,搜了一下,验证12306的证书的时候,也会报这个错误。而我刚刚注意到这里:
【Python】DAY02学习日记,记一次惨绝人寰的debug

OSError: Could not find a suitable TLS CA certificate bundle, 
invalid path: Bili_Index/new.pem

我死心了,全网没找到办法,*上面有个问题很像但不是。

饿死了,吃饭去。

上一篇:每天一点小进步(9):启用代理的情况下,使用python的requests库请求https接口


下一篇:从*等网站复制数据和公式到MathType里编辑