🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Python 3.x

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

Q&A

解決済

1回答

4021閲覧

Pythonでのフォルダ監視(watchdog): ファイル移動が達成されているが、元フォルダにファイルが残っていることがある!

saya24

総合スコア246

Python 3.x

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

0グッド

0クリップ

投稿2021/03/06 13:21

編集2021/03/07 06:35

クラウドのシステム向けに 社内データを送信する運用環境が整っています。
.txtでのファイルを送信し終えると、.csvのファイルが 同フォルダに提供されます。

この送信データであるTEXTファイルと処理結果のCSVファイルの自動退避、及び処理結果であるCSVファイルをメール送信しようと考え、経験浅いながらPythonで以下を作ってみました。(ネット記事切貼りとこちらでの支援により)

送信すべきTEXTファイルを、このプログラムで監視しようとするフォルダに配置した上、送信処理手前で起動しています。 
つまりは監視下にあるフォルダに CSVファイルの新規発生が メール送信・退避移動の発火にした作りです。 

python

1from watchdog.events import FileSystemEventHandler 2from watchdog.observers import Observer 3import os 4import time 5 6from email.mime.text import MIMEText 7from email.mime.application import MIMEApplication 8from email.mime.multipart import MIMEMultipart 9from email.mime.base import MIMEBase 10from email.header import Header 11from email import policy 12from email import encoders 13from os.path import basename 14from email.utils import formatdate 15import os 16import sys 17import smtplib 18 19import datetime 20import shutil 21 22arges = sys.argv 23argcnt = len(arges) -1 #0番目のコマンドライン引数は当該プログラムのパスになるため 24 25class Email(): 26 def __init__(self): 27 28 self.mlsrv = "" 29 self.mlsrv_port = 587 30 self.mlsrv_id = "" 31 self.mlsrv_pw = "" 32 self.mlsrv_fraddress = "@.com" 33 34 35 def send_Mail(self, toaddress, ccaddress, bccaddress, subject, filepath): 36 try: 37 38 #メール送信の処理 割愛 39 40 except Exception as e: 41 42 43 finally: 44 return result 45 46 47 48class BackUp(): 49############################################################################################### 50# 送信処理が終わると、処理結果がCSVで 同フォルダに提供される⇒⇒⇒メール送信後に送信データと併せ退避 51 52 # 監視フォルダと 退避先フォルダが引数 53 def move_files(self, src_dir, dst_dir): 54 55 dt_now = datetime.datetime.now() 56 dst_dir = dst_dir + "/" + dt_now.strftime('%Y%m%d%H%M%S') 57 os.makedirs(dst_dir, exist_ok=True) #指定の退避先フォルダ内に日付時間フォルダを作成 58 59 for p in os.listdir(src_dir): #監視フォルダ内コレクションループ 60 fullpath = os.path.join(src_dir, p) #ファイルのフルパスを取得 61 62 wcnt = 0 #この関数動作由来の新規ファイルはサイズ0の可能性あり-->待機 63 while True: 64 size = os.path.getsize(fullpath) 65 if size == 0 : 66 wcnt += 1 67 if wcnt > 1800 : #無限ループ対策(30) 68 break 69 time.sleep(3) 70 else : 71 break 72 73 base, ext = os.path.splitext(p) 74 if ext == ".csv" : #サイズ1以上CSVファイルは処理結果内容-->メールで添付送信 75 if argcnt < 5 : 76 if argcnt == 4 : 77 result = mail.send_Mail(引数の数から宛先のみの指定) 78 elif argcnt == 3 : 79 result = mail.send_Mail(引数の数から宛先・CCのみの指定) 80 else : 81 result = mail.send_Mail(引数の数から宛先・CC・BCC全てを指定) 82 83 wcnt = 0 84 while True: 85 wcnt += 1 86 if wcnt > 1800 : #無限ループ対策(30) 87 break 88 try : 89 shutil.move(os.path.join(src_dir, p), dst_dir)#★移動できれば使用中でないと判断★ 90 break 91 except Exception as e: 92 time.sleep(1) #★移動できなければ送信中と判断、また移動をリトライ★ 93 94 95#★★★監視フォルダ内全てのファイルが移動退避し終えたら 関数を終わらせてアプリも閉じたい★★★ 96 97''' 98###################################################################################################### 99# # 100# # 101# # 102###################################################################################################### 103''' 104class ChangeHandler(FileSystemEventHandler): 105 def __init__(self, observer): 106 self.observer = observer 107 108 def on_created(self, event): 109 110 if argcnt < 5 : 111 if argcnt == 4 : 112 backup.move_files(arges[3], arges[4]) 113 elif argcnt == 3 : 114 backup.move_files(arges[2], arges[3]) 115 else : 116 backup.move_files(arges[4], arges[5]) 117 118 119 self.observer.unschedule_all()#★★ 監視フォルダに送信ファイル残ることがある。でも退避もされている! 120 self.observer.stop() #★★ しかも終わってくれない(新たな監視状況がつくりだされているような感じ) 121 122 123 124if __name__ in '__main__': 125 mail = Email() 126 backup = BackUp() 127 128 if argcnt < 5 : 129 if argcnt == 4 : 130 target_dir = arges[3] 131 elif argcnt == 3 : 132 target_dir = arges[2] 133 elif argcnt <= 2 : 134 print("引数不足、起動できません") 135 sys.exit(9) 136 else : 137 target_dir = arges[4] 138 139 140 observer = Observer() 141 event_handler = ChangeHandler(observer) 142 observer.schedule(event_handler, target_dir, recursive=True)#特定のフォルダを引数に与えて監視スタート 143 observer.start() 144 try: 145 while observer.is_alive(): 146 time.sleep(0.1) 147 except KeyboardInterrupt: 148 observer.unschedule_all() 149 observer.stop() 150 observer.join() 151

