フォルダ同期は「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 の方が読みやすく保守性が高い
