对于长期在电脑本地存放工作资料的人来说,一旦硬盘损坏、电脑丢失或遭遇勒索病毒,数据可能瞬间归零。手动复制粘贴容易遗漏,也难坚持。利用 Python 写一个增量同步脚本,再配合 Windows 任务计划程序,即可实现指定文件夹的定时自动备份。本文围绕这一需求,给出完整的脚本实现与关键代码解析。
增量同步的核心思路是:每次运行时,只复制源目录中新增的、修改过的或大小发生变化的文件,而不是全量拷贝。这样既能节省时间,也能减少磁盘写入。实现上通过对比源文件和目标文件的修改时间、文件大小来决定是否需要复制,使用 shutil.copy2 完成复制并保留文件元数据。
环境方面,脚本只用 Python 标准库,无需安装第三方模块。需要特别提醒的是,shutil、os、pathlib、datetime 都是内置模块,直接 import 即可,不需要执行 pip install shutil,网上有的教程会误导读者去安装一个不存在的 PyPI 包。
下面是完整实现。脚本提供三个方案:增量同步、打包压缩备份、版本化备份。日常推荐使用增量同步,重要节点可配合压缩包和版本历史。
- import os
- import shutil
- import time
- from pathlib import Path
- from datetime import datetime
- # ==================== 方案 1:增量文件同步 ====================
- def sync_folders(source_dir, backup_dir, log_file=None):
- """
- 将源文件夹增量同步到备份文件夹
- 参数:
- source_dir: 源文件夹路径(原始文件)
- backup_dir: 备份文件夹路径(目标位置)
- log_file: 日志文件路径(可选)
- """
- source = Path(source_dir)
- backup = Path(backup_dir)
- if not source.exists():
- print(f"错误:源目录 {source_dir} 不存在!")
- return False
- # 创建备份目录
- backup.mkdir(parents=True, exist_ok=True)
- files_copied = 0
- files_updated = 0
- files_skipped = 0
- total_size = 0
- log_lines = []
- def log(msg):
- print(msg)
- log_lines.append(msg)
- log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 开始同步")
- log(f"源目录: {source}")
- log(f"备份目录: {backup}")
- log("-" * 50)
- # 遍历源目录所有文件
- for file_path in source.rglob('*'):
- if file_path.is_file():
- # 计算相对路径
- rel_path = file_path.relative_to(source)
- dest_path = backup / rel_path
- # 判断是否需要复制
- need_copy = False
- if not dest_path.exists():
- need_copy = True
- action = "新增"
- elif file_path.stat().st_mtime > dest_path.stat().st_mtime:
- need_copy = True
- action = "更新"
- elif file_path.stat().st_size != dest_path.stat().st_size:
- need_copy = True
- action = "大小变更"
- if need_copy:
- # 确保目标目录存在
- dest_path.parent.mkdir(parents=True, exist_ok=True)
- try:
- shutil.copy2(str(file_path), str(dest_path))
- file_size = file_path.stat().st_size
- total_size += file_size
- if action == "新增":
- files_copied += 1
- else:
- files_updated += 1
- log(f" [{action}] {rel_path} ({file_size / 1024:.1f} KB)")
- except Exception as e:
- log(f" [失败] {rel_path}: {e}")
- else:
- files_skipped += 1
- # 汇总报告
- log("-" * 50)
- log(f"同步完成!")
- log(f"新增文件: {files_copied}")
- log(f"更新文件: {files_updated}")
- log(f"跳过文件: {files_skipped}")
- log(f"总复制量: {total_size / 1024 / 1024:.2f} MB")
- # 写入日志
- if log_file:
- Path(log_file).parent.mkdir(parents=True, exist_ok=True)
- with open(log_file, 'a', encoding='utf-8') as f:
- f.write('\n'.join(log_lines) + '\n\n')
- return True
- # ==================== 方案 2:打包压缩备份 ====================
- def backup_as_zip(source_dir, backup_dir):
- """
- 将整个文件夹打包为带日期的 ZIP 压缩包
- """
- source = Path(source_dir)
- backup = Path(backup_dir)
- backup.mkdir(parents=True, exist_ok=True)
- date_str = datetime.now().strftime('%Y%m%d_%H%M%S')
- folder_name = source.name
- zip_name = f"{folder_name}_备份_{date_str}"
- zip_path = shutil.make_archive(
- str(backup / zip_name),
- 'zip',
- str(source)
- )
- file_size = os.path.getsize(zip_path) / 1024 / 1024
- print(f"备份完成: {zip_path} ({file_size:.2f} MB)")
- return zip_path
- # ==================== 方案 3:保留多个历史版本 ====================
- def versioned_backup(source_dir, backup_base_dir, keep_versions=5):
- """
- 保留多个历史版本的备份(类似时间机器)
- 参数:
- source_dir: 源目录
- backup_base_dir: 备份根目录
- keep_versions: 保留最近的几个版本
- """
- source = Path(source_dir)
- backup_base = Path(backup_base_dir)
- backup_base.mkdir(parents=True, exist_ok=True)
- # 按日期创建版本文件夹
- date_str = datetime.now().strftime('%Y%m%d_%H%M%S')
- version_dir = backup_base / f"v{date_str}"
- # 全量复制
- if version_dir.exists():
- shutil.rmtree(version_dir)
- shutil.copytree(str(source), str(version_dir))
- print(f"已创建备份版本: {version_dir}")
- # 清理旧版本(保留最新的 keep_versions 个)
- versions = sorted(backup_base.iterdir())
- while len(versions) > keep_versions:
- old_version = versions.pop(0)
- if old_version.is_dir():
- shutil.rmtree(old_version)
- print(f"已清理旧版本: {old_version}")
- # ==================== 使用示例 ====================
- if __name__ == "__main__":
- # 配置项
- SOURCE = r"D:\工作资料"
- BACKUP = r"E:\备份\工作资料"
- # 方案 1:增量同步
- sync_folders(SOURCE, BACKUP, log_file="backup_log.txt")
- # 方案 2:打包压缩
- # backup_as_zip(SOURCE, r"E:\备份")
- # 方案 3:版本化备份(保留 5 个版本)
- # versioned_backup(SOURCE, r"E:\备份\版本历史", keep_versions=5)
复制代码
这段代码中,增量同步是最常用的场景。其判断逻辑采用了三层条件:目标文件不存在时视为新增;源文件修改时间晚于目标文件时视为更新;文件大小不同时也视为变更。这样能覆盖绝大多数文件变动情况。复制时使用 shutil.copy2,它与 shutil.copy 的区别在于,copy2 会额外尝试保留文件的修改时间、访问时间等元数据,让备份文件与源文件属性尽可能一致。
目录遍历使用 Path.rglob('*'),它会递归取得所有子目录和文件,配合 file_path.is_file() 过滤出文件,用 file_path.relative_to(source) 得到相对路径,再拼接到备份目录下。这种写法比 os.walk 更简洁,也更符合面向对象风格。
如果希望脚本自动定时运行,可以配合 Windows 任务计划程序。先建立一个 .bat 批处理文件,内容为:
- @echo off
- python "D:\scripts\auto_backup.py"
复制代码
然后在系统的“任务计划程序”中创建基本任务,设置触发器(例如每天 18:00),操作选择启动这个 .bat 文件即可。这样脚本就会在指定时间自动执行,全程无需人工干预。
备份完成后,如果需要弹窗提示,可以在脚本末尾加入 Windows 原生消息框调用:
- import ctypes
- ctypes.windll.user32.MessageBoxW(0, "备份完成!", "自动备份", 0x40)
复制代码
如果只需要备份特定类型的文件,可以在遍历时加入后缀名过滤。例如只备份文档和表格:
- ALLOWED_EXTENSIONS = {'.docx', '.xlsx', '.pdf'}
- for file_path in source.rglob('*'):
- if file_path.is_file() and file_path.suffix.lower() in ALLOWED_EXTENSIONS:
- # 同步逻辑
- pass
复制代码
关于网络驱动器或 NAS 的备份,路径可以直接使用 UNC 格式,比如:
- BACKUP = r"\\192.168.1.100\shared\backup\工作资料"
复制代码
同步过程中如果遇到断电或中断,不必担心。因为脚本是逐个文件复制的,已经复制完的文件会保留目标文件及其元数据。下次重新运行同步时,脚本会根据修改时间和大小判断出这些文件无需再次复制,从而自动跳过,继续处理未完成的文件。
备份占用空间过大时,有两个优化方向。一是使用版本化备份函数 versioned_backup,通过 keep_versions 参数限制保留的版本数量,自动清理旧版本。二是使用 zipfile 模块进行压缩归档,例如:
- import zipfile
- with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zf:
- for file_path in source.rglob('*'):
- if file_path.is_file():
- arcname = file_path.relative_to(source)
- zf.write(file_path, arcname)
复制代码
总结一下,三种备份策略各有适用场景。增量同步 sync_folders 适合日常高频备份,速度快,占用空间相对小;压缩打包 backup_as_zip 适合需要传输或归档的场景;版本化备份 versioned_backup 类似时间机器,适合需要回溯历史版本的场景。推荐组合使用:日常增量同步 + 每周打包压缩 + 保留最近 3 到 5 个版本,这样既能保证数据安全,又不会让备份文件夹失控增长。 |