Pythonでエクセル操作の自動化してみた

この記事は [**セゾンテクノロジー Advent Calendar 2024**](https://qiita.com/advent-calendar/2024/saison-tech) 6日目の記事です。
シリーズ2は [**HULFT10**](https://www.hulft.com/software/hulft-10) のエンジニアによる投稿をお届けします。



# はじめに
初めまして、セゾンテクノロジーの齋藤です。
今回はHULFT10リリース記念アドベントカレンダーとのことで、新人の身ですが記事を書こうと思います。

昨年10月に別業種から転職をしたばかりで、エンジニア歴は2年目になります。技術的な間違いや指摘事項などあるかもしれませんが、どうかご容赦ください。



# どんな記事を書くのか

PythonでExcel作業の自動化について書こうと思います。
今回のプロジェクトの中で、膨大な量のExcel作業のタスクがありました。手作業で行うには気が遠くなるほどのボリュームで、なおかつ単調な作業だったので自動化するプログラムを作成してみました。
備忘録的な魂胆もありつつ、記事を書けたらと思います。

:::note warn
"**HULFT10リリース記念アドベントカレンダー**"とか言いながらHULFTとあまり関係のない記事を書こうとしてますが、新人なので大目に見てください。
:::



# 必要なもの
まずはPCを用意してください。

それから用意したPCにテキストエディターとPythonをインストールしてください。

テキストエディターはVSCodeがおすすめです。無料で使えて拡張性も高く、便利な拡張機能も豊富です。Pythonは[公式サイト](https://www.python.org/downloads/)からインストーラーをダウンロードして実行すれば完了です。

上記が準備できたら、次にライブラリのインストールです。
今回はエクセルを操作するライブラリとしてpandasを使用しました。
先ほどのPythonのインストールと同時にpipコマンドもインストールされているはずなので、
```bash
pip install pandas
```
を実行すれば、インストール可能です。





# 実際にプログラムを書いてみる

### エクセルファイルの読み込み
```python
# エクセルファイルの読み込み、withとセットで使うかclose処理を行う
excel = pandas.ExcelFile('failname')
# シート名のリスト取得
sheet_list = excel.sheet_names
# 先頭のシートのデータを取得
dataframe = excel.parse(sheet_list[0])
```

ファイル名はパスを指定してください。
また、取得したデータはDataframeというデータ型で返されます。

--------------------------------

### データを加工する
DataFrameはエクセルのシートをそっくりそのまま格納しているイメージです。
DataFrame.ilocとDataFrame.locで参照できます。
```python
# DataFrameイメージ 
    ID     Name  Age          City   Gender       job   Height
1  101    Alice   25          Tokyo  Female  Engineer      165
2  102      Bob   30       New York    Male    Doctor      180
3  103  Charlie   35  San Francisco    Male    Artist      170

# 行と列をインデックスで指定
dataframe.iloc[0, 1] # Alice が取得できる

# ラベルで指定できる
dataframe.loc[1, 'Name'] # Alice が取得できる
# locは条件検索なども可能
dataframe.loc[DataFrame['Age'] >= 30]

# 指定した列の削除
dataframe.drop(columns='Height')
dataframe.drop(index=4)

# 指定した順番に列を並び替え
dataframe[['ID', 'Name', 'City', 'job', 'Age']]

# シート同士の結合
combined_dataframe = pandas.concat([dataframe1, dataframe2]):

# データの欠損をチェック
pandas.isnull(dataframe)

# 反復処理、for文と組み合わせる
dataframe.iterrows()
for index, row in dataframe.iterrows()
```



この他にもDataFrameはいろいろな操作が可能です。
ググるなりAIに聞いてみるなりするといいことがあるかもしれません。

ちなみにAIに聞いてみたら、以下の答えが返ってきました。

:::note info
データの作成
データの表示
列の選択
行の選択
条件によるフィルタリング
新しい列の追加
列の削除
行の削除
欠損値の処理
集約と統計量の計算
データのソート
インデックスの設定とリセット
データの結合とマージ
データのエクスポート
データの変形
時系列データの操作
文字列操作
データのグルーピング（groupby）
ピボットテーブルの作成
データの結合（concatやmerge）
:::

割となんでもできそうです。

---------------------------------------

### データの出力
DataFrameで操作したデータをエクセルに保存します。
```python
# DataFrameをExcelファイルのシートに書き出す。
dataframe.to_excel('filename', sheet_name='Results', index=False)
```
引数のindexはエクセルファイルにインデックスを出力するかどうかを選べます。Falseの場合は出力されません。

また、エクセルのシート上で出力する場所なんかも指定できます。startrowとstartcolで開始する行と列を指定できます。

---------------------------------------
基本的な操作程度でしたが、使い方次第で柔軟な処理ができるかと思います。

-----------------

### 実際に作成したプログラム
:::note warn
ソースの一部を削除、または変更しています。
:::

```python
import os
import pandas as pd
from openpyxl import load_workbook
from wcwidth import wcswidth


def make_importfile(input_folder, output_file):

    sheet_index_end = int(input("Enter max_sheet_index: "))
    output_wb_sheet = output_wb = load_workbook(output_file)
    
    # inputファイルの各セルの内容を保存するための変数
    input_cells = {"C":"", "D":"", "E":"", "H":"", "K":"", "L":"", "M":"", "N":"", "O":"", "P":"", "Q":""}
    columns = ['C', 'D', 'E', 'K', 'L', 'M', 'N', 'O', 'P', 'Q']

    output_line = 2
    i = 0
    j= 0
    # 指定されたディレクトリ内のすべてのExcelファイルをリスト化（入力ファイル）
    excel_file_list = [f for f in os.listdir(input_folder) if f.endswith('.xlsx') or f.endswith('.xls')]

    for excel_file in excel_file_list:
        # ファイルパスの作成
        file_path = os.path.join(input_folder, excel_file)
        excel = pd.ExcelFile(file_path)
		sheet_name_list = excel.sheet_names

        # エクセルのシートごとに読み込む
        for i in range(sheet_index_end):
            sheet_data = excel.parse(file_path, sheet_name=i, usecols="A:L")
            sheet_data = sheet_data.iloc[8:1001]

            # 一行ずつ取得
            for index, row_of_sheet_data in sheet_data.iterrows():
                if not pd.isnull(row_of_sheet_data.iloc[11]):

                    output_wb_sheet[f"A{output_line}"] = test_suite
                    output_wb_sheet[f"B{output_line}"] = cell_b_number + str(output_line - 1).zfill(4)
                    output_wb_sheet[f"I{output_line}"] =  row_of_sheet_data.iloc[11].replace("\n", "")
                    output_wb_sheet[f"J{output_line}"] = 1
                    
                    # inputファイルの各セルの内容が存在すれば格納変数を更新する
                    for j, clmun in enumerate(columns, start=1):
                        if not pd.isnull(row_of_sheet_data.iloc[j]):
                            output_cells[clmun] =  row_of_sheet_data.iloc[j].replace("\n", "")
                    
                    if not pd.isnull(row_of_sheet_data.iloc[10]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L", "M", "N", "O", "P", "Q"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[9]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L", "M", "N", "O", "P"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[8]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L", "M", "N", "O"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[7]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L", "M", "N"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[6]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L", "M"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[5]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = '\n'.join([str(output_cells[col]) for col in ["K", "L"] if output_cells[col]])

                    elif not pd.isnull(row_of_sheet_data.iloc[4]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]
                        output_wb_sheet[f"H{output_line}"] = output_cells["K"]

                    elif not pd.isnull(row_of_sheet_data.iloc[3]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]

                    elif not pd.isnull(row_of_sheet_data.iloc[2]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]

                    elif not pd.isnull(row_of_sheet_data.iloc[1]):
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]

                    else:
                        output_wb_sheet[f"C{output_line}"] = output_cells["C"]
                        output_wb_sheet[f"D{output_line}"] = output_cells["D"]
                        output_wb_sheet[f"E{output_line}"] = output_cells["E"]

                    output_line += 1
                    
                else:
                    break

            # 出力ファイルを保存
            output_excel.to_excel(output_file)
            # シート名の出力（確認用）
            sheet_name_width = wcswidth(sheet_name_list[i])
            print(f"{str(i + 1).zfill(2)}p / {sheet_name_list[i] + ' ' * (40 - sheet_name_width)}  OK")
```
実際に書いたソースコードです。
ネスト深＆汚い＆見づらいで申し訳ありません。


# まとめ
以上、エクセル作業の自動化でした。
初めてPythonを書きましたが、普段学習しているC言語と比べると、だいぶ書きやすかったです。

これからは単調で退屈な作業はコンピュータに任せてしまおうと思います。