☰
Jupyter Notebook网页端执行模型与内核管理实战
2026/10/23 1:11:34 网站建设 项目流程

"Jupyter Notebook 网页使用"这几个字,看起来是把一个本地工具搬到浏览器里点点鼠标的事。真正用上一两个月就会发现,麻烦全在细节里:内核莫名其妙卡死、变量和输出对不上号、换个目录启动就找不到数据文件、同一份 notebook 发给同事,人家一跑全是报错。这些问题跟写代码的水平关系不大,跟"你有没有把网页这套执行模型吃透"关系很大。

我把 notebook 当成日常主力工作台用了挺长时间,从最早在本地弹窗里写脚本,到后来把它挂在实验室的机器上,用浏览器远程连着跑几小时的训练任务,中间踩过的坑基本覆盖了新手会遇到的绝大多数情况。这篇文章不打算复述官方文档里那些"点这里、点那里"的说明,我更想讲清楚三件事:网页端背后到底是谁在干活、每一个操作选择背后的取舍是什么、出问题时该按什么顺序排查。

内容面向的读者范围我尽量放宽:完全没用过、只知道它能写 Python 的人,看完能顺利跑起来并且避开最常见的坑;已经在用、但一直把它当"能分块运行的记事本"的人,看完应该能把这套工具的效率再压榨一层。文中涉及的启动方式、配置项、命令都经过实际验证,版本上以较新的 Notebook 7 为主,涉及旧版本差异的地方我会单独标出来。

1. 网页端到底在跑什么:先把执行模型说透

1.1 浏览器只是前台,真正干活的是内核

很多人第一次打开 notebook,会下意识觉得"代码是在网页里跑的"。这个直觉是错的,而且是后面一堆困惑的根源。浏览器在这里承担的角色,更像一个终端加一个富文本编辑器:它把你在单元格里敲的文字打包成一段消息发出去,对面有个叫**内核(kernel)**的独立进程接收、执行、再把结果回传,浏览器负责把结果渲染成表格、图像或者一段报错。

这个分离结构解释了很多现象。比如你把浏览器标签页关掉,内核并不会立刻死,它还在后台算你那段跑了十分钟的循环;比如你刷新页面,变量还在,因为内核没重启;再比如你换了台电脑打开同一个地址,看到的还是同一份运行状态。我之前就干过一件蠢事:以为关掉标签页就等于停止任务,结果第二天早上发现机器负载还是满的,因为那个算了一整夜的内核一直在跑。

理解这一层之后,"重启内核"这个动作的含义也就清楚了:它不是刷新页面,而是把那个干活的进程杀掉再起一个新的,所有变量、所有导入的模块、所有打开的文件句柄全部清零。这也是为什么我养成一个习惯:交付 notebook 之前,一定执行一次"重启并全部运行",确认从头到尾能跑通。

提示:浏览器只是显示层,内核才是执行层。任何"代码好像不对"的怀疑,第一步都应该是确认当前连的是哪个内核、这个内核是从什么时候开始跑的。

1.2 为什么这种形态值得保留,而不是直接用普通脚本

说实话,凡是需要长期维护、需要被别人 import 的逻辑,我都会老老实实写成.py文件。notebook 的价值不在"写代码",而在探索。数据长什么样、某个字段有没有脏值、这个模型在这批样本上表现如何,这类问题没法提前设计好流程,你得边看边改。分块执行让"改一行、看一次结果"的成本降到几乎为零,这个反馈速度是脚本加打印调试换不来的。

第二个价值是叙事。单元格可以随时切成 Markdown,把"我为什么这么处理""这一步的假设是什么"写在代码正上方。半年后回看自己的分析过程,那些注释救过我很多次。相比一堆散落的脚本文件,一份结构清晰的 notebook 本身就是一份可执行的技术笔记。

第三个价值是结果的富媒体呈现。表格、折线图、图片、公式、交互控件,都能直接嵌在代码下方,不需要另开窗口。给别人演示的时候,滚动一遍就能讲完全过程,这个体验确实比开终端加看图窗口要顺。

