尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

离线环境pip安装完全指南:从打包到避坑

离线环境pip安装完全指南:从打包到避坑 前阵子接手一台内网数据分析服务器CentOS系统Python 3.8业务脚本依赖numpy做数值计算、pandas做清洗聚合、matplotlib出图三个库都是日常套路。按平时习惯敲了个pip install numpy pandas matplotlib结果直接卡死——这台机器根本没有外网访问能力PyPI请求全部超时。之后折腾了整整一个下午才把环境配完期间还摔进wheel平台不匹配GLIBC版本过旧numpy属性缺失三个大坑。后来再看这类问题发现痛点根本不是装不装得上而是大部分人对pip联网安装时的完整链路缺乏概念一旦网络不可用就不知道该准备什么、不需要准备什么。这篇文章把我在这台离线机器上实操验证过的完整流程整理出来联网机器上如何正确打包离线机器上怎么装遇到报错怎么定位以及版本选型的硬核速查。无论你是给内网服务器配环境、在物理隔离机房部署还是在其他无法访问外部网络的场景下复现这套操作希望都能直接抄作业。1. 为什么pip install在断网环境会直接失败先搞清楚联网安装的完整链路1.1 pip联网安装时隐藏在命令行背后的事很多人对pip的印象就是一条命令装好一切但拆开看pip install numpy这条命令背后至少干了四件事读取当前环境的Python版本和目标平台比如cp38 manylinux2014_x86_64连接PyPI索引服务拉取numpy在索引页上暴露的所有版本元数据根据当前Python版本和平台标签筛选可用的wheel文件下载wheel并解析依赖确定依赖树中每个库的版本继续下载安装断网环境的问题不是少了一个包文件而是上面整个链路断在第一环pip连索引都访问不到后续的版本筛选、依赖解析全部无从谈起。而且很多人在断网机器上尝试pip install ./numpy.whl时还会遇到一个隐性问题——即使本地已经准备好了wheel文件如果命令写法不对pip仍会尝试访问PyPI去检查更新或解析其他依赖结果又卡在网络等待上。1.2 断网环境缺的到底是什么不只是包文件这么简单理解了联网链路之后就会意识到离线安装环境真正缺的是三种能力访问包索引的能力拿不到有哪些版本、每个版本有什么依赖的元数据依赖解析的能力pip的解析器需要在线拉取索引来构建依赖图获取二进制wheel的渠道无法通过命令天然下载这就是为什么把网上下载的.whl文件传到离线机器上直接装这种思路看起来直接实际却经常卡壳。你在网上下载的wheel可能是针对你的Windows电脑的而目标机器是Linux x86_64文件名里的标签对不上pip会直接拒绝安装。单一wheel还只是表面问题真正牵扯到numpy、pandas、matplotlib这种三层依赖结构的组合包准备工作必须做得比想象中更细。这一节想说明的其实就一句话离线安装的第一步不是去下载某一个包而是要在联网环境里把索引元数据 二进制wheel 依赖关系这三样东西尽可能完整地复制到离线机器上。2. 出发前准备在联网机器上把离线安装包完整打包2.1 用pip download一次性拉取主包和全部依赖准备阶段的正确姿势是找一台能访问PyPI的机器开发机、家里电脑都行用pip download一次性把目标包及其依赖全部下载到本地目录。命令很简单pip download numpy pandas matplotlib -d D:\offline_packages其中-d指定输出目录。这条命令会自动解析当前环境对应的Python版本和平台把主包和所有依赖的wheel文件都拉下来。执行完之后在offline_packages目录里你会看到一堆.whl文件除了numpy、pandas、matplotlib三个主包之外还有python_dateutil、pytz、six、cycler、kiwisolver、pillow、fonttools等依赖包。这里有个容易被忽略但很关键的点用pip download下载时如果当前机器上已经装过目标包pip可能会复用本地环境里的版本信息来做解析导致下载的版本不是PyPI上的最新版。想要纯粹复现PyPI上的选择逻辑建议加--no-cache-dir参数pip download --no-cache-dir numpy pandas matplotlib -d D:\offline_packages2.2 跨平台准备时的三个关键参数platform、python-version、only-binary如果准备机联网的那台和目标机断网的那台系统不同直接执行上面的命令往往会翻车。比如你在Windows上点击下载拿到的是win_amd64标签的wheel拷到Linux服务器上就会报 is not a supported wheel on this platform。正确做法是指定目标平台和Python版本。以目标机是Linux x86_64 Python 3.8为例pip download --no-cache-dir \ --platform manylinux2014_x86_64 \ --python-version 38 \ --only-binary:all: \ numpy pandas matplotlib \ -d ./offline_packages--platform指定目标平台。Linux x86_64 场景用manylinux2014_x86_64基本覆盖所有现代发行版Windows 用win_amd64ARM Linux 用manylinux2014_aarch64--python-version指定目标Python版本注意写法是38、39、311这种--only-binary:all:这里非常关键。它强制pip只下载编译好的二进制wheel跳过源码包。原因很简单断网目标机上大概率没有编译C扩展所需的工具链gcc、python-dev拿到源码包在本地现场编译根本不现实。很多老手都在这上面吃过亏——忘加--only-binarypip “好心”地给你下载了.tar.gz源码包而不是.whl结果传到离线机器上安装时pip尝试现场编译各种缺头文件、缺编译器报错完全没法看。所以记住一条铁律离线场景下只认wheel文件不碰源码包。2.3 用requirements.txt锁定版本避免在离线机器上解析依赖打包过程中还有个容易埋雷的地方依赖解析。pip download在联网机器上可以自由解析版本但到了离线机器上如果依赖关系没有被完整带过去安装时就会提示 No matching distribution found因为它没有能力连接PyPI去问pandas 2.x到底需要哪个版本的numpy。推荐的做法是提前准备一份requirements.txt把所有主包和依赖包的版本都锁定numpy1.24.3 pandas2.0.3 matplotlib3.7.2然后在联网机器上执行pip download -r requirements.txt -d ./offline_packages锁定版本最大的好处是确定性和可复现性。离线机器上的环境不是装完当天能跑就行而是三个月后出问题时还能知道当时装的是哪一版。尤其numpy这种底层库版本一变pandas和matplotlib的兼容性都会受影响。下载完成后建议顺手运行pip check验证一下本地依赖关系有没有冲突避免把矛盾带到离线环境。这一步在联网机器上做成本极低在离线机器上做就是四处碰壁。3. 断网机器上的三种安装路径按环境选最省事的一种3.1 最小可行方案--no-index加--find-links本地目录安装把打包好的offline_packages目录拷到目标机器U盘、内网共享目录都行然后直接安装pip install --no-index --find-links./offline_packages numpy pandas matplotlib这个命令是离线安装的核心姿势--no-index告诉pip绝不访问PyPI--find-links./offline_packages告诉pip从这个本地目录里找包pip会在这个目录里看到那堆.whl文件然后根据依赖关系自动挑出numpy、pandas、matplotlib以及它们依赖的包按顺序安装。这个过程不需要网络也不需要在命令里手动指定依赖安装顺序——只要离线目录里的文件是全的pip自身就能搞定依赖排序。实际部署中我建议在命令里直接写主包名而不是pip install ./*.whl。因为后者会让pip把目录里所有包都装一遍包括一些用不上的纯Python依赖增加不必要的出错面。如果确认目录里只有目标依赖也可以直接这么干省事。3.2 一劳永逸的设备源方案把离线目录变成内网PyPI仓库如果断网的不是一台机器而是一整个内网环境每次都拿U盘插来插去就不现实了。这种情况下可以在一台内网服务器上用pypiserver或devpi把离线包目录变成一个只读的PyPI仓库pip install pypiserver然后启动服务将包目录暴露为HTTP源。局域网内其他机器配置pip install --index-urlhttp://内网服务器IP:8080/simple/ numpy pandas matplotlib这个方案有一点需要特别注意pypiserver启动后的包目录结构和你用pip download得到的平铺目录结构不一致不要直接把offline_packages拿去启动。我通常会在内网服务器上单独建一个目录把所需的wheel文件放进去之后再启动pypiserver服务或者用pip install --find-links指向共享目录而不是--index-url。说实话如果你只是临时给一两台机器配环境搭本地源反而有点过度设计。但如果这个内网环境要长期维护多台机器后续还要持续加包搭一个内网源绝对值回时间成本。3.3 极轻量场景手动安装单个whl文件还有一种特殊情况目标机器上只缺一两个包而且没有复杂的依赖。比如已经有了numpy只差matplotlib那可以单独把离线目录里对应的.whl文件手动装pip install matplotlib-3.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whlpip同样会检查它的依赖cycler、kiwisolver、pillow但如果你确定这些依赖已经在环境里直接指定单个文件即可。这种方式的缺点也很明显不会自动排查依赖。所以只适合我明确知道当前环境缺什么的场景新手还是用--no-index --find-links这种方式更稳。4. 离线安装高频踩坑的完整排查链路从报错信息一路追到根因4.1 is not a supported wheel on this platform绝大多数情况是平台标签不匹配这是离线安装中遇到次数最多的报错字面意思就是你手里的wheel文件不被当前pip识别不能在这个平台装。有一次我帮同事排障他报这个错第一反应是怪pip版本太老。我让他执行下面这行命令看一下当前环境支持的所有平台标签pip debug --verbose输出里有一段Compatible tags列出了当前Python环境能够接受的wheel标签比如cp38-cp38-manylinux_2_17_x86_64 cp38-cp38-manylinux2014_x86_64 cp38-abi3-manylinux_2_17_x86_64然后我让他看一眼手里wheel的文件名numpy-1.24.3-cp39-cp39-manylinux2014_x86_64.whl问题瞬间清晰cp39标签需要Python 3.9而目标机器是Python 3.8pip自然拒绝。这就是典型的下载时选错版本问题。排查思路其实很机械先对照wheel文件名里的cpXX和当前Python版本是否一致再看平台标签manylinux2014、win_amd64、macosx是否匹配最后看是不是64位/32位的问题。离线环境里没有联网搜答案的便利按这个顺序排查最快。4.2 导入numpy时报module numpy has no attribute float是版本激进升级惹的祸装完包执行import numpy as np没报错但再往下运行老代码突然抛出一句AttributeError: module numpy has no attribute float这是numpy 1.24开始移除np.float、np.int、np.bool等Python内置类型别名导致的兼容性问题。老项目里如果写了np.float或np.int在numpy 1.24及以上版本就会炸。这个问题在离线环境里特别容易让人心态崩掉因为你连上网搜一下这个属性为什么没了都做不到。我个人的处理办法是不是在业务代码里临时改成float、int这种Python原生写法而是在离线部署前就在准备机上先跑一遍核心脚本确认目标包版本和业务代码兼容。如果项目确实老旧、没法快速改代码那就直接在requirements.txt里锁定numpy 1.23.5最后一个还能用np.float的版本而不是硬上最新版。4.3 导入时提示GLIBC版本过旧Linux旧发行版的隐形天花板这个坑比前两个更隐蔽因为它不在安装阶段报错而是在import阶段/lib64/libc.so.6: version GLIBC_2.17 not foundwhy出现这个因为manylinux2014这个平台标签对系统glibc有硬性要求至少要2.17。CentOS 6、Debian 7这类老系统的glibc停留在2.12左右即使wheel文件名和Python版本都匹配C扩展库加载时仍然会因为找不到对应的libc符号而崩溃。判断目标机器的glibc版本很简单ldd --version如果输出低于2.17就得换思路一是找manylinux2010平台标签的旧版wheel比如numpy 1.19.x还提供manylinux2010构建二是直接基于目标系统源码编译——但离线环境没有编译条件是常态所以这条路基本堵死。最实际的方案是在打包阶段就确认目标机器的glibc版本然后根据它选择对应的wheel标签。这也是我在准备阶段反复强调先摸清目标机器情况再下载的原因。4.4 依赖缺失导致安装中断离线目录不完整安装过程中提示No matching distribution found for pytz或者ERROR: Could not find a version that satisfies the requirement基本都是因为准备阶段用pip download时没有把依赖全部拉干净。常见原因有两个联网机器上已经有某些依赖包pip下载时认为当前环境已经满足就没有再次下载用--platform跨平台下载时某些纯Python依赖因为写法问题没有进入目录解决办法很简单不要在已经装了一堆包的环境里做离线打包尽量用干净的虚拟环境或者加--no-cache-dir重新执行下载最后对照本机site-packages里的目录清单逐个确认离线包里是否包含完整依赖。5. 版本选型和依赖关系的硬核速查提前避坑比事后修复省事一百倍5.1 Python版本与numpy版本的对应关系离线环境最怕的就是选错版本。下面这张表是我在实际部署中验证过的numpy与Python版本对应关系可以直接作为选型参考numpy版本支持的Python版本备注1.19.x3.6 - 3.9老项目最后的稳妥选择1.21.x3.7 - 3.10不再支持Python 21.22.x3.8 - 3.10过渡版本兼容性中规中矩1.23.x3.8 - 3.11最后一个支持np.float等内置别名的版本线1.24.x3.8 - 3.11移除了大量np.float兼容别名1.26.x3.9 - 3.12很多新项目的稳妥选择2.0.x3.9 - 3.12大版本升级依赖它的包也必须同步升级记住一个规律numpy的每个大版本升级都会伴随一波API清理。离线环境里没有快速搜索的条件所以不要追求最新而是追求跟你的项目代码匹配。5.2 pandas与numpy的版本约束pandas依赖于numpy但并不是pandas最新版依赖numpy最新版这么简单。每个pandas版本都锁了自己需要的最低numpy版本pandas版本最低numpy版本要求支持的Python版本1.3.xnumpy1.17.33.7 - 3.101.5.xnumpy1.21.03.8 - 3.112.0.xnumpy1.22.43.8 - 3.122.1.xnumpy1.23.23.9 - 3.12如果只是单独下载pandas wheel而没有把对应版本的numpy同时放进离线目录安装时pip会报需要numpy大于等于某个版本但找不到满足条件的版本。所以打包阶段直接写全版本锁定比让pip自己解析要省事得多。5.3 matplotlib的依赖面比想象中宽很多人以为matplotlib只依赖numpy实际它还有一长串依赖contourpy、cycler、fonttools、kiwisolver、packaging、pillow、pyparsing、python-dateutil。这些包在pip download时会被自动带下来但如果你是想手动补齐很容易漏掉一两个。另外matplotlib也挑Python版本matplotlib版本支持的Python版本3.5.x3.7 - 3.103.6.x3.8 - 3.113.7.x3.8 - 3.123.8.x3.9 - 3.12所以我给出的固定搭配建议是Python 3.8 环境用numpy1.24.3 pandas2.0.3 matplotlib3.7.2Python 3.10 环境用numpy1.26.x pandas2.1.x matplotlib3.8.x。这组搭配实测下来比较稳定。6. 一条捷径直接拷贝已装环境的site-packages以及它的边界6.1 为什么这个歪路能通有时候又死得很难看离线装包装到绝望的时候很多人都动过把能联网机器上的site-packages整个目录拷过去的念头。这个方法在某些条件下真的可行但边界条件非常苛刻。可行条件是两台机器使用完全相同的操作系统发行版精确到小版本、完全相同的Python解释器版本连小版本都要一样3.8.10和3.8.12之间的差异偶尔也会触发ABI兼容问题、并且Python安装方式一致都是系统自带、或者都是官方安装包安装。满足这些条件时把site-packages里的numpy、pandas、matplotlib相关目录和文件拷贝到目标机器的site-packages下确实能import成功。我实测过一次两台同样Ubuntu 20.04 Python 3.8.10的机器拷贝方式安装后运行了完整的pandas聚合、matplotlib绘图流程没出问题。但如果两台机器的发行版不同、Python版本不同或者一边是Anaconda一边是系统Python这个方案翻车的概率极高。C扩展包的二进制本质决定了它和系统库、编译器ABI深度绑定跨环境拷贝基本等于赌运气。而且这种方案还有一个致命弱点没有依赖检查缺了某个依赖包时只有在运行到对应的导入语句时才会暴露排查起来反而更累。6.2 更稳的半拷贝方案用pip的--target参数输出到独立目录如果确实想在拷贝方案上碰碰运气建议别直接覆盖目标机器的site-packages而是用pip在联网机器上把包导出到一个独立目录比如pip install numpy pandas matplotlib --target ./local_site然后把整个local_site目录拷过去通过PYTHONPATH环境变量指向它export PYTHONPATH/path/to/local_site:$PYTHONPATH这样做的好处是不污染目标机器的系统site-packages出现问题直接撤销环境变量就行。但本质上仍然是二进制包跨机器搬运同样的ABI风险依然存在只适合短期临时用一下的诉求长期维护不推荐。6.3 我的最终建议拷贝方案只作应急规范离线包才是正路说实话前面写了这么多我的真实观点是大多数情况下老老实实走pip download--no-index --find-links这条正规离线安装路径比拷贝整个site-packages要省心得多。拷贝方案看着快但是排查成本高、不可复现、还需要额外解释为什么这台机器能跑那台不能跑。真正一劳永逸的做法是给自己维护一个离线包仓库平时在联网机器上就把numpy、pandas、matplotlib等常用库的固定版本下载好按Python版本和平台分目录存放并写清对应的requirements。这样不管是临时来一台裸机还是换了一个项目环境都能做到随取随用不需要每次现研究版本依赖。我现在连个人U盘里都会常备一份Python 3.8和3.10两套环境的离线包因为遇到内网、隔离环境、临时演示这种场景的次数实在太多了。7. 安装完成后的收尾动作自检比版本检查更可靠7.1 用一行命令验证最核心的功能通路离线安装完成后不要急着跑业务脚本先做一次最小功能验证。我通常会让机器执行这样一段代码python -c import numpy as np import pandas as pd import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt arr np.array([1.0, 2.0, 3.0]) s pd.Series(arr) fig, ax plt.subplots() ax.plot(s) fig.savefig(/tmp/test_plot.png) print(all ok) 这段代码同时覆盖了三层验证numpy的数组运算、pandas的Series创建、matplotlib在无图形界面环境下Agg后端的绘图和保存。如果能正常打印 all ok说明三个包的安装基本没有大问题。用Agg后端这一点值得多说一句很多服务器是没有图形界面的matplotlib默认的绘图后端初始化可能会尝试连接显示设备导致脚本在plt.subplots()这一步异常退出。在服务器环境里绘图脚本开头加上matplotlib.use(Agg)是一个很常见也很实用的习惯。7.2 自检脚本跑通后的额外检查项自检脚本通过只是第一步。我建议再检查三件事pip list看一下安装的版本确认和requirements里锁定的版本完全一致。这个很重要pip有时候会因为你手动操作装错版本而不自知。运行一遍业务脚本里的核心数据处理链路确认pandas和numpy之间的版本配合没有问题。毕竟有的代码会用到np.float、np.int这类老API自检脚本不会轻易暴露但业务代码一跑就崩。如果后续还要装别的包记住以后任何新包都先回准备机更新离线目录保证离线环境长期处于版本可控的状态。这一步做完离线环境的维护才算真正闭环。我见过太多人在import成功了之后就以为大功告成结果第二天同事跑代码时才发现某个功能因为版本不兼容静默失败那种排查成本远超当初多花五分钟做自检。
返回列表