基礎から学ぶPython入門 90日コース | 実践Python - Day 30:中間プロジェクト

90日で身につけるPython
スポンサーリンク
スポンサーリンク

Day 30:中間プロジェクト「ファイル自動整理ツール」を作る

Day 30では、ここまで学んできた内容を組み合わせて、 ファイル自動整理ツールという中間プロジェクトを作っていきます。

扱う機能は次の通りです。

  • 指定フォルダを監視
  • 拡張子を判定
  • ファイルを分類
  • 自動移動
  • ログ出力
  • エラー処理
  • 設定ファイル対応

「完璧な製品」を目指すのではなく、 学んだ要素をつなげて1つのツールにすることをゴールにします。

全体像をイメージする:どんなツールにするか

ツールのざっくりした動き

今回作る「ファイル自動整理ツール」は、次のような動きをします。

  1. 設定ファイル(例:config.json)を読み込む
    • 監視するフォルダ
    • 拡張子ごとの分類先フォルダ
    • ログレベルなど
  2. ログ設定を行う
  3. 指定フォルダを定期的にチェックする(監視)
  4. 新しく見つかったファイルの拡張子を判定する
  5. 拡張子に応じて分類先フォルダを決める
  6. ファイルを自動で移動する
  7. その過程をログに記録する
  8. エラーが起きた場合はログに残して処理を続ける

「監視」といっても、ここではシンプルに 一定間隔でフォルダを見に行く方式にします。

設定ファイルを用意する:config.json

設定ファイルの役割

設定ファイルには、次のような情報を持たせます。

  • 監視対象フォルダ
  • 拡張子ごとの分類先フォルダ
  • 監視間隔(秒)
  • ログレベル

これにより、コードを変更せずに設定だけ変えることができます。

config.jsonの例

{
  "watch_dir": "watch",          // 監視するフォルダ
  "output_dirs": {
    "images": "sorted/images",   // 画像ファイルの保存先
    "docs": "sorted/docs",       // ドキュメントの保存先
    "others": "sorted/others"    // その他の保存先
  },
  "extensions": {
    "images": [".jpg", ".jpeg", ".png", ".gif"],
    "docs": [".pdf", ".docx", ".txt"]
  },
  "interval_seconds": 5,         // 監視間隔(秒)
  "log_level": "INFO"            // ログレベル
}
JSON

※コメントは説明用ですので、実際のJSONファイルでは削除してください。

ログ設定を行う:loggingの活用

ログの基本設定

ログは、ツールの動きを記録するために使います。 ここでは、コンソールとファイルの両方に出力するシンプルな形にします。

import logging

def setup_logging(log_level: str = "INFO"):
    """ログ設定を行う関数です。"""
    level = getattr(logging, log_level.upper(), logging.INFO)

    logging.basicConfig(
        level=level,
        format="%(asctime)s [%(levelname)s] %(message)s",
        filename="file_sorter.log",  # ログファイル名
        filemode="a"                 # 追記モード
    )

    # コンソールにも出したい場合は、ハンドラを追加する方法もありますが、
    # 初心者向けの段階ではファイル出力だけでも十分です。
Python

設定ファイルを読み込む:JSONの利用

設定読み込み関数

import json

def load_config(path: str = "config.json") -> dict:
    """JSON形式の設定ファイルを読み込む関数です。"""
    with open(path, "r", encoding="utf-8") as f:
        config = json.load(f)
    return config
Python

この関数で、先ほどの config.json を読み込みます。

ファイル分類のロジックを考える

拡張子からカテゴリを決める

設定ファイルには、

  • extensions.images
  • extensions.docs

のように、カテゴリごとの拡張子リストが入っています。

これを使って、「ファイルの拡張子からカテゴリを決める」関数を書きます。

import os
import logging