代价也很明确:执行顺序不受约束,状态会残留,diff 难看得要命。这三个问题在后面的章节我会逐个说应对办法。

1.3 三类用法,三种配置思路

同样是"网页使用",用法差别很大,配置策略也完全不同。我大致分成三类:

使用场景典型配置关键诉求
本机一次性探索默认启动,本地回环地址启动快,不折腾
固定项目长期使用独立虚拟环境 + 自定义工作目录环境隔离,路径稳定
挂在服务器上远程连着用指定绑定地址 + 密码鉴权 + 进程守护稳定、安全、断电不掉

第一种最简单,pip install完直接就开。第二种需要在项目目录里建虚拟环境,然后把内核注册进去,否则你今天装的包明天换个项目就冲突了。第三种要考虑的事情最多,进程得能长期活着,鉴权得做好,端口得固定,不然每次连都要去翻日志找地址。下面两节会把后两种的具体做法拆开讲。

2. 启动与环境:第一次打开之前该做的决定

2.1 三条安装路径,怎么选不后悔

装 notebook 大致有三条路,我不推荐"哪个火用哪个",而是看你后续要不要管理多个环境。

第一条是直接用 pip 装进当前 Python 环境。命令很短:pip install notebook。优点是干净、没有额外东西,缺点是如果你机器上有多个项目,早晚会遇到"装了个包把另一个项目的依赖顶掉了"的惨案。我个人只在容器或者一次性环境里这么干。

第二条是用 conda 系的包管理器建独立环境。这是我在自己机器上的默认做法:每个项目一个环境,notebook 装在环境里,互相不干扰。大致流程是先建环境再装包,然后把环境注册成内核。这个"注册内核"的步骤是新手最容易漏的一步,漏了就会出现"我在终端里明明装了这个包,为什么 notebook 里 import 失败"这种经典问题。

第三条是用集成发行版,装完自带一堆常用库。好处是开箱即用,坏处是自带的东西太多、版本你未必想要,而且一旦和 pip 混用,依赖关系容易乱成一锅粥。我给完全零基础的人推荐这条,给准备长期用的人推荐第二条。

# 独立环境 + 内核注册,我常用的组合 conda create -n myproj python=3.11 conda activate myproj pip install notebook pandas matplotlib python -m ipykernel install --user --name myproj --display-name "Python (myproj)"

最后那行命令的作用是把当前环境登记到 Jupyter 的内核列表里,--display-name是你在网页里看到的菜单项名字。写清楚一点很重要,我见过有人注册了七八个环境,名字全是Python 3,选内核全靠猜。

2.2 第一次启动:那一串 token 是怎么来的

在终端里敲jupyter notebook,回车之后你会看到几行日志,最后一行通常长这样:http://127.0.0.1:8888/?token=一长串字符。浏览器一般会自动弹出来,如果没弹,把这一整行粘进去就行,注意 token 要连参数一起带,只敲地址会要求你手动粘贴。

这个 token 是一次性访问凭证,机制很简单:服务在本地监听一个端口,任何请求必须带对 token 才放行,防止你机器上的其他程序随便读你的文件。它每次启动都会变,所以别存书签,存了下次也是失效。

如果嫌每次复制 token 麻烦,可以设一个固定密码:jupyter notebook password,按提示输两遍,会在你的用户配置目录下生成一个存着哈希值的文件。之后启动就直接输密码,网页上会记住一段时间。

注意:密码文件是哈希存储的,忘了没法找回,只能重新执行一次上面的命令覆盖。设密码这一点在把服务挂到局域网里时几乎是必须的,别图省事留 token。

启动日志里还有几个信息值得看一眼:Serving notebooks from local directory表示当前的工作根目录,The Jupyter Notebook is running at后面是访问地址,0 active kernels表示内核还没起。工作根目录这一条尤其重要,因为它决定了你在网页文件列表里能看到什么、open('data.csv')这种相对路径从哪里开始找。

2.3 配置文件:把每次都要敲的参数固化下来

