一次Python无法安装模块的问题探索与解决之旅

源起

在Windows 10系统中安装 Python 3.11.5版本(目前最新版)并安装模块 (比如flask),安装步骤很简单:

  1. 到官方下载安装档https://www.python.org/downloads/
  2. 点击安装文件安装Python
  3. 到命令行执行 pip install packagename 安装扩展的模块

在一般的环境下, 这都没什么问题,到时在企业内部环境中, https的根证书是自行颁布的,虽然在浏览器中访问这个地址正常,但是使用 pip install 命令就是无法安装, 报的错误就是证书不对, 握手失败,无法建立连接, 完整的错误信息如下:

WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))': /simple/flask/
Could not fetch URL https://pypi.org/simple/flask/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/flask/ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))) - skipping
ERROR: Could not find a version that satisfies the requirement flask (from versions: none)
ERROR: No matching distribution found for flask
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError(SSLError(1, '[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled (_ssl.c:1006)'))) - skipping

原因分析与方法探求

从上面的错误可以看到, pip 在安装扩展包的时候会从 https://pypi.org/simple/flask/ 下载对应的包的问题, 但是pip 访问https站点的时候又没有正确的证书。
在当地机器的浏览器中https://pypi.org/simple/flask/打开这个地址, 可以正常访问。

忽略证书访问:

于是就想: 是否可以忽略证书访问呢? 找了一下,是可以通过 --trusted-host 命令选项设置, 于是使用如下命令:

pip install --trusted-host pypi.org flash

不行, 是站点不够吗?于是使用如下:

pip install  --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host=files.pythonhosted.org flash

还是不行, 改方式宣告失败

添加证书访问

Python有自身的证书库文件, 位于安装目录的: Lib\site-packages\certifi\cacert.pem,所以有种思路就是把站点的证书附加到这个证书库文件中,于是到浏览器中下载证书,保存证书名为: pypi.crt。
如何在流量器下载证书, 可以参考 : 如何在浏览器中下载网站的https证书

添加的方式可以有两种:

  1. 使用命令行: type pypi.crt >> Python\Python311\Lib\site-packages\certifi\cacert.pem
  2. 直接用记事本打开这两个文件, 把pypi.crt 的内容复制到cacert.pem 中

复制完成之后, 执行pip install flash 还是失败, 是pip 找不到这个证书库吗 ?于是分别尝试下面两种方式显示指定证书库文件

  1. 设置证书库文件的环境变量 set PIP_CERT=C:\Python\Python311\Lib\site-packages\certifi\cacert.pem
  2. 使用 --cert命令选项指定证书:类似: pip --cert \Lib\site-packages\pip\_vendor\certifi\cacert.pem install flask

还是失败。

又猜想是不是pip 的版本原因,于是使用 pip3 又试了一轮, 还是不行。

确认证书

到这, 就有点手足无措了, 难道是内部封装的这个站点的证书有什么不一样吗? 联想到在Java开发时, 使用Maven下载远端库的时候也有相同的问题,当时的解决方法就是: 在浏览器下载证书, 导入到Java的证书库中, 之后就可以了。 于是想到把https://pypi.org/ 的证书导入到Java证书库中试试。
这里有提供一个Java类工具:
SSLPoke.class:可以验证https 的站点是否可以访问
可以到如下地址下载:
https://download.csdn.net/download/oscar999/18855774
于是先用Java测试: java SSLPoke pypi.org 443
果然无法访问:


sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
        at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
        at sun.security.validator.Validator.validate(Unknown Source)

使用Java的keytool 工具导入 pypi站点的https证书,

导入命令如下:

keytool -import -trustcacerts -alias pypi -file pypi.cer -keystore  %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit

关于如何导入https到Java证书库, 可以参考:

Java如何安装https证书
导入之后, 使用Java连接成功, 如下显示:

java SSLPoke pypi.org 443
Successfully connected

操作系统的证书

到这又产生了一个想法: Windows 本 身也有证书库 ,是否导入操作系统本身的证书库文件就可以了呢。
在运行输入 certmgr.msc 打开证书库管理。

一次Python无法安装模块的问题探索与解决之旅_第1张图片

打开后的界面如下:

一次Python无法安装模块的问题探索与解决之旅_第2张图片

在这里,尝试把pypi 的证书导入了根证书以及企业信任等目录, 发现都无效, 也就是导入后, Java依旧无法访问该https站点, 同理, pip 应该也就不行。

临时方案

无奈之下,就找了一个临时方案:

  1. 找一台非企业颁发证书的机器下载pip 的包的文件, 文件的后缀名是 *.whl, 因为有依赖关系, 会存在多个whl 文件。下载的命令是:
pip download flask
  1. 将这些文件复制到目标机器的某个目录,
  2. 执行 pip install *.whl 安装所有的包。 但是这里会失败,因为windows 的命令行不认识 * 的通配符。这里的解决方法是使用 Git 的bash 进行安装。(因为该机器安装了Git , 所以可以直接使用Git Bash, 也就是说, 在Linux系统中,pip install的命令是认识 *通配符的)。


你可能感兴趣的:(python,pip,install,windows)