期末に備えて爆速でExcel書類を作成する(PythonによるExcel操作の自動化)

# 結論から先に言うと。

Excelで定型書類作成の手間が膨大だったので、 Pythonと `pywin32` ライブラリで自動化した。

# ことの始まり

こんにちは。京セラコミュニケーションシステムの徳丸です。( @kccs_masataka-tokumaru )

今年も、ほぼほぼ半分が過ぎ去りましたね。この時期には事務作業に忙殺されていた方もいらっしゃるのではないでしょうか。

普段、あまり事務作業の多くない私でも、この時期にはぼちぼち事務作業が増え忙しいことがままありました。

特に、同じような定型書類をいくつも作ったりするシーンが多く、大変だった記憶があります。

主にExcelに必要事項を記入していくことが多いのですが、Excelでの事務作業、なかなかに厄介です。

私が実施していた事務作業は次の通りでした。

# この記事の狙い・対象者

独力でPythonのコードが書ける方。

Excelを自動操作することで、事務作業を効率的に行えるようにする例を示す。

# この記事でやらないこと

ライブラリの細かい引数や、使い方の説明。

# 作業の概要

複数の他社向けに、書類を作成する必要があります。

書類の形式はExcelです。

どの会社にも、定型書式で書類を作成するのですが数が多く、1社あたり4～5種類の書類があります。

書類は作成後、レビューに通されレビューの指摘があると修正を行わなければいけません。

共通した指摘であれば、複数社にまたがって修正する必要があります。

<img width="800px" alt="現在のフロー" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/174efc69-7621-8d51-6099-a0dbed13fe69.png">

# この作業の辛い所

書類の作成自体は楽なのですが、辛い所は「本当に手数が多い」ところです。

- 同名ファイルを同時に開けないのでファイルの開閉に時間がかかる
- 操作ミスで、不要なセルを操作してしまわないように集中力を要する
- 一つの修正を他の書類に適用するのにまとめて適用できない
- 複数ファイルの内容に間違いが無いか確認する必要がある
- 見た目が似ているので、画面を切り替えているうちに編集ミスをしやすい

などなど、結構なつらみがあります。

特に、1つの修正を全社に渡って適用しないといけない場合は、ファイルを開いて閉じて開いて閉じて開いて閉じて開いて閉じて開いて閉じて開いて閉じて開いて閉じて修正漏れが無いか確認して、開いて閉じて開いて閉じて開いて閉じて開いて閉じて。。。。~~ラジオ体操か？~~

と、気力をだいぶ奪われる状況でした。

<img width="200px" alt="混乱する人" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/d7f7699e-3319-a886-b8be-9987b3596c75.png">

また、期限まで時間が無いことも多々あり、スピードが求められます。

# 改善してみよう

そもそも、書類をExcelで書くな。などありますが、与えられた書式がExcelであることはままありますよね。

しかしさすがに、手作業でそのままやってられません。

ということで、プログラミングで解決することにしました。

# 改善案

まずは、解決したいことを考えてみます。

- Excel操作を簡単にできるようにしたい
- 複数のファイルを開かなくても、記入する内容を一望して確認できるようにしたい
- 複数のファイルを開かなくても、記入する内容をまとめて変更できるようにしたい

とにかく、Excelを直接触らずに記入内容は俯瞰して管理できるようにしたい感じですね。

そこで、下記のようなフローを作成しました。

- 共通した書式自体は、テンプレートとして作成しておく
- 書類に入力する内容は `json` 形式の1ファイルで管理できるようにする
- jsonに記入した内容を、Pythonでテンプレートに反映していき、会社ごとに別名で保存する

<img width="800px" alt="改善後のフロー" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/8c846cce-7c0c-a331-1ec7-aad58c207b14.png">

jsonが見やすいのかどうか。

というところもありますが、一旦はテキストエディタで一括編集が可能なので良さそうです。

使いにくいようでしたらフォーマットを見直すということで。。。。

# サンプル書類について

とりあえず、例としてサンプル書類を用意しました。

挨拶などはChat-GPTに考えてもらいました。１つのExcelファイルに下記の2シートがあるとします。

<img width="800px" alt="テンプレートの書類1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/7fc0c172-2aaa-95ac-afe7-a4d0d5d04494.png">

シート1

<img width="800px" alt="テンプレートの書類2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/927e7078-909c-13f3-00fa-fc8469fbf570.png">

シート2

この書類の内容は実在の人物や団体などとは関係ありません。

