很多人在用 Jupyter Notebook 和 JupyterLab 时,其实只把它当成了一个带执行的草稿纸:写一段代码,按下 Shift+Enter,看到结果,完事。这个用法不能说错,但远远低估了 Jupyter 家族能解决的问题。作为一个日常拿它写分析、做演示、甚至搭内部小工具的人,我今天想把这些年踩过的坑和沉淀下来的技巧一次性梳理出来,从安装环境、路径权限,到长文档导航、魔法命令和调试器,再到导出和分享的边界问题,都尽量讲透。
如果你是刚入门的小白,这篇文章能帮你少走一半冤枉路;如果你已经用了很久但总觉得卡手,里面不少细节大概率是你没注意过的。核心关键词只有一个——Jupyter Notebook/Lab 使用技巧,我把重心放在“实际按下键盘之后会发生什么”上,尽量不谈空泛的概念。
1. 别急着装完就跑:安装、启动与路径选择里的坑
1.1 用对安装方式,才不糟蹋你的 Python 环境
第一次按网上的教程装 Jupyter 时,最常见的操作就是打开终端,输入pip install jupyter,然后等它慢慢装。如果用的是系统自带的 Python,尤其是 macOS/Linux 上,这一步很容易埋雷:系统环境里已经有一堆依赖,pip装的时候又不敢乱动权限,最后要么装上去了启动报错,要么把系统的 Python 搞乱。
我的建议是,优先用 Anaconda 或者 Miniconda 建一个独立环境再装。比如:
conda create -n jupyter_env python=3.11 conda activate jupyter_env pip install jupyterlab notebook这样做的核心原因不是洁癖,而是 Jupyter 的内核机制。Jupyter 本身只是一个 Web 前端,真正跑代码的是一个“内核”,要是一个笔记本文件记录的内核和你当前激活的环境对不上,启动时最大的表现就是报错、无响应、或者找不到包。独立环境能让你随时重建、随时扔掉,不会影响你干其他活。
当然,如果你对 conda 无感,用python -m venv也一样:
python -m venv jupyter_venv source jupyter_venv/bin/activate # Windows 下是 Scripts\activate pip install notebook jupyterlab装完之后,直接在终端敲jupyter lab或jupyter notebook,浏览器会自动弹出。要是没弹出,终端里会打印一个带 token 的 URL,手动复制到浏览器即可,这就涉及下面要说到的登录入口问题。
1.2 启动失败和 PermissionError 究竟卡在哪
热词里有个很典型的报错:permissionerror:[errno 13] permission denied。我在各个群里见过不下三四十次,大部分场景都是在 Linux 服务器或者 macOS 上敲了jupyter notebook,结果提示这个错误,一脸懵。
这个错误的本意是“当前用户没有权限访问某个文件或目录”。在 Jupyter 语境下,最常涉及两处:
- 一处是配置文件目录,通常叫
.jupyter,在用户主目录下。Jupyter 启动时会往里面读写history.sqlite、runtime等文件。如果这个目录的属主不是你,或者之前用sudo启动过导致文件属主变成了root,后面再用普通用户启动就会撞上 PermissionError。 - 另一处是运行时创建的
runtime目录,默认在~/.local/share/jupyter/runtime或者/tmp/jupyter-runtime附近。某些服务器会把/tmp权限锁得很紧,或者挂载的/home分区有noexec/ 限制写入的选项,也会导致类似报错。
排查思路比直接给命令更重要。先找到 Jupyter 认为自己的工作目录在哪,运行:
jupyter --config-dir jupyter --runtime-dir然后对照着看:
ls -ld ~/.jupyter ls -ld $(jupyter --runtime-dir)如果是属主问题,直接:
sudo chown -R $USER:$USER ~/.jupyter如果是/tmp被限制了,就显式指定一个可写的运行目录:
export JUPYTER_RUNTIME_DIR=$HOME/.jupyter/runtime jupyter notebook同样,配置目录也可以就近迁移:
export JUPYTER_CONFIG_DIR=$HOME/.config/jupyter这套方法在服务器上尤其好用,毕竟不是所有机器都让你随便sudo。
1.3 默认保存路径和下载文件最终去哪了
另一个高频热搜是“jupyter notebook 默认保存路径”和“使用 jupyter 下载的文件默认安置在哪”。先搞清楚一个概念:在浏览器里点Download下载的.ipynb文件,走的是浏览器自身的下载逻辑,存到哪里由浏览器的设置决定,跟 Jupyter 无关。所以有人发现默认下载到了~/Downloads,那不是 Jupyter 管的。
但是,如果你在 notebook 里用 Python 代码写文件,比如:
with open("out.txt", "w", encoding="utf-8") as f: f.write("hello")这个文件会出现在“当前工作目录”下,也就是 notebook 文件所在的目录。因为 Jupyter 启动后,会把 notebook 所在目录作为当前目录,而open()的相对路径就是相对于这个目录的。
那默认保存路径怎么改?最干净的做法是在启动前就指定:
jupyter notebook --notebook-dir=/path/to/your/projects可以把--notebook-dir写进启动目录的快捷方式或别名里:
alias jn='jupyter notebook --notebook-dir=$HOME/projects'更一劳永逸的是修改配置文件:
jupyter notebook --generate-config打开生成的~/.jupyter/jupyter_notebook_config.py,找到这一段:
# c.ServerApp.root_dir = ''改成你的目标目录:
c.ServerApp.root_dir = '/home/me/projects'注意老版本里变量名可能是c.NotebookApp.notebook_dir,新版本已经迁移到ServerApp。版本差异是这里最搞人的点,遇到修改后不生效,先查一下你现在装的版本和对应的配置项名。
2. 在 Notebook 和 Lab 之间怎么选:不是升级换代那么简单
2.1 Notebook 的老派优雅与掣肘
Jupyter Notebook(也就是经典版)的界面是“一个页面里上下排列多个单元格”,虽然简单,但用了很多年后你会很明显地感受到它的限制:整个页面只有一个视图,没法同时看两个 notebook;每个文件单独占一个标签页,一旦开得多标签页密密麻麻;拖拽单元格换位置在单个文档里还可以,跨文档基本叫天天不应。
Notebook 的好处是它老,插件生态成熟。jupyter_contrib_nbextensions里一大堆经典扩展,比如代码折叠、目录、高亮选中的变量、自动格式化等等,都只在 Notebook 界面里比较顺手。如果你的主力机器还在跑老项目,那么新版 JupyterLab 往往无法直接复用这些 nbextensions。
2.2 Lab 补齐了什么,切换成本又有多高
JupyterLab 从设计之初就奔着“集成开发环境”去:文件列表在左边,主区域可以开多个窗口,支持拖拽布局,同一个浏览器窗口里同时排两个笔记本毫无压力。它还统一了很多之前要单独装的东西:多语言支持、命令面板(Ctrl+Shift+C 呼出),以及内置的表格查看器、CSV 预览等。
切换成本不是我一开始想得那么低。比如原先在 Notebook 里靠 nbextensions 开启的“目录”功能,在 Lab 里需要单独装@jupyterlab/toc(新版本甚至已经内置为右侧的 Outline 按钮)。还有一些自定义的 nbextensions 在 Lab 里完全不认,只能等作者出 Lab 插件版本,有的等了两三年都没消息。
所以我的个人建议是:如果你主要做数据分析,不太依赖老插件,直接上 Lab;如果维护的是三年前的 notebook 文件,而且经常要用老扩展,那就老老实实继续用 Notebook,或者两个都装,用哪种界面取决于当时的任务。
2.3 多标签、文件树、单元格拖拽实测
在 Lab 里拖拽单元格比想象中顺手。你先在左侧文件树点击文件名打开两个 notebook,然后在任意一个 notebook 里选中若干单元格(Shift+点击可以多选),直接拖到另一个 notebook 的目标位置。这个操作在 Notebook 里是做不到的,除非你 Ctrl+X、Ctrl+V 跨页面复制,再手动修一下代码里的状态。
文件树的支持也算一个决定性差异。在 Lab 里可以直接用右键菜单对文件做重命名、移动、新建文件夹,甚至打开一个临时终端,日常文件操作就省得跳出浏览器了。
快捷键体系两个界面基本一致。熟悉的核心组合:
Shift+Enter运行单元格并选中/跳到下一个Ctrl+Enter只运行当前单元格A在上方插入,B在下方插入D D连续按两次 D 删除单元格M进入 Markdown 状态,Y切回 Code 状态
我建议所有用户把这几个刻进肌肉记忆,比点鼠标效率高出一个量级。
3. 让目录树滚起来:长文档导航方案实测
3.1 装插件前先搞清你用的是哪个环境
“为 jupyter notebook 添加目录”是永久热搜。很多人遇到的尴尬是:明明在网上搜到pip install jupyter_contrib_nbextensions,装完也执行了 enable,但打开 Notebook 还是看不到目录。最常见原因是你装了插件 A,但 Jupyter 跑的是另一个环境的内核。
确认环境一致性最直接的方法:
which jupyter jupyter --version pip list | grep nbextensions确保这三者指向同一个 Python 环境。尤其是当你用 conda 激活了环境,但终端里的jupyter还是指向另一个路径时,插件安装到哪里去了根本说不清。这时候要么老老实实把jupyter重装到当前环境,要么用python -m jupyter notebook来强制走当前解释器。
3.2 两种主流目录插件配置与对比
在 Notebook 界面,主流方案是jupyter_contrib_nbextensions提供的Table of Contents (2),也叫toc2。安装过程:
pip install jupyter_contrib_nbextensions jupyter contrib nbextension install --user jupyter nbextension enable toc2/main装好后重启 Notebook,在“Nbextensions”配置页面里找到Table of Contents (2)勾选上。它会视情况在工具栏上多一个目录按钮,同时给 Markdown 标题生成锚点。
在 Lab 界面,新版默认自带 Outline,入口在右侧侧边栏的书签图标下方。如果你用的版本比较老,可以手动装:
pip install jupyterlab jupyter labextension install @jupyterlab/toc对比一下两者体验:
| 功能 | Notebook toc2 | Lab Outline |
|---|---|---|
| 实时定位 | 点击目录跳转 | 点击目录跳转 |
| 标题层级折叠 | 支持 | 支持 |
| 跟随滚动高亮 | 部分版本支持 | 支持较好 |
| 与代码块协作 | 仅 Markdown 标题 | 支持 Markdown 和代码单元格大纲 |
| 拖拽调整窗口 | 否 | 是 |
我自己的实测感受是:如果你只是需要一个简单的导航,Lab 自带 Outline 反而更干净;但老牌 toc2 可以和“折叠代码”等 nbextensions 协同,习惯后其实非常好用。
3.3 目录/折叠/大纲的一体化用法
光有目录还不够,实际阅读体验要配合代码折叠。在 Notebook 里,Codefolding是 nbextensions 里非常受欢迎的一个扩展,点击代码单元格左侧的小箭头,即可对函数体、类体或if块进行折叠。这个功能长 notebook 里特别有用,尤其当你只关心某个函数的位置时,折叠掉 80% 的代码,配合目录,整个文档的可视性会大幅提升。
另外一个小技巧:Markdown 标题顺序要及时更新。目录本身是读取 notebook 的元数据来生成的,如果你在单元格里删改了很多标题位置,最好先保存文件,再刷新一下目录,避免跳转目标错乱。在 toc2 里还有一个小齿轮,可以设置“标题编号”、“超链接跳转”等选项。我一般会开启“标题编号”,这样段落之间有层级感,导出成 PDF 或者 HTML 时也更专业。
在 Lab 里,Outline 支持搜索过滤,直接键入标题关键字可以快速定位到长文中某个章节。对于动不动几百个单元格的报告型 notebook,这个功能我几乎每天都会用到。
4. 魔法命令与交互模式:真正提升日常效率的十来个玩法
4.1 把时间测出来:%time 和 %%time 的差别
很多人不知道 Jupyter 里自带“魔法命令”,以百分号开头的指令。最开始值得记住的是%time和%%time。区别很直接:
%time只测量这一行的代码耗时%%time测量整个单元格的耗时
%time sum(range(10_000_000))%%time total = 0 for i in range(10_000_000): total += i使用场景是:当你在两个写法之间犹豫时,别靠猜,跑一遍%%time,比什么都有说服力。如果你想知道更精细的差异,还有%%timeit,它会自动把代码执行很多次然后取平均,适合对微性能敏感的项目。
4.2 自动重载模块、一键运行任意代码串
在 notebook 里开发自己的 Python 模块时,最痛苦的一件事是:改了.py文件,再重新导入,模块不会自动更新。解决方案不是importlib.reload一遍遍手工来,而是用内置的自动重载:
%load_ext autoreload %autoreload 2开了%autoreload 2之后,只要.py文件的代码发生变化,运行任意单元格前 Jupyter 会先自动重新加载这些模块。这几乎是本地开发一个包时的标配,省掉的“重启内核”时间不计其数。注意%autoreload 1只对%aimport指定的模块生效,一般不如 2 来得省心。
还有一个很有用的命令是%run,它可以运行一个 Python 脚本,并把脚本执行后的命名空间留在当前内核里:
%run myscript.py如果你有一个生成的.py文件,想看看里面的变量结果,又不想复制粘贴,这个命令非常合适。相反,%load可以把一个文件内容直接加载到当前单元格:
%load myscript.py这适合你想拆解别人的脚本,加载进来后手动调整再执行。
4.3 魔改输出显示:%config 与装饰器的结合
Jupyter 的显示系统远比 print 丰富。我最常用的是IPython.display:
from IPython.display import display, HTML, Markdown, Image, Video display(HTML("<table><tr><td>A</td><td>B</td></tr></table>")) display(Markdown("### 这是一个三级标题")) display(Image(filename="plot.png"))对于 DataFrame,Jupyter 会自动渲染成可滚动的表格,但如果你希望控制 pandas 显示的行数和列数,可以这样:
%config ZMQInteractiveShell.ast_node_interactivity = "all"这是一个被低估的配置,在普通 Python 里,一行表达式只有最后一个会被输出,而在 Jupyter 里加上这个配置后,单元格内所有能求值的表达式都会显示出来,不需要一个个写display。类似地,可以限制 DataFrame 显示:
import pandas as pd pd.set_option("display.max_rows", 100) pd.set_option("display.max_columns", 50)还可以通过@interact创建交互控件,这是 IPython widgets 的入口。简单示例:
from ipywidgets import interact @interact(x=(-10, 10, 1)) def f(x): return x ** 2运行后会生成一个滑动条,调节参数立即重新执行函数。这在做参数探索时很有用,比如调整绘图窗口大小、过滤阈值等,亲测能省大量“改参数重跑”的无聊循环。
5. 排错和调代码:那些让人抓狂的异常与诊断技巧
5.1 内核死了别慌,先查这四类问题
在 Jupyter 里你可能会遇到Kernel Restarting或者“The kernel appears to have died”这类提示。内核崩了,最直接的影响是之前定义的变量全部丢失,只能重跑一遍。按照经验,八成原因逃不开四种:
- 无限递归或死循环,占满了 CPU 和内存。
- 一次性分配超大数组,比如
numpy.arange(10**12),瞬间把内存打满。 - C 扩展段错误,例如某个库底层用了 ctypes 且指针写坏。
- 多线程/多进程管理不当,比如在 fork 之后调用了某些非线程安全的库。
应对手段是分级排查。先看任务管理器或top,确认是不是内存爆了。如果只是死循环,可以用菜单里的Interrupt Kernel(快捷键是I I)中断执行。如果已经完全卡死,那就只能重启内核,但注意重启前把还热乎的结果手动保存,因为输出缓冲区不一定来得及写盘。
5.2 调试器的三种打开姿势(pdb、%debug、ipdb)
代码报错后,最常见的动作是看 traceback,然后一头扎进代码里加 print。但 Jupyter 其实给了三套调试姿势,效率完全不同。
第一种,直接在异常之后执行%debug:
def buggy(): x = 1 return x / 0 buggy()报错后,在下一个单元格输入:
%debug会自动进入事后调试模式,停在出错的位置。这个时候你能做的是p x查看局部变量、u/d切换上下帧、n单步、c继续、q退出。
第二种是主动在函数里加断点,使用IPython.core.debugger的set_trace:
from IPython.core.debugger import set_trace def func(): a = 1 set_trace() # 运行到这里会进入交互调试界面 b = 2 return a + b第三种是ipdb,它是把 IPython 的能力和pdb结合起来的工具:
pip install ipdb然后在代码里:
import ipdb; ipdb.set_trace()在 Notebook 中的体验比纯pdb好,支持 Tab 补全和彩色输出。说实话,一旦习惯了用%debug事后复盘,就再也不想从头加 print 再去猜了。
5.3 日志、打印、变量探查三板斧
调试不仅靠断点,还靠对变量的快速探查。三个林林总总的函数,建议背下来:
type(obj)看类型vars(obj)看对象属性字典dir(obj)看对象可用的属性/方法列表
尤其是dir(pd.DataFrame)这样的用法,比翻文档还快。配合 help:
help(df.groupby)或者df.groupby?,这是 Jupyter 特有的“object? 显示文档、object?? 显示源码”的便捷查询。
关于日志,我长期吐槽在 notebook 里只靠print做记录效率太低。更推荐专门开一个单元配置 logging,输出到文件:
import logging logging.basicConfig( filename="./my_jupyter.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", ) logger = logging.getLogger(__name__) logger.info("这一步跑到了,参数: %s", param)这样对环境进行异常分析时,至少不会因为某个输出单元格被误删而痛失线索。
5.4 PermissionError 完整排查链路
既然前面提到了 PermissionError,这里专门放一条完整的排查链路,方便直接照做:
先在当前环境里确认是不是只有 Jupyter 报错,还是所有用户目录都写不进去。执行:
touch ~/.jupyter/write_test && rm ~/.jupyter/write_test如果这一步也报错,那就是主目录权限本身的问题,检查
~的属主和权限。如果写用户目录没问题,再查 runtime 目录:
jupyter --runtime-dir ls -ld $(jupyter --runtime-dir)runtime目录下的文件要求只能是当前用户可写,如果发现一些 root 属主的僵尸文件,清空后重新启动。如果是在服务器上被 SELinux 或 AppArmor 限制,那需要看具体的审计日志,
dmesg | tail -n 20里常有线索。优先考虑把 Jupyter 的数据目录放到用户自己的设定路径下,绕开限制目录。最后一个兜底方案:新建一个专用目录,并从零配置:
mkdir -p ~/jupyter_data cd ~/jupyter_data jupyter notebook --notebook-dir=$PWD如果这样还报错,那就说明问题不在 Jupyter 本身,需要去查终端里实际运行命令的用户身份是不是你想的那个人。有时候你用某云服务器的默认账号登录,但 Web 界面绑定的端口启动脚本是另一个服务账号,自然对某些路径没有写权限。
6. 分享与复用:导出、部署、插件生态里值得留意的细节
6.1 导出为 md/PDF/HTML 时的边界问题
在 LaTeX 和 pandoc 都安装好的环境下,Jupyter 支持直接把 notebook 导出成 PDF、HTML、Markdown 等格式。命令行对应是:
jupyter nbconvert --to markdown my_nb.ipynb jupyter nbconvert --to html my_nb.ipynb jupyter nbconvert --to pdf my_nb.ipynb但导出看着简单,坑不少。默认情况下,nbconvert 会保留所有的代码输出,包括那些非常长的 DataFrame 表格、动态图片、甚至报错信息。分享给别人的时候,这些输出往往是噪音。解决方案是先清理输出再导出:
jupyter nbconvert --clear-output --to markdown my_nb.ipynb如果你想把 notebook 当作一份报告发给团队参考,我经常用:
jupyter nbconvert --to html --template full --no-input my_nb.ipynb--no-input的意思是不渲染代码单元格,只保留输出结果和 Markdown,观感更像一份报表。
6.2 别人给的文件怎么快速复现(requirements、npm 依赖)
拿到别人的.ipynb,最怕的就是运行到一半报ModuleNotFoundError。要让 notebook 可复现,可不能只发.ipynb文件,至少得带上环境说明。我在每次分享 notebook 项目时,都会在同一目录下生成两份文件:
pip freeze > requirements.txt jupyter nbextension list > extensions.txtrequirements.txt记录 Python 包;extensions.txt记录 Jupyter 前端插件。不要嫌它们繁琐,这是能救命的“安全网”。如果有人明确都是 Lab 用户,还需要注意jupyter labextension list,因为前端扩展不在pip freeze范围内。
在复现别人 notebook 时,强烈建议开一个全新的干净环境,而不是往现有的原环境里硬塞包。冲突了很难排查,而且也能逼着对方把依赖说明白。常见顺序:
conda create -n reproduction python=3.11 conda activate reproduction pip install -r requirements.txt jupyter notebook my_nb.ipynb6.3 用 Jupyter 写小系统的扩展思路
最后一个环节,聊聊怎么在 notebook 基础上再往前走一步。我发现不少人辛辛苦苦在 notebook 里调通了数据处理流程,但每当要把它变成可复用工具时就犯难。这里有一个比较顺手的做法:把 notebook 里稳定不变的逻辑抽成一个.py模块,然后在 notebook 里用%autoreload调用这个模块。这样既能享受 notebook 的交互性和可视化,又保住了模块化工程的可维护性。
如果你想直接给非技术同事一个交互界面,可以试一下 Voila (我提这个不是广告,是它确实解决了从 notebook 到 Web 面板的最后一公里)。它能把 notebook 里的可视化输出、交互控件包装成一个临时网页,不需要你额外写 Flask 或者 Django。安装:
pip install voila然后启动:
voila my_notebook.ipynb它会自动识别代码中表征交互的 widgets 和 matplotlib 图表,生成一个可点击、可滑动的浏览器应用。适合小范围内的数据看板、报表工具。
还有一类场景是定时执行 notebook。虽然可以用papermill这样的工具批量跑,但我的经验是:当任务真的需要定时调度时,Jupyter 并不是最好的载体,把逻辑抽成普通脚本 + cron 会可靠得多。Notebook 的优势在于“人机交互、实时反馈”,真到了无人值守的生产环境,脚本化和参数化才是正路。
以上就是我这些年围绕 Jupyter Notebook/Lab 沉淀下来的实战要点。写到这里,最想提醒你的一点是:工具技巧说到底是为了少打断心流。安装选型一步到位、目录和调试两个痛点提前布置好,后面每次打开 Jupyter 就能把注意力全放在问题上,而不是和环境较劲。不知道你有没有遇到过更好用的小技巧?如果有,欢迎在评论区给我留言,我也想去试试。