PR

PythonのforループでExcelから請求書を連続PDF出力|ファイル名の重複回避とtry-finallyの例外処理【第2回】

Python

Invoice Maker 第2回

スポンサーリンク

はじめに:1枚できれば100枚できる

こんにちは、ニンジンです🥕

前回(第1回)では、Pythonで「1枚の請求書」をExcelから自動作成してPDF出力する基本ロジックを解説しました。ですが実務では、月末に数十枚〜数百枚の請求書を作ることになります。

この記事では、前回のコードを for ループ で拡張し、受注管理表に並んだ複数行のデータを次々と読み込んで一気に大量のPDFを生成します。あわせて、実務でツールを動かすなら必ず知っておきたい「ファイル名の重複回避」と「例外処理(try-finally)」も解説します。

この記事で分かること

  • for i in range() でExcelの行を1行ずつずらしながら読み込む書き方
  • f-string でファイル名を動的に作り、上書き(重複)を防ぐ方法
  • try...finally で、エラー時もExcelのプロセスを確実に終了させる方法
  • 6件のサンプルデータから、6枚のPDFを一括生成するテストコード(全文)
PR
Invoice Maker NINJIN EDITION のGUI画面

「自分でコードを書く時間がない」「社内の誰でもクリックだけで使える完成品が欲しい」という方に向けて、本シリーズの最終形であるGUI搭載のフルパッケージ版 Invoice Maker (NINJIN EDITION) をnoteで頒布しています。

noteで見る →

スポンサーリンク

連続一括処理の事前準備

準備1:サンプルExcelファイル

お手持ちの管理表と書類フォーマットを使うか、下のサンプルを使ってください(第1回と同じファイルです)。

  • 受注管理表_Sample.xlsx
  • 請求書_Sample.xlsx
ダウンロードリンクはWordPress側のファイルブロックで設置してください(第1回と同じファイル)。

準備2:フォルダ構成とPython環境

同じフォルダの中に、次の3つを置きます。

  • テストコード(この記事のコード)
  • 受注管理表(Excel)
  • 請求書の雛形(Excel)
VS Codeで作業フォルダに3つのファイルを配置した状態

3ファイルを同じフォルダに入れた状態です。Pythonのコードはこのフォルダを基準に相対パスでExcelを読みます。

Pythonの実行環境がまだの方は、VS CodeとPythonの環境構築を先に済ませてください。

スポンサーリンク

複数データを一括処理するPythonスクリプト(全文)

連続処理に対応させたスクリプトの全体です。ファイルパスやシート名は自分の環境に合わせて変更してください。1行ずつコメントを付けてあります。

# invoice_maker_test02.py
# 受注管理表の複数行からデータを読み込み、請求書の雛形に書き込んでPDF化するサンプルコード
# invoice_maker_test01.pyをベースに、for文で複数行を処理するように拡張したコードです

import openpyxl
import win32com.client
import os

# 1. 設定
KANRI_PATH = "受注管理表_Sample.xlsx"   # 変数KANRI_PATHに受注管理表のExcelファイルのパスを指定
HINAGATA_PATH = "請求書_Sample.xlsx"    # 変数HINAGATA_PATHに請求書の雛形となるExcelファイルのパスを指定

# Excelアプリケーションの起動(ループの外で行うと高速です)
excel = win32com.client.Dispatch("Excel.Application")   # DispatchでExcelアプリケーションを起動
excel.Visible = False          # Excelのウィンドウを表示しない
excel.DisplayAlerts = False    # 警告ダイアログを表示しない
excel.ScreenUpdating = False   # 画面更新を停止(より徹底する場合)