質問

元フォルダにファイルが残ってしまっている状況が発生しています。データの種類によって、元フォルダがクリアされている・されていないの違いがあるようです。
しかし 退避先フォルダには 元フォルダへ残ってしまったファイルも必ず配置されていることが確認できています。

本来メール報告とファイル退避を終えたら 【当該アプリケーション自体】を終わらせることを望んでいますが、先の問題が生じるパターンでは いつになっても終わらない問題も生じています。---->新たな監視状況が作られてしまっているような感じ?! でしょうか.....

これは 上記のコードで 一体何が悪く このような問題が起きてしまっているのでしょうか? どなたかご教示を頂けますと幸いです、よろしくお願い申し上げます。

追記20210307 10:10

ベンダ様から提供されているデータ送信処理のバットファイルに、自分のアプリが前もって起動されるよう挿入したのですが
データ種類の違いで データ送信処理の実装方法が異なるようです。このことから監視フォルダに生成されるCSVファイルの生成・使用タイミング(共有状況)に違いを生んでいる可能性があります。以下バットファイルの【違い】を表しました。
フリーの監視ソフトによって 送信すべきデータファイルが所定フォルダに配置されると 以下のバットファイルが動作するようになっていますが、これが再度実施されていて 自分のアプリケーションも再起動されている可能性があります....(でもフリーの監視ソフトにその記録が残っていない)

【問題ないパターン↓】

bat

1@ECHO OFF 2rem ######################################################### 3rem # 4rem ######################################################### 5 6START D:\今回作ったプログラム 引数1, 引数2, 引数3 7 8rem 送信処理、本来このコマンド行のみのバットファイルだが上記START文を追加 9 10UPSERT.bat "ITEMMASTER_upsert" 11```   12 13【問題あるパターン↓】 14```bat 15@ECHO OFF 16rem ######################################################### 17rem # 18rem ######################################################### 19 20START D:\今回作ったプログラム 引数1, 引数2, 引数3 21 22rem 送信処理、本来このコマンド行のみのバットファイルだが上記START文を追加 23DELETE_INSERT.bat "STOCKS_export" "STOCKS_delete" "STOCKS_insert"

追加報告 20200307 11:40

送信データが移動元フォルダに残ってしまう事象につき、また当該アプリケーションが動作し続けることから以下を試行
①フリーソフトの当該バットファイルの実装設定(検知設定)を前回実行から3分過ぎない限り再実施無理なよう対応。
=> 効果なし、フリーソフトのログに当初から2重実行されている記録は残っていないので納得の結果

②自アプリケーション内で 新規ファイル察知(1バイト以上)後の メール・退避動作までの待機時間3分を挿入。
=>効果なし、相変わらず移動元フォルダに送信データ残るが 退避先フォルダには処理結果CSVと共に保管されている。

一体何が起きているのだと思い、移動元フォルダに残ったファイルを コマンドプロンプトから削除を試行
イメージ説明

