Pythonでopenpyxlを使ってExcelファイルをCSVに変換する方法

プログラマーの尾関です。

ゲーム開発の現場では、Excelファイル(.xlsx)をCSVファイルに変換する作業がよく発生します。手作業では手間がかかりますが、Pythonのopenpyxlライブラリを使えば自動化が簡単にできます。

この記事では、openpyxlのインストールから、サンプルデータの作成、そしてExcelからCSVへの変換方法までを順を追って解説します。

目次

openpyxlのインストール

Pythonの標準機能では Excel を読み込むのは難しいので、openpyxlをインストールします。

ターミナル(コマンドプロンプト)で以下のコマンドを実行します。

pip install openpyxl

正常にインストールできると「Successfully installed openpyxl-x.x.x」というメッセージが出力されます。

インストールが完了したら、Pythonでimport openpyxlがエラーなく動作することを確認しましょう。

# この記述だけして実行できるかどうかを確認.
import openpyxl

print("Hello openpyxl!")

テストデータ (Excel) の作成

Excelでテストデータを作成します。

ここでは以下のようなデータとしました。

  • ファイル名: Sample.xlsx
  • シート名: Enemy

openpyxlでExcelを読み込む

openpyxlを使ってExcelを読み込むには「openpyxl.load_workbook()」を使用します。これはワークブック (Excelファイル全体) を開く処理となります。

ここで欲しいのは “Enemy” シートなので、ブックからシート情報を取り出します。今回は入力されている行すべてを取り出したいので、iter_rows() を使うと便利です。

上記のことをコードにすると以下の通りとなります。

# coding: utf_8
import openpyxl

def main():
  # Sample.xlsxを読み込む.
  wb = openpyxl.load_workbook('Sample.xlsx')
  # シートを取得する.
  sheet = wb["Enemy"]
  
  s = "" # 出力テキスト.
  for line in sheet.iter_rows(values_only=True):
    for idx, d in enumerate(line):
      if idx == 0:
        s += str(d)
      else:
        s += "," + str(d) # カンマ区切りにする.
    s += "\n"
  
  # 確認用.
  print(s)
  
  # 出力するCSVファイル.
  f = open("Sample.csv", "w")
  f.write(s)
  f.close()

if __name__ == "__main__":
  main()

もし指定のセルのみ取得したい場合は、cell([row], [col]).value で値を取得することができます。

# coding: utf_8
import openpyxl

def main():
  # Sample.xlsxを読み込む.
  wb = openpyxl.load_workbook('Sample.xlsx')
  # シートを取得する.
  sheet = wb["Enemy"]
  
  print(sheet.cell(2, 3).value) # 2行目,3列目の値を取得.

if __name__ == "__main__":
  main()

おしまい

openpyxlを使えば、Excelファイルの読み書きやCSVへの変換が簡単に自動化できます。

以上、ゲーム開発の効率化のお役に立てれば幸いです。

おまけ: UEデータテーブル用 CSV変換ツール

Unreal Engineでデータテーブルにインポートするために、ExcelをCSVに変換するツールを作成したので添付しておきます。

このPytyonスクリプトは CMDシートに定義されているリストから対象のシートをCSVファイルとして出力するツールです。

例えばこのようなデータがあります。

これをコンバートすると以下のCSVに変換されます。

Pythonスクリプト全文

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ====================================================================
# ExcelからCSVにするツール
# --------------------------------------------------------------------
# @note 実行には openpyxl のインストールが必要
# >pip install openpyxl
# ====================================================================
import sys
import csv
from tkinter import messagebox
from openpyxl import load_workbook
import traceback
from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet
from typing import List

# 定数定義.
# ■コマンドシート関連.
# コマンドシート名.
CMD_SHEET_NAME = "CMD"
# コマンドシートの情報.
# 1列目はコンバート対象のシート名 (openpyxlのvalues_only=Trueは0始まり).
CMD_COL_SHEET = 0
# 2列目は出力するCSVファイル名.
CMD_COL_CSV = 1

# ■各データシート関連.
# 1行目は有効無効を判定するヘッダ行.
DATA_ROW_HEADER = 1
# 2行目は列名を保持する行.
DATA_ROW_COLUMN_NAME = 2
# 有効無効の文字列表記.
DATA_VALID_TRUE = '<1>'
DATA_VALID_FALSE = '<0>'

# コマンド情報を格納するクラス.
class CmdInfo:
	def __init__(self, sheet_name: str, csv_name: str):
		# コンストラクタ.
		self.sheet_name = sheet_name
		self.csv_name = csv_name
		# csv_nameの拡張子が.csvでなければエラーとする.
		if not self.csv_name.endswith('.csv'):
			raise ValueError(f"CSVファイル名は '.csv' で終わる必要があります: {self.csv_name}")

	def is_valid(self) -> bool:
		# csv_nameの拡張子が.csvでなければ無効.
		# どちらかが空文字だったら無効とする.
		return bool(self.sheet_name and self.csv_name.endswith('.csv'))

	def __repr__(self):
		# 表示用文字列.
		return f"CmdInfo: {self.sheet_name} -> {self.csv_name}"
	
# CMDシートをパースしてコマンドのリストを返します.
def parse_cmd_sheet(ws_cmd: Worksheet) -> List[CmdInfo]:
	# 1列目が有効な行をCMD情報としてパースします.
	cmds: List[CmdInfo] = []
	for row in ws_cmd.iter_rows(values_only=True):
		if row[CMD_COL_SHEET] and row[CMD_COL_CSV]:
			cmd_info = CmdInfo(sheet_name=str(row[CMD_COL_SHEET]), csv_name=str(row[CMD_COL_CSV]))
			if cmd_info.is_valid():
				cmds.append(cmd_info)
	return cmds

