背景:深夜2時の悪夢
コーヒーは冷めきっているのに、Slackは「チン、チン」と鳴り止みません。上司から急ぎの依頼が届きました:「50拠点分の売上レポートが必要だ。比較グラフ、KPIの書式設定を含め、今朝一番で相手先に送るためにファイルをロックしておいてくれ」。データベースから出力されたバラバラのCSVを眺め、もし50枚のシートを手作業で作っていたら、明日の朝には会社を辞めるしかないと悟りました。疲労困憊な上に、数値のミスも非常に起きやすい状況です。
この時, PythonとOpenpyxlのコンボが唯一の生存ルートでした。実際、プロフェッショナルなレポートは、単にセルにデータを流し込むだけでなく、美しさと一貫性も求められます。VBAを使うと、互換性の問題が発生したり、ウイルス対策ソフトにマクロをブロックされたりすることがあります。Openpyxlは .xlsx ファイルを直接処理し、サーバーにExcelをインストールする必要がないため、cronジョブやバックエンドAPIでの実行に最適です。
武器のセットアップ
まず、仮想環境(venv)を作成しましょう。私は以前、仮想環境の作成を怠ったために、本番環境でライブラリの競合という苦い経験をしました。同じ轍を踏まないようにしてください。
# 仮想環境の作成と有効化
python3 -m venv venv
source venv/bin/activate
# ライブラリのインストール
pip install openpyxl
画像の挿入や複雑なグラフを描画する場合は、pillow もインストールしてください。基本的な数値レポートであれば、openpyxl だけで十分対応可能です。
実戦での実装
以下は私がよく使うシナリオです:データの読み込み、自動書式設定、グラフ描画、そして最後にパスワード保護で締めくくります。
1. 初期化とスタイルの処理
デフォルトのCalibriフォントのまま、真っ白なファイルを送ってはいけません。背景色と白い文字で、プロフェッショナルなヘッダーを作成して印象を良くしましょう。Openpyxlは styles モジュールを使って、これらを非常にスムーズに管理できます。
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
wb = Workbook()
ws = wb.active
ws.title = "売上レポート"
# ヘッダーのスタイル定義
header_font = Font(bold=True, color="FFFFFF", size=12)
header_fill = PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid")
center_aligned = Alignment(horizontal="center", vertical="center")
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin'))
headers = ["月", "売上 (VND)", "費用 (VND)", "利益 (VND)"]
ws.append(headers)
# 1行目にスタイルを適用
for cell in ws[1]:
cell.font = header_font
cell.fill = header_fill
cell.alignment = center_aligned
cell.border = thin_border
生データの処理中に、正規表現(Regex)を使って電話番号を抽出したり文字列をフォーマットしたりする必要があることがよくあります。Pythonコードを再実行せずに複雑なパターンを素早くテストしたい場合は、toolcraft.app/ja/tools/developer/regex-tester のツールが便利です。ブラウザ上で動作するので、急いでいる時に重宝します。
2. データの流し込みと条件付き書式
Excel内で計算させる代わりに、Pythonで利益を計算し、マイナスの場合は赤色で表示するようにします。
data = [
["1月", 150000000, 120000000],
["2月", 200000000, 180000000],
["3月", 100000000, 115000000], # 1500万の赤字
]
for row_idx, row_data in enumerate(data, start=2):
revenue, cost = row_data[1], row_data[2]
profit = revenue - cost
# データの書き込み
ws.cell(row=row_idx, column=1, value=row_data[0])
ws.cell(row=row_idx, column=2, value=revenue).number_format = '#,##0'
ws.cell(row=row_idx, column=3, value=cost).number_format = '#,##0'
profit_cell = ws.cell(row=row_idx, column=4, value=profit)
profit_cell.number_format = '#,##0'
# 赤字(利益 < 0)の場合にハイライト
if profit < 0:
profit_cell.font = Font(color="FF0000", bold=True)
3. グラフ(Chart)の自動作成
上司は無機質な数値の表よりも、グラフを見る方を好みます。売上を比較する棒グラフ(Bar Chart)を追加することで、レポートの価値がぐっと高まります。
from openpyxl.chart import BarChart, Reference
chart = BarChart()
chart.type = "col"
chart.title = "月別売上比較"
chart.y_axis.title = "VND"
# データ範囲の指定(B列)
data_ref = Reference(ws, min_col=2, min_row=1, max_row=4)
cats_ref = Reference(ws, min_col=1, min_row=2, max_row=4)
chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(cats_ref)
ws.add_chart(chart, "F2")
4. セキュリティとファイルのロック
注意:openpyxl は、ユーザーが数式を編集できないようにシート構造をロックすることをサポートしています。ただし、ファイルを開く際のパスワード(Open Password)を設定するには、msoffcrypto-python を併用する必要があります。基本的なレベルでは、シートの保護だけでレポートの整合性を守るには十分です。
ws.protection.sheet = True
ws.protection.password = "secure2026"
wb.save("売上確定レポート.xlsx")
実務のヒント:本番環境でスクリプトを「落とさない」ために
スクリプトの実行が終わっても、すぐに寝てはいけません。約10万行のような大量のデータを扱う場合、Openpyxlはかなりメモリを消費します(500MB〜1GBに達することもあります)。メモリを最適化するために、write_only=True モードの使用を検討してください。
また、save 命令は常に try...except ブロックで囲むようにしましょう。非常によくあるエラーは、ファイルが別のユーザーによって開かれているためにPythonが上書き権限を持たない(PermissionError)ケースです。最後に、ファイルの容量を確認してください。入力データが数百万行あるのに出力ファイルが数KBしかない場合、ロジックに問題がある可能性が高いです。
自動化は、私たちがスプレッドシートの奴隷になるのを防いでくれます。50拠点の作業を4時間かけて手で行う代わりに、15秒で実行されるスクリプトを見れば、Openpyxlを学ぶために費やした時間は完全に報われると感じるはずです。

