先说下我为什么写这篇东西。实际开发里最头疼的不是代码写不出来而是代码写完要去部署环境的时候发现目标机器是个纯内网环境连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 --formatfreeze ~/env_backup/myproject_2025-06/pip-list-freeze.txtspec-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 --formatfreeze requirements.txtpip freeze和pip list --formatfreeze的区别很多人不知道。前者是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: - python3.9.13 - numpy1.21.5py39h7cd7d39_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_amd64macOS则要看具体版本比如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 python3.9 numpy pandas # 方式C指定本地路径作为 channel conda create -n offline_env --channel file:///home/user/pkgs --offline python3.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 downloadpip 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 --formatfreeze重新导出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 --formatfreeze快照的原因。一条一条比对版本是最笨也是最可靠的办法。第四步优先级解决。离线环境下最怕的不是版本不一致而是版本解析冲突。如果requirements.txt里同时要求pandas1.5和numpy1.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 --formatfreeze快照部署到内网机器时优先判断平台是否一致一致就conda-pack带走不一致就不折腾离线安装这种方案直接在联网机器上按照目标平台参数下载wheel包再拷贝安装。最后再分享一个小技巧也是这次实践中觉得最划算的把pip download和pip install --no-index这两条命令写成一个带参数的bash脚本把平台、Python版本、包目录都做成变量。以后每遇到一次离线部署改一下参数就能用省下来的时间够刷好几集剧了。