# データのヘッダ情報を格納するクラス.
class DataHeader:
	def __init__(self):
		# 有効な列と無効な列をリストとして保持する.
		self.valid_columns :List[bool] = []
		# 行名を保持する.
		self.column_names :List[str] = []
		# データ開始行.
		self.start_row = 0
	def append_valid_column(self, b :bool):
		# 有効な列を追加します.
		self.valid_columns.append(b)
	def append_column_name(self, name: str):
		# 列名を追加します.
		self.column_names.append(name)
	def get_first_valid_column_index(self) -> int:
		# 有効な列の先頭の列番号を取得します.
		for idx, valid in enumerate(self.valid_columns):
			if valid:
				return idx
		return 0
	def parse_header(self, ws: Worksheet):
		# ヘッダ行を解析して有効な列と列名を設定します.
		for idx, row in enumerate(ws.iter_rows(min_row=DATA_ROW_HEADER, max_row=DATA_ROW_COLUMN_NAME, values_only=True), start=DATA_ROW_HEADER):
			if idx == DATA_ROW_HEADER:
				# 1行目は有効無効を判定するヘッダ行.
				for col in row:
					if str(col) == DATA_VALID_TRUE:
						# 有効な列.
						self.append_valid_column(True)
					else:
						# 無効な列.
						self.append_valid_column(False)
			elif idx == DATA_ROW_COLUMN_NAME:
				# 2行目は列名を保持する行.
				for col, valid in zip(row, self.valid_columns):
					if valid:
						self.append_column_name(str(col))
					else:
						self.append_column_name('')

		# データ開始行を設定.
		self.start_row = DATA_ROW_COLUMN_NAME + 1

	def __repr__(self):
		# 表示用文字列.
		return f"有効列={self.valid_columns},\nカラム名={self.column_names})"

# 指定されたシートをパースしてCSVに変換します.
def convert_sheet_to_csv(wb: Workbook, cmd: CmdInfo):
	# シート名が存在するか確認.
	if cmd.sheet_name not in wb.sheetnames:
		raise ValueError(f"'{cmd.sheet_name}' シートが見つかりません。")
	# シートを取得.
	ws: Worksheet = wb[cmd.sheet_name]

	# ヘッダの解析.
	header: DataHeader = DataHeader()
	header.parse_header(ws)
	print(f"ヘッダ情報: {header}")

	# ヘッダー行の作成.
	headers: List[str] = []
	for valid, name in zip(header.valid_columns, header.column_names):
		if valid:
			# 有効な列のみを出力.
			headers.append(name)

	# データ本体の抽出.
	get_first_valid_column_index: int = header.get_first_valid_column_index() # 有効な列の先頭の列番号を取得.
	data_rows: List[List[str]] = []
	for row in ws.iter_rows(min_row=header.start_row, values_only=True):
		# コメント行かどうかを確認.
		if row[0] is not None and str(row[0]).startswith('#'):
			# 0列目がコメント行の場合はスキップ.
			continue
		# データ行かどうかを確認.
		if row[get_first_valid_column_index] is None:
			continue # 空行はスキップ
		# 有効な行かどうかを確認 (#で始まる行は無効).
		row_0 = str(row[get_first_valid_column_index])
		if (len(row_0) <= 0 or row_0.startswith('#')):
			continue # 無効な行はスキップ

		# データ行を出力用に整形.
		row_data: List[str] = []
		for idx, valid in enumerate(header.valid_columns):
			if valid:
				value = row[idx]
				if value is None:
					value = ''
				else:
					value = str(value).strip()
				row_data.append(value)
		print(row_data)
		data_rows.append(row_data)


	# CSVに書き出し
	with open(cmd.csv_name, 'w', newline='', encoding='utf-8-sig') as csvfile:
		writer = csv.writer(csvfile, quoting=csv.QUOTE_MINIMAL)
		writer.writerow(headers)  # ヘッダー行
		writer.writerows(data_rows)

	print(f"変換完了: {cmd.csv_name}")


def main():
	# 起動引数からExcelファイル名を取得.
	if len(sys.argv) > 1:
		input_excel = sys.argv[1]
	else:
		# エラー
		raise ValueError("Excelファイル名を指定してください。例: python excel_to_csv.py Sample.xlsx")

	# Excelファイルの読み込み
	wb: Workbook = load_workbook(input_excel, data_only=True)
	# CMDシートからコンバート情報を取得します.
	# CMDシートが存在しない場合はエラーになります。
	if CMD_SHEET_NAME not in wb.sheetnames:
		raise FileNotFoundError(f"'{input_excel}' に '%s' シートが見つかりません。"%CMD_SHEET_NAME)
	ws_cmd: Worksheet = wb[CMD_SHEET_NAME]
	# コマンドシートをパースしてコマンド情報を取得します.
	cmds: List[CmdInfo] = parse_cmd_sheet(ws_cmd)
	if not cmds:
		raise ValueError(f"'{CMD_SHEET_NAME}' シートに有効なコマンドが見つかりません。")
	print("コマンド情報:")
	for cmd in cmds:
		print(" - ", cmd)
	   
	# 各コマンドに対して処理を行います.
	for cmd in cmds:
		print(f"シート名: {cmd.sheet_name}, CSVファイル名: {cmd.csv_name}")
		convert_sheet_to_csv(wb, cmd)

if __name__ == "__main__":
	try:
		main()
	except Exception as e:
		# エラー発生.
		print(traceback.format_exc())
		messagebox.showerror("エラー発生", traceback.format_exc())

\ 最新情報をチェック /