如果你平时和 Python 打交道,很难完全绕开 Jupyter Notebook/JupyterLab 这两个名字。我 2015 年第一次用 IPython Notebook 写实验记录,后来一路看着它改名、升级,变成现在几乎每个数据分析师都会打开的 Jupyter 生态。很多刚上手的人会把它们当成同一个东西,其实这中间既有延续,也有很明显的差异;而日常使用中那些让人抓狂的报错,很多也不是代码问题,而是出在环境、目录、内核这类“看起来无关”的地方。
这篇文章不做官方文档的复读,把我这几年在 Notebook 和 JupyterLab 上反复踩过的坑合并成一份实战笔记,重点讲清楚四件事:安装配置时常见的 SSL 和构建报错该怎么处理,侧边栏标题总览到底怎么调出来,单元格和目录的使用逻辑是什么,以及当你遇到“无法打开、无法运行代码”时应该从哪条线开始排查。适合刚装好 Anaconda 的新手,也适合已经写了几个月但总觉得哪里不顺手的人。
1. 先分清 Jupyter Notebook 和 JupyterLab:不是替代关系,而是两种工作方式
1.1 前端、内核、文档:三个不能被混为一谈的概念
很多人分不清 Notebook 和 Lab,是因为打开后满屏都是单元格,看起来差不多。实际上 Jupyter 生态里存在三个层次:前端界面、内核、文档格式。文档格式都是.ipynb,内核也是同一套 Python、R、Julia 进程,真正变的是前端界面。
Notebook 是早期的单体界面,一个页面只能专注一个文档,顶部菜单、工具栏、单元格从上到下排开,结构简单,学习成本低。JupyterLab 是后来重新设计的“集成开发环境式”界面,在同一个窗口里可以并排打开多个 notebook、终端、文本文件、图像,窗口还能拖拽分栏。我自己的感受是:如果你只是临时算个数、做个小演示,Notebook 足够;但只要开始把 Jupyter 当日常主力工具,你会慢慢被 Lab 的多标签布局吸引,因为同一个项目里代码、文档、终端同时摊开,不用反复切窗口。
另一个容易忽略的点是启动入口。Anaconda 装完后,开始菜单里既有 Jupyter Notebook 也有 JupyterLab,很多人以为要分别安装,其实它们共享同一套内核和配置。你完全可以今天用jupyter notebook打开,明天用jupyter lab打开,看到的是同一个目录、同一批 notebook 文件。
1.2 选型建议:不同场景用不同前端
如果你带着数据科学任务从零开始,我的建议是直接上 JupyterLab,同时保留 Notebook 作为备选。并不是说 Notebook 过时,而是 Lab 在文件管理、多文档操作、扩展支持上更接近现代工具。官方也把新功能的开发重点放在 Lab 上,边缘情况修复和插件生态都更新得勤快。
我做一个简化对照,方便不同使用习惯的人快速判断:
| 判断维度 | Jupyter Notebook | JupyterLab |
|---|---|---|
| 单文档写作体验 | 简洁、直观、无干扰 | 窗口面板丰富,需要适应拖拽 |
| 多文件并排 | 基本做不到 | 天然支持分栏,超好用 |
| 内置终端 | 不提供 | 可以直接打开终端,省去来回切 |
| 侧边栏大纲 | 需要第三方目录扩展 | 内置 TOC,点开就有 |
| 插件安装方式 | nbextensions 体系 | labextension/扩展管理器体系 |
| 适合场景 | 新手入门、快速记笔记 | 常态化开发、项目综合管理 |
不过要注意,Notebook 时代的很多经典扩展,比如jupyter_contrib_nbextensions,是为 Notebook 做的,并不能直接用于 JupyterLab。不少人都在这上面翻过车:按教程装好,打开 Lab 却找不到任何变化。这个我在第 3 章会专门说。
2. 安装配置避坑:SSL、subprocess 和内核混乱问题
2.1 用 conda 独立环境代替裸装包,先治本
在讲具体报错之前,我最想强调一个习惯:不要把所有包都往 base 环境里堆。很多 Jupyter 报错表面上是“启动失败”“无法运行代码”,根子其实是不同项目的依赖搅在一起,今天升级一个包,明天另一个库就不兼容。
我现在的标准做法是每类任务创建一个干净环境。比如做课程演示和快速实验,我通常会这样初始化:
conda create -n lab python=3.11 -y conda activate lab conda install -c conda-forge jupyterlab -y conda install -n lab ipykernel -y这里每一步都是有目的的。conda create隔离出独立 Python;conda install从 conda-forge 通道装 Lab,而不是用 pip 裸装,因为 pip 在 Windows 上经常需要现场编译;ipykernel则是让 Jupyter 能识别这个环境的关键,少了它,你即使在终端里conda activate lab,打开 notebook 后用的还是别的内核。
注册内核是另一个常被跳过但至关重要的动作:
python -m ipykernel install --user --name=lab --display-name "Python (lab)"执行完可以用jupyter kernelspec list查看,你会看到多了一个名为 lab 的内核。这一步做完,JupyterLab 的启动器里才会出现 “Python (lab)” 这个选项。我遇到过不少“环境里明明装了 pandas,notebook 里却 import 不到”的情况,十有八九是内核没注册,notebook 实际跑的是另一套 Python。
2.2 Windows 11 下配置 JupyterLab 提示 SSL ASN1 错误是什么情况
这个话题几乎是搜索热词级别的了。报错信息大概长这样:ssl.SSLError: [SSL] ASN1: not enough data,有的版本还会在文字里带ca-certificates、certificate verify failed之类的上下文。好多人看到 “SSL” 就以为要配置证书或代理,其实在 conda 环境里遇到这个,大多数是环境内部组件版本错位。
原因说起来并不神秘。conda 在升级某个环境时,可能会更新openssl或者ca-certificates,但 Python 自己编译时依赖的 ssl 模块版本没有同步,或者环境里有旧版证书链,导致在建立 HTTPS 连接时解析证书失败。Windows 11 上更常见的场景是:你创建了一个新环境,里面默认的 openssl 版本比较新,而conda、pip或jupyter在拉取远程源时,内部使用的 SSL 库和证书路径对不上。
我按自己的经验整理过一套解决顺序,你不需要全做,但顺序很重要:
conda update -n base conda -c conda-forge -y conda activate lab conda update --all -y conda install -c conda-forge openssl ca-certificates -y先更新 base 的 conda 本身,再更新当前环境的全部包,最后单独强制装一次openssl和ca-certificates。多数情况下,到这一步重启终端,再执行jupyter lab就不会再报 ASN1 错误。如果还不行,检查一下系统环境变量里有没有手填过SSL_CERT_FILE或REQUESTS_CA_BUNDLE,这类变量一旦指向过期证书文件,会让 Python 忽略系统证书库,造成一样的表现。
这里特别提醒:网上有些帖子会让人直接关掉 SSL 校验,比如设置pip config set global.trusted-host或者改环境变量跳过验证。这在临时环境里也许能跑,但本质是掩盖问题,之后会有更隐蔽的证书错误,不建议长期使用。
2.3 pip 安装扩展时报 subprocess-exited-with-error,别急着重装
搜索词里还有一个高频错误:error: subprocess-exited-with-error。它通常出现在你用 pip 安装某个 Jupyter 扩展或 Python 包时,下载完成、进入构建阶段后立即退出,终端能看到类似 “Getting requirements to build wheel ... error” 的字样。
新手最容易犯的错误是反复卸载重装同一个包,其实问题根本不在包本身。现在 pip 安装源码包默认会用 PEP 517 的隔离构建流程,很多包在构建阶段需要调用编译器。Windows 上最常见的是缺少 Microsoft C++ Build Tools;Linux 上常见的是缺少python3-dev、gcc;macOS 上则是 Xcode Command Line Tools 未安装完整。
我给你的排查思路是分三步走:
第一,能用 conda 装的优先用 conda,例如:
conda install -c conda-forge jupyter_contrib_nbextensions -y它会直接拉预编译好的二进制,不需要在你的机器上碰编译器,这是省时间最明显的一条路。
第二,确实要用 pip,先升级构建工具链:
python -m pip install --upgrade pip setuptools wheel然后重新安装,并在命令里加上--no-cache-dir避免缓存了损坏的源码包。
第三,如果依然失败,不要盲目用最新版本。某些源码包在特定 Python 版本下有兼容性问题,可以尝试指定旧一点的版本安装,或者加--no-build-isolation参数跳过 PEP 517 隔离,让安装过程复用当前环境已有的编译工具。这个参数不是银弹,但能在找不到编译头文件时给出更真实的提示,方便你判断是缺依赖还是包本身的问题。
3. 日常工作流:目录侧边栏、单元格操作和魔术命令
3.1 侧边栏如何显示标题总览?先说结论
“侧边栏怎么显示标题总览”是很多人第一次接触 Jupyter 的高级功能时问的问题。先说结论:JupyterLab 直接打开左侧栏的“目录”图标就行,不需要额外插件;老版 Jupyter Notebook 需要装一个 Table of Contents 扩展;两个前端的大纲都来自 Markdown 单元格里的标题标记。
在 JupyterLab 里,只要打开任意.ipynb文件,点左上角侧边栏的目录图标,右边就会出现带层级的大纲。但这个功能有个前提:你的标题必须写在 Markdown 单元格里,用#、##、###表示,不能只是用代码注释或普通文本。很多人以为写了# 标题就会出现在目录,结果不显示,就是因为那个单元格还是 Code 类型,按Esc后按M把单元格切换成 Markdown,再运行一次,大纲就出来了。
对于 Classic Notebook,我推荐走jupyter_contrib_nbextensions路线。安装方式:
conda install -c conda-forge jupyter_contrib_nbextensions -y jupyter contrib nbextension install --user jupyter nbextension enable toc2/main安装完成后打开 notebook,顶部菜单会出现 Nbextensions 选项,在里面勾选 Table of Contents,侧边栏或悬浮窗口就能看到标题总览。这里有个细节:个别 Windows 环境下,即使安装成功,浏览器里也不显示 Nbextensions 菜单,通常是因为 notebook 版本太新或浏览器缓存问题。我建议装完后重启 jupyter 服务,并用无痕窗口重新打开一次。
为什么标题总览如此重要?因为 notebook 是“线性记录”,几十个单元格一路滑下去,想回头找某段结论非常痛苦。有目录之后,长文档的定位效率会提升非常明显,尤其是每周复盘实验、给别人演示分析流程时,相当于给笔记加了一套索引。
3.2 单元格操作快捷键:每天节省半小时的细节
标题总览解决的是“找得到”,单元格快捷键解决的是“改得快”。Jupyter 的操作模式分成命令模式和编辑模式,两者最直观的区别是:编辑模式下单元格里有一个闪烁光标,可以打字;命令模式下单元格边框是蓝色,按键对应各种操作。
我最常用的组织方法可以浓缩成一句口诀:Enter进编辑,Esc回命令,Shift+Enter运行并到下一个单元格。日常还会用到几组不复杂但极其顺手的快捷键:
| 快捷键 | 作用 | 使用频率 |
|---|---|---|
Shift+Enter | 运行当前单元格,光标跳到下一个 | 几乎每次 |
Ctrl+Enter | 运行当前单元格,光标不移动 | 反复试参数时 |
Alt+Enter | 运行并在下方插入新单元格 | 逐步推进时 |
Esc后按A/B | 在上方/下方插入单元格 | 高频 |
Esc后按D D | 删除当前单元格 | 高频 |
Esc后按Z | 撤销删除 | 救命用 |
Esc后按M/Y | 切换 Markdown / Code | 写文档时 |
Esc后按Shift+↑/↓ | 多选批量删除或移动 | 整理长 notebook 时 |
如果你把 JupyterLab 当主力,还可以在 Settings → Keyboard Shortcuts 里自定义快捷键。我有一个小习惯,把“运行当前单元格并向下插入”绑到Ctrl+Enter,用起来比默认顺手很多,适合想一边跑结果一边补注释的流程。快捷键不值得背完,先把 Shift+Enter、A/B、D D、M/Y 用到形成肌肉记忆,效率就已经肉眼可见提升。
3.3 魔术命令:装了三年没用的隐藏功能
除了界面操作,Jupyter 底层继承自 IPython 的魔术命令也很值得用。它们以%开头,能直接嵌入单元格,解决很多“本来要写十几行 Python 才能做”的事。
我最常用的一套是这样:
%timeit:测量单行代码执行时间,自动跑多次取最短值,比手写time.time()靠谱得多。在单元格开头用双百分号%%timeit,还能计时整个单元格。%run xxx.py:把外部 Python 脚本在当前内核里执行,相当于把脚本内容塞进 notebook,还共享当前变量。做代码评审时,我经常把同事给的.py脚本用%run拉进来跑,不用复制粘贴。%load xxx.py:把外部文件内容加载进单元格,方便边看边改。%env:查看或设置环境变量,比如%env MY_KEY=123,在 notebook 里管理临时配置很好用。%matplotlib inline:让 matplotlib 图形直接显示在输出区域。新版本里用%matplotlib widget还能得到可交互的缩放图形。!pip install xxx:感叹号开头表示执行系统命令,很多教程会让你打开终端装包,其实在 notebook 里直接用!pip install也行,但那是在当前内核对应 Python 环境里安装,注意别和自己激活的 conda 环境错位。
魔术命令还有一个隐藏入口,在单元格里输入%magic会弹出完整帮助文档。我看过不少写了两三年 notebook 的人,从没用过%timeit,通篇手写计时逻辑,后面很容易被“看起来运行很快、实际卡很久”的假象骗到。
4. 扩展和内核管理:真正把 JupyterLab 变成开发环境的两个关键
4.1 JupyterLab 扩展的安装方式和适量原则
Jupyter 生态的扩展体系经历过几次变化,很多人还按老教程执行jupyter labextension install xxx,结果在新版本里报错。以 JupyterLab 4.x 为例,大部分扩展已经可以通过左侧的 Extension Manager 图形化安装,命令行的推荐方式是先激活你的环境,再用 conda 或 pip:
conda install -c conda-forge jupyterlab-git -y conda install -c conda-forge jupyterlab-lsp -y conda install -c conda-forge python-lsp-server -y第一行是 Git 集成,第二三行是语言服务器协议,装上后能在 notebook 里获得跳转定义、悬停提示这类 IDE 功能。从实际体验来说,我建议扩展数量克制一点,最少主义优先:目录(内置)、Git 集成、LSP 就够覆盖日常开发。装太多花哨主题和预览插件会让启动速度变慢,还容易互相冲突。
有一个很多人踩过的坑:安装了某个扩展后,JupyterLab 无法启动,进度条卡在 Building 阶段。这时候不用急着卸载整个环境,可以删除对应的扩展目录,或者启动时加--disable-check暂时绕过检查,再进入界面卸载问题扩展。另外,JupyterLab 和 Notebook 的扩展体系根本不互通,你在 Lab 里安装jupyter_contrib_nbextensions不会对 Lab 界面有任何效果,它只作用于经典 Notebook。
4.2 内核管理:为什么换了 conda 环境,notebook 里依然没有这个包
这是搜索词里和“报 red 无法运行代码”并行的核心问题:明明在终端里conda activate myenv之后再启动 Jupyter 了,为什么 notebook 里仍然ModuleNotFoundError?
本质原因是,Jupyter 内核不一定等同于你的终端 Python。启动 Jupyter 时,它读取的是 kernelspec 配置,每个 kernelspec 指向一个特定的解释器路径。你只是激活了 myenv,却没有把 myenv 注册给 Jupyter,它自然不会出现在内核列表中。
解决办法很简单,在目标环境中执行一次注册:
conda activate myenv python -m ipykernel install --user --name=myenv --display-name "Python (myenv)"执行完重启 JupyterLab,在 Launcher 里就能看到 “Python (myenv)” 的新内核选项。验证当前 notebook 到底在用哪个 Python,可以在单元格里运行:
import sys print(sys.executable)输出的路径会直接暴露内核指向。如果你运行出来的路径是/anaconda3/bin/python,而你明明打算用 myenv,那说明你打开 notebook 的那一刻选错了内核,或者压根没注册成功。这个检查手段是我排查一切“代码无法运行”类问题的第一板斧。
4.3 notebook 与 .py 脚本的双向转换
Jupyter 项目最终总得沉淀成可维护的脚本或报告。我最常用的方式是把 notebook 转成.py,在代码评审阶段发给同事看 diff,比直接发.ipynb干净得多:
jupyter nbconvert --to script my_notebook.ipynb反向操作也有用。别人给你一个.py脚本,你想进 notebook 里逐步运行、边跑边加注释,可以用:
jupyter nbconvert --to notebook --execute sample.py --output sample.ipynb这条命令会把脚本内容转成 notebook 并自动执行一遍,生成的.ipynb可以直接打开查看每个步骤的输出。我自己在整理教程或者把旧的实验脚本转成可复现文档时经常这样操作,比手动复制粘贴单元格省下几十倍时间。
5. 高频错误排查速查:从“打不开”到“代码跑不了”的完整处置
5.1 典型症状、原因和解决思路对照表
我平时最常被问到的问题集中在几个症状里。把这些现象汇总成一张速查表,遇到问题可以直接对应查看:
| 症状 | 可能原因 | 优先尝试 |
|---|---|---|
| 启动 JupyterLab 后页面空白 | 扩展冲突或缓存损坏 | 清浏览器缓存,删除~/.jupyter/lab/workspaces后重启 |
双击.ipynb发现只是文件,没有进入交互页 | 没有启动 Jupyter 服务 | 先在终端运行jupyter lab,再从网页里打开文件 |
| 打开后提示 Kernel error 或 Dead kernel | 内核指向的 Python 环境异常 | 在单元格打印sys.executable确认,重启 kernel |
| 按 Shift+Enter 后代码一直不执行 | 内核未连接或卡死 | 点 Kernel → Restart,看终端输出日志 |
| 端口被占用,提示 address already in use | 上一次 Jupyter 进程未退出 | 找到并结束占用 8888 的进程,或jupyter lab --port=8899 |
| 安装目录插件后不生效 | Notebook/Lab 扩展体系混用 | 确认你打开的是经典 Notebook,并重启服务 |
| 页面能开但 import 不到刚装的包 | 内核不是当前 conda 环境 | 注册目标环境为内核,再开启新 notebook |
其中“按 Shift+Enter 代码不跑”应该是最让人崩溃的。我的排查路线不分先后,先看两个地方:右上角内核图标是空心还是实心,终端里有没有 kernel 启动日志。如果内核图标变成空心或显示 “No Kernel”,最快的方式是选择 Kernel → Restart;如果重启后立刻又死掉,多半是内核解释器本身有动态库加载问题,这时候去终端手动敲python -c "import flask"之类的包看看能不能导入,能帮助定位是不是 Python 环境坏了。
5.2 侧边栏目录不显示的特殊情况
刚才说过,JupyterLab 的 TOC 是内置功能,但偶尔也有点不出来的时候。一种常见场景是:你只打开了一个空白 Launcher 页面,没打开任何 notebook,所以 TOC 面板显示“无匹配的标题”。这既不是 bug,也不是没装好,只要新建 notebook 并敲入一两个 Markdown 标题,右侧大纲会立即出现。
另一种情况是用老版 Notebook 时按教程装了 nbextensions,但 TOC 按钮不见。我遇到过一次 Windows 下jupyter nbextension enable toc2/main显示成功,浏览器依然没变化,最后发现是浏览器缓存了旧页面。解决方法是重启 Jupyter,用无痕窗口重新访问一次,或者在终端执行:
jupyter nbextension list查看toc2是否在 enabled 列表里。只要列表里存在,基本就是前端缓存或刷新时机的问题,不是安装失败。
5.3 我对环境的最终配置习惯和保持稳定的一点经验
上面这些方法,很多都是我栽过跟头之后才总结出来的。比如有一阵子,我的 base 环境因为实验装了很多包,conda update --all之后某个依赖升级,直接把 notebook 的内核搞崩了,连着两周反复出现 Kernel error。后来我把实验迁移到独立环境,base 几乎不动,这个问题基本绝迹。
给新手朋友一个保守策略:如果你没有把握,不要在已经跑通 Jupyter 的环境里频繁执行conda update --all。需要新包时,优先用 conda 安装;必须用 pip 时,先确认当前激活的是哪个环境。我见过太多案例是培训现场 pip 装包,看起来装成功了,重启后全消失,最后发现是因为 pip 和 conda 指向了不同 Python。
确保 notebook 稳定运行的最小验证动作是三步:用sys.executable确认内核路径,在单元格里确认版本号,然后上传一份真实数据跑通入口函数。只要这两点清晰,绝大部分“明明什么也没改、突然就坏了”的问题都能在五分钟内定位。
我做过的另一个小习惯是给每个 notebook 文件在开头建一个“环境信息”单元格,写清楚用它属于哪个 conda 环境、依赖了哪几个关键包版本。这个习惯看起来不痛不痒,但当 notebook 写完后隔两个星期甚至三个月需要重跑时,价值会突然放大——你不需要再靠猜去复现当初的运行环境了。
实际上,这篇文章里所有弯路汇总起来,核心也就一件小事:让 Jupyter 跑在你清楚知道的那个 Python 上,用你清楚掌握内化过的交互方式去操作它。目录、扩展、快捷键、内核排查都是为这个目标服务的。如果你刚起步,先顺手打开 JupyterLab 点一下左侧的 TOC 图标确认目录能用,再新建一个环境跑一遍ipykernel install,之后踩坑的概率不会太高。