PythonでExcelの値を取得する方法

お久しぶりです。EFです。
今回はPythonExcelのデータを取得する方法をまとめておきます。
値を取得できると、そこから別フォーマットに変換できたりします。

環境

Windows 10
Python 3.7.1
・pywin32

pywin32とは

pywin32はCOMオブジェクトを介してExcelを操作できるモジュールです。

導入方法

コマンドプロンプトで以下を実行します。

pip install pywin32

使い方

# -*- coding: utf-8 -*-

import os
import win32com.client as win32

# Excelファイルは以下の想定です
#         A               B
# ┌───────────────┬───────────────┐
# │      名前     │       値      │ # 1行目
# ├───────────────┼───────────────┤
# │   アイテム1    │        100    │ # 2行目
# ├───────────────┼───────────────┤
# │   アイテム2    │        200    │ # 3行目
# ├───────────────┼───────────────┤
# │   アイテム3    │        300    │ # 4行目
# ~~~~~~~~~~~~~~~~~~~
# ~~~~~~~~~~~~~~~~~~~
# │  アイテム999   │      99900    │ # 1000行目
# ├───────────────┼───────────────┤
# │  アイテム1000  │     100000    │ # 1001行目
# └───────────────────────────────┘

try:
    # COMを利用してExcelに接続
    app = win32.Dispatch("Excel.Application")

    # エクセルファイルを開く
    path = os.path.abspath("./test.xlsx")
    book = app.Workbooks.Open(path)

    # シートを取得
    # book.Worksheets("シート1") のように文字列でも取得可能.
    sheet = book.Worksheets(1) 

    # セルを取得
    wk = sheet.UsedRange          # 使用範囲を取得
    columns = wk.Columns.Count    # 行数
    rows = wk.Rows.Count          # 列数

    for y in range(1,rows+1):                # セルは1から始まるので注意
        for x in range(1,columns+1):      # セルは1から始まるので注意
            # セルから値を取得
            print(sheet.Cells(y, x).Value)
    
finally:
    # ブックを閉じ、COMを終了させる
    book.Close(SaveChanges=False)
    app.Quit()
    del app

これで値は取得できます。
ですが、上記のコードを実行すると非常に時間がかかることがわかります。このままではダメなので高速化を行います。

高速化

for文で表示しているところを計測します。処理のばらつきを考慮し、3回行った平均にします。

# 3回の平均を計測
N = 3
times = []
for idx in range(N):
    start = time.perf_counter()
    for y in range(1,rows+1):
        for x in range(1,columns+1):
            print(sheet.Cells(y, x).Value)
    times.append(time.perf_counter() - start)

print("time {:.7f} sec".format(sum(times)/len(times))) #秒

結果は 6.5105476秒 でした。
 
遅い原因はCells関数が現在のプロセスからExcelのプロセス(EXCEL.EXE)にアクセスし、大量のオーバーヘッドを発生をさせているためです。

なのでCells関数を使わない方法でアクセスします。

cells = wk.Value
for y in range(0,rows):
	for x in range(0,columns):
		print(cells[y][x])

wk.ValueExcelのプロセスからシートのデータを取得しています。
なので、それをローカルのメモリにコピーすることで高速に値を取得できます。
 
こちらも同じように3回の平均を計測したところ、0.1483422秒 でした。
速度が97.7%早くなり、高速化に成功しました。

最後に

お手軽にExcelの値を取得することができました。
あとはstructモジュールを使用すれば、バイナリデータも作成できます。
また、.xlsxはバイナリデータなので中間ファイルを吐き出せばテキストベースで管理できます。