于是继续对比版本包,结果在 pip 包的路径下发现有一个 _vendor\urllib3 目录,原来 pip 是直接把 urllib3 集成到了自己的包里面,不受系统安装包的影响。检查其中的 _version.py 里的版本信息,果然也是 1.26.x。
出错的 pip 的版本是 20.3,把 pip 也降级到 20.2 以下,就没有问题了。
显然,鉴于 pip 的高频使用,这种致命的问题不可能没人报,所以在 pip 项目的 issue 列表里很快就找到了相关讨论:
https://github.com/pypa/pip/issues/9216
urllib3 更新了啥根据 所说,更改代理配置可以解决问题:
绕了好大一圈大概明白是怎么回事了:
以前 urllib3 其实并不支持 https 代理,也就是说代理服务器的地址虽然大家配置的是 https,但是一直都是悄无声息地就按照 http 连接的,刚好代理服务器确实也只支持 http,所以皆大欢喜。
现在 urllib3 要支持 https 代理了,那么既然配置代理是 https 就尝试用 https 的方式去连接,但是由于代理服务器其实只支持 http,所以没法处理请求,ssl 握手阶段就出错了。
注意,这里的 https 是指代理服务器自己的,和我们要访问的目标网站无关。
因为目标网站的协议和代理服务器的协议并不要求一样,所以只需要更改代理配置 ,将访问 https 网站的代理服务器地址改为 http 即可,也就是这样:
HTTPS_PROXY=http://proxy_ip:proxy_port前面的 HTTPS_ 表示,如果访问的站点是 https 的,需要走这里配置的代理服务器;后面的 则表示这个代理服务器自己只支持 http。
而我们一直以来看到的配置建议,这两者前后通常都是保持一致的:
HTTP_PROXY=http://proxy_ip:proxy_port HTTPS_PROXY=https://proxy_ip:proxy_port这个是错误的!
代理到底该咋配Windows 10 中的代理服务器设置如下,并没有区分什么 http 和 https:
手动给 requests 传入代理配置requests 的请求参数中是支持指定代理服务器的,刚开始的代码没有指定:
url = 'https://github.com/' r = requests.get(url)前面在尝试解决问题的时候,也试过了传入代理服务器配置:
proxies={ 'http': 'http://127.0.0.1:7890', 'https': 'https://127.0.0.1:7890' } r = requests.get(url, proxies=proxies)上面两种写法的效果其实是差不多一样的,结果当然也是一样出错。
按照上面 issue 中的修改建议改为:
proxies={ 'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890' # https -> http } r = requests.get(url, proxies=proxies)运行结果就 OK 了。
好了,现在我们可以不用降级版本了,但是却要多出一段配置,要改代码,总归还是不爽。
其实,如果是 Linux 系统是没这个问题的,本来代理配置就是通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来设置的,改一下环境变量的值就可以了,麻烦还是在 Windows 系统中。
要搞明白 Python 代码是如何获取 Windows 系统中的代理服务器设置的。
谁解析的系统代理配置在代码中不难发现,当用户有传入 proxies 参数时,requests 是通过标准库提供的 getproxies 函数来获取系统代理服务器配置的:
>>> # 如果是 python 2,则是 from urllib import getproxies >>> from urllib.request import getproxies >>> getproxies() {'http': 'http://127.0.0.1:7890', 'https': 'https://127.0.0.1:7890', 'ftp': 'ftp://127.0.0.1:7890'}上面显示的结果就是对应到截图中的代理配置。
注意,urllib 和 urllib3 不是一个库,前者是 Python 标准库自带。