Python自動化 #1 — 繰り返し作業を終わらせる: スクリプトの第一歩とファイル整理

読了 6分

ダウンロードフォルダを開くと、スクリーンショット、インストーラ、PDF の請求書、圧縮ファイルが数ヶ月分たまっています。一度整理するのに 10 分、1 週間でまた元通りです。こうした繰り返し作業こそ、Python が最も早く報いてくれる領域です。このシリーズは、Python の文法基礎だけを知っている人が、実際に動く自動化スクリプトを手にすることを目標にします。モダンPython基礎 を終えたレベルなら十分で、社会人が仕事の合間に追いかけても無理のない分量で構成します。全 7 本の地図は次の通りです。

  • #1 繰り返し作業を終わらせる: スクリプトの第一歩とファイル整理 ← 今回
  • #2 Excel自動化: openpyxl で繰り返しレポートを処理
  • #3 Webスクレイピング基礎: 静的ページからデータを集める
  • #4 Webスクレイピング応用: 動的ページを扱う
  • #5 メールと通知: 結果を自動で受け取る
  • #6 スケジューリング: 毎日勝手に動くようにする
  • #7 CLIツールに仕上げる: シリーズまとめ

自動化スクリプトの形 #

自動化スクリプトは、Web サービスやライブラリとは違う形をしています。ファイル 1 つ、上から下へ読める流れ、そして一番下の if __name__ == "__main__": エントリポイント。この単純な構造が基本形です。エントリポイントブロックの役割は 1 つで、このファイルを直接実行したときだけ下のコードを動かせという目印です。このブロックがないと、別のスクリプトから関数だけ使おうと import した瞬間に整理が実行されてしまいます。自動化スクリプトは再利用する場面がよくあるので、最初からこの形で書く習慣をつけておくのがおすすめです。具体的な形は下の完成例で見ます。

実行する方法 #

今回のコードは標準ライブラリだけを使うので、インストールするパッケージはありません。Python がインストールされていればターミナルから python organize.py で実行できますし、モダンPython基礎 #1 で扱った uv を使っているなら uv run organize.py でも結果は同じです。uv run 側の利点は、後で外部パッケージが必要になったときに現れます。スクリプトの先頭に依存関係を書いておけば uv が一時環境を勝手に組み立ててくれるので、仮想環境の管理なしにスクリプト 1 ファイルだけを持ち歩けます。

完成例: ダウンロードフォルダ整理スクリプト #

ファイルを扱う標準の道具は pathlib です。パスを文字列ではなくオブジェクトとして扱うため、OS が違っても同じコードが動きます。必要な動作は 3 つです。iterdir() でフォルダ内の項目を巡回し、suffix で拡張子を読み、rename() で移動します。

organize.py
from pathlib import Path

RULES = {
    "images": {".jpg", ".jpeg", ".png", ".gif", ".webp"},
    "docs": {".pdf", ".docx", ".xlsx", ".pptx", ".txt"},
    "archives": {".zip", ".tar", ".gz", ".7z"},
    "installers": {".dmg", ".pkg", ".exe", ".msi"},
}


def category_for(item: Path) -> str | None:
    for name, exts in RULES.items():
        if item.suffix.lower() in exts:
            return name
    return None  # ルールにない拡張子は触らない

def organize(target: Path) -> None:
    for item in target.iterdir():
        if not item.is_file():
            continue
        category = category_for(item)
        if category is None:
            continue
        dest_dir = target / category
        dest_dir.mkdir(exist_ok=True)
        item.rename(dest_dir / item.name)
        print(f"{item.name} -> {category}/")

if __name__ == "__main__":
    organize(Path.home() / "Downloads")

流れは単純です。フォルダ内のファイルを 1 つずつ見て、拡張子がルールにあれば該当フォルダを作って移動します。mkdir(exist_ok=True) はフォルダがすでにあってもエラーを出さないオプションで、suffix.lower().PDF のような大文字の拡張子も同じルールで拾うための処理です。ルールにない拡張子はそのまま残すので、知らないファイルを変な場所へ動かす心配はありません。

ミス防止: dry-run と上書き #

