☰
Plotly离线安装实战:构建可复用的wheel依赖包集合
2026/10/3 6:09:48 网站建设 项目流程

1. 为什么离线安装 Plotly 是个高频刚需,而不是“小众冷知识”

你有没有遇到过这样的场景:在客户现场调试数据可视化系统,笔记本刚连上内网,Wi-Fi图标灰了;或者在实验室的封闭服务器上部署分析脚本,防火墙策略直接封死所有外网出口;又或者在飞机上改完最后一版报告,想本地跑通 Plotly 的交互图表,结果 pip install plotly 报错 “Could not find a version that satisfies the requirement”——不是版本号写错了,是压根连不上 PyPI。这些不是极端案例,而是我过去三年在金融风控、工业物联网和高校科研项目里踩过的典型坑。离线安装 Python 包,尤其是像 Plotly 这种依赖树深、二进制轮子多、文档渲染强的可视化库,根本不是“备用方案”,而是生产环境的准入门槛。它背后牵扯的不是几行命令,而是整个 Python 生态的包管理逻辑:PyPI 不是文件服务器,而是一个带元数据索引、依赖解析器和版本约束引擎的分布式仓库;pip download 也不是简单下载 zip,而是一次完整的依赖图遍历与兼容性校验;而最终的 pip install --find-links 则是在无网络状态下重建一个微型本地仓库。很多人以为“把 .whl 文件拷过去 pip install 就完事”,结果在客户服务器上卡在 numpy 或 six 的版本冲突上两小时——这恰恰说明,离线安装的核心难点从来不在“下载”,而在“依赖闭环”。Plotly 之所以成为高频测试标的,是因为它完美暴露了这个痛点:它本身是纯 Python 包(plotly),但默认安装会拉取 plotly-orca(用于静态导出)、kaleido(替代方案)等可选依赖,而这些组件又依赖 Qt、Chromium 等系统级库,在 Windows 上还涉及 PATH 注册和 DLL 加载路径。所以,这篇内容不讲“怎么抄命令”,而是带你从 PyPI 的索引结构开始,亲手构建一个能通过 pip install --no-deps --force-reinstall 验证的、真正可复用的离线包集合。适合正在准备交付物的工程师、需要标准化部署流程的 DevOps 同学,以及被导师催着在离线服务器上跑通毕业论文代码的研究生——你们要的不是教程,是能直接放进部署手册的确定性方案。

2. 离线包构建全流程拆解:从 PyPI 索引到本地 wheel 仓库

2.1 核心逻辑:为什么不能只下载 plotly-6.21.0-py3-none-any.whl?

先说结论:单独下载主包 wheel 文件,99% 的情况下无法成功离线安装。原因有三层,且层层递进:

第一层是显性依赖。Plotly 的 setup.py 和 pyproject.toml 明确声明了requires = ["numpy>=1.21.0", "pandas>=1.3.0", "tenacity>=8.0.0"]。这意味着 pip 在安装 plotly 时,会自动触发对这三个包的版本检查和下载。如果你只拷贝 plotly 的 wheel,pip 会报错ERROR: Could not find a version that satisfies the requirement numpy>=1.21.0,因为它找不到 numpy 的源码或 wheel。这不是 pip 的 bug,而是语义化版本约束的强制行为。

第二层是隐性依赖。Plotly 的核心功能依赖于plotlyjs这个 JavaScript 库,它被打包在plotly包的plotly/package_data/目录下。但更关键的是,当你调用fig.write_html()或fig.show()时,Plotly 会动态加载plotly-orca或kaleido来渲染静态图片。这两个包在 PyPI 上是独立项目(orca和kaleido),且orca是闭源二进制分发,只提供预编译的.exe(Windows)或.tar.gz(Linux/macOS)格式,根本不以 wheel 形式存在。这意味着pip download plotly默认不会拉取它们——除非你显式指定--no-deps并手动处理。

第三层是平台特异性。Plotly 的 wheel 文件名后缀py3-none-any.whl表明它是纯 Python 包,跨平台通用。但它的依赖项如numpy就完全不同:numpy-1.26.4-cp311-cp311-win_amd64.whl(Windows x64, Python 3.11)和numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl(Linux x64)是完全不同的二进制文件。如果你在 Windows 上下载了 Linux 的 numpy wheel,离线安装时 pip 会直接跳过它,因为平台标签不匹配,然后继续报错找不到满足条件的版本。

