Skip to content

Instantly share code, notes, and snippets.

@CookieBox26
Last active October 17, 2025 14:29
Show Gist options
  • Select an option

  • Save CookieBox26/835c16fb140391d49120f2cbbde69e3e to your computer and use it in GitHub Desktop.

Select an option

Save CookieBox26/835c16fb140391d49120f2cbbde69e3e to your computer and use it in GitHub Desktop.
Jupyter Notebook を HTML 経由で PDF に変換するスクリプト (Playwright 使用)
"""
Jupyter Notebook を PDF に変換します。以下のように実行してください。
使用例:
python nb2pdf.py aaa/bbb/ccc.ipynb
python nb2pdf.py aaa/bbb/ccc.ipynb --header # ファイル冒頭に「関係者外秘」と印字します。
"""
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
from pathlib import Path
import subprocess
import re
import argparse
class NotebookPDFConverter:
""" Jupyter Notebook を HTML 経由で PDF に変換するクラス
Note:
aaa/bbb/ccc.ipynb を aaa/bbb/tmp.html を経由して aaa/bbb/ccc.pdf にします。
中間ファイル・出力ファイルは上書きするので、必要があれば上書き防止処理を追加してください。
中間 HTML への加工処理 [2a] [2b] [2c] は不要なら削除して構いません。
"""
@classmethod
def _repl(cls, match):
return '\n' + '\u2423' * len(match.group(1))
@classmethod
def replace_whitespaces_in_pre(cls, soup, pre):
# ある pre タグ内の行頭の空白を空白文字に置換する
for child in pre.children:
if child.name or ('\n' not in child):
continue
# 改行を含む文字列のとき改行後の空白を同じ数の U+2423 Open Box に置換 (ライトグレーで)
tag_open_box = soup.new_tag('span', style='color: lightgray;')
tag_open_box.string = str(re.sub(r'\n( +)', NotebookPDFConverter._repl, child))
child.replace_with(tag_open_box)
@classmethod
def replace_whitespaces(cls, soup, cells):
# セル内の行頭の空白を空白文字に置換する
for cell in cells:
pres = cell.find_all('pre')
for pre in pres:
NotebookPDFConverter.replace_whitespaces_in_pre(soup, pre)
def __init__(self, ipynb_path, print_header):
html_path = ipynb_path.parent / 'tmp.html'
pdf_path = ipynb_path.with_suffix('.pdf')
# [1] まずノートブックを HTML に変換し Beautiful Soup でパースする
subprocess.run(['jupyter', 'nbconvert', '--to', 'html', '--output', html_path, ipynb_path])
soup = BeautifulSoup(html_path.read_text(encoding='utf8'), 'html.parser')
# [2a] インプットセル内の行頭の空白を空白文字に置換する (PDF 上のコードがコピペできなくなるため)
cells = soup.find_all('div', class_='jp-Cell-inputWrapper')
NotebookPDFConverter.replace_whitespaces(soup, cells)
# [2b] head タグ末尾に独自スタイルを挿入する
tag_custom_style = soup.new_tag('style', type='text/css')
tag_custom_style.string = 'body {margin: 0 !important; padding: 0 !important;}\n' \
+ 'div.jp-MarkdownOutput {font-family: "Rounded Mplus 1c"; line-height: 1.2;}\n' \
+ 'pre {font-family: "JetBrains Mono" !important;}\n'
soup.head.append(tag_custom_style)
# [2c] main タグ先頭に「関係者外秘」と印字する
if print_header:
soup.find('main').insert(0, '関係者外秘')
# [3] HTML を PDF として保存する
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.set_content(str(soup))
page.pdf(path=pdf_path, format='A4', print_background=True, scale=0.9)
browser.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('ipynb_path', type=str)
parser.add_argument('--header', action='store_true')
args = parser.parse_args()
NotebookPDFConverter(Path(args.ipynb_path).resolve(), args.header)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment