☰
CentOS 7离线安装requests库:依赖分析与实操指南
2026/10/14 1:12:58 网站建设 项目流程

搞内网服务器的兄弟应该都遇到过这种场景:项目要对接下游接口,脚本第一行就是import requests,结果这台服务器在隔离内网,根本没有外网权限,pip install requests直接超时。我最近就帮一个项目组处理了 CentOS 7 离线安装 requests 库的需求,整个过程踩了几个坑,比如依赖包缺失、Python 版本不对、pip 太老不认 wheel 包等等。这篇文章把这一套流程完整梳理一遍,从思路到实操再到排查,后面遇到同样任务的可以直接照着抄。

1. 离线安装的整体思路拆解

1.1 为什么偏偏是 requests 需要离线装

requests 算是 Python 界最常见的 HTTP 库了,内网机器要调接口、抓数据、做接口测试时经常绕不开它。很多项目脚本第一行就是import requests,哪怕只是写个简单的健康检查也用得上。

有人会问:Python 标准库不是有 urllib 吗,为什么非要用 requests?说实话,纯 urllib 写 HTTP 请求太啰嗦,处理 headers、超时、重定向、session 都要自己写一堆代码,而 requests 把这些都封装好了。所以实际项目里,尤其是接口对接类脚本,几乎默认 requests。这也导致离线环境下它的出现频率格外高。

但 requests 有个特点:它不是纯标准库实现,安装时需要拉取 4 个第三方依赖包,分别是 urllib3、idna、certifi 和 charset_normalizer。这就给离线安装带来了本质难度——只传一个 requests 的包过去是不够的,pip 安装时会因为找不到依赖直接报错。很多第一次搞离线安装的兄弟就栽在这里,以为把 requests 的 tar.gz 包传过去就行,结果pip install requests-xxx.tar.gz之后一运行就报ModuleNotFoundError。

1.2 离线安装的本质是把 pip 拆成三步

在线安装时,pip install requests会自动从 PyPI 拉取 requests 及其所有依赖,然后逐一解压、编译(如果需要)、安装。整个过程对使用者是黑盒,你只看到它一会儿下载这个一会儿下载那个,最后装好了。

离线安装的核心思路,就是把"下载"和"安装"这两个动作拆开:

  1. 在一台能上网且 Python 版本足够接近的机器上,先把 requests 和它的全部依赖一次性下载到本地目录。
  2. 把整个目录打包,拷贝到目标内网服务器。
  3. 在目标服务器上用 pip 的--no-index --find-links参数,让它只从本地目录查找安装包,不访问网络。

这套流程听着简单,但每个环节都有细节。比如下载时 pip 默认会优先下载 wheel 包,但如果某个包没有对应的 wheel,就会退回去下载源代码包(tar.gz)。源代码包到目标机器上可能需要编译,如果目标机器缺少 gcc、python3-devel 这些编译工具,就会报错。这个坑我在后面会详细讲,这里先提醒一句:能拿 wheel 就尽量只拿 wheel。

2. 动手前必做的环境检查与依赖分析

2.1 系统与 Python 版本确认

拿到一台目标服务器,先别急着找安装包,第一步是确认环境和版本。CentOS 7 的系统自带 Python 2.7.5,但实际业务脚本多半跑在 Python 3 上。这里存在一个容易踩坑的点:系统自带的 python 2.7.5 一定不要动,也不要用它来装 requests。CentOS 7 的 yum 依赖系统 Python,你要是一不小心把系统 Python 的包管理搞坏了,yum 直接罢工,那麻烦就大了。

正确的做法是先确认业务用的是哪个 Python。我在目标机器上一般按顺序执行这几条命令:

cat /etc/redhat-release python3 -V which python3 python3 -m pip --version

which python3很关键,它告诉你 python3 的真实路径。有些机器上 python3 在/usr/bin/python3,有些是自己编译安装的/usr/local/bin/python3,还有的是用虚拟环境。不同路径对应不同的 pip 环境,后面安装时一定要用同一个 pip 来装,否则就会出现"装是装上了,import 还是失败"的怪现象。

另外还要确认系统架构,CentOS 7 服务器绝大多数是 x86_64,执行uname -m看一下。虽然 requests 全家桶基本都是纯 Python 包,不区分架构,但这个习惯能帮你排查一些奇怪的兼容问题,尤其是需要带上其他二进制依赖的时候。

2.2 requests 的依赖树分析

requests 的依赖关系在离线安装中非常关键。以 requests 2.28.2 这个版本为例,它的核心依赖是:

  • urllib3:底层 HTTP 连接池、连接管理和重试逻辑的实现
  • idna:处理国际化域名编码
  • certifi:提供 Mozilla 维护的 CA 证书集合,用于 HTTPS 证书校验
  • charset_normalizer:处理响应内容的字符集检测