每次都带一堆命令行参数不是长久之计。notebook 支持把常用配置写进文件,生成方式是jupyter notebook --generate-config,它会告诉你配置文件落在哪里,通常在用户主目录下的.jupyter文件夹里。

这里有一个版本差异必须提醒:较新的 Notebook 7 底层换成了新的服务框架,配置项的类名前缀从NotebookApp变成了ServerApp。如果你照着几年前的教程改了c.NotebookApp.notebook_dir却没生效,八成就是这个原因。判断方法很简单,打开配置文件翻一下,看里面注释掉的示例用的是哪个前缀,跟着它写就行。

我常改的几项:

# 默认工作目录,指向我的项目根目录,省得每次 cd c.ServerApp.notebook_dir = '/home/me/work' # 固定端口,避免端口被占时自动跳到别的端口导致书签失效 c.ServerApp.port = 8888 c.ServerApp.port_retries = 0 # 不自动弹浏览器,我在远程机器上根本不需要 c.ServerApp.open_browser = False # 允许访问的地址,按需设置 c.ServerApp.ip = '127.0.0.1'

port_retries = 0这一项是我吃了亏才加的。默认情况下端口被占会自动往后找,结果是你以为还在 8888,实际跑在 8889 上,浏览器打开一片空白,排查半天。设成 0 之后端口冲突会直接报错,反而好定位。

2.4 挂到局域网或服务器上:鉴权是底线

把服务跑在自己电脑上只给自己看,安全性基本不用操心。但只要监听地址从本地回环改成对外的网卡地址,性质就完全变了——这个服务的权限边界是"能访问的人可以读写你账户下有权限的所有文件",没有任何沙箱隔离。

所以我只有两个建议:第一,一定要设密码;第二,能用端口转发就不要直接暴露端口。如果是在同一局域网内用,把监听地址设成局域网地址、配好密码,通常够用。如果是要跨网络连一台机器,我一般的做法是只让它监听本地回环,然后从自己的终端做一个端口转发,把远端的端口映射到本地,浏览器里访问本地地址,效果一样,暴露面小得多。

进程要长期活着的话,别用裸终端跑,关掉会话就没了。用系统自带的服务管理方式或者终端复用工具挂住,具体选哪个看你那台机器的习惯,原则只有一条:能开机自启、能看日志、挂了能自动拉起。

3. 界面拆解与单元格操作:用得顺不顺全在这

3.1 主界面五个区域,各管什么

打开之后第一眼是文件列表,也就是所谓的仪表盘。这个页面很多人直接跳过,其实几个按钮挺有用:右上角可以新建 notebook、新建终端、新建文本文件;文件列表里勾选文件之后会出现移动、重命名、删除的操作栏;顶部的 Running 标签能列出当前所有活着的内核和终端,跑了一堆忘了关的时候从这里统一清理最方便。

点进一个 notebook 之后,界面大致分五块:最上面是标题和文件操作,往下是菜单栏,再往下是工具栏(保存、加行、剪切、运行、单元格类型、内核状态),中间是单元格区域,底部是内核状态指示。工具栏那排小图标我建议至少记住三个:保存、运行、单元格类型下拉框,剩下的大多用快捷键更快。

右上角那个圆点值得单独说。空心表示内核空闲,实心表示忙。一个跑了两分钟还没变空心的单元格,说明确实在算,别急着点重启;但如果它一直是实心且日志里毫无动静,那可能是真卡了,这时候重启内核比干等着强。

3.2 两种模式、三种运行方式

单元格有两种状态:编辑模式和命令模式。编辑模式就是光标在里面、能打字,边框一般是绿色;命令模式是选中了这个单元格但不能输入,边框蓝色。这两个模式的区分是 Jupyter 所有快捷键体系的基础,不搞清楚这一点,按什么键都像在抽奖。

切换方式:命令模式按Enter进编辑,编辑模式按Esc回命令。我早期最常犯的错就是以为自己在命令模式,按了dd想删单元格,结果只是在代码里打出了两个 d。

