Python | 自動化:PDF 自動生成

Python
スポンサーリンク

概要(PDF自動生成は「レポートや帳票を押印前まで自動で作る」)

Python で PDF 自動生成ができると、こんなことができます。

  • 日次・月次レポートを PDF で自動出力
  • 見積書・請求書・領収書などの帳票をデータベースから一括生成
  • 分析結果やグラフを PDF でまとめて共有

イメージとしては「Excel レポート自動作成」+「見た目を PDF にする」です。
ここでは、初心者でも扱いやすい

  • 「ライブラリを使って直接 PDF を描く方法(reportlab)」
  • 「テンプレ的な帳票を自動生成する流れ」

を、具体的なコード付きで解説します。


基本の考え方(PDF は「座標に文字や線を描くキャンバス」)

どんなライブラリを使うか

Python で PDF を作る代表的な方法は大きく2つあります。

  • PDF を直接描画する(ReportLab, fpdf2 など)
  • HTML を書いて、それを PDF に変換する(weasyprint, wkhtmltopdf など)

HTML→PDF は「Web ページを書く感じ」で作れるので便利ですが、
外部コマンドや環境依存も増えるので、まずは「直接描画」の方から入るのがおすすめです。

ここでは reportlab を使います。

pip install reportlab

PDF を「キャンバス」としてイメージする

reportlab の基本は「用紙に対して、座標を指定してテキストや線を描く」です。

  • 用紙サイズ(A4、LETTER など)を決める
  • (x, y) 座標に文字を描く
  • 線や四角形、画像などを配置する

この「紙にペンで描く」感覚を掴めばOKです。


例1:1枚もののシンプルな PDF レポートを作る

最小のサンプル(1ページにタイトルと本文)

まずは「1ページだけの PDF」を作ってみます。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from pathlib import Path
from datetime import datetime

BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "output"
OUT_DIR.mkdir(exist_ok=True)

def create_simple_report():
    today = datetime.today().strftime("%Y-%m-%d")
    out_path = OUT_DIR / f"report_{today}.pdf"

    c = canvas.Canvas(str(out_path), pagesize=A4)
    width, height = A4   # 用紙の幅・高さ(ポイント単位)

    # タイトル
    c.setFont("Helvetica-Bold", 20)
    c.drawString(72, height - 72, "売上レポート")

    # サブタイトル
    c.setFont("Helvetica", 12)
    c.drawString(72, height - 100, f"作成日: {today}")

    # 本文テキスト
    text = c.beginText(72, height - 140)
    text.setFont("Helvetica", 11)
    lines = [
        "これは Python によって自動生成された PDF レポートです。",
        "ここに集計結果やコメントを好きなだけ書いていくことができます。",
        "",
        "・日次売上のサマリ",
        "・重要なKPI",
        "・コメント など"
    ]
    for line in lines:
        text.textLine(line)
    c.drawText(text)

    c.showPage()  # ページを確定
    c.save()      # ファイルを保存

    print(f"作成しました: {out_path}")

if __name__ == "__main__":
    create_simple_report()
Python

ここで重要なのは次の点です。

座標系
左下が (0, 0)、右上が (width, height) です。
A4 の場合、height - 72 は「上から 72pt(約 2.5cm)」あたりの位置になります。

フォント指定
setFont("Helvetica", 12) のように、フォント名とサイズを指定します。
最初は組み込みフォント(Helvetica, Times-Roman など)で十分ですが、
日本語をちゃんと出したい場合は後で触れる「日本語フォントの設定」が重要になります。

ページと保存
showPage() でページを確定し、save() でファイルに書き込みます。
ページを増やすときは、描画 → showPage → 次のページ描画、という流れです。


日本語 PDF の重要ポイント(フォント埋め込みをちゃんとやる)

なぜ日本語が「□」や「?」になるのか

reportlab の組み込みフォントは基本的に英数字用で、日本語には対応していません。
そのため、上のサンプルのように直接日本語を書こうとすると、文字化けしたり出なかったりします。

日本語を PDF に綺麗に出したい場合は、

  • 日本語フォント(TTF など)を用意する
  • reportlab にそのフォントを登録する

というステップが必要です。

TrueType フォントを登録して使う例

手元の OS に入っている日本語フォントを使う例です(フォントファイルのパスは環境に合わせて変えてください)。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "output"
OUT_DIR.mkdir(exist_ok=True)

# 例: Windows の Meiryo (環境に合わせてパスを変更)
FONT_PATH = r"C:\Windows\Fonts\meiryo.ttc"  # Mac や Linux では別のフォントに

def register_japanese_font():
    pdfmetrics.registerFont(TTFont("Meiryo", FONT_PATH))

