☰
Anaconda离线迁移指南:依赖导出与离线安装的三种方案详解
2026/10/2 19:37:38 网站建设 项目流程

先说下我为什么写这篇东西。实际开发里最头疼的不是代码写不出来,而是代码写完,要去部署环境的时候发现目标机器是个纯内网环境,连pip、conda的源都访问不了。这时候如果开发机上依赖库装得乱七八糟,想在离线机器上1:1复现环境,那真是一个头两个大。

我最近就帮同事处理了一台离线服务器的Python环境搭建,前后折腾了半天,把conda导出、离线安装的几条路线都踩了一遍。趁着热乎,把方案和坑都整理出来,给后面要做anaconda离线迁移的朋友当个参考。

1. 方案选型:先想清楚“导出”和“离线安装”的几种路线

在动手敲命令之前,得先看明白自己的处境。所谓“anaconda导出依赖库”,其实有两条完全不同的路:一条是把已安装的包整理成清单文件(requirements.txt、environment.yaml这种),到了离线机器上再拿清单去安装;另一条是干脆把整个conda环境目录打包拷贝过去,直接解压即用。

这两条路各有适用场景,不是随便选的。清单文件方式的好处是轻量、可读、方便版本管理,坏处是离线机器上必须有对应的安装包缓存,否则光有清单也装不了;整体打包方式的好处是能100%保证环境一致、连pip装的那些包也能一起带走,坏处是体积大,而且目标机器的操作系统和CPU架构必须和源机器一致。

再往下分,其实还有“在线下载安装包再搬运”的中间路线。比如在能联网的机器上用pip download把wheel包全部拉下来,然后U盘拷贝到内网机器上离线安装。这种做法在项目现场很常见,也是本文后面要重点展开的部分。

做选型的时候,我建议先用三个问题框定思路:

  • 目标机器有没有外网?还是说有本地镜像源(比如企业自建的PyPI、conda镜像)?
  • 目标机器的Python版本、操作系统、CPU架构和开发机是否一致?
  • 依赖库规模多大?是二三十个的轻量项目,还是包含CUDA、机器学习全家桶的重型环境?

根据这三个问题的答案,方案就清楚了。完全断网、架构一致、环境很大,优先考虑整体打包;只是缺几个包、机器架构不同,那就老老实实导出清单+下载whl包;如果有内网镜像源,连离线安装都可以省了,直接配好源地址在线安装。

我个人的习惯是:不管走哪条路,都先把conda list和pip list的结果存一份快照放旁边,这东西关键时刻能救命——下面会说到怎么用。

2. 动手之前:先把“家底”摸清楚

这一步很多人会跳过,直接上pip freeze > requirements.txt就开始干活。但实测下来,这样做的坑太多了,后面排查起来更费劲。不如先花两分钟把三样东西看清楚。

2.1 确认conda和pip版本,以及当前工作在哪个环境

先看版本。这可不是走形式,不同版本的pip对依赖解析、导出格式都有差异,特别是新版pip对本地可编辑安装包的记录方式,跟老版本完全不一样。

conda --version pip --version

接着确认当前激活的环境:

conda info --envs conda activate 你的环境名

很多现场翻车的案例,本质都是“导出的环境”和“我以为在的环境”不是同一个。我曾经见过有人直接在base环境里pip freeze,导出的清单里全是anaconda自带的包,装了四五个环境都起不来,最后才发现是导出环节就错了。所以导出之前,千万先确认which python和which pip指向的是不是你想要的那个环境。

2.2 确认平台信息,决定后续安装包格式

离线安装最怕的就是包格式不兼容。假设你在Windows开发机上导出依赖,拿到内网Linux服务器上装,直接拿conda env export出来的yaml去conda env create,大概率是失败告终。因为conda的yaml里包含了每个包的构建版本,这个build号是针对具体平台的,换个平台就得重新解析依赖,离线状态下根本没法解析。

所以务必先记录三样信息:

# Linux/macOS uname -m cat /etc/os-release | grep PRETTY_NAME # Windows 下打开命令行执行 echo %PROCESSOR_ARCHITECTURE%

这些信息决定了后面pip download要指定什么样的--platform参数,也决定了整体打包方案是否可行。

2.3 给当前环境拍一张“快照”