运行单元格有三种方式,区别在于运行之后光标去哪:

  • Ctrl + Enter:运行,停在原地。适合反复调试同一个单元格。
  • Shift + Enter:运行,跳到下一格。适合从头往下推流程。
  • Alt + Enter:运行,并在下方插入一个新格。适合从零开始写。

这三种我一天要用几百次,尤其是Shift + Enter和Alt + Enter的组合,基本能覆盖写代码的全部节奏。

3.3 Markdown 单元格:别把 notebook 写成流水账

切单元格类型的方式是命令模式下按Y变代码、按M变 Markdown,或者用工具栏下拉框。这一步看起来无关紧要,但它决定了你半年后还能不能看懂这份文件。

我给自己定的规矩是:每一个逻辑段落前面必须有一个 Markdown 单元格写清楚"这段要解决什么问题、输入是什么、为什么选这个方法"。这不是为了好看,是因为 notebook 的执行顺序可以被任意打乱,如果没有文字锚点,你回看的时候根本分不清哪段代码对应哪次分析。

Markdown 单元格支持标准语法加公式,标题、列表、表格、加粗、代码块都能用,公式用美元符号包裹。写公式这一点在写分析报告时特别省事,不用另外开文档工具排版。另外Shift + Enter渲染之后双击可以回到编辑状态,这个操作很多人不知道,总以为渲染完就改不了了。

3.4 执行顺序陷阱:Out 编号会骗人

这是 notebook 最大的坑,没有之一。假设你在第 1 格定义了x = 10,第 2 格打印x,然后你回去把第 1 格改成x = 20但没有重新运行,再运行第 2 格,输出还是 10。等你在第 2 格上方插入新代码时,编号可能变成In [7]和In [3],顺序彻底乱了。

更隐蔽的情况是删除。你删掉了某一格,但它定义过的变量还在内核里活着,后面的代码照样能跑。等你把这份文件发给别人,人家从头运行,第一个格子就报NameError。

我见过最惨的一次,是某个分析脚本在本地跑出来的结论和同事复现的不一致,查了大半天,最后发现是因为我本地内核里残留了一个手工改过的中间变量,而文件里根本没这行。从那以后我给自己定了一条硬规矩:任何要交出去、要发出去的 notebook,发之前必须"重启内核并全部运行"一遍。菜单里这个功能是现成的,一次点击,几十秒的事,能省掉后面几小时的扯皮。

判断顺序是否正常,有个简单的自查方法:从上往下看In [n]的编号,如果不是从 1 开始依次递增,就说明中途有乱序执行。全部重跑一遍,编号就整齐了。

4. 快捷键与魔法命令:把重复动作压到一次按键

4.1 我每天真正在用的那几个键

快捷键列表很长,但真正常用的就那么十来个。我按使用频率排了一下:

快捷键作用使用场景
Shift + Enter运行并下移顺序推进流程
Ctrl + Enter运行停在原地反复调同一段
Esc/Enter切命令/编辑模式一切操作的前提
A/B上方/下方插入单元格补代码
DD删除当前单元格清理废格
Z撤销删除手滑救星
Y/M转代码 / 转 Markdown排版
Shift + M合并选中单元格收拢碎片
Ctrl + Shift + -在光标处拆分单元格把一大坨拆开
Shift + Tab查看函数签名忘了参数怎么传

Shift + Tab这一条我要单独夸一下。光标停在函数名上按一下,会浮出参数说明;按两下展开更详细的文档;按三下直接变成完整帮助。这比开浏览器查文档快十倍,而且不用离开当前上下文。刚学某个库的时候我用这个键的频率比运行键还高。

Ctrl + Shift + -拆分也很实用。写着写着发现一个单元格太长,把光标放到要断开的位置按一下,干净利落地分成两格,比复制粘贴省事。

4.2 行魔法与单元魔法:省掉一堆样板代码

以单个百分号开头的是行魔法,作用于一行;两个百分号开头的是单元魔法,作用于整个单元格。下面几个是我几乎每个 notebook 都会用到的。

