
目次
はじめに
Notionの文章をPDFに変換する際に、下記リンクにあるようにHTML形式でエクスポートし、テキストエディタで編集してから、プラウザの印刷あるいはPDF出力機能でPDFに変換する方法を掲載していました。
最近は特にNotion to PDF の変換をすることが多く、手作業での変換が面倒になったので、生成AIにお願いして自動的に変換するためのPythonのコードを作成してもらったので、紹介します。
Pythonでの利用
Pythonの環境構築手順は省略します。他のWebサイトを参考にしてください。
下記のPythonコードを適当なファイル名(ここではconvert.py)として保存し、次のコマンドで変換します。
python convert.py -i input.html -o output.html
import argparse
from bs4 import BeautifulSoup
def process_html(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as f:
soup = BeautifulSoup(f, 'html.parser')
# imgタグがaタグの中にある場合、aタグを削除してimgタグだけ残す
for img in soup.find_all('img'):
if img.parent.name == 'a':
img.parent.unwrap()
# <head>タグを取得または作成
head_tag = soup.find('head')
if not head_tag:
head_tag = soup.new_tag('head')
soup.html.insert(0, head_tag)
# mermaid.min.js のscriptタグを追加
script1 = soup.new_tag('script', src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js')
head_tag.append(script1)
# mermaid.initializeのscriptタグを追加
script2 = soup.new_tag('script')
script2.string = "mermaid.initialize({startOnLoad:true,theme: 'default'});"
head_tag.append(script2)
# <code class="language-Mermaid">...</code> を <div class="mermaid">...</div> に書き換え
for code in soup.find_all('code', class_='language-Mermaid'):
new_div = soup.new_tag('div', **{'class': 'mermaid'})
for content in code.contents:
content.extract()
new_div.append(content)
code.replace_with(new_div)
# <h1>目次</h1>を探す
toc_h1 = soup.find('h1', string='目次')
if toc_h1:
# <h1>目次</h1>より前のすべての要素を取得(header以外)
prev_elements = []
current = toc_h1.previous_sibling
while current:
prev_elements.insert(0, current)
current = current.previous_sibling
# 前の要素が存在する場合
if prev_elements:
center_div = soup.new_tag('div', id='top-page', style='text-align:center;margin:20px 0;')
for elem in prev_elements:
elem.extract()
center_div.append(elem)
toc_h1.insert_before(center_div)
# 目次項目削除(table_of_contents-item table_of_contents-indent-0の「目次」)
for elem in soup.find_all(True, class_=lambda c: c and 'table_of_contents-item' in c.split() and 'table_of_contents-indent-0' in c.split()):
if elem.text.strip() == '目次':
# 一つ前のタグ要素を取得
prev = elem.find_previous_sibling(name=True)
if prev:
prev.decompose() # 一つ前の要素を削除
elem.decompose() # 「目次」要素を削除
break # 複数ある場合はbreakせず続けることも可能
# CSS追加
new_css = '''
header {
display: none;
}
#top-page h1 {
margin-top: 10rem;
margin-bottom: 10rem;
}
#top-page .column-list {
display:block;
width: 100%;
text-align: center;
}
#top-page .column-list .column {
width: 100% !important;
padding: 0;
text-align: center;
}
#top-page .column-list .column .image {
margin-top: 5rem;
margin-bottom: 5rem;
text-align: center !important;
}
h1 {
page-break-before: always;
}
/* 先頭の見出し1は改ページ無効 */
/*
h1:first-of-type {
page-break-before: auto;
}
*/
hr {
page-break-after: always;
visibility: hidden !important;
}
a, a.visited {
color: #000;
}
.table_of_contents-link {
opacity: 1;
border-bottom: none;
}
.table_of_contents {
margin-top:1.5rem;
}
.table_of_contents-item {
font-size: 1rem;
line-height: 1.5;
}
table {
width: 100%;
table-layout: fixed;
max-width: 100%;
}
tr, th, td {
color: #000 !important;
border-color: #000 !important;
}
th {
font-weight: bold !important;
background: rgb(247, 246, 243);
}
ul li details td {
text-align: right;
}
.toggle > li > details {
padding-left: 0;
}
.code:has(.mermaid) {
padding: 0 !important;
}
@media print {s
@page {
size: portrait;
}
@page rotated {
size: landscape;
}
body {
margin: 0;
padding: 0;
}
details:not(ul li details) {
page: rotated;
}
td, th {
word-wrap: break-word;
overflow-wrap: break-word;
}
.mermaid > svg {
max-width: 100% !important; /* ページの幅に合わせて最大幅を100%にする */
max-height: 100vh !important; /* ページの高さに合わせて最大高さを100%にする */
width: auto !important; /* アスペクト比を保ちつつ幅を自動調整 */
height: auto !important; /* アスペクト比を保ちつつ高さを自動調整 */
page-break-inside: avoid !important; /* 図の途中で改ページされるのを防ぐ */
}
#top-page .column-list {
position: absolute;
bottom: 0;
}
}
'''
style_tag = head_tag.find('style')
if style_tag:
style_tag.string = (style_tag.string or '') + '\n' + new_css
else:
style_tag = soup.new_tag('style')
style_tag.string = new_css
head_tag.append(style_tag)
# detailsタグの前後に空白のpタグがある場合、そのpタグを削除
for details in soup.find_all('details'):
# 直前のpタグをチェック
prev_p = details.find_previous_sibling('p')
if prev_p and (not prev_p.get_text(strip=True)):
prev_p.decompose()
# 直後のpタグをチェック
next_p = details.find_next_sibling('p')
if next_p and (not next_p.get_text(strip=True)):
next_p.decompose()
with open(output_file, 'w', encoding='utf-8') as f:
f.write(str(soup))
def main():
parser = argparse.ArgumentParser(description='HTMLから「目次」項目を削除します')
parser.add_argument('-i', '--input', required=True, help='入力HTMLファイル')
parser.add_argument('-o', '--output', default='output.html', help='出力HTMLファイル')
args = parser.parse_args()
process_html(args.input, args.output)
print(f'処理が完了しました。出力ファイル: {args.output}')
if __name__ == '__main__':
main()
専用の記法
Notionに記入する際に、次のルールで記入すると自動変換されます。
表紙
「目次」ブロック前の記載は表紙として扱われ、HTML で id:top-pageが付与されます。
文章のタイトルを見出し1で記載します。
その下に2列のカラムを作成し、左の列に版数、日付、ロゴなどを記載します。
カラム部分は表紙ページの最下部に自動的に配置されます。
目次
見出し1で「目次」というタイトルを作成すると、目次内に「目次」という行が追加されてしまうため、この行を自動的に削除します。また、表紙内に文章のタイトルを見出し1で作成するとこちらも目次に掲載されるため、自動的に削除します。
なお、Notionページ自体の「タイトル」はPDFには含まれないため、見出し1として文章のタイトルを追加する必要があります。これは、タイトル行内で改行したい場合に、Notionページ自体のタイトルを利用すると対応できないためです。
改ページ
見出し1、区切り線は自動的に改ページされます。
Mermaid記法
フローチャート等をCodeブロックにMermaid記法でかきます。
Mermaid記法により作成されたSVGの図は、ちょうど1ページに収まるように拡大・縮小されます。
用紙方向切替(ポートレート・ランドスケープ)
トグル見出し内は横方向(ランドスケープ)になります。
表の要素の右寄せ
トグルリスト内に作成された表の要素は右寄せになります。
数値等で右寄せが見やすい場合に利用します。
なお、表のタイトルは中央寄せです。
Pythonでの変換例
変換元Notionページ
下記のサンプルページをPDFに変換します。
変換後PDF
残念ながら、ページ番号は自動挿入できませんが、Adobe Acrobat PDFオンラインツール等を利用すれば、簡単にページ番号を追加できます。
表紙、目次にはページ番号を記載せず、本文から記載することもできます。
次回は…
今回は、ローカル環境でPythonを使ってNotionからエクスポートしたHTMLを自動編集し、ブラウザを使ってPDFへエクスポートするところまでできました。
次回は、今回作成したPythonのコードをクラウドで動作させることで、Python環境が構築されていないPCからでもブラウザを使うことで、今回と同等の変換処理を行えるようにしてみます。
FAQ
-
NotionからHTMLやPDFにエクスポートする理由は?
-
仕様書や資料を外部と共有したり、印刷配布する際にPDF形式が便利だからです。HTML経由で編集することで、レイアウトやスタイルを細かく調整できます。
-
Pythonスクリプトを使うメリットは?
-
手作業での編集や変換を自動化できるため、作業効率が大幅に向上します。また、Mermaid記法など特殊な記述も自動で適切に扱えます。
-
この方法でMermaid図表もPDFに反映されますか?
-
はい。PythonスクリプトがMermaid記法をHTMLのdivタグに変換し、ブラウザのPDF出力機能で正しく図表として出力されます。
-
表紙や目次の自動生成はできますか?
-
はい。スクリプトが「目次」ブロック前の内容を表紙として扱い、目次内の不要な行も自動で削除します。
-
ページ番号は自動で挿入されますか?
-
現時点では自動挿入できませんが、Adobe AcrobatなどのPDF編集ツールで後から簡単に追加できます。
-
用紙の向き(縦・横)は自動で切り替わりますか?
-
トグル見出し内は横方向(ランドスケープ)に自動切り替えされます。
-
表の要素を右寄せにしたい場合、どうすればいいですか?
-
トグルリスト内に作成した表の要素は自動で右寄せになります。
-
この方法はどんな人におすすめですか?
-
Notionで仕様書や資料を作成し、定期的にPDFに変換する必要がある方、HTMLやCSSの編集が苦にならない方におすすめです。
-
変換後のPDFのレイアウト崩れを防ぐには?
-
HTMLのスタイル(CSS)で印刷用のレイアウトを細かく調整し、ブラウザのPDF出力機能を利用してください。
-
Notionページタイトルと見出し1の違いは?
-
NotionのページタイトルはPDF出力時に反映されません。見出し1でタイトルを記載することで、PDFにも反映されます。また、見出し1で改行したい場合も、Notionページタイトルでは対応できないため、見出し1ブロックを使う必要があります。
-
表紙や目次以外のページでも自動改ページはされますか?
-
見出し1や区切り線(hrタグ)がある場合、自動で改ページされます。
-
トグルリスト内の表以外の要素も右寄せになりますか?
-
現状のCSSでは、トグルリスト内の表の要素のみ右寄せになります。他は標準の配置です。