最近我一直在折腾 AI 辅助开发,前前后后做了好几个小工具,其中用得最顺手、也最有代表性的是一个文件提取工具,我给这个系列起名叫“影”。今天想重点聊聊其中这个文件提取工具,它是典型的“AI 写 80% 代码、我负责补 20% 细节”的项目,过程踩了不少坑,也总结出一些可以复用的经验。如果你经常要从一堆乱糟糟的目录、压缩包、日志文件里把目标文件捞出来,或者想试试让 AI 帮你写这类效率小工具,那这篇文章应该对你有参考价值。
先说清楚这个工具到底解决什么问题。日常工作里我经常遇到这类需求:某个项目目录下面散落着几十个 PDF、图片、日志文件,它们分布在不同的子文件夹里,有时候还躺在嵌套了好几层的 zip 压缩包中,我要做的就是把其中某一类文件全部提取出来,统一放到一个干净目录里。手动一个个找太慢,用系统搜索又不够灵活,这时候写个一次性脚本最合适。而我写脚本的方式,已经慢慢从“全手写”变成了“AI 生成初版 + 我审核 + 我修边界问题”。
这个工具不是那种需要复杂架构的大型系统,它就是一个单文件 Python 脚本,核心功能可以概括为:按扩展名、文件名关键词、正则规则,从指定目录递归提取文件,也支持把压缩包里的匹配文件解放出来。整套代码量不大,但“AI 辅助开发”的过程很有意思,值得展开说说。
1. 内容整体设计与思路拆解
1.1 为什么选择 AI 辅助开发这类小工具
先说我的结论:像文件提取工具这种“需求明确、逻辑直白、代码量不大”的脚本,是最适合 AI 辅助开发的类型。原因有三个方面。
第一,这类工具边界很清晰。输入就是“源目录 + 匹配规则”,输出就是“一批文件”,中间过程无非是遍历、判断、复制。没有复杂业务状态,没有需要长期维护的数据结构,AI 很容易理解。
第二,踩坑点集中在少数几个细节上。比如 Windows 和 macOS 的路径分隔符不一致、压缩包里的文件名可能带非法字符、重名文件怎么处理。这些问题只要在提示词里点一下,AI 通常能提前写进代码里,或者我后期补丁加进去也不费劲。
第三,调试成本低。脚本跑一遍就知道结果对不对,错误信息也直观,非常适合用“让 AI 写、我验证、不行再让 AI 改”这种循环模式。
我一开始也试过让 AI 直接生成一个功能特别全的“万能文件管理器”,结果提示词写了八百字,出来的代码反而哪哪都不对。后来我换了个思路:只让它实现最核心的一条功能线,也就是“扫描目录 + 复制文件 + 解压提取”,其他的像 GUI 界面、进度条、日志输出,全都砍掉或放到第二版再加。
1.2 工具定位与功能规划
这个文件提取工具“影”,最初的需求其实特别朴素:我需要把某个课程资料文件夹里所有的 PDF 讲义提取出来,那个文件夹里混着视频、图片、Word 文档、压缩包,还有各种子目录。我把需求拆成几个优先级:
- 第一优先级:给定一个根目录,递归找出所有指定扩展名的文件,复制到目标目录。
- 第二优先级:重名文件不覆盖,自动追加编号。
- 第三优先级:遇到 zip 压缩包,能进入包内,把匹配的文件也提取出来。
- 第四优先级:输出日志,告诉我每个文件从哪里来、到哪里去。
这个规划顺序很重要。因为 AI 生成代码的时候,如果你把一堆需求全塞给它,它很容易在某个环节上过度设计。比如我之前让它实现上面的功能,AI 还自作主张加了 MD5 去重、文件时间筛选、超长路径处理,代码一下子多了两百多行,我审起来头大,实际用不到的功能还会引入莫名其妙的 bug。后来我学乖了,每次只让 AI 做“当前这一步”的事,跑通后再提下一个需求。这也是我和 AI 协作开发这类小工具最大的心得:把它当实习生,一次只交代一个任务,比一次给一个大需求靠谱得多。
1.3 技术选型:为什么是 Python
文件提取工具用什么语言写,很大程度上决定后面的开发效率。我选的是 Python,有几个具体原因。
Python 标准库里的pathlib处理路径非常顺手,Path.rglob()一行就能递归遍历目录;zipfile标准库直接支持 zip 读取;re做正则匹配也不在话下。这三个库组合起来,几乎不需要装任何第三方依赖,直接python3 script.py就能跑,跨平台也稳。
我不用 Node.js 或 Go 是因为没必要。Node.js 的文件遍历要自己写或者引第三方库,Go 的编译虽然爽,但为了一个脚本引入编译流程反而繁琐。Python 这种“拿起来就能写、写完就能跑”的特性,和 AI 辅助开发的高效节奏很搭。
有一个细节要注意:如果目标机器上没装 Python,可以考虑用 PyInstaller 把脚本打包成独立可执行文件。我后面对“影”做过一次打包,在 Windows 上直接用很简单,不过 Mac 上需要额外处理签名问题。这个放到后面第 4 节再细说。
2. 核心细节解析与实操要点
2.1 文件匹配规则的三种形态
文件提取的核心不是“复制文件”,而是“怎么判断哪些文件该提取”。我在提示词里明确要求支持三种匹配方式:
- 按扩展名精确匹配,比如
.pdf、.jpg、.log。 - 按文件名关键词模糊匹配,比如文件名里包含“报告”“凭证”之类的词。
- 按正则表达式匹配,这是最灵活的兜底方案。
AI 对这三种方式都能熟练生成,但你得在提示词里说清楚优先级。我的设计是:如果提供了扩展名列表,就按扩展名筛;否则按关键词;再否则按正则。三种规则二选一即可,不要同时叠加,否则逻辑容易乱。
按扩展名匹配看似简单,但有一个容易踩的坑:大小写。Path.suffix返回的是带点的小写形式,但文件系统里可能是.PDF或者.Pdf。我在回归测试时发现 AI 生成的初版代码直接把.pdf当后缀,结果一批.PDF文件漏掉了。后来我在匹配之前统一做了一次suffix.lower(),问题立刻解决。这个点也提醒我:文件系统远比我们想象的要“自由”,写工具的时候不要假设文件名一定规范。
2.2 递归扫描与性能控制
遍历目录这个动作,理论上很简单,但实际工程里极容易翻车。AI 刚生成代码时,用的是Path.rglob('*')走目录里所有文件,这在小目录没问题,可一旦碰到有权限限制的文件夹、符号链接循环、或者被占用的网络磁盘,就会报错卡住。
我后来在方案里加入了几道防线:
- 跳过
.git、node_modules、__pycache__、venv这类明显不需要处理的目录,一方面是性能考虑,另一方面是避免误提取。 - 对符号链接目录,默认不递归进入,防止系统里出现循环链接导致无限循环。
- 对权限报错,用
try/except包起来,继续扫描而不是整体崩溃。
这些经验不全是 AI 教的,更多是我自己踩完坑之后总结的。实际使用中,如果源目录里有几万个文件,单纯用rglob也能跑得动,但要随时看内存占用和耗时。你的需求如果涉及超大目录,可以考虑改用os.scandir做迭代式遍历,避免一次把所有路径都载入内存。不过“影”目前的定位是处理几百到几千文件的场景,rglob足够用。
2.3 文件名冲突与路径安全
“复制文件”这件事里最容易被忽略的是重名问题。不同的子目录下可能有两个都叫report.pdf的文件,如果直接复制到同一个输出目录,后写的会覆盖先写的,数据就丢了。
AI 生成的初版代码用的是最粗暴的方案:先删除已存在的同名文件再复制。我看到这行代码的时候心里咯噔一下,赶紧改成了“自动追加编号”策略。具体逻辑是:如果目标文件名已存在,就在主文件名后面加_1、_2,直到不冲突为止。比如report.pdf冲突了,就生成report_1.pdf,再冲突就report_2.pdf。
除了重名,路径安全也值得说。尤其是从 zip 压缩包里提取文件的时候,如果你直接用压缩包里的文件名拼接到输出路径,很容易出现路径穿越问题。典型的攻击方式是文件名里包含../../,如果处理不当,解压时文件会写到输出目录之外。这不是危言耸听,业界早就把这类漏洞叫 Zip Slip。我在代码里加了一个校验:
raw_name = Path(info.filename).name也就是只取文件名的最后一段,不要保留压缩包内部的相对路径。这样既避免了路径穿越,也让提取出来的文件平铺在输出目录里,更符合“提取”这个动作的语义。如果你希望保留内部目录结构,那是另一个功能,但至少要对路径做归一化和前缀校验。
2.4 命令行交互设计
小工具也要讲究使用体验。“影”的命令行参数我设计成下面这样:
python fextract.py --src ./source --out ./output --ext pdf jpg png --extract-zip参数含义分别是:源目录、输出目录、扩展名列表(可多个)、是否处理 zip。再配合一个--dry-run参数,让它只打印会提取什么文件但不实际复制,这样我在正式执行前可以确认规则是否正确。
这个设计也是我和 AI 来回试出来的。最初 AI 给的是一个交互式问答界面,运行后让我输入目录、输入扩展名,一次只能处理一个规则。我说不行,批量处理和脚本化调用才是刚需,改成命令行参数以后明显好用多了,也方便放到定时任务里跑。小工具的设计原则就是这样:先核心,再便利,最后才是“看起来完整”。
3. 实操过程与核心环节实现
3.1 我是怎么向 AI“下需求”的
很多人用 AI 写代码,提示词就一句话:“帮我写一个文件提取工具。”出来的东西当然能用,但基本是通用实现,离自己的真实场景差很远。我这次用的是结构化提示词,把关键信息拆开交代:
- 角色:Python 开发专家。
- 任务:实现一个命令行文件提取工具。
- 输入:源目录、输出目录、扩展名列表。
- 行为:递归扫描,按扩展名匹配,复制到输出目录,重名自动加编号。
- 附加条件:跳过常见无关注目录,支持 zip 包内提取,打印处理日志。
- 输出要求:提供完整单文件 Python 代码,并给出使用示例。
这段提示词放在任何主流的代码生成助手里面都能得到一份能跑的初稿。但注意,我只让 AI 做“初稿”,从来没有指望它一次到位。拿到代码之后,我第一件事不是跑,而是从头读一遍,重点看五个地方:路径拼接是否正确、递归是否终止、文件是否可能被覆盖、压缩包是否安全、参数默认值是否合理。
3.2 核心模块代码拆解
下面是我最终定稿里的几个核心代码片段,它们不是一次生成的,是在 AI 初版基础上改了两轮之后的结果。
文件收集部分:
from pathlib import Path import re EXCLUDE_DIRS = {'.git', '__pycache__', 'node_modules', 'venv', '.venv'} def collect_files(src_dir, exts): pattern = re.compile( r'\.(' + '|'.join(ext.lower() for ext in exts) + r')$' ) hits = [] for p in Path(src_dir).rglob('*'): if p.is_dir(): continue if any(part in EXCLUDE_DIRS for part in p.parts[:-1]): continue if pattern.search(p.name.lower()): hits.append(p) return hits这段代码的关键点有两个:一是把扩展名全部转小写再组合成正则,二是p.parts[:-1]检查父目录路径里是否有排除项。前者解决大小写问题,后者避免误扫依赖目录。
复制与去重部分:
def dedupe_path(target: Path) -> Path: if not target.exists(): return target stem, suffix = target.stem, target.suffix i = 1 while True: candidate = target.with_name(f"{stem}_{i}{suffix}") if not candidate.exists(): return candidate i += 1 def safe_copy(src: Path, out_dir: Path) -> Path: target = out_dir / src.name target = dedupe_path(target) shutil.copy2(src, target) return targetshutil.copy2会保留文件的修改时间等元数据,这个细节很重要。有些文件提取工具复制完以后,文件时间全变成“现在”,后面核对版本就麻烦了。用copy2虽然只多写一点点代码,但体验完全是两码事。
压缩包提取部分:
import zipfile def extract_from_zip(zip_path: Path, out_dir: Path, exts): pattern = re.compile( r'\.(' + '|'.join(ext.lower() for ext in exts) + r')$' ) with zipfile.ZipFile(zip_path) as zf: for info in zf.infolist(): if info.is_dir(): continue if not pattern.search(info.filename.lower()): continue safe_name = Path(info.filename).name target = dedupe_path(out_dir / safe_name) with zf.open(info) as src, open(target, 'wb') as dst: shutil.copyfileobj(src, dst)这里专门用Path(info.filename).name取出文件名最后一段,不带任何上层路径,其实就是在做安全过滤。压缩包嵌套压缩包的情况我也遇到过,理论上可以做递归解压,但考虑到会让代码复杂度上升不少,目前版本没有加这个功能。如果真有这种需要,我会单独再做一个嵌套解压工具,而不是把所有逻辑都塞进“影”里面。
最后是入口函数,用argparse处理参数:
import argparse def main(): parser = argparse.ArgumentParser(description="影 - 文件提取工具") parser.add_argument("--src", required=True, help="源目录") parser.add_argument("--out", required=True, help="输出目录") parser.add_argument("--ext", nargs="+", required=True, help="扩展名列表") parser.add_argument("--extract-zip", action="store_true", help="同时提取zip内的文件") parser.add_argument("--dry-run", action="store_true", help="只预览不复制") args = parser.parse_args() src_dir = Path(args.src) out_dir = Path(args.out) if not src_dir.exists(): raise SystemExit(f"源目录不存在: {src_dir}") out_dir.mkdir(parents=True, exist_ok=True) files = collect_files(src_dir, args.ext) if args.extract_zip: for zf in collect_files(src_dir, ["zip"]): print(f"处理压缩包: {zf}") if not args.dry_run: extract_from_zip(zf, out_dir, args.ext) if args.dry_run: for f in files: print(f"[DRY RUN] 将提取: {f}") return for f in files: target = safe_copy(f, out_dir) print(f"已提取: {f} -> {target}")argparse是标准库,不用额外装东西,而且自动支持--help,对小工具来说足够了。
3.3 从 AI 初稿到可用代码的迭代记录
我记录一下这次开发的真实迭代过程,方便你感受“AI 辅助开发”的节奏。
第一轮,AI 给出了一个完整脚本,能跑通基本流程,但有几个问题:它把输出目录直接放在源目录下的output/,如果源目录太大,会导致递归扫描时把输出文件也扫进去,形成死循环。我改成输出目录必须在源目录之外,或者用排除规则跳过输出目录。
第二轮,AI 给的重名处理是直接覆盖,我改成dedupe_path追加编号。同时发现它没有跳过.git这些目录,我补充了EXCLUDE_DIRS。
第三轮,我要求支持 zip 内提取,AI 写了个能用的版本,但存在路径穿越风险。我修正为只取安全文件名。这轮之后代码就稳定了,实际跑了几次批量提取都没有问题。
整个迭代过程大概花了一个小时,其中一半时间是我在看代码、跑测试、补充边界条件,真正“手写”的部分可能就二十行。这就是我觉得 AI 辅助开发有价值的原因:它没有替我做判断,但帮我省掉了大量敲代码的体力活。
4. 常见问题与排查技巧实录
4.1 中文文件名与编码问题
文件提取工具在国内环境下绕不开中文文件名。Python 3 在绝大多数情况下处理中文路径没什么问题,但有几个场景要注意。
从 zip 压缩包提取时,如果压缩包是 Windows 上生成的,内部文件名编码可能是 GBK,而标准zipfile模块默认假设它是 UTF-8。遇到这种压缩包,info.filename会变成乱码,提取出来的文件名没法看。我目前的处理方案并不优雅但有效:遇到UnicodeDecodeError就回退到 GBK 解码。你也可以用第三方库zipfile的cp437编码参数,但实测下来还是有点复杂。对偶尔一次的手动使用来说,先用unzip -O gbk把压缩包解出来再交给“影”处理,反而是最省事的方式。
另外,输出目录的权限问题也值得注意。在 Linux 服务器上,如果目标目录属于 root,普通用户写入会直接PermissionError。我在脚本里加了try/except,失败时把错误信息打出来,而不会因为某个文件失败导致整个任务中断。
4.2 大文件与性能表现
“影”目前是用copyfileobj做流式复制,所以大文件不会爆内存。真正影响性能的是海量小文件,比如几千个几 KB 的日志文件。我实测过,在一台普通的 MacBook 上,处理 5000 个左右的小文件,耗时大概在十秒级别,这个结果完全可以接受。
如果你要处理的是几十万文件级别的大目录,建议换一个思路:不要用 Python 脚本去复制,先用find和cp --parents这类系统命令把文件快速挑出来,“影”只负责做更细粒度的匹配和去重工作。没有哪个工具能通吃所有场景,知道自己工具的极限在哪里,比硬撑着塞进去更多功能更重要。
4.3 AI 生成代码的常见“幻觉”和应对
用 AI 写代码这几年,我总结出它最常见的几类问题:
- 虚构 API。AI 有时会编造一些根本不存在的第三方库或方法,比如
os.path.islinkdir这种,编译或运行时报错。应对方法很简单:运行前通读一遍代码,遇到没见过的 API 就查文档。 - 优化过度。AI 喜欢给简单逻辑加缓存、并发、配置化,结果代码量大增而实际收益为零。我的原则是:第一版只要能跑就行,优化留给以后真正需要时再说。
- 忽略异常。AI 生成的代码默认“所有文件都能正常读取”,但现实中的文件可能被占用、损坏、无权限。你必须在需求里明确要求处理异常。
我自己的做法是,给 AI 的提示词里固定加一句:“所有涉及 IO 操作的地方都必须捕获异常并打印清晰错误,不能中断整体流程。”这句话几乎能规避掉一半的线上问题。
4.4 问题速查表
我把这次开发中遇到的主要问题整理成了下面的表格,方便你以后直接对照:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 提取结果缺少文件 | 扩展名大小写不匹配 | 匹配前统一转小写 |
| 重名文件被覆盖 | 没有去重逻辑 | 使用追加编号策略 |
| zip 解压出乱码文件名 | 压缩包内部是 GBK 编码 | 先解码再取名,或用系统 unzip 工具 |
| 扫描到输出目录自身 | 输出目录在源目录内 | 输出目录放到源目录外,或加排除规则 |
| 权限不足导致中断 | 目标目录不可写 | try/except 捕获,跳过并记录 |
| 路径穿越安全隐患 | 直接使用压缩包内文件名拼路径 | 只取文件名最后一段,过滤非法路径 |
| AI 无法一次生成正确代码 | 需求描述不够具体 | 结构化提示词,一次只提一个核心需求,多次迭代 |
5. 用 AI 辅助开发这类工具的价值边界与扩展空间
5.1 什么样的项目适合让 AI 写
经过这几个小工具的开发,我的判断标准越来越明确:如果一个项目的需求能用一两句话说清楚,输入输出都很明确,逻辑边界清晰,那它就很适合 AI 辅助开发。文件提取工具、批量重命名、日志分析、文本格式转换、定时备份脚本,都属于这个范围。反过来,涉及复杂业务状态、多人协作、在线服务稳定性、用户权限体系的系统,AI 可能给你搭个框架,但核心决策和设计思考还是得自己来。
用 AI 开发小工具,最忌讳的是“需求都说不清楚就希望 AI 一步到位”。你得先花十分钟把自己的需求想明白,再花十分钟写提示词,这样 AI 生成代码的质量会有质的提升。你省掉的时间不是“思考需求”的时间,而是“编写重复代码”的时间。
5.2 “影”后续可能的扩展方向
作为一个实际在用的工具,“影”目前已经够用,但我也想过它还能往哪些方向发展。
一是增加按文件内容识别类型的能力。有些文件扩展名是错的,但用 magic number 能识别真实格式,比如把伪装成.dat的 PNG 图片捞出来。Python 的magic库可以做到,但会引入第三方依赖,所以我目前没加。
二是支持更多压缩格式。除了 zip,还想让它处理 7z、rar、tar.gz。tar.gz 用标准库的tarfile就能处理,7z 和 rar 需要依赖系统里的工具命令行,集成起来略麻烦。如果你只是自己用,建议直接用外部命令解压,再交给工具做最终提取。
三是打包成桌面应用。我之前用 PyInstaller 把“影”打成了 Windows 下的 exe,给一个完全不懂命令行的同事用,效果还不错。但打包体积不小(大概 10MB 左右),启动速度也不如直接跑 Python 脚本快。要不要打包,取决于使用人群。
四是做成更通用的“文件规则提取器”,把匹配规则外置成配置文件,支持用户自定义优先级、过滤条件、目标目录结构。这个方向很像写一个“迷你版 Everything 加批量移动功能”,但工程量会明显上去,能不能保持“小工具”的轻便感,是个值得权衡的问题。
5.3 与 AI 协作的一些长期心得
最后聊聊使用 AI 开发工具这件事本身的感受。我用它一年多,最大的变化不是代码量变少了,而是“想法到落地”的周期变短了。以前想到一个工具,可能会因为“要写太多代码”而放弃,现在只要需求能说清楚,基本都能很快做出来。这种快速迭代带来的信心,反而让我更愿意留心日常工作中的低效环节。
不过也要清醒地认识到,AI 生成的代码只是你的起点,不是你的终点。我几乎不直接拿 AI 的第一次输出去生产环境,一定会做一轮人工审查和边界测试。尤其在涉及文件操作、网络请求、数据处理这类场景时,一个小小的错误可能带来不可逆的影响。对于一次性运行的临时脚本,AI 初版直接跑问题不大;对于准备长期使用、甚至要分发给同事的工具,流程就可以再严格一点。
还有一个细节:AI 生成的代码结构有时不统一,比如函数命名一会儿是get_files,一会儿是fetch_files。小工具无所谓,但代码量到一定程度后,建议先让它按你的风格整理一遍再入库。“影”现在的代码风格是我人工统一过的,后续再往上加功能,AI 生成的代码也能保持相对一致。
最后分享一个我实际踩过的坑
之前我写过一个类似工具,第一次跑的时候直接把输出目录放在了源目录里面,而且没加排除规则。结果工具一边复制文件,一边把自己刚复制出来的文件又重新扫进列表,最后陷入死循环,目录里生成了几千个重复文件,差点把磁盘塞爆。从那以后,我对“输出目录位置”这个点特别敏感,凡是做文件批处理脚本,都会强制检查输出目录不会出现在扫描范围内。
这个教训也让我养成了一个习惯:写任何文件处理工具,第一件事就是确认“输出不会污染输入”。你用 AI 写代码的时候,也可以主动把这个约束写进提示词,比如“输出目录必须在源目录之外,如果输出目录在源目录内,需要排除输出目录本身”。AI 其实很吃这种具体约束,你给的条件越细,它生成的代码就越贴近你的真实需求。
“影”这个系列的另一个小技巧是:我每次批量提取前,都会先跑一遍--dry-run,确认匹配的文件数量和类型符合预期,再正式执行。整个过程多花不到一分钟,但能避免把一堆不相关的文件复制到目标目录后再花十分钟删掉。这个小习惯,对我来说比任何代码优化都值钱。