主要功能说明:
1.界面组件:源文件夹和目标文件夹选择(带浏览按钮),备份间隔时间设置(分钟),立即备份按钮,自动备份切换按钮,状态栏显示备份状态。
2.进度条显示:使用ttk.Progressbar实现精确进度显示,根据实际文件数量更新进度,实时显示已复制文件数量。
3.文件过滤功能:包含:*.docx, *.xlsx,排除:temp, ~$,支持包含/排除模式(支持通配符)。
4.日志记录系统:自动生成backup_log.txt,记录每次备份的详细信息,支持查看日志窗口(带滚动条)。
5.增量备份选项:通过MD5哈希比较文件内容,仅复制修改过的文件,可切换完整/增量备份模式。
6.使用说明:设置源目录和目标目录,配置过滤规则(可选),选择备份模式(完整/增量),设置自动备份间隔,通过按钮执行立即备份或自动备份,可随时查看备份日志。使用独立线程进行自动备份,避免界面卡顿,自动生成带时间戳的备份目录,实时状态反馈,错误处理机制。
7.注意事项:需要确保目标驱动器有足够空间,长时间运行的自动备份建议在后台运行,第一次使用时建议先进行手动备份测试,增量备份基于文件内容比对,大文件可能会影响性能,过滤规则支持标准shell通配符语法,日志文件保存在程序运行目录,进度条精度取决于文件数量而非大小。
# -*- coding: utf-8 -*-
# @Author : 小红牛
# 微信公众号:WdPython
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import shutil
import os
from datetime import datetime
import threading
import time
import fnmatch
import hashlib
class BackupApp:
def __init__(self, root):
self.root = root
self.root.title("高级自动备份工具")
self.root.geometry("650x450")
# 初始化变量
self.is_auto_backup_running = False
self.total_files = 0
self.copied_files = 0
self.backup_mode = tk.StringVar(value="full") # 备份模式
# 创建界面组件
self.create_widgets()
self.setup_logging()
def setup_logging(self):
"""初始化日志系统"""
self.log_path = os.path.join(os.getcwd(), "backup_log.txt")
if not os.path.exists(self.log_path):
with open(self.log_path, "w") as f:
f.write("备份日志记录\n\n")
def create_widgets(self):
# 创建主容器
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 源文件夹选择
source_frame = ttk.LabelFrame(main_frame, text="源目录设置")
source_frame.grid(row=0, column=0, columnspan=3, sticky="we", pady=5)
self.source_entry = ttk.Entry(source_frame, width=50)
self.source_entry.grid(row=0, column=0, padx=5, pady=5)
ttk.Button(source_frame, text="浏览...", command=self.select_source).grid(row=0, column=1, padx=5)
# 目标文件夹选择
target_frame = ttk.LabelFrame(main_frame, text="目标目录设置")
target_frame.grid(row=1, column=0, columnspan=3, sticky="we", pady=5)
self.target_entry = ttk.Entry(target_frame, width=50)
self.target_entry.grid(row=0, column=0, padx=5, pady=5)
ttk.Button(target_frame, text="浏览...", command=self.select_target).grid(row=0, column=1, padx=5)
# 过滤设置
filter_frame = ttk.LabelFrame(main_frame, text="文件过滤设置")
filter_frame.grid(row=2, column=0, sticky="we", pady=5, padx=5)
ttk.Label(filter_frame, text="包含模式(逗号分隔):").grid(row=0, column=0)
self.include_pattern = ttk.Entry(filter_frame)
self.include_pattern.grid(row=0, column=1, padx=5)
ttk.Label(filter_frame, text="排除模式(逗号分隔):").grid(row=1, column=0)
self.exclude_pattern = ttk.Entry(filter_frame)
self.exclude_pattern.grid(row=1, column=1, padx=5)
# 备份设置
setting_frame = ttk.LabelFrame(main_frame, text="备份设置")
setting_frame.grid(row=2, column=1, sticky="we", pady=5, padx=5)
ttk.Label(setting_frame, text="间隔(分钟):").grid(row=0, column=0)
self.interval_entry = ttk.Entry(setting_frame, width=8)
self.interval_entry.grid(row=0, column=1, padx=5)
self.interval_entry.insert(0, "60")
ttk.Radiobutton(setting_frame, text="完整备份", variable=self.backup_mode, value="full").grid(row=1, column=0)
ttk.Radiobutton(setting_frame, text="增量备份", variable=self.backup_mode, value="incremental").grid(row=1,
column=1)
# 进度条
self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, mode='determinate')
self.progress.grid(row=3, column=0, columnspan=3, sticky="we", pady=10)
# 按钮区域
btn_frame = ttk.Frame(main_frame)
btn_frame.grid(row=4, column=0, columnspan=3, pady=10)
ttk.Button(btn_frame, text="立即备份", command=self.manual_backup).pack(side=tk.LEFT, padx=5)
self.auto_btn = ttk.Button(btn_frame, text="开始自动备份", command=self.toggle_auto_backup)
self.auto_btn.pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="查看日志", command=self.show_logs).pack(side=tk.LEFT, padx=5)
# 状态栏
self.status_label = ttk.Label(main_frame, text="状态: 就绪", relief=tk.SUNKEN)
self.status_label.grid(row=5, column=0, columnspan=3, sticky="we")
def select_source(self):
path = filedialog.askdirectory()
if path:
self.source_entry.delete(0, tk.END)
self.source_entry.insert(0, path)
def select_target(self):
path = filedialog.askdirectory()
if path:
self.target_entry.delete(0, tk.END)
self.target_entry.insert(0, path)
def manual_backup(self):
threading.Thread(target=self.perform_backup, daemon=True).start()
def should_include(self, file_path):
"""文件过滤逻辑"""
include = self.include_pattern.get().strip()
exclude = self.exclude_pattern.get().strip()
filename = os.path.basename(file_path)
# 包含规则
if include:
patterns = [p.strip() for p in include.split(",")]
if not any(fnmatch.fnmatch(filename, p) for p in patterns):
return False
# 排除规则
if exclude:
patterns = [p.strip() for p in exclude.split(",")]
if any(fnmatch.fnmatch(filename, p) for p in patterns):
return False
return True
def get_file_hash(self, file_path):
"""计算文件哈希值(用于增量备份)"""
hasher = hashlib.md5()
with open(file_path, 'rb') as f:
while chunk := f.read(8192):
hasher.update(chunk)
return hasher.hexdigest()
def perform_backup(self):
"""执行备份的核心方法"""
try:
source = self.source_entry.get()
target = self.target_entry.get()
if not source or not target:
messagebox.showerror("错误", "请先选择源文件夹和目标文件夹")
return
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_folder = os.path.join(target, f"backup_{timestamp}")
os.makedirs(backup_folder, exist_ok=True)
# 获取文件列表并过滤
all_files = []
for root, dirs, files in os.walk(source):
for file in files:
full_path = os.path.join(root, file)
if self.should_include(full_path):
all_files.append(full_path)
self.total_files = len(all_files)
self.copied_files = 0
self.progress['maximum'] = self.total_files
self.progress['value'] = 0
# 记录日志
log_entry = {
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'source': source,
'target': backup_folder,
'mode': self.backup_mode.get(),
'status': 'success',
'files': 0
}
# 执行备份
for file_path in all_files:
if not self.is_auto_backup_running and self.backup_mode.get() == "incremental":
break # 允许取消增量备份
rel_path = os.path.relpath(file_path, source)
dest_path = os.path.join(backup_folder, rel_path)
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
# 增量备份检查
if self.backup_mode.get() == "incremental":
if os.path.exists(dest_path):
src_hash = self.get_file_hash(file_path)
dst_hash = self.get_file_hash(dest_path)
if src_hash == dst_hash:
continue
shutil.copy2(file_path, dest_path)
self.copied_files += 1
self.update_progress()
log_entry['files'] = self.copied_files
self.log_backup(log_entry)
self.update_status(f"备份完成:复制 {self.copied_files}/{self.total_files} 个文件")
except Exception as e:
self.update_status(f"备份失败:{str(e)}")
messagebox.showerror("错误", str(e))
log_entry['status'] = 'failed'
log_entry['error'] = str(e)
self.log_backup(log_entry)
def update_progress(self):
"""更新进度条"""
self.progress['value'] = self.copied_files
self.root.update_idletasks()
def log_backup(self, entry):
"""记录备份日志"""
with open(self.log_path, "a") as f:
f.write(f"[{entry['timestamp']}] {entry['mode'].upper()}备份 "
f"源目录: {entry['source']}\n目标目录: {entry['target']}\n"
f"状态: {entry['status'].upper()} 文件数: {entry['files']}\n")
if 'error' in entry:
f.write(f"错误信息: {entry['error']}\n")
f.write("-" * 50 + "\n")
def show_logs(self):
"""显示日志窗口"""
log_win = tk.Toplevel(self.root)
log_win.title("备份日志")
text_area = tk.Text(log_win, wrap=tk.WORD, width=80, height=20)
scroll = ttk.Scrollbar(log_win, command=text_area.yview)
text_area.configure(yscrollcommand=scroll.set)
with open(self.log_path, "r") as f:
text_area.insert(tk.END, f.read())
text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
def toggle_auto_backup(self):
if not self.is_auto_backup_running:
try:
interval = int(self.interval_entry.get()) * 60
if interval <= 0:
raise ValueError
except ValueError:
messagebox.showerror("错误", "请输入有效的正整数分钟数")
return
self.is_auto_backup_running = True
self.auto_btn.config(text="停止自动备份")
self.update_status("自动备份已启动...")
self.auto_backup_thread = threading.Thread(
target=self.auto_backup_loop, args=(interval,), daemon=True
)
self.auto_backup_thread.start()
else:
self.is_auto_backup_running = False
self.auto_btn.config(text="开始自动备份")
self.update_status("自动备份已停止")
def auto_backup_loop(self, interval):
while self.is_auto_backup_running:
self.perform_backup()
for _ in range(interval):
if not self.is_auto_backup_running:
return
time.sleep(1)
def update_status(self, message):
self.status_label.config(text=f"状态: {message}")
if __name__ == "__main__":
root = tk.Tk()
app = BackupApp(root)
root.mainloop()
完毕!!感谢您的收看
----------★★跳转到历史博文集合★★----------
我的零基础Python教程,Python入门篇 进阶篇 视频教程 Py安装py项目 Python模块 Python爬虫 Json Xpath 正则表达式 Selenium Etree CssGui程序开发 Tkinter Pyqt5 列表元组字典数据可视化 matplotlib 词云图 Pyecharts 海龟画图 Pandas Bug处理 电脑小知识office自动化办公 编程工具 NumPy Pygame