try:    # エラー発生時もExcelを確実に終了させるためにtryブロックを使用
    wb_kanri = openpyxl.load_workbook(KANRI_PATH, data_only=True)   # KANRI_PATHを開く data_only=Trueで数式の結果を値で取得
    ws_kanri = wb_kanri["Sheet1"]   # シート名を指定してシートを取得

    # 2. for文で4行目から9行目まで繰り返す
    # range(4, 10) は 4, 5, 6, 7, 8, 9 までを指します
    for i in range(4, 10):
        try:    # for文の中でエラーが起きても次の行に進むように、ここでもtryブロックを使用
            # 行番号(i)を使って各データを取得
            order_id     = ws_kanri[f"A{i}"].value # 受注番号 .valueでセルの値を取得
            company_name = ws_kanri[f"C{i}"].value # 会社名 
            s_name       = ws_kanri[f"F{i}"].value # 商品名
            count        = ws_kanri[f"G{i}"].value # 数量
            amount       = ws_kanri[f"H{i}"].value # 単価

            print(f"--- 処理中({i}行目): {company_name} 様 ---")

            # 3. 雛形へ書き込み
            wb_hina = openpyxl.load_workbook(HINAGATA_PATH) #HINAGATA_PATHを開く
            ws_hina = wb_hina["Sheet1"] # シート名を指定してシートを取得
            ws_hina["A5"] = company_name    #HINAGATA_PATHのA列5行目に会社名を書き込み
            ws_hina["B18"] = s_name     #HINAGATA_PATHのB列18行目に商品名を書き込み
            ws_hina["L18"] = amount     #HINAGATA_PATHのL列18行目に金額を書き込み
            ws_hina["J18"] = count      #HINAGATA_PATHのJ列18行目に数量を書き込み

            # 一時ファイルのパス(会社名を含めて重複を避ける)
            temp_excel = os.path.abspath(f"temp_{order_id}.xlsx")   #abspathで絶対パスを取得 order_idを使って一時ファイルの名前を個別化する
            wb_hina.save(temp_excel)    # 保存してからPDF化する必要があるため、一時的なExcelファイルを作成

            # 4. PDF化処理
            output_pdf = os.path.abspath(f"請求書_{order_id}_{company_name}.pdf")   # 出力するPDFのパスを作成(会社名と受注番号を含めて重複を避ける)
            doc = excel.Workbooks.Open(temp_excel)  # Openで先ほど保存した一時的なExcelファイル(temp.xlsx)を開く
            doc.ExportAsFixedFormat(0, output_pdf)  # ExportAsFixedFormatでPDF形式で保存。第1引数は0でPDF、第2引数は保存先のパス
            doc.Close(False)    # Closeでtemp.xlsxを閉じる。引数は変更を保存するかどうか(Falseで保存しない)

            # 一時ファイルの削除
            os.remove(temp_excel)   # 一時的なExcelファイルを削除
            print(f"成功: {output_pdf}")

        except Exception as e:  # エラーが発生しても次の行に進むための例外処理
            print(f"【失敗】 {i}行目の処理でエラー: {e}")
        finally:
            # ループ内での後片付け(必要に応じて)
            pass    # 今回は特に後片付けはないですが、ここでExcelの設定をリセットすることもできます

finally:
    # 5. Excelを完全に終了
    excel.Quit()
    print("すべての処理が完了しました。")

実行結果:6件分のPDFが生成されれば成功

ターミナルに6件の処理完了ログが表示された状態

VS Codeのターミナルに6件分の処理完了が表示されれば成功です。

フォルダ内に6件のPDFが生成された状態

コードと同じフォルダに、6件分のPDFファイルができています。


スポンサーリンク

実務でトラブルを起こさないための3つの技

① for i in range() で行を1つずつずらす

for i in range(4, 10): と書くと、変数 i に 4・5・6・7・8・9 が順に入り、下のブロックが繰り返されます。ws_kanri[f"C{i}"] のように書けば、1周目は C4、2周目は C5 と、自動で行を下にずらしながらデータを取得できます。

② f-string でファイル名の重複(上書き)を防ぐ

前回はファイル名を "請求書_出力.pdf" で固定していました。これをループでやると、100回すべて同じ名前に上書きされ、最後の1社分しか残りません。

output_pdf = f"請求書_{order_id}_{company_name}.pdf"

このように書くと「請求書_A001_株式会社〇〇.pdf」のように1件ずつ個別のファイル名になり、上書きされません。

③ try…finally でExcelのゴーストプロセスを防ぐ

