这次我们来看一个自带“后悔药”的清理工具,项目代号叫“赵光义清理工具(义主)”。名字只是个代号,重点不在名字,而在它解决的问题:很多清理脚本写到最后就变成一条rm -rf,扫描结果不展示,删除之前不备份,删除之后也没有审计记录。一旦误删了缓存的源代码、模型权重或者刚生成的报表,连恢复入口都找不到。
这个工具的设计思路正好反过来。它把清理过程拆成“扫描 - 预览 - 移动 - 审计”四个阶段,默认不直接删除原始文件,而是先移动到备份目录或回收站。等确认没有影响,再由人工执行二次清理。标题里“总是分外用心”说的就是这个逻辑:不是删除能力不够强,而是每次清理都要留证据、留退路。
下面以“赵光义清理工具(义主)”作为示例项目代号,给出一套可落地的本地清理工具实现方案。整套工具不需要 GPU,不依赖显卡算力,普通 CPU 和 1GB 内存的机器就能跑。它同时提供命令行和 HTTP API,既能手动扫描,也能接入批量任务队列,适合需要定期清理开发缓存、CI 构建产物、日志目录和 AI 模型缓存的开发者。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 本地清理工具示例实现,CLI + HTTP API |
| 显存需求 | 无 GPU 需求,显存占用为 0 |
| 运行平台 | Windows / Linux / macOS,需 Python 3.9+ |
| 启动方式 | 命令行扫描、Uvicorn 启动 API 服务 |
| 默认 API 端口 | 8765,可在启动命令中修改 |
| 主要功能 | 目录扫描、空间统计、干跑预览、安全清理、黑白名单、审计日志 |
| 清理方式 | 移动到备份目录 / 回收站,不直接物理删除 |
| 批量任务 | 支持多配置文件循环任务 |
| 接口 API | 支持/scan、/clean等 REST 接口 |
| 适用场景 | 开发缓存清理、日志归档、临时文件整理、模型缓存清理 |
先强调一点,这个工具的核心卖点不是“删得更多”,而是“删得可回退”。当你对着一堆.log、.tmp、__pycache__、build目录犹豫要不要删时,可以先跑一次干跑模式。它会告诉你按当前规则会清掉哪些文件、释放多少空间,但不做任何删除动作。只有确认无误后再执行清理,而且清掉的文件也会进入备份目录,而不是直接从磁盘消失。
2. 设计原则与使用边界
工具代号里的“义主”,可以理解为“以用户数据主权义务为优先”。清理工具是所有开发辅助工具里最容易翻车的一类,因为它的操作不可逆。很多工具没有想清楚边界就直接递归删除,一旦路径写错,可能把整个项目目录清空。为了避免这个问题,这套工具给自己定了几个强制约束。
第一,默认不删除文件,只移动文件。执行清理时,工具会把匹配到的文件移动到同一个磁盘分区下的.cleanup_trash目录。这样做的原因是同分区移动速度极快,不会产生大量额外磁盘占用。等观察几天确认没有影响后,再手动清空备份目录。
第二,必须支持干跑模式。命令行工具提供--dry-run参数,API 中对应dry_run字段。在干跑模式下,工具只输出扫描结果和预估释放空间,不写任何文件。这是所有自动化清理任务上线前必须走的一步。
第三,白名单优先级高于清理规则。配置文件里exclude列表中的目录或文件,无论匹配多少条清理规则,都不会被移动。保险起见,这套设计默认会对.git、node_modules、数据库文件、图片、文档等目录做额外保护。如果确实需要清理这些目录里的内容,要显式编写额外配置。
从适用场景看,这套工具适合清理有明显“过期属性”的文件:开发过程中的编译产物、测试日志、各类缓存、临时下载文件、CI 构建残留。它不适合当普通用户的“电脑垃圾清理”工具,更不应该被用来扫描个人照片、私人文档、数据库备份这类无法重建的数据。
合规和安全边界也必须提前说明。在公司电脑、服务器或他人机器上运行前,需要确认操作权限,未经授权不能扫描和清理不属于自己的数据。如果工具要开放成 HTTP 服务,默认只能绑定127.0.0.1,不要直接暴露到公网。任何清理工具都应当保留审计日志,以便出现误删时能定位原因。
3. 环境准备与前置条件
清理工具本质上是文件系统操作密集任务,对硬件要求很低。开发环境只需要三样东西:Python 3.9 或更高版本、pip、一个可以访问的终端。如果只是手动清理,普通笔记本即可;如果要通过 API 跑批量任务,建议准备至少 2GB 内存,避免扫描大量小文件时内存占用过高。
建议使用虚拟环境隔离项目依赖,避免污染系统 Python 环境。下面是一个最小项目目录结构:
cleanup-tool/ ├── config.yaml ├── cleaner.py ├── api.py ├── requirements.txt ├── logs/ ├── configs/ └── demo_cache/其中config.yaml是默认配置文件,cleaner.py是核心命令行工具,api.py是 HTTP API 入口,configs/目录用来放批量任务需要的多份配置,demo_cache/用来做功能测试。requirements.txt内容如下:
fastapi>=0.110,<1.0 uvicorn[standard]>=0.29,<1.0 typer>=0.9,<1.0 PyYAML>=6.0,<7.0 pydantic>=2.6,<3.0安装命令:
cd cleanup-tool python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install -r requirements.txt如果你的环境里已经装过 fastapi、uvicorn 等依赖,可以直接复用,版本不低于上面给出的下限即可。在 Linux 和 macOS 上,路径分隔符使用/;在 Windows 上,代码内部要统一用Path对象处理路径,避免硬编码\或/。
磁盘空间方面,需要预留日志目录和备份目录的空间。备份目录默认放在被清理目录同分区的.cleanup_trash下,同分区移动不会显著增加空间占用。但如果后续跨磁盘迁移或复制备份文件,就需要额外空间。判断原则是你清理 1GB 文件,备份区域最好也预留 1GB 可用空间。
4. 安装部署与启动方式
这里的设计不是把删除逻辑散落在脚本各处,而是用一个统一入口管理规则。先看一份最简配置文件config.yaml:
version: 1 targets: - path: ./demo_cache patterns: - "*.log" - "*.tmp" - "__pycache__" - "build" max_file_mb: 10 storage: mode: move_to_backup backup_dir: .cleanup_trash exclude: - ".git" - "node_modules" - "important.md" audit_log: ./logs/cleanup.log这份配置的意思是:扫描./demo_cache目录,凡是匹配*.log、*.tmp、__pycache__、build,且单文件大小不超过 10MB 的内容,都属于清理候选。最终执行时,这些内容会被移动到.cleanup_trash,而不是直接删除。.git、node_modules、important.md被列入白名单,无论规则如何都不会被清理。
命令行启动前,核心扫描函数可以按下面的思路实现。它只负责收集候选文件,不负责删除:
from pathlib import Path import fnmatch import yaml def collect_candidates(root: Path, config: dict): """根据规则收集候选清理文件,不做任何删除操作""" patterns = config.get("patterns", []) exclude = config.get("exclude", []) exclude_set = set(exclude) for current_path in root.rglob("*"): if not current_path.exists(): continue relative_parts = set(current_path.relative_to(root).parts) if exclude_set.intersection(relative_parts): continue if any(part == "__pycache__" or part == "build" for part in relative_parts): yield current_path continue if current_path.is_file(): for pattern in patterns: if fnmatch.fnmatch(current_path.name, pattern): yield current_path break上面的代码只是功能性示例。实际项目里,还需要把“匹配目录的所有文件”和“匹配单个文件”分开处理,否则移动目录时会和移动文件冲突。更稳妥的做法是先用目录规则筛掉整个目录,再对剩余文件做文件名匹配。
启动 CLI 时,先进入项目目录,然后运行:
# 只扫描,不清理 python cleaner.py scan --config config.yaml # 干跑,展示将要清理的内容 python cleaner.py clean --config config.yaml --dry-run # 正式清理,把候选文件移动到备份目录 python cleaner.py clean --config config.yaml如果一切正常,启动日志会显示扫描到的文件数以及总大小。若需要启动 API 服务,使用 Uvicorn:
python -m uvicorn api:app --host 127.0.0.1 --port 8765服务启动后,浏览器访问http://127.0.0.1:8765/docs可以直接看到 FastAPI 自动生成的 Swagger 调试页面。这里建议不要改绑0.0.0.0,除非你明确知道如何做访问控制。
5. 功能测试与效果验证
功能测试不要直接拿系统目录开始。先准备一个小型测试环境,把真实目录的复杂度模拟出来。
在项目根目录执行:
mkdir -p demo_cache/sub/build mkdir -p demo_cache/logs echo "application log" > demo_cache/app.log echo "temp data" > demo_cache/data.tmp echo "important content" > demo_cache/important.md echo "nested build output" > demo_cache/sub/build/output.bin echo "fresh source" > demo_cache/src.py这样demo_cache下有日志文件、临时文件、白名单文件、源码文件和嵌套构建目录。接下来按顺序验证。
5.1 扫描测试
运行命令:
python cleaner.py scan --config config.yaml预期输出中应该能看到app.log、data.tmp、sub/build/output.bin,不应当出现important.md和src.py。判断标准是:白名单文件没有被列为候选,正常文件没有被误伤。
5.2 干跑测试
运行命令:
python cleaner.py clean --config config.yaml --dry-run这一步非常关键。干跑模式的预期结果是:终端展示候选文件列表和预计释放空间,但磁盘上的app.log、data.tmp仍然存在。判断成功的标准很简单,清理前和清理后对比文件大小,文件数量没有任何变化。
5.3 安全清理测试
运行正式清理:
python cleaner.py clean --config config.yaml执行后检查demo_cache/.cleanup_trash目录,应该能看到刚才候选文件被移动进来。原来的demo_cache/app.log已经不在原位置,但文件内容在备份目录中完整保留。判断成功的标准是:原目录文件消失,备份目录文件存在,important.md仍然存在于原目录。
5.4 权限边界测试
把测试目录中某个文件设置成只读,再把config.yaml里的targets.path指向该目录。清理命令执行后,工具应当跳过只读文件,并在审计日志中写入权限错误。更好的设计是只把这类错误记入日志,而不是让整个任务中断。
5.5 审计日志测试
清理完成后打开logs/cleanup.log,预期能看到每条清理记录包含时间、配置来源、源路径、目标路径、操作结果。没有审计日志的清理工具不值得信任,日志缺失的情况下无法定位误删原因。
6. 接口 API 与批量任务
命令行工具适合人肉运维,但如果要把清理能力接入自己的 Web 系统或定时任务,最好走 HTTP API。FastAPI 的实现非常轻量,api.py可以写成一个透明转发层。
示例代码:
from fastapi import FastAPI from pydantic import BaseModel import cleaner app = FastAPI(title="Cleanup API") class CleanRequest(BaseModel): config_path: str = "config.yaml" dry_run: bool = False @app.post("/scan") def scan_remote(req: CleanRequest): # 实际实现中,这里调用 cleaner.scan_for_api() return { "status": "ok", "config_path": req.config_path, "candidates": [] } @app.post("/clean") def clean_remote(req: CleanRequest): # dry_run=True 时只返回预览结果,不做删除 return { "status": "ok", "dry_run": req.dry_run, "moved": [] }实际项目里,cleaner.scan_for_api()和clean_remote()中必须返回结构化的文件清单。推荐统一返回三项字段:status表示任务状态,total_bytes表示预计释放或实际释放空间,items表示每一条文件路径和处理结果。
用 curl 调用扫描接口:
curl -X POST http://127.0.0.1:8765/scan \ -H "Content-Type: application/json" \ -d '{"config_path": "./config.yaml"}'调用清理接口前,建议先采用 dry-run:
curl -X POST http://127.0.0.1:8765/clean \ -H "Content-Type: application/json" \ -d '{"config_path": "./config.yaml", "dry_run": true}'返回结果后,人工确认没有异常,再发送dry_run: false的请求:
curl -X POST http://127.0.0.1:8765/clean \ -H "Content-Type: application/json" \ -d '{"config_path": "./config.yaml", "dry_run": false}'在 Python 项目中,也可以用requests批量调用:
import requests API_BASE = "http://127.0.0.1:8765" config_list = [ "./configs/project-a.yaml", "./configs/project-b.yaml", "./configs/project-c.yaml", ] for config_path in config_list: response = requests.post( f"{API_BASE}/clean", json={"config_path": config_path, "dry_run": True}, timeout=120, ) result = response.json() print(config_path, response.status_code, result.get("status"))批量任务的核心不在于并发,而在于可控。文件清理本身就是 IO 密集型任务,不建议同时发起几十个并发清理请求。更稳妥的方式是写一个循环脚本,每次清理一个目录;如果目录数量很多,就做成队列,逐个领取任务,每个任务执行后记录结果。出现错误时重试前必须重新扫描,确保文件状态没有发生变化。
7. 资源占用与性能观察
这款工具没有任何 GPU 运算,所以显存和显卡驱动都不是考察重点。真正需要观察的是 CPU、内存和磁盘 IO。
启动 API 服务后,可以用任务管理器(Windows)或htop(Linux / macOS)查看进程资源。清理工具的主要开销来自Path.rglob("*")遍历目录,以及移动大量小文件时产生的磁盘 IO。扫描一万个文件通常只在几秒到几十秒之间,具体取决于磁盘类型和文件数量。如果是机械硬盘,遍历大量小文件会比固态硬盘慢很多。
内存占用和扫描范围直接相关。如果targets.path指向了一个非常深的目录树,程序会先把所有路径加入内存里再做过滤。为避免内存失控,可以在配置里增加max_depth字段,只允许扫描到指定层级。在遍历逻辑中,深度超过阈值的目录直接跳过。这个字段是必要的保护机制,建议默认值设为 6 到 8 层。
另一个降低资源占用的方式是扩大exclude白名单。很多扫描慢不是因为规则不够有效,而是因为工具反复进入node_modules、.git、venv这些不需要检查的目录。把这些目录写进exclude,可以明显减少扫描耗时。
清理大批量文件时,建议把同时执行的移动线程数限制为 1。因为文件移动速度通常不取决于 CPU,而取决于磁盘 IO 和文件系统锁。单线程顺序移动反而更稳定,也更容易在中断后恢复。如果需要测试服务稳定性,可以关注批量任务执行后 API 是否还正常响应。如果一个任务长时间占用,其他请求可能等待,这是正常的,因为当前示例没有做异步任务队列。生产场景更适合增加一个任务表,把scan和clean都设计成异步任务。
日志本身也会占用空间。清理任务执行得越频繁,cleanup.log会快速增长。建议在审计日志模块里增加按大小轮转能力,当日志超过 20MB 时自动归档。归档后的日志可以继续遵守旧的清洁规则,比如只保留最近 14 天。
8. 常见问题与排查方法
清理工具一旦出问题,后果通常比较直接:文件消失、脚本卡住、权限错误。下面把常见现象整理成排查表。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
启动后提示找不到cleaner模块 | 未激活虚拟环境或依赖未安装 | 检查当前 Python 环境 | 执行python -m pip install -r requirements.txt |
读取config.yaml报错 | YAML 缩进或路径格式错误 | 用在线 YAML 检查工具验证 | 对照示例配置修正 |
| 扫描结果为空 | patterns与文件名不匹配,或路径写错 | 查看扫描日志和当前目录绝对路径 | 调整 patterns,确认代码运行时路径 |
| 清理命令没有移动文件 | 仍处于dry-run模式 | 查看命令行输出 | 去掉--dry-run后再运行 |
important.md被误删 | exclude没有配置或路径写错 | 检查配置文件中的 exclude 列表 | 补充白名单路径 |
| 移动文件提示权限不足 | 当前账号无目录写入权限 | 查看审计日志中的异常信息 | 以有读写权限的账号运行,不要直接在系统根目录执行 |
| API 端口被占用 | 8765 端口已被其他进程使用 | 检查端口监听状态 | 启动时改用--port 8766 |
| API 调用超时 | 目录文件数量多,遍历速度慢 | 查看服务日志和 CPU 占用 | 增加max_depth或扩大 exclude,减少扫描目录范围 |
| 文件清理后无法恢复 | 备份目录被清空或不在同一磁盘 | 检查配置中的backup_dir | 从文件系统回收站或备份盘中恢复 |
| 批量任务中途卡住 | 某个目录持续被占用或权限异常 | 查看任务日志最后一条记录 | 将该目录加入排除列表,跳过问题目录 |
遇到“清理后文件找不到”时,第一时间不要继续执行任何删除命令,先检查备份目录。由于本工具默认采用移动方案,备份目录中大概率还能找到文件。如果备份目录也被清空,就需要看审计日志,确认执行清理的时间点,再从文件系统恢复工具尝试恢复。这也是为什么开头强调:在生产环境中,备份目录清空必须走单独确认流程,不能和清理任务混在一个按钮里。
9. 最佳实践与使用建议
清理工具虽然简单,但工程化落地时有不少细节值得注意。
第一次使用,永远从小目录测试开始。用dry-run模式跑通全流程,确认每条规则都符合预期,再换到真实目录。不要一上来就把清理规则指向用户主目录或系统盘。即使白名单存在,也经不起一个路径拼接失误带来的后果。
配置文件要有版本管理。config.yaml不是无关紧要的设置文件,它是清理行为的“法律条文”。应该把它提交到 Git 仓库,记录每次新增规则、删除规则的原因。这样出了事故,可以快速回滚到上一个可用配置。
备份目录要设计成独立生命周期。普通清理任务可以每天把新候选移入.cleanup_trash,但清理任务不应该负责清空备份目录。建议由人工或独立定时任务每周检查一次备份目录,确认没有告警后再删除超过 7 天的备份文件。
对外开放 API 时,不要直接在 FastAPI 的 Swagger 页面上操作生产目录。至少在服务外层加一层访问令牌,并且让 API 调用只能使用独立配置。如果你在局域网内开放服务,也要把网段限制住,不要轻易用--host 0.0.0.0。建议的启动方式仍然是:
python -m uvicorn api:app --host 127.0.0.1 --port 8765要让批量任务可靠,还需要给每个任务加唯一 ID,并把每次清理结果保存成独立 JSON 文件。这样即使脚本中途崩掉,也能根据任务 ID 判断哪些作业完成了、哪些还没有处理。任务失败后不要直接重跑同一个参数,而要先执行一次 scan,再比较文件列表,防止第一次任务已经移动过的文件被第二次任务再次判断。
任何团队工具上线前,都要经过一轮权限复核。确认哪些人可以触发清理、哪些目录属于高危目录、哪些文件需要永久保留。如果工具部署在多人开发机上,最好使用独立的低权限账号运行,而不是使用 root 或管理员账号。清理操作必须做到任何一步都能追溯到具体的人和时间。
10. 总结与下一步
这套“赵光义清理工具(义主)”最值得尝试的点,是把危险操作变成可预览、可回退、可审计的流程。不要一上来就追求全自动,优先验证三个能力:扫描结果是否正确、干跑模式是否真的不写文件、清理后文件是否能在备份目录找回。这三个点过了,工具才谈得上批量任务和 API 集成。
最容易踩坑的地方是路径规则。一个通配符写得太宽,可能把不该清理的日志全部卷进来;一个 exclude 写得不够具体,又会把目录中某个特殊状态的文件漏掉。因此每个新增规则都应该用最小测试目录验证一次,而不是直接改配置文件并全量执行。
如果后续要继续扩展,可以从三个方向入手。第一个方向是可视化报告,把扫描结果生成 HTML 或 Markdown 报告,让人工复核时一目了然。第二个方向是增量清理,通过记录上次扫描时间和文件哈希,只清理“新产生且已过期”的文件,减少重复扫描。第三个方向是接入 AI 模型缓存清理,把 Hugging Face 等模型下载器的缓存目录纳入规则,清理已经不再被引用的历史版本,这一步需要先读清楚模型缓存目录的结构,防止把正在使用的权重文件移走。
清理工具的难点从来不是删除,而是把握“删除”和“保留”之间的分寸。能从“能删”走向“删得明白”,这个工具就值得保留一套在自己的日常工具箱里。