アクセスが拒否されました、と言われながら 移動はできた....状況ですね。
ベンダ様提供のプログラムが C#やVB.NETでいうUsingみたいなものを使っていない、というこでしょうか?
何か 当方のPython側で どうにか対策できないでしょうか

20210307 14:35 qnoirさん提供プログラム、デバッグ実行の結果

イメージ説明

20210305 15:35 qnoirさん提供プログラム実行結果

【1回目↓】

log

12021-03-07 15:21:11,822 | Started to observe. [path : .\20210307_152111_6038_log.txt] 22021-03-07 15:21:35,150 | on_created() : C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv 32021-03-07 15:21:38,384 | on_modified(): C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv 42021-03-07 15:21:46,792 | on_modified(): C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv

【2回目↓】

log

12021-03-07 15:25:08,158 | Started to observe. [path : .\20210307_152508_5250_log.txt] 22021-03-07 15:25:34,611 | on_created() : C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv 32021-03-07 15:25:38,299 | on_modified(): C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv 42021-03-07 15:25:49,393 | on_modified(): C:\Tools\SalesforceConnect\data\SFA09\Reservation_insert_error.csv

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2021/03/06 15:17 編集

念のため確認ですが、退避先(コピー先 dst_dir)は、監視元フォルダ(target_dir = src_dir)の子フォルダではない、という理解でいいでしょうか? ( observer.schedule(event_handler, target_dir, recursive=True) で recursive=True(再帰)となっているため、 仮にコピー先(退避先)がtarget_dirの子フォルダの場合、コピーすることによってon_createdイベントが追加で発生する気がするため)
saya24

2021/03/06 16:33 編集

qnoirさん ご質問の件、退避先のフォルダは 監視先のフォルダと ドライブさえも異なります。 でもon createdイベントが余計に走ってしまっている雰囲気は同感です。 明日実行パラメータの部分を再度確認してみます
saya24

2021/03/07 03:16

フリーのフォルダ監視ソフトと、自分のアプリケーション(watchdog)でも 同じ対象フォルダが監視されていることに起因しているのだろうか??? フリーのフォルダ監視ソフトは 別サーバから送信すべきファイルが置かれるので、送信処理発火用。 自アプリケーションは 送信処理が終わったことに由来し対応する作業を 自動化するためのもの。 はぁ困ったわ
guest

回答1

0

ベストアンサー

本質的な解決に関する回答ではなりませんが、二重起動の要因について

・フリーの監視ソフトが二重起動している可能性 (質問文の内容から、監視ソフトのログを見る限りではその可能性はなさそう、と理解しました)
・watchlogが同じファイルの作成を二重検知している可能性
・ファイル送信csvは一度しか配置されないはずだが、内部で別の隠しファイルが一時的に作成され、それを検知している可能性

まず上記の切り分けのために、下記コードを、CSVメール・ファイル退避用コードの代わりに起動するようにバッチファイルに記述していただけないでしょうか。

単純に、下のコードの起動以降の、監視対象フォルダ内のファイルの動きを、ログファイルとして表示・記録するものです。

ずっと動き続けるので停止する場合はCtrl+C等で止めてください。

(下記コードを実行すると、下記コードを保存したフォルダにログファイルが作成されます。
ログの保存フォルダを変える場合はlogfiledirを書き換えてください。)

これをテスト実行した結果のシナリオとして下記が考えられます。

1.ログファイルは1回しか作成されない->フリーの監視ソフトは二重起動していない、ということになる。
仮にログファイルに、watchdog側で、把握していなかった別のファイルが密かに作成され、削除されていることが記録されていた場合
→本番処理では、当該ファイルが作成されてもファイルのコピー退避処理を行わないようにすることで対応
(あくまで、送信完了のcsvが作成されたときに限定して退避処理を行うようにする。
どのファイルが作成されたかは、on_created(self, event)関数内で、event.src_pathを使えば取得できます。)



2.ログファイルが2回作成される->ファイル配置時にフリーの監視ソフトが二重起動している可能性があります。
(下記コードでは作成時刻をログファイル名にするようにしているため、フリーの監視ソフトが二重起動し下記watchdogのコードも2回起動したならば、ログファイルは2つ作成されてしまうはずです)

この場合、フリーの監視ソフトを一旦停止(終了)し、フリーの監視ソフトの代わりに下記コードをあらかじめ実行して改めてログをとってみてください。
フリーの監視ソフトが二重起動していた理由がわかるかもしれません。
(フリーの監視ソフトのログで記録できていなかったものが、下記のコードで記録できるかどうかは不明ですが・・・)