def get_category_by_extension(filename: str, extensions_config: dict) -> str:
    """
    ファイル名と拡張子設定からカテゴリ名を返す関数です。
    該当するカテゴリがなければ 'others' を返します。
    """
    _, ext = os.path.splitext(filename)  # 拡張子を取得します(例: ".txt")
    ext = ext.lower()

    for category, ext_list in extensions_config.items():
        if ext in ext_list:
            logging.debug(f"{filename}{category} カテゴリに分類されます。")
            return category

    logging.debug(f"{filename} はどのカテゴリにも該当しないため others に分類されます。")
    return "others"
Python

ここでのポイントは、

  • os.path.splitext で拡張子を取り出していること
  • 設定ファイルの extensions を使ってカテゴリを決めていること
  • 該当しない場合は others に分類していること

です。

ファイルを移動する:shutilの利用

ファイル移動関数

ファイルの移動には、shutil.move を使います。

import os
import shutil
import logging

def move_file(src_path: str, dest_dir: str):
    """
    ファイルを指定ディレクトリに移動する関数です。
    必要に応じてディレクトリを作成します。
    """
    os.makedirs(dest_dir, exist_ok=True)  # 移動先ディレクトリがなければ作成します。

    filename = os.path.basename(src_path)
    dest_path = os.path.join(dest_dir, filename)

    try:
        shutil.move(src_path, dest_path)
        logging.info(f"ファイルを移動しました: {src_path} -> {dest_path}")
    except Exception as e:
        logging.error(f"ファイルの移動に失敗しました: {src_path} -> {dest_path} ({e})")
Python

ここでのポイントは、

  • os.makedirs(..., exist_ok=True) で、移動先フォルダがなければ自動作成していること
  • 例外が発生した場合に logging.error でエラー内容を記録していること

です。

指定フォルダを監視する:定期的にチェックする

シンプルな監視ループ

ここでは、一定間隔でフォルダをチェックする方式にします。 高度なファイル監視ライブラリ(watchdogなど)はありますが、 初心者向けの段階では「定期的に見る」だけでも十分です。

import os
import time
import logging

def scan_and_sort_files(config: dict):
    """
    監視フォルダ内のファイルをスキャンして分類・移動する関数です。
    """
    watch_dir = config["watch_dir"]
    output_dirs = config["output_dirs"]
    extensions_config = config["extensions"]
    interval = config.get("interval_seconds", 5)

    logging.info(f"監視フォルダ: {watch_dir}")
    logging.info(f"監視間隔: {interval} 秒")

    # 監視フォルダが存在するか確認します。
    if not os.path.isdir(watch_dir):
        logging.error(f"監視フォルダが存在しません: {watch_dir}")
        return

    while True:
        logging.debug("フォルダをスキャンします...")

        try:
            for entry in os.scandir(watch_dir):
                if entry.is_file():
                    filename = entry.name
                    filepath = entry.path

                    # カテゴリを判定します。
                    category = get_category_by_extension(filename, extensions_config)

                    # カテゴリに対応する出力先ディレクトリを取得します。
                    dest_dir = output_dirs.get(category, output_dirs["others"])

                    # ファイルを移動します。
                    move_file(filepath, dest_dir)
        except Exception as e:
            logging.error(f"フォルダスキャン中にエラーが発生しました: {e}")

        # 次のスキャンまで待機します。
        time.sleep(interval)
Python

ここでの重要ポイントは、

  • os.scandir で監視フォルダ内のファイルを列挙していること
  • entry.is_file() でファイルだけを対象にしていること
  • カテゴリ判定 → 移動 → ログ出力という流れを1つのループで行っていること
  • 例外が発生してもログに残し、ループ自体は続ける設計にしていること

です。

メイン関数を作る:全体の流れをまとめる

main()で「ツールの流れ」を見える化する

ここまで作った関数を組み合わせて、main() を書きます。

# file_sorter.py
# Day 30 中間プロジェクト:ファイル自動整理ツール

import logging
from typing import Dict

from pathlib import Path
import json
import os
import time
import shutil


# --- 設定読み込み ---

def load_config(path: str = "config.json") -> Dict:
    """JSON形式の設定ファイルを読み込む関数です。"""
    with open(path, "r", encoding="utf-8") as f:
        config = json.load(f)
    return config


# --- ログ設定 ---

