查看: 197|回复: 3

Python实现文件夹增量同步到移动硬盘的定时备份脚本

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
对于长期在电脑本地存放工作资料的人来说,一旦硬盘损坏、电脑丢失或遭遇勒索病毒,数据可能瞬间归零。手动复制粘贴容易遗漏,也难坚持。利用 Python 写一个增量同步脚本,再配合 Windows 任务计划程序,即可实现指定文件夹的定时自动备份。本文围绕这一需求,给出完整的脚本实现与关键代码解析。

增量同步的核心思路是:每次运行时,只复制源目录中新增的、修改过的或大小发生变化的文件,而不是全量拷贝。这样既能节省时间,也能减少磁盘写入。实现上通过对比源文件和目标文件的修改时间、文件大小来决定是否需要复制,使用 shutil.copy2 完成复制并保留文件元数据。

环境方面,脚本只用 Python 标准库,无需安装第三方模块。需要特别提醒的是,shutil、os、pathlib、datetime 都是内置模块,直接 import 即可,不需要执行 pip install shutil,网上有的教程会误导读者去安装一个不存在的 PyPI 包。

下面是完整实现。脚本提供三个方案:增量同步、打包压缩备份、版本化备份。日常推荐使用增量同步,重要节点可配合压缩包和版本历史。
  1. import os
  2. import shutil
  3. import time
  4. from pathlib import Path
  5. from datetime import datetime
  6. # ==================== 方案 1:增量文件同步 ====================
  7. def sync_folders(source_dir, backup_dir, log_file=None):
  8.     """
  9.     将源文件夹增量同步到备份文件夹
  10.     参数:
  11.     source_dir: 源文件夹路径(原始文件)
  12.     backup_dir: 备份文件夹路径(目标位置)
  13.     log_file: 日志文件路径(可选)
  14.     """
  15.     source = Path(source_dir)
  16.     backup = Path(backup_dir)
  17.     if not source.exists():
  18.         print(f"错误:源目录 {source_dir} 不存在!")
  19.         return False
  20.     # 创建备份目录
  21.     backup.mkdir(parents=True, exist_ok=True)
  22.     files_copied = 0
  23.     files_updated = 0
  24.     files_skipped = 0
  25.     total_size = 0
  26.     log_lines = []
  27.     def log(msg):
  28.         print(msg)
  29.         log_lines.append(msg)
  30.     log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 开始同步")
  31.     log(f"源目录: {source}")
  32.     log(f"备份目录: {backup}")
  33.     log("-" * 50)
  34.     # 遍历源目录所有文件
  35.     for file_path in source.rglob('*'):
  36.         if file_path.is_file():
  37.             # 计算相对路径
  38.             rel_path = file_path.relative_to(source)
  39.             dest_path = backup / rel_path
  40.             # 判断是否需要复制
  41.             need_copy = False
  42.             if not dest_path.exists():
  43.                 need_copy = True
  44.                 action = "新增"
  45.             elif file_path.stat().st_mtime > dest_path.stat().st_mtime:
  46.                 need_copy = True
  47.                 action = "更新"
  48.             elif file_path.stat().st_size != dest_path.stat().st_size:
  49.                 need_copy = True
  50.                 action = "大小变更"
  51.             if need_copy:
  52.                 # 确保目标目录存在
  53.                 dest_path.parent.mkdir(parents=True, exist_ok=True)
  54.                 try:
  55.                     shutil.copy2(str(file_path), str(dest_path))
  56.                     file_size = file_path.stat().st_size
  57.                     total_size += file_size
  58.                     if action == "新增":
  59.                         files_copied += 1
  60.                     else:
  61.                         files_updated += 1
  62.                     log(f"  [{action}] {rel_path} ({file_size / 1024:.1f} KB)")
  63.                 except Exception as e:
  64.                     log(f"  [失败] {rel_path}: {e}")
  65.             else:
  66.                 files_skipped += 1
  67.     # 汇总报告
  68.     log("-" * 50)
  69.     log(f"同步完成!")
  70.     log(f"新增文件: {files_copied}")
  71.     log(f"更新文件: {files_updated}")
  72.     log(f"跳过文件: {files_skipped}")
  73.     log(f"总复制量: {total_size / 1024 / 1024:.2f} MB")
  74.     # 写入日志
  75.     if log_file:
  76.         Path(log_file).parent.mkdir(parents=True, exist_ok=True)
  77.         with open(log_file, 'a', encoding='utf-8') as f:
  78.             f.write('\n'.join(log_lines) + '\n\n')
  79.     return True
  80. # ==================== 方案 2:打包压缩备份 ====================
  81. def backup_as_zip(source_dir, backup_dir):
  82.     """
  83.     将整个文件夹打包为带日期的 ZIP 压缩包
  84.     """
  85.     source = Path(source_dir)
  86.     backup = Path(backup_dir)
  87.     backup.mkdir(parents=True, exist_ok=True)
  88.     date_str = datetime.now().strftime('%Y%m%d_%H%M%S')
  89.     folder_name = source.name
  90.     zip_name = f"{folder_name}_备份_{date_str}"
  91.     zip_path = shutil.make_archive(
  92.         str(backup / zip_name),
  93.         'zip',
  94.         str(source)
  95.     )
  96.     file_size = os.path.getsize(zip_path) / 1024 / 1024
  97.     print(f"备份完成: {zip_path} ({file_size:.2f} MB)")
  98.     return zip_path
  99. # ==================== 方案 3:保留多个历史版本 ====================
  100. def versioned_backup(source_dir, backup_base_dir, keep_versions=5):
  101.     """
  102.     保留多个历史版本的备份(类似时间机器)
  103.     参数:
  104.     source_dir: 源目录
  105.     backup_base_dir: 备份根目录
  106.     keep_versions: 保留最近的几个版本
  107.     """
  108.     source = Path(source_dir)
  109.     backup_base = Path(backup_base_dir)
  110.     backup_base.mkdir(parents=True, exist_ok=True)
  111.     # 按日期创建版本文件夹
  112.     date_str = datetime.now().strftime('%Y%m%d_%H%M%S')
  113.     version_dir = backup_base / f"v{date_str}"
  114.     # 全量复制
  115.     if version_dir.exists():
  116.         shutil.rmtree(version_dir)
  117.     shutil.copytree(str(source), str(version_dir))
  118.     print(f"已创建备份版本: {version_dir}")
  119.     # 清理旧版本(保留最新的 keep_versions 个)
  120.     versions = sorted(backup_base.iterdir())
  121.     while len(versions) > keep_versions:
  122.         old_version = versions.pop(0)
  123.         if old_version.is_dir():
  124.             shutil.rmtree(old_version)
  125.             print(f"已清理旧版本: {old_version}")
  126. # ==================== 使用示例 ====================
  127. if __name__ == "__main__":
  128.     # 配置项
  129.     SOURCE = r"D:\工作资料"
  130.     BACKUP = r"E:\备份\工作资料"
  131.     # 方案 1:增量同步
  132.     sync_folders(SOURCE, BACKUP, log_file="backup_log.txt")
  133.     # 方案 2:打包压缩
  134.     # backup_as_zip(SOURCE, r"E:\备份")
  135.     # 方案 3:版本化备份(保留 5 个版本)
  136.     # 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 批处理文件,内容为:
  1. @echo off
  2. python "D:\scripts\auto_backup.py"
