はじめに
「毎月同じフォーマットのExcelを手作業で作っている」
「複数のExcelファイルを1つにまとめる作業が面倒」
「同じセルに同じ書式を毎回設定している」
こうしたExcel作業は、Pythonの openpyxl で自動化できます。この記事では、実務でよく使うパターンに絞って解説します。
pandasでの集計は以前の記事で扱いましたが、**openpyxlは「書式やレイアウトを含めたExcelファイルそのものの操作」**が得意です。用途に応じて使い分けます。
準備
pip install openpyxl
基本操作
ファイルを開く・保存する
from openpyxl import load_workbook, Workbook
# 既存ファイルを開く
wb = load_workbook('data.xlsx')
ws = wb.active # アクティブなシート
ws = wb['売上データ'] # シート名で指定
# 新規作成
wb = Workbook()
ws = wb.active
ws.title = '集計結果'
# 保存
wb.save('output.xlsx')
注意:
load_workbook()で開いて保存すると、グラフやピボットテーブルなど一部の要素が失われることがあります。重要なファイルは必ずバックアップを取ってから処理してください。
セルの読み書き
# 読み取り
value = ws['A1'].value
value = ws.cell(row=1, column=1).value # 行列指定
# 書き込み
ws['A1'] = '商品名'
ws.cell(row=1, column=2, value='売上')
# 全行をループ
for row in ws.iter_rows(min_row=2, values_only=True):
print(row) # ('りんご', 1500, ...)
パターン① 複数のExcelファイルを1つにまとめる
支店ごと・月ごとに分かれたファイルを集約する、最頻出のパターンです。
import glob
from openpyxl import load_workbook, Workbook
# 出力用ブック
out_wb = Workbook()
out_ws = out_wb.active
out_ws.title = '統合データ'
# ヘッダーを書き込む
out_ws.append(['ファイル名', '日付', '商品名', '数量', '売上'])
# フォルダ内の全xlsxを処理
for file_path in sorted(glob.glob('data/*.xlsx')):
wb = load_workbook(file_path, data_only=True) # data_only=True で数式ではなく値を取得
ws = wb.active
file_name = file_path.split('/')[-1]
# 2行目以降を転記(1行目はヘッダーとみなす)
for row in ws.iter_rows(min_row=2, values_only=True):
if row[0] is None: # 空行はスキップ
continue
out_ws.append([file_name, *row])
wb.close()
out_wb.save('統合結果.xlsx')
print('統合が完了しました')
ポイント: data_only=True を指定すると、数式ではなく計算結果の値を取得できます。数式のまま取得したい場合は指定しません。
パターン② 書式を設定する(見やすい表を作る)
自動生成したExcelを「そのまま人に渡せる品質」にするための書式設定です。
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
wb = Workbook()
ws = wb.active
# データ
data = [
['商品名', 'カテゴリ', '数量', '売上'],
['りんご', '果物', 10, 1500],
['牛乳', '飲料', 5, 900],
['パン', '食品', 12, 2400],
]
for row in data:
ws.append(row)
# ── ヘッダー行の書式 ──
header_font = Font(bold=True, color='FFFFFF', size=11)
header_fill = PatternFill('solid', fgColor='4472C4')
for cell in ws[1]:
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center', vertical='center')
# ── 罫線を引く ──
thin = Side(style='thin', color='CCCCCC')
border = Border(left=thin, right=thin, top=thin, bottom=thin)
for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=ws.max_column):
for cell in row:
cell.border = border
# ── 数値に3桁区切りを設定 ──
for row in ws.iter_rows(min_row=2, min_col=4, max_col=4):
for cell in row:
cell.number_format = '#,##0'
# ── 列幅を自動調整 ──
for col_idx in range(1, ws.max_column + 1):
max_len = 0
for row_idx in range(1, ws.max_row + 1):
v = ws.cell(row=row_idx, column=col_idx).value
if v:
max_len = max(max_len, len(str(v)))
ws.column_dimensions[get_column_letter(col_idx)].width = max_len + 4
# ── 1行目を固定(スクロールしても見出しが残る)──
ws.freeze_panes = 'A2'
wb.save('formatted.xlsx')
よく使う書式設定
| やりたいこと | コード |
|---|---|
| 太字 | cell.font = Font(bold=True) |
| 文字色 | Font(color='FF0000') |
| 背景色 | PatternFill('solid', fgColor='FFFF00') |
| 中央揃え | Alignment(horizontal='center') |
| 3桁区切り | cell.number_format = '#,##0' |
| 通貨表示 | cell.number_format = '¥#,##0' |
| パーセント | cell.number_format = '0.0%' |
| 日付形式 | cell.number_format = 'yyyy/mm/dd' |
パターン③ テンプレートに値を差し込む
請求書や報告書など、フォーマットが決まっているファイルに数字だけ入れるパターンです。書式はテンプレート側で作っておけるので、コードがシンプルになります。
from openpyxl import load_workbook
from datetime import datetime
# テンプレートを開く(書式・ロゴ・罫線などは設定済み)
wb = load_workbook('請求書テンプレート.xlsx')
ws = wb.active
# 決まった位置に値を差し込む
ws['C3'] = '株式会社サンプル 御中'
ws['C5'] = datetime.now().strftime('%Y年%m月%d日')
ws['C6'] = 'INV-2026-0042'
# 明細行を書き込む(10行目から)
items = [
['Webシステム開発', 1, 300000],
['保守サポート', 3, 20000],
]
start_row = 10
for i, (name, qty, price) in enumerate(items):
r = start_row + i
ws.cell(row=r, column=2, value=name)
ws.cell(row=r, column=3, value=qty)
ws.cell(row=r, column=4, value=price)
ws.cell(row=r, column=5, value=f'=C{r}*D{r}') # 数式も書き込める
# 合計欄
last_row = start_row + len(items) - 1
ws['E20'] = f'=SUM(E{start_row}:E{last_row})'
# 顧客名を含むファイル名で保存
wb.save('請求書_株式会社サンプル_2026-04.xlsx')
このパターンの利点: 書式設定をコードで書く必要がなく、テンプレートをExcelで直接編集できるため、非エンジニアでもレイアウト変更が可能です。実務では最も現実的なアプローチです。
パターン④ 条件付きで色を塗る(アラート表示)
「在庫が10未満なら赤」のように、条件に応じて視覚的に強調します。
from openpyxl import load_workbook
from openpyxl.styles import PatternFill
wb = load_workbook('在庫管理.xlsx')
ws = wb.active
red = PatternFill('solid', fgColor='FFC7CE') # 薄い赤
yellow = PatternFill('solid', fgColor='FFEB9C') # 薄い黄
# C列(在庫数)を判定して行全体に色を付ける
for row in ws.iter_rows(min_row=2):
stock = row[2].value # C列 = インデックス2
if stock is None:
continue
if stock < 10:
fill = red
elif stock < 30:
fill = yellow
else:
continue
for cell in row:
cell.fill = fill
wb.save('在庫管理_チェック済み.xlsx')
pandas と openpyxl の使い分け
| 用途 | 推奨 |
|---|---|
| 大量データの集計・分析 | pandas |
| 書式付きのExcelを出力したい | openpyxl |
| テンプレートに差し込む | openpyxl |
| CSVとの相互変換 | pandas |
| 複数ファイルの単純な結合 | どちらでも可 |
組み合わせるのが最も実用的です。 pandasで集計してから、openpyxlで書式を整えるという流れがよく使われます。
import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font
# ① pandasで集計して出力
df = pd.read_csv('sales.csv')
summary = df.groupby('カテゴリ')['売上金額'].sum().reset_index()
summary.to_excel('summary.xlsx', index=False)
# ② openpyxlで書式を整える
wb = load_workbook('summary.xlsx')
ws = wb.active
for cell in ws[1]:
cell.font = Font(bold=True)
wb.save('summary.xlsx')
つまずきやすいポイント
① 開いて保存したらグラフが消えた
→ openpyxlはグラフやピボットテーブルの一部を保持できません。テンプレート方式にするか、書式部分は触らない設計にします。
② 数式の結果がNoneになる
→ load_workbook(path, data_only=True) を指定します。ただしこれは「Excelで一度開いて保存された結果」を読むため、一度もExcelで開いていないファイルではNoneになります。
③ 日付がおかしな数値になる
→ Excelの日付は内部的にシリアル値です。cell.number_format = 'yyyy/mm/dd' を設定するか、Pythonの datetime オブジェクトを直接書き込みます。
④ 大量データで遅い
→ 読み取り専用なら load_workbook(path, read_only=True)、書き込み専用なら Workbook(write_only=True) を使うとメモリ効率が大幅に改善します。
まとめ
-
基本:
load_workbook()で開き、ws['A1']で読み書き、save()で保存 -
複数ファイル統合:
glob+ ループで一括処理 -
書式設定:
Font/PatternFill/Border/number_format - テンプレート差し込み:実務で最も現実的。書式管理をExcel側に任せられる
- pandasとの併用:集計はpandas、仕上げはopenpyxl
「毎月同じExcel作業をしている」なら、それは自動化できる作業です。まずは一番面倒な1ファイルから試してみてください。
Excel業務の自動化のご相談
「毎月のレポート作成を自動化したい」「複数ファイルの集約処理を仕組み化したい」などのご相談を受け付けています。