Jupyter Notebook數據格式解析
- 2019 年 10 月 8 日
- 筆記
本文2772字,預計閱讀需16分鐘;
比較干,歡迎收藏與實踐
最近遇到一個問題:
如何合併多個Jupyter Notebook的筆記為一個筆記文件?
經常用Jupyter Notebook寫Python程式碼,看到這個需求不是想去找輪子而是想自己做解析和合併。通過深入文件格式去加深對Jupyter Notebook的了解。用Jupyter 寫程式碼有很多優勢:互動式的編程體驗、文檔圖表整合、擴展性強而且非常容易復現結果。
從2017年開始,已有大量的北美頂尖電腦課程,開始完全使用Jupyter Notebook作為工具。如李飛飛的CS231N《電腦視覺與神經網路》課程,在16年時作業還是命令行Python的形式,但是17年的作業就全部在Jupyter Notebook上完成了。雖然現在又推出了Jupyterlab,不能否認的是Notebook仍然是很值得使用和研究的工具,因此除了改主題安插件之外,探索更多的Jupyter Notebook用法和原理是有趣有用的。
用文本編輯器打開一個Jupyter Notebook文件,驚奇地發現不是亂碼,說明不是直接存二進位格式而是文本格式,那就不用按數據塊去解析了。如下圖,熟悉的大括弧和鍵值對讓人想到JSON,仔細看果然是JSON,那讀取就容易了,關鍵就是各個鍵的意義和數據組織。

ipynb文件打開效果
1, 基礎結構
Jupyter Notebook的文件是通過json格式存儲和組織其中的數據的。JSON (JavaScript Object Notation)獨立於程式語言,基礎的結構就是 {鍵1:值1,鍵2:值3}
這樣的字典形式,值可以是數字、字元串、數組和字典。Jupyter Notebook的頂層結構是一個鍵值對:{"metadata":{},"nbformat":4,"nbformat_minor":0,"cells":[]}
我們寫程式碼的一個個格子對應的就在cells里,我們交互產生的數據都記錄在cells鍵對應的列表裡,如下圖

cell與我們交互區域的對應
其他的鍵,像metadata是一些描述性的元數據,因此我們重點關注cells的列表。
2, 內容部分
我們在裡面寫程式碼的一個個小塊就是一個個cell,它整體也是一個字典,包含cell_type(內容類型)、source(我們輸入的內容)、metadata(描述性的元數據);這三個鍵就構成了一個cell。如下面的思維導圖,也可以結合上面 程式碼塊區域示例 的圖來理解。

cell的基礎結構
其中execution_count 、output和attachments是不一定每個cell都存在的鍵,因此做解析是要有判斷語句。cell_type有3種選擇:code、markdown、raw,下面對這三種類型分別解析。
程式碼塊通過cell的cell_type標識 "cell_type"="code"
程式碼塊里裝的就是我們寫的一行行程式碼,程式碼裝在source鍵對應的列表裡,source鍵對應的類型是列表list,列表裡是字元串,一行程式碼是一個字元串。executioncount表示執行次數,對應我們前端能看到的In里的次數。
metadata記各種元數據,包括一些插件產生的數據,例如我安裝了一個看執行時間的插件ExecuteTime,每次運行可以看執行耗時和最後一次執行的時間,這個數據也是會記錄在ipynb文件里,對應的就裝在metadata里,如果在一個沒安裝這個插件的環境里運行就不會讀metadata的對應內容,可以說metadata給jupyter提供了很好的擴展性。程式碼輸出的內容在output對應的列表裡。output的列表裡裝的不直接是數值或字元串,而是字典,outputtype有多種可能,包括正常的程式碼輸出的stream、execute_result,還有報錯輸出的error。

各種輸出的效果
Markdown塊是寫報告和文檔常用的cell,在前端會渲染出很好的效果,因為語義和格式就通過markdown本身約定的格式體現,對應記錄的數據比程式碼塊簡單。不涉及輸出所以不需要有output鍵,核心就是source和metadata。
無格式塊的官方說法是叫 Raw NBConvert,對應cell_type的值是raw,因為是純文本效果,在頁面上不做特殊渲染,和markdown有的內容基本一致,核心就在source的字元串列表裡。
以上內容整理為思維導圖如下:

