Last active
October 17, 2025 14:29
-
-
Save CookieBox26/835c16fb140391d49120f2cbbde69e3e to your computer and use it in GitHub Desktop.
Jupyter Notebook を HTML 経由で PDF に変換するスクリプト (Playwright 使用)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| 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