Jupyter Notebook 用得好好的,辛辛苦苦跑完一堆代码,结果导出 Markdown 的时候,里面的输出结果全没了,只剩下光秃秃的代码。我第一次遇到这情况,还以为是 Notebook 文件坏了,急得满头大汗。后来才知道,这只是 nbconvert 这个转换工具在默认情况下“不听话”,它把输出内容当成可以丢掉的东西了。别慌,今天咱们就聊聊怎么让它乖乖把执行结果也一起导出来,顺便说说如果已经导出了,还有没有办法补救。

一、为什么导出 Markdown 会丢失执行结果?

要搞清楚这个问题,得先明白 Jupyter Notebook 到底是怎么存东西的。你看到的 .ipynb 文件,本质上就是一个 JSON 格式的文本文件。里面除了你写的代码,还有一个叫“outputs”的区域,专门存代码跑出来的结果,比如打印的文字、表格、图片等等。

当你用 nbconvert 导出 Markdown 的时候,它默认的模板和配置只关心“代码”和“Markdown 单元格”,至于输出结果,它可能觉得你不需要,或者为了避免转换过程太慢(比如有些输出特别大),就直接给你跳过了。这就好比你去餐厅点了个套餐,结果服务员把主菜给你了,配菜和饮料全扣下了,你肯定不乐意。

还有一个常见原因:你的 Notebook 里有些输出是“流式输出”(比如用 print() 在循环里不断打印),这种输出在内存里是有长度的,如果没被正确捕获,或者 Notebook 文件本身保存的时候就没带上,那导出自然也没了。另外,如果你用了某些自动清空输出的插件,那更是从源头就没了,补救都无从谈起。

二、先检查一下你没白忙活

在调参数之前,咱们先确认一下你的 Notebook 文件里到底有没有输出。别费了半天劲,最后发现输出根本没保存。

你可以用文本编辑器直接打开 .ipynb 文件,搜索 "outputs" 这个字段。如果能看到 "outputs": [],说明这个单元格的输出是空的。如果能看到一个列表,里面甚至有 "text" 或者 "data" 这样的内容,那就说明输出是存在的,只是导出的姿势不对。

另一个更简单的方法:打开 Jupyter Notebook 页面,看看代码单元格下方有没有显示结果。如果显示了,那说明文件里有;如果页面里也没有,那多半是文件本身就没存上,或者保存之前被清理过。

三、让 nbconvert 乖乖带上输出 —— 参数调优实战

3.1 最简单的方式:直接指定不排除输出

在终端里,你只要给 nbconvert 加一个参数,叫 --to markdown,然后让它不要“排除输出”。这个参数就是 --TemplateExporter.exclude_output=True,但注意,默认是 False,所以你其实什么都不用写,它本来就该带上。但为什么没带呢?问题出在有些版本的配置或模板上。

为了保险起见,我建议你明确地告诉它“我要输出”。命令如下:


# 用 --stdout 直接输出到终端,方便快速测试
# --no-prompt 去掉代码前的 In [ ] 和 Out [ ],让 Markdown 更干净
jupyter nbconvert --to markdown --no-prompt --TemplateExporter.exclude_output=False 你的笔记本.ipynb

你看,这里我把 exclude_output 明确设成 False,意思就是“不要排除输出”。如果你用了这个命令,一般就能导出带输出的 Markdown 了。

3.2 输出图片怎么办?—— 嵌入还是单独存放

有时候,你的结果里包含 matplotlib 画的图。这种情况下,nbconvert 默认会把图片转成 base64 编码,然后嵌到 Markdown 文件里。这样你拿到一个 .md 文件,就能看到图片,特别方便,但缺点是文件会变得巨大。如果你想把图片单独存成一个文件夹,那就需要调整另一个参数。


# --NbConvertApp.output_files_dir 指定图片存放的文件夹
# --TemplateExporter.extra_template_basedirs 确保模板能找到
# --to markdown 输出 md 文件
# 注意:图片不会自动嵌入,而是在 md 里用相对路径引用
jupyter nbconvert --to markdown --NbConvertApp.output_files_dir=images 你的笔记本.ipynb