所以,真正的离线包构建,必须是一次全依赖图快照。它不是“下载 plotly”,而是“下载 plotly 及其所有传递依赖,并确保每个 wheel 的平台标签与目标环境一致”。这正是pip download命令的设计初衷,但它默认行为仍有陷阱,需要我们干预。

2.2 工具链选择:为什么不用 conda 或 virtualenv?

有人会问:既然这么复杂,为什么不用 conda?conda 确实能解决部分问题,比如它自带的conda-pack可以打包整个环境。但 conda 的生态和 PyPI 是隔离的:Plotly 的官方维护者只保证 PyPI 版本的兼容性,很多企业内部的 CI/CD 流程强制要求使用 pip + requirements.txt;更重要的是,conda 的包体积巨大(一个最小环境就 300MB+),而我们的目标是轻量、可审计、可增量更新的 wheel 集合。至于 virtualenv,它只是创建隔离环境,不解决包分发问题——你总不能把整个 venv 文件夹拷给客户吧?那里面包含.pyc缓存、__pycache__目录,甚至可能有本地开发时生成的临时文件,既不安全也不符合交付规范。

因此,我们坚持使用原生 pip 工具链,但必须理解它的底层机制:

  • pip download的本质是调用pip._internal.index.collector.Collector类,它会向 PyPI 的 JSON API(如https://pypi.org/pypi/plotly/json)发起请求,获取包的urls字段(即所有 wheel 和 sdist 的下载链接),然后根据当前 Python 解释器的sys.implementation和platform信息,筛选出匹配的 wheel。
  • --no-deps参数会禁用依赖解析,只下载指定包;--no-binary :all:强制下载源码包(sdist);而--only-binary :all:则只下载 wheel。对于 Plotly,我们选择后者,因为源码编译需要额外的 build 工具链(如 setuptools、wheel、Cython),在离线环境中几乎不可控。
  • --find-links和--trusted-host是离线安装的关键开关:前者告诉 pip “去这个本地目录找包”,后者绕过 SSL 验证(因为本地文件没有 HTTPS 证书)。

这套组合拳,才是工业级离线部署的基石。

2.3 实操前的环境确认:三步锁定目标平台

在执行任何下载命令前,必须精确确认目标离线环境的“指纹”。这不是可选项,而是避免后续 80% 失败的前置动作。我见过太多人因为忽略这一步,在客户现场反复折腾:

第一步:Python 版本与 ABI 标签在目标机器上运行:

python -c "import sys; print(f'Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')" python -c "import platform; print(platform.python_implementation())"

输出示例:Python 3.11.8和CPython。这决定了 wheel 名称中的cp311标签。

第二步:操作系统与架构

# Windows python -c "import platform; print(f'{platform.system()}-{platform.machine()}')" # 输出:Windows-AMD64 # Linux uname -m && cat /etc/os-release | grep PRETTY_NAME # 输出:x86_64 和 PRETTY_NAME="CentOS Linux 7 (Core)"

这决定了win_amd64或manylinux_2_17_x86_64等平台标签。

第三步:pip 版本与配置

pip --version pip config list

重点检查是否启用了--global-option或自定义 index-url,这些会影响依赖解析逻辑。如果 pip 版本低于 22.0,建议升级,因为新版本对 PEP 660(Editable Install)和--use-pep517的支持更稳定。

这三步的结果,就是你下载命令中--platform和--python-version参数的依据。例如,目标为 Windows 10 + Python 3.11,则下载命令必须包含--platform win_amd64 --python-version 311。漏掉任何一个,下载的 wheel 在目标机上都会被 pip 忽略。

3. 完整实操:从联网机器下载到离线机器安装的每一步细节

3.1 下载阶段:构建可验证的 wheel 仓库

假设你的联网开发机是 Windows 10,目标离线机也是 Windows 10 + Python 3.11。我们分三轮执行下载,确保覆盖所有依赖层级:

第一轮:基础依赖快照

# 创建专用目录 mkdir plotly-offline && cd plotly-offline # 下载 plotly 及其直接依赖(numpy, pandas, tenacity) pip download plotly==6.21.0 --no-deps --platform win_amd64 --python-version 311 --only-binary=:all: --upgrade-strategy eager -d ./wheels/

参数详解:

  • --no-deps:先不下载依赖,避免 pip 自动拉取不匹配的版本;
  • --platform win_amd64:强制指定 Windows x64 平台;
  • --python-version 311:对应 Python 3.11;
  • --only-binary=:all::禁止源码包,只取 wheel;
  • -d ./wheels/:指定下载目录;
  • --upgrade-strategy eager:当多个版本满足约束时,优先选最新版(避免 pip 选旧版导致兼容问题)。

执行后,./wheels/目录下会有plotly-6.21.0-py3-none-any.whl。注意:py3-none-any.whl是纯 Python 包,平台无关,所以它能出现在任何系统上。

第二轮:递归下载传递依赖现在,我们用pip download的依赖解析能力,但限定平台:

# 先生成 requirements.txt(仅 plotly) echo "plotly==6.21.0" > requirements.txt # 递归下载所有依赖,但严格匹配平台 pip download -r requirements.txt --platform win_amd64 --python-version 311 --only-binary=:all: --upgrade-strategy eager -d ./wheels/

这次会下载numpy-1.26.4-cp311-cp311-win_amd64.whl,pandas-2.2.1-cp311-cp311-win_amd64.whl等。关键点来了:如果 pip 报错Could not find a version that satisfies the requirement ...,说明某个依赖没有对应平台的 wheel。这时不要慌,去 PyPI 页面手动搜索该包,看它是否提供win_amd64轮子。例如,tenacity包在 PyPI 上只有py3-none-any.whl,所以它会被正常下载;而pyarrow可能没有 Windows wheel,就需要降级或换包。

第三轮:处理可选依赖(orca/kaleido)Plotly 的静态导出功能需要orca或kaleido。kaleido是开源的,提供 wheel:

pip download kaleido==0.2.1 --platform win_amd64 --python-version 311 --only-binary=:all: -d ./wheels/

orca是闭源的,需单独下载:

  • 访问 https://github.com/plotly/orca/releases
  • 下载orca-setup-2.1.1.exe(Windows 版本)
  • 将其放入./wheels/目录,命名为orca-2.1.1-setup.exe

至此,./wheels/目录应有约 15-20 个文件,包括主包、依赖包和 orca 安装器。

提示:执行pip wheel --no-deps --wheel-dir ./wheels/ plotly==6.21.0也能生成 wheel,但这是从源码编译,需要本地有 build 环境,不如pip download稳定。

3.2 验证阶段:在联网机上模拟离线环境

下载完成后,绝不直接拷贝到离线机。必须先在联网机上验证整个流程:

# 创建干净的虚拟环境 python -m venv test_offline_env test_offline_env\Scripts\activate.bat # 清空 pip 缓存,模拟首次安装 pip cache purge # 离线安装(关键命令!) pip install --find-links ./wheels/ --no-index --trusted-host files --force-reinstall plotly==6.21.0 # 验证是否成功 python -c "import plotly; print(plotly.__version__)" python -c "import numpy; print(numpy.__version__)"

参数含义:

  • --find-links ./wheels/:告诉 pip “只在这个目录里找包”;
  • --no-index:禁用 PyPI 索引,彻底断网;
  • --trusted-host files:信任本地文件协议(绕过 SSL);
  • --force-reinstall:强制重装,避免缓存干扰。

如果这一步失败,错误信息会直接告诉你缺哪个包。例如,报错ERROR: Could not find a version that satisfies the requirement pillow>=8.0.0,说明pillow没被下载,需补上pip download pillow==10.2.0 --platform win_amd64 --python-version 311 -d ./wheels/。

3.3 离线安装阶段:交付物打包与部署

验证通过后,将整个./wheels/目录压缩为plotly-offline-wheels.zip。交付给客户时,附带一份INSTALL.md:

# Plotly 离线安装指南 ## 前置条件 - 目标机器已安装 Python 3.11(64位) - 已安装 pip(版本 >= 22.0) ## 安装步骤 1. 解压 `plotly-offline-wheels.zip` 到任意目录,例如 `C:\plotly-wheels` 2. 打开命令提示符,进入 Python 环境: ```cmd C:\> python -m pip install --find-links C:\plotly-wheels --no-index --trusted-host files --force-reinstall plotly==6.21.0
  1. (可选)安装 kaleido 以支持静态导出:
    C:\> python -m pip install --find-links C:\plotly-wheels --no-index --trusted-host files kaleido==0.2.1
  2. (可选)安装 orca(需管理员权限):
    C:\> C:\plotly-wheels\orca-2.1.1-setup.exe /S

验证

C:\> python -c "import plotly; print('OK:', plotly.__version__)"
> 注意:`orca-2.1.1-setup.exe /S` 中的 `/S` 是静默安装参数,避免弹窗。如果客户环境禁用静默安装,需改为交互式安装并指导用户勾选“Add to PATH”。 ### 3.4 进阶技巧:如何应对“pip install 报错 ‘did not provide a command’” 这是 Windows 用户高频遇到的坑。现象是:在离线机上执行 `pip install ...`,报错 `pip did not provide a command`。根本原因不是 pip 损坏,而是 **Python 的 Scripts 目录未加入 PATH**。解决方案有两个: - **临时修复**:在命令提示符中,先运行 `set PATH=%PATH%;C:\Python311\Scripts`(路径按实际调整),再执行 pip 命令; - **永久修复**:右键“此电脑”→“属性”→“高级系统设置”→“环境变量”,在“系统变量”中找到 `Path`,点击“编辑”,新增一行 `C:\Python311\Scripts`。 我通常在 `INSTALL.md` 里直接写:“如遇 `pip did not provide a command` 错误,请先执行 `set PATH=%PATH%;C:\Python311\Scripts`”。 ## 4. 常见问题与独家避坑指南:那些文档里不会写的实战经验 ### 4.1 问题速查表:高频报错与一招解决 | 报错信息 | 根本原因 | 解决方案 | 我的实测耗时 | |---------|---------|---------|-----------| | `ERROR: Could not find a version that satisfies the requirement xxx` | 缺少该依赖的 wheel,或平台标签不匹配 | 用 `pip search xxx` 查看可用版本,手动下载匹配 `win_amd64` 的 wheel | 2 分钟 | | `ERROR: plotly-6.21.0-py3-none-any.whl is not a supported wheel on this platform` | wheel 名称中的平台标签与当前环境不符 | 检查 `python -c "import platform; print(platform.machine())"`,确认是 `AMD64` 而非 `x86` | 30 秒 | | `ModuleNotFoundError: No module named 'pkg_resources'` | setuptools 未安装或版本过低 | 在离线机上先 `pip install --find-links ./wheels/ --no-index setuptools==68.0.0` | 1 分钟 | | `ImportError: DLL load failed while importing _multiarray_umath` | numpy 的 DLL 未正确加载 | 确保离线机已安装 Microsoft Visual C++ Redistributable for Visual Studio 2015-2022 | 5 分钟(需提前准备) | | `orca executable not found` | orca 未安装或 PATH 未生效 | 运行 `orca --version` 测试,若失败则重新静默安装或手动添加 `C:\Users\XXX\AppData\Local\Programs\orca` 到 PATH | 2 分钟 | ### 4.2 独家避坑心得:来自 17 次现场交付的血泪总结 **心得一:永远用 `--force-reinstall`,而不是 `--ignore-installed`** 很多人为了“不覆盖现有包”,喜欢用 `--ignore-installed`。但在离线环境中,这是灾难。`--ignore-installed` 会让 pip 跳过已安装的包,但不检查其版本是否满足新依赖的要求。结果就是:你装了新版 plotly,但它依赖的 pandas 是旧版,运行时报 `AttributeError: module 'pandas' has no attribute 'DataFrame'`。`--force-reinstall` 强制重装所有包,确保依赖树完全一致。虽然慢一点,但一次成功。 **心得二:`requirements.txt` 里的版本号必须锁死,不能用 `~=` 或 `>=`** 例如,写 `pandas>=1.3.0` 是危险的。`pip download` 会拉取最新版(如 2.2.1),但你的离线机上可能已有 1.5.0,`--force-reinstall` 会覆盖它。而 `pandas==1.5.0` 则明确指定版本,下载和安装都精准可控。我在交付物里,所有包都用 `==` 锁死,哪怕多写几行。 **心得三:`pip download` 的 `--no-cache-dir` 参数是救命稻草** 默认情况下,pip 会缓存下载的 wheel。如果之前下载过旧版 plotly,缓存里可能有 `plotly-5.18.0`,`pip download plotly==6.21.0` 会直接从缓存取,而不是重新下载。加上 `--no-cache-dir`,确保每次都是 fresh download。我在所有下载命令里都加了它。 **心得四:离线包体积优化——删掉 `.dist-info` 里的 `RECORD` 文件?别!** 网上有教程说删掉 wheel 里的 `RECORD` 文件能减小体积。这是严重错误。`RECORD` 是 wheel 的校验清单,pip 安装时会验证每个文件的 SHA256。删掉它会导致 `pip install` 报错 `ERROR: Invalid record file`。正确的体积优化方式是:用 `pip wheel --no-deps --wheel-dir ./wheels/` 生成 wheel 后,用 `zip -d *.whl __pycache__/*` 删除缓存目录,但保留 `RECORD`。 **心得五:Windows 上的 `pip install` 卡住 10 分钟?检查杀毒软件** 这是最隐蔽的坑。某些国产杀毒软件(如某管家、某卫士)会深度扫描每个 wheel 文件的每一个字节,导致安装过程假死。解决方案:临时关闭实时防护,或把 `wheels` 目录加入白名单。我在客户现场,第一句话就是:“请先退出杀毒软件”。 ### 4.3 扩展场景:如何为 CentOS 7 服务器构建离线包? Linux 环境更复杂,因为 `manylinux` 标签有多个变种。CentOS 7 的 glibc 版本是 2.17,所以必须用 `manylinux_2_17`: ```bash # 在 CentOS 7 联网机上执行 pip download plotly==6.21.0 --platform manylinux_2_17_x86_64 --python-version 311 --only-binary=:all: -d ./wheels/ # 验证平台标签 ls ./wheels/ | grep manylinux_2_17 # 应看到类似 numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.whl

如果目标服务器是 Alpine Linux(musl libc),则需用--platform musllinux_1_1_x86_64,且很多包(如 numpy)不提供 musl wheel,必须用--no-binary :all:下载源码并编译——这就超出了离线包的范畴,属于定制化构建了。

4.4 终极验证:用 Docker 模拟离线环境(推荐给 DevOps)

为了 100% 确保交付物可靠,我用 Docker 做最终测试:

# Dockerfile.offline-test FROM python:3.11-slim COPY wheels/ /wheels/ RUN pip install --find-links /wheels/ --no-index --trusted-host files --force-reinstall plotly==6.21.0 CMD ["python", "-c", "import plotly; print('Offline install OK')"]
docker build -f Dockerfile.offline-test -t plotly-offline-test . docker run --rm plotly-offline-test

Docker 镜像默认无网络,完美模拟离线环境。这个步骤我从不跳过,因为它是交付前的最后一道保险。

5. 后续维护:如何让离线包仓库持续可用

离线包不是一次性的。随着 Plotly 发布新版本(如 6.22.0),你需要更新 wheel 仓库。我的维护流程是:

自动化脚本化写一个update_offline.sh(Linux)或update_offline.bat(Windows):

# update_offline.bat @echo off set PLOTLY_VERSION=6.22.0 pip download plotly==%PLOTLY_VERSION% --no-deps --platform win_amd64 --python-version 311 --only-binary=:all: -d ./wheels/ pip download -r requirements.txt --platform win_amd64 --python-version 311 --only-binary=:all: -d ./wheels/ # ... 其他步骤

每次更新,只需改一行版本号,然后运行脚本。

版本管理wheels/目录按版本号分文件夹:wheels/plotly-6.21.0/,wheels/plotly-6.22.0/。这样不同项目可以共用同一个仓库,避免重复下载。

增量更新不必每次都重下全部包。用pip download --no-deps plotly==6.22.0只下载新主包,然后用pip download --no-deps -r new-requirements.txt下载新增依赖。老的 wheel 依然可用。

安全审计定期用pip show plotly查看已安装包的许可证(Plotly 是 MIT),并在INSTALL.md中注明:“本离线包集所有组件均符合 MIT 或 BSD 许可,可用于商业项目”。

最后分享一个小技巧:在wheels/目录下放一个README.md,记录每次构建的日期、Python 版本、pip 版本和目标平台。这样半年后你再打开这个 zip,一眼就知道它适配什么环境——毕竟,技术人的记忆力,永远不如一份清晰的文档可靠。

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

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

立即咨询