ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Jupyter Notebook 从安装到排错:内核、单元格与常见报错全解析

2026/9/17 19:26:33 拓冰建站 浏览量
Jupyter Notebook 从安装到排错:内核、单元格与常见报错全解析 第一次在浏览器里敲代码感觉挺别扭的左边没有项目树右边没有调试面板连个像样的代码折叠都要靠快捷键。直到有次接手一份数据清洗任务我要反复调整几行过滤逻辑、来回看中间结果才意识到 Jupyter Notebook 这种把代码切成一格一格、每格单独跑、跑完立刻看输出的模式在探索性工作里简直顺手得不讲道理。这篇就围绕 Jupyter Notebook原名 IPython Notebook聊透它到底是什么、内核和单元格是怎么配合的、安装路线怎么选、日常高频操作有哪些、以及那些把人逼到摔键盘的报错——打不开、单元格执行没反应、ImportError: DLL load failed while importing rpds、cv2 导入失败——到底该怎么一步步排查。无论你是刚装完 Python 想找个地方练手的新手还是已经能写脚本、但想弄清楚 Notebook 底层机制的老手下面这些内容都能直接拿去用。1. Jupyter Notebook 到底是什么为什么数据圈离不开它1.1 从 IPython Notebook 改名这件事说起不少人在搜资料时会犯迷糊一会儿是 IPython Notebook一会儿是 Jupyter Notebook装的时候又冒出个 JupyterLab这几个名字容易把人绕晕。事情的脉络其实很清楚——早期这套东西叫 IPython Notebook是 IPython 项目的一个网页前端。2014 年前后项目做了拆分IPython 专心做 Python 的交互式内核也就是今天你看到的 ipykernel而负责网页界面、文件格式、内核通信协议的那一层独立成了 Project Jupyter。所以你今天在旧教程里看到ipython notebook这条命令或者看到别人说.ipynb是 IPython 文件本质上指的都是同一套东西只是叫法停留在旧版本。Jupyter 这个名字来自 Julia、Python、R 三个语言名的首字母从一开始就暗示了它的野心不只是给 Python 用的。现在它能挂载的内核有几十种Python、R、Julia、Scala、Go、甚至 C 都有对应的 kernel 实现。你写的内容存成.ipynb其实是一个 JSON 文本文件里面按顺序记录了每个单元格的类型、源码、执行计数和输出结果。理解了这一层很多事情就通了——为什么.ipynb用 Git diff 看会一坨一坨的因为里面还塞着 base64 编码的图片为什么同一个文件在两台机器上跑出来的结果不一样因为输出被写死在文件里而代码本身可能依赖本地环境。我在团队里带过几个刚转行的同事最常见的误区就是把.ipynb当成普通的.py来理解。它不是脚本它是一个带执行历史的会话记录。文件里既有代码也有结果甚至还有你已经删掉但历史里残留的输出。所以把它交给别人之前习惯性地点一下重启内核并运行所有是一份 Notebook 能不能被别人顺利复现的底线。1.2 单元格执行模型为什么乱序执行是新手最大的坑要真正把 Jupyter 用明白得先理解它的执行模型。整个系统大致分三层浏览器里跑的前端界面、负责干活的内核进程、以及连接两者的通信层。你在单元格里敲下代码按 ShiftEnter前端把代码通过消息通道发给内核内核在自己的 Python 进程里执行把 stdout、异常、图片等结果打包回传前端再渲染出来。注意关键点变量状态保存在内核的进程内存里不在单元格里。单元格只是要执行的片段它本身不携带任何上下文。这就解释了新手最容易踩的坑。比如你先在第 3 格定义了df pd.read_csv(data.csv)然后在第 8 格里用df.head()跑出来没问题。接着你觉得第 3 格那段读文件的代码写得不优雅把它删了或者把第 8 格拖到了第 2 格的位置再点运行——NameError: name df is not defined。明明代码看着没问题啊因为内核里的df还在但你这次执行的那一格之前没有把数据读进来的动作。Notebook 的执行顺序是你按下运行键的顺序不是单元格从上到下的顺序。所以有个我几乎强迫自己执行的习惯任何一段逻辑写完先Kernel - Restart Run All重启并运行全部跑一遍。跑得通说明顺序是对的、依赖是完整的跑不通就是有隐藏的乱序依赖。这个动作花不了几秒但能省掉大量我这明明能跑怎么发给你就报错的扯皮。另外一个细节是每个代码格左边的方括号——[12]表示这是内核启动以来第 12 次执行如果是[*]说明这格正在跑或者卡住了这个标记在排查没反应的问题时非常好用。判断一段 Notebook 是否健康最快的方法不是读代码而是看方括号里的数字有没有出现大的在前、小的在后。出现[27]在[5]上面基本可以确定存在乱序执行。1.3 它真正解决的三个问题以及它不适合干什么Jupyter 能在数据领域站住脚靠的不是能写 Python这一条毕竟能写 Python 的地方太多了。它真正解决的是三件很具体的事。第一件是中间结果的可见性。写数据分析最耗时的不是敲代码而是改一行、看一眼结果、再改一行的循环。传统脚本要么在关键位置插 print要么挂调试器打断点来回切换很碎。Notebook 把这个循环压缩到一次 ShiftEnter改完立刻看表、看分布、看图。我处理一份几万行的销售数据时光靠逐格调整 groupby 的维度就能在十几分钟内把口径摸清楚这是脚本模式做不到的效率。第二件是代码与结论同屏。一份完整的分析报告通常需要这段代码在做什么的说明和结果说明什么的结论。Notebook 的 Markdown 单元格可以把两者混排导出成 HTML 之后别人拿到的是一份带可运行代码的报告而不是一张孤零零的截图。这在和业务方对齐口径时特别有用——对方质疑数字你当场改条件重跑。第三件是教学与知识传递的门槛低。把一个算法拆成五六个单元格每个格跑一小步、看一眼中间变量理解速度比读一整段函数快得多。我见过不少 Python 基础教程、动态规划讲解、聚类演示都是用 Notebook 做载体的。反过来它不适合做什么也很明确不适合当生产环境的部署单元。Notebook 没有清晰的主入口执行顺序依赖人工维护状态藏在内存里出了问题不好定位。所以务实的做法是在 Notebook 里做探索和验证把验证好的逻辑抽成.py模块Notebook 只负责调用和展示。这条探索在 Notebook、沉淀在模块的路线是我踩过几次坑之后最认可的用法。2. 安装与环境配置三条主流路线怎么选不踩坑2.1 pip、conda、Anaconda 全家桶横向对比搜jupyter notebook 安装的人十有八九会在第一步就卡住教程有的让你pip install notebook有的让你装 Anaconda还有的让你用conda install。这几条路线都能用但适用场景差别挺大选错了后面会一直难受。方案安装体积适合谁常见坑点pip install notebook venv几十 MB 级别已有 Python、想保持环境干净命令行找不到jupyter命令多半是 PATH 问题Miniconda conda 环境几百 MB需要频繁切换环境、装科学计算库混用 pip 和 conda 装同一个包容易冲突Anaconda 全家桶3 GB 以上完全新手、不想折腾依赖环境臃肿base环境里装东西容易污染我自己的选择是 Miniconda原因很实际科学计算类库numpy、pandas、scipy在 conda 源里是预编译好的Windows 上装起来比 pip 少了大量编译踩坑的概率同时它不像 Anaconda 那样预装几百个用不到的包磁盘和心智负担都小。如果你只是想试试水、不想装任何管理器直接pip install notebook也能跑起来前提是你的 Python 版本别太老——Notebook 7 要求 Python 3.8 以上实际用下来 3.10 到 3.12 是比较舒服的区间。这里有个容易忽略的版本分叉notebook这个包在 7.x 之后做了大重构界面底层换成了 JupyterLab 的组件观感和老版本差别明显。如果你跟着一篇三年前的教程操作发现菜单位置完全对不上可以先确认版本jupyter notebook --version。想看老界面可以退回 6.x但我建议直接用新版快捷键和扩展生态后面都会往新版迁移。2.2 虚拟环境与内核注册这一步不做后面全是坑很多人装完 Notebook 就兴冲冲地开写结果import pandas报 ModuleNotFoundError转头去装装完还是找不到。根源通常只有一个你装包的 Python 和 Notebook 用的内核不是同一个 Python。正确姿势是先造独立环境再把这个环境注册成 Notebook 能识别的内核。以 venv 为例Windows 和类 Unix 系统的激活命令不一样这点要注意# 创建虚拟环境 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 装 Notebook 和内核 pip install notebook jupyterlab ipykernel # 把当前环境注册成 Jupyter 内核 python -m ipykernel install --user --namemyenv --display-name Python (myenv)--name是内核的内部标识别用中文和空格--display-name是你在界面上看到的菜单项随便写。注册完之后启动 Notebook在Kernel - Change kernel里就能看到 Python (myenv) 这个选项。之后所有pip install都必须在激活了这个环境的前提下执行否则包装到了全局 Python 里Notebook 依然看不见。日常维护会用到两条命令jupyter kernelspec list列出所有已注册内核及其路径用来确认有没有重复注册jupyter kernelspec remove myenv在环境删掉之后清理残留。我遇到过环境已经删了、但内核列表里还挂着的情况选中它启动会直接报内核启动失败看着吓人其实就是个僵尸条目。2.3 在 VSCode 和 Neovim 里跑 Notebook值不值得有不少人搜 vscode python 环境配置 或者 jupyter notebook nvim说明主力编辑器不是浏览器。这两条路我都试过说说体感。VSCode 的路子最省心装官方 Python 扩展和 Jupyter 扩展打开.ipynb文件右上角点选内核直接就能一格一格跑。变量面板、调试、Git 集成全都在写代码时的补全体验比浏览器版强不少。它和浏览器版共享同一套.ipynb文件两边来回切没问题。唯一需要注意的是内核选择——VSCode 会同时列出 conda 环境、venv、以及系统 Python选错了就又是那个装了包找不到的老问题。Neovim 这边就没那么开箱即用了。常见的做法有两类一是用 jupytext 把.ipynb和.py做双向同步你在.py里写改完自动写回 ipynb配合一个能发代码块到终端的插件比如 slime 风格的方案执行二是走专门的 Notebook 前端插件在编辑器里内嵌单元格输出。前者的好处是文件可以正常 Git diff、可以用 LSP 补全代价是图片输出、富文本展示会打折扣。如果你的工作是纯代码逻辑验证、图看得少这条路很舒服如果要频繁看图看表我还是会回到浏览器。3. 高频操作与效率技巧这些细节决定你用得爽不爽3.1 单元格类型与必须记住的快捷键Notebook 的单元格有三种类型Code代码、Markdown富文本、Raw原样输出基本用不上。切换靠命令模式下的 Y 和 M 两个键。这里要强调两种模式的概念按 Enter 进入编辑模式边框变绿可以打字按 Esc 退回命令模式边框变蓝快捷键生效。很多新手抱怨快捷键没反应八成是还在编辑模式里按了 A 或 B。快捷键作用使用场景ShiftEnter执行并跳到下一格顺序往下推的最常用键CtrlEnter执行并停在当前格反复调同一段逻辑时用AltEnter执行并在下方插入新格边写边往下扩A / B在上方 / 下方插入单元格补一段说明或中间步骤DD删除当前单元格注意是连按两下 DZ撤销删除单元格误删之后的救命键ShiftTab查看函数签名和文档忘了参数顺序时用连按可展开CtrlShiftP打开命令面板找不常用的功能ShiftTab这个我几乎每分钟都用。光标停在函数名后面的括号里按一下会弹出参数提示连按两下、三下会展开更完整的文档。比去浏览器查文档快得多。Z键也值得单独记一下它撤销的是删除单元格这个操作本身和编辑模式里的 CtrlZ 撤销文字输入是两回事误删一整格的时候全靠它。3.2 魔法命令那些让 Notebook 变得聪明的东西魔法命令magic commands是 Notebook 区别于普通 Python 解释器的核心加分项。以%开头的作用于单行%%开头的作用于整个单元格。下面这几个是我日常用得最频繁的。计时类的%timeit和%%time用途不同%timeit会把语句跑很多次取平均值适合比较两个写法的性能差异%%time只在单元格开头写一次统计整格的真实耗时适合看一段完整流程花了多久。做数据清洗时我习惯在耗时的格子上加%%time改一次看一次很直观地知道哪个步骤是瓶颈。%matplotlib inline现在其实已经是默认行为但在某些老版本或者特定后端下仍然需要显式声明它保证画出来的图直接嵌在单元格下方而不是弹独立窗口。%run script.py能把一个外部脚本整个当成当前单元格来执行执行完里面的变量会留在内核里这个在模块开发 Notebook 调试的组合里特别顺手。%load更狠它会把目标文件的源码直接读进当前单元格方便你拿来改。还有一组管理类的%pwd看当前工作目录%cd切目录%env查看或设置环境变量%who/%whos列出内核里现存的所有变量。最后这个在排查变量到底还在不在的问题时非常有用——报 NameError 之前先%whos看一眼比瞎猜快。%%writefile则可以把当前单元格的内容写成一个文件用来把验证好的代码快速落盘成.py。用%who的时候如果变量太多输出会很长。可以加类型过滤比如%who DataFrame只列出所有 DataFrame 类型的变量定位数据对象很快。3.3 代码自动补齐三种方案按需选jupyter notebook 代码自动补齐是个被搜爆的关键词说明大家对默认补全的期待和实际体验有落差。现状是这样的Notebook 7 和 JupyterLab 默认就带有基于内核的补全按 Tab 能补出变量名、模块属性、函数参数但这个补全只在当前内核已知的命名空间里有效跨行推断能力有限对刚导入还没执行过的模块基本没戏。想要更强的补全方案有三档。第一档是装jupyterlab-lsp配合 Python 语言服务器jedi-language-server 或 pylsp它能做到类型推断、跨行补全、跳转定义、悬浮提示体验接近正经 IDE。代价是配置稍麻烦需要额外装语言服务器并在设置里指认。第二档是用jupyter-contrib-nbextensions里的 Hinterland 扩展它在老版 Notebook 6 上是标配级别的存在能边打字边弹补全菜单但新版 Notebook 7 对 nbextensions 的支持已经变了需要换用基于 JupyterLab 扩展体系的替代品。第三档最简单也最稳把关键逻辑拆到.py模块里在 VSCode 里写补全天然完善Notebook 只做调用和展示。我的实际组合是第二档和第三档混着用日常快速探索靠默认补全加 Tab正式写到一段需要反复推敲的逻辑时切到 VSCode 写模块Notebook 里%autoreload 2配合importlib.reload热加载改完存盘就能看到新效果不用重启内核。3.4 Markdown 目录原理和三种实现jupyter notebook 怎么生成 markdown 目录语法这个问题背后有个前提要知道Notebook 的 Markdown 单元格渲染出来的 HTML并不会自动给标题生成可点击的锚点链接。标准的 Markdown 语法[文字](#标题)在这里默认是不生效的。所以想要目录得靠扩展或者手动处理。手动方案是自己维护锚点。在 Markdown 单元格里写a idsec1/a这种 HTML 锚点然后在顶部写[第一节](#sec1)跳转就通了。这个做法零依赖、导出 HTML 后也有效缺点是维护成本高标题一改就得同步改锚点。中文标题还有个细节Jupyter 对中文的锚点处理规则在不同版本里不完全一致稳妥起见别依赖自动生成自己写 id 最保险。扩展方案在 JupyterLab 里是jupyterlab-toc它会从所有 Markdown 标题自动抽取层级在左侧边栏生成一个可折叠的目录树点一下直接跳。老版 Notebook 6 用的是 nbextensions 里的 Table of Contents (2)功能类似还能把目录直接插到文档最上面。这两个都是装上就干活的类型比手动维护锚点省太多事。第三种是导出环节解决用jupyter nbconvert导出 HTML 时有些模板会带上自动目录适合交付场景因为对方拿到的是一份完整文档不需要自己装任何扩展。4. 常见问题排查手册从打不开到没反应4.1 命令敲下去没动静浏览器起不来这是最高频的求助场景表现形式有好几种得分清楚。第一种是敲jupyter notebook直接提示command not found或者jupyter 不是内部或外部命令。这几乎可以断定是 PATH 问题装 jupyter 的那个环境没被正确加进系统路径或者你压根没激活虚拟环境。先用python -m jupyter notebook试试如果能起来说明包在、只是命令入口没暴露那就检查激活状态和 PATH。第二种是命令跑起来了、终端打印了一堆 URL但浏览器没弹出来。这时候别慌手动把终端里那行带 token 的地址形如http://localhost:8888/?tokenxxxx复制粘贴到浏览器地址栏就行。token 这串东西是访问凭证复制的时候别漏掉问号后面的部分漏了会显示要求输入密码。如果你希望每次都不用手动复制可以在配置里设置固定的访问口令但更省事的做法是启动时加参数让它自动打开浏览器。第三种是端口被占用报错里会明确写Address already in use或者port 8888 is already in use。因为默认端口是 8888你同时开两个 Notebook 实例就会撞车。解决办法是指定新端口jupyter notebook --port 8889。如果记不住端口加--port0让系统自动挑一个空闲端口终端会打印实际用的那个。# 指定端口、不自动开浏览器、启动时不带 token仅本机调试用 jupyter notebook --port 8889 --no-browser第四种相对少见但很坑Notebook 明明启动了页面也加载出来了但一直转圈或者显示 Kernel not found。这通常是内核注册信息坏了用jupyter kernelspec list看看有没有指向不存在的路径有的话删掉重新注册。4.2 单元格执行代码没有任何反应这个问题的描述通常是单元格左边的方括号变成了[*]然后再也不动了既不报错也没输出。排查逻辑应该按确认它到底是在算还是真死了这个方向走。第一步看方括号。如果是普通的数字比如[7]说明其实执行完了只是没输出——你的代码可能真的没有 print或者最后一个表达式返回了 None。很多新手以为没输出就是没跑其实 Pandas 的赋值操作本来就不打印任何东西想看结果得显式写df.head()或者print(...)。第二步看是不是在跑。如果方括号里是*说明内核正忙。这时候要判断是长时间计算还是卡死跑一个几百万行的循环、训练一个小模型确实会卡很久。可以开另一个单元格试试简单语句比如11能不能执行——能执行说明内核活着只是在排队完全没响应那就是卡死了。卡死的常见原因有三个。一是死循环while True忘了加退出条件或者逻辑写错导致条件永远为真这种情况只能中断内核菜单里点Kernel - Interrupt二是内存爆了读了一个超大文件或者做了笛卡尔积式的合并系统开始疯狂交换内存表现就是整个界面都变卡这时候要看系统任务管理器里内存占用三是内核进程真的崩了比如调用了某个 C 扩展库触发了段错误前端不知道只能等超时。判断内核是不是彻底崩了最直接的办法是看终端窗口。如果内核进程已经消失终端里会有对应的退出信息如果进程还在但 CPU 占用为零多半是死锁在某个 I/O 或者锁上。中断不管用的时候只能Kernel - Restart。这里要提醒一句重启内核会清空所有变量你前面辛苦读进来的数据全没了。所以长耗时的数据处理我习惯把中间结果存成 parquet 或者 pickle重启之后直接读缓存几秒钟就能恢复到现场比重新跑十分钟划算得多。4.3 ImportError: DLL load failed while importing rpds 怎么修这个报错挺有代表性因为它看起来和数据科学八竿子打不着——rpds 是个持久化数据结构的库是 JupyterLab 依赖链里的一环用户根本不会主动去装它。报错完整形式通常是ImportError: DLL load failed while importing rpds: 找不到指定的模块出现在启动 Notebook 的阶段直接导致界面起不来。根因有两个方向。方向一是rpds-py 是带原生扩展的包它用 Rust 写的、编译成二进制安装时必须匹配你的 Python 版本和操作系统架构。如果你的 Python 是比较新的小版本、或者环境是 32 位 Python而现在很多包只发 64 位 wheelpip 可能拿不到预编译包退而求其次去本地编译编译环境不全就产生了残缺的二进制。方向二是Windows 上缺少运行库二进制扩展依赖系统级的运行时组件缺了就会出现加载 DLL 失败。修复顺序我建议这样走先升级 pip 再重装因为老版本 pip 对 wheel 的匹配逻辑有 bug经常选错包python -m pip install --upgrade pip pip install --force-reinstall rpds-py如果还是不行确认一下 Python 架构是不是 64 位python -c import platform; print(platform.architecture())32 位 Python 在今天的生态里是已经被边缘化的存在强烈建议换成 64 位。再不行就装一下系统的运行库组件Windows 上对应的是常见的 C 运行库安装包装完重启终端再试。最后一条退路是降级或升级整个 JupyterLab 版本让依赖链重新解析一遍比如指定pip install jupyterlab4.1很多时候换个版本就绕过了那个坏掉的二进制。还有个细节值得说如果你在 conda 环境里遇到过这个错试试用 conda 装而不是 pip因为 conda 的包是统一编译的二进制兼容性通常更好conda install -c conda-forge rpds-py。4.4 cv2 装不上、导入失败包名和版本这两个坑python 下载 cv2是个搜索量很大的词但这里有个非常经典的误会import cv2对应的包名不是 cv2而是 opencv-python。你执行pip install cv2会得到一个完全无关的同名包装了之后import cv2照样报错。正确的安装命令是pip install opencv-python需要 GUI 相关的功能再加opencv-contrib-python。第二个坑是numpy 版本冲突。OpenCV 的 Python 包对 numpy 的 ABI 有要求如果你环境里的 numpy 版本过老或过新导入时会抛ImportError: numpy.core.multiarray failed to import这类信息。这时候的处理顺序是先pip install -U numpy升级到较新版本再pip install --force-reinstall opencv-python让它针对当前 numpy 重新链接。反过来如果你在用某个老版本的深度学习框架它锁死了 numpy 版本那就得找与之匹配的 opencv 版本不能盲目追新。第三个是镜像源缓存问题。有时候 pip 从缓存里拿出了不完整的 whl 文件表现也是导入报错。加--no-cache-dir强制重新下载往往就好了。4.5 中文乱码、图片显示和横坐标太挤Matplotlib 在 Notebook 里默认的字体不支持中文画出来的中文标签会变成一堆方框。解决办法是显式指定字体同时把负号的处理也改掉否则负号也会变方块import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 常用 # macOS 可用 [Arial Unicode MS]Linux 常见 [WenQuanYi Micro Hei] plt.rcParams[axes.unicode_minus] False # 正常显示负号如果设置完仍然乱码先确认这个字体在你系统里真的存在用matplotlib.font_manager查一下可用字体列表另外设置必须在绘图之前执行画完再改是没用的已经在内存里的图不会回溯更新。python 画图横坐标太密集也是高频困扰本质是标签数量和画布宽度不匹配。处理手段有几种可以组合使用把画布拉宽plt.figure(figsize(14, 5))旋转标签plt.xticks(rotation45, haright)控制标签数量时间序列可以只显示间隔的几个刻度或者干脆换成水平条形图把标签放到 y 轴上。我个人最常用的是拉宽 旋转 45 度这个组合五分钟能解决九成的情况。对于时间轴用matplotlib.dates的格式化器配合AutoDateLocator让它自动挑选合适的刻度密度比手动数标量靠谱。4.6 常见问题速查表现象最可能的原因优先尝试的处理提示 jupyter 不是内部或外部命令环境未激活或 PATH 未配置用python -m jupyter notebook启动浏览器不自动打开默认行为被禁用或浏览器关联异常手动复制终端里的带 token 地址端口占用报错8888 已被其他实例占指定--port8889或--port0单元格一直显示 [*]内核繁忙、死循环或内存耗尽先Interrupt无效则Restart执行完无输出代码本身无输出语句显式打印或把表达式放最后一行NameError 变量不存在乱序执行导致定义未生效Restart Run All验证顺序DLL load failed while importing rpds二进制扩展不匹配或缺运行库升级 pip、重装 rpds-py、检查架构import cv2 失败装错包名或 numpy 冲突装 opencv-python对齐 numpy 版本中文显示为方框字体未配置设置 font.sans-serif 和 unicode_minus5. 把 Notebook 用得更稳的几个经验5.1 内核管理与内存别让一个格拖垮整个会话内核是 Notebook 的命脉但它同时也是最容易被忽视的资源。一个内核就是一个完整的 Python 进程它占着内存、持有所有变量。你开的 Notebook 越多系统里攒的内核就越多。我见过同事电脑卡到打字延迟一看任务管理器后台挂着七八个 Jupyter 内核每个都吃了几个 G。管理内核有几个习惯值得养起来。第一用完的 Notebook 页面关掉之后顺手在终端里 CtrlC 两次结束服务不要只关浏览器标签页那样进程还在跑。第二及时清理大变量处理完一个几百万行的 DataFrame 之后del df再import gc; gc.collect()虽然 Python 的垃圾回收对循环引用有处理但 pandas 对象里常有不那么规矩的引用手动触发一次回收能立刻把内存还给系统。第三善用中间结果落盘把清洗完的数据写成 parquet 文件下次重启内核后读文件比重新跑一遍 ETL 快一个数量级。parquet 比 CSV 体积小、读取快、还保留数据类型是数据量上到几十万行之后我几乎固定的选择。%whos这个魔法命令在这里很有价值它会列出当前内核里所有变量的名字、类型和大小。当你感觉内存吃紧却不知道是谁占的跑一下这个按大小排一眼就能找出肇事者。5.2 和数据分析、爬虫工作流的配合方式Notebook 最常见的两个应用场景一个是数据分析与可视化一个是爬虫相关的快速验证。这两个场景对工具的要求其实很像需要频繁试错、需要立刻看到结果、需要保留过程。但用法上有各自的注意点。做数据分析时我通常把一份 Notebook 分成三段。第一段是环境准备和数据加载只跑一次把数据读进内存。第二段是一连串的探索格每个格只做一件事看缺失值、看分布、看异常值、试试不同的分组维度。这段最乱也最随意改来改去很正常。第三段是把确定下来的逻辑整理成连贯的代码块配上 Markdown 说明每个步骤的意图。第三段才是能拿给别人的部分。把这个探索区和结论区分开是我保证 Notebook 可读性的核心方法——不加区分的 Notebook 会变成一锅粥三天后自己都看不懂。做爬虫的时候Notebook 的优势在于快速迭代请求参数和解析逻辑。请求头怎么构造、返回的 JSON 结构长什么样、分页参数怎么变化这些都可以在一格一格里试。但要注意一点长时间的批量抓取不要放在 Notebook 里跑。一方面中断不好控制另一方面页面一刷新或者内核一重启跑到一半的任务就没了。务实的做法是在 Notebook 里把单个请求跑通、把解析函数写好然后把函数复制到.py脚本里批量执行Notebook 里只保留调试用的几行。数据抓回来之后再回到 Notebook 上做清洗和可视化两边分工明确。5.3 导出与交付nbconvert 和 jupytext 的分工写好的 Notebook 怎么交给别人这件事比想象中重要。直接发.ipynb文件的问题在于对方如果没装环境打开只能看到输出看不到代码对方如果装了环境可能会不小心执行出不同的结果。所以交付前通常要做一次转换。nbconvert是官方自带的转换工具可以把 Notebook 转成 HTML、PDF、Markdown、Python 脚本等多种格式。转 HTML 是最常用的因为输出、图片、Markdown 说明全都保留对方用浏览器直接打开就能看不需要任何环境# 转成 HTML并执行一遍确保输出是最新的 jupyter nbconvert --to html --execute mynotebook.ipynb--execute这个参数很关键它会先把整个 Notebook 从头到尾跑一遍再导出保证导出的结果是基于当前代码的真实输出而不是文件里残留的旧结果。这一步顺便还能帮你发现乱序执行的问题——跑不通就说明顺序有问题。jupytext走的是另一条路它专注于让.ipynb和纯文本格式.py、.md双向同步。好处是纯文本可以正常用 Git 做 diff 和 code review不会被 JSON 里的输出干扰。团队协作场景下我倾向于让.py作为版本控制里的事实来源.ipynb作为本地运行时的产物通过 jupytext 自动配对。这样既保留了 Notebook 的交互体验又避免了每次合并都冲突。另外提一个容易被忽略的细节交付之前把 Notebook 里所有没用到的 import、调试用的 print、临时写的测试格清理掉。一份干净的 Notebook 和一份塞满碎片的 Notebook给人的专业感差距很大也直接影响别人复现你工作的意愿。最后说个我自己踩过的坑。有段时间我习惯在一个 Notebook 里塞十几个不同的分析主题觉得方便后来发现自己找个两周前的结论要翻半天而且某些格子和另一些格子有隐藏的依赖一动就崩。现在的做法是一个 Notebook 只解决一个问题主题相关但独立的分析拆成独立文件公共的读取和清洗逻辑抽成模块。这个调整之后我的 Notebook 打开速度变快了重跑的成功率也高了很多。另外一个实用的小技巧是给每个 Notebook 的第一格固定放上环境说明——Python 版本、关键库的版本、数据文件的路径三个月后回头看这份记录能省掉大量当时到底用的哪个版本的回忆成本。