这四个包是 requests 的直接依赖,而且这几个包本身都比较"干净",基本没有再往下依赖第三方包。这意味着离线安装 requests 时,你只需要准备 requests 加上这 4 个包,一共 5 个文件就够了。

不过有一点要注意:不同版本的 requests,对依赖包的版本要求不一样。比如 requests 2.28.x 要求urllib3>=1.21.1,<1.27(部分新版本也有支持 urllib3 2.x 的),以及idna>=2.5,<4,charset_normalizer>=2,<4,certifi>=2017.4.17。如果目标机器的 Python 环境里已经装了某个依赖包的旧版本,pip 在离线安装时不会像在线那样自动去 PyPI 找满足要求的版本,而是会直接报错说找不到合适版本。所以在准备安装包之前,先看看目标环境里已经有了什么。

2.3 目标环境已有依赖盘点

在目标服务器上执行:

python3 -m pip list

重点看看 urllib3、idna、certifi、charset_normalizer 这几个包是不是已经存在、版本号是多少。比如有些项目之前离线装过另一个依赖包,它可能已经带了一个旧版本的 urllib3,这个版本如果不在 requests 允许的范围内,就会产生冲突。

这里我多说一句:内网机器上的 Python 环境往往是被多个项目共用或者反复修改过的,依赖版本本来就乱。盘点已有依赖不是走形式,是为了决定在下载阶段到底用哪个版本的 requests。比如我发现目标环境里已经有urllib3 1.26.12而且这个版本不能随便动(其他脚本在用),那我会优先选择兼容这个版本的 requests 版本。如果目标环境什么都没有,那我一般直接选 requests 2.28.2,这个版本稳定,依赖约束也相对宽松。

3. 在有网环境准备安装包

3.1 用 pip download 拉取依赖包

在有网络的机器上,准备一个干净的目录,然后用pip download一次搞定。这个命令专门用来下载包到本地,不会安装,非常适合离线分发场景。

mkdir -p /opt/requests_pkgs pip3 download requests==2.28.2 -d /opt/requests_pkgs

执行完看一下目录内容:

ls -lh /opt/requests_pkgs

正常情况下你应该能看到这样几个文件:

certifi-xxxx-py3-none-any.whl charset_normalizer-xxxx-py3-none-any.whl idna-xxxx-py3-none-any.whl requests-2.28.2-py3-none-any.whl urllib3-xxxx-py3-none-any.whl

看到全是.whl结尾的 wheel 包就对了。wheel 是预打包的二进制分发格式,本质上就是 zip 压缩包,安装时不需要编译,直接解压到 site-packages 就行。这是离线安装最理想的形态。

如果下载时网络环境本身访问 PyPI 很慢,可以加镜像源参数。比如:

pip3 download requests==2.28.2 -d /opt/requests_pkgs -i https://pypi.tuna.tsinghua.edu.cn/simple

这里提醒一下,镜像源的地址如果你在内网有自己的 PyPI 代理(比如基于 nexus、devpi 搭的内部源),直接用内网代理地址下载是最佳方案,大文件速度稳定,也避免通过公网下载源码包时被限速。

3.2 处理源代码包与镜像减速问题

有时候pip download会下载到.tar.gz而不是.whl,原因一般是这个包在 PyPI 上没有提供对应 Python 版本的 wheel,或者你指定了特殊平台参数。requests 全家桶正常情况下都有 wheel,但如果下载源是某些不完整的镜像,就可能拿不到 wheel。

我的建议是强制 pip 只下载 wheel 包,命令加一个参数:

pip3 download requests==2.28.2 -d /opt/requests_pkgs --only-binary=:all:

这样 pip 遇到没有 wheel 的包会直接报错而不是降级下载源码包。如果你看到报错说某个包找不到 wheel,那大概率是你用的镜像源资源不完整,换一个更完整的源试试。

但也要说明一个例外:如果你要在目标机器上装一个某些特殊平台的包(比如老的 ARM 架构、特殊的 glibc 版本),而这些平台没有对应 wheel 时,--only-binary=:all:就没法用了。这种情况只能下载源码包,同时目标机器必须准备齐全编译环境。实际我经手的绝大多数 CentOS 7 x86_64 环境,requests 全家桶都有现成 wheel,不用考虑源码编译。