# 计时:单次执行耗时 %time result = heavy_function(data) # 多次执行取统计,跑基准测试用这个 %timeit -n 10 -r 3 sorted(random_list) # 整个单元格计时 %%time model.fit(X_train, y_train) # 自动重载外部模块,改了 .py 文件不用重启内核 %load_ext autoreload %autoreload 2 # 直接把代码存成文件 %%writefile helper.py def normalize(x): return (x - x.mean()) / x.std()

%autoreload 2这个组合我强烈建议写进每个 notebook 的第二个单元格。默认情况下,你在别的文件里改了函数,notebook 里 import 进来的还是旧版本,必须重启内核才生效——重启意味着所有变量丢失,之前算的十分钟白费。开了自动重载之后,改完文件下一次调用就是新版本,开发体验完全不一样。

%timeit和%time的区别要说清楚:前者会自动多次运行取平均值,适合测很短的操作,几微秒到几毫秒级别;后者只跑一次,适合测耗时较长的函数。用反了要么浪费时间,要么结果噪声大到没法看。

4.3 跟系统和文件打交道:不用切窗口

在单元格里用感叹号开头可以直接执行系统命令,输出会原样显示在下面。这个功能方便到有点危险,因为它没有任何保护。

!ls -lh data/ !wc -l train.csv files = !ls *.csv print(files)

最后那种写法是把命令结果接到 Python 变量里,返回的是一个字符串列表。做批量文件处理时挺顺手,但我一般只用来做只读操作,涉及删除、移动这类有副作用的命令,我还是老老实实开终端窗口,至少那里有完整的提示和确认。

装包也可以用魔法命令,%pip install和%conda install。相比直接!pip install,它的好处是把包装到当前内核所在的那个环境里,而不是你启动 notebook 的那个 shell 环境里。这两个环境经常不是同一个,尤其在你注册了多个内核之后,搞混了就变成"我明明装了但 import 不到"的经典问题。

心得:判断包到底装没装,不要看终端,直接在下一个单元格里跑import 包名; print(包名.__file__),打印出来的路径能一眼看出它属于哪个环境。

4.4 观测与调试的几个小工具

%who列出当前所有变量名,%whos多给类型和值,%reset -f清空全部变量。写长流程时我习惯在关键节点跑一次%whos,看看内存里堆了多少东西,有没有该删没删的大对象。

调试方面,%pdb打开之后,任何报错都会自动进入交互式调试器,可以打印变量、单步执行。不过说实话,在 notebook 里做复杂调试体验一般,真要跟一个 bug 死磕,我还是会把它拎到正经的调试器里去。notebook 的强项是探索,不是debug。

还有一个用得少的%prun,做性能分析,能看到每个函数调用的耗时占比。优化前先跑一次这个,比凭感觉猜哪里慢要靠谱得多。

5. 数据展示、绘图与大输出处理

5.1 绘图:两种模式别搞混

画图这件事上,配置不同会导致行为完全不一样,这一条踩坑的人特别多。

# 静态内联图,图固定在输出区,最稳定 %matplotlib inline # 交互模式,图可以缩放平移,但会占一个独立区域且容易崩 %matplotlib notebook # 新一代交互后端,体验更好,需要单独安装 %matplotlib widget

inline是默认也是我最常用的,输出是静态图片,随 notebook 一起保存,导出 HTML 的时候也在。缺点是图多的时候文件会变得非常大,而且不能缩放。

notebook模式能让图支持缩放和动态更新,适合看三维散点或者需要连续更新的场景。但它的问题是不稳定,尤其是图多、数据大的时候,经常出现图不刷新或者整个前端变卡。

分辨率也有讲究。默认输出的图在高分屏上看着发虚,可以设一下:

%config InlineBackend.figure_format = 'retina'

输出会清晰很多,代价是图片体积翻几倍。给文档配图的时候值得,日常探索我就用默认的。

5.2 表格显示:默认只显示一截是故意的

