把“清理硬盘空间”这件事想简单了的人,往往还没有经历过真实环境:系统 Temp 目录里堆着几个月前安装包留下的缓存,下载文件夹中躺着一两个体积惊人的虚拟磁盘镜像,备份盘里同一份照片出现多个副本。手工删除不仅费时,而且一旦清理逻辑不清晰,很容易把不该删的文件顺手删掉。
这篇文章要分享的内容也和硬盘清理有关,只不过名字有点特殊:“三连发,但都不是人写的”。更准确地说,这三款硬盘清理小工具,都是借助 AI 编程助手生成初版代码,再由人工做安全审查、目录过滤与参数调整后得到的可执行脚本。三个人各自解决一个不同的磁盘占用问题:临时文件清理、大文件定位、重复文件检测。代码都使用 Python 标准库编写,不依赖第三方 GUI,也不包含任何隐藏的“全家桶”安装逻辑。
如果你正在为 C 盘爆红发愁,或者想体验一下“用 AI 辅助开发工具类脚本”的完整工作流,这篇文章会比较合适。
1. 为什么要做三款硬盘清理工具?
1.1 从一次“系统盘变红”的现实场景说起
Windows 系统盘剩余空间不足时,任务栏会频繁出现红条提示,软件更新失败、浏览器缓存写入异常、IDE 索引无法生成,各种问题会接连出现。很多人第一反应是打开“磁盘清理”,勾选“临时文件”后点击清理。这个动作确实有用,但往往只能解决一部分问题。
更常见的场景是这样的:下载目录中保留了十几个不同版本的安装包;Python 项目构建时留下的.pyc缓存和__pycache__目录;旧项目里为了排查问题而复制出来的日志文件;开发工具在用户目录下悄悄写入的索引和缓存。这些东西不会出现在系统自带的清理列表里,也不会被常规“垃圾清理”软件完整覆盖。
这就需要一个更灵活的清理工具,能自己指定扫描范围、自己定义过期时间、自己决定是否执行删除。而这类“自定义的清理脚本”并不复杂,却非常值得沉淀成工具,避免每次都重复排查。
1.2 “都不是人写的”到底指什么
标题里的“都不是人写的”,并不是指工具没有人开发,而是指这三份代码的初版并非由开发者逐行手写,而是由 AI 编程助手根据需求描述生成,再由人工审查、修改和补充边界逻辑。
这种开发方式在脚本型工具中非常实用。AI 编程助手有一个天然优势:它可以快速把一个“功能描述”翻译成 Python 代码。例如“写一个扫描当前目录下大文件的脚本”,它能立刻生成一个可运行的版本。但 AI 生成的代码有一个明显短板:它不一定理解你的真实使用场景,也不一定了解你所在环境的目录权限、命名习惯和安全边界。如果直接把 AI 输出的代码放到生产环境运行,风险相当大。
所以真正落地的流程应当是:使用 AI 生成初版脚本,人工理解脚本逻辑,补充 dry-run 演练模式,明确“默认不删除任何文件”,最后在小范围目录中验证,再放到真实环境中使用。标题表面在说“不是人写的”,实际上是在讲“AI 负责生成,人负责审查”的新协作方式。
1.3 这三款工具分别解决什么问题
| 工具名称 | 解决的核心问题 | 典型场景 |
|---|---|---|
| cleanup_tmp.py | 临时目录与缓存文件积累 | Temp 目录占用过大、安装包残留 |
| find_big_files.py | 无法快速定位大文件在哪 | 磁盘空间告警,需要找到 Top 大文件 |
| find_duplicates.py | 同一份文件在多个目录重复存放 | 备份文件重复、照片视频出现多份副本 |
这三款工具不是替代品,而是互补关系。实际排查磁盘空间时,建议先运行第二款工具找出大文件,再运行第三款工具检查重复文件,最后用第一款工具清理超过指定天数的临时文件。扫描和删除分开执行,可以避免误删。
2. 环境准备与项目目录规划
2.1 运行环境版本要求
本文示例以 Windows 11 + Python 3.10 为主要环境,同时兼容 Windows 10、Linux 和 macOS。三个脚本全部使用 Python 标准库,不需要额外安装 requests、pandas 等第三方依赖,因此对 Python 版本的要求也不高,Python 3.8 以上基本都能运行。
如果你的电脑还没有安装 Python,可以先到 Python 官网下载安装包。安装时务必勾选“Add Python to PATH”,方便在命令行中直接使用python命令。如果安装的是 Python 3.12 等更新版本,脚本逻辑也不需要改动。
需要注意一点:不同 AI 编程助手生成的代码风格会有差异,版本要求也可能不同。因此在生成脚本时,最好在提示词中写明目标平台和 Python 版本。例如“请用 Python 编写一个命令行脚本,目标平台是 Windows 11,Python 版本是 3.10”,这样能减少兼容问题。
2.2 建议的项目目录结构
为了便于管理和后续扩展,建议把三款工具放在同一个目录下:
disk-cleaner-ai/ ├── cleanup_tmp.py ├── find_big_files.py ├── find_duplicates.py └── README.mdREADME.md不是必需文件,但建议记录每个脚本的功能、运行参数和已知风险。如果后续让 AI 继续迭代代码,这些记录会变成非常有价值的上下文,帮助 AI 快速理解之前的设计决策。
在写作本文前,我建议你单独准备一个测试目录,例如D:/clean_test,在测试目录中创建一些临时文件和大文件。先在这个目录里反复 dry-run,确认无误后再把扫描对象切到真实目录。
2.3 运行前必须明确的安全边界
磁盘清理工具和普通代码不同,它一旦真正执行删除操作,就是不可逆的。即使删除后使用数据恢复工具,也不能保证 100% 找回。因此,在运行任何清理类脚本前,必须先明确几条红线:
第一,默认永远使用 dry-run 模式。所谓 dry-run,就是“演练模式”,脚本只扫描、统计并打印将要做的事情,但不真正删除。只有用户明确加入类似--delete的参数后,脚本才执行删除。把删除开关设计为“主动加入”而不是“默认开启”,能最大程度避免误操作。
第二,不要对系统关键目录做无差别清理。例如C:/Windows/System32、C:/Program Files这类目录,不建议交给通用清理脚本扫描删除。脚本扫描范围应当限定在临时目录、缓存目录、用户下载目录等明确安全的范围内。
第三,扫描结果不等于删除名单。脚本输出“发现多少个文件”只是第一步,真正删除前,应当用人工或白名单机制再确认一遍。尤其在删除重复文件时,要知道保留哪一个路径下的副本。
3. 清理类工具的共同套路与提示词设计
3.1 磁盘清理背后的操作模型
不管是临时文件清理、大文件定位还是重复文件检测,它们的底层逻辑都有相似之处:遍历目录 → 收集文件属性 → 判断条件 → 输出结果。临时文件清理多了“删除”动作,大文件定位多了“排序”过程,重复文件检测则多了一次“内容哈希计算”。
把清理任务抽象成这个流程,非常有利于和 AI 协作。因为 AI 很难在没有约束的情况下一次生成完美代码,但如果你把步骤拆清楚,它生成的代码质量会明显提升。
一次典型的目录扫描至少需要处理以下问题:目录不存在时怎么办;遇到权限不足的目录是终止还是跳过;文件被占用导致删除失败时怎么处理;扫描大目录时如何避免重复遍历符号链接。这些问题里,“权限”和“文件被占用”是 Windows 环境最常见的两个异常点,AI 往往能写出 try-except,但选择忽略哪些异常、在哪里忽略,则需要人工确认。
3.2 如何向 AI 描述清理需求
一个“能跑”的清理脚本提示词,至少应当包含五类信息:目标平台与语言版本、输入参数、扫描范围、输出形式、安全边界。下面以临时文件清理器为例,给出一段可以直接使用的提示词模板:
请用 Python 写一个临时文件清理脚本,目标平台是 Windows 11。 需求如下: 1. 默认扫描系统临时目录,同时支持通过 --dirs 参数传入多个自定义目录; 2. 只清理最后修改时间早于 --days 天数的文件,默认值是 7 天; 3. 默认不执行删除,只有带上 --delete 参数时才真正删除文件; 4. 删除前打印每个文件的路径、文件大小,以及总计可释放空间; 5. 遍历时遇到文件占用的错误或权限错误,跳过并记录,不要中断程序; 6. 只使用 Python 标准库实现。这段描述中,最重要的是第 3 条。如果没有把“默认不执行删除”写清楚,AI 很可能生成一个默认直接删除的脚本。这类脚本在测试环境中看不出问题,一旦放到真实 Temp 目录下执行,后果无法估计。
3.3 AI 生成代码后的人工检查顺序
拿到 AI 生成的代码后,不要急着直接运行。建议按照下面的顺序做一次代码审查:先看命令行参数解析部分,确认“默认操作”是否安全;再看目录遍历方式,确认是否使用了递归遍历、是否可能形成循环;然后看文件筛选条件,确保删除逻辑只作用于符合条件的普通文件;最后看异常处理范围,判断 catch 到的异常是不是太宽泛。
代码审查能力是使用 AI 编程助手过程中最值得提升的技能。很多时候,AI 给出的代码初版已经覆盖了 80% 的主流程,剩下 20% 的安全边界与异常分支需要人来补充。这三款硬盘清理工具的代码,也会在实际项目中反复调整。
4. 第一发:临时文件与过期缓存清理器
4.1 需求要点
第一款工具解决的是“临时文件越积越多”的问题。Windows 临时目录通常存储在%TEMP%环境变量中,不同用户的 Temp 路径不同,因此脚本不能把路径写死,而应该通过环境变量读取。
清理临时文件时不能一刀切。有些文件虽然位于 Temp 目录,但可能正在被某个程序使用,删除时会抛出 PermissionError。还有些文件是最近几分钟刚生成的,大概率还在被使用,清理它们的意义不大。因此,第一版工具增加了两个条件:只处理最后修改时间早于--days天数的文件;遇到文件被占用或权限问题时跳过。
4.2 完整代码:cleanup_tmp.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 临时文件清理小工具 功能: - 扫描指定目录或系统 Temp 目录 - 找出最后修改时间早于指定天数的文件 - 默认只打印结果,不执行删除 - 只有传入 --delete 参数时才真正删除文件 用法示例: python cleanup_tmp.py --days 7 python cleanup_tmp.py --dirs D:/clean_test --days 7 python cleanup_tmp.py --days 7 --delete """ import argparse import os import time from pathlib import Path def parse_args(): parser = argparse.ArgumentParser(description="临时文件清理工具") parser.add_argument( "--dirs", nargs="*", default=None, help="要扫描的目录,可传入多个;默认使用系统临时目录", ) parser.add_argument( "--days", type=int, default=7, help="只清理最后修改时间早于 N 天的文件,默认 7 天", ) parser.add_argument( "--delete", action="store_true", help="加入该参数后才会真正删除文件,默认只做演练", ) return parser.parse_args() def get_default_temp_dirs(): """从系统环境变量中获取临时目录。""" temp_dir = os.environ.get("TEMP") if temp_dir: return [Path(temp_dir)] return [Path.home() / "AppData" / "Local" / "Temp"] def collect_expired_files(dirs, cutoff_ts, exclude_dirs=None): """ 遍历目录,找出最后修改时间早于 cutoff_ts 的普通文件。 返回列表,每个元素为 (文件路径, 文件大小, 最后修改时间)。访问失败的文件直接跳过。 """ if exclude_dirs is None: exclude_dirs = {".git", "$RECYCLE.BIN", "System Volume Information"} candidates = [] for base_dir in dirs: base_dir = Path(base_dir).expanduser().resolve() if not base_dir.exists(): print(f"[跳过] 目录不存在: {base_dir}") continue for dirpath, dirnames, filenames in os.walk(base_dir): # 删除目标列表,不进入这些子目录,避免碰到系统和回收站目录 dirnames[:] = [d for d in dirnames if d not in exclude_dirs] for filename in filenames: file_path = Path(dirpath) / filename try: stat_info = file_path.stat() if stat_info.st_mtime < cutoff_ts: candidates.append( (file_path, stat_info.st_size, stat_info.st_mtime) ) except (PermissionError, FileNotFoundError, OSError): continue return candidates def format_size(size_bytes): """将字节数格式化为便于阅读的字符串。""" if size_bytes >= 1024 * 1024 * 1024: return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB" if size_bytes >= 1024 * 1024: return f"{size_bytes / (1024 * 1024):.2f} MB" if size_bytes >= 1024: return f"{size_bytes / 1024:.2f} KB" return f"{size_bytes} B" def main(): args = parse_args() dirs = [Path(d) for d in args.dirs] if args.dirs else get_default_temp_dirs() cutoff_ts = time.time() - args.days * 24 * 60 * 60 print(f"扫描目录: {', '.join(str(d) for d in dirs)}") print(f"清理阈值: {args.days} 天前被修改的文件") print() candidates = collect_expired_files(dirs, cutoff_ts) total_count = len(candidates) total_size = sum(size for _, size, _ in candidates) if total_count == 0: print("没有找到符合条件的文件,无需清理。") return print(f"共发现 {total_count} 个候选文件,预计释放 {format_size(total_size)}") print("以下是占用空间最大的 15 个候选文件:") candidates_sorted = sorted(candidates, key=lambda item: item[1], reverse=True) for file_path, size, mtime in candidates_sorted[:15]: time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime)) print(f" {format_size(size):>10} {time_str} {file_path}") if not args.delete: print() print("当前为演练模式(dry-run),未删除任何文件。") print("确认无误后,可加上 --delete 参数执行真正的清理。") return deleted_count = 0 deleted_size = 0 print() print("开始执行删除...") for file_path, size, _ in candidates: try: file_path.unlink(missing_ok=True) deleted_count += 1 deleted_size += size print(f"[已删除] {file_path}") except PermissionError: print(f"[跳过] 文件被占用或无权限: {file_path}") except OSError as exc: print(f"[跳过] 删除失败: {file_path},错误: {exc}") print() print(f"清理完成,共删除 {deleted_count} 个文件,释放 {format_size(deleted_size)}。") if __name__ == "__main__": main()4.3 参数与运行方式说明
在正式运行前,先用命令行查看帮助信息:
python cleanup_tmp.py --help帮助信息中可以看到三个关键参数:--dirs用于指定要扫描的目录,可传入多个路径,以空格分隔;--days用于设置文件过期天数;--delete是真正的删除开关,不加这个参数时,脚本只做扫描和统计。
一个比较安全的执行方式是先演练,再确认:
python cleanup_tmp.py --days 7 python cleanup_tmp.py --days 7 --delete第二条命令只有在第一条命令的输出结果看起来没有问题时才执行。执行完成后,脚本会打印“共删除多少文件、释放多少空间”,这些信息可以作为操作留痕,方便后续排查。
4.4 这段代码为什么这样设计
代码中有一个容易被忽略的细节:遍历目录时使用了os.walk(base_dir),并通过dirnames[:] = [...]修改子目录列表,从而跳过.git、$RECYCLE.BIN这类目录。如果不跳过回收站目录,脚本可能会尝试清理回收站中的文件,而 Windows 回收站有一套自己的文件命名和权限体系,清理逻辑不当容易影响回收站恢复功能。
删除时使用file_path.unlink(missing_ok=True),这个 API 在文件已被其他进程删除时不会抛出 FileNotFoundError。不过,文件被占用时仍会抛出 PermissionError,所以删除逻辑中专门捕获了这个异常并打印“跳过”,不会让整个脚本中断。
5. 第二发:大文件定位器
5.1 使用场景与实现思路
临时文件清理只能解决“目录臃肿”的问题,但很多时候,磁盘空间的真正大头是某一个 GB 级别的文件。要找到这个大文件,不能靠肉眼逐个目录翻看,最直接的方法是扫描整个目录树,统计每个文件的大小,再按大小降序排列输出 Top N。
这款大文件定位器的目标很简单:指定一个根目录,列出该目录下体积超过阈值的文件。脚本不会删除任何文件,输出内容只做定位参考。在此基础上,加入--top参数控制输出数量,加入--min-size参数控制最小体积阈值,避免输出太多无意义的小文件。
5.2 完整代码:find_big_files.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 大文件定位器 功能: - 递归扫描指定目录 - 列出文件大小大于等于阈值的大文件 - 按文件大小降序输出 Top N 用法示例: python find_big_files.py D:/Downloads python find_big_files.py D:/Downloads --min-size 200MB --top 30 python find_big_files.py . --min-size 1GB """ import argparse import os import time from datetime import datetime from pathlib import Path def parse_size(size_text): """将 100MB、1GB、500KB 这类文本解析成字节数,默认按 MB 处理。""" size_text = size_text.strip().upper() if size_text.endswith("KB"): return int(float(size_text[:-2]) * 1024) if size_text.endswith("MB"): return int(float(size_text[:-2]) * 1024 * 1024) if size_text.endswith("GB"): return int(float(size_text[:-2]) * 1024 * 1024 * 1024) try: # 没有单位时按 MB 处理,例如 100 表示 100MB return int(float(size_text) * 1024 * 1024) except ValueError: raise argparse.ArgumentTypeError(f"无法解析大小参数: {size_text}") def scan_large_files(root_dir, min_size): """ 扫描 root_dir 下的普通文件,返回大小大于等于 min_size 的文件列表。 返回列表元素为 (文件路径, 文件大小, 最后修改时间戳)。 """ result = [] for dirpath, dirnames, filenames in os.walk(root_dir): # 跳过 Windows 系统卷信息目录和回收站目录 dirnames[:] = [ d for d in dirnames if not d.startswith("$") and d not in {"System Volume Information", "Recovery"} ] for filename in filenames: file_path = Path(dirpath) / filename try: stat_info = file_path.stat() if stat_info.st_size >= min_size: result.append( (file_path, stat_info.st_size, stat_info.st_mtime) ) except (PermissionError, FileNotFoundError, OSError): continue result.sort(key=lambda item: item[1], reverse=True) return result def format_size(size_bytes): if size_bytes >= 1024 * 1024 * 1024: return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB" if size_bytes >= 1024 * 1024: return f"{size_bytes / (1024 * 1024):.2f} MB" if size_bytes >= 1024: return f"{size_bytes / 1024:.2f} KB" return f"{size_bytes} B" def main(): parser = argparse.ArgumentParser(description="扫描目录并定位大文件") parser.add_argument( "root", nargs="?", default=".", help="要扫描的根目录,默认当前目录", ) parser.add_argument( "--min-size", type=parse_size, default="100MB", help="文件大小阈值,例如 200MB、1GB、500KB,默认 100MB", ) parser.add_argument( "--top", type=int, default=20, help="只输出体积最大的前 N 个文件,默认 20", ) args = parser.parse_args() root_dir = Path(args.root).expanduser().resolve() if not root_dir.exists(): print(f"错误:目录不存在:{root_dir}") return print(f"正在扫描目录: {root_dir}") print(f"文件大小阈值: {format_size(args.min_size)}") start_time = time.time() large_files = scan_large_files(root_dir, args.min_size) elapsed = time.time() - start_time print(f"扫描完成,耗时 {elapsed:.2f} 秒,共发现 {len(large_files)} 个大文件。") if not large_files: print("未发现超过阈值的文件。") return print(f"\n体积最大的 {min(args.top, len(large_files))} 个文件如下:") for rank, (file_path, size, mtime) in enumerate( large_files[: args.top], start=1 ): mod_time = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S") print(f"{rank:>3}. {format_size(size):>10} {mod_time} {file_path}") if __name__ == "__main__": main()5.3 运行示例与结果解读
假设要扫描D:/Downloads目录,只关心大于 200MB 的文件,按体积输出前 20 个:
python find_big_files.py D:/Downloads --min-size 200MB --top 20扫描结果中,每一行会依次显示排名、文件大小、最后修改时间、完整路径。定位到大文件后,不要马上删除,而是先确认这个文件是否属于某个仍然在使用的软件。例如虚拟磁盘镜像文件可能被虚拟机正在使用,日志文件可能正被后台服务写入,这些情况下直接删除会导致程序异常。
6. 第三发:重复文件检测器
6.1 为什么重复文件很难人工排查
重复文件往往隐藏在多个目录层级中。比如“照片备份”目录下有一份原始照片,桌面上又复制了一份用于发送给朋友;下载目录里一个安装包出现了两个版本,文件名略有不同但内容完全一致。人工排查时,如果只看文件名,很难判断内容是否相同。
更可靠的方法是计算文件内容的哈希值。哈希值可以简单理解成文件的“内容指纹”,只要两个文件内容完全一致,计算出的 MD5 值就相同。不过,全盘范围两两比较哈希值性能很低。因此重复文件检测通常采用“两步筛选”:第一步先按文件大小分组,只把大小相同的文件放到一组;第二步再对同一组内的文件计算 MD5。由于真正内容的重复概率远大于刚好同大小但内容不同的情况,这种方式能大幅减少哈希计算量。
6.2 完整代码:find_duplicates.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 重复文件检测器 功能: - 扫描指定目录 - 先按文件大小分组,再对候选文件计算 MD5 - 找出内容完全一致的文件组并输出 用法示例: python find_duplicates.py D:/backup python find_duplicates.py D:/backup --min-size 1MB """ import argparse import hashlib import os from collections import defaultdict from datetime import datetime from pathlib import Path