def create_jp_report():
    register_japanese_font()
    out_path = OUT_DIR / "jp_report.pdf"

    c = canvas.Canvas(str(out_path), pagesize=A4)
    width, height = A4

    c.setFont("Meiryo", 18)
    c.drawString(72, height - 72, "日本語タイトル:月次売上レポート")

    c.setFont("Meiryo", 12)
    text = c.beginText(72, height - 110)
    lines = [
        "このPDFは Python と reportlab で自動生成されています。",
        "日本語フォントを登録することで、本文も日本語で出力できます。",
        "",
        "・店舗別の売上",
        "・カテゴリ別の売上",
        "・前月比などを出力可能です。"
    ]
    for line in lines:
        text.textLine(line)
    c.drawText(text)

    c.showPage()
    c.save()
    print(f"作成しました: {out_path}")

if __name__ == "__main__":
    create_jp_report()
Python

ここで深掘りしたいポイントは、次の2つです。

フォントファイルの場所をちゃんと指定すること
Windows/Mac/Linux でフォントの場所が違います。
「実際に使っている環境でのフォントファイルパス」を調べて、変数に入れます。

フォント名を自分で決めて登録し、その名前で setFont すること
registerFont(TTFont("Meiryo", FONT_PATH)) の第1引数が「PDF 内でのフォント名」になります。
setFont("Meiryo", 12) と書くことで、そのフォントを使えます。


表形式のレポート(売上表や一覧を PDF に描く)

表を「線とテキスト」で描画するイメージ

Excel 的な表を PDF に出したい場合、基本は「線を引いて、その中に文字を描く」です。
reportlab には簡易的な Table 機能もありますが、まずは素直に座標で描いてみましょう。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors

def draw_table_example():
    out_path = OUT_DIR / "table_report.pdf"
    c = canvas.Canvas(str(out_path), pagesize=A4)
    width, height = A4

    # フォント(日本語フォントを登録済みと仮定)
    c.setFont("Meiryo", 10)

    x0, y0 = 72, height - 150  # 表の左上の起点
    col_widths = [80, 80, 80]  # 列幅
    row_height = 18

    headers = ["店舗", "カテゴリ", "売上"]
    rows = [
        ["東京", "A", "100,000"],
        ["大阪", "A", "80,000"],
        ["東京", "B", "50,000"],
        ["大阪", "B", "60,000"],
    ]

    # ヘッダー行の背景色
    c.setFillColor(colors.lightgrey)
    c.rect(x0, y0 - row_height, sum(col_widths), row_height, fill=1, stroke=0)

    c.setFillColor(colors.black)
    # ヘッダー文字
    x = x0 + 4
    for i, h in enumerate(headers):
        c.drawString(x, y0 - row_height + 4, h)
        x += col_widths[i]

    # データ行
    y = y0 - 2 * row_height
    for row in rows:
        x = x0 + 4
        for i, val in enumerate(row):
            c.drawString(x, y + 4, str(val))
            x += col_widths[i]
        y -= row_height

    # 枠線(縦横)
    c.setLineWidth(0.5)
    total_rows = 1 + len(rows)
    table_height = total_rows * row_height

    # 外枠+縦線
    x = x0
    c.rect(x0, y0 - table_height, sum(col_widths), table_height, fill=0)
    for i in range(1, len(col_widths)):
        x += col_widths[i-1]
        c.line(x, y0, x, y0 - table_height)

    # 横線
    y = y0 - row_height
    for _ in range(total_rows - 1):
        c.line(x0, y, x0 + sum(col_widths), y)
        y -= row_height

    c.showPage()
    c.save()
    print(f"作成しました: {out_path}")
Python

ここで意識したいのは「表の起点 (x0, y0) と、行高さ・列幅さえ決めれば、あとはループで描ける」ということです。
この考え方に慣れれば、pandas で作った DataFrame をループして、行数分だけ PDF に書いていけます。


テンプレ+データ差し込みで「請求書」「見積書」を自動生成する流れ

テンプレに必要な考え方

請求書・見積書のような帳票は、だいたいレイアウトが決まっています。

  • 左上に発行日・番号
  • 右上に宛先情報
  • 中央に明細表
  • 下部に合計・備考

この「位置は固定で、値だけ差し替え」というのは PDF 自動生成と非常に相性が良いです。

考え方としては、

  1. レイアウトを紙に書き出し(または Excel で決め)、座標を決める
  2. 「データ構造(dict)」として請求書情報を持つ
  3. その dict の値を、指定の位置に描画する関数を書く

という3ステップです。

シンプルな請求書テンプレ+データ差し替え例

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "output"
OUT_DIR.mkdir(exist_ok=True)

FONT_PATH = r"C:\Windows\Fonts\meiryo.ttc"  # 環境に合わせて変更

def register_font():
    pdfmetrics.registerFont(TTFont("Meiryo", FONT_PATH))

