はじめに
ある日、Pythonでパスワード付きExcelファイル(.xlsx)を開くという案件が舞い込んできました。
最初は、Windows環境でよく使われる pywin32
を使って問題なく操作できていたのですが、別の環境で実行してみると、パスワード入力画面が表示されたまま処理が止まるという現象が発生しました。
この記事では、最終的にたどり着いた 「msoffcrypto + openpyxl」 によるスマートな解決方法を、エラー回避のTipsとともに紹介します。
1. pywin32 での挑戦
import win32com.client
excel = win32com.client.Dispatch("Excel.Application")
workbook = excel.Workbooks.Open("ファイルパス.xlsx", False, True, None, "パスワード")
上記のようなコードで、一部のWindows環境では問題なく開けていたのですが、別の環境ではパスワード入力ダイアログが出て、スクリプトが停止。
環境差異(パッケージやOfficeバージョンなど)を疑いましたが、原因は不明でした。
2. xlwings を試してみた
import xlwings as xw
wb = xw.Book("ファイルパス.xlsx", password="パスワード")
こちらも一見動きそうでしたが、同様にパスワード画面が表示され止まるという結果に。
GUIが絡むツールの限界を感じました。
3. msoffcrypto + openpyxl によるスマートな解決策
最終的に辿り着いたのがこの組み合わせです。
msoffcrypto
でExcelファイルのパスワードを解除し、 openpyxl
で中身を読み込むという流れです。
必要なパッケージのインストール
pip install msoffcrypto-tool openpyxl
サンプルコード
2025年4月12日 NASの場合h失敗する現象を修正
from msoffcrypto.format.ooxml import OOXMLFile
import msoffcrypto
import openpyxl
import io
import os
import tempfile
import shutil
def test_excel_password(file_path, password):
"""
エクセルファイルのパスワード動作を検証する最小限のコード
Args:
file_path (str): エクセルファイルのパス
password (str): パスワード
"""
temp_file = None
decrypted = None
try:
print("1. ファイルを確認します")
print(f" ファイルパス: {file_path}")
print(f" パスワード: {password}")
# ファイルパスの正規化(ネットワークパス対応)
normalized_path = os.path.abspath(os.path.normpath(file_path))
print(f" 正規化されたパス: {normalized_path}")
# 一時ファイルの作成(ローカルディスク上に作成)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f"temp_excel_{os.getpid()}.xlsx")
print(f" 一時ファイル: {temp_file}")
# ファイルが存在しない場合は新規作成
if not os.path.exists(normalized_path):
print("2. 新規ファイルを作成します")
wb = openpyxl.Workbook()
ws = wb.active
ws.title = 'Sheet1'
else:
try:
# まず通常のExcelファイルとして開く
wb = openpyxl.load_workbook(normalized_path)
print("2. 既存の暗号化されていないファイルを開きました")
except:
# パスワード付きファイルの場合
print("2. パスワード付きファイルを開きます")
try:
with open(normalized_path, 'rb') as f:
file = msoffcrypto.OfficeFile(f)
file.load_key(password=password)
decrypted = io.BytesIO()
file.decrypt(decrypted)
# 解除したファイルを読み込む
decrypted.seek(0)
wb = openpyxl.load_workbook(decrypted)
except Exception as e:
print(f"ファイルの読み込み中にエラーが発生しました: {str(e)}")
return False
# シート名を表示
for sheet in wb.sheetnames:
print(f" シート名: {sheet}")
# Sheet1のB1セルに値を書き込む
if 'Sheet1' in wb.sheetnames:
ws = wb['Sheet1']
ws['B1'] = 'bbb'
print("3. Sheet1のB1セルに'bbb'を書き込みました")
else:
print("警告: Sheet1が見つかりません")
print("4. パスワード付きで保存します")
try:
# 一時ファイルに保存
wb.save(temp_file)
# パスワード付きで保存
with open(temp_file, "rb") as plain:
file = OOXMLFile(plain)
with open(normalized_path, "wb") as f:
file.encrypt(password, f)
print("5. 処理が完了しました")
return True
except Exception as e:
print(f"ファイルの保存中にエラーが発生しました: {str(e)}")
return False
except Exception as e:
print(f"エラーが発生しました: {str(e)}")
return False
finally:
# メモリバッファのクリーンアップ
try:
if decrypted is not None:
decrypted.close()
except:
pass
# 一時ファイルのクリーンアップ
try:
if temp_file and os.path.exists(temp_file):
os.remove(temp_file)
except Exception as e:
print(f"一時ファイルの削除中にエラーが発生しました: {str(e)}")
if __name__ == "__main__":
# テスト用のパスとパスワードを設定
test_file_path = r"c:\test.xlsx" # ネットワークパス
test_password = "zxcv" # 実際のパスワードに置き換えてください
print("=== パスワードテスト開始 ===")
print(f"ファイルパス: {test_file_path}")
print(f"パスワード: {test_password}")
test_excel_password(test_file_path, test_password)
まとめ
方法 | メリット | デメリット |
---|---|---|
pywin32 | Windowsでは簡単に使える | 環境依存・パスワード画面が止まる場合あり |
xlwings | Pythonらしい書き方 | 同様に止まるケースあり |
msoffcrypto + openpyxl | GUIなし、安定動作 | 少し手間がかかるが信頼性高い |
最終的に、msoffcrypto + openpyxlの組み合わせが最も安定しており再現性が高いと感じました。
同じような壁にぶつかっている方の助けになれば幸いです!