こんな感じに、複数シートあるExcelファイルがあって、フォーマットは決まっていて、必要事項を入力していく形ですね。


今回はこのExcelファイルをテンプレートにします。

担当者印は、予めテンプレートに貼っておき、PDFに出力しても問題ないように、改ページの設定は事前に行っておきます。

# 入力したい内容を整理する。

## サンプル書類1に入力したい内容

サンプル書類１に入力したい内容は次の通りです。

- 作成日
- 取引先の会社名
- 発注内容
- 発注金額
- 担当者名
- 担当社員

挨拶の文面は今回は同じとします。

## サンプル書類2に入力したい内容

サンプル書類2に入力したい内容は次の通りです。

- 作成日
- 取引先の会社名
- 契約形態
- 担当者名
- 担当社員

契約形態に関する注意事項は、今回はすべて同じとします。

## 必要な入力内容を整理する

上記を基に、必要な入力内容を整理するとこんな感じになります。

- 作成日
- 相手先の会社名
- 発注内容
- 発注金額
- 契約形態
- 担当者名

# 整理した入力内容をjson形式にする

これをjsonに落とし込むとこんな感じになります。

書類ごとに管理しやすいようにファイル名のサフィックスを振っておきましょう。
```json
[
    {
        "ファイル名サフィックス": "XXXXXX株式会社_機器メンテナンス",
        "作成日": "2024年06月24日",
        "取引先の会社名": "XXXXXX株式会社",
        "発注内容": "機器のメンテナンス",
        "発注金額": "\\3,000,000",
        "契約形態": "請負契約",
        "担当者名": "担当 太郎"
    }
]
```

これが複数社向けに増えるとこんな感じになります。

```json
[
    {
        "ファイル名サフィックス": "XXXXXX株式会社_機器メンテナンス",
        "作成日": "2024年06月24日",
        "取引先の会社名": "XXXXXX株式会社",
        "発注内容": "機器のメンテナンス",
        "発注金額": "\\3,000,000",
        "契約形態": "請負契約",
        "担当者名": "担当 太郎"
    },
    {
        "ファイル名サフィックス": "YYYYYY株式会社_▲▲▲▲システムの開発",
        "作成日": "2024年06月24日",
        "取引先の会社名": "YYYYYY株式会社",
        "発注内容": "▲▲▲▲システムの開発",
        "発注金額": "\\10,000,000",
        "契約形態": "準委任契約",
        "担当者名": "担当 太郎"
    }
]
```

配列の要素が1個ずつ増える感じですね。

# Pythonでjsonの内容をテンプレートに反映させる

このjsonを基にテンプレートに反映させていって、別名Excelで保存するPythonコードを書きます。

`pywin32` ライブラリを使用してWindowsのAPIを叩いて、Excelを操作します。

このライブラリを使用することで、ExcelをPythonで操作できるようになり、セルに指定した内容を入力させたり、ファイルを保存したり、PDFに出力することができるようになります。

https://pypi.org/project/pywin32/

以下のコマンドでライブラリをインストールします。

```bash
pip install pywin32
```

ライブラリを読み込み、実装していきます。

1つ１つ説明していくと全体が見通しづらいので、以下にコメント付きで説明します。

