- Python
Python openpyxlでExcel操作|読み書き・書式・グラフのコード一覧
openpyxlとは、PythonからExcelファイル(.xlsx)を読み書きするためのライブラリです。 Excel本体がなくても動くので、Linuxサーバーやコンテナの中でも、毎月の集計表づくりや帳票の作成を自動化できます。
この記事では、openpyxlでできる操作を「やりたいこと → コード」の早見表にまとめたうえで、架空の売上データから集計表とグラフ入りのxlsxを作る完成スクリプトを実行し、生成されたファイルを読み直してセルの値と書式を確認した結果まで載せています。さらに、data_only=True で数式の値が None になる理由、.xls を開いたときのエラー文、10万行の読み書き速度の実測、pandas・xlsxwriter・xlwingsの使い分けも整理しました。
検証環境:Python 3.11.15 / openpyxl 3.1.5(2026年10月時点でPyPIの最新版。2024-06-28公開)/ pandas 3.0.2 / XlsxWriter 3.2.9。
openpyxlでできる操作の早見表
| やりたいこと | コード | 補足 |
|---|---|---|
| インストール | pip install openpyxl |
画像を扱うなら pip install pillow も |
| 新規作成して保存 | wb = Workbook(); ws = wb.active; wb.save("a.xlsx") |
最初のシートは wb.active |
| 既存ファイルを開く | wb = load_workbook("a.xlsx") |
.xlsx .xlsm に対応。.xls は非対応 |
| 数式ではなく計算結果を読む | load_workbook("a.xlsx", data_only=True) |
Excelで保存したファイルのみ値が入る |
| シート取得 | ws = wb["明細"] / wb.sheetnames |
名前で取得するのが確実 |
| シート追加・削除 | wb.create_sheet("集計", 0) / del wb["B"] |
第2引数は挿入位置 |
| シートのコピー・並べ替え | wb.copy_worksheet(ws) / wb.move_sheet("A", offset=-1) |
コピーは同じブック内のみ |
| セルに書く・読む | ws["A1"] = 100 / ws["A1"].value |
ws.cell(row=1, column=1, value=100) も可 |
| 範囲を読む | for row in ws.iter_rows(min_row=2, values_only=True): |
values_only=True で値のタプル |
| 行を末尾に追加 | ws.append([1, "ノート", 120]) |
表データの書き込みはこれが基本 |
| 行・列の挿入・削除 | ws.insert_rows(2) / ws.delete_cols(1) |
数式の参照先は自動では直らない |
| フォント | cell.font = Font(bold=True, color="FFFFFF") |
色は RRGGBB |
| 塗りつぶし | cell.fill = PatternFill("solid", fgColor="1F4E78") |
"solid" を忘れると色が出ない |
| 罫線 | cell.border = Border(top=Side(style="thin"), ...) |
4辺それぞれ指定 |
| 配置 | cell.alignment = Alignment(horizontal="center", wrap_text=True) |
折り返しもここ |
| 表示形式 | cell.number_format = "#,##0" / "yyyy/mm/dd" |
値は変えず見た目だけ変わる |
| 列幅・行高 | ws.column_dimensions["A"].width = 12 / ws.row_dimensions[1].height = 24 |
自動調整機能はない |
| セル結合 | ws.merge_cells("E1:H1") / ws.unmerge_cells(...) |
値は左上セルに入れる |
| 数式 | ws["E2"] = "=C2*D2" |
文字列で書く。計算はExcelが開いたとき |
| 条件付き書式 | ws.conditional_formatting.add("C2:C31", CellIsRule(...)) |
ColorScaleRule FormulaRule もある |
| 入力規則(プルダウン) | DataValidation(type="list", formula1='"A,B,C"') |
ws.add_data_validation(dv) を忘れずに |
| オートフィルタ | ws.auto_filter.ref = "A1:E31" |
絞り込み条件の適用はExcel側 |
| ウィンドウ枠の固定 | ws.freeze_panes = "A2" |
"B2" で1行目とA列を固定 |
| グラフ | BarChart() / LineChart() / PieChart() + Reference |
ws.add_chart(chart, "E3") |
| 画像 | ws.add_image(Image("logo.png"), "E2") |
Pillowが必要 |
| 印刷設定 | ws.page_setup.orientation = "landscape" / ws.print_title_rows = "1:1" |
1ページに収める設定もある |
| 大量データの書き込み | Workbook(write_only=True) |
省メモリ。append のみ使える |
| 大量データの読み込み | load_workbook(..., read_only=True) |
省メモリ。最後に wb.close() |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python openpyxlでExcel操作|読み書き・書式・グラフのコード一覧」
openpyxlのインストールと基本の流れ
openpyxlの操作は「ブック(Workbook)→ シート(Worksheet)→ セル(Cell)」の3階層で考えると整理できます。 Excelの画面でいう「ファイル → 下のタブ → マス目」に対応します。
pip install openpyxl pillow
python -c "import openpyxl; print(openpyxl.__version__)"
3.1.5
| 用語 | openpyxlのクラス | 取得方法 |
|---|---|---|
| ブック(ファイル全体) | Workbook |
Workbook() で新規、load_workbook() で既存 |
| シート | Worksheet |
wb.active、wb["シート名"] |
| セル | Cell |
ws["A1"]、ws.cell(row=1, column=1) |
| セル範囲 | Cell のタプル |
ws["A1:C3"]、ws.iter_rows(...) |
最小のコードは次のとおりです。
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "在庫"
ws.append(["品名", "数量"])
ws.append(["ノート", 120])
ws["C1"] = "メモ"
wb.save("stock.xlsx")
save() は同名ファイルを確認なしで上書きします。 元のファイルを残したい場合は、別名で保存するのが安全です。また、Excelでそのファイルを開いたまま保存すると、Windowsでは PermissionError になります。Excelを閉じてから実行してください。
完成スクリプト:売上データから集計表とグラフ入りxlsxを作る
早見表の操作をひとつのスクリプトにまとめると、次のようになります。 架空の文房具の売上(2026年1〜6月、5商品)から、書式つきの明細シートと、SUMIFの集計・棒グラフ・印刷設定つきの集計シートを作ります。コピーしてそのまま実行できます。
# make_report.py Python 3.11 / openpyxl 3.1.5
from datetime import date
import random
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
from openpyxl.formatting.rule import CellIsRule
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.chart import BarChart, Reference
from openpyxl.utils import get_column_letter
random.seed(1)
items = ["ノート", "ボールペン", "ファイル", "付箋", "ホチキス"]
rows = []
for m in range(1, 7): # 2026年1〜6月の架空の売上
for it in items:
rows.append((date(2026, m, 1), it, random.randint(20, 200), random.choice([120, 150, 300, 80, 900])))
wb = Workbook()
ws = wb.active
ws.title = "明細"
ws.append(["月", "商品", "数量", "単価", "金額"])
for i, (d, it, qty, price) in enumerate(rows, start=2):
ws.append([d, it, qty, price, f"=C{i}*D{i}"]) # 金額は数式で
last = ws.max_row
# 書式
head_font = Font(bold=True, color="FFFFFF")
head_fill = PatternFill("solid", fgColor="1F4E78")
thin = Side(style="thin", color="999999")
for c in ws[1]:
c.font, c.fill = head_font, head_fill
c.alignment = Alignment(horizontal="center")
for row in ws.iter_rows(min_row=1, max_row=last, max_col=5):
for c in row:
c.border = Border(top=thin, bottom=thin, left=thin, right=thin)
for r in range(2, last + 1):
ws[f"A{r}"].number_format = "yyyy/mm"
ws[f"D{r}"].number_format = "#,##0"
ws[f"E{r}"].number_format = "#,##0"
for col, w in zip("ABCDE", [10, 12, 8, 8, 12]):
ws.column_dimensions[col].width = w
ws.freeze_panes = "A2" # 見出し行を固定
ws.auto_filter.ref = f"A1:E{last}" # フィルタ
ws.conditional_formatting.add(f"C2:C{last}",
CellIsRule(operator="greaterThanOrEqual", formula=["150"], fill=PatternFill("solid", fgColor="FFF2CC")))
dv = DataValidation(type="list", formula1='"' + ",".join(items) + '"', allow_blank=False)
ws.add_data_validation(dv)
dv.add(f"B2:B{last}")
# 集計シート(商品別合計を SUMIF で)
sm = wb.create_sheet("集計")
sm.append(["商品", "数量合計", "金額合計"])
for i, it in enumerate(items, start=2):
sm.append([it, f"=SUMIF(明細!B:B,A{i},明細!C:C)", f"=SUMIF(明細!B:B,A{i},明細!E:E)"])
total = len(items) + 2
sm[f"A{total}"] = "合計"
sm[f"B{total}"] = f"=SUM(B2:B{total-1})"
sm[f"C{total}"] = f"=SUM(C2:C{total-1})"
sm.merge_cells("E1:H1")
sm["E1"] = "商品別 数量(2026年1〜6月)"
sm["E1"].alignment = Alignment(horizontal="center")
for c in sm[1][:3]:
c.font, c.fill = head_font, head_fill
for r in range(2, total + 1):
sm[f"C{r}"].number_format = "#,##0"
sm[f"A{total}"].font = Font(bold=True)
for col in range(1, 4):
sm.column_dimensions[get_column_letter(col)].width = 12
# グラフ用に数量は Python で集計した値も持っておく
qty_sum = {it: sum(q for _, i2, q, _ in rows if i2 == it) for it in items}
sm["J1"] = "グラフ用"
for i, it in enumerate(items, start=2):
sm[f"J{i}"] = it
sm[f"K{i}"] = qty_sum[it]
chart = BarChart()
chart.title = "商品別 数量"
chart.y_axis.title = "数量"
chart.add_data(Reference(sm, min_col=11, min_row=2, max_row=len(items) + 1), titles_from_data=False)
chart.set_categories(Reference(sm, min_col=10, min_row=2, max_row=len(items) + 1))
chart.legend = None
chart.width, chart.height = 16, 8
sm.add_chart(chart, "E3")
# 印刷設定
sm.page_setup.orientation = "landscape"
sm.page_setup.fitToWidth = 1
sm.sheet_properties.pageSetUpPr.fitToPage = True
sm.print_title_rows = "1:1"
wb.save("sales_report.xlsx")
print("saved:", "sales_report.xlsx", "明細行数:", last - 1)
$ python3 make_report.py
saved: sales_report.xlsx 明細行数: 30
グラフの元データには、数式ではなくPythonで計算した数値(J〜K列)を使っています。 openpyxlは数式を計算しないため、数式セルを参照したグラフは「Excelで開いて再計算されるまで」値を持ちません。Excelで開くだけなら数式参照でも表示されますが、ほかのツールで変換・プレビューする場合に備えて、グラフ用の値はPython側で持たせておくと確実です。
生成したファイルを読み直して確認する
画像は載せられないので、できあがったxlsxを load_workbook で読み直し、値と書式が意図どおり入っているかを出力で確かめます。 自動化スクリプトのテストにもそのまま使える書き方です。
# check_report.py
from openpyxl import load_workbook
wb = load_workbook("sales_report.xlsx")
print("シート:", wb.sheetnames)
ws = wb["明細"]
print("明細 範囲:", ws.dimensions, "固定:", ws.freeze_panes, "フィルタ:", ws.auto_filter.ref)
for row in ws.iter_rows(min_row=1, max_row=3, values_only=True):
print(row)
a2 = ws["A2"]
print("A2:", type(a2.value).__name__, a2.value, a2.number_format)
h = ws["A1"]
print("A1 font bold:", h.font.bold, "color:", h.font.color.rgb, "fill:", h.fill.fgColor.rgb)
print("E列幅:", ws.column_dimensions["E"].width)
print("条件付き書式:", [str(r.sqref) for r in ws.conditional_formatting])
print("入力規則:", [(d.type, d.formula1, str(d.sqref)) for d in ws.data_validations.dataValidation])
sm = wb["集計"]
for row in sm.iter_rows(min_row=1, max_row=7, max_col=3, values_only=True):
print(row)
print("結合:", sm.merged_cells.ranges, "グラフ数:", len(sm._charts), "向き:", sm.page_setup.orientation)
wb2 = load_workbook("sales_report.xlsx", data_only=True)
print("data_only E2:", wb2["明細"]["E2"].value, " 集計B7:", wb2["集計"]["B7"].value)
シート: ['明細', '集計']
明細 範囲: A1:E31 固定: A2 フィルタ: A1:E31
('月', '商品', '数量', '単価', '金額')
(datetime.datetime(2026, 1, 1, 0, 0), 'ノート', 54, 900, '=C2*D2')
(datetime.datetime(2026, 1, 1, 0, 0), 'ボールペン', 36, 300, '=C3*D3')
A2: datetime 2026-01-01 00:00:00 yyyy/mm
A1 font bold: True color: 00FFFFFF fill: 001F4E78
E列幅: 12.0
条件付き書式: ['C2:C31']
入力規則: [('list', '"ノート,ボールペン,ファイル,付箋,ホチキス"', 'B2:B31')]
('商品', '数量合計', '金額合計')
('ノート', '=SUMIF(明細!B:B,A2,明細!C:C)', '=SUMIF(明細!B:B,A2,明細!E:E)')
('ボールペン', '=SUMIF(明細!B:B,A3,明細!C:C)', '=SUMIF(明細!B:B,A3,明細!E:E)')
('ファイル', '=SUMIF(明細!B:B,A4,明細!C:C)', '=SUMIF(明細!B:B,A4,明細!E:E)')
('付箋', '=SUMIF(明細!B:B,A5,明細!C:C)', '=SUMIF(明細!B:B,A5,明細!E:E)')
('ホチキス', '=SUMIF(明細!B:B,A6,明細!C:C)', '=SUMIF(明細!B:B,A6,明細!E:E)')
('合計', '=SUM(B2:B6)', '=SUM(C2:C6)')
結合: {<MergedCellRange E1:H1>} グラフ数: 1 向き: landscape
data_only E2: None 集計B7: None
読み取れるポイントは3つです。
- 書式・固定・フィルタ・条件付き書式・入力規則・結合・グラフ・印刷の向きは、すべて保存されています。色は
00FFFFFFのように先頭2桁(アルファ値)つきの8桁で返ります。 dateで書いた値は、読み直すとdatetimeになります(理由は後述)。data_only=Trueで読んでも数式セルはNoneです。 openpyxlで作ったファイルには、まだ計算結果が保存されていないためです。
数式の計算結果を確認する(LibreOfficeで再計算)
計算結果を確かめるため、LibreOffice(Excel互換の表計算ソフト)のヘッドレスモードでxlsxを開き直して保存し、もう一度 data_only=True で読みました。
soffice --headless --calc --convert-to xlsx --outdir recalc sales_report.xlsx
from openpyxl import load_workbook
wb = load_workbook("recalc/sales_report.xlsx", data_only=True)
print("E2:", wb["明細"]["E2"].value)
for r in wb["集計"].iter_rows(min_row=2, max_row=7, max_col=3, values_only=True):
print(r)
E2: 48600
('ノート', 560, 115910)
('ボールペン', 721, 114150)
('ファイル', 641, 84950)
('付箋', 775, 110990)
('ホチキス', 876, 233030)
('合計', 3573, 659030)
数量合計はグラフ用にPythonで計算した値(ノート560、ボールペン721…)と一致しました。サーバー上で計算済みの値まで必要なときは、このようにLibreOfficeで再計算させるか、最初からPython側で計算した値を書き込むのが確実です。
操作別のコード(逆引き)
シートの追加・コピー・削除・並べ替え
from openpyxl import Workbook
wb = Workbook()
ws = wb.active; ws.title = "A"
wb.create_sheet("B"); wb.create_sheet("先頭", 0) # 0 = 先頭に挿入
cp = wb.copy_worksheet(wb["A"]); cp.title = "Aのコピー"
del wb["B"]
wb.move_sheet("A", offset=-1) # 1つ左へ
print("シート:", wb.sheetnames)
シート: ['A', '先頭', 'Aのコピー']
copy_worksheet は同じブック内でのみ使えます。 別ファイルへシートを移したい場合は、値と書式をセル単位でコピーするか、pandasで値だけ移すのが一般的です。
セル・範囲の読み書き、行列の挿入と削除
for r in range(1, 4):
for c in range(1, 4):
ws.cell(row=r, column=c, value=r * 10 + c)
print("範囲:", [[c.value for c in row] for row in ws["A1:C3"]])
print("列B:", [c.value for c in ws["B"]])
ws.insert_rows(2); ws.insert_cols(1)
print("挿入後:", list(ws.iter_rows(values_only=True)))
ws.delete_rows(2); ws.delete_cols(1)
print("削除後:", list(ws.iter_rows(values_only=True)))
範囲: [[11, 12, 13], [21, 22, 23], [31, 32, 33]]
列B: [12, 22, 32]
挿入後: [(None, 11, 12, 13), (None, None, None, None), (None, 21, 22, 23), (None, 31, 32, 33)]
削除後: [(11, 12, 13), (21, 22, 23), (31, 32, 33)]
insert_rows / delete_rows はセルを動かすだけで、数式の中の参照(=C2*D2 など)や結合範囲・グラフの参照先は書き換えません。 数式を含む表に行を差し込むときは、数式を後から書き直す手順にしておくと安心です。
| 読み方 | 使いどころ |
|---|---|
ws["A1"].value |
1セルだけ |
ws["A1:C3"] |
範囲を2次元のタプルで |
ws["B"] / ws[2] |
列全体/行全体 |
ws.iter_rows(min_row=2, values_only=True) |
表を1行ずつ(最もよく使う) |
ws.iter_cols(min_col=1, max_col=3) |
列ごとに処理したいとき |
ws.max_row / ws.max_column |
最終行・最終列の番号 |
書式(フォント・塗り・罫線・表示形式・列幅)
| 対象 | クラス/属性 | よく使う引数 |
|---|---|---|
| 文字 | Font |
bold italic size color name underline |
| 背景 | PatternFill |
fill_type="solid" fgColor |
| 罫線 | Border + Side |
style="thin"/"medium"/"double"、color |
| 配置 | Alignment |
horizontal vertical wrap_text indent |
| 表示形式 | cell.number_format |
"#,##0" "0.0%" "yyyy/mm/dd" "@"(文字列) |
| 列幅 | ws.column_dimensions["A"].width |
半角文字数のおおよその目安 |
スタイルのオブジェクトは変更できない(イミュータブル)ので、cell.font.bold = True とは書けません。 一部だけ変えたいときは copy を使います。
from copy import copy
f = copy(ws["A1"].font)
f.bold = True
ws["A1"].font = f
同じ書式を大量のセルに使うなら、NamedStyle を登録して cell.style = "見出し" のように名前で当てるとファイルも軽くなります。
条件付き書式・入力規則・フィルタ
from openpyxl.formatting.rule import ColorScaleRule, FormulaRule
# 3色スケール
ws.conditional_formatting.add("E2:E31",
ColorScaleRule(start_type="min", start_color="F8696B", mid_type="percentile", mid_value=50,
mid_color="FFEB84", end_type="max", end_color="63BE7B"))
# 数式で条件(行全体を色付け)
ws.conditional_formatting.add("A2:E31",
FormulaRule(formula=['$B2="付箋"'], fill=PatternFill("solid", fgColor="DDEBF7")))
入力規則は ws.add_data_validation(dv) と dv.add("範囲") の2つが必要です。 どちらかが抜けるとプルダウンが表示されません。数値の範囲制限なら DataValidation(type="whole", operator="between", formula1="1", formula2="999") のように書きます。
画像を貼る
from openpyxl.drawing.image import Image
img = Image("logo.png") # Pillow が必要
img.width, img.height = 120, 60
ws.add_image(img, "E2")
読み直して ws._images の件数が1になることを確認しました。Pillowが入っていないと ImportError になるため、pip install pillow を先に済ませます。
グラフの種類
| グラフ | クラス | メモ |
|---|---|---|
| 棒 | BarChart |
chart.type = "bar" で横棒 |
| 折れ線 | LineChart |
月次推移に |
| 円 | PieChart |
系列は1つ |
| 散布図 | ScatterChart |
Series(values, xvalues) で作る |
| 面 | AreaChart |
積み上げは grouping="stacked" |
データの指定は Reference(シート, min_col, min_row, max_col, max_row) で行い、見出し行を含めるなら titles_from_data=True にします。
つまずきやすいポイントと直し方
data_only=True で数式の値が None になる
openpyxlは数式を計算せず、xlsxの中に保存されている「前回の計算結果(キャッシュ値)」を読むだけです。 Excelで開いて保存したファイルにはキャッシュ値が入っていますが、openpyxlで作っただけのファイルには入っていません。上の確認結果で data_only E2: None となったのはこのためです。
| 状況 | data_only=True の結果 | 対処 |
|---|---|---|
| Excelで作成・保存したファイル | 計算結果が読める | そのままでよい |
| openpyxlで作っただけのファイル | None |
Pythonで計算した値を書く/Excel・LibreOfficeで開いて保存 |
| data_onlyで開いて保存した | 数式が消えて値だけになる | 数式を残すなら通常モードで開いて保存 |
3つ目は見落としやすい点です。data_only=True で開いたブックを save() すると、数式は計算結果の数値に置き換わって保存されます(実行して、保存後のE2が 48600 という数値になることを確認しました)。値を読む用と、編集して保存する用で、ブックを分けて開くのがおすすめです。
また、pandasの read_excel もキャッシュ値を読むため、openpyxlで作っただけのファイルを読むと数式列は nan になります(実行結果:[nan, nan])。
.xls は開けない
InvalidFileException : openpyxl does not support the old .xls file format, please use xlrd to read this file, or convert it to the more recent .xlsx file format.
openpyxlが扱えるのは .xlsx / .xlsm / .xltx / .xltm です。 .xls は、読むだけなら xlrd、まとめて変換するなら soffice --headless --convert-to xlsx *.xls で .xlsx にしてから処理します。
大量データは write_only / read_only で軽くする
10万行×5列で、通常モードと省メモリモードを比べました(2コアのコンテナ。時間は time.perf_counter、メモリは別の回に tracemalloc のピーク値で計測。lxmlあり)。
| 処理 | 時間 | Pythonのピークメモリ |
|---|---|---|
| 書き込み(通常) | 4.38 秒 | 147.4 MB |
書き込み(write_only=True) |
3.61 秒 | 0.4 MB |
| 読み込み(通常) | 7.60 秒 | 191.0 MB |
読み込み(read_only=True) |
5.30 秒 | 8.1 MB |
速度の差は2〜3割ですが、メモリは桁違いに減ります。 行数がさらに増えるとメモリの差が効いてくるので、数万行を超えるなら省メモリモードを選ぶとよいでしょう。ただし制約があります。
| モード | できないこと(実際のエラー) |
|---|---|
read_only=True |
セルへの書き込み:AttributeError: Cell is read only |
write_only=True |
セル番地での参照:TypeError: 'WriteOnlyWorksheet' object is not subscriptable(append で1行ずつ書く) |
read_only=True で開いたブックはファイルを開いたままにするので、読み終わったら wb.close() を呼びます。
日付の型
from datetime import date, datetime, time
ws["A1"] = date(2026, 10, 1)
ws["A2"] = datetime(2026, 10, 1, 9, 30)
ws["A3"] = "2026/10/01" # 文字列のまま
ws["A4"] = 46296 # シリアル値(数値)
ws["A4"].number_format = "yyyy/mm/dd"
ws["A5"] = time(9, 30)
保存して読み直した結果です。
A1 datetime datetime.datetime(2026, 10, 1, 0, 0) yyyy-mm-dd True
A2 datetime datetime.datetime(2026, 10, 1, 9, 30) yyyy-mm-dd h:mm:ss True
A3 str '2026/10/01' General False
A4 datetime datetime.datetime(2026, 10, 1, 0, 0) yyyy/mm/dd True
A5 time datetime.time(9, 30) h:mm:ss True
Excelの日付は「1900年起点の通し番号(シリアル値)+日付の表示形式」で保存されています。 そのためopenpyxlは表示形式が日付のセルを datetime に変換して返し、date で書いた値も datetime で戻ってきます。日付だけ欲しいときは .value.date() を使います。文字列で書いた日付(A3)は文字列のままなので、Excel側で日付として並べ替え・計算したいなら date 型で書き込みます。
| やりたいこと | 書き方 |
|---|---|
| 日付だけ取り出す | cell.value.date() |
| セルが日付か判定 | cell.is_date |
| シリアル値から変換 | from openpyxl.utils.datetime import from_excel; from_excel(46296) |
| 表示を「2026/10/01」に | cell.number_format = "yyyy/mm/dd" |
そのほかの注意点
| 症状 | 原因 | 対処 |
|---|---|---|
| マクロが消えた | .xlsm を通常どおり開いて保存 | load_workbook(..., keep_vba=True) で開き、拡張子 .xlsm で保存 |
| 塗りつぶしが出ない | PatternFill の fill_type 未指定 |
PatternFill("solid", fgColor=...) |
| 結合セルの右側に書けない | 結合範囲の左上以外は MergedCell |
左上セルに書く |
| ピボットテーブルや一部の図形が消えた | openpyxlが対応していない要素は保存時に保持されない | 元ファイルを残し、テンプレートは最小限の要素で作る |
| 列幅が合わない | 自動調整機能がない | 文字数から width を計算して設定 |
pandas・xlsxwriter・xlwingsとの使い分け
「表データを集計してExcelに出す」ならpandas、「書式やグラフを細かく作り込む」ならopenpyxlかxlsxwriter、「開いているExcelそのものを操作する」ならxlwings、と考えると選びやすくなります。
| ライブラリ(2026年10月の最新版) | 得意なこと | 既存ファイルの編集 | Excel本体 | 向いている場面 |
|---|---|---|---|---|
| openpyxl 3.1.5 | 読み書き・書式・グラフをひととおり | できる | 不要 | テンプレートに値を流し込む、既存ファイルを加工 |
| pandas 3.0.2 | 集計・結合・抽出(read_excel / to_excel) |
値の入れ替えが中心 | 不要 | データ分析、CSVとExcelの相互変換 |
| XlsxWriter 3.2.9 | 書き込み専用。書式・グラフ・条件付き書式が豊富で高速 | できない(新規作成のみ) | 不要 | 大きなレポートを毎回新しく作る |
| xlwings 0.37.5 | 起動中のExcelを操作、マクロ・再計算 | できる | 必要(Windows/macOS) | Excelの計算結果が必要、VBAの置き換え |
pandasとopenpyxlは組み合わせて使えます。pandasで集計し、ExcelWriter のシートをopenpyxlのシートとして触って書式を整える流れです。
import pandas as pd
df = pd.read_excel("recalc/sales_report.xlsx", sheet_name="明細")
summary = df.groupby("商品", as_index=False)[["数量", "金額"]].sum()
with pd.ExcelWriter("pandas_out.xlsx", engine="openpyxl") as w:
summary.to_excel(w, sheet_name="集計", index=False)
w.sheets["集計"].column_dimensions["A"].width = 14 # openpyxl のシートとして操作
print(summary)
商品 数量 金額
0 ノート 560 115910
1 ファイル 641 84950
2 ホチキス 876 233030
3 ボールペン 721 114150
4 付箋 775 110990
pandasの集計結果は、先ほどのSUMIFの再計算結果と一致しました。CSVの読み書きや文字化け対策、pandasの操作そのものは別記事で詳しく扱っているので、ここではExcelとの連携部分にとどめます。
自動化スクリプトを安定させるチェックリスト
| チェック項目 | 理由 |
|---|---|
| 元ファイルは上書きせず別名で保存している | 上書き保存は元に戻せない |
| 数式の結果が必要な値はPythonで計算している | openpyxlは数式を計算しない |
| 値を読むブックと保存するブックを分けている | data_onlyで保存すると数式が消える |
| シートは名前で取得している | wb.active は保存時に選ばれていたシートになる |
日付は date / datetime で書いている |
文字列だとExcelで日付として扱えない |
| 大きなファイルは read_only / write_only | メモリを大きく節約できる |
| 生成後に読み直して値と書式を確認している | 自動テストにでき、修正時の確認も速い |
よくある質問
Q. openpyxlはExcelがインストールされていないPCやLinuxでも使えますか? A. 使えます。openpyxlはxlsxファイル(中身はXMLをまとめたZIP)を直接読み書きするため、Excel本体は不要です。ただし数式の計算はしないので、計算結果が必要なら本文のようにPythonで計算するか、LibreOfficeで再計算します。
Q. openpyxlとpandas、どちらを使えばよいですか? A. 集計や抽出が目的ならpandas、セルの書式・結合・グラフ・入力規則など見た目を作るならopenpyxlが向いています。pandasで集計し、openpyxlで書式を整える組み合わせもよく使われます。
Q. 既存のExcelテンプレートに値を書き込むと、書式は残りますか? A. セルの書式・列幅・結合・条件付き書式などは残ります。一方で、openpyxlが対応していない要素(ピボットテーブルや一部の図形など)は保存時に保持されないことがあります。元のテンプレートは別名で残しておくと安心です。
Q. 2026年時点でのopenpyxlの最新版は? A. PyPIの最新版は3.1.5(2024年6月公開、Python 3.8以上)です。この記事のコードはすべて3.1.5で実行しています。
まとめ
- openpyxlはExcel本体なしで.xlsxを読み書きできるライブラリで、最新版は3.1.5
- 基本は
Workbook→Worksheet→Cell。表データはappend、読み込みはiter_rows(values_only=True) - 書式・結合・条件付き書式・入力規則・フィルタ・固定・グラフ・画像・印刷設定まで、完成スクリプトで作成し、読み直して保存内容を確認できる
- 数式は計算されないため、
data_only=TrueではNone。結果が必要ならPythonで計算するか再計算する - data_onlyで開いて保存すると数式が消えるので、読む用と保存する用を分ける
- 10万行ではwrite_only/read_onlyでメモリが大幅に減る(147MB→0.4MB、191MB→8MB)
- 集計はpandas、作り込みはopenpyxl/xlsxwriter、Excel本体の操作はxlwingsと使い分ける
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python openpyxlでExcel操作|読み書き・書式・グラフのコード一覧」
株式会社RJCでは、PythonによるExcel自動化の知識を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。