ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python离线安装库完整指南:从依赖下载到内网部署

Python离线安装库完整指南:从依赖下载到内网部署 搞了好几年 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 \ pandas2.0.3这里几个参数的作用分别是--platform 指定目标平台。比如 Linux x86_64 是 manylinux2014_x86_64Windows 64 位是 win_amd64macOS 则是 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 pandas2.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.whlcp38 表示 CPython 3.8manylinux_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 里直接固定版本号比如numpy1.24.3 pandas2.0.3 scikit-learn1.3.2版本号在离线情况下特别重要因为离线环境里你无法通过 pip 自动选择兼容版本所有版本的兼容性必须在联网阶段就确定下来。4. 更稳的方案搭建内部 PyPI 镜像或本地源4.1 用 pip2pi 快速搭建本地源如果只是偶尔给一两台机器装离线包前面那种“拷贝目录 find-links”的方式已经够了。但如果是给十几台、几十台机器装同一批包每台机器都维护一个本地目录就很低效。这时候搭建一个内部的 PyPI 源会省心很多。最轻量级的方案是用 pip2pi 这个工具。在联网机器上安装 pip2pipip install pip2pi然后把下载好的 whl 包目录构建成简单索引dir2pi ./offline_packages这个命令会在 offline_packages 目录下生成一个 simple/ 子目录里面是按包名分组的索引页面。之后只要把这整个目录放到一台内网 Web 服务器上比如用 nginx 或者 Python 自带的 http.servercd 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 指向 devpipip install -i http://server:3141/root/pypi/simple/ pandasdevpi 还支持先同步 PyPI 的热门包到本地缓存devpi use http://server:3141/root/pypi devpi push pandas2.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 的 whlWindows 用户只能走源码编译。解决方式就是去掉 --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-devCentOS/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 工程化里最容易被忽视、又最影响交付效率的环节之一。把这些流程整理成自己的标准动作无论你面对的是新机部署还是老系统迁移都会从容很多。
返回列表