还有一个容易忽略的问题:在有网机器上执行pip download时,要求这台机器的 Python 版本和目标机器尽量一致。比如你在一台 Python 3.11 的机器上执行,下载到的 wheel 包可能标注了cp311,放到目标机器 Python 3.6 上就装不了。不过 requests 本身是py3-none-any的通用 wheel,纯 Python 代码,兼容性还算好,但它的依赖比如 charset_normalizer 在新版本里有带 C 扩展的 wheel 和纯 Python 的 wheel 两种,下载时 pip 会优先选带 C 扩展的那个,这种就可能带版本标记。所以稳妥起见,我一般直接用--python-version 3.6 --only-binary=:all:这类参数来限定目标 Python 版本。

pip3 download requests==2.28.2 -d /opt/requests_pkgs --python-version 3.6 --only-binary=:all:

当然,如果你的目标机器 Python 版本就是当前这台机器的版本,那不加这些参数也行。

3.3 打包与传输到内网

下载完成后,把整个目录打包,注释一下版本信息,便于后续追溯:

cd /opt tar czf requests_pkgs_2.28.2.tar.gz requests_pkgs

传输方式就看你的内网管理习惯而定。我遇到过几种方式:能通 SSH 的直接用scp或者rsync推过去;严格隔离的环境就通过运维审批后走专用文件通道;还有的机器有内网 NAS 挂载点,直接把 tar 包丢到 NAS 上再在目标机器拉取。总之只要包能完整到达目标机器,什么方式都行。

有一点别忽略了:包到达目标机器后先校验一下完整性,建议在下载机器上生成一个 SHA256 校验值,并在目标机器上核对。内网文件通道偶尔会在传输过程中丢数据,校验这一步一分钟的事却能避免装到一半发现包损坏的尴尬。

4. 目标服务器安装与功能验证

4.1 本地目录安装与参数说明

把 tar 包拷贝到目标服务器后,先解压:

cd /tmp tar xzf requests_pkgs_2.28.2.tar.gz

然后进入目录,执行本地安装:

cd /tmp/requests_pkgs python3 -m pip install --no-index --find-links=. requests

这里解释一下两个参数的含义:

  • --no-index:告诉 pip 不要访问 PyPI 等远程索引,完全禁用网络索引查找。
  • --find-links=.:告诉 pip 从当前目录(也可以写成具体路径)查找可用的安装包。

这两个参数必须配合使用。只加--no-index而不加--find-links,pip 找不到任何安装源会直接失败;只加--find-links而不加--no-index,pip 还是会尝试访问远程索引,如果网络不通又会超时。很多兄弟离线装失败就是只写了--find-links=/tmp/xxx忘了--no-index,然后卡在漫长的超时等待上。

安装完看一眼输出,确认没有报错。然后自己检查一下:

python3 -m pip show requests

输出里的Location字段要重点看,它告诉你 requests 被安装到了哪个 Python 环境的 site-packages。确认这个路径和你业务的 Python 解释器一致,不然会出现别名或环境混乱的问题。

4.2 无网络下验证 requests 可用

安装完成后必须做一次实际验证,不能只看pip show就说完事。我的验证步骤分两层:

第一层验证导入是否正常:

python3 -c "import requests; print(requests.__version__)"

能打印出版本号说明基础安装没问题。

第二层验证请求功能是否真正可用。离线环境下没法请求外网,但可以在内网起一个简单的 HTTP 服务来测。比如就用 Python 自带模块在另一个终端起个临时服务:

mkdir -p /tmp/test_http && cd /tmp/test_http echo "hello" > index.html python3 -m http.server 8899

然后另开终端测试:

python3 -c "import requests; r = requests.get('http://127.0.0.1:8899/index.html'); print(r.status_code); print(r.text)"

返回200和hello就说明 requests 的连接、请求、响应处理这一套链路是正常的。这一步看似多余,但对内网环境非常有必要,因为有些机器上面有代理环境变量干扰,或者 Python 环境本身缺了 SSL 相关模块,直接请求外网地址时会报错,而本地请求可以快速定位是不是 requests 本身的问题。

4.3 多 Python 环境下的安装定位

CentOS 7 服务器上存在多个 Python 环境是常态。系统自带 Python 2.7.5、业务用 Python 3.6 或者自编译的 Python 3.9、虚拟环境里的 Python,可能同时存在。前面已经强调过,which python3看到的路径和你业务实际用的解释器可能不是同一个。

我在实际项目里遇到过这样一种情况:目标机器上/usr/bin/python3指向 Python 3.6,业务脚本在/data/project/venv/bin/python里面用的是同一个 Python 3.6 但独立的虚拟环境。我在/usr/bin/python3环境下离线安装 requests 后,业务脚本里照样ModuleNotFoundError,因为虚拟环境里没有这个包。后来我把安装操作改成先激活虚拟环境再执行,问题就解决了。

