Day 30:中間プロジェクト「ファイル自動整理ツール」を作る
Day 30では、ここまで学んできた内容を組み合わせて、 ファイル自動整理ツールという中間プロジェクトを作っていきます。
扱う機能は次の通りです。
- 指定フォルダを監視
- 拡張子を判定
- ファイルを分類
- 自動移動
- ログ出力
- エラー処理
- 設定ファイル対応
「完璧な製品」を目指すのではなく、 学んだ要素をつなげて1つのツールにすることをゴールにします。
全体像をイメージする:どんなツールにするか
ツールのざっくりした動き
今回作る「ファイル自動整理ツール」は、次のような動きをします。
- 設定ファイル(例:
config.json)を読み込む- 監視するフォルダ
- 拡張子ごとの分類先フォルダ
- ログレベルなど
- ログ設定を行う
- 指定フォルダを定期的にチェックする(監視)
- 新しく見つかったファイルの拡張子を判定する
- 拡張子に応じて分類先フォルダを決める
- ファイルを自動で移動する
- その過程をログに記録する
- エラーが起きた場合はログに残して処理を続ける
「監視」といっても、ここではシンプルに 一定間隔でフォルダを見に行く方式にします。
設定ファイルを用意する: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.imagesextensions.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()
PythonDay 30のまとめ:ここまでの学びを「1つのツール」にする
Day 30では、中間プロジェクトとして「ファイル自動整理ツール」を作りながら、
- 設定ファイル(JSON)で環境や分類ルールを管理する
loggingでツールの動きを記録するos・shutilでファイル操作を行う- 拡張子判定ロジックでファイルを分類する
- 監視ループでフォルダを定期的にチェックする
- 例外処理でエラーをログに残しつつ、ツールを止めないようにする
といった要素を、1つのプロジェクトにまとめていきました。
ここまで来ると、「学んだ機能をバラバラに試す」段階から、 「機能を組み合わせてツールとして動かす」段階に一歩進んでいます。
この中間プロジェクトは、まだまだ改良の余地がたくさんありますが、 「自分で作ったツールが、実際にファイルを整理してくれる」という体験そのものが、 次のステップへの大きな自信につながっていきます。

