質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

2回答

4555閲覧

Pythonで暗号化されたエクセルファイルを更新したい

Rodeo.9071

総合スコア4

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2021/05/20 17:03

Pythonにて暗号化されたエクセルファイルを開き、ファイルの中身を更新し、保存するというアプリを作成したいのですが、エラーが発生し、うまくいきません。
例として、同様のエラーが発生する簡単なプログラム(エクセルファイルの"B2"に"1"を入力する)を作りました。ソースコードとエラー内容は下記の通りです。具体的な対処方法を教えていただけないでしょうか。
よろしくお願いします。
※なお、親モジュール(gui2.py)にて、子のエクセル更新モジュール(main2.py)を呼び出して実行するという関係性は変更しない方針でご教授いただけると幸いです。
※子のエクセル更新モジュール(main2.py)は定刻にファイルを更新するという機能をもっていますが、この機能も残したいです。
※エクセル(test.xlsx)の書き込みパスワードは"abcd"です。
※環境はWindows10 Pro、Pythonのバージョンは3.9です。

python

1# <親モジュール(gui2.py)> 2 3import PySimpleGUI as sg 4import threading 5import time 6import main2 7 8# レイアウト 9layout = [ 10 [sg.Text('実行ボタンを押してください')], 11 [sg.Submit(button_text='実行', size=(20,1))], 12] 13 14# ウィンドウの生成 15window = sg.Window('エクセル更新アプリ', layout) 16 17# 変数の定義 18thread=None 19 20# イベントループ 21while True: 22 event, values = window.read() 23 24 if event is None: 25 print('exit') 26 break 27 28 elif event=='実行': 29 #二重実行防止 30 if not thread: 31 thread = threading.Thread(target=main2.sub, daemon=True) 32 thread.start() 33 print('実行完了') 34 35# ウィンドウの破棄と終了 36window.close()

python

1# <子モジュール(main2.py)> 2import datetime as dt 3import schedule 4import time 5import openpyxl as excel 6import msoffcrypto 7 8def sub(): 9 def job(): 10 # 暗号化されたExcelファイルを指定 11 fin = open("test.xlsx","rb") 12 msfile = msoffcrypto.OfficeFile(fin) 13 # パスワードを指定 14 msfile.load_key(password="abcd") 15 # 復号化したファイルを保存 16 fout = open("test.xlsx","wb") 17 msfile.decrypt(fout) 18 19 # ワークブックを開いて内容を更新、保存 20 book=excel.load_workbook("test.xlsx") 21 sheet=book.active 22 sheet["B2"].value=1 23 book.save("test.xlsx") 24 25 # スケジューラーへのジョブ登録 26 schedule.every().day.at("08:00").do(job) 27 28 29 while True: 30 schedule.run_pending() 31 time.sleep(5)

<エラー内容>
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\threading.py", line 892, in run
self._target(*self.args, **self.kwargs)
File "C:\Users\kaish\OneDrive\デスクトップ\TEST\main2.py", line 25, in sub
schedule.run_pending()
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\schedule_init
.py", line 780, in run_pending
default_scheduler.run_pending()
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\schedule_init
.py", line 100, in run_pending
self.run_job(job)
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\schedule_init
.py", line 172, in run_job
ret = job.run()
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\schedule_init
.py", line 661, in run
ret = self.job_func()
File "C:\Users\kaish\OneDrive\デスクトップ\TEST\main2.py", line 13, in job
msfile.decrypt(fout)
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\msoffcrypto\format\ooxml.py", line 188, in decrypt
if not zipfile.is_zipfile(io.BytesIO(obuf)):
UnboundLocalError: local variable 'obuf' referenced before assignment

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/05/20 22:04

確認なのですが、「Pythonにて暗号化されたエクセルファイル」とのことですが、「シートの保護」または 「ブックの保護」でパスワードを設定しただけのファイルを開こうとされていませんか? msoffcrypto-toolのOfficeFile()で復号できるエクセルファイルは、「名前を付けて保存」の際に出てくるダイアログの「その他のオプション」→ツール→「全般オプション」で読み取りパスワードをかけたMS-XLSXファイルであり、 この読み取りパスワードをかけていない普通のMS-XLSXファイル(シートやブックを校閲メニューでパスワード保護しただけのMS-XLSXファイル) をmsoffcrypto-toolで復号しようとすると、上記のエラーが出る場合があるため、確認する次第です。
Rodeo.9071

2021/05/21 00:40

コメントありがとうございます。 ご指摘のとおり、「名前を付けて保存」→「その他のオプション」→ツール→「全般オプション」で書き込みパスワードを設定しておりました。 作成したいアプリはエクセルファイルの更新を行う必要があり、そのように設定しておりました。 エラーの回避方法などございましたら、ご教授のほどお願いします。
退会済みユーザー

退会済みユーザー

2021/05/21 02:27 編集

