📝

Jupyter Notebookで快適な資料作成 - PDF変換方法

2024/11/15に公開

概要

Jupyter Notebookをご存知でしょうか?markdownに慣れている方なら、驚くほど効率的に資料作成ができるツールです。今回は、その魅力と、よく躓きがちなPDF変換の解決策についてお話しします。

実際に変換してみたのがこちら👇

Jupyter Notebookの強み

  • Markdownで素早い文章作成
  • GPTとの相性が抜群
  • LaTeX記法でスマートな数式表現
  • Mermaidによる図表作成
  • Pythonを使用した柔軟なプログラミング

PDF変換の課題

Jupyter Notebookで文書を作成すると、当然PDFにしたくなりますよね。しかし、このPDF変換が意外な落とし穴になっています。

従来の方法の問題点

  1. 素のnbconvertを使用する場合:
    • 大量の依存パッケージのインストールが必要
    • フォントの設定が面倒
    • LaTeXの有効化など、複雑な設定が求められる

解決策の変遷

  1. HTML経由の変換

    • 一度HTMLに出力してから、ブラウザの「PDFとして保存」機能を使用
    • 手動での作業が必要で、効率が悪い
  2. nbconvertのwebpdfオプション

    • 自動化の一歩前進
    • ただし、日本語フォントの問題が残る
    • 細かい出力制御が難しい

新しいアプローチ

これらの課題を解決するため、Pythonを使用してJupyter NotebookからPDFへの変換スクリプトを一から作成しました。

カスタマイズの自由度

  • CSSによるスタイリング
  • 不要な要素の削除
  • フォントの細かい調整
  • その他、必要に応じた自由なカスタマイズが可能

この方法により、これまでの制限から解放され、より柔軟なPDF変換が可能になりました。
ただし、ページの区切りの調整の方法はわからないです。
まぁでもたったの50行程度のコードなので、気軽にやってみてください。

インフラの準備

  1. pythonの勉強
  2. 以下のパッケージをインストール:
pip install nbconvert
pip install playwright
playwright install  # ブラウザのインストール

ちなみにplaywrightは次世代のブラウザ操作ツール!Selenium使ってる人は乗り換えを強くおすすめします!

構成

nb2pdf.pyという50程度のスクリプトです

ドメイン

convert_notebook_to_html

def convert_notebook_to_html(notebook_path: str) -> str:
    """Jupyter NotebookをHTML形式に変換する

    Args:
        notebook_path (str): 変換対象のNotebookファイルパス

    Returns:
        str: 変換後のHTML文字列
    """
    ...

save_html_as_pdf

def save_html_as_pdf(html: str, output_path: str):
    """HTMLをPDFファイルとして保存する
    
    Args:
        html (str): 変換対象のHTML文字列
        output_path (str): 出力先PDFファイルパス
    """
    ...

parse_args

def parse_args():
    """コマンドライン引数をパースする
    
    Returns:
        argparse.Namespace: パースされた引数
            file: 入力Notebookファイルパス
            output: 出力PDFファイルパス
    """
    ...

実装 (まとまったソースコードあり)

全部実装したら以下のようになります

#!/usr/bin/env python3

import argparse
from nbconvert import HTMLExporter
from playwright.sync_api import sync_playwright
from traitlets.config import Config
from nbconvert import HTMLExporter
import time


def convert_notebook_to_html(notebook_path: str) -> str:
    c = Config()
    c.HTMLExporter.exclude_input_prompt = True
    c.HTMLExporter.exclude_output_prompt = True
    html_exporter = HTMLExporter(config=c)
    body, _ = html_exporter.from_file(notebook_path)
    return body


def save_html_as_pdf(html: str, output_path: str):
    with sync_playwright() as p:
        browser = p.chromium.launch()
        time.sleep(0.5)  # monkey patch for waiting chromium launch
        page = browser.new_page()
        page.set_content(html)
        page.add_style_tag(
            content="* { font-family: 'Noto Sans CJK JP', 'Hiragino Kaku Gothic ProN', 'メイリオ', sans-serif !important; }")
        time.sleep(1)  # monkey patch for waiting mathjax load
        page.pdf(
            path=output_path,
            scale=0.9,
            format="A4"
        )
        browser.close()


def parse_args():
    parser = argparse.ArgumentParser(
        description='Convert Jupyter notebook to PDF')
    parser.add_argument('-f', '--file', help='Notebook file path')
    parser.add_argument(
        '-o', '--output', help='Output PDF file path (default: output.pdf)')
    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()
    html = convert_notebook_to_html(args.file)
    if args.output is None:
        args.output = args.file.replace(".ipynb", ".pdf")
    save_html_as_pdf(html, args.output)
    print("PDFを保存しました:", args.output)

まとめ

だいぶ雑なコードだけど、以外とすぐできたしplaywrightとかもためせたので良かったです。
Shebang書いて実行権限を与え、パスの通ったフォルダに置いとくと直接呼び出せるのでおすすめです。

Discussion