这样运行之后,图片会保存到 images 文件夹里,Markdown 文件里的图片链接会指向 images/xxx.png。要是你希望图片既能在 md 里直接看,又不想额外带文件夹,那就用回默认的嵌入方式,别设置 output_files_dir 就行。这个看你的具体需求,比如你要把 md 发给别人,那肯定是嵌入更省事。

3.3 输出内容太多,被截断了怎么办?

有时候你 pip install 一个包,输出几百行,默认 nbconvert 可能只给你保留最后几十行。那是因为 Notebook 里的输出对象自带一个截断机制。如果你想保留完整输出,需要修改 Notebook 文件的 metadata,或者写一个小脚本把输出展开。但这里我们只讲 nbconvert 相关的,所以你可以用下面这个参数来增加输出的最大长度。


# --TemplateExporter.output_mimetype 保持默认
# 通过 NotebookApp 设置来避免截断,这个参数实际上控制的是每个输出对象的“text/plain”表示长度
jupyter nbconvert --to markdown --TemplateExporter.exclude_output=False --NotebookExporter.max_buffer_size=1000000000 你的笔记本.ipynb

实际上,max_buffer_size 控制的是导出过程中的缓冲上限。如果你的输出特别大(比如几 MB 的文本),默认的 100MB 还是够的。但如果真的超了,就调大这个值。注意,这个参数不能解决“单条输出字符串超过 Jupyter 显示上限”的问题,那个是写在 .ipynb 文件里的,如果保存时就被截断了,那么导出的也是被截断的版本。这种情况,还是得回头在 Jupyter 里重新执行一下那个单元格,让输出重新生成。

3.4 复杂场景:用配置文件一步到位

如果你经常需要导出带输出的 Markdown,每次打一长串命令很烦。你可以写一个配置文件,把这些参数都放进去。假设我们创建一个 my_nbconvert_config.py

# 这是一个 nbconvert 的 Python 配置文件
# 你可以直接在命令行里用 --config 指定它

# 让导出结果包含代码执行输出
c.TemplateExporter.exclude_output = False

# 不要代码前的 In[] 提示符
c.TemplateExporter.exclude_input_prompt = True
c.TemplateExporter.exclude_output_prompt = True

# 把图片存到单独文件夹,你也可以改成 True 让它嵌入
c.NbConvertApp.output_files_dir = 'images'

# 默认的 markdown 模板就很好,不需要额外定制
c.NbConvertApp.default_template_file = 'markdown'

然后在终端里执行:


# --config 指定我们的配置文件
jupyter nbconvert --to markdown --config my_nbconvert_config.py 你的笔记本.ipynb

这样写的好处是,以后你只要改配置文件,命令不用变,而且你还能把配置共享给团队其他人。注意,配置文件名可以随便起,但必须以 .py 结尾,因为 nbconvert 是通过 Python import 机制来加载它的。

四、已经丢失了?别怕,这里有补救方案

如果你没有提前调整参数,已经导出了一个没有输出的 Markdown,但好在你的 .ipynb 文件还在,那就不算完。最简单的方法是重新用上面正确的命令再导一次。但如果 .ipynb 文件里的输出已经被清空了(比如你手滑清空过),那就只能回到 Jupyter Notebook 里重新跑一遍所有代码,让它重新产生输出,然后再保存。这里我提供一个自动化的小脚本,帮你实现“重新执行并保存输出”的操作,用的是 Python 的 nbformatnbclient 库。先安装依赖:


# 需要安装 nbclient,nbformat 一般自带
pip install nbclient nbformat

然后我们写一个 Python 脚本来批量处理:

"""
这个脚本用来重新执行指定的 .ipynb 文件,
并把执行后的输出保存回原文件。
"""
import nbformat
from nbclient import NotebookClient

def rerun_notebook(path):
    # 读取笔记本文件
    with open(path, 'r', encoding='utf-8') as f:
        nb = nbformat.read(f, as_version=4)  # 始终用最新的格式版本

    # 创建一个 NotebookClient 实例
    # timeout 参数表示每个单元格最长执行时间(秒),防止死循环卡死
    client = NotebookClient(nb, timeout=600, kernel_name='python3')

    # 执行所有单元格,这一步会运行代码并捕获输出
    client.execute()

    # 把带输出的笔记本写回原文件
    with open(path, 'w', encoding='utf-8') as f:
        nbformat.write(nb, f)

    print(f"已完成:{path},输出已保存。")

