
这次我们来看一个自带“后悔药”的清理工具项目代号叫“赵光义清理工具义主”。名字只是个代号重点不在名字而在它解决的问题很多清理脚本写到最后就变成一条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内容如下fastapi0.110,1.0 uvicorn[standard]0.29,1.0 typer0.9,1.0 PyYAML6.0,7.0 pydantic2.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.yamlversion: 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 服务使用 Uvicornpython -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(titleCleanup 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_runTrue 时只返回预览结果不做删除 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-runcurl -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}, timeout120, ) result response.json() print(config_path, response.status_code, result.get(status))批量任务的核心不在于并发而在于可控。文件清理本身就是 IO 密集型任务不建议同时发起几十个并发清理请求。更稳妥的方式是写一个循环脚本每次清理一个目录如果目录数量很多就做成队列逐个领取任务每个任务执行后记录结果。出现错误时重试前必须重新扫描确保文件状态没有发生变化。7. 资源占用与性能观察这款工具没有任何 GPU 运算所以显存和显卡驱动都不是考察重点。真正需要观察的是 CPU、内存和磁盘 IO。启动 API 服务后可以用任务管理器Windows或htopLinux / 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 8766API 调用超时目录文件数量多遍历速度慢查看服务日志和 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 等模型下载器的缓存目录纳入规则清理已经不再被引用的历史版本这一步需要先读清楚模型缓存目录的结构防止把正在使用的权重文件移走。清理工具的难点从来不是删除而是把握“删除”和“保留”之间的分寸。能从“能删”走向“删得明白”这个工具就值得保留一套在自己的日常工具箱里。