0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python Tips: パスワード付きExcel (.xlsx) を扱うならコレ!

Last updated at Posted at 2025-04-10

はじめに

ある日、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の組み合わせが最も安定しており再現性が高いと感じました。
同じような壁にぶつかっている方の助けになれば幸いです!

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?