細かくて済みませんが、「書き込みパスワード」と「読み取りパスワード」両方設定されていますでしょうか。 (こちらで試したところ、保存時に「書き込みパスワード」だけ設定し、「読み取りパスワード」が空欄のファイルをmsoffcryptoで開こうとした場合、同じエラーが発生しました。 「書き込みパスワード」の有無にかかわらず、「読み取りパスワード」が設定されてあるxlsxファイルであれば、正常に開くことができました。 また、msoffcrypto-toolは、読み取りパスワードを解除することはできても、再度パスワードを設定する機能は持っていないようです。 つまり、python上でmsoffcryptoで復号したファイルを、python上で(openpyxlの book.save()で)保存した時点で、読み取りパスワードも書き込みパスワードもないファイルとして保存されることになるため 再度同じファイルをmsoffcryptoで開くと同じエラーが発生します。
guest

回答2

0

いろいろとご検討くださり本当にありがとうございます。
現在、ご提案頂いたwin32com.clientを使用した方法で対処しようと考えているのですが、
エラーがでており、うまくいっておりません。
原因など分かりましたら、ご教授頂けないでしょうか。
※なお、事前にpip install pywin32はインストールしております。

ちなみに、「パスワード付きエクセルとしてVBScriptで脳筋で保存」の方法もとってみたのですが、
パスワードを解除して、更新まではできるものの、新たにパスワードが設定されませんでした。
(エラーもでておりません。)

Python初学者につき、ここまでご丁寧にアドバイスして頂いているにもかかわらず、
質問を繰り返してしまい、申し訳ありません。

python

1#main2.py 2 3import datetime as dt 4import schedule 5import time 6import win32com.client 7 8def sub(): 9 def job(): 10 # フルパス必須 11 xlpath = r"C:\Users\kaish\OneDrive\デスクトップ\TEST\test.xlsx" 12 password = 'abcd' 13 XL_OPENXML_WORKBOOK = 51 14 xlApp = win32com.client.Dispatch("Excel.Application") 15 # 確認ダイアログ抑止 16 xlApp.DisplayAlerts = False 17 # パスワードを解除して開く (なお、読み取りパスワードが設定されていなくてもエラーにならない模様) 18 wb = xlApp.Workbooks.Open(xlpath, False, False, None, password) 19 # データ編集 20 sheet = wb.Worksheets(1) 21 sheet.Activate() 22 sheet.Range("A4").Value = "1" 23 24 # パスワード付きで保存する。 25 wb.SaveAs(xlpath, FileFormat=XL_OPENXML_WORKBOOK, Password=password) 26 wb.Close() 27 xlApp.DisplayAlerts = True 28 29 schedule.every().day.at("08:00).do(job) 30 31 while True: 32 schedule.run_pending() 33 time.sleep(5)

python

1#<呼び出し元のモジュール> 2import PySimpleGUI as sg 3import main2 4import threading 5import time 6 7layout = [ 8 [sg.Text('実行ボタンを押してください')], 9 [sg.Submit(button_text='実行', size=(20,1))], 10] 11 12# ウィンドウの生成 13window = sg.Window('エクセル更新アプリ', layout) 14 15thread=None 16 17# イベントループ 18while True: 19 event, values = window.read() 20 21 if event is None: 22 print('exit') 23 break 24 25 elif event=='実行': 26 if not thread: 27 thread = threading.Thread(target=main2.sub, daemon=True) 28 thread.start() 29 print('実行完了') 30 31# ウィンドウの破棄と終了 32window.close()

<エラーメッセージ>
Traceback (most recent call last):
File "C:\Users\kaish\OneDrive\デスクトップ\TEST\gui2.py", line 2, in <module>
import main6
File "C:\Users\kaish\OneDrive\デスクトップ\TEST\main6.py", line 4, in <module>
import win32com.client
File "C:\Users\kaish\AppData\Local\Programs\Python\Python39\lib\site-packages\win32com_init_.py", line 5, in <module>
import win32api, sys, os
ModuleNotFoundError: No module named 'win32api'

投稿2021/05/22 07:51

Rodeo.9071

総合スコア4

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/05/22 08:35

まず、VBscriptによる方法は、回答中にも書きましたが、質問者様の環境では対応していなかったということで、諦めてください。 win32comのエラーに関しては、下記サイトの「対応方法」に記載のコードを試していただけませんでしょうか。 http://blog.livedoor.jp/blackcode/archives/python-win32api-import-error.html
guest

0

ベストアンサー

コメントにも書きましたが、調べたところ、
・「読み取りパスワード」が空欄のファイルをmsoffcryptoで開こうとした場合、質問のエラーが発生する。
・「書き込みパスワード」の有無にかかわらず、「読み取りパスワード」が設定されていなければエラー。
・msoffcrypto-toolは、読み取りパスワードを解除することはできても、再度パスワードを設定する機能は持っていない。

また、
・質問文のように、パスワードを解除して開いた状態で重ねて同じファイルを開こうとするとエラー
・かといって、msfileをつかんでいる状態でdecrypt()を実行する場合、パスワード解除と保存が同時に行われるため、同じファイル名でファイル読み出し->解除->保存ができない

ようなので、

・msoffcrypto-toolは読み取りパスワードの解除だけに使用
・パスワードが解除されたファイルは一時ファイルで保存
・読み取りパスワードが設定されていないときは例外処理でトラップしそのまま開く
パスワード付きエクセルとしてVBScriptで脳筋で保存

という戦略を提案します。

(VBSciptによるファイルオブジェクトの直接操作は環境によってはうまく動かないかもしれません)

なお、set_password()関数のxlpathは、保存先エクセルのフルパスを指定しないとエラーになります。

Windows 10 Pro
Excel 2019 (Microsoft 365)
python 3.6.4
openpyxl 3.0.5
msoffcrypto-tool 4.11.0

にて動作確認

python

1# <子モジュール(main2.py)> 2import datetime as dt 3import schedule 4import time 5import openpyxl as excel 6import msoffcrypto 7import subprocess 8from pathlib import Path 9 10# パスワード付きで保存する。 11# xlpath = エクセルファイルのフルパス(フルパス指定しないとエラー) 12# password = 設定するパスワード 13def set_password(xlpath, password): 14 xlpath = Path(xlpath) 15 s = """Set z=CreateObject("Excel.Application") 16 Set r1=z.Workbooks.Open("{}") 17 z.DisplayAlerts=False 18 z.Visible=False 19 r1.SaveAs "{}",,"{}" 20 z.Application.Quit""".format(xlpath,xlpath,password) 21 p = xlpath.parent.joinpath("_.vbs") 22 with open(p, "w") as file: 23 file.write(s) 24 subprocess.call(['cscript.exe', str(p)]) 25 p.unlink() 26 27def sub(): 28 # 暗号化されたExcelファイルを指定 29 try: 30 with open("test.xlsx","rb") as fin: 31 msfile = msoffcrypto.OfficeFile(fin) 32 # パスワードを指定 33 msfile.load_key(password="abcd") 34 # 復号ファイルを一時ファイル名で保存 35 with open("__temp.xlsx","wb") as f: 36 msfile.decrypt(f) 37 # 一時ファイルを開く 38 book=excel.load_workbook("__temp.xlsx") 39 except UnboundLocalError: 40 # 読み取りパスワードが設定されていないのでそのままワークブックを開く 41 print("読み取りパスワードが設定されていない可能性がありますので、そのままワークブックを開きました。") 42 book=excel.load_workbook("test.xlsx") 43 except Exception as e: 44 print("不明なエラーが発生しました。サイズがゼロ,または壊れているエクセルを開こうとした可能性があります") 45 print(e) 46 return 47 48 sheet = book.active 49 sheet["B2"].value = 2 50 # いったんパス無しで保存。 51 book.save("test.xlsx") 52 # パスワード付きで保存する。 フルパス指定しないとエラー。 53 set_password(r"C:\Docs\test.xlsx", "abcd") 54 55 56sub() 57print("終了しました。")

大変参考にさせていただいた情報:
https://stackoverflow.com/questions/36122496/password-protecting-excel-file-using-python


別のやり方

下記のようにwin32com.clientを使うとだいぶシンプルになります。
(ただ、簡単なデータ編集程度であればopenpyxl無しでデータ操作できますが、
もっと込み入ったことをopenpyxlでやろうとした場合、「パスワード解除して開いたデータをそのままopenpyxlに渡して加工する」ということはできないようです。
・win32com.clientでパスワードを解除して開く
・一時的に別ファイル名でパスワード無しで保存
・一時ファイルをopenpyxlで開きなおして、openpyxlを使ってデータ操作して保存。
・一時ファイルをもう一度
win32com.clientで開いて、パスワードつきで正式なファイル名で保存、
という流れにならざるを得ないかと。)

# 事前に pip install pywin32 が必要 import win32com.client def sub(): # フルパス必須 xlpath = r"C:\Docs\test.xlsx" password = 'abcd' XL_OPENXML_WORKBOOK = 51 xlApp = win32com.client.Dispatch("Excel.Application") # 確認ダイアログ抑止 xlApp.DisplayAlerts = False # パスワードを解除して開く (なお、読み取りパスワードが設定されていなくてもエラーにならない模様) wb = xlApp.Workbooks.Open(xlpath, False, False, None, password) # データ編集 sheet = wb.Worksheets(1) sheet.Activate() sheet.Range("A4").Value = "1" # パスワード付きで保存する。 wb.SaveAs(xlpath, FileFormat=XL_OPENXML_WORKBOOK, Password=password) wb.Close() xlApp.DisplayAlerts = True

投稿2021/05/21 05:06

編集2021/05/21 08:11
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Rodeo.9071

2021/05/22 07:54

回答にてコメントした通りですが、win32com.clientにて対応したところ、 エラーが発生しており、うまくいっておりません。 原因など分かりましたら、ご教授頂ければ幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問