if __name__ == "__main__":
    # 换成你要处理的笔记本文件名
    rerun_notebook("你的笔记本.ipynb")

注意,这个脚本会自动按顺序执行你笔记本里所有代码单元格。如果你的代码需要交互输入、或者依赖外部服务,可能会卡住。建议先用一个简单的笔记本测试。执行完这个脚本后,你的 .ipynb 文件里就有输出了,然后再用之前调优过的 nbconvert 命令导出 Markdown,就能拿到完整的结果。

五、应用场景与实际需求分析

这个需求最常出现在写技术文档、博客、或者给别人分享代码示例的时候。比如说你想把一份数据清洗的分析过程做成一个 Markdown 报告,发给领导或者同事看。如果没有输出,人家根本不知道你代码跑出了什么结果,还得自己跑一遍,体验很差。带上输出后,报告里直接能看数据和图表,非常直观。

还有一种情况是你在用 Jupyter Book 或者 MkDocs 这类工具构建在线文档,它们经常会把 Notebook 转换成 Markdown 作为页面内容。如果转换时丢掉了输出,你的文档就会变成“空壳代码”,读者一头雾水。

在机器学习场景下,模型训练的 loss 曲线、准确率表格,都是重要的输出内容。导出 Markdown 时如果把这些都丢了,那文档等于没写。所以调好 nbconvert 参数,对任何用 Jupyter 做数据工作的人来说都很重要。

技术的优缺点很明确。优点是:你不需要修改任何 Python 代码,只用命令行参数就能精准控制导出行为,非常灵活;而且 nbconvert 支持很多输出格式,不止 Markdown。缺点是:参数比较多,初学者容易搞混;另外,如果输出里有动态内容(比如 HTML 交互组件),转成 Markdown 之后就变成静态文本了,效果会打折。

六、注意事项与避坑指南

第一,如果你在命令行里看到了 -e 之类的参数,千万别乱用,那些是给高级用户定制模板的,不是标准参数。我们上面用的参数都是官方文档里能找到的,放心使用。

第二,--no-prompt 参数会把代码前面的 In [ ]Out [ ] 去掉。如果你希望保留这些提示符,就不要加这个参数。个人建议去掉,因为 Markdown 里带 InOut 看起来非常丑,而且没有任何实际作用。

第三,如果你的 Notebook 里有一些单元格是故意不执行的(比如 Markdown 说明),nbconvert 不会动它们,你放心。只有包含代码的单元格才会被考虑。

第四,如果你导出的 Markdown 文件里面图片是相对路径,但你把这个 md 文件挪到了另一个文件夹,那么图片就可能显示不出来。解决方法是把 md 和 images 文件夹一起移动,或者干脆用嵌入模式。

第五,nbconvert 对某些特殊输出(比如 pandas 的 DataFrame 样式化输出)支持得不是很好,可能会丢掉样式。这种情况,建议你在 Notebook 里手动用 print(df.to_markdown()) 打印出表格内容的 Markdown 表示,这样导出的时候就能带上纯文本表格了,虽然样式简单点,但至少内容完整。

第六,执行上面的补救脚本时,一定要确保你的代码能在当前环境下正常运行。如果 Notebook 里调用了某个包没安装,脚本会在半路报错,然后你得到的笔记本文件可能只执行了一半。所以建议先把环境装好,或者用一个没有外部依赖的 Notebook 做测试。

七、文章总结

导出 Markdown 丢失代码执行结果,本质上是对 nbconvert 默认行为不清楚造成的。通过显式设置 exclude_output=False,以及合理使用 output_files_dir、配置文件等参数,你完全可以控制导出结果。如果输出已经丢了,也不要绝望,用 nbclient 重新执行一遍笔记本就能把输出找回来。整个过程不需要写复杂的代码,一条命令或者一个小脚本就能解决。

我建议你在自己的电脑上拿一个简单的 Notebook 先试试这些参数,看看效果有什么不一样。比如写一个 print("hello") 和一个画图的单元格,然后用不同的命令导出几次,对比一下。多试几次,你就不会再被这个问题困扰了。以后不管是写文章还是做汇报,导出 Markdown 都能顺顺利利。