Lazy loaded image
简历格式化工具开发总结
字数 2191阅读时长 6 分钟
2026-4-19
2026-4-19
type
Post
status
Published
date
Apr 19, 2026
slug
summary
tags
低代码+自动化+B端
category
代码学习
icon
password
Property
Apr 19, 2026 09:39 AM
文章来源
项目:简历统一格式化工具(ResumeForge) 周期:2026-04-09 ~ 2026-04-19(11 天) 版本:v1.0 → v1.5 对话:6 轮,约 360 条用户消息

一、开发时间线

日期
版本
主要工作
04-09
v1.0
项目启动:需求分析、管线+插件架构搭建、端到端跑通
04-09
v1.0
姓名数据集建设(440单姓+63复姓+百家姓),代码审查修复18个问题
04-14
v1.1
Word 输出 + 指纹识别 + 数据驱动识别库、证书数据库建设
04-14
Google Design.md 设计规范研究(MCP Server 集成),输出格式确定
04-18
v1.2
Qwen Flash LLM 提取工作经历和项目经历(通用 Prompt 26条规则)
04-18
v1.3
分类提取架构(L/C/E)+ L类猎聘专用模块 + 渲染间距优化
04-18
v1.4
L类教育+证书LLM提取 + PDF渲染全面修复 + 多轮截图核查
04-19
v1.4+
L类遗留问题修复(城市提取+层级结构+内联编号拆分)
04-19
v1.5
C类+E类提取模块 + 公共后处理抽取 + 终端输出优化
04-19
v1.5
去WeasyPrint、run.py交互优化、使用说明、代码审查、全量核查

二、走过的弯路

1. 通用 Prompt 的陷阱(v1.2 → v1.3 推翻重来)

弯路:v1.2 用一套 26 条规则的通用 Prompt 处理所有格式的简历。64 份全量测试 LLM 调用成功率 100%,但端到端截图核查暴露了严重的结构性问题:
  • "项目"分类子标题被误判为独立项目经历
  • 多层级职责结构被压扁
  • 规则互相矛盾(猎聘需"标签后无内容留空",候选人自写需"保留分类标题")
教训LLM 调用成功 ≠ 提取质量合格。自动化测试只能验证"有输出",不能验证"输出正确"。必须做端到端截图核查(人眼审 PDF 输出),不能只看测试通过率。
正确做法:v1.3 改为分类提取——每种类型独立 Prompt、独立预处理、独立校验。规则数从 26 条减到 8-19 条,精准度大幅提升。

2. 正则提取教育/证书的无底洞(v1.1 → v1.4 放弃正则)

弯路:v1.1 用正则提取教育背景和证书。看似简单,实际越改越复杂:
  • 专业用 · 分隔(应用统计·硕士·统招),正则没覆盖
  • 时间可能在行尾或下一行,正则互相干扰
  • 证书子串匹配误命中正文关键词("精算师"出现在职责描述中)
  • 每修一个格式变体就引入新的 bug
教训正则适合格式固定的简单字段(姓名/电话/邮箱),不适合格式多变的复杂字段。教育和证书的格式变体太多,正则的维护成本远高于 LLM。
正确做法:v1.4 把教育和证书从 Part A(正则)移到 Part B(LLM),一个 Prompt 搞定所有变体。

3. WeasyPrint 的假兜底

弯路:技术方案里写"WeasyPrint 主力 + Playwright 兜底",但 WeasyPrint 在 Windows 上因为缺 GTK 库每次都失败,然后自动切到 Playwright。用户每次看到一大段红色警告信息,以为出了问题。
教训不要保留一个永远失败的"主力"方案。如果兜底方案才是实际跑的,就直接用兜底方案,不要浪费时间在注定失败的路径上报错。
正确做法:直接去掉 WeasyPrint,只用 Playwright。从 requirements.txt 也删除。

4. 城市提取修了两次

弯路:b04 冯无年龄信息时城市为空。第一次修复加了"从求职意向行提取"策略,但只对原始文本有效——clean_text() 去掉表格 | 后,求职意向行的格式变了,新策略匹配不上。用户反馈后才发现第二次修。
教训修 bug 后要同时测原始文本和 clean_text 后的文本。提取函数的输入是 clean_text 处理后的,不能只用原始文本验证。