复制代码

然后在系统的“任务计划程序”中创建基本任务,设置触发器(例如每天 18:00),操作选择启动这个 .bat 文件即可。这样脚本就会在指定时间自动执行,全程无需人工干预。

备份完成后,如果需要弹窗提示,可以在脚本末尾加入 Windows 原生消息框调用:
  1. import ctypes
  2. ctypes.windll.user32.MessageBoxW(0, "备份完成!", "自动备份", 0x40)
复制代码

如果只需要备份特定类型的文件,可以在遍历时加入后缀名过滤。例如只备份文档和表格:
  1. ALLOWED_EXTENSIONS = {'.docx', '.xlsx', '.pdf'}
  2. for file_path in source.rglob('*'):
  3.     if file_path.is_file() and file_path.suffix.lower() in ALLOWED_EXTENSIONS:
  4.         # 同步逻辑
  5.         pass
复制代码

关于网络驱动器或 NAS 的备份,路径可以直接使用 UNC 格式,比如:
  1. BACKUP = r"\\192.168.1.100\shared\backup\工作资料"
复制代码

同步过程中如果遇到断电或中断,不必担心。因为脚本是逐个文件复制的,已经复制完的文件会保留目标文件及其元数据。下次重新运行同步时,脚本会根据修改时间和大小判断出这些文件无需再次复制,从而自动跳过,继续处理未完成的文件。