在后来的项目实施中,我给自己定了一个标准流程:安装前先明确业务脚本的 shebang 和实际解释器路径,安装时用同一个解释器调用python3 -m pip install,而不是直接用pip install。因为python3 -m pip可以确保 pip 和 python3 是同一环境的,避免了系统里多个 pip 互相干扰的问题。

5. 常见问题与排查技巧实录

5.1 依赖版本冲突

这类问题在已经有部分依赖包的内网环境里最容易出现。比如目标机器之前装过botocore,它自带了一个旧版本的 urllib3,而 requests 2.28.2 对 urllib3 有版本范围要求,离线安装时 pip 发现已有 urllib3 不在允许范围内,就会报类似这样的错误:

ERROR: Cannot install requests==2.28.2 because these package versions have conflicting dependencies.

遇到这种问题,我的思路是先分析目标环境里的 urllib3 被谁依赖:

python3 -m pip show urllib3

看看有没有其他包依赖它。如果 urllib3 只是被某些不重要的脚本引用,你可以在离线包目录里放一个满足 requests 要求的 urllib3 wheel,然后正常执行安装命令,pip 会自动升级它。如果 urllib3 被重要服务依赖,不能随便动版本,那就调整策略,去找一个兼容当前 urllib3 版本的更老一点的 requests 版本。比如当前环境是 urllib3 1.26.x,那用 requests 2.22 或 2.25 这类约束更宽松的版本通常也能装。

5.2 装到了错误的 Python 环境

这是我见过次数最多的离线安装失败原因。表现形式很典型:安装命令明明执行成功,但用户一运行import requests就报ModuleNotFoundError: No module named 'requests'。

排查思路很清楚:

which python3 python3 -m pip show requests

如果pip show requests能显示信息,说明你之前确实装成功了,但装的是which python3指向的环境。这时再看业务脚本的执行环境。如果业务脚本是用/usr/local/bin/python3运行的,那刚才的安装就没意义。解决方式就是切换到正确环境重新执行一次本地安装,或者找到具体环境变量设置错误的根源。

这里我多说一句:很多系统管理员习惯直接执行pip install而不是python3 -m pip install,前者调用的 pip 不一定属于你想要的那个 Python。这是老生常谈的坑,但对于离线安装这种容错率本来就低的场景,这个细节值得反复强调。

5.3 源代码包编译失败的规避办法

如果你下载到的包是.tar.gz,在目标机器安装时又缺少编译环境,你会看到 gcc、python3-devel 相关的报错,常见的如:

error: command 'gcc' failed with exit status 1

对于 requests 全家桶,纯 Python 代码理论上不需要编译,但如果在下载阶段拿不到 wheel,或者目标机器的 Python 版本和构建 wheel 环境不一致,pip 就会走 setup.py 源码安装,此时就依赖编译工具链。

最直接的规避办法就是在下载阶段用--only-binary=:all:强制拿 wheel 包。我前面提过这个参数。如果你之前下载的目录里已经有 tar.gz 了,重新用这个参数下载一遍即可。如果某个包在目标平台确实没有 wheel(这种概率对 requests 全家桶极低),那就只能给目标机器装编译工具:

yum install -y gcc python3-devel

但内网机器装 yum 包本身也可能受限,所以我的原则还是:能避免编译就尽量避免,wheel 是第一选择。

5.4 小技巧:内网 pip 源与批量分发

最后分享一个进阶思路。有些内网环境并不是完全不能访问任何网络,而是允许访问公司内部搭建的服务。这种情况下,你可以在公司内部部署一个 PyPI 镜像服务,把 requests 相关的包同步到内网,然后目标机器直接指定内网源地址来安装:

python3 -m pip install requests -i http://内网镜像地址/simple --trusted-host 内网镜像地址

这个方案相比离线包目录更适合多台机器反复部署的场景,因为它省去了每次打包传包的环节。不过如果内网连这种服务都没有,那还是老老实实用前面讲的离线目录方式,把装好的文件留作备份,下次新机器部署直接复用。

我自己的习惯是在每次离线安装完成后,把整个/tmp/requests_pkgs目录保留一段时间再清理,同时把下载机器的包目录记录到项目文档里。这样万一有其他环境需要离线安装,不用重新上网下载一遍,直接传目录过去就行。多花一分钟保留原始包,后面可能省下几小时的重操作时间。

我个人在实际操作中的体会是:离线安装本身并不复杂,真正让人栽跟头的往往是环境检查这一步没做细。系统 Python 别动、目标解释器路径确认好、wheel 优先、依赖盘点清楚,这几件事做好,requests 的离线安装基本不会出大问题。如果后面有人委托你帮忙搞类似的内网包安装,建议先把这套流程跑一遍,然后把目录保存好,内网环境装包这种事,一套可靠的离线包可以反复用好多次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询