这个习惯是我后来养成的。准备一个目录,把导出过程中所有中间文件都放进去,命名带上日期和环境名:

mkdir -p ~/env_backup/myproject_2025-06 conda list -n myproject --explicit > ~/env_backup/myproject_2025-06/spec-file.txt pip list --format=freeze > ~/env_backup/myproject_2025-06/pip-list-freeze.txt

spec-file.txt是conda的“精确锁文件”,不仅记录包名版本,还记录了每个包所在的channel地址;pip-list-freeze.txt则用来兜底,把pip安装的那些包也留个底。这两份文件不直接用于离线安装,但当你后面排查“为什么装了这么多包环境还是跑不起来”的时候,拿出来一对比就知道差了什么。

3. 批量导出依赖清单:pip方案与conda方案全面对比

依赖导出的核心产物就两类:requirements.txt和environment.yaml。但要导出得干净、够用,还真不是一句pip freeze > requirements.txt能解决的。

3.1 用pip导出依赖清单(适合Python库为主的环境)

最常用的导出方式:

# 方式一:直接用 pip freeze(简单但不推荐直接用) pip freeze > requirements.txt # 方式二:用 pip list 加 freeze 格式(推荐) pip list --format=freeze > requirements.txt

pip freeze和pip list --format=freeze的区别很多人不知道。前者是pip内部实现的一套导出格式,会把通过pip install -e .这种可编辑方式安装的本地项目记录成-e file:///...这种路径格式,这种内容拷贝到别的机器上根本没法用;后者输出更规整,只输出包名==版本号的形式,更适合作为离线安装的清单。

另外,pip freeze默认会把当前环境里所有pip安装的包全部列出来,不管你是不是项目真正用到的。如果你是在anaconda的base环境里操作,那导出的清单可能包含几百个用不到的包。这种时候有两个应对策略:

一是尽量在干净的虚拟环境里开发,部署时只带项目用到的依赖;二是用pipreqs这类工具,直接扫描项目代码里的import语句,反推依赖清单。后者的命令很简单:

pip install pipreqs pipreqs . --force # 扫描当前目录下所有代码文件

这样生成的requirements.txt只包含代码里实际import过的库,非常精简。但要注意,pipreqs是基于静态扫描的,动态导入、延迟导入的库容易漏掉,所以生成后还是得人工过一遍,把明显缺失的补上。

3.2 用conda导出依赖清单(适合创建完整虚拟环境)

如果项目用到的是conda管理的包比较多,或者整体环境都需要复刻,推荐用conda的方式导出:

# 导出当前环境为 yaml 文件(包含所有显式安装和依赖传递安装的包) conda env export > environment.yaml # 只导出“显式安装过”的包(不包含依赖传递的包) conda env export --from-history > environment.yaml

这两条命令导出的内容差别很大。第一条会包含环境里所有安装的包,包括那些作为依赖被自动装上的,好处是环境复刻最准确,坏处是文件很大,而且里面有一个prefix字段记录了当前环境路径,目标机器上用的话容易踩坑;第二条只包含你手动安装过的包,清爽适合重构,但也可能漏掉关键依赖,导致新环境创建后有些包没装全。

我实际使用时,是两条命令都执行,各存一份。部署时优先用conda env export的完整版,如果遇到跨平台问题,就改用--from-history版本,再手动补依赖。

还有一个使用频率没那么高但很有用的命令:

conda list -n myproject --explicit > spec-file.txt

这个生成的纯文本文件每一行都是一个conda包的URL地址,相当于“精确到构建版本”的锁文件。离线环境里如果有本地channel,可以用它精确复刻环境。

3.3 yaml文件在离线安装前必须做的“清理手术”

如果你打算拿着environment.yaml去目标机器上跑conda env create -f environment.yaml,那动手之前建议先处理两件事:

第一,去掉文件末尾的prefix行。否则conda会尝试把环境装到跟开发机一模一样的路径,目标机器上如果不存在这个目录就直接报错。顺手把name改成目标机器上想要的环境名。

第二,考虑是否删除各个包的build字段。用文本编辑器打开yaml可以看到大概这种结构:

name: myproject channels: - defaults dependencies: - python=3.9.13 - numpy=1.21.5=py39h7cd7d39_0