def setup_logging(log_level: str = "INFO"):
    """ログ設定を行う関数です。"""
    level = getattr(logging, log_level.upper(), logging.INFO)

    logging.basicConfig(
        level=level,
        format="%(asctime)s [%(levelname)s] %(message)s",
        filename="file_sorter.log",
        filemode="a",
    )


# --- ファイル分類ロジック ---

def get_category_by_extension(filename: str, extensions_config: Dict) -> str:
    """
    ファイル名と拡張子設定からカテゴリ名を返す関数です。
    該当するカテゴリがなければ 'others' を返します。
    """
    _, ext = os.path.splitext(filename)
    ext = ext.lower()

    for category, ext_list in extensions_config.items():
        if ext in ext_list:
            logging.debug(f"{filename}{category} カテゴリに分類されます。")
            return category

    logging.debug(f"{filename} はどのカテゴリにも該当しないため others に分類されます。")
    return "others"


# --- ファイル移動 ---

def move_file(src_path: str, dest_dir: str):
    """
    ファイルを指定ディレクトリに移動する関数です。
    必要に応じてディレクトリを作成します。
    """
    os.makedirs(dest_dir, exist_ok=True)

    filename = os.path.basename(src_path)
    dest_path = os.path.join(dest_dir, filename)

    try:
        shutil.move(src_path, dest_path)
        logging.info(f"ファイルを移動しました: {src_path} -> {dest_path}")
    except Exception as e:
        logging.error(f"ファイルの移動に失敗しました: {src_path} -> {dest_path} ({e})")


# --- 監視ループ ---

def scan_and_sort_files(config: Dict):
    """
    監視フォルダ内のファイルをスキャンして分類・移動する関数です。
    """
    watch_dir = config["watch_dir"]
    output_dirs = config["output_dirs"]
    extensions_config = config["extensions"]
    interval = config.get("interval_seconds", 5)

    logging.info(f"監視フォルダ: {watch_dir}")
    logging.info(f"監視間隔: {interval} 秒")

    if not os.path.isdir(watch_dir):
        logging.error(f"監視フォルダが存在しません: {watch_dir}")
        return

    while True:
        logging.debug("フォルダをスキャンします...")

        try:
            for entry in os.scandir(watch_dir):
                if entry.is_file():
                    filename = entry.name
                    filepath = entry.path

                    category = get_category_by_extension(filename, extensions_config)
                    dest_dir = output_dirs.get(category, output_dirs["others"])

                    move_file(filepath, dest_dir)
        except Exception as e:
            logging.error(f"フォルダスキャン中にエラーが発生しました: {e}")

        time.sleep(interval)


# --- メイン関数 ---

def main():
    """ファイル自動整理ツールのメイン関数です。"""
    # 設定ファイルを読み込みます。
    config = load_config()

    # ログ設定を行います。
    setup_logging(config.get("log_level", "INFO"))

    logging.info("ファイル自動整理ツールを開始します。")

    # 監視と整理を開始します。
    scan_and_sort_files(config)

    logging.info("ファイル自動整理ツールを終了します。")


if __name__ == "__main__":
    main()
Python

Day 30のまとめ:ここまでの学びを「1つのツール」にする

Day 30では、中間プロジェクトとして「ファイル自動整理ツール」を作りながら、

  • 設定ファイル(JSON)で環境や分類ルールを管理する
  • logging でツールの動きを記録する
  • osshutil でファイル操作を行う
  • 拡張子判定ロジックでファイルを分類する
  • 監視ループでフォルダを定期的にチェックする
  • 例外処理でエラーをログに残しつつ、ツールを止めないようにする

といった要素を、1つのプロジェクトにまとめていきました。

ここまで来ると、「学んだ機能をバラバラに試す」段階から、 「機能を組み合わせてツールとして動かす」段階に一歩進んでいます。

この中間プロジェクトは、まだまだ改良の余地がたくさんありますが、 「自分で作ったツールが、実際にファイルを整理してくれる」という体験そのものが、 次のステップへの大きな自信につながっていきます。

タイトルとURLをコピーしました