三種內容塊概覽
3, 需求實現
基於以上我們對Jupyter Notebook文件結構的了解,就可以開工寫合併多個ipynb文件為一個的程式碼了。假設我們需要合併一個文件夾下的所有ipynb文件為一個,根據文件名的順序組織。我們首先讀取得到需要合併的文件名的列表,然後通過json庫讀取ipynb文件的內容,因為我們寫的程式碼、文字、程式碼輸出結果這些都在cells里,而且順序是cells列表裡元素的順序,所以我們合併cells里的內容就實現了這一需求。程式碼如下:
import osimport jsonwpt='d:/readingForDS/'#文件所在路徑for root, dirs, files in os.walk(wpt): flst=filesflst=[wpt+f for f in flst if f.endswith('.ipynb')]jmain=json.load(open(flst[0],'r',encoding='utf-8'))for f in flst[1:]: jn=json.load(open(f,'r',encoding='utf-8')) jmain['cells'].extend(jn['cells']) with open('ipynb-combine.ipynb','w',encoding='utf-8') as wf: json.dump(jmain,wf)#寫入文件
因為nbformat等鍵是通用的,所以程式碼中直接用了第一個ipynb文件的nbformat值。一個合併的效果如下圖

合併多個ipynb文件示例
關於合併多個ipynb文件這個需求有一個挺好的輪子是https://github.com/jbn/nbmerge 。
同樣的思路我們可以根據一些條件對一個大的ipynb文件拆分為多個文件,例如按章拆分一個讀書筆記(每個章節的特徵是用了markdown語法,如 ## 第3章 用Python讀寫Excel文件
)。
4, 應用舉例
了解了Jupyter Notebook的文件組織結構之後,除了合併ipynb文件還可以做哪些事情呢?其實我們可以造很多輪子。例如自己實現:
- 導出ipynb文件為py腳本文件:
- 導出ipynb文件為markdown文件;
- 導出為HTML文件;
導出ipynb文件為py腳本文件的程式碼示例如下:
#ipynb 2 pyjn_py=[]jn_py.extend(['#!/usr/bin/env python','# coding: utf-8'])ja=json.load(open('ipynb2pdf.ipynb','r',encoding='utf-8'))for c in ja['cells']: if c['cell_type']=='markdown': jn_py.append('n{0}n'.format('# '.join(c['source']))) elif c['cell_type']=='code': if c['execution_count']==None: jn_py.append('# In[ ]:') else: jn_py.append('# In[{0}]:'.format(c['execution_count'])) jn_py.append(''.join(c['source'])+'n') elif c['cell_type']=='raw': jn_py.append('n{0}n'.format('#'.join(c['source']))) with open('ipynb2pdf-c2py.py','w',encoding='utf-8') as wf: for k in jn_py: wf.write(k+'n') # ipynb 2 mdmd_str='' #兩種模式:直接裝到一個字元串里或裝到列表裡,一行是一個字元串for c in ja['cells']: if c['cell_type']=='markdown': md_str=md_str+'n'+''.join(c['source'])+'nn' elif c['cell_type']=='code': md_str=md_str+'n```python n'+''.join(c['source'])+'n```nn' if len(c['outputs'])>0: # !=[] for o in c['outputs']: if 'text/html' in o['data']: #keys md_str=md_str+'n'+''.join(o['data']['text/html'])+'n' elif 'text/plain' in o['data']: md_str=md_str+'n'+''.join(o['data']['text/plain'])+'n' elif c['cell_type']=='raw': md_str=md_str+'n'+''.join(c['source'])+'n' with open('ipynb2pdf-c2md.md','w',encoding='utf-8') as wf: wf.write(md_str)

直接導出py與程式碼導出對比
因為有時候我們在Github上看ipynb格式的資料時,可能會載入不出來渲染的效果,這時候懂得了上面的Jupyter Notebook的文件組織結構後,我們可以從原始數據大致確定看的ipynb里有那些程式碼,輸出的結果。
5, 總結
總結這篇文章的內容:
- Jupyter Notebook有良好的文檔圖表整合能力和擴展性,已有大量的北美CS課程使用Jupyter Notebook作為編程環境;
- .ipynb文件是以json格式組織數據的;我們編寫的程式碼、文本和輸出存在cell列表裡;
- 程式碼的順序就是cell列表中元素順序;
- 基於以上特點我們可以寫程式碼合併和拆分Notebook文件,還可實現ipynb文件轉換為py、html格式文件。
以上內容自己整理了一個xmind腦圖,獲取思維導圖文件和文中示例程式碼ipynb文件可在公眾號後台回復 jupyter 獲取。

格式解析導圖概覽