如果目标机器的平台和开发机不一致,像py39h7cd7d39_0这种build号就是“定时炸弹”。建议把等号后面的build号删掉,只保留包名=版本号,让conda在目标机器上重新解析当前平台可用的构建版本。当然离线环境下conda无法联网解析,所以更好的做法是配合本地channel使用,这个下一章细说。

4. 离线安装依赖库:三种主流方式实操记录

拿到清单不是终点,真正把包装进离线机器才是重点。这一章我会把三种方式逐一演示,你根据场景挑一个用就行。

4.1 方式一:pip download 下载wheel包再批量离线安装

这个思路最直接:在能联网的机器上把requirements.txt里所有包和它们的依赖全部下载到本地目录,拷贝到离线机器后用--no-index --find-links安装,全程不访问外网。

下载阶段的核心命令:

pip download -r requirements.txt -d ./packages \ --platform manylinux2014_x86_64 \ --python-version 39 \ --abi cp39 \ --only-binary=:all:

这里的参数含义我得展开说一下:

  • -d ./packages指定下载目录。
  • --platform manylinux2014_x86_64指定目标平台。manylinux是Linux下Python wheel包的通用平台标签,兼容大多数主流Linux发行版。如果是目标机器是Windows,就换成win_amd64;macOS则要看具体版本,比如macosx_10_9_x86_64。
  • --python-version 39指定目标机器Python版本,注意不是本机版本。
  • --abi cp39指定ABI兼容性,cp39表示CPython 3.9。
  • --only-binary=:all:强制只下载wheel二进制包,不下载源码包。这个参数很关键,因为源码包在离线机器上安装时需要编译,很容易因为缺gcc或者Python头文件失败,而wheel包是预编译的,安装时直接解压就行。

执行完,你会在./packages目录下看到一堆.whl文件,文件名的格式类似numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl。看一眼文件名就能确认平台是否选对。

如果下载过程中提示某个包找不到匹配的wheel版本,最常见的原因就是--platform和--python-version的组合不对,或者这个包只发布了源码包没有发布wheel包。这时候可以把--only-binary=:all:去掉,改成--no-deps先强行拉源码包试试,但要清楚源码包在离线机器上能不能编译成功,这是另一层风险。

安装阶段:

pip install --no-index --find-links=./packages -r requirements.txt

--no-index就是告诉pip不要连接PyPI,完全只在本地目录中找包;--find-links=./packages指定本地包所在的目录。如果requirements.txt里所有的包和它们的依赖都下载齐全了,这条命令就能一次性装完。

容易忽略的是,pip download默认会把所有依赖以递归方式下载完整,也就是说你在联网机器上下载时,它会自动把每个包的依赖也下载进同一个packages目录。所以安装时一般不需要额外加--no-deps。但如果你发现个别包因为有特殊依赖而下载不全,安装时报错找不到匹配版本,最简单的处理方式是回头在联网机器上重新用pip download 包名补下这个包以及它的依赖。

提示:生成的packages目录里如果有*.tar.gz源码包,安装时大概率会遇到编译报错。优先找对应包名+平台的wheel版本替代。

4.2 方式二:conda本地channel离线安装

如果你要复刻的是整个conda环境,而不是单纯的pip环境,直接操作conda更省事。

核心思路是:conda在安装包时会把下载的.tar.bz2包缓存到本地的pkgs目录。你只要把有网机器上那个缓存目录整个拷贝到离线机器,然后用--offline或者--channel file:///的方式告诉conda“用本地文件作为包源”。

先找到pkgs目录在哪:

conda config --show pkgs_dirs

默认一般在~/anaconda3/pkgs,里面能看到大量包名-版本号-构建号.tar.bz2文件,这些就是conda的安装包缓存。

然后在离线机器上做两件事。第一,把整个pkgs目录拷贝到目标机器的对应位置,可以直接覆盖目标机器已有的pkgs目录内容;第二,用以下命令之一创建新环境:

# 方式A:基于 spec-file 精确安装(推荐,但要求包都来自同一平台) conda create -n offline_env --offline --file spec-file.txt # 方式B:直接用本地缓存创建环境 conda create -n offline_env --offline python=3.9 numpy pandas # 方式C:指定本地路径作为 channel conda create -n offline_env --channel file:///home/user/pkgs --offline python=3.9 numpy

