本文目录导读:

我来为你提供一个文件夹增量同步脚本的完整方案。
基础Python增量同步脚本
#!/usr/bin/env python3
"""
文件夹增量同步脚本
功能:将源文件夹中新增或修改的文件同步到目标文件夹
"""
import os
import hashlib
import shutil
import json
import time
from datetime import datetime
from pathlib import Path
class IncrementalSync:
def __init__(self, source_dir, target_dir, state_file="sync_state.json"):
"""
初始化同步器
:param source_dir: 源文件夹路径
:param target_dir: 目标文件夹路径
:param state_file: 状态文件路径,用于记录同步状态
"""
self.source_dir = Path(source_dir)
self.target_dir = Path(target_dir)
self.state_file = Path(state_file)
self.sync_state = self._load_state()
def _load_state(self):
"""加载同步状态"""
if self.state_file.exists():
with open(self.state_file, 'r') as f:
return json.load(f)
return {}
def _save_state(self):
"""保存同步状态"""
with open(self.state_file, 'w') as f:
json.dump(self.sync_state, f, indent=2)
def _get_file_hash(self, file_path):
"""计算文件的MD5哈希值"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def _get_file_info(self, file_path):
"""获取文件信息"""
stat = file_path.stat()
return {
"size": stat.st_size,
"mtime": stat.st_mtime,
"hash": self._get_file_hash(file_path)
}
def _needs_sync(self, relative_path, file_info):
"""检查文件是否需要同步"""
if relative_path not in self.sync_state:
return True
prev_state = self.sync_state[relative_path]
# 检查文件大小或修改时间是否变化
if (file_info["size"] != prev_state["size"] or
abs(file_info["mtime"] - prev_state["mtime"]) > 1):
return True
# 检查哈希值是否变化
if file_info["hash"] != prev_state["hash"]:
return True
return False
def sync(self):
"""执行同步操作"""
print(f"开始同步: {self.source_dir} -> {self.target_dir}")
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
synced_count = 0
skipped_count = 0
error_count = 0
# 确保目标目录存在
self.target_dir.mkdir(parents=True, exist_ok=True)
# 遍历源文件夹
for source_path in self.source_dir.rglob("*"):
if source_path.is_file():
relative_path = str(source_path.relative_to(self.source_dir))
target_path = self.target_dir / relative_path
try:
# 获取文件信息
file_info = self._get_file_info(source_path)
# 检查是否需要同步
if self._needs_sync(relative_path, file_info):
# 创建目标文件夹
target_path.parent.mkdir(parents=True, exist_ok=True)
# 复制文件
shutil.copy2(source_path, target_path)
# 更新状态
self.sync_state[relative_path] = file_info
synced_count += 1
print(f" ✓ 同步: {relative_path}")
else:
skipped_count += 1
except Exception as e:
print(f" ✗ 错误: {relative_path} - {str(e)}")
error_count += 1
# 保存状态
self._save_state()
print(f"\n同步完成!")
print(f" 新增/更新: {synced_count} 个文件")
print(f" 跳过: {skipped_count} 个文件")
print(f" 错误: {error_count} 个文件")
return synced_count, skipped_count, error_count
def get_summary(self):
"""获取同步摘要"""
return {
"source": str(self.source_dir),
"target": str(self.target_dir),
"last_sync": datetime.now().isoformat(),
"synced_files": len(self.sync_state)
}
# 使用示例
def main():
# 配置同步参数
source = "/path/to/source/folder"
target = "/path/to/target/folder"
# 创建同步器
syncer = IncrementalSync(source, target, "sync_state.json")
# 执行同步
syncer.sync()
if __name__ == "__main__":
main()
高级版本(支持排除规则和日志)
#!/usr/bin/env python3
"""
高级增量同步脚本 - 支持排除规则、日志和通知
"""
import os
import hashlib
import shutil
import json
import logging
from pathlib import Path
from datetime import datetime
import fnmatch
class AdvancedIncrementalSync:
def __init__(self, source_dir, target_dir, config=None):
"""
初始化高级同步器
:param source_dir: 源文件夹
:param target_dir: 目标文件夹
:param config: 配置字典
- exclude_patterns: 排除的文件模式列表
- exclude_dirs: 排除的目录列表
- max_file_size: 最大文件大小(字节)
- backup: 是否备份被覆盖的文件
"""
self.source_dir = Path(source_dir)
self.target_dir = Path(target_dir)
self.config = config or {}
# 设置日志
self._setup_logging()
# 加载状态
self.state_file = self.target_dir / ".sync_state.json"
self.sync_state = self._load_state()
# 默认排除规则
self.exclude_patterns = self.config.get('exclude_patterns', [
'*.tmp', '*.log', '.DS_Store', 'Thumbs.db'
])
self.exclude_dirs = self.config.get('exclude_dirs', [
'.git', '__pycache__', 'node_modules'
])
self.max_file_size = self.config.get('max_file_size', 100 * 1024 * 1024) # 100MB
self.backup_enabled = self.config.get('backup', True)
def _setup_logging(self):
"""设置日志"""
log_file = self.target_dir / f"sync_{datetime.now().strftime('%Y%m%d')}.log"
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
def _load_state(self):
"""加载同步状态"""
if self.state_file.exists():
try:
with open(self.state_file, 'r') as f:
return json.load(f)
except:
return {}
return {}
def _save_state(self):
"""保存同步状态"""
try:
with open(self.state_file, 'w') as f:
json.dump(self.sync_state, f, indent=2)
except Exception as e:
self.logger.error(f"保存状态文件失败: {e}")
def _should_exclude(self, path):
"""检查路径是否应该被排除"""
# 检查排除目录
for part in path.parts:
if part in self.exclude_dirs:
return True
# 检查排除模式
for pattern in self.exclude_patterns:
if fnmatch.fnmatch(path.name, pattern):
return True
return False
def _get_file_info(self, file_path):
"""获取文件信息"""
try:
stat = file_path.stat()
# 只读取小文件的哈希值,大文件使用修改时间比较
if stat.st_size <= 10 * 1024 * 1024: # 10MB以下计算哈希
hash_value = self._calculate_hash(file_path)
else:
hash_value = None
return {
"size": stat.st_size,
"mtime": stat.st_mtime,
"hash": hash_value
}
except Exception as e:
self.logger.error(f"读取文件信息失败 {file_path}: {e}")
return None
def _calculate_hash(self, file_path):
"""计算文件MD5哈希"""
hash_md5 = hashlib.md5()
try:
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
except:
return None
def _backup_file(self, file_path):
"""备份即将被覆盖的文件"""
if not self.backup_enabled:
return
backup_dir = self.target_dir / ".sync_backup"
backup_dir.mkdir(exist_ok=True)
relative_path = file_path.relative_to(self.target_dir)
backup_path = backup_dir / f"{relative_path}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
backup_path.parent.mkdir(parents=True, exist_ok=True)
try:
shutil.copy2(file_path, backup_path)
self.logger.info(f"已备份: {relative_path} -> {backup_path}")
except Exception as e:
self.logger.error(f"备份失败 {file_path}: {e}")
def sync(self):
"""执行同步操作"""
self.logger.info(f"开始同步: {self.source_dir} -> {self.target_dir}")
stats = {"synced": 0, "deleted": 0, "errors": 0, "skipped": 0}
# 确保目标目录存在
self.target_dir.mkdir(parents=True, exist_ok=True)
# 同步文件
for source_path in self.source_dir.rglob("*"):
if not source_path.is_file():
continue
# 检查排除规则
if self._should_exclude(source_path):
continue
relative_path = str(source_path.relative_to(self.source_dir))
target_path = self.target_dir / relative_path
try:
# 检查文件大小限制
if source_path.stat().st_size > self.max_file_size:
self.logger.warning(f"跳过大文件: {relative_path}")
stats["skipped"] += 1
continue
# 获取文件信息
file_info = self._get_file_info(source_path)
if not file_info:
stats["errors"] += 1
continue
# 检查是否需要同步
needs_sync = True
if relative_path in self.sync_state:
prev_state = self.sync_state[relative_path]
if (prev_state.get("size") == file_info["size"] and
prev_state.get("mtime") == file_info["mtime"]):
needs_sync = False
# 如果有哈希值,进行精确比较
if (file_info["hash"] and prev_state.get("hash") and
file_info["hash"] == prev_state["hash"]):
needs_sync = False
if needs_sync:
# 备份旧文件
if target_path.exists():
self._backup_file(target_path)
# 创建目标目录
target_path.parent.mkdir(parents=True, exist_ok=True)
# 复制文件
shutil.copy2(source_path, target_path)
# 更新状态
self.sync_state[relative_path] = file_info
stats["synced"] += 1
self.logger.info(f"同步: {relative_path}")
else:
stats["skipped"] += 1
except Exception as e:
self.logger.error(f"同步失败 {relative_path}: {e}")
stats["errors"] += 1
# 清理目标文件夹中已删除的文件(可选)
if self.config.get('delete_removed', False):
for target_path in self.target_dir.rglob("*"):
if not target_path.is_file():
continue
relative_path = str(target_path.relative_to(self.target_dir))
source_path = self.source_dir / relative_path
if not source_path.exists():
try:
target_path.unlink()
self.sync_state.pop(relative_path, None)
stats["deleted"] += 1
self.logger.info(f"删除: {relative_path}")
except Exception as e:
self.logger.error(f"删除失败 {relative_path}: {e}")
# 保存同步状态
self._save_state()
# 输出统计
self.logger.info(f"同步完成!")
self.logger.info(f" 同步: {stats['synced']} 个文件")
self.logger.info(f" 跳过: {stats['skipped']} 个文件")
self.logger.info(f" 删除: {stats['deleted']} 个文件")
self.logger.info(f" 错误: {stats['errors']} 个文件")
return stats
# 使用示例
def main():
config = {
'exclude_patterns': ['*.tmp', '*.log', '.DS_Store', 'Thumbs.db'],
'exclude_dirs': ['.git', '__pycache__', 'node_modules'],
'max_file_size': 500 * 1024 * 1024, # 500MB
'backup': True,
'delete_removed': False # 是否删除目标中已不存在的文件
}
syncer = AdvancedIncrementalSync(
source_dir="/path/to/source",
target_dir="/path/to/target",
config=config
)
stats = syncer.sync()
print(f"同步统计: {stats}")
if __name__ == "__main__":
main()
定时同步脚本
#!/usr/bin/env python3
"""
定时增量同步脚本
"""
import schedule
import time
from datetime import datetime
def scheduled_sync():
"""定时执行同步"""
print(f"\n=== 定时同步开始: {datetime.now()} ===")
syncer = AdvancedIncrementalSync(
source_dir="/path/to/source",
target_dir="/path/to/target"
)
syncer.sync()
print(f"=== 定时同步结束: {datetime.now()} ===\n")
def main():
# 设置定时任务
schedule.every().hour.do(scheduled_sync) # 每小时同步一次
# schedule.every().day.at("02:00").do(scheduled_sync) # 每天凌晨2点同步
# schedule.every(30).minutes.do(scheduled_sync) # 每30分钟同步一次
print("定时同步服务已启动...")
# 执行一次立即同步
scheduled_sync()
# 持续运行
while True:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次
if __name__ == "__main__":
main()
使用建议
安装依赖
pip install schedule
配置建议
- 排除规则:根据需求配置需要排除的文件类型和目录
- 文件大小限制:避免同步过大的文件
- 备份策略:默认开启文件备份,可配置关闭
- 日志记录:同步操作会记录详细日志
注意事项
- 首次运行:会全量同步所有文件
- 状态文件:保存在目标目录下,用于增量判断
- 文件完整性:大文件建议使用修改时间比较,小文件使用哈希值
这个脚本适合日常文件备份、项目同步等场景,使用简单且可靠。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播