使用nbconvert导出Jupyter Notebook为HTML时同时实现目录与输入单元格隐藏
我之前也碰到过一模一样的问题——单独用html_toc导出目录正常,单独用隐藏代码模板也能隐藏输入,但两者结合就只剩单元格内容,目录直接消失了。核心原因是--template参数会直接覆盖html_toc的内置模板结构,导致目录所需的HTML结构、CSS和JS都没被渲染出来。下面是我亲测有效的解决思路和步骤:
核心思路:合并两个模板的功能
我们需要创建一个继承自html_toc的自定义模板,同时把隐藏输入单元格的逻辑加进去,这样就能同时保留目录和隐藏代码的功能。
步骤1:定位nbconvert模板的存放路径
首先找到你的Jupyter模板目录,执行以下命令:
jupyter --data-dir
输出的路径一般类似~/.local/share/jupyter(Linux/macOS)或者C:\Users\<你的用户名>\AppData\Roaming\jupyter(Windows)。进入这个路径下的nbconvert/templates文件夹,这是自定义模板的存放位置。
步骤2:创建自定义模板文件夹
在templates里新建一个文件夹,比如命名为html_toc_hidecode,然后在里面创建两个文件:
文件1:template.conf(用于继承html_toc模板)
内容如下:
[main] extends = html_toc
这个文件告诉nbconvert,我们的新模板是基于内置的html_toc模板扩展的,这样目录功能会被保留。
文件2:index.html.j2(添加隐藏输入单元格的逻辑)
这个文件是模板的核心,我们需要修改输入单元格的渲染方式,同时可以添加切换显示/隐藏的按钮。内容如下:
{% extends 'html_toc/index.html.j2' %} {% block header %} {{ super() }} <!-- 自定义CSS:默认隐藏输入单元格 --> <style> .input { display: none; } .show-input-btn { margin: 8px 0; padding: 4px 10px; border: 1px solid #ccc; border-radius: 4px; background-color: #f8f8f8; cursor: pointer; } .show-input-btn:hover { background-color: #e8e8e8; } </style> <!-- 自定义JS:添加显示/隐藏输入的按钮 --> <script> document.addEventListener('DOMContentLoaded', function() { const inputBlocks = document.querySelectorAll('.input'); inputBlocks.forEach(block => { const btn = document.createElement('button'); btn.className = 'show-input-btn'; btn.textContent = '显示输入代码'; btn.onclick = () => { block.style.display = block.style.display === 'none' ? 'block' : 'none'; btn.textContent = block.style.display === 'none' ? '显示输入代码' : '隐藏输入代码'; }; // 把按钮放到输入块的前面 block.parentNode.insertBefore(btn, block); }); }); </script> {% endblock header %} {% block input %} <!-- 修改输入单元格的渲染逻辑:默认隐藏 --> <div class="input"> <div class="prompt input_prompt"></div> <div class="input_area"> {% for attachment in cell.attachments.values() %} <img src="{{ attachment['image/png'] }}" class="attachment" alt="attachment" /> {% endfor %} {{ cell.source | highlight_code(cell.language) }} </div> </div> {% endblock input %}
如果你不想全局隐藏所有输入单元格,而是想通过单元格元数据控制,可以把{% block input %}部分改成:
{% block input %} {% if cell.metadata.get('hide_input', True) %} <div class="input" style="display:none;"> {% else %} <div class="input"> {% endif %} <div class="prompt input_prompt">{% if not cell.metadata.get('hide_input', True) %}In [{% trans cell_index=cell.index %} {{ cell_index }} {% endtrans %}]:{% endif %}</div> <div class="input_area"> {% for attachment in cell.attachments.values() %} <img src="{{ attachment['image/png'] }}" class="attachment" alt="attachment" /> {% endfor %} {{ cell.source | highlight_code(cell.language) }} </div> </div> {% endblock input %}
这样的话,只有添加了"hide_input": true元数据的单元格才会被隐藏(你可以在Jupyter里右键单元格→“编辑元数据”来添加)。
步骤3:使用自定义模板导出
现在执行以下命令导出Notebook,就能同时得到目录和隐藏的输入单元格了:
jupyter nbconvert --to html --template html_toc_hidecode YOUR_NOTEBOOK.ipynb
注意这里用--to html而不是html_toc,因为我们的自定义模板已经继承了html_toc的目录功能。
额外提示
- 如果你的隐藏代码模板有其他自定义样式(比如修改输出单元格样式),可以把对应的代码也复制到
index.html.j2的对应块里,比如{% block output %}。 - 导出后的HTML文件里,所有输入单元格默认隐藏,点击“显示输入代码”按钮就能展开查看代码,非常方便。
内容的提问来源于stack exchange,提问作者Zoe_89