--offline参数是核心,它强制conda不走网络,只从本地缓存目录里找包。如果缓存里缺少某个包,conda会明确报错,这时需要回到联网机器上把这个包和依赖装一遍(从而把包缓存进pkgs目录),再重新拷贝缓存过来。

有个细节值得注意:conda的pkgs缓存目录里除了.tar.bz2压缩包,还有一堆已经解压好的包名-版本号-构建号文件夹,这些是conda做硬链接时留下的。整个目录拷贝过去其实是包含了解压内容的,所以体积会比只拷贝tar包大不少。如果在意传输体积,也可以只拷贝.tar.bz2文件到离线机器的临时目录,再用conda install --offline --channel file:///临时目录/安装。

4.3 方式三(进阶):conda-pack整体迁移,免安装直接跑

如果上面两种方式都嫌麻烦,或者项目环境太复杂实在解析不清楚,可以试试我现在最常用的方案:用conda-pack把整个环境打包成压缩包,拷到目标机器上解压就能用。

先在联网机器上安装并打包:

conda install -c conda-forge conda-pack conda pack -n myproject -o myproject_env.tar.gz

执行完会生成一个几百MB到几GB的压缩包,取决于环境大小。

拷贝到目标机器后,解压到一个目录,然后调整环境变量就可以直接使用:

mkdir -p ~/envs/myproject tar -xzf myproject_env.tar.gz -C ~/envs/myproject # 关键一步:执行环境自带的修复脚本 source ~/envs/myproject/bin/activate # 此时虚拟环境的 python 已经可以直接运行 ~/envs/myproject/bin/python -V

为什么打包之后还要执行bin/activate?因为conda环境里的很多脚本、路径是写死的绝对路径,一旦环境被移动到新位置,就需要运行环境自带的脚本重新计算路径。conda-pack在打包时就已经在环境里内置了这段修正逻辑,所以解压后执行一下activate就能正常用。

这套方案最大的优点是快、准、狠。不管环境里有多少包、多少编译过的扩展,打包迁移后都能原样跑起来,连pip安装的包、自定义配置都能带走。缺点也有两条:一是目标机器的操作系统和CPU架构必须一致,不能跨平台;二是如果目标机器上的conda也想管理这个解压出来的环境,还需要把环境目录放到conda的envs目录下,并用conda-unpack做一次根目录修正。

# 如果想让 conda 也认识这个解压出来的环境 mkdir -p ~/anaconda3/envs/myproject tar -xzf myproject_env.tar.gz -C ~/anaconda3/envs/myproject conda-unpack # 需要在激活该环境后执行

5. 单个依赖库的导出和导入:轻量场景也不要蛮干

批量导出的方法有了,但日常还有很多场景只需要处理一两个包。比如你发现在内网机器上跑代码,报错ModuleNotFoundError: No module named 'xxhash',这时候犯不着为这一个包专门搞一套完整的转移方案。

单个库的处理,我的做法是分两种情况。

第一种,如果你只是想在联网机器上下载某个包,然后拷到离线机器上安装,直接用pip download:

pip download xxhash -d ./single_pkg

这条命令会连依赖一起下载。装的时候:

pip install --no-index --find-links=./single_pkg xxhash

第二种,如果内网机器上有可能联网的镜像或内网源,往往配置一下index-url就能直接装:

pip install xxhash -i http://内网源地址/simple --trusted-host 内网源地址

平时跑在无网环境里的经验是:不到万不得已不要直接下载源码包去编译,因为目标机器上缺少编译工具链的概率极高,一个简单的C扩展都能折腾一上午。优先找wheel包,wheel的安装本质就是解压复制,几乎不会有编译问题。

如果连pip都用不了,还可以考虑把单包解压后手动拷贝到site-packages目录。这个方法虽然不体面,但应急场景下确实能解决燃眉之急——方法很简单,联网机器上pip download后解压whl(它本质是个zip),把解压出的包文件夹整个放到离线机器的site-packages目录里。不过这只适合纯Python包,包含编译型so/pyd文件的包必须在同一平台下才能这样干。

6. 常见问题与排查技巧实录

这部分是血泪总结,每条都踩过坑,建议收藏。

6.1 高频报错与解决方案对照表

