1. 为什么要在内网机器上死磕离线安装
先说个我自己的真实经历。前年接手一个项目,客户的生产车间是纯物理隔离的内网,机器装在机柜里,网口都是封死的,连USB口都做了策略管控。当时需要在这台Windows机器上搭一套Python开发环境,用来跑产线上的数据处理脚本和几个自动化测试工具。我一开始想得很天真,觉得下个安装包装一装就完事了,结果发现完全不是那么回事——Python装完之后要装一堆第三方库,pip install全部报连接超时,VS Code装完之后一片空白,连语法高亮都不给,因为Python扩展根本没装上。
那次我折腾了整整两天,才把一套完整的离线方案跑通。后来这类需求我又碰到过好几次,有做嵌入式的朋友要在无外网的工作站上调试C代码,有做数据分析的同事要把环境复制到涉密机器上,还有要给一批新员工批量部署标准化开发环境的。每次踩的坑都不太一样,但核心逻辑是共通的:离线环境的本质,是把"运行时依赖"和"构建时依赖"全部提前打包好,然后用一种不依赖网络的方式搬运过去。
这里有个很多人一开始就想错的地方。大部分人认为"离线安装"就是"把安装包拷过去装一下",但实际上现代开发工具链的依赖是分层的,尤其是VS Code加Python这套组合,至少涉及四层东西:
- 第一层是基础运行时,也就是Python解释器本体。
- 第二层是编辑器本体,也就是VS Code的程序文件。
- 第三层是编辑器扩展,VS Code的Python、Pylance、Jupyter这些插件。
- 第四层是项目依赖包,也就是你代码里import的那些第三方库。
这四层每一层的离线处理方法都不一样,而且层与层之间还有版本匹配的坑。很多教程只讲其中一层,结果照着做还是会卡住。我下面会按这四层一条条拆开讲,每一层都会给出我自己验证过的操作路径和踩过的具体坑。
还有一点要提前说清楚:离线环境的准备工作必须在有网的机器上完成,而且最好是和目标机器同操作系统、同CPU架构。这一点看起来是废话,但我见过太多次有人拿Linux上准备好的包往Windows上搬,或者拿x86_64的轮子往ARM板子上装,最后全部报错。这个原则贯穿整个流程,后面每个环节我都会强调。
说说这一套环境最终能达到什么效果。跑通之后,目标机器上的VS Code可以正常打开Python文件、有完整的语法高亮和智能补全、能下断点调试、能跑Jupyter Notebook、能正常安装和使用你打包好的第三方库,整体体验和有网环境下基本没差别。适合的读者是需要在隔离环境、内网、涉密机器或网络不稳定环境下做Python开发的工程师,也包括需要批量部署标准化环境的运维人员。哪怕你之前完全没接触过离线部署,跟着走一遍也能搞明白每个环节到底在干什么。
2. Python解释器的离线落地:从选版本到静默部署
2.1 版本选择的门道:为什么我建议卡在小版本号上
选Python版本这件事,有网的时候随便选,反正不合适当场再装一个。但离线环境下,版本选择要有前瞻性,因为后面所有第三方库的兼容性都挂在这个版本上。
我的建议是选一个比最新版落后一到两个小版本的稳定版本,比如当时最新是3.13,那就选3.11或者3.12。原因很实在:第三方库对新版Python的适配总是滞后的,尤其是那些带C扩展的库,比如numpy、pandas、lxml这类,它们发布的预编译wheel包(就是.whl文件)通常只覆盖到某个版本。你要是选了刚发布的最新版,很可能在离线源里找不到对应的wheel,只能自己编译,而编译又要拉一堆构建工具和头文件,离线环境下这是灾难。
具体到版本号,还有个更细的坑:Python的小版本号变化会导致wheel不兼容。Python的wheel包命名里带cp311这样的标记,意思是CPython 3.11。cp311的包只能装在3.11上,装到3.12上会报not a supported wheel on this platform。所以离线环境里定版本的时候,最好精确到3.11.x这一级,比如锁定3.11.9,别到时候目标是3.11.5结果准备了3.11.9的包。
至于从哪拿安装包,Windows上直接去python.org的下载页面找Windows installer (64-bit),Linux上一般发行版都自带或者从源码编译。这里我不推荐用那些第三方整合包,虽然它们号称开箱即用,但目录结构经常和标准安装不一样,离线迁移的时候会出各种路径问题,出了事很难排查。
2.2 Windows下的静默安装参数与可选组件
拿到安装包(比如python-3.11.9-amd64.exe)之后,有网机器上装一遍当然简单,但离线部署到多台机器上时,一台台点安装程序太慢,而且容易点到不一样的选项。这时候要用静默安装。
Python的Windows安装程序支持命令行参数,常用的组合是这样:
python-3.11.9-amd64.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1逐项解释一下。/quiet是静默模式,不弹界面。InstallAllUsers=1表示给所有用户安装,装到C:\Program Files\Python311下面,这个很重要,因为如果不指定,默认是只给当前用户装,路径会跑到AppData里,多用户环境下其他人用不了。PrependPath=1是把Python和Scripts目录加到系统PATH里,这样命令行里直接敲python和pip就能用,不然每次都要写全路径。Include_test=0是跳过测试套件,能省几十兆空间,离线包本来就金贵,能省则省。Include_pip=1是确保装上pip,虽然通常默认就有,但显式写上更保险。
有个坑我要单独讲:PrependPath=1在静默模式下有时候不会立即生效,因为环境变量的刷新依赖一个通知机制。装完之后如果发现命令行里敲python还是找不到,不是装错了,是环境变量没刷新。新开一个命令行窗口通常就好了,如果还不行,注销重登或者直接去系统属性里看一眼PATH。
还有Include_launcher=1这个参数也值得加上,它会安装py这个启动器。这个启动器在多版本共存的时候特别好用,可以写py -3.11精确调用某个版本,离线环境下多版本共存是很常见的,这个启动器能省不少事。
Linux下就简单多了,如果是源码包Python-3.11.9.tgz,需要先装编译依赖,然后./configure --prefix=/opt/python3.11 && make && make install。但离线环境下编译依赖本身也是个大问题,编译Python需要gcc、make、zlib-devel、openssl-devel等等一堆东西。所以如果目标机器是Linux,我强烈建议优先找现成的二进制包或者用系统的包管理器离线缓存功能,比如在能联网的同版本机器上用apt-get install --download-only把deb包下载下来,然后拷过去用dpkg -i装,这比从源码编译省太多事。
2.3 把pip也要离线化:本地源目录的建立
装完Python之后,pip本身是可以用的,但它默认去PyPI拉包,离线环境下必然失败。解决办法是建一个本地包目录,让pip从这个目录装。
这里有个非常关键的参数叫--no-index,意思是完全不查在线索引,只用你指定的本地路径。配合--find-links指定目录,命令是这样的:
pip install --no-index --find-links=D:\offline_pkgs numpy pandas openpyxl--no-index是必须的,不加的话pip还是会在超时前尝试访问网络,拖慢速度还可能报一堆错。--find-links指向的目录里放的是一堆.whl文件,pip会在里面找匹配的包。
那这些whl文件怎么来?在有网机器上下载。最稳妥的方式是用pip download命令,这个命令只下载不安装,而且可以指定平台和Python版本:
pip download --only-binary=:all: --platform win_amd64 --python-version 311 --dest D:\offline_pkgs numpy pandas这里--only-binary=:all:是强制只下载二进制wheel,不下载源码包。为什么强调这个?因为源码包(.tar.gz)到了目标机器上还需要现场编译,而编译又需要编译器,离线环境下大概率没有。--platform和--python-version是为了下载适配目标机器的包,你在Linux上下载给Windows用的包,不加这两个参数就会下成Linux的。--dest指定下载目录。
注意:
pip download如果目标包有依赖,会把依赖一起下载下来,这个特性非常有用。但有时候依赖的依赖会漏掉,所以下载完之后最好在目标机器上装一遍,缺什么再补什么。
我自己的习惯是,下载完之后把整个offline_pkgs目录连同Python安装包一起打包,放到一个U盘或者移动硬盘里。目录结构大概长这样:
offline_bundle/ ├── python-3.11.9-amd64.exe ├── offline_pkgs/ │ ├── numpy-1.26.4-cp311-cp311-win_amd64.whl │ ├── pandas-2.2.2-cp311-cp311-win_amd64.whl │ └── ... └── install.bat那个install.bat是我自己写的一键脚本,内容就是把静默安装和pip安装串起来执行。这个脚本的具体写法后面讲批处理的时候再展开。
3. VS Code本体的搬运:便携模式与配置固化
3.1 用户安装器还是系统安装器,这是个分岔路口
VS Code有两个版本的安装器,一个是User Installer(用户安装器),装在用户目录下;一个是System Installer(系统安装器),装在Program Files下。有网环境下随便选,但离线场景我强烈推荐用System Installer,或者干脆用它的ZIP免安装版。
理由是这样的:User Installer装到AppData\Local\Programs下面,这台机器上换一个用户登录,VS Code就没了,配置也是每个用户一份。而离线部署往往是给一批机器或者一台机器的多个使用场景用的,用户级的安装会带来重复配置的麻烦。System Installer装完之后全机器可用,配置放在统一的位置,方便我一次性把配置写好然后批量复制。
如果你连安装都想免掉,那就用ZIP版。VS Code官网提供.zip格式的免安装包,解压就能跑,不写注册表。这种方式的优势是我可以把整个VS Code目录连同配置一起打包,拷到目标机器上解压就完事,不需要管理员权限,特别适合权限受限的环境。缺点是右键菜单、文件关联这些系统集成功能没有,但对于纯写代码来说影响不大。
3.2 便携模式怎么开:data目录的妙用
说到ZIP版,就不得不提VS Code的便携模式(Portable Mode)。这个模式很多天天用VS Code的人都不知道,但对离线部署来说是个宝贝。
开启方法很简单:在VS Code的解压目录里,和Code.exe同级的位置,新建一个名字叫data的文件夹。VS Code启动的时候检测到这个data目录存在,就会把用户数据、扩展、缓存全部放到这个目录里,而不是放到AppData下面。
这个机制的价值在于所有的环境数据都集中在一个目录树里,迁移的时候直接拷整个文件夹就行,配置、插件、设置全都跟着走。我在标准化部署的时候就是这么干的:在有网机器上开便携模式,装好所有需要的插件,配好settings.json,测好Python解释器路径,然后把这个文件夹整体打包。目标机器上解压,改一下路径相关的配置,就能直接用。
配置目录的结构大致是这样:
VSCode-Portable/ ├── Code.exe ├── data/ │ ├── user-data/ │ │ ├── User/ │ │ │ ├── settings.json │ │ │ └── snippets/ │ │ └── ... │ └── extensions/ │ ├── ms-python.python-2024.x.x/ │ ├── ms-python.vscode-pylance-2024.x.x/ │ └── ...user-data\User\settings.json是用户设置,extensions目录就是你装的插件。这个结构清楚了之后,后面做迁移和备份都一目了然。
3.3 离线安装插件:VSIX文件的获取与安装
VS Code的插件在正常情况下是从市场(Marketplace)在线下载的,离线环境下市场是打不开的。解决办法是提前把插件下载成.vsix文件,然后离线安装。
获取vsix文件有两种方式。第一种是在有网的VS Code里,找到插件,点齿轮图标,选"Download VSIX",它会下载到本地。但这个方法需要插件页面上有这个选项,不是所有插件都有。第二种更可靠,是从市场网站直接搜插件,进到详情页,在"Version History"里找到想要的具体版本,点下载。这种方式的好处是我能精确控制版本,避免下到最新版结果和目标VS Code版本不兼容。
VS Code的插件版本和VS Code本体版本之间是有兼容要求的,插件的package.json里有个engines.vscode字段,比如^1.80.0,意思是需要VS Code 1.80以上。如果你下的插件要求1.85,而目标机器上的VS Code是1.80,装的时候会提示不兼容。所以在下载插件之前,最好先确认目标VS Code的版本要求,或者反过来,先把VS Code版本定下来,再挑兼容的插件版本。
安装vsix的命令行方式是这样:
code --install-extension ms-python.python-2024.6.0.vsixcode命令在装VS Code的时候如果勾选了"添加到PATH"就会有,ZIP版的话在bin目录下有code.cmd。如果命令行调不通,也可以在VS Code界面里,扩展面板右上角三个点,选"Install from VSIX",然后选文件。
Python开发必需的插件我列一下,这些是必备的:
| 插件名称 | 扩展ID | 作用 |
|---|---|---|
| Python | ms-python.python | 核心支持,调试、运行、环境管理 |
| Pylance | ms-python.vscode-pylance | 智能补全、类型检查 |
| Jupyter | ms-toolsai.jupyter | Notebook支持 |
| Black Formatter | ms-python.black-formatter | 代码格式化 |
Pylance这个插件要注意,它体积不小,而且依赖Python扩展。安装顺序最好先装Python,再装Pylance,不然可能会有依赖警告。Jupyter插件也是个大块头,如果不用Notebook可以不装,能省不少空间。
提示:插件装完之后,VS Code会在
extensions目录下建对应文件夹,每个文件夹里会有一个.obsolete文件或者.extension标记。如果你想确认插件是否真的装上了,看这个目录比看界面更直观。
3.4 插件依赖的二进制文件:一个容易被忽略的坑
这里要讲一个很多人会踩的坑。有些VS Code插件内部会去下载额外的二进制文件,在线的时候自动下,离线的时候就卡住。最典型的是Pylance,它本身是个语言服务器,插件包里带了一部分的二进制,但某些版本会尝试下载额外的组件。Jupyter插件的一些内核管理功能也会去下东西。
规避方法是在准备阶段,把插件在联网环境下完整激活一遍——打开一个Python文件,让Pylance完成一次索引,打开一个Notebook,让Jupyter把内核检测跑一遍。这样触发它把该下的东西都下到插件目录里。然后你再打包,这些下载好的文件就会跟着走。我自己测试的时候,就吃过这个亏:插件装上了,但Python文件的补全一直转圈,查了半天才发现是语言服务器的一个组件没下下来。
4. 项目依赖包的完整打包:从离线源到跨平台兼容
4.1 依赖清单怎么生成:requirements.txt的正确用法
前面讲了单个包的下载,但实际项目里依赖是成套的,而且有版本约束。这时候要用requirements.txt来管理。
生成依赖清单的标准做法是在项目环境里跑:
pip freeze > requirements.txt但这个命令有个问题,它会把环境里所有包都列进去,包括那些间接依赖和跟项目无关的。更精准的方式是只导出项目直接依赖,或者用pipreqs这种工具扫描代码里的import语句来生成。
有了清单之后,下载命令变成:
pip download --only-binary=:all: --platform win_amd64 --python-version 311 -r requirements.txt --dest D:\offline_pkgs批量下载的好处是依赖关系会被pip自动解析,不会漏包。但这里有个隐蔽的坑:如果某个包只有源码包没有wheel包,加了--only-binary=:all:会直接报错而不是跳过。这时候要单独处理这个包,要么去掉这个参数让它下源码包(前提是目标机器能编译),要么找替代的纯Python实现。
我遇到过好几次这种情况,比如某个小众库只发.tar.gz。处理办法是提前在目标环境探测一遍,把这类包列出来单独准备。还有一种情况是,某些包虽然发了wheel,但只发了特定平台的,比如只有macosx的轮子没有win_amd64的,这种在Windows目标机上就得找源码编译。
4.2 跨平台下载的技巧:--platform和--abi的配合
跨平台准备包这件事,说起来简单做起来容易错。因为pip download默认是按当前机器的环境来下,你在Linux上跑,它就下Linux的包。要下给别用的,必须把目标参数显式写全。
完整的参数组合是这样:
pip download \ --only-binary=:all: \ --platform win_amd64 \ --python-version 311 \ --implementation cp \ --abi cp311 \ -r requirements.txt \ --dest D:\offline_pkgs--platform指定目标系统的架构标识,常见的有win_amd64、manylinux2014_x86_64、macosx_11_0_arm64这些。--python-version是Python版本,写311就是3.11。--implementation是解释器实现,一般都是cp(CPython)。--abi是应用二进制接口,通常和Python版本对应,cp311。
这套参数要全部写对,少一个都可能下错包。--platform和--abi要匹配,比如你写--platform win_amd64但--abi写成了cp311m(那个m是pymalloc标记,Python 3.8之后就没这个了),就会报找不到合适的包。
还有一个特殊的架构标识叫any,专门给纯Python的包用。有些包比如requests,是纯Python写的,它的wheel名字里带py3-none-any,意思是任何Python 3、任何平台都能用。这种包不用管--platform,pip会优先选它。
4.3 版本锁定的价值:一次准备,多次复用
离线环境准备最怕的就是"这次能用下次不能用"。避免这个问题的方法是把版本完全锁死。
pip freeze导出的清单其实是带版本号的,比如numpy==1.26.4。但pip download的时候如果不加约束,它可能会去下一堆相关的包的最新版。为了保证一致性,可以把下载和安装都基于同一个requirements.txt,并且在里面把所有包都写成==精确版本。
更狠一点的做法是用pip-compile(来自pip-tools)来生成一个完全锁定版本的清单,包括所有间接依赖的版本都被锁死。这样在任何机器上重现的环境都是一模一样的,离线环境下这一点特别重要,因为你没法在线装个新版本来试试行不行。
我自己的习惯是,每次给离线环境准备包的时候,都记一个"环境快照",包含这些信息:
- Python的精确版本号
- 操作系统和架构
- 所有包的精确版本
- 下载时的完整命令
这个快照存一份在项目的docs目录里,下次要给别人复制环境,或者过了几个月要重建环境,照着快照来就行,不会出现"当时能跑现在跑不了"的尴尬。
5. 一键化部署脚本:让重复劳动自动化
5.1 批处理脚本的组织结构
前面每一步都讲完了,但如果每次部署都手动敲一遍命令,那太折磨人了。我后来把这些步骤全部串成脚本,一次写好,到处能用。
Windows下用.bat批处理,Linux下用.sh。先说Windows的。脚本的整体思路是:先检查Python是否已安装、版本对不对,没装就静默安装;然后设置环境变量;再装pip包;最后装VS Code和插件。
写批处理有几个坑要避开。第一是管理员权限,装System级别的Python和写PATH都需要管理员权限,批处理开头要加权限检测:
@echo off net session >nul 2>&1 if %errorlevel% neq 0 ( echo 请以管理员身份运行此脚本 pause exit /b 1 )这段net session是个经典技巧,它能检测当前是不是有管理员权限,因为普通用户跑这个命令会报错。
第二是路径里有空格的问题。C:\Program Files这种路径,在批处理里如果不加引号会被截断。所有涉及路径的地方都要养成加引号的习惯。
第三是错误处理,脚本里每执行一步都要检查%errorlevel%,失败了就报错停下,别让脚本一路跑到底最后报一堆无关错误。
5.2 安装脚本的核心流程
一个完整的安装脚本,核心流程大概是这样组织的:
@echo off setlocal enabledelayedexpansion set PYTHON_VERSION=3.11.9 set PYTHON_EXE=python-%PYTHON_VERSION%-amd64.exe set OFFLINE_PKGS=%~dp0offline_pkgs set VSCode_DIR=%~dp0VSCode-Portable echo [1/4] 检查Python环境... python --version 2>nul | findstr "%PYTHON_VERSION%" >nul if %errorlevel% neq 0 ( echo 未检测到目标版本,开始安装... "%PYTHON_EXE%" /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_launcher=1 if !errorlevel! neq 0 ( echo Python安装失败 exit /b 1 ) ) echo [2/4] 安装离线依赖包... python -m pip install --no-index --find-links="%OFFLINE_PKGS%" -r "%~dp0requirements.txt" if !errorlevel! neq 0 ( echo 依赖包安装失败,请检查offline_pkgs目录 exit /b 1 ) echo [3/4] 部署VS Code... xcopy /E /I /Y "%VSCode_DIR%" "%LOCALAPPDATA%\VSCode-Portable" echo [4/4] 安装VS Code插件... for %%f in ("%~dp0vsix\*.vsix") do ( "%LOCALAPPDATA%\VSCode-Portable\bin\code.cmd" --install-extension "%%f" ) echo 部署完成 endlocal这个脚本里有几个细节值得说。%~dp0是批处理的内置变量,表示脚本自身所在的目录(d是drive,p是path),用它可以保证脚本不管在哪个目录下执行,都能找到同目录下的资源。setlocal enabledelayedexpansion开启延迟变量扩展,这样在if块里才能用!errorlevel!而不是%errorlevel%,这是批处理里的一个经典陷阱,不开启的话if块里的错误码永远是进入块之前的值。
findstr那段是版本检测,如果已经装了目标版本就跳过安装,能避免重复装。xcopy /E /I /Y是复制目录,/E包含空目录,/I是如果目标是目录就当作目录处理,/Y是覆盖不提示。
5.3 配置文件的路径修正:最容易忘的一步
脚本跑完,环境装好了,但这时候VS Code里的Python解释器路径大概率还是错的,因为settings.json里如果写死了绝对路径,换台机器就失效了。
settings.json里和Python相关的配置主要是这几项:
{ "python.defaultInterpreterPath": "C:\\Program Files\\Python311\\python.exe", "python.analysis.typeCheckingMode": "basic", "python.formatting.provider": "black", "editor.formatOnSave": true, "files.autoSave": "afterDelay" }python.defaultInterpreterPath如果是绝对路径,迁移之后必须改。所以我在准备阶段就有个习惯:尽量不在settings里写死解释器路径,而是让VS Code自己去探测。VS Code的Python插件会自动找系统PATH里的Python,也会找虚拟环境,通常都能找到。如果非要用虚拟环境,那就把虚拟环境放在项目目录里,这样路径相对稳定。
另一个要检查的是插件自己的状态文件。如果插件在准备阶段记录了一些绝对路径(比如语言服务器的路径),迁移后可能也会失效。检查方法就是打开一个Python文件,看补全是否正常,不正常就去输出面板看Python插件的日志,里面会写明它在找什么、找没找到。
6. 实测中反复出现的几个问题与对应的解法
6.1 报错"找不到合适的包"到底在说什么
离线环境里最常遇到的报错就是这个:
ERROR: Could not find a version that satisfies the requirement xxx完整的原因链条是这样的:pip在--find-links目录里找包,它会根据当前Python的版本、平台、ABI去筛选文件名匹配的wheel。如果目录里只有numpy-1.26.4-cp312-cp312-win_amd64.whl,而你用的是Python 3.11,那pip就会报找不到,因为cp312不匹配cp311。
排查方法有个小技巧,直接列出wheel文件名对照。wheel的命名规则是:
{包名}-{版本}-{python标记}-{abi标记}-{平台标记}.whl比如numpy-1.26.4-cp311-cp311-win_amd64.whl,四个部分分别是包名版本、cp311(Python 3.11)、cp311(ABI)、win_amd64(平台)。你对照目标机器的这三个标记,就知道目录里有没有能用的包了。
还有一个容易忽视的情况是包的版本号本身有约束。如果你在requirements里写了numpy>=1.20,而目录里最高的才1.19,那也会报找不到。离线环境下我建议全部写==,精确锁定版本,这样报错只可能是文件名不匹配,排查简单。
6.2 PATH污染引发的Python版本错乱
装完Python、装完VS Code,满以为万事大吉,结果命令行里敲python --version蹦出来一个系统自带的旧版本,或者pip装包装到了另外的Python里。这是PATH的问题。
Windows下PATH是按顺序找的,如果系统里本来就有别的Python(比如从某个软件附带的),它的路径在前面,就会先被找到。排查命令是:
where python where pip这个命令会列出所有叫python的程序路径,第一个就是实际被调用的那个。如果第一个不是你期望的,就要去调PATH的顺序。
VS Code里也有类似的问题。Python插件会探测到多个解释器,如果你选错了,装包的时候就会装到别的环境。这个在状态栏能看到,点左下角的解释器名字可以切换。我吃过一次亏,装了半天的包,结果解释器选到了另一个Python上,一直报模块找不到,查了半天才发现是选错了。
提示:如果目标机器上不可避免有多个Python,建议用虚拟环境隔离。虚拟环境用
python -m venv venv创建,创建后激活,所有包都装到这个环境里。虚拟环境的好处是它自带一个pyvenv.cfg记录基础解释器位置,迁移的时候整个目录拷过去改一下cfg里的路径就能用。
6.3 VS Code扩展装上了但不生效
插件装上了,但功能没反应,这是另一个常见的坑。原因通常是插件之间的依赖没满足,或者插件的二进制文件没准备好。
具体表现有几种。一种是Python文件的补全不工作,光标停在那里转圈,这是Pylance的问题。排查方法是看VS Code底部的输出面板,选"Python Language Server",里面会显示它在加载什么、有没有报错。最常见的原因是它想下载某个组件但下不了,这时候要么手动把组件补上,要么在设置里关掉相关功能。
另一种是Jupyter插件找到了Python但找不到内核。这个是因为Jupyter需要注册内核,而内核注册依赖于Python环境。离线解决方案是在准备阶段就跑一次python -m ipykernel install --user --name offline-env,把内核信息写进去,然后把这个内核的配置文件也打包带走。
还有一种是插件版本和VS Code版本不匹配,报"扩展与当前版本不兼容"。这个只能换插件版本,去市场页面的历史版本里找一个满足engines.vscode要求的。我一般会在准备阶段把所有插件的版本记录一次,出一个对照表,后面哪天要重建环境就照着这个表来。
6.4 磁盘空间和打包体积的平衡
离线包越装越大是个现实问题。Python本体百来兆,VS Code几百兆,插件几百兆,再加上一堆numpy、pandas这种带二进制的大包,整个包几个G很正常。如果目标机器空间紧张,就得做减法。
减法的思路有几个。第一,只装必需的插件,Pylance和Jupyter这种大块头如果不必要就不装,语法高亮用Python插件自带的简易功能也能凑合。第二,依赖包按需裁剪,比如pandas可能只用它的DataFrame,那就准备这个就够,不用把整个数据科学生态都带上。第三,清理缓存,pip下载目录里的.cache、VS Code的CachedData这些都可以删掉,能省不少。
我自己的经验是,一个合理的Python开发离线包(Python + VS Code + 核心插件 + 10个常用库)大概在1.5G到2G之间,再压缩一下能到800M左右。用7z的最大压缩比打包,比zip能小不少,这也是我的习惯之一。
6.5 版本回退和更新时的注意事项
离线环境还有个麻烦是更新。有网的时候更新点一下就行,离线环境要从头走一遍准备流程。我的应对策略是保留上一个版本的完整包,不要覆盖,新建一个带日期或版本号命名的目录。这样新版本出问题的时候,能快速回到旧版本。
更新的时候有个顺序建议:先在测试机上验证新的离线包没问题,再往生产环境推。因为离线环境没法在线装补丁,一旦推错,回退成本很高。我自己维护过的一个环境,更新了Python的一个小版本之后,有个库因为ABI变化跑不起来,还好保留了旧包,几分钟就切回去了。
还有一点,准备新版本包的时候,尽量让新旧版本的目录结构一致,脚本能通用,这样切换的时候只需改一个版本号变量,不用重写脚本。这个习惯让我后来的多次升级都很顺,基本就是改个参数、重新跑一遍脚本的事。
7. 我这些年攒下来的几条实操心得
前面把整个流程拆得很细,这里说几个从实际操作里抠出来的、文档上一般不会写的点。
第一个是关于准备环境的机器选择。准备用的机器最好和目标机器完全一样——同样的操作系统版本、同样的架构、甚至同样的系统补丁级别。我有一次在Windows 10上准备的环境推到Windows Server上,结果某个库因为缺一个系统组件跑不起来,折腾了很久。后来我就养成了一个习惯,准备环境尽量在一个和目标一致的虚拟机里做,这样可以随时快照、随时重来,也不怕把工作机搞乱。
第二个是关于USB介质的选择。离线环境搬运一般靠U盘或移动硬盘,这里有个坑是文件系统。如果U盘是FAT32格式,单个文件不能超过4G,一个大的离线压缩包很容易就超了。所以要么用exFAT或NTFS格式的U盘,要么把包切分成多个小文件。我一般会提前把U盘格式化好,避免到了现场才发现拷不进去。
第三个是关于校验。文件在复制、传输过程中可能损坏,尤其是在网络传输或者老旧的U盘上。我习惯在准备端生成一个校验清单(用certutil -hashfile 文件名 SHA256),到了目标机器上再生成一次对比,对不上的重新传。这个步骤看起来多余,但真的救过我好几次,有一次一个whl文件传输时损坏了,装的时候报的解压错误,完全不像是文件损坏,差点往网络问题上查。
第四个是关于文档。每次部署完成后,我都会花十分钟写一个部署记录,记下:这次用的Python版本、VS Code版本、插件版本、遇到的特殊问题、解决办法。这个记录积累下来,就成了我自己的一个知识库,下次遇到类似环境直接从记录里翻,比重新摸索快得多。尤其离线环境,很多坑是环境特有的,文档的价值比在线环境高得多。
最后说一个关于心态的。离线环境部署这事,第一次做会觉得处处是坑,但做顺了之后会发现它其实比在线环境更可控——因为所有依赖都是你自己准备、自己验证过的,不会因为上游更新而突然出问题。我现在反而更倾向于在关键项目上做完整的离线环境包,图的就是这种确定性。一次准备,多处复用,出问题也有据可查,这是在线环境给不了的踏实感。