用表格库展示数据时,默认只显示前几行和后几行,中间用省略号代替。这不是 bug,是防止一个几百万行的表直接把浏览器渲染卡死。想看更多就改配置:

import pandas as pd pd.set_option('display.max_rows', 200) pd.set_option('display.max_columns', 50) pd.set_option('display.width', 200) pd.set_option('display.precision', 4) pd.set_option('display.max_colwidth', 80)

这几个参数我一般只在需要仔细看数据的时候临时开,看完就改回去。原因很简单:设得太大,一个不小心display(df)就能让页面卡十几秒,尤其是带长文本列的时候。

还有个技巧是善用display()函数。一个单元格里只写变量名,只会显示最后一个值;写成display(a); display(b)就能依次显示多个,做数据对比时很方便。

5.3 输出太大:前端卡死的头号原因

notebook 文件本质上是个文本文件,所有输出都序列化存进去了。这意味着如果你在循环里打印了十万行日志,或者把一个几百兆的数组直接显示出来,文件会瞬间膨胀到几十兆,浏览器打开就卡,保存要等半天。

几个应对手段,按我使用的频率排:

第一,循环里别裸打印。要么加个计数器只打关键节点,要么用%%capture把输出截住不显示:

%%capture cap for i in range(100000): result = process(i) # 需要的时候再看 print(len(cap.stdout))

第二,大数组别直接print,看形状、看统计量、看前几行就够了。真想全看,写到文件里再用别的方式打开。

第三,已经产生的垃圾输出要清掉。菜单里有清空当前单元格输出和清空全部输出的选项。命令行也能做,导出之前跑一遍,文件能小一个数量级:

jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace notebook.ipynb

注意:清空输出不可逆,清之前先保存一份或者确认结果已经不需要了。我有过一次在演示前手滑清空全部输出,只能从头重跑二十分钟。

5.4 富媒体输出:图片、进度条、超链接

除了表格和图,notebook 还能直接嵌其他东西。展示图片可以用专门的显示模块,写报告时把关键截图贴进去,比在外面维护一堆图片文件再引用要省心。

跑长循环的时候,加个进度条能显著减少焦虑。装个轻量的进度条库,把循环对象包一层就行,它会实时在输出区刷新,跑完自动变成一行完成信息。我原来都是靠打印计数器估进度,用了进度条之后再也回不去了。

还有一个小细节:单元格里的最后一行如果是个对象,会调用它的展示方法。所以自定义类的时候,实现一下对应的展示方法,就能让它以 HTML 表格或者带格式的形式出现,调试自定义数据结构时特别直观。

6. 内核管理:重启、清空、多环境

6.1 什么时候该重启,什么时候不该

内核菜单里那几个选项,含义差别很大,很多人一直混着用:

操作做了什么什么时候用
Restart杀进程重开,变量全丢状态脏了、卡死了
Restart & Clear Output重开并清空所有输出交付前整理
Restart & Run All重开并从头跑一遍交付前必做
Interrupt中断当前执行死循环、跑太久
Clear Output只清输出减小文件体积

Interrupt是被低估的一个。写了个死循环或者发现参数写错想中止,按中断比等它跑完好得多。中断之后内核还在,变量也还在,只是当前那个单元格停了。但如果中断之后状态变得奇怪,比如某些操作只做了一半,那就别再接着跑了,直接重启更干净。

Restart & Run All我前面提过,这里再强调一次它的意义:它是唯一能验证"这份文件从头到尾自洽"的方式。任何交付、任何发布、任何给别人看的 notebook,这一步不能省。

6.2 多环境:内核和终端不是一个东西

这一节可能是新手最容易困住的地方。你在终端里conda activate myenv然后启动 notebook,notebook 用的是myenv;但如果你之后在另一个终端里激活了other_env再启动一个 notebook,两个浏览器页面连的是两个不同的内核,装包互不影响。

更常见的情况是:notebook 从桌面图标或者菜单直接启动,它继承的是系统默认环境,不是你以为的那个。于是就有了"我 pip install 明明成功了,import 还是报错"。