```python
import json
import os
import json
import win32com.client

def load_json(path):
    """
    テンプレートに適用するインプットとなるjsonファイルを読み込む。
    """
    with open(os.path.abspath(path), 'r', encoding='utf-8') as fp:
        return json.load(fp)

def open_excel_template(excel_client, path):
    """
    指定されたExcelファイルをテンプレートとして読み込む
    """
    return excel_client.Workbooks.Open(os.path.abspath(path))

def select_sheet(excel_file, sheet_name):
    """
    指定されたシートを選択する。
    """
    excel_file.WorkSheets(sheet_name).Activate()
    return  excel_file.WorkSheets(sheet_name)


def set_value_to_cell(sheet, index, val):
    """
    指定されたシートの番地(index)に、valの内容を入力する
    """
    sheet.Range(index).Value = val

def generate_pdf(excel_file, target_sheets, pdf_file_name):
    """
    target_sheets で、指定されたシートを対象にPDFを出力する。
    """
    excel_file.Worksheets(target_sheets).Select()
    excel_file.ActiveSheet.ExportAsFixedFormat(0, os.path.abspath(f'{pdf_file_name}.pdf'))

def save_and_close(excel_file, new_file_name):
    """
    別名でExcelファイルを保存し、現在開いているExcelは閉じる。
    """
    excel_file.SaveAs(os.path.abspath(f'{new_file_name}.xlsx'))
    excel_file.Close(SaveChanges=0)

def main():
    """
    メイン処理
    """

    # テンプレートに使うExcelのパス
    TEMPLATE_FILE_PATH = "./template.xlsx"

    # テンプレートに入力する内容を記したJSONのパス
    INPUT_JSON_PATH ="./input.json"

    # Win32API経由でExcelを操作するクライアントを生成する
    excel_client = win32com.client.Dispatch("Excel.Application")
    excel_client.Visible = True

    # JSONを読み込む
    try:
        doc_inputs = load_json(INPUT_JSON_PATH)
        for doc_input in doc_inputs:
            print(f"サンプル依頼書_{doc_input['ファイル名サフィックス']}のExcelとPDFを作成します。")
            # テンプレートのExcelファイルを開く
            template_excel_file = open_excel_template(excel_client, TEMPLATE_FILE_PATH)

            # 「サンプル書類1」のシートを選択する
            current_sheet = select_sheet(template_excel_file, "サンプル書類1")

            # 現在選択しているシートの「G4セル」(作成日)に、jsonで予め入力しておいた内容を入力する
            set_value_to_cell(current_sheet, "G4", doc_input["作成日"])
            # 以下同様に指定したセルにjsonの内容を入力していく
            set_value_to_cell(current_sheet, "F5", doc_input["取引先の会社名"])
            set_value_to_cell(current_sheet, "D8", doc_input["発注内容"])
            set_value_to_cell(current_sheet, "D9", doc_input["発注金額"])
            set_value_to_cell(current_sheet, "D27", doc_input["担当者名"])

            # 「サンプル書類2」のシートに切り替える
            current_sheet = select_sheet(template_excel_file, "サンプル書類2")

            # 同様に、「サンプル書類2」シートの指定したセルにjsonの内容を入力していく
            set_value_to_cell(current_sheet, "G4", doc_input["作成日"])
            set_value_to_cell(current_sheet, "F5", doc_input["取引先の会社名"])
            set_value_to_cell(current_sheet, "D8", doc_input["契約形態"])
            set_value_to_cell(current_sheet, "D27", doc_input["担当者名"])

            # 作成したExcelをPDFで出力する
            generate_pdf(template_excel_file,  ["サンプル書類1", "サンプル書類2"], f"./サンプル依頼書_{doc_input['ファイル名サフィックス']}")

            # 作成したExcelを別名で保存する
            save_and_close(template_excel_file, f"./サンプル依頼書_{doc_input['ファイル名サフィックス']}")
            print(f"サンプル依頼書_{doc_input['ファイル名サフィックス']}のExcelとPDFを作成しました。")
    finally:
        # Excelを終了する
        excel_client.Quit()

if __name__ == "__main__":
    main()

```

# 動作確認

実際に実行してみます。
```bash
python sample_code.py
```

実行結果
```
サンプル依頼書_XXXXXX株式会社_機器メンテナンスのExcelとPDFを作成します。
サンプル依頼書_XXXXXX株式会社_機器メンテナンスのExcelとPDFを作成しました。
サンプル依頼書_YYYYYY株式会社_▲▲▲▲システムの開発のExcelとPDFを作成します。
サンプル依頼書_YYYYYY株式会社_▲▲▲▲システムの開発のExcelとPDFを作成しました。
```

ファイルが無事作成され


<img width="800px" alt="出力した書類の画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/2c892b32-904e-c821-c7ba-c10802b86205.png">

Excel、PDFが出力されました。

<img width="800px" alt="出力した書類の画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/a17ba396-c22e-3958-f2c6-c8161bcb8994.png">

<img width="800px" alt="出力した書類の画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/e32e31d5-1ab0-d411-584c-7e1759ac9fd3.png">

<img width="800px" alt="出力した書類の画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/56760996-0424-7ebb-de4e-70d13fe951d4.png">

<img width="800px" alt="出力した書類の画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809400/503419a2-4379-6094-62b8-6565aadce9a5.png">

内容もjsonの内容がしっかりと適用されているようです。

# 終わりに

無事jsonから複数のExcel書類を出力することができました。

jsonで入力事項がまとまっていると、書類に記載したい内容が一望できてチェックしやすいですし、横断的に内容を編集できて便利ですね。

定期的に実施するような作業でしたら、1回作っておけばjsonを書いて実行するだけで書類が作成されるのでとても便利で、ミスも減りそうです。

大量の降りかかってくる事務作業を乗り切りましょう！

ここまでご覧くださいまして、ありがとうございました！