python

1import logging 2import os 3import datetime 4import time 5import random 6from watchdog.events import FileSystemEventHandler 7from watchdog.observers import Observer 8 9folder_path = "監視対象のフォルダ" 10 11logfiledir = "." 12sn = str.zfill(str(random.randint(0,10000)),4) 13logfilename = datetime.datetime.now().strftime('%Y%m%d_%H%M%S_'+sn) + "_log.txt" 14 15logfilepath = os.path.join(logfiledir, logfilename) 16logger = logging.getLogger(__name__) 17logger.setLevel(logging.DEBUG) 18 19handler1 = logging.StreamHandler() 20handler1.setFormatter(logging.Formatter("%(asctime)s | %(message)s")) 21 22handler2 = logging.FileHandler(filename=logfilepath, encoding='utf-8') 23handler2.setLevel(logging.DEBUG) 24handler2.setFormatter(logging.Formatter("%(asctime)s | %(message)s")) 25 26logger.addHandler(handler1) 27logger.addHandler(handler2) 28 29 30class ChangeHandler(FileSystemEventHandler): 31 32 def __init__(self): 33 super().__init__() 34 35 def on_created(self, event): 36 if event.src_path != logfilepath: 37 logger.debug('on_created() : %s'%(event.src_path)) 38 39 def on_modified(self, event): 40 if event.src_path != logfilepath: 41 logger.debug(f"on_modified(): {event.src_path}") 42 43 def on_moved(self, event): 44 logger.debug('on_moved() : %s ' % event.src_path) 45 46 def on_deleted(self, event): 47 logger.debug('on_deleted() : %s ' % event.src_path) 48 49event_handler = ChangeHandler() 50observer = Observer() 51 52observer.schedule(event_handler, folder_path, recursive=False) 53 54observer.start() 55logger.debug(f"Started to observe. [path : {logfilepath}]") 56print() 57try: 58 while observer.is_alive(): 59 time.sleep(1) 60except KeyboardInterrupt: 61 pass 62 63observer.stop() 64observer.join() 65logger.debug('Script quit.')

投稿2021/03/07 04:46

編集2021/03/07 06:13
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

saya24

2021/03/07 05:02

qnoirさん、大変親切なご対応を誠にありがとうございます。今回自分が作成したアプリの代替え版として、今からやってみます。 target-dirの変数に 監視フォルダを直書きしたうえで ですね。少々お待ちをくださいませ。 不慣れで時間要してしまうかもですが
saya24

2021/03/07 05:31

本文に今から 貼り付けますが ご提供のプログラムがSyntaxエラーを招くようです。 一度運用環境で試してFatalErrorになったので 試しにVisualStudioでのデバッグ実行を確認したところ 本文に貼り付けた エラーを招きました。 今から画像貼り付けます、すみません....。
saya24

2021/03/07 05:43

import random を入れればよいですかね
退会済みユーザー

退会済みユーザー

2021/03/07 05:44

すみません。修正しました。
saya24

2021/03/07 05:48 編集

いえ、とんでもないです。実は また別のSyntaxエラーが生じております。 これは さすがに見当がつきません.... Message='locale' codec can't encode character '\u5e74' in position 2: Illegal byte sequence Source=C:\Users\xxxxx\source\repos\QNOIR\QNOIR\QNOIR.py スタック トレース: File "C:\Users\xxxxx\source\repos\QNOIR\QNOIR\QNOIR.py", line 15, in <module> logfilename = datetime.datetime.now().strftime('%Y年%m月%d日%H時%M分%S秒'+sn) + "_log.txt"
退会済みユーザー

退会済みユーザー

2021/03/07 06:14 編集

strftimeのパターンに日本語が含まれていたため発生したエラーのようです。 検索するとlocale をインポートしてコード追加すれば対応することができるとの記述もありましたが それでもエラーが出る場合があるとのことで。 念のため一旦コードから日本語を全部除去しました。お手数ですがこれで試していただけますでしょうか。
saya24

2021/03/07 06:36 編集

本当にご親切に至れり尽くせり ありがとうございます、 無事にご提示のプログラム動作しまして、ログの採取が行えました。 2回試しました結果を いまから本文に貼り付けますね。 尚、自動では停止しなかったので タスクマネージャから停止させて頂きました
saya24

2021/03/07 07:00