排查顺序应该是这样的:先看网页右上角的内核名字,再看import sys; print(sys.executable)打印的解释器路径,最后确认这个路径和你装包的那个环境是不是同一个。三步下来,问题基本都能定位。

要新增内核,用第 2 节里那条注册命令。要删除内核,用jupyter kernelspec list看列表,然后jupyter kernelspec remove 名字。我每隔一段时间会清理一次,删掉那些早就不用的老旧环境,不然新建 notebook 的时候下拉菜单长得能滚动半天。

6.3 内存问题:变量不删就一直占着

内核是个常驻进程,里面所有变量都不会自动回收。这点和跑脚本完全不一样——脚本跑完进程退出,内存全还回去;notebook 里你加载了一个几 G 的数据表,哪怕后面再也不用了,它还在那儿蹲着。

几个处理习惯:

处理完大对象立刻del,然后调一下垃圾回收模块,虽然不保证立刻还给系统,但至少标记了。用%whos定期看看谁在占地方,尤其是那些名字叫df、df2、df_copy、temp的中间变量,全是历史遗留。真要彻底清干净,重启内核是唯一可靠的办法。

另一个隐藏的内存杀手是图画得太多。每张图的对象、每个大数组的输出,都会留在内核里。画了几十张图之后内存飙升是正常现象,这时候清输出加重启,能立刻恢复。

7. 常见故障排查实录

7.1 启动不起来:先看端口和路径

最常见的启动失败是端口被占。报错信息里会明确写地址已被使用。定位办法很直接,查一下谁占着这个端口:

# Linux / macOS lsof -i :8888 # Windows netstat -ano | findstr 8888

查出来之后要么杀掉那个进程,要么换个端口。我前面说的port_retries = 0在这里就派上用场了——它让问题立刻暴露,而不是默默换个端口让你找不着。

第二常见的是工作目录不存在。配置文件里写了个路径,但那个目录被删了或者移动了,启动会直接报错。这种错误信息通常很直白,照着改就行。

第三是命令找不到,提示jupyter: command not found。这基本都是路径问题:包装在某个环境里,但那个环境的可执行目录没在当前 shell 的 PATH 里。解决办法是激活对应环境再启动,或者干脆用python -m notebook这种写法,让它跟着当前的 Python 走。

7.2 内核连不上:一直转圈或者自动重连

页面能打开,但每个单元格跑起来都是转圈然后断开重连,提示内核似乎挂了。这种问题我遇到过几次,原因各不相同。

一种是内核进程真的崩了。最常见于内存耗尽被杀,或者某个底层库崩了。表现是日志里能看到进程退出的记录。这种情况重启内核就行,但要留意是不是数据太大,下次还会崩。

另一种是环境里的依赖坏了。比如你在 notebook 运行期间用 pip 升级了某个底层库,正在跑的内核引用的还是旧版本的文件,两边对不上就崩了。所以我在 notebook 开着的时候基本不动环境,要装包就装完重启一次内核。

还有一种是浏览器缓存的老页面在反复请求一个已经不存在的会话。表现是刷新就好,过一会儿又断。处理办法是彻底关掉标签页重新从地址进,别用历史记录里的旧链接。

7.3 显示异常:图不显示、中文变方块

画了图但输出区一片空白,只显示一行对象地址。这个问题九成是绘图后端没配好,或者忘了在某处调用展示函数。先确认开头有没有设内联后端,再看是不是在函数里画的图但没返回、也没显示。

中文显示成方块是另一个高频问题,本质是默认字体里没有中文字形。解决办法是显式指定一个系统里存在的中文字体,并顺手把坐标轴的负号显示修一下,否则负号也会变成方块。这段配置我一般放在 notebook 开头的绘图设置区,一次配好,后面所有图都正常。

公式渲染不出来,通常是 Markdown 语法写错了,比如美元符号没配对,或者前后多了空格。渲染失败的时候不会报错,只会原样显示,找起来比较费劲,建议写完扫一眼。