报错现象根本原因解决方法
conda env create时提示prefix相关错误environment.yaml里包含原环境绝对路径编辑yaml,删除末尾的prefix:行
离线机器pip install报找不到包--no-index模式下本地packages目录不完整回到联网机器,用pip download 包名逐个补齐缺失依赖
下载wheel包时提示No matching distribution--platform或--python-version参数与实际不匹配用pip debug --verbose查看当前pip支持的平台标签,再重新指定
离线安装时卡在Building wheel然后失败下载到了源码包,目标机器缺编译环境优先找对应平台的wheel包;必要时换conda-pack整体迁移方案
conda create --offline报找不到包conda缓存目录里没有对应平台的包检查pkgs目录是否完整;确认联网机器的平台和目标机器一致
解压conda-pack包后python -V还是系统Python没执行环境内的activate,或PATH未调整先source 解压目录/bin/activate,再用which python确认路径
pip导出清单里出现-e file:///...记录环境中存在可编辑安装的本地项目包手动删除这些行,改用pip list --format=freeze重新导出

6.2 排查思路:离线环境“装上了但跑不起来”

最让人头疼的不是安装失败,而是安装成功后运行代码依然报错。这种时候不要慌,按下面的顺序排查:

第一步,验证Python解释器和site-packages路径是不是你预期的:

which python python -c "import sys; print(sys.path)"

第二步,用最小化测试确认核心包能正常导入:

python -c "import numpy; print(numpy.__version__)"

如果报错说缺少某个动态依赖库(比如libgomp.so.1),说明是系统层面的库缺失,不是conda/pip能解决的。这种系统动态库缺失,往往是因为目标机器和开发机的系统库版本有差异,最快的方法是直接在目标机器上用包管理器安装对应的基础库,或者在联网机器上把这个系统库的安装包也带上。

第三步,如果包能导入但版本不对,用快照文件对比一下。这就是前面让做pip list --format=freeze快照的原因。一条一条比对版本是最笨也是最可靠的办法。

第四步,优先级解决。离线环境下最怕的不是版本不一致,而是版本解析冲突。如果requirements.txt里同时要求pandas>=1.5和numpy<1.22,而安装顺序又导致numpy先装成新版,就可能出现启动即崩溃。建议安装前先把requirements.txt里的大版本边界检查一遍,必要时加--no-deps手动控制安装顺序。

6.3 几个提升离线迁移效率的实用习惯

  • 固定一个“打包机”。我通常固定一台专门负责下载、打包的联网机器,上面维护一套和目标环境一致的基础环境,避免每次都在不同的机器上临时下载,导致包来源五花八门。
  • 包缓存统一管理。把pip download下来的wheel包目录按项目和日期分开存放,同时生成一个checksums.txt记录所有包的MD5值,拷贝到内网机器后先校验再安装。这能避免U盘传输过程中文件损坏导致的各种诡异报错。
  • 记录原始安装命令。用pip freeze只能记录版本号,记录不了安装时用的参数。建议把组合安装命令沉淀成一个shell脚本,比如install_all.sh,离线机器上直接跑脚本,可复现性会好很多。
  • 大环境优先用conda-pack。如果你的项目环境超过1GB,或者包含OpenCV、PyTorch这种大型二进制库,不要贸然走requirements.txt离线安装的路,直接用conda-pack整体打包。一次编译的麻烦,好过在现场排查一整天。

7. 一点个人体会,关于离线迁移这件事

折腾完这次离线部署,我最大的体会是:工具链本身并不复杂,复杂的是搞清楚自己的使用场景。同样一个需求——“把依赖库带到另一台机器上”,可以做轻量级的、文档化的requirements.txt迁移,也可以做重量级的、黑盒式的整包迁移。选错了方案,后面的坑会接踵而至。

我个人现在的工作流基本固定了:日常开发时就把环境搞得干净一些,pip安装的包集中在虚拟环境里,conda安装的包单独管理;每次搭建复杂环境后顺手留一份pip list --format=freeze快照;部署到内网机器时,优先判断平台是否一致,一致就conda-pack带走,不一致就不折腾离线安装这种方案,直接在联网机器上按照目标平台参数下载wheel包再拷贝安装。

最后再分享一个小技巧,也是这次实践中觉得最划算的:把pip download和pip install --no-index这两条命令写成一个带参数的bash脚本,把平台、Python版本、包目录都做成变量。以后每遇到一次离线部署,改一下参数就能用,省下来的时间够刷好几集剧了。

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

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

立即咨询