ファイルを動かすスクリプトは、一度間違えて実行すると元に戻すのが大変です。そこで先に安全装置を 2 つ付けます。1 つ目は dry-run モードです。実際には動かさず、何をどこへ動かす計画なのかを出力するだけのモードで、新しいフォルダに初めて実行するとき結果を事前に確認できます。2 つ目は上書き防止です。移動先に同名のファイルがすでにあると rename が既存ファイルを上書きしてしまう可能性があるので、同名がある場合は report (1).pdf のように番号を付けて回避します。上のコードの organize を次のように差し替えるだけです。

安全装置を加えた organize
def unique_path(dest: Path) -> Path:
    if not dest.exists():
        return dest
    for n in range(1, 1000):
        candidate = dest.with_name(f"{dest.stem} ({n}){dest.suffix}")
        if not candidate.exists():
            return candidate
    raise RuntimeError("使えるファイル名がありません")

def organize(target: Path, dry_run: bool = False) -> None:
    for item in target.iterdir():
        if not item.is_file():
            continue
        category = category_for(item)
        if category is None:
            continue
        dest_dir = target / category
        dest = unique_path(dest_dir / item.name)
        if dry_run:
            print(f"[計画] {item.name} -> {category}/{dest.name}")
            continue
        dest_dir.mkdir(exist_ok=True)
        item.rename(dest)
        print(f"{item.name} -> {category}/{dest.name}")

dry_run=True で呼び出すと出力だけして止まります。ファイルに触る自動化は、「計画を先に見て、確認してから実行する」という順序が基本です。

argparse で対象フォルダを受け取る #

今はダウンロードフォルダがコードの中に固定されています。整理するフォルダを実行時に引数として渡せるよう argparse を付けます。標準ライブラリなのでインストール不要で、ヘルプまで自動で作ってくれます。

コマンドライン引数の処理
import argparse

def main() -> None:
    parser = argparse.ArgumentParser(description="拡張子ごとにファイルを分類して整理します")
    parser.add_argument("target", type=Path, help="整理するフォルダのパス")
    parser.add_argument("--dry-run", action="store_true", help="移動せずに計画だけ出力")
    args = parser.parse_args()
    organize(args.target, dry_run=args.dry_run)

if __name__ == "__main__":
    main()

type=Path を指定したので、引数は文字列ではなく Path オブジェクトとしてそのまま入ってきます。これでどのフォルダでも対象に指定でき、--dry-run フラグでプレビューもできます。

使い方
python organize.py ~/Downloads --dry-run   # [計画] invoice.pdf -> docs/invoice.pdf
python organize.py ~/Downloads             # invoice.pdf -> docs/invoice.pdf

一歩先の応用: 月別フォルダと古いファイルの保管 #

拡張子での分類に慣れたら、同じパターンで変形が作れます。分類基準を拡張子からファイルの更新時刻に変えれば、月別整理スクリプトになります。organize の中の category_for(item) を次の関数に差し替えるだけです。

月別フォルダ分類
from datetime import datetime

def month_folder(item: Path) -> str:
    mtime = datetime.fromtimestamp(item.stat().st_mtime)
    return mtime.strftime("%Y-%m")  # 2026-07 形式のフォルダ名

古いファイルの保管も同じ材料で作れます。datetime.now() - timedelta(days=90) より更新時刻が古いファイルだけを絞り込んで archive フォルダへ動かせば、保管スクリプトになります。巡回して、条件で絞って、動かすという 1 つのパターンの中で、分類基準だけを差し替える構造です。このパターンが手になじめば、ファイルを扱う自動化の大半を同じ枠組みで作れます。

まとめ #

今回作ったものを整理します。

  • 自動化スクリプトの基本形: 1 ファイル、上から下へ、if __name__ == "__main__": エントリポイント
  • pathlibiterdir, suffix, rename で作ったダウンロードフォルダ整理スクリプトと、dry-run・unique_path の 2 つの安全装置
  • argparse で対象フォルダとフラグを引数として受け取る構造
  • 分類基準を差し替えて月別整理・古いファイル保管へ広げるパターン

次回(#2 Excel自動化)では、社会人の繰り返し作業の代表格である Excel を扱います。openpyxl で複数のファイルを読んで 1 つにまとめ、書式を整えてレポートを作り上げる過程まで進めます。

X