def generate_invoice(data, out_path):
    register_font()
    c = canvas.Canvas(str(out_path), pagesize=A4)
    width, height = A4
    c.setFont("Meiryo", 11)

    # タイトル
    c.setFont("Meiryo", 18)
    c.drawCentredString(width / 2, height - 72, "請求書")

    c.setFont("Meiryo", 11)

    # 発行日・請求書番号
    c.drawString(72, height - 110, f"発行日: {data['issue_date']}")
    c.drawString(72, height - 130, f"請求書番号: {data['invoice_no']}")

    # 宛先
    c.drawString(300, height - 110, f"{data['customer_name']} 御中")
    c.drawString(300, height - 130, data["customer_address"])

    # 明細テーブル
    x0, y0 = 72, height - 200
    col_widths = [220, 60, 80, 80]  # 品名, 数量, 単価, 金額
    row_height = 18

    headers = ["品目", "数量", "単価", "金額"]

    # ヘッダー
    c.setFont("Meiryo", 11)
    c.rect(x0, y0 - row_height, sum(col_widths), row_height, fill=0)
    x = x0 + 4
    for i, h in enumerate(headers):
        c.drawString(x, y0 - row_height + 4, h)
        x += col_widths[i]

    # 明細
    y = y0 - 2 * row_height
    total = 0
    for item in data["items"]:
        x = x0 + 4
        c.drawString(x, y + 4, item["name"]);   x += col_widths[0]
        c.drawRightString(x - 4, y + 4, str(item["qty"]));   x += col_widths[1]
        c.drawRightString(x - 4, y + 4, f"{item['unit_price']:,}"); x += col_widths[2]
        amount = item["qty"] * item["unit_price"]
        c.drawRightString(x - 4, y + 4, f"{amount:,}")
        total += amount
        y -= row_height

    # 合計
    c.drawRightString(x0 + sum(col_widths) - 4, y - 10, f"合計: {total:,} 円")

    c.showPage()
    c.save()

def example_invoice():
    invoice_data = {
        "issue_date": "2025-01-31",
        "invoice_no": "INV-2025-001",
        "customer_name": "株式会社サンプル",
        "customer_address": "東京都〇〇区△△ 1-2-3",
        "items": [
            {"name": "サービスA", "qty": 10, "unit_price": 5000},
            {"name": "サービスB", "qty": 5,  "unit_price": 8000},
        ]
    }

    out_path = OUT_DIR / "invoice_sample.pdf"
    generate_invoice(invoice_data, out_path)
    print(f"請求書を作成しました: {out_path}")

if __name__ == "__main__":
    example_invoice()
Python

ここでのポイントは、

  • レイアウトを「座標と幅」でガチっと決めてしまう
  • 請求書の内容を invoice_data という dict で表現して、描画ロジックと分離する
  • dict を差し替えるだけで何通でも請求書が作れる

という構造にしていることです。

この設計にしておくと、データベース(顧客や請求情報)から invoice_data をループで作って、
全件分の PDF を一気に生成する、というようなバッチに育てられます。


自動化の観点(バッチ化・定期実行・ファイル名設計)

「1回分の PDF 生成処理」を関数に閉じ込める

ここまでの例で共通しているのは、「PDF 生成の中身を関数にしている」ことです。

  • create_simple_report()
  • create_jp_report()
  • generate_invoice(data, out_path)

この形さえできていれば、

  • コマンドラインから python make_report.py
  • cron / タスクスケジューラから毎日実行

といった自動化がとても簡単になります。

ファイル名のルールを最初に決めておく

PDF の自動生成では、ファイル名・フォルダ名のルールを最初に決めておくことが重要です。

  • 日付入り(report_YYYYMMDD.pdf
  • 顧客名やID入り(invoice_顧客ID_YYYYMM.pdf
  • 古いレポートと新しいレポートを区別できるようにする

などを意識すると、後から探しやすくなります。

datetime や ID を使って、必ず一意になる名前を生成するのが定番です。


まとめ(「キャンバスに座標で描く」と「テンプレに差し込む」を型にする)

Python での PDF 自動生成のポイントをまとめると、こうなります。

  • reportlab で PDF を「キャンバス」として扱い、座標に文字や線を描くイメージを掴む
  • 日本語を出したいときは、必ず日本語フォント(TTF など)を登録してから使う
  • 表や帳票は、「起点座標+行高さ+列幅」を決めておき、ループで描画する
  • 請求書などは「データ構造(dict)」と「描画関数」を分離し、テンプレ化する
  • 1回分の処理を関数にしておけば、バッチ化・定期実行との連携が簡単

この型を一度体に入れてしまえば、

  • 日次レポートPDF
  • 月次の顧客別請求書一括生成
  • 分析結果の PDF レポート集約

などを、今ある Excel や手作業から徐々に Python へ置き換えていくことができます。

タイトルとURLをコピーしました