我一直有个很土的需求:每周都要从几十份格式各异的文件里把关键字段抠出来。财务发PDF,商务发Word,还有人直接发网页另存的HTML。手动复制粘贴倒是不难,但干到第三十份的时候,手是真的会酸,眼睛也花了。于是我用AI辅助开发了一个文件提取工具,专门干这种脏活。整个过程我没有从零手敲所有代码,而是把AI当成一个随叫随到的结对编程搭档——我来拆需求、定边界、审代码,它来写初版、补逻辑、处理那些我记不太住的库函数。这篇文章会把这个工具的需求来源、选型过程、踩坑记录和优化思路完整拆开,给同样想做点小工具的人一条能直接上手的路径。
1. 为什么先做文件提取:一个被重复劳动逼出来的项目
1.1 一件本该自动化的“杂活”长什么样
先还原一下场景。每周一我会收到一个压缩包,里面是上一周新增的合同、报价单、回执,混杂着PDF、Word、Excel,偶尔还有几个网页另存为的HTML。我需要把这些文件里的合同编号、甲方乙方、合同金额、签署日期挨个找出来,填进一张汇总表。听起来不复杂,但文件一多就非常折磨人。
我记得有一回,一个36页的PDF合同里,金额和日期分散在四五个不同的章节,我反复用搜索键翻了十几分钟才核清楚。等我把三十多份文件全部处理完,一个上午已经没了。最让人崩溃的是,下周一同样的流程又会来一遍。
我把这些文件的格式和要提取的字段列了一张小表:
| 文件类型 | 常见来源 | 我需要提取的字段 |
|---|---|---|
| 电子签章合同、扫描件 | 合同编号、签署日期、金额 | |
| Word | 商务起草的合同/报价单 | 甲方、乙方、总金额 |
| Excel | 对账单、明细表 | 客户名、金额、交易期 |
| HTML | 网页导出、邮件存档 | 标题、日期、正文关键段落 |
每类文件的排版都还不一样,同一家公司的PDF合同,上个月和下个月的可能就差了页脚。这就是典型的“看起来能复制粘贴,实际上全是重复劳动”的场景,是最适合写个小工具来解放双手的。
1.2 现成工具为什么没救我
既然这么痛苦,我第一反应肯定是找现成工具。市面上的PDF提取工具、OCR识别工具、网页正文提取插件,我试了一圈,要么不顺手,要么不敢用。
先说在线转换网站。把涉密的合同文件传上去做解析,我心理上就过不去。再说桌面软件,一些老牌的PDF解析工具确实能提取文本,但批量处理要付费版才开放,而且输出格式非常固定,我想让它只提取“合同编号和金额”这种自定义字段,配置起来比手动复制还麻烦。开源方案我也试过,比如某些命令行工具,功能很强,但依赖环境复杂,装完就劝退了我这种“半吊子”使用者。
最后我确认了真实需求:要本地运行,不上传任何内容;要免费;要能一次拖入一个文件夹,批量处理;还要能自定义提取规则,而不是只能导出全文。这些东西靠手工选择很难配齐,正好适合自己写代码。
1.3 为什么选“AI辅助开发”而不是纯手写
我不是专业程序员,日常会用Python处理点Excel,但让我从零封装一个能稳定处理PDF、Word、HTML的解析工具,还是有点心虚。我记不清pdfplumber的API,也不记得BeautifulSoup的常用写法,每次都要临时翻文档。
最开始我考虑过完全让AI生成一个完整脚本,但我很快意识到一个问题:AI生成的代码,如果我看不懂,出了问题只能干瞪眼。所以我的定位是“AI辅助开发”,不是“AI全包开发”。我负责把模糊的需求拆成AI能理解的步骤,告诉它输入是什么、输出是什么、边界在哪里;它负责给我生成初版代码,帮我把不熟悉的库函数拼起来。每段代码我都会看一遍,大致知道它在干什么,再放到真实文件上跑。
实际体验下来,这个分工方式让我的开发速度快了至少三倍,而且踩坑的时候不至于完全抓瞎。接下来就说说具体怎么选型、怎么让AI干活。
2. 技术选型:AI凭什么推荐这套提取方案
2.1 核心库选型:让AI先给建议,我再做兼容性检查
我把需求发给AI的第一句话是:“我想写一个批量文件提取工具,需要处理PDF、Word、Excel、HTML,分别应该用什么Python库?”它很快给出了一组推荐。这个推荐结果和我之前查过的一些技术文章基本一致,算是靠谱:
| 文件类型 | 处理库 | 主要用途 | 我后来确认过的注意事项 |
|---|---|---|---|
| pdfplumber、PyMuPDF | 提取文本、表格 | 合并单元格和跨页表格容易错位 | |
| Word | python-docx | 读取段落、表格 | 模板变量常被拆成多个run,直接查找会漏 |
| Excel | openpyxl、pandas | 读取单元格、做汇总 | 大文件要开启只读模式,不然很吃内存 |
| HTML | BeautifulSoup、lxml | 定位节点、提取正文 | 页面编码和标签嵌套要额外处理 |
AI在选型上确实有用,因为它被训练过大量技术文档和社区讨论,能直接给出常见的“最佳实践”。但我不完全盲信。我会再补一句:“这些库都有稳定的PyPI版本吗?给我一个requirements.txt。”然后让它生成依赖清单。这么做的目的,是把“道理上可行”和“当前环境能跑”的差距尽量缩小。
我最终锁定的依赖是:
pdfplumber==0.10.3 python-docx==0.8.11 openpyxl==3.1.2 beautifulsoup4==4.12.2 pandas==2.0.3版本号都做了固定,不写“>=”,否则哪天某个库大版本更新,AI生成的旧代码可能直接跑不起来。
2.2 我发给AI的第一个详细Prompt
选型确定后,我没有直接说“帮我写个工具”,而是写了一段相对完整的Prompt:
你是一个Python开发助手。我要写一个批量文件提取工具。 背景:有大量PDF和Word合同,需要提取“合同编号、甲方、乙方、金额、签署日期”这五个字段。 输入:一个文件夹路径,里面可能有PDF、docx、xlsx、html文件。 输出:每个文件生成一个同名的markdown文件,存放在指定的输出目录,内容是一个字段表格。 约束:只读原文件,不修改原文件;遇到格式异常的文件要跳过并记录日志;所有路径要兼容Windows中文目录。 示例:如果PDF里出现“合同编号:HT-2024-001”,就提取HT-2024-001;如果找不到对应字段,留空并继续。 请先给出目录结构和关键函数,不要一次性把全部代码写完。这里我刻意加了几个关键约束:只读不修改、跳过异常、兼容中文路径。这些都是以前手动写脚本时踩过的坑,提前告诉AI,能省掉非常多返工。
AI根据这个Prompt给出了一个目录结构:
file_extractor/ ├── extract.py # 入口,命令行解析 ├── parsers/ │ ├── pdf_parser.py │ ├── docx_parser.py │ ├── excel_parser.py │ └── html_parser.py ├── output/ # 提取结果输出目录 └── logs/ # 日志目录然后它先写了一个PDF解析函数:
import pdfplumber def extract_from_pdf(file_path): results = {} with pdfplumber.open(file_path) as pdf: full_text = "\n".join(page.extract_text() or "" for page in pdf.pages) # 用正则做简单字段定位,后续再优化 return results这版代码能跑通,但字段提取很粗糙。不过没关系,我的策略是先有骨架,再迭代。
2.3 为什么第一版做成了命令行工具
很多朋友一上来就想做个图形界面,但我坚持第一版做成命令行工具。原因是批量处理场景下,命令行太适合了。我可以把几十个文件丢进一个文件夹,然后执行一条命令:
python extract.py --input ./contracts --output ./extracted --formats pdf docx xlsx html它可以被脚本调用,可以定时执行,甚至可以并进团队自己的流程里。图形界面的拖拽体验确实更好,但那等于把“如何连接各模块”的问题变成了“如何画按钮和布局”,偏离了核心目标。
AI在这个阶段帮我生成了argparse参数解析部分,把输入目录、输出目录、格式过滤这几个参数都接好了。它还给了一个很实用的建议:输出文件和原文件同名,但放到新的“extracted”目录里,这样不容易覆盖源文件。
这一版跑通后,我的感觉是:AI辅助开发的价值不在于它能一次性写出完美代码,而在于它能帮我快速把脑子里模糊的需求变成可以运行的结构,让我把精力集中在那些真正需要判断和测试的地方。
3. AI生成的代码不能直接用:完整调试与纠错过程
3.1 第一个坑:Windows中文路径和编码问题
说实话,AI生成的代码第一次跑通只是“看起来能跑”。我把一个真实的PDF文件放进去,立刻就报错了。报错信息指向读取文件路径的地方,原因是我的文件夹名称里带中文。
AI第一版代码里用了比较简单的路径拼接写法,在Windows上遇到中文路径时,如果没有正确处理编码,很容易出现文件找不到或者乱码。让我印象更深刻的是,它读取文本时用了默认编码,结果提取出的中文字符有一半变成了“锟斤拷”这种经典乱码。
我当时的排查链路是这样的:
- 先在命令行手动执行,观察完整报错。
- 把报错信息贴给AI,要求它解释为什么会出问题。
- AI给出修改建议:用
pathlib.Path替代字符串拼接,读取文本时统一使用encoding="utf-8",必要时回退到gb18030。
修正后的代码类似:
from pathlib import Path def read_pdf_text(file_path): file_path = Path(file_path) # 统一转成绝对路径,避免相对路径导致的奇怪问题 with pdfplumber.open(str(file_path.resolve())) as pdf: text = "\n".join(page.extract_text() or "" for page in pdf.pages) return text这个改动很小,但非常关键。从那之后,我再也不敢在AI生成代码里直接使用裸的字符串路径参数,都会让AI改成pathlib风格。
3.2 第二个坑:表格提取的字段错位和跨页断裂
PDF里的表格是最折磨人的。尤其是那种带合并单元格的报价单,用pdfplumber直接提取,导出来的数据经常是错位的:第一列的金额跑到了第三列,表头下面的空值也不按套路出牌。
AI第一次处理方式是“把表格原样输出”,结果我的汇总表里出现了很多None和空单元格。我拿着真实截图和提取结果对比,发现问题的本质是:视觉上合并的单元格,在底层数据结构里只有其中一个格子有值,其他格子是空的。
AI给的解决办法很朴素,却也有效:提取完表格后用pandas做“前向填充”,把空值按上一行有值的逻辑补上。
import pandas as pd def table_to_dataframe(table): # table是pdfplumber提取出的二维列表 df = pd.DataFrame(table[1:], columns=table[0]) # 合并单元格导致的空值,用上一行同列的值填充 df = df.ffill() return df这个方案救了不少场景,但也要注意:它只适用于“纵向合并”的表格。如果遇到横向合并的复杂表头,还是得写更复杂的对齐规则。我在测试报告里把这些特殊情况单独列出来,让AI辅助生成了一个“异常表登记”文件,方便后续人工复核。
跨页表格又是另一类问题。一个表格被PDF分页拆开以后,pdfplumber会把它识别成两个独立的表格,第二页的表头可能丢失。AI帮我写了一个拼接函数:如果上一页表格最后一列和下一页表格第一列结构相同,就自动合并成同一个DataFrame。这个逻辑不复杂,但如果没有真实样本驱动,AI是想象不到这个需求的。
3.3 第三个坑:AI幻觉出一个不存在的API
必须承认,AI生成的代码里出现过“看起来合理但实际不存在”的API。比如它在某个函数里写:
page.extract_tables_with_lines()我跑测试的时候直接报AttributeError。这个方法是它根据extract_tables()和lines推测出来的,文档里根本没有。
我的处理方式不是自己去查API,而是把这个报错重新投喂给AI,并要求它“先解释这段代码想做什么,再给出当前pdfplumber版本中真正可用的替代方法”。它很快承认自己“记混了API”,然后改成:
page.extract_tables()这条经验很重要:AI生成的代码必须经过“运行验证”,不能因为它看起来讲得头头是道就直接投入生产。我给自己定了个规矩:凡是AI生成的代码,至少要在一个最小样例上跑通,再放到真实文件集上做回归测试。
我还整理了一张排错表,记录常见问题:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 中文乱码 | 编码指定错误 | 统一用utf-8,并做编码回退 |
| 提取结果为None | 页面没有文字层,可能是扫描件 | 先提示需要OCR,不强行处理 |
| 字段错位 | 合并单元格未被展开 | 用pandas ffill,并人工抽查 |
| 报错AttributeError | AI生成了不存在的API | 贴回让AI修正,并跑测试集 |
这些看起来很小的问题,才是工具能否真正稳定使用的决定性因素。
4. 实测效果与性能优化:从“能跑”到“好用”
4.1 用一批真实文件做的回归测试
代码修完以后,我马上做了一个“小规模回归测试”。我从历史文件里挑了50份脱敏后的真实样本,按类型分组,跑了一遍工具,并记录了字段级提取准确率。
| 文件类型 | 样本数量 | 字段级准确率 | 主要失败原因 |
|---|---|---|---|
| 20 | 85% | 扫描件、复杂表格 | |
| Word | 10 | 95% | 模板变量被拆成多个run |
| Excel | 10 | 90% | 单元格跨行合并 |
| HTML | 10 | 88% | 编码和动态加载 |
这个结果比我预想的好,但距离“免人工复核”还差不少。我特别注意到PDF扫描件几乎全军覆没,因为扫描件根本没有文本层,任何基于文本提取的方案都无能为力。我后来在工具的说明文档里明确标注:扫描件需要先用OCR预处理,不在当前版本的支持范围内。承认边界,反而让工具更可靠。
与此同时,我让AI辅助生成了一个“人工复核脚本”,把提取结果导成Excel,自动标记出空值和格式可疑的值,然后我只需要检查这些标记行就行。这比从头到尾看一遍表格效率高很多。
4.2 性能瓶颈:串行读PDF确实慢
第一批测试跑完,功能上已经能用了,但性能让人着急。处理100份混合文件,耗时将近三分钟。大部分时间花在PDF上,因为一份几十页的PDF打开后要逐页解析,有的库还会重复加载同一个文件。
我用Python内置的profile工具简单看了一下,发现瓶颈在“多次调用pdfplumber.open”和“字符串逐页拼接”。AI给我的优化建议是:
- 在整个处理流程里,每个文件只打开一次。
- 批量任务改成线程池并发,PDF解析是IO密集型和部分CPU密集型混合,开4个线程是个比较稳的选择。
我按它的建议改成了这样:
from concurrent.futures import ThreadPoolExecutor def process_one_file(file_path): # 每个文件只处理一次,返回结果和日志 return extract_single(file_path) with ThreadPoolExecutor(max_workers=4) as pool: results = list(pool.map(process_one_file, file_paths))这个改动立竿见影,同样100份文件,从2分55秒降到了38秒左右。要注意的是,线程数不是越大越好。我曾经试着开8个线程,反而因为CPU资源争抢导致速度没提升多少。4个线程在我的机器上是最稳的平衡点。
4.3 从命令行到拖拽执行:提高日常使用率
工具做好后,我打算让同事也用起来。但让同事打开终端敲命令,基本不可能。我需要的是一种“双击即用”的交互方式。
于是AI辅助我给工具加了一个拖拽入口。Windows下,可以把文件或文件夹直接拖到某个脚本图标上,拖入的文件路径会通过sys.argv传给程序。核心脚本很简单:
import sys from pathlib import Path if __name__ == "__main__": for arg in sys.argv[1:]: p = Path(arg) if p.is_dir(): run_batch(p) elif p.is_file(): run_single(p)我把这个脚本打包成了一个run_extract.bat,里面加了一行切换到工作目录再调用Python。同事只要把文件拖到run_extract.bat上,松手,就会自动弹出一个结果文件夹。这个体验虽然不如精美GUI,但对内部工具来说已经足够实用。
这一步让我真正感受到:好用的工具不是“功能堆砌”,而是“在最省事的前提下把活儿干完”。
5. 把“文件提取”扩展成“一系列小工具”的思路
5.1 模块化设计:解析层、规则层、输出层
文件提取工具做顺手以后,我很快意识到它可以继续扩展成一系列小工具,而核心是把代码拆成可复用的模块。
我最终把工具分成了三层:
- 解析层:负责把PDF、Word、Excel、HTML变成结构化的文本或表格;
- 规则层:负责定义要提取哪些字段、用什么正则、如何做后处理;
- 输出层:负责生成Markdown、Excel、日志等结果。
这样拆的好处是,新工具无需重写解析层。比如我想做一个“文件去重工具”,只需要复用“读文件 + 计算哈希”的逻辑;我想做一个“格式转换工具”,也只需要把解析层的结果重新序列化成其他格式。
AI在这件事上帮了我大忙,因为它能轻松把一个复杂的“解析层”函数改造成独立模块。但我仍然需要自己定义模块之间的接口,这是AI很难替代的判断。
5.2 沉淀下来的AI辅助开发提示词模板
经过这个项目,我沉淀出了一个相对通用的AI辅助开发提示词模板,以后做任何小工具都用它:
你是一个[语言]开发助手。我要做一个[工具名]。 目标:用一句话说清楚这个工具解决什么问题。 输入:描述输入是什么,包含哪些格式。 输出:描述输出是什么,存放位置、命名规则、格式要求。 约束:列出边界,例如只读不修改、兼容中文路径、遇到异常要跳过、不做删除操作等。 示例:给出1到2个具体例子,说明理想结果和允许的容错。 请先给目录结构和关键函数,不要一次性写完所有代码。写完后解释每个函数的作用,特别说明哪些地方需要我人工确认。这个模板最关键的部分是“约束”和“示例”。没有约束,AI会自由发挥;没有示例,它很难理解“提取合同编号”这种模糊指令到底长什么样。
我还会在AI给出初步方案后追问一句:“这段代码在Windows上能直接跑吗?依赖哪个版本?”这两句能过滤掉不少坑。
5.3 我判断“哪些能让AI放手写、哪些必须自己盯”的标准
用AI辅助开发多了,我慢慢总结出一条个人经验:不是所有代码都值得让AI直接生成,也不是所有代码都需要自己逐行手写。
我现在的判断标准很简单:如果代码只处理“纯文本、纯内存、不碰外部敏感资源”,那可以让AI放手写,我负责提供清晰输入输出和测试集。比如格式转换、文本清洗、图表统计,这些都很安全。但如果代码涉及删除文件、覆盖源文件、网络请求、权限切换,那我一定会自己看完整逻辑,再加一道“确认开关”,绝不会让AI直接“一把梭”。
这次的文件提取工具,正好处在安全区:它只读文件,不删不传,顶多提取结果不准,不会造成破坏性后果。所以我敢让AI生成大量代码,也敢放心地在本地批量跑。
最后分享一个我自己很受用的小习惯:每个AI辅助开发的工具,我都会准备一个test_samples目录,里面放着不同格式的真实脱敏文件,每次改完代码就跑一遍。不一定能覆盖所有边界,但至少能拦住大部分回归问题。文件提取这种工具,做得再炫,也不如一次性能稳定更重要。