直面した問題:毎月500ファイルのExcel処理
今年の初め、12支店からの売上レポートを処理するタスクを担当した。各支店が週1ファイルのExcelを送ってくるので、月合計約500ファイルになる。手作業でマージし、手作業で色を塗り、手作業でボーダーを引く。その作業だけで毎月2日近くかかっていた。
openpyxlでスクリプトを書くのに3週間かかったが、あのパイプライン全体が4分で終わるようになった。この記事では実際に役立ったことをまとめる — Hello Worldチュートリアルではなく、本番環境で実際に使っているものだ。
まず押さえておくべきコアコンセプト
openpyxlは3つの主要レイヤーで動作する:Workbook(Excelファイル)、Worksheet(シート)、Cell(セル)。この3つを理解すればライブラリの80%を把握できる。
多くの人が見落とす、重要なファイル読み込みモードが2つある:
- Normalモード:ファイル全体をRAMに読み込む — 柔軟な読み書きに便利だが、50MB以上のファイルではRAM消費が顕著になる。
- Read-onlyモード(
read_only=True):ジェネレーターを使用し、メモリに全部読み込まずに1行ずつ読む — 200MB以上のファイルでもRAM不足の心配なし。
openpyxlを選んだのは、.xlsxをネイティブに扱えるからだ:読み込み、書き込み、書式の維持、スタイルとチャートのサポート。Pandasはデータ分析が得意だが、Excelにエクスポートするとボーダーや背景色が失われる。クライアントに直接渡す出力が必要なタスクでは、openpyxlの方が明確な選択肢だ。
実践:データ読み込みから完成レポートまで
1. RAM不足を気にせず大容量ファイルを読み込む
10MB未満の小さいファイルなら、normalモードで問題ない:
from openpyxl import load_workbook
wb = load_workbook('月次レポート.xlsx')
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
chi_nhanh, doanh_thu, so_don = row[0], row[1], row[2]
print(f"{chi_nhanh}: {doanh_thu:,} VNĐ ({so_don}件)")
50MB以上のファイルはすぐにread-onlyモードに切り替えよう:
wb = load_workbook('大容量データ.xlsx', read_only=True)
ws = wb.active
total = 0
for row in ws.iter_rows(min_row=2, values_only=True):
if row[1]: # 空行をスキップ
total += row[1]
wb.close() # 重要:read_only使用時は必ずcloseすること
print(f"合計: {total:,}")
2. データの書き込みと新しいレポートファイルの作成
複数のソースファイルから統合レポートファイルを作成する — これが最もよく使うユースケースだ:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
import glob
wb_out = Workbook()
ws_out = wb_out.active
ws_out.title = "統合"
# ヘッダー
headers = ["支店名", "売上", "注文数", "平均/件"]
ws_out.append(headers)
# ヘッダースタイル
for col_idx, cell in enumerate(ws_out[1], 1):
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill(fill_type="solid", fgColor="2E5090")
cell.alignment = Alignment(horizontal="center")
# 各支店のファイルを読み込む
for filepath in glob.glob('支店_*.xlsx'):
wb_src = load_workbook(filepath, read_only=True)
ws_src = wb_src.active
for row in ws_src.iter_rows(min_row=2, values_only=True):
if row[0]: # データあり
tb = row[1] / row[2] if row[2] else 0
ws_out.append([row[0], row[1], row[2], round(tb, 0)])
wb_src.close()
wb_out.save('統合.xlsx')
3. 条件付き書式(Conditional Formatting)
これが最も調べるのに時間がかかった部分だ。openpyxlは条件付き書式をかなり充実してサポートしているが、APIはExcel UIとは少し異なる。
値に基づいてセルに色を付ける — 例えば売上が低いものをハイライト:
from openpyxl.formatting.rule import ColorScaleRule, CellIsRule
from openpyxl.styles import PatternFill
# カラースケール:赤 → 黄 → 緑(値に応じて)
color_scale = ColorScaleRule(
start_type='min', start_color='FF0000',
mid_type='percentile', mid_value=50, mid_color='FFFF00',
end_type='max', end_color='00FF00'
)
ws_out.conditional_formatting.add('B2:B50', color_scale)
# 50,000,000未満のセルを赤で塗りつぶす
red_fill = PatternFill(start_color='FFB3B3', end_color='FFB3B3', fill_type='solid')
low_rule = CellIsRule(operator='lessThan', formula=['50000000'], fill=red_fill)
ws_out.conditional_formatting.add('B2:B50', low_rule)
この2つをよく組み合わせて使う:color scaleで全体の分布を一目で把握し、CellIsRuleですぐに確認が必要なセルを正確にマークする。
4. レポートグラフの作成
openpyxlは最初に思っていたより多くの種類のチャートをサポートしている — bar、line、pie、scatter。売上レポートにはBarChartで十分だ:
from openpyxl.chart import BarChart, Reference
from openpyxl.chart.series import SeriesLabel
# データはA列(名前)とB列(売上)、1〜13行目を想定
chart = BarChart()
chart.type = "col" # 縦棒グラフ
chart.title = "支店別売上"
chart.y_axis.title = "売上(VNĐ)"
chart.x_axis.title = "支店"
chart.style = 10 # ExcelのプリセットスタイルNo.10
chart.width = 20
chart.height = 12
# データ系列(B列)
data = Reference(ws_out, min_col=2, min_row=1, max_row=13)
chart.add_data(data, titles_from_data=True)
# ラベル(A列)
categories = Reference(ws_out, min_col=1, min_row=2, max_row=13)
chart.set_categories(categories)
# シートにチャートを挿入(D2セルから)
ws_out.add_chart(chart, "D2")
wb_out.save('グラフ付きレポート.xlsx')
5. 列幅の自動調整
小さな細部だが、クライアントからよく列が切れていると言われる。この関数はすべてのExcelスクリプトにコピーして使っている:
def auto_fit_columns(ws):
for col in ws.columns:
max_len = 0
col_letter = col[0].column_letter
for cell in col:
try:
val_len = len(str(cell.value)) if cell.value else 0
if val_len > max_len:
max_len = val_len
except:
pass
# +2で少しパディングを追加
ws.column_dimensions[col_letter].width = min(max_len + 2, 50)
auto_fit_columns(ws_out)
6. テーブルのボーダー設定
データ全体を囲む細いボーダーを付けると一気にプロらしい見栄えになる:
from openpyxl.styles import Border, Side
thin = Side(border_style='thin', color='AAAAAA')
border = Border(left=thin, right=thin, top=thin, bottom=thin)
for row in ws_out.iter_rows(min_row=1, max_row=ws_out.max_row,
min_col=1, max_col=ws_out.max_column):
for cell in row:
cell.border = border
まとめ:自動化する価値があること
6ヶ月の本番運用を経て、openpyxlが最も得意とする3つの作業がわかった:ファイルをマージして統合レポートを作成すること、テンプレートに沿って一貫した書式を適用すること、データからグラフを生成すること。タスクがこの3つのどれかに当てはまるなら — 50行のスクリプトで毎回2時間の手作業を代替できる。
openpyxlを使わないもの:複雑な統計分析(pandasの方が優れている)、旧形式の.xlsファイルの処理(xlrdを使う)、本格的なピボットテーブルが必要な場合(手作業かWindowsでwin32comを使うしかない)。
毎週1時間以上Excelを手作業で処理しているなら、スクリプトを検討するサインだ。最初にコードを書くのに半日かけた。その代わり、それ以降は毎月2日分の作業が浮いている。