备份占用空间过大时,有两个优化方向。一是使用版本化备份函数 versioned_backup,通过 keep_versions 参数限制保留的版本数量,自动清理旧版本。二是使用 zipfile 模块进行压缩归档,例如:
  1. import zipfile
  2. with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zf:
  3.     for file_path in source.rglob('*'):
  4.         if file_path.is_file():
  5.             arcname = file_path.relative_to(source)
  6.             zf.write(file_path, arcname)
复制代码

总结一下,三种备份策略各有适用场景。增量同步 sync_folders 适合日常高频备份,速度快,占用空间相对小;压缩打包 backup_as_zip 适合需要传输或归档的场景;版本化备份 versioned_backup 类似时间机器,适合需要回溯历史版本的场景。推荐组合使用:日常增量同步 + 每周打包压缩 + 保留最近 3 到 5 个版本,这样既能保证数据安全,又不会让备份文件夹失控增长。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python实现文件夹增量同步到移动硬盘的定时备份脚本

楼主的思路很实用,标准库就能搞定,特别适合不想装第三方依赖的场景。那个“不要 pip install shutil”的提醒很到位,网上确实有些教程会误导新手。 增量判断我用过类似方案,不过有几个点想和楼主探讨一下:只比对修改时间和大小,如果文件内容被改但时间被刻意还原,可能漏同步。当然日常备份够用了。另外目前只处理了源目录新增或更新的文件,如果源目录里删了文件,备份端不会自动清理,时间长了可能积累冗余。可以加一个反向遍历,把备份端有但源端没有的文件移到一个“已删除”文件夹,或者按需开启。 还有就是如果文件数量特别大,`rglob` 全量遍历一次也可能耗时,不过对于个人工作资料来说通常没问题。 方案里提到还有压缩打包和版本化备份,期待这部分。日常用增量同步,重要节点再打包,这个习惯确实比较稳妥。感谢分享,收藏了。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python实现文件夹增量同步到移动硬盘的定时备份脚本

感谢分享,这个脚本很实用。增量同步的思路清晰,用修改时间和大小判断是否复制也够用了,日常备份完全够使。特别是提醒了不要 pip install shutil 这点很贴心,确实容易踩坑。 方案里提到打包压缩和版本化备份,不知道后面会不会展开讲?如果能把删除文件的处理也考虑进去就更好了,现在这样只增不改删,长期看备份目录会越积越大。另外如果源文件数量特别多,每次遍历全部文件可能有点慢,或许可以加个简单的文件清单缓存来提速。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python实现文件夹增量同步到移动硬盘的定时备份脚本

这脚本很实用,正好解决了我经常忘了备份的毛病。增量同步的逻辑清晰,日志输出也方便排查。有个小建议,如果能把删除操作也同步过去就更好了,比如源目录删了文件,备份那边也自动清掉,这样备份会更干净。当然有历史版本需求的话另说。总之感谢分享,先收藏了!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-24 15:49 , Processed in 0.022319 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部