搞了好几年 Python,我发现自己最头疼的往往不是代码本身,而是环境问题。尤其当你跑到一个物理隔离的内网机器、或者客户现场的部署环境里,没有外网、没有 pip 源,面前只有一台装着 Python 的机器,这时候想装一个第三方库,难度直接上升一个量级。这篇文章我想把这些年踩过的坑、用顺手的办法一次性说透,主题就围绕“python 安装离线库”这件事来展开。
你可能会说,离线安装有什么好讲的,把 whl 文件拷过去 pip install 一下不就完了?但真实情况远比这个复杂:依赖链断裂、平台标签不匹配、缺少编译工具链、两个包之间版本互相打架,任何一个问题都能让你在机房耗掉一上午。我写这篇内容,就是希望通过一个完整可落地的流程,让不管是刚接触 Python 的读者,还是在生产环境里摸爬滚打过的工程师,都能真正理解离线安装库背后的逻辑,并且能直接把我的方法拿去用。
1. 先想清楚:为什么需要离线安装,以及核心思路
1.1 离线安装的典型场景
我遇到的最常见的几类场景,基本覆盖了离线安装库的所有需求:
一是纯物理隔离的内网环境,比如银行、军工、电力这类单位的研究网和生产网,与外网没有物理连接。你只能在内部申请一台“摆渡机”,靠 U 盘或者光盘把文件拷进去。
二是云上环境临时断网,比如某些云厂商给企业定制的专有云区域,默认并不对外开放 pip 等软件源,或者出于安全策略,需要走内部审批流程才能开通外网访问,短时间等不起。
三是生产环境的安全管控,即使机器有外网,但安全团队不允许在运行生产服务的机器上直接访问公网。所有的软件包都要先经过安全扫描,再统一分发。
四是离线目标常常不止一台机器,可能是几十台甚至上百台服务器组成的集群。如果每台机器手动装一遍,不说时间成本,光出错概率就够让人崩溃的。
在这些场景里,学会系统性的离线安装方法,不只是一个技巧,更是一项能直接救命的生产技能。
1.2 核心思路:两端操作——“能上网的机器准备,断网机器安装”
离线安装库的本质,就是把原本由 pip 在网上做的事情搬到本地完成。一句话总结就是:在能上网的机器上准备好所有的安装文件,然后通过移动介质或内部网络把这个目录拷贝到断网机器上,再从本地进行安装。
很多人第一次做离线安装,习惯直接把目标库对应的 whl 文件拷到离线机器上,然后发现装不上。原因往往占两大部分:
- 依赖缺失。比如你要装 pandas,它依赖 numpy、python-dateutil、pytz、six 等一堆库,这些库在目标机器上没装过,pip 会尝试从网络下载,结果发现没网,直接报错。
- 平台不匹配。你在联网机器上用 pip download 下载的时候,默认下载的是当前 Python 版本和当前操作系统的匹配包;但如果离线机器的 Python 版本是 3.8,你机器是 3.11,下载的 whl 可能根本装不上去。
所以,做离线安装一定要有“分批处理”的意识:
第一步,在联网机器上,明确目标库和它的完整依赖树; 第二步,用 pip download 下载适合目标平台的所有包(不是只下目标库本身); 第三步,把整个下载目录拷贝到离线机器; 第四步,在离线机器上用 pip install --no-index --find-links 指向本地目录安装。
这个思路听着简单,但里面每一步都有不少坑。下面我把每个环节的细节和实操过程分开讲。
2. 准备工作:在一台联网机器上把依赖包全部拉下来
2.1 确定目标库及其依赖链
很多人上来就直接 pip download,其实这是个误区。下载之前你要先搞清楚两件事:目标库依赖哪些第三方库;这些依赖库各自还需要依赖什么。
一个典型的例子是安装 scikit-learn,它依赖 numpy、scipy、joblib、threadpoolctl。其中 scipy 又可能依赖 numpy、pillow(某些版本),numpy 的安装版本又可能跟 Python 版本互相影响。依赖关系是树状的,只下载最外层的结果,必然缺枝少叶。
我个人的经验是:用 pip 的一个技巧来帮你自动解析依赖树。在联网机器上执行:
pip download scikit-learn --no-deps -d ./offline_packages这个命令只下载 scikit-learn 本身,不下载依赖,适合一开始摸清它的版本信息。如果你想看它完整依赖是什么,可以用:
pip show scikit-learn但 show 只能看到已安装的库,对未安装的依赖不太直观。更推荐的方法是直接在 PyPI 网页上查包元数据,或者用 pip 的 --dry-run 选项模拟安装,看看它会拉什么东西。
实际工作中,我很少一条条手动查依赖树,都是用下面的批量下载命令一并解决,因为 pip 本身就会自动解析依赖并下载所有依赖包,这是最省事的做法。
2.2 用 pip download 批量下载 whl 包
在联网机器上(建议直接用一台与目标机器同版本 Python 的环境),执行:
pip download \ --platform manylinux2014_x86_64 \ --python-version 3.8 \ --implementation cp \ --abi cp38 \ --only-binary=:all: \ -d ./offline_packages \ pandas==2.0.3这里几个参数的作用分别是:
- --platform 指定目标平台。比如 Linux x86_64 是 manylinux2014_x86_64,Windows 64 位是 win_amd64,macOS 则是 macosx_10_9_x86_64 等。
- --python-version 目标机器的 Python 版本,比如 3.8。
- --implementation 指定 CPython,一般填 cp。
- --abi 指定二进制接口,cp38 对应 Python 3.8 的 CPython ABI。
- --only-binary=:all: 表示只下载二进制包(whl),不下载源码包。这个参数非常重要,因为它能避免你在离线机器上装源码包时碰到编译环境缺失的问题。
如果目标机器上装的 Python 是从 python.org 下载的官方版本,你甚至不需要指定 --platform 等参数,直接在联网机器上执行:
pip download pandas==2.0.3 -d ./offline_packagespip 会自动下载当前 Python 版本和当前系统对应的 whl。但这么做的前提是联网机器和目标机器的 Python 版本、系统架构完全一致。不一致的话,老老实实指定平台参数是更稳妥的。
下载完成后,你会在 offline_packages 目录里看到一堆 .whl 文件。如果没有报错,说明依赖链已经完整拉下来了。不过我建议下载完以后再用一条命令验证一下完整性:
pip install --no-index --find-links=./offline_packages pandas这一条可以在联网机器上先跑一遍,模拟离线环境安装。如果这条命令在联网机器上都能报依赖缺失,那说明下载的包版本之间有问题,趁早排查,别等到离线机器上才发现。
2.3 处理源码包与编译依赖
有些库比较特殊,官方并没有提供对应平台的预编译 whl 包。最典型的就是一些比较小众的库,或者某些库的新版本还没发布对应 Python 版本的二进制包。这时候 --only-binary=:all: 会直接报错,提示找不到匹配的发行版。
碰到这种情况,你只能下载源码包(.tar.gz 或 .zip),到离线机器上通过编译安装。下载源码包的方式是去掉 --only-binary=:all: 参数:
pip download somepackage -d ./offline_packages --no-binary=:all:但源码包安装的前提是目标机器上有完整的编译工具链。Linux 上需要 gcc、g++、make,以及 Python 开发头文件(python3-dev 或 python3-devel),这个环节最容易出问题。我的建议是:能找预编译的 whl 就优先找 whl,实在找不到预编译包,再考虑源码编译,同时提前准备编译工具链。
判断一个 whl 是否适合目标平台有个小技巧,直接看文件名。比如 numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl,cp38 表示 CPython 3.8,manylinux_2_17_x86_64 表示支持 Linux x86_64 的 manylinux 标准。对方机器是否兼容,只需要确认 Python major.minor 版本一致,以及系统架构一致。
3. 离线安装的核心实操:从本地目录安装
3.1 最简单的方式:pip install --no-index --find-links
离线机器上安装本地包,我推荐用 --no-index 参数,意思是不去索引 PyPI,只从本地查找包。配合 --find-links 指定本地目录:
pip install --no-index --find-links=/path/to/offline_packages pandas这个命令会去 /path/to/offline_packages 目录里找 pandas 以及它的所有依赖,找到就安装。如果目录里缺某个依赖包,命令直接报错,不会偷偷去访问外网。
在只装了基础 Python 的离线机器上,通常还需要先安装 pip 本身。但大多数情况下,Python 官方安装包都会自带 pip,所以这一步可以略过。有些精简后的 Python 环境没有 pip,那就需要在联网机器上下载 get-pip.py,拷到离线机器上执行:
python get-pip.pyget-pip.py 可以在 https://bootstrap.pypa.io/get-pip.py 获取,这是一个单文件脚本,直接拷过去就能装。
3.2 离线安装到指定路径、虚拟环境
很多时候我们不希望把库装到系统全局环境里,尤其是目标机器上可能有多套 Python 环境、不同的项目依赖互相冲突。我的习惯是在离线机器上先创建虚拟环境,再在虚拟环境里安装离线库。
python -m venv /opt/myproject/venv source /opt/myproject/venv/bin/activate pip install --no-index --find-links=/path/to/offline_packages pandas如果目标机器没有 venv 模块,可以用 virtualenv,但这又是一个额外要离线分发的包。所以提前在联网机器上把 virtualenv 的 whl 也一并下载到 offline_packages 里,是最稳的。
还有一种情况是目标机器上已经存在多个 Python 版本,比如系统自带 Python 3.6,同时还有源码编译的 Python 3.9。离线安装时,务必用对对应的 pip:
/usr/local/python3.9/bin/python3.9 -m pip install --no-index --find-links=/path/to/offline_packages pandas用 python -m pip 的方式,而不是直接调用裸 pip 命令,可以避免因为 PATH 环境变量导致装错环境。这是个很小的细节,但我在生产环境排查安装错位的案例时遇到过不少次。
3.3 使用 requirements.txt 批量安装
离线安装往往不止一个库,而是一整套依赖。比如你要部署一个 Django 项目,可能涉及 django、djangorestframework、celery、redis、requests 等一堆库。这时候在联网机器上生成 requirements.txt 是很常规的操作:
pip freeze > requirements.txt但要注意,pip freeze 导出的文件里包含了当前虚拟环境中所有库的精确版本号,如果目标环境 Python 版本不同,直接把这份 requirements.txt 拿过去安装,很可能因为某些包的版本不支持目标 Python 版本而失败。我建议在联网机器上单独建一个干净的虚拟环境,装好项目需要的库,然后再 pip freeze 导出,这样产出的 requirements.txt 更干净。
生成 requirements.txt 之后,所有下载命令可以写成:
pip download -r requirements.txt -d ./offline_packages安装时同样:
pip install --no-index --find-links=/path/to/offline_packages -r requirements.txt如果 requirements.txt 里的某些库最新版本不支持目标 Python,还可以在 requirements.txt 里直接固定版本号,比如:
numpy==1.24.3 pandas==2.0.3 scikit-learn==1.3.2版本号在离线情况下特别重要,因为离线环境里你无法通过 pip 自动选择兼容版本,所有版本的兼容性必须在联网阶段就确定下来。
4. 更稳的方案:搭建内部 PyPI 镜像或本地源
4.1 用 pip2pi 快速搭建本地源
如果只是偶尔给一两台机器装离线包,前面那种“拷贝目录 + find-links”的方式已经够了。但如果是给十几台、几十台机器装同一批包,每台机器都维护一个本地目录就很低效。这时候搭建一个内部的 PyPI 源,会省心很多。
最轻量级的方案是用 pip2pi 这个工具。在联网机器上安装 pip2pi:
pip install pip2pi然后把下载好的 whl 包目录构建成简单索引:
dir2pi ./offline_packages这个命令会在 offline_packages 目录下生成一个 simple/ 子目录,里面是按包名分组的索引页面。之后只要把这整个目录放到一台内网 Web 服务器上,比如用 nginx 或者 Python 自带的 http.server:
cd offline_packages && python -m http.server 8080内网其他机器就可以直接把 pip 源指向这台服务器:
pip install pandas -i http://192.168.1.10:8080/simple/如果担心 HTTP 明文传输被安全部门拦,也可以在内网配 HTTPS,但一般内网环境问题不大。
4.2 用 devpi / Nexus 做团队级镜像
当团队规模再大一些,需要长期维护一个可持续更新的 Python 包源时,pip2pi 就显得单薄了。它只能静态管理已有文件,不能自动同步 PyPI 全量包,也没有权限控制。这个级别我更推荐 devpi 或 Nexus Repository。
devpi 的用法是先在服务器上安装并启动:
pip install devpi-server devpi-client devpi-server --init devpi-server --start然后在客户端机器上把 index 指向 devpi:
pip install -i http://server:3141/root/pypi/+simple/ pandasdevpi 还支持先同步 PyPI 的热门包到本地缓存:
devpi use http://server:3141/root/pypi devpi push pandas==2.0.3 http://server:3141/root/pypi如果你们公司已经有 Nexus,直接在 Nexus 上创建 PyPI proxy repository,把外网 PyPI 代理进来,内部机器统一走 Nexus 即可。这是一个典型的“离线/受限网络”团队基础设施,值得花点时间搭建。
4.3 conda 离线安装时怎么办
如果你用的是 Anaconda 或 Miniconda,情况稍有不同。conda 安装包的后缀通常是 .conda 或 .tar.bz2,直接用 pip download 是搞不定的。
最简单的方式是在联网机器上使用 conda 的缓存机制。执行:
conda install pandas numpy此时包会下载到 conda 的 pkgs 目录(一般在 ~/.conda/pkgs 或 /opt/miniconda3/pkgs)。然后把这个目录整个拷贝到离线机器上。在离线机器上安装时,指定本地缓存:
conda install --offline pandas numpy但 --offline 只对当前用户目录里的缓存生效。如果你想把缓存目录放到指定位置,可以设置:
conda config --set pkgs_dirs /path/to/pkgs再把拷贝过来的 pkgs 内容放到这个路径下,然后执行 --offline 安装。
更专业的做法是搭建 conda 的本地 channel。用 conda 官方推荐的方式,在服务器上装一个 Miniconda,然后用 conda-build 或者直接放包文件到指定目录,再通过 web server 共享。离线机器配置:
conda config --add channels http://192.168.1.10:8080/channel/之后正常 conda install 就会走本地 channel,不再请求外网。
结合我的经验,如果你在一个大规模离线环境里工作,强烈建议把 pip 和 conda 两条路的离线方案都准备好。不同项目对依赖管理工具的偏好不一样,只准备一条路,遇到用另一种工具的项目就会很被动。
5. 常见问题与排查技巧
5.1 pip 报 “no matching distribution” 怎么办
这是离线安装里最容易遇到的报错,字面意思是没找到匹配的发行版。原因通常有两类:
第一类是下载时平台参数没写对。比如目标机器是 Windows,你在 Linux 上用 --platform win_amd64 下载,pip 就会报这个错。此时要检查你下载时填的平台标签、Python 版本号是否跟目标机器完全一致。
第二类是目标库根本没有对应目标平台的预编译包。比如有些库只发布了 macOS 和 Linux 的 whl,Windows 用户只能走源码编译。解决方式就是去掉 --only-binary=:all:,改用源码包下载,并且在目标机器上保证有编译工具链。
另外一个容易忽略的点是 manylinux 标准版本。有些较老的服务器系统里 glibc 版本较低,只支持 manylinux2010 甚至更老的 manylinux1 包,而你下载的是 manylinux2014 或者 manylinux_2_28 的包,安装时会明确报错。这种情况下,要么降低依赖库的版本,选支持老 glibc 的旧版包,要么在目标机器上静态编译。
5.2 编译报错:缺少 gcc、python.h 等
如果离线安装时被迫走了源码编译流程,最常见的报错是:
error: command 'gcc' failed with exit status 1或者:
fatal error: Python.h: No such file or directory这两个问题都属于编译环境缺失。gcc 缺失需要离线安装编译工具链,在 Linux 上相关 rpm 或 deb 包也要一并下载,这个操作已经不局限于 Python 层面了。Python.h 缺失则说明没有安装 Python 的开发头文件,Debian/Ubuntu 上是 python3-dev,CentOS/RHEL 上是 python3-devel,需要通过系统包管理器离线安装。
我的经验是,能绕开源码编译就尽量绕开。尽量选择目标平台有预编译 whl 的版本,如果某个库太新没有二进制包,优先降低到上一个有二进制包的版本,而不是硬着头皮去编译。
5.3 平台标签不匹配怎么解决
有时候下载的 whl 文件已经放在目录里了,但安装时报:
ERROR: pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.whl is not a supported wheel on this platform.这个报错说明 whl 文件名的平台标签跟当前安装环境不匹配。常见原因是你把在 Python 3.11 机器上下载的包拿到 Python 3.8 机器上安装,或者反过来。
解决办法第一优先是回到联网机器,按目标机器的 Python 版本重新下载。如果不能重新下载,也可以尝试修改文件名称里的平台标签来骗过 pip,但我不推荐这么做,因为即使重命名后能安装,内部代码可能依赖特定 ABI,运行时会崩。
如果你同时有多个目标机器,Python 版本还不一致,下载时最好分目录管理,比如:
offline_packages/py38/ offline_packages/py39/ offline_packages/py311/安装时按目标机器的 Python 版本选择对应的目录,避免文件混在一起。
5.4 依赖包版本冲突的排查思路
离线安装比在线安装更容易触发版本冲突,因为在线的 pip 会灵活调整每个依赖的版本,而离线包目录里往往只准备了一个版本。
比如你同时要装 pandas 2.0.3 和 numpy 1.19.5,而 pandas 2.0.3 要求 numpy >= 1.21.0,那安装时就会报冲突。这种问题可以提前在联网机器上解决:在联网机器上做好完整的依赖解析,安装成功后把虚拟环境里的包全部导出,再作为离线包集。
如果离线安装时或者安装后 import 时报依赖版本不对,排查步骤我建议这样走:
- 检查 requirements.txt 里固定的版本范围是否合理;
- 用 pip check 检查当前环境依赖是否完整、冲突;
- 在联网机器上重新创建虚拟环境,降低或升级某个库的版本,重新生成离线目录。
pip check 真的是离线环境下的好工具,安装完一堆包之后,跑一下它能快速列出所有依赖不满足的库。
5.5 安装后 import 报错的处理
最后一种常见问题是库装上了,但 import 时报错。比如:
ImportError: libpython3.8.so.1.0: cannot open shared object file这种要么是 Python 解释器编译时没有带动态库路径,要么是系统缺少某些共享库。对于前者,可以通过设置 LD_LIBRARY_PATH 指向 Python 的 lib 目录解决;对于后者,要确认系统基础库完整,安装对应的 lib 包。
还有一种 import 报错是二进制库冲突,比如 numpy 和 OpenBLAS 相关的问题,经常出现在多架构混合部署的机器上。这类问题没有统一解法,基本要靠逐个环境排查。
我个人在处理离线安装问题时,始终会带一杯咖啡和一份包版本清单。离线环境没法实时查资料,所有依赖版本提前记下来,会让排查快很多。
最后分享一个我自己的习惯
离线安装库这件事,做多了就会发现,真正难的往往不是安装命令本身,而是前置的规划。我现在每到一个新项目,只要判断目标环境可能没有外网,就会在第一时间把 requirements.txt 和相关离线目录准备好,而不是等部署的时候才手忙脚乱。
还有个小技巧,我会把每次下载完的离线包目录保留做快照,标注好 Python 版本、操作系统版本、glibc 版本和库版本。过了一个月,当有人问“上次那个环境是怎么装的”时,我只需要翻出快照就能复现,不需要重新在网络上大海捞针。这个习惯帮我省了太多时间。
离线安装不是什么高深技术,但它确实是 Python 工程化里最容易被忽视、又最影响交付效率的环节之一。把这些流程整理成自己的标准动作,无论你面对的是新机部署还是老系统迁移,都会从容很多。