7.4 排查速查表

把上面这些整理成一张表,出问题时从上往下对照:

现象最可能的原因第一步动作
命令找不到环境路径没进 PATH换python -m notebook试
页面打不开端口被占 / 地址写错查端口占用,看启动日志的完整地址
要求 token 或密码鉴权正常行为从终端日志里复制带 token 的完整链接
import 报错找不到模块内核环境 ≠ 装包环境打印解释器路径核对
变量莫名不存在内核重启过 / 单元格顺序乱看In [n]编号,全部重跑
跑起来一直转圈内存耗尽 / 依赖冲突看日志是否有进程退出记录
图不显示后端没设 / 没调用展示开头补上内联后端配置
中文方块缺中文字体显式指定字体
文件几十兆打不开输出太多清空输出,导出前跑清理命令
卡得动不了输出过大 / 图太多中断,清输出,重启内核

8. 从个人工具走向团队可用

8.1 导出与交付:别让人家装环境才能看

notebook 最尴尬的一点是,它的原生格式需要环境才能打开。发给不看代码的人,最好的方式是导出成静态网页,滚动就能看,图和表都在里面:

# 导出网页 jupyter nbconvert --to html report.ipynb # 导出 Markdown,方便进文档系统 jupyter nbconvert --to markdown report.ipynb # 先从头执行一遍再导出,确保结果是最新的 jupyter nbconvert --to html --execute report.ipynb

--execute这个参数很关键。它会先把整份文件从头跑一遍再导出,保证你看到的输出和代码是匹配的,不会出现"图上写的是旧数据"这种事。当然,前提是这份文件本身能从头跑通——又是那句话,交付前先自己跑一遍。

如果对方也是要跑代码的人,那就直接发.ipynb文件,但一定要在文件开头写清楚依赖版本,或者干脆给个环境描述文件。我吃过太多次"在我这儿好好的"的亏,最后发现是对方库的版本差了一个大版本号。

8.2 和版本控制配合:把输出剥掉再提交

.ipynb本质是个 JSON 文件,里面混着代码、输出、执行计数、图片的编码数据。直接提交到版本控制里,diff 会变得完全没法看——改了一行代码,diff 里可能全是 base64 的图片数据。

处理办法有两种。一种是在提交前把输出清掉,命令前面给过,--ClearOutputPreprocessor.enabled=True --inplace,加个--inplace是原地改写。另一种是装个提交钩子工具,在提交时自动剥掉输出,仓库里只留代码。我个人倾向后者,因为靠人记得每次手动清是不现实的,早晚会漏。

还有一个细节:执行计数也会进文件,导致同样的代码因为跑的次数不同而产生 diff。清理输出的时候一般会一并重置,不用单独处理。

8.3 什么时候该把它改写成脚本

notebook 不适合做长期维护的工程代码,这一点我态度很明确。判断标准大致是这几条,中一条就该考虑抽成.py:

代码要被别的程序调用;需要在无人值守的情况下定时跑;逻辑分支复杂到需要写单元测试;文件长到滚动条都嫌累。这几种情况下 notebook 的交互性反而成了负担,你没法可靠地测它,也没法干净地 import 它。

我一般的做法是:探索阶段全在 notebook 里,等逻辑稳定了,把核心函数抽到模块文件里,notebook 只负责调用和展示。这样既保留了探索的灵活性,又让核心逻辑可以被测试、被复用、被别人引用。抽出去之后,notebook 里那个自动重载的开关就显得格外重要了——改模块文件,notebook 里立刻生效,不用重启。

我个人在实际操作中的体会是,notebook 最大的价值不是"能分块跑代码",而是它把思考过程和执行过程放在了同一个平面上。你能看到自己当时的假设、试错、修正,全都在一页纸上。这个特性用好了,它就不只是个工具,而是你工作过程的存档。也正因为如此,那些"跑完就扔"的临时格子最好及时清掉,留下来的应该是对以后有用的东西——一份三个月后自己还能看懂、别人也能接手的工作记录。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询