有时候人不在目标服务器旁边,又要装一堆 Python 依赖,最常用的办法就是在本地先把 pip 包下载好,拷过去离线安装。但很多人在这一步就卡住了:明明本地是 Windows x64,目标机器是 Linux ARM64,直接pip download一堆命令跑完,拷过去才发现装不上。问题出在没搞清楚 pip download 默认只按“当前解释器 + 当前操作系统 + 当前 CPU 架构”去拉包。这篇就把怎么指定 CPU 类型和 OS 类型下载 pip 包讲透,包括每个参数背后的匹配规则、具体命令模板、常见报错和处理方法。
1. pip download 默认行为与为什么要改平台参数
1.1 默认下载的是“当前环境能装的包”
pip download在绝大多数人的认知里就是pip install的下载版:它会先分析当前环境的 Python 版本、操作系统、CPU 架构,然后从 PyPI 上找满足这些条件的 wheel 包,下载到指定目录。听起来没问题,但换个目标平台就不行了。
举个例子,你的开发机是 Windows 10 x64,安装的是 Python 3.10,这时候执行:
pip download numpy -d ./offlinepip 会优先下载类似numpy-1.26.4-cp310-cp310-win_amd64.whl的文件。文件名里的win_amd64就是当前平台生成的 wheel 标签。如果你要把这个包拷到一台 Linux aarch64(ARM64)服务器上,Linux 环境根本不会认win_amd64标签,安装时直接提示“is not a supported wheel on this platform”。
所以跨平台下载的第一步,就是明确告诉 pip:别猜了,我就是要给另一个 CPU / OS 下载包。核心参数就是--platform、--python-version、--implementation、--abi,再用--only-binary=:all:确保只拉预编译的 wheel,不拉源码包。
1.2 pip 是怎么用文件名匹配平台的?
要理解怎么指定参数,先要明白 pip 的匹配逻辑。PyPI 上的 Python 包有两种常见形态:wheel(.whl)和源码发布包(.tar.gz/.zip)。wheel 文件名本身就是规范化的兼容性标签,格式是:
{distribution}-{version}-{python tag}-{abi tag}-{platform tag}.whl例如pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl,拆开看:
cp311:Python 实现 + 版本,CPython 3.11。- 第二个
cp311:ABI 标签,表示二进制接口适用于 CPython 3.11。 manylinux_2_17_x86_64、manylinux2014_x86_64:平台标签,表示在 ManyLinux 2014 或更新规范下的 x86_64 系统可运行。
pip 在解析时,会把自己当前环境的标签集合(来自sysconfig.get_platform()、sys.implementation等)与 wheel 文件名里的标签做交集。有交集就能装,没交集就跳过。--platform、--python-version、--abi这三个参数的组合,实际上就是帮你“伪造”目标环境的标签集合,让 pip 认为当前就是在目标平台上操作。
注意一点:不是随便填个--platform就能成功。wheel 文件的平台标签必须和你指定的--platform恰好匹配,或者能形成兼容关系。比如指定--platform manylinux2014_x86_64,pip 会匹配所有平台标签为manylinux2014_x86_64或兼容的manylinux_2_17_x86_64包,但不会匹配win_amd64。
1.3 什么时候才需要手动指定平台?
我总结了几类必须手写平台参数的场景,你可以对照一下自己属于哪种:
- 离线安装到同架构但不方便联网的环境:比如本地 CentOS 7 下载,目标也是 CentOS 7,这种一般不用指定,默认即可。但为了保险,可以用
--platform manylinux2014_x86_64 --python-version 3.x把包固定下来。 - 跨 CPU 架构:本地是 x86_64,目标服务器是 ARM64(aarch64)或反之。这是最常见的需求。
- 跨操作系统:本地 Windows,目标是 Linux;或者本地 Linux,目标是 macOS。特别是打包给同事、发布到容器镜像、构建交叉编译环境。
- 目标环境的 Python 版本和本地不同:比如本地 Python 3.12,目标服务器还是 Python 3.8。即使操作系统一致,也需要指定
--python-version 38,否则 cp312 的 wheel 在 cp38 下装不了。
还有一类隐藏需求:目标机器根本没有 Python 环境,只是需要一个 wheel 包交给固件环境、嵌入式系统或者特殊运行时处理。这时候你就要精确指定--implementation和--abi,甚至要查 Python 版本对应的 ABI 标签。
2. 核心参数逐一拆解,别瞎填
2.1 --platform 怎么填才准确
--platform接受的是 wheel 平台标签,不是随便写个“linux”就行。它一般遵守manylinux、win、macosx、musllinux等规范。常见的写法:
| 目标环境 | 平台标签示例 |
|---|---|
| Linux x86_64,glibc 2.17+ | manylinux2014_x86_64或manylinux_2_17_x86_64 |
| Linux ARM64,glibc 2.17+ | manylinux2014_aarch64或manylinux_2_17_aarch64 |
| Linux ARMv7 / ARMv8 32位 | manylinux2014_armv7l |
| Linux x86_64,musl libc(Alpine) | musllinux_1_2_x86_64 |
| Windows x64 | win_amd64 |
| Windows 32位 | win32 |
| macOS Intel | macosx_10_9_x86_64、macosx_10_10_x86_64等 |
| macOS Apple Silicon | macosx_11_0_arm64、macosx_10_9_universal2等 |
这里有个细节容易踩坑:很多现代 Linux 发行版的 wheel 只提供manylinux_2_28_x86_64,而 older 的manylinux2014_x86_64可能没有对应版本。这时候你就要把--platform写得更具体,比如manylinux_2_28_x86_64。但问题来了:不是所有包都会为每个平台发布 wheel,像一些纯 Python 库(requests、urllib3)实际平台标签是py3-none-any,这类包任何平台都能用,指定任意--platform都会拉下来。
所以最稳妥的做法是,在目标机器上执行pip debug --verbose,看输出里Compatible tags列表,从中挑一个和你要下载的平台一致的标签。比如 ARM64 服务器上会出现:
cp39-cp39-manylinux_2_17_aarch64 cp39-cp39-manylinux2014_aarch64 cp39-cp39-linux_aarch64那你下载时--platform manylinux2014_aarch64就是安全的,它能匹配到 manylinux_2_17_aarch64 的包。后面我会给详细操作。
2.2 --python-version 和 --implementation 的匹配规则
--python-version填目标环境的 Python 版本,但格式有讲究,比如 Python 3.9 写39,Python 3.11 写311,不要带小数点。它对应 wheel 文件名里的 cp39/cp311 这样的标签。
--implementation一般填cp(CPython)、pp(PyPy)、ip(IronPython)等。绝大多数场景填cp,因为 PyTorch、numpy 等核心库通常只发 CPython 的 wheel。但如果你目标环境是 PyPy,就要改成pp,并且--abi也要跟着变化,比如pypy39_pp73。
需要注意,--python-version不是孤立的,它会和--abi一起决定最终的匹配范围。比如你指定--python-version 39 --implementation cp --abi cp39,pip 只会下载cp39-cp39-*的 wheel。但如果目标 Python 3.9 编译时使用了较新的 ABI 策略,可能既有cp39-cp39也有cp39-abi3(stable ABI)的 wheel。这时候如果你强制--abi cp39,会把abi3的包全部过滤掉,导致下载失败或包不完整。所以如果是下载目标环境的常规 CPython,我一般建议--abi也写成同标签的 cpXY,如果失败再放宽到--abi abi3或干脆不指定(不指定时 pip 会默认用当前环境的 ABI 标签,跨平台会有问题,所以还是建议显式指定)。
--abi常见值对应关系:
| Python 目标版本 | ABI 标签 |
|---|---|
| CPython 3.8 | cp38 |
| CPython 3.9 | cp39 |
| CPython 3.10 | cp310 |
| CPython 3.11 | cp311 |
| CPython 3.12 | cp312 |
| CPython 3.13 | cp313 |
| 兼容稳定 ABI | abi3 |
| 纯 Python 无扩展 | none |
但有个更省事的小技巧:当你不确定时,--implementation cp --abi cp39就够用了。很多包同时满足多个 ABI 标签,pip 会在候选集里挑选最合适的。
2.3 --only-binary=:all: 到底起了什么作用
没有--only-binary=:all:,pip download 遇到找不到匹配 wheel 的包时,会自动回头找 sdist 源码包。源码包可不是目标平台能用的,下载下来还要在目标机器上现场编译,既慢又容易缺编译工具链。更麻烦的是,如果你指定了--platform是 ARM64,但某些包没有 ARM64 的 wheel,pip 可能直接拉个tar.gz下来,你测了半天最后才发现这不是预编译产物。
所以跨平台下载时,我几乎总是写:
pip download --no-deps --only-binary=:all: ...:all:的意思是“只用 wheel,绝不接受源码包”。这么一来 pip 会明确报错而不是偷偷下载 sdist,你就能及时知道这个包是不是不支持目标平台。如果确认目标平台没有 wheel,那只能考虑目标机器上源码编译,或者寻找其他替代包。
补充一点:--only-binary=:all:和--no-binary=:all:是相反的。前者只要二进制 wheel,后者只要源码。跨平台下载默认是前者。
2.4 --no-deps 和 -d 的使用时机
很多人第一次用pip download会漏掉--no-deps。不写的话,pip 会递归下载当前包的所有依赖,这其实很多时候也是你想要的。但问题是,依赖解析可能受到本地已安装包的影响,也可能把一些当前平台独有的包(比如colorama在 Windows 下才需要)拉下来,导致离线目录里出现冗余或平台不匹配的文件。
我建议的做法分两步:
- 第一步,先在目标机上用
pip download -r requirements.txt -d /tmp/offline(不指定--platform)生成一份完整的依赖列表并下载,因为这时候 pip 会按目标机的真实环境解析。 - 第二步,如果反过来是跨平台给目标机备包,就在本地上台用
--platform参数逐个包下载,并且加上--no-deps,避免把本地平台的依赖误拉进来。依赖关系自己单独处理。
-d指定输出目录,这个很简单,但有个坑:目录不存在时 pip 会自动创建,所以不用提前 mkdir。可如果你用了--target(安装到目录)和-d搞混,下载目录里会出现一堆.whl文件而不是被展开的包目录,这是正常现象,离线安装时直接用pip install --no-index --find-links=/offline package即可。
3. 实战:给 Linux ARM64、Windows x64、macOS 分别下载包
3.1 命令模板一:本机 Windows,给 Linux aarch64 下载 pandas
假设你的开发机是 Windows,目标服务器是 ARM64 架构、跑 Ubuntu 20.04、Python 3.9。要下载 pandas 及其依赖,可以这样分步操作。
第一步,确定平台标签。ARM64 Ubuntu 20.04 的 glibc 是 2.31,满足 manylinux_2_17 的要求,所以平台标签写manylinux2014_aarch64或manylinux_2_17_aarch64都可以。Python 3.9 对应cp39,ABI 也是cp39。
第二步,执行下载:
pip download \ --only-binary=:all: \ --platform manylinux2014_aarch64 \ --python-version 39 \ --implementation cp \ --abi cp39 \ -d ./linux_arm64_pkgs \ pandas==2.2.2这里的--no-deps先不加,原因是 pandas 依赖 numpy、python-dateutil、pytz、tzdata 等,如果不解析依赖,离线目录不完整,目标机上还得单独补。但用默认解析有一个风险:本地 Windows 的依赖解析结果可能和 Linux 不完全一样。实际上 pip download 在指定--platform后,依赖解析也是基于该平台标签进行的,所以一般没问题。如果出现了依赖包缺失,再把--no-deps打开,逐个补下。
下载完成后,./linux_arm64_pkgs目录里应该出现类似:
pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl看到文件名里的aarch64就说明平台匹配正确。
3.2 命令模板二:本机 Linux,给 Windows x64 下载 openpyxl
换一种方向,目标是 Windows 10 x64、Python 3.10。平台标签用win_amd64,Python 标签cp310,ABIcp310:
pip download openpyxl==3.1.2 \ --only-binary=:all: \ --platform win_amd64 \ --python-version 310 \ --implementation cp \ --abi cp310 \ -d ./win_amd64_pkgsopenpyxl 是纯 Python 包,但依赖的 et_xmlfile 也是纯 Python,所以这里其实不指定 platform 也能下载。但指定后能确保产物中不会因为某些平台特定依赖而出错。如果包里有 C 扩展(比如lxml),你就会看到lxml-*.whl的后缀是win_amd64,这就对了。
有一个容易忽略的点:win_amd64标签的 wheel 文件在 Linux 本地可以对内容做校验(比如用 unzip 查看),但绝不能直接解开导入。你只需要确认文件名符合预期,不要尝试在本地安装。
3.3 命令模板三:给 macOS Apple Silicon 下载 numpy
目标 Mac 是 Apple Silicon(M1/M2/M3)、macOS 11.0+、Python 3.11。平台标签可以写macosx_11_0_arm64,也可以尝试macosx_10_9_universal2。universal2 的 wheel 同时支持 x86_64 和 arm64,在 M 系列 Mac 上能装,而且兼容性更好。
pip download numpy==1.26.4 \ --only-binary=:all: \ --platform macosx_11_0_arm64 \ --python-version 311 \ --implementation cp \ --abi cp311 \ -d ./macos_arm64_pkgs如果你不确定目标 macOS 版本,用macosx_10_9_universal2往往更保险,因为 macOS 的部署目标往前往后都能兼容。但注意:有些包只发布macosx_11_0_arm64,有些只发 universal2,由于 PyPI 上可能同时存在多个版本,建议先到 PyPI 页面看该版本实际提供的 wheel 文件列表再决定。
3.4 一次下载多个包:requirements.txt 该怎么配合
如果项目依赖比较多,可以在目标平台上维护一份 requirements.txt,然后在本地跨平台下载:
pip download \ --only-binary=:all: \ --platform manylinux2014_x86_64 \ --python-version 311 \ --implementation cp \ --abi cp311 \ -r requirements.txt \ -d ./linux_x64_pkgs这比一条条执行快得多。但前提是 requirements.txt 里的版本要存在对应平台的 wheel,否则 pip 会直接报错。遇到这种情况,解决办法是看具体是哪个包,判断它是不是纯 Python(py3-none-any),如果是纯 Python,其实可以不放在下载清单里,直接让目标机从离线目录安装时读取依赖即可;如果是有 C 扩展的包且没有目标平台 wheel,那就只能换成支持目标平台的替代库,或者在目标机上编译。
3.5 怎么确认目标机的真实兼容标签
不需要在目标机上装 Python 也可以查“通用标签列表”,但更准确的方法是让有目标机的人执行一行命令:
pip debug --verbose输出里有一段:
Compatible tags: 372 cp312-cp312-manylinux_2_28_x86_64 cp312-cp312-manylinux_2_27_x86_64 ...把这里所有的manylinux_*标签拍下来,选择范围更大的那个(比如manylinux_2_28_x86_64说明系统 glibc 较新,兼容manylinux2014)。如果没有目标机环境,也可以用python -m pip debug --verbose在本地模拟查看当前机器标签,再根据 CPU 架构手写目标标签。经验法则:glibc 2.17 对应 manylinux2014,glibc 2.28 对应 manylinux_2_28,glibc 2.35 对应 manylinux_2_35。在较新的发行版上能用manylinux_2_28就不必纠结manylinux2014。
4. 常见报错与排查技巧
4.1 “No matching distribution found” 是怎么回事
这是跨平台下载中最常见的报错。出现这句话,说明 pip 根据你给的--platform、--python-version、--abi组合,在 PyPI 上没有找到满足条件的 wheel。
常见原因有三个:
- 版本号对不上:比如你指定了
--platform manylinux2014_aarch64,但该包最新版只提供 manylinux_2_28_aarch64,或者干脆没有 aarch64 wheel。这时可以把版本放宽,或者查 PyPI 文件列表确认。 - ABI 写得太死:比如
--abi cp39,但包只发布了cp39-abi3的 wheel。遇到这种情况,把--abi改成abi3,或者不区分 ABI 直接一次尝试多个。 - Python 版本不存在对应 wheel:比如你给 Python 3.7 下载某个包,但该包最低支持 3.8,那自然无解。
排查时可以先用不带--platform的命令跑一次:
pip download 包名==版本 --no-deps --only-binary=:all: -d /tmp/test如果本地能下载,说明 PyPI 有对应包,问题出在平台的标签上。然后去 PyPI 的“Download files”页面手工看有哪些 wheel 后缀,逆向得出该填什么参数。
4.2 “is not a supported wheel on this platform” 出现在本机安装阶段
有时候你辛辛苦苦下载好一包.whl,拿到目标机上用pip install /path/to/lxml-*.whl安装,结果报错不支持。这不一定是下载的参数错了,可能是安装命令有问题。比如你把manylinux_2_17_aarch64的 wheel 拷到了 x86_64 的机器上,自然装不了。或者你把win_amd64的 wheel 拷到了 32 位 Python 环境里。
正确的做法是,先在目标机上pip debug --verbose看看真实支持哪些标签,再把它和 wheel 文件名的 platform tag 对齐。如果对不齐,说明离线包给错了,重新按目标平台下载。
一个更隐蔽的坑:同一个 wheel 文件名里可能包含多个平台标签,比如numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl,这个文件实际上既能匹配 manylinux_2_17_x86_64,也能匹配 manylinux2014_x86_64。你在本地下载时用了前一个标签,到了目标机上安装依然有效。所以文件名里多个标签不是错误,反而说明它兼容范围更广。
4.3 下载目录权限导致 “拒绝访问” 或 os error 5
跨平台下载时,如果你把-d指定到系统保护目录(比如 Windows 的C:\Program Files\...或 Linux 的/root之外某个无写权限路径),pip 会在写文件时报错,可能看到类似error: 拒绝访问。(os error 5)的信息。这通常不是 pip 本身的问题,而是目录权限不足。
建议把下载目录放到用户目录下,比如~/offline_pkgs或当前工作目录下的./offline。如果是 Windows,避免放在C:\根目录或Program Files下。另外,如果之前执行过 pip 时用了 sudo 或管理员终端,后续普通终端可能因为缓存权限不一致而报错。这时候清理 pip 缓存即可:
pip cache purge这个报错和“os error 5”里的 os 没有任何关系,它只是操作系统层面的错误码,不用往系统或者平台架构上想,先检查写路径。
4.4 离线安装时提示 “has requirement xxx, but you have yyy”
下载阶段不会报这个,等到目标机离线安装时才出现,原因是依赖版本冲突。常见于你下载时用了--no-deps导致依赖清单不完整,或 requirements.txt 里的版本约束被安装到目标机上时,目标机已有其他版本的库。
我的建议是离线安装时依然使用一个完整的 wheelhouse:
pip install --no-index --find-links=./linux_arm64_pkgs -r requirements.txt这条命令会强制只用./linux_arm64_pkgs里的 wheel,如果缺少依赖会明确提示缺哪个,方便你从下载目录补。如果下载目录里已经包含了所有依赖但还是报冲突,那就要检查 requirements.txt 的版本范围是否过窄,比如同时要求 numpy==1.24 和 pandas==2.2(pandas 2.2 需要 numpy>=1.22.4,其实没问题,但有些组合会有 bug),这时候没有捷径,只能调整版本约束让它们兼容。
4.5 下载 py3-none-any 纯 Python 包时的特殊处理
像requests、idna、certifi这样的包,wheel 文件名通常是requests-2.31.0-py3-none-any.whl。它们的平台标签是any,ABI 是none,所以不管你指定什么--platform、--python-version,只要 Python 支持 Py3,都能下载。这类包也能直接放到任何平台的离线目录中。
但注意:如果你用了--abi cp39,pip 在匹配py3-none-any时,实际上会因为py3和cp39的兼容关系而成功。因为py3标签本身表示包装后可以运行于 Python 3 任何版本,包含 cp39。所以不要因为文件名是py3-none-any就认为它是“废包”,它在离线安装时是通用的。
4.6 常用排查命令速查表
| 场景 | 命令 |
|---|---|
| 查看当前环境兼容标签 | pip debug --verbose |
| 查看某个包的已发布文件 | 直接去 PyPI 项目页面的 Download files 目录 |
| 只下载不装,忽略依赖 | pip download 包名 --no-deps -d ./pkgs |
| 强制只下载 wheel | pip download 包名 --only-binary=:all: -d ./pkgs |
| 查看下载目录里的包名和版本 | pip list --path ./pkgs |
| 离线安装目录中的所有包 | pip install --no-index --find-links=./pkgs 包名 |
| 清空 pip 缓存 | pip cache purge |
5. 跨平台下载的几条独家经验
5.1 不要迷信 purelib 包,也要注意包内部的动态依赖
很多纯 Python 包虽然本身是py3-none-any,但它会在安装时通过 setup.py 动态拉取系统库或者 ctypes 加载本机.so/.dll。这类包下载时没问题,但离线安装到目标机后运行时才发现缺系统库。比如一些涉及 Bluetooth、GPU 调用、串口通信的库,它们依赖系统级别的 libusb、libcuda 等。遇到这种情况,光靠 pip 参数解决不了,还得把对应系统依赖也放进离线部署方案里。
5.2 用“target 目录”验证而不是强行安装
有些人在本地下载完 Windows 的 wheel 后,想临时验证能不能用,会尝试pip install到本地 Python。这几乎必失败,因为没有 Windows 平台的 Python 扩展能在 Linux 上导入。验证方法很简单:只需要用unzip -l看看解压出来的.pyd或.so文件是否是目标平台对应的扩展名。比如 Windows wheel 里是xxx.pyd,Linux wheel 里是xxx.so,macOS 里也是.so但依赖 Mach-O 格式。确认扩展名一致,基本就能判断下载无误。
5.3 一次下载多个 Python 版本的方案
有时候目标环境不是单一版本,比如同一台服务器上有 Python 3.8 和 Python 3.11 两个项目。可以分别下载两个目录:
pip download --platform manylinux2014_x86_64 --python-version 38 --implementation cp --abi cp38 -d ./wheelhouse/py38 -r requirements.txt pip download --platform manylinux2014_x86_64 --python-version 311 --implementation cp --abi cp311 -d ./wheelhouse/py311 -r requirements.txt这样目录是隔离的,安装时也不用担心 cp38 的包被 3.11 引用。如果目标机器 CPU 架构不同,比如同时有 x86_64 和 ARM64 节点,目录加上架构后缀更好,例如wheelhouse/arm64_py38、wheelhouse/x64_py311。
5.4 优先使用目标机原生 Python 版本的关键字
如果目标环境已经安装好了 Python,最省事的是直接在目标机上执行:
python -m pip download -r requirements.txt -d ./offline不需要指定任何--platform、--python-version,因为 pip 此时用的就是目标机的真实标签,绝不会错。只有当目标机无法联网、或你需要在生产环境之外预先准备离线包时,才需要手动指定参数。我经常遇到的情况是,目标服务器连不上外网,但有一台装了同样 Python 版本的跳板机,那就在跳板机上下载,连参数都不用写。
5.5 版本锁定的重要性
跨平台下载时,如果 requirements.txt 里写的是不精确的版本,比如numpy>=1.24,pip 在不同时间下载到的版本会不同。今天下载 1.26.4,明天可能变成 1.26.5,这会让离线包清单不可复现。我的习惯是所有下载都用==把版本钉死,或者下载完后生成一份pip freeze结果,下次直接按冻结版本下载。否则一旦目标环境已经安装过部分旧版本包,依赖解析可能会挑一个和你离线目录不同的版本,导致冲突。
6. 写在最后的实操心得
踩过几次坑之后,我现在的跨平台下载流程基本固定了:拿到目标机器的pip debug --verbose输出,确认 Python 版本、glibc 和架构;然后写一个带--only-binary=:all:的下载命令,把所有依赖装进一个 wheelhouse;最后在目标机上用--no-index --find-links安装。这套流程走下来,几乎没有再因为平台匹配问题翻过车。
最后再分享一个小技巧:如果你给 ARM64 Linux 下载不到某个包的 wheel,不妨看看 PyPI 文件列表里是不是只有manylinux_2_28_aarch64而没有manylinux2014_aarch64,那就把--platform改成manylinux_2_28_aarch64,同时确认目标系统 glibc 版本大于等于 2.28。反正只要目标机 glibc 比 wheel 要求的新,兼容性就是成立的,不必死守 older 标签。