PythonやVBAでExcelを裏側から操作するとき、いちばん厄介なのが「エラーで途中停止したのに、裏でExcelが起動したまま残る現象(ゴーストプロセス)」です。溜まるとPCが重くなります。

try:
    # メインのループ処理
finally:
    excel.Quit() # 絶対にExcelを終了させる!
  • try: の中にメインの処理を書きます。
  • finally: の中に「エラーが起きても起きなくても、必ず最後に実行する処理」を書きます。

この形にしておけば、途中で予期せぬエラーが出ても、裏側のExcelは確実に閉じられます。


スポンサーリンク

まとめ:裏側の処理は完成、次は「操作画面」

このスクリプトを実行すると、指定した行のデータが順に読み込まれ、次々とPDFが生成されます。裏側の処理はこれで完成です。

ただし実務では、毎月処理する行数が変わります。「今月は4行目から50行目まで」となるたびに、コードの range(4, 51) を書き換えるのは、事務スタッフにとってハードルが高く、コードを壊すリスクもあります。

そこで次回・第3回では、CustomTkinterで「誰でもクリックと文字入力だけで使える操作画面(GUI)」を作り、ツールとして仕上げます。

シリーズの記事

🧾 前の記事・第1回 PythonでExcelをPDFに変換する方法|openpyxlとpywin32で請求書を自動作成 1枚の請求書をExcelから自動生成してPDF出力する、基本のロジック。 🖥️ 次の記事・第3回 CustomTkinterでPythonツールをGUIアプリ化する方法|filedialogでファイル選択・クラスで設計 黒い画面を卒業して、誰でも使えるGUI搭載ツールに仕上げます。 📚 シリーズまとめ PythonでExcel請求書を自動PDF化するツールの作り方|openpyxl・pywin32・CustomTkinter【全3回まとめ】 全3回の内容とよくある質問をまとめたハブ記事です。
PR

「次回の解説まで待てない」「環境構築が面倒だから今すぐツールが欲しい」という方は、noteで完成版のGUIアプリ Invoice Maker (NINJIN EDITION) を頒布中です。ダウンロード後すぐに起動して使えます。

Invoice Maker (NINJIN EDITION) をnoteで確認する →

スポンサーリンク

よくある質問(FAQ)

Qrange(4, 10) はなぜ10まで書くのに9で止まるのですか?

APythonのrange(開始, 終了)は「終了の手前まで」という仕様です。range(4, 10)は4〜9の6個。Excelの4行目から9行目までを処理したいときは10と書きます。

QExcelのファイルがPDF化されず、エラーになります。

APDF変換部分(ExportAsFixedFormat)はMicrosoft ExcelがインストールされたWindowsでのみ動きます。Excelが入っていない環境やMacでは動きません。また、対象のExcelを開いたままだとロックされて失敗します。

Q処理の途中でエラーが出ても、残りの行は処理してほしいです。

Aこのコードはfor文の中にtry...exceptを入れてあるので、ある行でエラーが出ても【失敗】と表示して次の行に進みます。全体を止めたい場合はそのtryを外します。

Qdata_only=True は何のためにありますか?

A受注管理表に数式(合計金額など)が入っている場合、openpyxlは既定だと数式の文字列(=SUM(...))を返します。data_only=Trueを付けると、Excelが最後に計算した「結果の値」を読み込みます。

QExcelのゴーストプロセスが既に溜まっている場合はどうすればいいですか?

Aタスクマネージャーの「詳細」タブでバックグラウンドのEXCEL.EXEを探して終了するか、PCを再起動します。今回のtry...finallyを入れておけば、以降は溜まりません。

Qnoteで配布しているツールと、この記事のコードは何が違いますか?

Aこの記事のコードは「行番号を直接書き換える」前提の学習用です。配布版(Invoice Maker NINJIN EDITION)はGUIで行範囲やファイルを画面から指定でき、コードを触らずに使えます。


ニンジン🥕アイコン

Follow me on X

本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕

@ninjin_py_vba をフォロー

この記事が役に立ったら、シェアしてもらえると嬉しいです🥕

Xでポスト はてブ LINE Pocket

PROJECT LOG — SERIES INDEX
スポンサーリンク

シリーズ記事一覧

電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。


コメント