Reservation_insert_error.csvとは メールに添付されるべき処理結果ファイルで この存在の察知からメール配信と 当該ファイルおよび送信データであるテキストファイルの退避を 予定しています。 余計なファイルは 生成されていない、という結果ですかねぇ
退会済みユーザー

退会済みユーザー

2021/03/07 07:18 編集

結果の貼り付けありがとうございます。 結果を見る限りではwatchdog側でも二重起動はしていない(on_createdは2回呼ばれておらず、Reservation_insert_error.csv以外の余計なファイルも特に作られている気配はない)ということのようですね・・・ (on_modifiedが数秒あけて発生していることから、通知ファイルについては最初に空のファイルが作られて、後から10秒~20秒かけて内容が追記されていく、ということのようですね) あと、コピー/移動のところ、前回の質問のコメントで「もし退避対象のファイルがクラウドへ送信中ならばshutilsでmoveさせようとした時点で例外が発生するはず」と申し上げましたが、これも誤りだったようです(実際には、送信中ファイルに対しshutil.move()をかけても例外は発生せず、コピー扱いになる)。申し訳ございません。 「1つ1つのファイルがクラウドへ送信中かどうか」が分からないと、スマートな手段での解決は難しそうですね。 あとは、 ・Reservation_insert_error.csvファイルが届いたことを検知した後、2分待ってReservation_insert_error.csvファイルをメール添付(届いてから2分も経過すればReservation_insert_error.csvは完成しているはずとの前提) ・フォルダへの退避は、移動ではなく、shutillを使ってコピーするだけにして、コピー時に削除対象ファイルのパスを別ファイルに記録しておき、削除処理は、別プログラムで半日後/もしくは一日後に記録を参照して一気に行う。 といったやり方しかなさそうです。 (「何時間たてば確実にaccess_deniedが発生しなくなるか」というのがわかればその時間まで短縮できると思いますが) 本件でwatchdogが終了できない、という原因も今のところ分かりませんでした(それこそexit(0)で終了させるしかないかも) お役に立てずすみません。
saya24

2021/03/07 07:27

>「1つ1つのファイルがクラウドへ送信中かどうか」が分からないと、 データ種類ごとにバットファイルが分かれています。今、問題がおきている部類のデータ種類はクラウド側の同データを クリアしたのち投入する部類で 全入れ替えが行われる部類です。 うまくいくデータ種類(期待の動作を達成するバットファイル)は 社内で変化のあったデータのみを渡す部類で、クラウド側で更新されるべきデータのみを送っている部類です。 やはり まだCSVファイルが書き込みされている段階で 異動されていることが 当該送信データが残る・当該アプリが閉じない ことの問題の本質となると ファイル名のリネームの試行というのは どうですかね??? 現段階では 自分のアプリケーションが終わらなくなってしまっていることが 一番片づけたい課題です。
saya24

2021/03/07 10:17

qnorさん 色々とありがとうございました、心の底より 本当に感謝を申し上げます。 処理結果のCSVファイルのリネームが成功したかしないかで、処理がおえたか否かを判断するようにして(現実終わっているのに送信データのアクセス権が解放されないデータ種類がある解釈)、処理を終えたと判断できたら 従来どおり メールと退避フォルダへの移動を行うようにしてアプリケーションを終わらせるようにしました。 テキストファイルである送信データが移動できたとしても フォルダに残りっぱなしだと、フォルダ内のコレクション(For文)が永遠にループして アプリがいつまでも閉じなかっただけなんですね....。 現況ですが 退避フォルダに処理結果CSVと送信データテキストが無事保管され、アプリも無事に終わるようになった、しかし元フォルダに送信データテキストが残ってしまうデータ種類がある、という状況です。 Pythonで管理者権限でファイル削除って できますか?? はっきりいって送信処理は完全に終わっていますから、問題が生じているデータ種類の送信ファイルは アクセス権が解放されることは永遠になさそう?!時期は想像できません、ベンダさんが提供した処理なので
退会済みユーザー

退会済みユーザー

2021/03/07 10:57

pythonでの管理者権限での削除ですが、いくつか調べてみましたが、それらしいものは探し当てることができませんでした。 お役に立てずすみません。
saya24

2021/03/07 11:18

了解です、調べてもらってすみません。 前回のテキストファイルが監視フォルダに残っていて上書きされただけでも、フリーソフトが バットファイルを発火するよう 調整しましたので 大丈夫です。 本件一件落着とします、この御恩一生忘れません
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問