Python Watchdogでファイル監視を自動化:『ポーリング』を卒業し『リスニング』を始めよう

Python tutorial - IT technology blog
Python tutorial - IT technology blog

手動チェックの問題点と『その場しのぎ』の解決策

IT業界に入りたての頃、少し厄介なタスクを任されたことがあります。顧客がFTP経由でサーバーにデザインファイルをアップロードするたびに、すぐにバックアップフォルダにコピーし、チームに通知を送るというものでした。一見簡単そうですが、顧客は深夜2時でも食事中でも、いつでもアップロードしてくる可能性があります。

最初は15分おきにサーバーにリモート接続して確認していましたが、これでは手間がかかる上に漏れも生じます。そこで、while Trueループとtime.sleep(60)を組み合わせた Pythonスクリプトを書き、ファイルリストをスキャンするようにしました。しかし、待ち時間を長くすると遅延が発生し、逆に1秒ごとにスキャンすると、ディスクを読み込み続けるだけでサーバーのCPU使用率が20〜30%に跳ね上がってしまいました。

何度かの失敗を経て、自分から聞きに行くよりも、ファイルシステムに変更があった時に通知してもらう方がはるかに効率的であることに気づきました。

なぜポーリング(定期スキャン)は避けるべきなのか?

プログラミングにおいて、この定期的に確認する手法をポーリング(Polling)と呼びます。OSに対して「新しいファイルはある?」と問い続けることになりますが、これには2つの致命的な欠点があります。

  • I/Oリソースの浪費: 数千ものファイルリストを継続的に読み取るのは非常に重い処理です。ディスクが常に稼働することになり、デバイスの寿命を縮め、他のアプリケーションの動作を遅くします。
  • 遅延(レイテンシ): 5分おきにスキャンする場合、ファイルが処理されるまで最大4分59秒待機することになります。即時性が求められるシステムでは、この遅延は許容できません。

Linux (inotify)、macOS (FSEvents)、Windowsなどの現代的なOSには、イベント駆動(Event-driven)の仕組みが備わっています。こちらから問い合わせる代わりに、ファイルの作成、削除、変更などのイベントが発生した瞬間に信号を受け取るようOSに登録するのです。

Watchdog – プロフェッショナルなファイル監視ツール

PythonのWatchdogライブラリは、このイベント駆動の仕組みを活用するための最適なソリューションです。マルチプラットフォームに対応しており、CPUリソースを極めて低く抑えられます。

Watchdogは主に2つのコンポーネントで構成されています。

  1. Observer: OSからの信号をバックグラウンドで待ち受けるスレッド。
  2. Event Handler: 特定のイベント(on_modifiedon_createdなど)が発生した際のロジックを処理する関数の集合。

基本的な監視ツールの実装

まず、pipを使用してライブラリをインストールします。

pip install watchdog

以下は私がよく使う雛形(ボイラープレート)です。ディレクトリを監視し、変更があった際に即座に通知を表示します。

import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class MyHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if not event.is_directory:
            print(f"ファイル {event.src_path} が更新されました。")

    def on_created(self, event):
        if not event.is_directory:
            print(f"新しいファイルを検出しました: {event.src_path}")

if __name__ == "__main__":
    path = "./my_folder" 
    event_handler = MyHandler()
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    
    print(f"ディレクトリを監視中: {path}...")
    observer.start()
    
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

補足:recursive=Trueパラメータを指定すると、サブディレクトリも監視対象になります。ルートディレクトリのみを監視してメモリ負荷を抑えたい場合は、Falseに変更してください。

応用例:ダウンロードファイルの自動仕分け

スクリプトをアップグレードして、実用的な課題を解決してみましょう。Downloadsフォルダを自動的に整理します。<a href="https://itfromzero.com/ja/python-vi-ja/pillow%e3%81%a7%e7%94%bb%e5%83%8f%e5%87%a6%e7%90%86%e3%82%92%e8%87%aa%e5%8b%95%e5%8c%96%ef%bc%9a%e6%95%b0%e5%8d%83%e6%9e%9a%e3%81%ae%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%e3%82%92%e4%b8%80%e6%8b%ac.html">.jpg</a><a href="https://itfromzero.com/ja/python-vi-ja/python%e3%81%a8pymupdf%e3%81%a7pdf%e3%82%92%e3%80%8c%e7%88%86%e9%80%9f%e3%80%8d%e5%87%a6%e7%90%86%ef%bc%9a%e3%82%b7%e3%83%b3%e3%83%97%e3%83%ab%e3%81%aa%e3%82%b9%e3%82%af%e3%83%aa%e3%83%97%e3%83%88.html">.pdf</a>ファイルがダウンロードされるたびに、対応するフォルダへ自動的に移動させます。

ここでのコツはtime.sleep(1)です。ファイルのダウンロード中、データが完全に書き込まれる前にOSがcreatedイベントを発行することがあります。少し待機することで、ファイル移動時の「ファイルが使用中です」というエラーを回避できます。

実践導入時の重要な注意点

本番環境でスクリプトを安定して動作させるには、以下の3点に注意してください。

  1. イベントループの回避: 監視対象のディレクトリ自体にスクリプトがログを書き込むと、Watchdogがその書き込みを検知して再びイベントを発生させます。これにより無限ループが発生し、スクリプトがフリーズする原因になります。
  2. 例外処理: ファイル操作時には必ずtry-exceptを使用してください。ファイルが他のソフトウェア(Excelなど)にロックされている場合、shutil.moveはプログラム全体をクラッシュさせる可能性があります。
  3. 永続化: Linuxでは、スクリプトをsystemdサービスとして設定しましょう。これにより、サーバーの再起動や予期せぬエラーが発生しても、スクリプトが自動的に再起動されるようになります。

Watchdogを導入したことで、細かなチェック作業に費やしていた時間を毎日少なくとも30〜40分短縮できました。ファイルに関連する繰り返しのワークフローがあるなら、ぜひ今日から監視スクリプトを書いてみてください。

Share: