Python 業務自動化 | ファイル・フォルダ自動化:基本操作 - フォルダ同期

Python Python
スポンサーリンク

フォルダ同期は「2つのフォルダを同じ状態に保つ」ための業務自動化の基礎

バックアップ先フォルダと作業フォルダを常に同じ状態にしたい、
サーバーとローカルのフォルダを同期したい、
更新されたファイルだけコピーしたい、
削除されたファイルも同期したい、
こうしたニーズを満たすのが フォルダ同期(Folder Sync) です。

Python では、更新日時・ハッシュ・存在チェックを組み合わせることで、
「差分だけコピーする」「不要なファイルを削除する」など、柔軟な同期処理が実現できます。

ここでは、初心者でも理解しやすいように、基本から実務テンプレートまで丁寧に解説します。


フォルダ同期の基本構造を理解する

フォルダ同期は次の3つの処理で構成されます。

  • 片方にあってもう片方にないファイルをコピーする
  • 片方で更新されたファイルを上書きコピーする
  • 片方で削除されたファイルをもう片方でも削除する(必要に応じて)

この3つを組み合わせることで「完全同期」が実現します。


更新日時を基準にしたシンプルな同期(最も基本的)

更新日時が新しいファイルだけコピーする

import os
import shutil

def sync_folders(src, dst):
    os.makedirs(dst, exist_ok=True)

    for name in os.listdir(src):
        src_path = os.path.join(src, name)
        dst_path = os.path.join(dst, name)

        if os.path.isfile(src_path):
            if not os.path.exists(dst_path) or os.path.getmtime(src_path) > os.path.getmtime(dst_path):
                shutil.copy2(src_path, dst_path)
                print("コピー:", src_path, "→", dst_path)

sync_folders("source", "backup")
Python

深掘りポイント

  • copy2 はメタ情報(更新日時)もコピーするため同期に向いている
  • 更新日時比較は高速で、一般的な同期処理に十分
  • 「差分コピー」なので無駄がない

サブフォルダも含めて同期する(階層構造対応)

os.walk を使って階層全体を同期

import os
import shutil

def sync_recursive(src, dst):
    for current, dirs, files in os.walk(src):
        rel = os.path.relpath(current, src)
        dst_current = os.path.join(dst, rel)
        os.makedirs(dst_current, exist_ok=True)

        for name in files:
            src_path = os.path.join(current, name)
            dst_path = os.path.join(dst_current, name)

            if not os.path.exists(dst_path) or os.path.getmtime(src_path) > os.path.getmtime(dst_path):
                shutil.copy2(src_path, dst_path)
                print("コピー:", src_path, "→", dst_path)

sync_recursive("source", "backup")
Python

深掘りポイント

  • relpath を使うことでフォルダ構造をそのまま再現
  • 大規模プロジェクトの同期に向いている
  • 更新されたファイルだけコピーするため高速

ハッシュ比較を使った「完全一致」同期(精度重視)

更新日時だけでは「内容が同じか」までは分かりません。
確実に一致させたい場合は ハッシュ比較 を使います。

ハッシュ比較で同期するテンプレート

import os
import shutil
import hashlib

def file_hash(path, chunk=1024*1024):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while chunk_data := f.read(chunk):
            h.update(chunk_data)
    return h.hexdigest()

def sync_with_hash(src, dst):
    os.makedirs(dst, exist_ok=True)

    for name in os.listdir(src):
        src_path = os.path.join(src, name)
        dst_path = os.path.join(dst, name)

        if os.path.isfile(src_path):
            if not os.path.exists(dst_path) or file_hash(src_path) != file_hash(dst_path):
                shutil.copy2(src_path, dst_path)
                print("コピー:", src_path, "→", dst_path)

sync_with_hash("source", "backup")
Python

深掘りポイント

  • ハッシュ比較は「内容が完全一致か」を判定できる
  • 更新日時が同じでも内容が違う場合に対応できる
  • 精度は高いが、ハッシュ計算に時間がかかるため大容量ファイルでは注意

削除同期(片方で削除されたファイルをもう片方でも削除)

完全同期を実現するテンプレート

import os
import shutil

def sync_delete(src, dst):
    for name in os.listdir(dst):
        dst_path = os.path.join(dst, name)
        src_path = os.path.join(src, name)

        if not os.path.exists(src_path):
            if os.path.isfile(dst_path):
                os.remove(dst_path)
                print("削除:", dst_path)
            else:
                shutil.rmtree(dst_path)
                print("フォルダ削除:", dst_path)

sync_delete("source", "backup")
Python

深掘りポイント

  • 「片方で削除されたらもう片方でも削除」=完全同期
  • 誤削除のリスクがあるため、実務ではオプション扱いにすることが多い
  • 削除前にログを残すと安全性が高まる

例題①:業務用バックアップフォルダを毎日同期する

シナリオ

work フォルダを backup に差分同期したい。

sync_recursive("work", "backup")
Python

深掘りポイント

  • 差分同期なので毎日実行しても高速
  • 更新されたファイルだけコピーされる

例題②:サーバーとローカルのフォルダを同期する

シナリオ

ネットワークドライブ(例:Z:\share)とローカルを同期したい。

sync_recursive(r"Z:\share", "local_copy")
Python

深掘りポイント

  • ネットワークドライブは遅いので差分同期が特に有効
  • 更新日時比較で無駄なコピーを避けられる

例題③:同期前に「差分一覧」を表示する(安全性向上)

実際にコピーする前に確認したい場合

def preview_sync(src, dst):
    for current, dirs, files in os.walk(src):
        rel = os.path.relpath(current, src)
        dst_current = os.path.join(dst, rel)

        for name in files:
            src_path = os.path.join(current, name)
            dst_path = os.path.join(dst_current, name)

            if not os.path.exists(dst_path):
                print("新規:", src_path)
            elif os.path.getmtime(src_path) > os.path.getmtime(dst_path):
                print("更新:", src_path)

preview_sync("source", "backup")
Python

深掘りポイント

  • 実務では「同期前に確認」が非常に重要
  • 誤コピーや上書き事故を防げる

pathlib を使った読みやすい同期コード

Path オブジェクトで直感的に書ける

from pathlib import Path
import shutil

def sync_path(src: Path, dst: Path):
    for p in src.rglob("*"):
        rel = p.relative_to(src)
        dst_path = dst / rel

        if p.is_file():
            dst_path.parent.mkdir(parents=True, exist_ok=True)
            if not dst_path.exists() or p.stat().st_mtime > dst_path.stat().st_mtime:
                shutil.copy2(p, dst_path)
                print("コピー:", p, "→", dst_path)

sync_path(Path("source"), Path("backup"))
Python

メリット

  • relative_to() でフォルダ構造を簡単に再現
  • パス結合が / で直感的
  • 可読性が高く、保守しやすい

フォルダ同期を業務で設計するときの視点

  • 更新日時比較は高速で実務向け
  • ハッシュ比較は精度重視だが重い
  • 削除同期は誤削除リスクがあるため慎重に扱う
  • 同期前に「差分一覧」を出すと安全性が高まる
  • ネットワーク同期はリトライやログが重要
  • 大規模同期では pathlib の方が読みやすく保守性が高い

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