【Excel + Python】セルの値が変わったタイミングで色付けして読みやすくする

# 概要

以下のような Excel ファイルを、

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/a82b1ccf-22cd-d687-3910-79cb52b01f72.png"  width=70%>

以下のように色付けし、1つ上のセルと比較して変更されている箇所を分かりやすくしたい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/675c7f00-35a0-4235-813f-1f4ec0db2119.png"  width=70%>

# 実行イメージ (ゴール)

![excel-color.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/55e7ec1c-133b-e08e-55a1-ca5e6ed3d639.gif)

# Python バージョン

3.8.2

# 実行手順

1. venv などで仮想環境を構築する
2. `pip install -r requirements.txt`
3. 対象とする Excel ファイルを開く
4. `python main.py`
    1. 引数として `all` を渡す: アクティブブック内の全てのシートに対して適用する
    2. 引数を渡さない: アクティブブック内のアクティブシートのみに適用する

# コード

## requirements.txt

```
xlwings
xlsxwriter
```

## settings.py

```Python
import xlwings as xw
from xlsxwriter.utility import xl_col_to_name


# 1つ上のセルと値が違う際に使用する背景色 (RGB)
background_color = (252, 228, 214)
# 1つ上のセルと値が同じ際に使用する文字色 (RGB)
font_color = (191, 191, 191)


def make_target_cell_range(sheet: xw.Sheet) -> str:
    """
    実行対象とするセルの範囲を 'A1:C5' のような形式の文字列で返す
    Excel ファイルの内容に応じて処理を修正する必要あり
    """
    # 列の開始と終了地点のアルファベットを取得
    start_col_alphabet = None
    end_col_alphabet = None
    for cell in sheet.used_range:
        if cell.value == '操作':
            start_col_alphabet = xl_col_to_name(cell.column - 1)
        if cell.value == '想定結果':
            end_col_alphabet = xl_col_to_name(cell.column - 2)
            break

    # 行の開始と終了地点の番号を取得
    a_col_values = list(map(lambda a: a.value, sheet.used_range.columns[0]))
    a_col_nums = list(filter(lambda a: isinstance(a, float), a_col_values))
    min_num = min(a_col_nums)
    max_num = max(a_col_nums)
    start_row_num = a_col_values.index(min_num) + 1
    end_row_num = a_col_values.index(max_num) + 1

    # 列のアルファベットと行番号を 'A1:C5' のように形に連結して返す
    return start_col_alphabet + str(start_row_num) + ':' + end_col_alphabet + str(end_row_num)
```

## main.py

```Python
from settings import *
import xlwings as xw
import sys


def sheet_to_readable(
    sheet: xw.Sheet,
    range_str: str,
):
    """
    エクセルの1シートと、対象とするセルの範囲を受け取り、色付けして読みやすくする
    :param sheet: Excel ファイルの1シート
    :param range_str: 実行対象とするセルの範囲。例: 'A1:C5'
    """
    for col in sheet.range(range_str).columns:
        # 1つ上のセルの値
        prev_value = None

        for cell in col:
            # 背景色をリセット
            if cell.color == background_color:
                cell.color = None
            # 文字色をリセット
            if cell.font.color == font_color:
                cell.font.color = (0, 0, 0)

            value = cell.value
            if (value != prev_value) and (value is None):
                pass
            elif value != prev_value:
                cell.color = background_color
            else:
                cell.font.color = font_color

            prev_value = value


def main():
    # Excel ファイルが開かれていない場合は処理を終える
    if len(xw.books) <= 0:
        print('対象とする Excel ファイルを開いてから再度実行してください。')
        return

    # 受け取った引数内に 'all' という文字列が含まれている場合、
    # アクティブブック内の全てのシートに適用する
    if (len(sys.argv) >= 2) and ('all' in sys.argv[1]):
        book = xw.books.active
        for sheet in book.sheets:
            sheet.activate()
            sheet_to_readable(sheet, make_target_cell_range(sheet))

    # アクティブブック内のアクティブシートのみに適用する
    else:
        sheets = xw.sheets
        sheet = sheets.active
        sheet.activate()
        sheet_to_readable(sheet, make_target_cell_range(sheet))

    print('🚀 Success')


if __name__ == '__main__':
    main()
```