3. 定时清理过期临时文件
任务拆解:保留期限、扫描范围与删除清单
本案例处理的对象是“指定目录中的临时文件”,核心约束是“超过保留期限”。脚本需要完成三件事:按保留天数筛选过期文件、删除这些文件、把删除结果写入清单日志。与批量重命名或分类归档不同,这里的判断依据是文件的修改时间(mtime),而不是文件名或扩展名。
关键参数定义如下:
target_dir:要清理的目录,例如/tmp/myapp_cache。retention_days:保留期限,单位为天,例如 7 天。log_file:删除清单日志路径,例如cleanup_log.txt。now:当前时间戳,用于计算过期阈值。
过期判断公式为:now - file.stat().st_mtime > retention_days * 86400。其中 86400 是一天的秒数。只有严格超过保留期限的文件才会被删除,等于阈值时保留。
核心实现:扫描、判断、删除、记录
下面给出一个完整可运行的脚本。它使用 pathlib 遍历目录,用 time.time() 获取当前时间,用 datetime 生成可读时间戳,并把每个被删除文件的路径、修改时间和文件大小写入日志。
from pathlib import Path
from datetime import datetime
import time
def cleanup_expired_files(target_dir, retention_days, log_file, dry_run=True):
target = Path(target_dir)
if not target.is_dir():
raise NotADirectoryError(f"目录不存在: {target_dir}")
now = time.time()
threshold = retention_days * 86400
deleted = []
for item in target.rglob("*"):
if not item.is_file():
continue
try:
stat = item.stat()
except OSError as e:
print(f"跳过无法访问的文件: {item} ({e})")
continue
age = now - stat.st_mtime
if age > threshold:
mtime_str = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
record = f"{item}\t{mtime_str}\t{stat.st_size} bytes"
deleted.append(record)
if not dry_run:
try:
item.unlink()
except OSError as e:
print(f"删除失败: {item} ({e})")
deleted.pop()
continue
with open(log_file, "a", encoding="utf-8") as f:
f.write(f"# 清理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"# 目录: {target_dir} 保留天数: {retention_days} 模式: {'dry-run' if dry_run else 'delete'}\n")
for line in deleted:
f.write(line + "\n")
f.write(f"# 共处理 {len(deleted)} 个文件\n\n")
return deleted
if __name__ == "__main__":
result = cleanup_expired_files(
target_dir="/tmp/myapp_cache",
retention_days=7,
log_file="cleanup_log.txt",
dry_run=True
)
print(f"本次{'预演' if True else '实际'}清理 {len(result)} 个文件")
for r in result:
print(r)
脚本中几个容易出错的点:rglob("*") 会递归扫描子目录,如果只想清理顶层文件,应改用 iterdir();item.is_file() 过滤掉目录,避免对目录调用 unlink();dry_run=True 时只记录不删除,适合首次验证。
删除清单日志格式与判读
日志采用追加写入,每次运行生成一个块,块内包含运行参数和每个被处理文件的记录。这样即使多次运行,也能追溯每次清理的范围和结果。
| 字段 | 示例 | 说明 |
|---|---|---|
| 文件路径 | /tmp/myapp_cache/session_old.tmp | 被判定为过期的文件完整路径 |
| 修改时间 | 2025-01-10 08:30:00 | 文件最后修改时间,用于核对是否真的过期 |
| 文件大小 | 2048 bytes | 便于评估清理释放的空间 |
| 运行参数 | 目录、保留天数、模式 | 记录本次清理的上下文 |
判读日志时,重点看三件事:被删除文件是否确实超过保留期限;是否有“删除失败”的记录;dry-run 和真实删除两次运行的清单是否一致。如果 dry-run 清单为空,说明没有文件超过保留期限,不需要执行删除。
定时执行与安全边界
“定期删除”通常借助系统定时任务实现。Linux 下可用 cron,Windows 下可用任务计划程序。以 cron 为例,每天凌晨 3 点执行一次:
0 3 * * * /usr/bin/python3 /opt/scripts/cleanup_expired_files.py >> /var/log/cleanup_cron.log 2>&1
安全边界建议:
- 不要对系统关键目录(如
/etc、/usr)执行清理。 - 保留期限不要设得过短,避免误删仍在使用的缓存。
- 日志文件本身不要放在被清理目录内,否则可能被自己删除。
- 首次上线先用 dry-run 运行至少一个周期,确认清单稳定后再切换为真实删除。
验证与常见问题
验证脚本是否按预期工作,可以手动构造测试文件:
mkdir -p /tmp/myapp_cache
touch -d "10 days ago" /tmp/myapp_cache/old.tmp
touch /tmp/myapp_cache/new.tmp
python3 cleanup_expired_files.py
预期结果:old.tmp 出现在清单中,new.tmp 不出现。如果 dry-run 模式下清单为空,检查 retention_days 是否过大,或文件 mtime 是否被正确读取。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 清单为空 | 没有文件超过保留期限 | 确认 retention_days 和文件 mtime |
| 删除失败 | 文件被占用或权限不足 | 检查进程占用和目录权限 |
| 日志文件也被删 | 日志放在被清理目录内 | 把 log_file 移到目录外 |
| 子目录文件未被清理 | 使用了 iterdir() 而非 rglob() | 按需改为递归扫描 |