毎月おなじExcelファイルを開いて、おなじ列をコピーして、おなじ集計をする。そんな作業に心当たりはありませんか。
一度きりなら手でやったほうが早い。でも毎月となると話は別です。
Pythonを学び始めた人が最初に効果を実感しやすいのが、まさにこのExcel作業の自動化です。手元にある見慣れたファイルが題材になるので、動いたときの手応えがまるで違います。
その入り口になるのがopenpyxlというライブラリです。Excelを起動しなくても、ファイルの中身をPythonから直接読み書きできます。
この記事では、openpyxlの基本と、初心者がつまずきやすいところを順番に整理していきます。
openpyxlは、Excelファイルを直接さわる道具¶
まずは立ち位置から確認しましょう。openpyxlは、Excelのファイルそのものを読み書きするライブラリです。
Excelアプリを裏で動かしているわけではありません。ファイルの中身を組み立てたり読み解いたりしているだけなので、Excelが入っていないサーバーでも動きます。
扱えるのは新しい形式だけ、という点だけ先に押さえてください。公式の説明では、対応するのはxlsx・xlsm・xltx・xltmの4つです。
拡張子ごとの対応をまとめておきます。手元のファイルがどれか、先に確認しておくと安心です。
| 拡張子 | 中身 | openpyxlで扱えるか |
|---|---|---|
.xlsx |
Excel 2007以降の標準的なブック | 扱える |
.xlsm |
マクロを含むブック | 扱える(マクロ自体は実行しない) |
.xltx / .xltm |
テンプレート | 扱える |
.xls |
Excel 2003以前の古い形式 | 扱えない |
.csv |
ただのテキスト | 対象外(標準ライブラリのcsvを使う) |
古い .xls が出てきたら、いったんExcelで開いて .xlsx として保存し直すのが一番早い解決策です。
拡張子がcsvのファイルなら、そもそもopenpyxlの出番はありません。文字コードの落とし穴も含めて、こちらで詳しく扱っています。【関連記事】Pythonのcsvモジュールとは?表データの読み書きと文字化けの防ぎ方を初心者向けに解説
インストールと、読み込みの3行¶
openpyxlは標準ライブラリではないので、まずインストールが必要です。pipで入れます。
pip install openpyxl
インストールが済んだら、読み込みは驚くほど短く書けます。ブックを開いて、シートを選んで、セルの値を取るだけです。
from openpyxl import load_workbook
wb = load_workbook("売上.xlsx")
ws = wb["8月"] # シート名で選ぶ
# ws = wb.active # 開いたときに表示されるシートでよければこちら
print(ws["B2"].value) # セルB2の値
print(ws.title) # 8月
print(ws.max_row) # データが入っている最終行
load_workbook() が返すのがブック、つまりファイル全体です。そこからシートを取り出し、シートからセルを取り出す。この入れ子の関係さえつかめば、あとは応用です。
3行目の .value を忘れる人がとても多いところです。ws["B2"] はセルという入れ物そのものなので、中身がほしいときは必ず .value を付けてください。
ライブラリを入れるときは、プロジェクトごとに仮想環境を分けておくと後々ラクになります。【関連記事】Pythonの仮想環境(venv)って何のためにある?プロジェクトごとに混ぜない管理法
セルの指定には2つの書き方がある¶
セルを指すやり方は1つではありません。Excelでおなじみの表記と、行と列の番号で指す表記があります。
慣れないうちは混乱しますが、使い分けの基準はとてもはっきりしています。
print(ws["A1"].value) # Excelと同じ表記
print(ws.cell(row=1, column=1).value) # 行と列の番号で指定
for row in range(2, 6):
name = ws.cell(row=row, column=1).value
price = ws.cell(row=row, column=2).value
print(name, price)
決め打ちで特定のセルを見るなら ws["A1"] が読みやすい。ループで動かすなら ws.cell() のほうが素直に書けます。
ここでPythonの常識とひとつだけ違う点があります。行番号も列番号も1から始まります。
リストのように0から数えると、1行ずつずれた結果が返ってきます。私は10年ほどエンジニアとして開発に関わってきましたが、久しぶりにopenpyxlを触ると今でも最初の一回はずらします。エラーにならず静かに1行ずれるので、気づくのが遅れるのが厄介なところです。
行ごとにまとめて読むならiter_rows¶
1セルずつ取るコードは、行数が増えると読みにくくなります。表として一気に読みたいときは iter_rows() が便利です。
for row in ws.iter_rows(min_row=2, max_row=5, values_only=True):
print(row)
# ('りんご', 120, 30)
# ('みかん', 80, 45)
values_only=True を付けると、セルではなく値のタプルが返ってきます。表計算のデータを扱いたいだけなら、ほとんどの場面でこれで足ります。
min_row=2 としているのは、1行目の見出しを飛ばすためです。読み取りの起点を指定できるので、ヘッダー付きの表がそのまま扱えます。
新しいブックを作って書き出す¶
読むだけでなく、作るほうも簡単です。空のブックを用意して、行を足して、保存します。
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "集計結果"
ws.append(["商品", "単価", "在庫"])
for item in [("りんご", 120, 30), ("みかん", 80, 45)]:
ws.append(list(item))
ws["E1"] = "作成: 自動集計"
wb.save("結果.xlsx")
append() は、いま入っているデータの次の行にまとめて書き足すメソッドです。行番号を自分で数える必要がないので、ループとの相性がとてもよくなります。
保存するまでファイルは1バイトも変わりません。 save() を呼び忘れて、なぜ反映されないと悩む人が本当に多いところです。
もうひとつ大事な注意があります。save() は同じ名前のファイルを黙って上書きします。元データを読み込んで加工するときは、必ず別名で保存してください。
保存先のパスを組み立てるときは、文字列をつなぐより専用の道具を使ったほうが安全です。【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説
見た目を整えるところまでできる¶
openpyxlの強みは、値だけでなく書式も扱えることです。太字にする、色を変える、列幅を広げる。どれもコードから指定できます。
from openpyxl.styles import Font, PatternFill
ws["A1"].font = Font(bold=True, size=12)
ws["A1"].fill = PatternFill("solid", fgColor="FFF2CC")
ws.column_dimensions["A"].width = 18
ws.freeze_panes = "A2" # 1行目を固定して見出しを残す
最後の freeze_panes は、Excelのウィンドウ枠の固定にあたる設定です。人が目で見る資料を出すときは、これがあるだけで印象が変わります。
書式まで再現できるのは、報告書の雛形を毎月配るような仕事で効いてきます。データだけ差し替えて配布する流れが、そのまま自動化できます。
数式は計算されない。ここがいちばんの落とし穴¶
初心者がもっとも驚くのが、この振る舞いです。openpyxlは数式を計算しません。
=SUM(B2:B10) と書かれたセルを読むと、返ってくるのは計算結果ではなく数式の文字列そのものです。openpyxlは表計算エンジンではないので、当然といえば当然の話です。
では計算結果を取りたいときはどうするか。data_only=True を付けて開きます。
wb = load_workbook("売上.xlsx", data_only=True)
ws = wb.active
print(ws["D10"].value) # 数式ではなく、保存時の計算結果が返る
ただし、ここには条件があります。返ってくるのはExcelが最後に保存したときの計算結果、つまりキャッシュされた値です。
Pythonだけで作ったファイルや、Excelで一度も開いていないファイルには、その計算結果が保存されていません。その場合は None が返ります。
読み取り方の違いを表にしておきます。迷ったらここに戻ってきてください。
| 開き方 | 数式セルから返る値 | 向いている場面 |
|---|---|---|
load_workbook(path) |
数式の文字列 | 数式を保ったまま編集する |
load_workbook(path, data_only=True) |
保存時の計算結果(無ければNone) | データとして値だけ使う |
私の失敗談をひとつ。月次の集計ファイルをPythonで組み立て、その中に数式を書き込んで、別のスクリプトで読み直す仕組みを作ったことがあります。手元では動くのに本番だけ結果が空になり、原因にたどり着くまでに半日かかりました。誰もExcelで開いていないファイルには、計算結果が存在しなかったのです。
計算が必要なら、Python側で計算してから値として書き込む。この順番にしておけば事故は起きません。
pandasとどう使い分けるか¶
Excelを扱うライブラリとしては、pandasの名前も耳にすると思います。どちらを使うべきか迷いますよね。
実は対立するものではありません。pandasが .xlsx を読むとき、裏側ではopenpyxlが動いています。
役割の違いはこう整理できます。集計や分析が主役ならpandas、ファイルそのものの形を触りたいならopenpyxlです。
| やりたいこと | 向いている道具 |
|---|---|
| 平均や合計を出す、条件で絞り込む | pandas |
| 複数の表を結合する | pandas |
| セルの色や罫線を設定する | openpyxl |
| 既存の帳票の一部だけ書き換える | openpyxl |
| シートの追加や削除 | openpyxl |
pandasは表を丸ごとデータとして扱うので、書式や図は落ちてしまいます。逆にopenpyxlで大量データの集計を書くと、コードが長くなりがちです。
データ分析寄りの使い方から知りたい方は、こちらから読んでみてください。【関連記事】pandas入門 データ処理をやってみよう
大きいファイルはread_onlyで開く¶
行数が数万を超えるファイルを普通に開くと、メモリを一気に使います。全セルをオブジェクトとして持つためです。
公式ドキュメントでは、こうした場面のために最適化モードが用意されています。読むだけなら read_only=True です。
wb = load_workbook("大量データ.xlsx", read_only=True)
ws = wb.active
for row in ws.iter_rows(values_only=True):
... # 1行ずつ処理する
wb.close() # read_onlyのときは明示的に閉じる
必要になった分だけ読み込む仕組みなので、メモリの使用量がほぼ一定に保たれます。そのかわり、読み込んだセルは編集できません。
close() を忘れないでください。通常のモードと違い、read_onlyでは明示的に閉じる必要があります。
書き出す側にも Workbook(write_only=True) という対になるモードがあります。行を上から順に追加していくだけなら、こちらも同じようにメモリを節約できます。
今日から使える形にまとめておく¶
ここまでの内容を、実際に書くときの判断だけに絞って整理します。
読むときは load_workbook()、作るときは Workbook()。この2つが入り口です。
セルの中身がほしいときは .value を付ける。行と列は1から数える。この2点さえ体に入れば、初日のつまずきはほとんど消えます。
数式のセルを値として読みたいなら data_only=True を思い出してください。返るのは保存時のキャッシュだという前提を、いつも頭の片隅に置いておきましょう。
そして集計はPython側でやる。openpyxlはあくまで、Excelファイルという入れ物を扱う道具です。
まずは手元にある1つのファイルを開いて、ws.max_row を表示するところから試してみてください。自分の仕事のデータが画面に出た瞬間、Pythonがぐっと身近になるはずです。
ここまでお読みいただきありがとうございました。