5. 层级结构的三次迭代

弯路:duties 的 ## 层级前缀处理经历了三次迭代:
  1. 第一版:直接补 ##,但 LLM 有时不返回 ##
  1. 第二版:后处理自动识别标题并补 ##,但把"业绩:1.xxx"也拆成了独立标题,破坏了已有层级
  1. 第三版:加 has_pure_heading 检查——有纯标题时不做"标签:内容"拆分
教训后处理规则需要考虑上下文,不能孤立地处理每个条目。一个 duty 是标题还是子项,取决于同组其他 duties 的结构。

6. E 类纯英文简历的 validate 卡住

弯路:E 类纯英文简历(Wu Jun CV)处理成功但 validate 返回 False——因为 extract_name() 只支持中文名,纯英文简历提取不到姓名。pipeline 误判为"解析失败"。
教训validate 的通过条件要考虑不同类型的特殊性。英文简历没有中文名是正常的,不应该因此判定失败。
正确做法:validate 接受 source_type 参数,english 类型允许 name 为空。同时在 E 类 Prompt 中加 name 提取,LLM 能提取到就回填。

三、有效的做法

1. 截图核查驱动开发

整个项目最有价值的质量保证手段不是自动化测试,而是用户截图反馈。几乎所有重要的 bug 都是通过截图发现的:
  • 证书挤在同一行(缺 Markdown hard break)
  • "业绩"错误变成独立标题
  • 项目经历空标签残留
  • 终端输出太乱
经验:自动化测试验证"不崩溃",截图核查验证"看起来对"。两者缺一不可。

2. 分类提取架构

最重要的架构决策:输入端分类,处理端差异化,输出端统一
  • 每种类型独立文件(extract_liepin.py / extract_candidate.py / extract_english.py)
  • 改一个类型不影响其他类型
  • 公共逻辑抽到 post_process.py
  • 所有类型输出统一的 ResumeData dict,渲染器不需要知道来源

3. 后处理管线弥补 LLM 不稳定

LLM 输出不确定——同一份简历跑两次,## 前缀可能有可能没有,编号可能拆可能不拆。后处理管线(normalize_duty_headings + split_inline_numbered)在校验层统一规范化,大幅降低了对 LLM 输出稳定性的依赖。
经验:不要假设 LLM 输出格式稳定,永远在校验层做规范化。

4. 先 L 后 C 后 E 的开发顺序

  • L 类数量最多(34份)、格式最固定 → 先做,建立架构模式
  • C 类问题最复杂(结构感知)→ 有了 L 类的模式后照着做
  • E 类数量最少(3份)→ 最后做
每做完一类就做全量测试 + 截图核查,再进入下一类。避免一次性铺开三类然后调不过来。

5. 开发文档保持同步

技术方案、架构流程图、CLAUDE.md 在每次重要改动后同步更新。新开会话时能快速了解项目状态,不用从头看代码。

四、关键数据

指标
数值
总代码量
~5000 行 Python
核心脚本
20 个 .py 文件
支持格式
PDF / Word / TXT / HTML
简历类型
L类猎聘 + C类自写 + E类英文
全量测试
60/64 成功(94%)
事实性核查
59/60 准确(98.3%)
唯一幻觉
LLM 把脱敏的"111"还原为"阿里"(1/60)
Git 提交
27 次
LLM Prompt
L类15条 + C类19条 + E类15条

五、未来优化方向

  1. 渲染器拆分renderer.py 拆为 render_md.py + render_pdf.py(v1.6)
  1. E 类英文名提取:Part A 加英文名正则,或完全依赖 LLM
  1. LLM 幻觉防护:对公司名做原文回溯校验,发现不匹配时回退原文
  1. 用户反馈驱动:让用户试用后根据实际反馈优化,比在这里空改效率高
上一篇
关于躺平的回答备份
下一篇
最近思考的几条原则

评论
Loading...