☰
简历拷问机—— 上传简历,先让面试官问一遍
2026/10/28 10:09:41 网站建设 项目流程

简历拷问机 —— 上传简历,先让面试官问一遍

赛道一 · TextIn xParse 实用价值赛道 作品形态:可运行的单页 Web 应用(本地 + 线上),配文档与演示样例


一、作品介绍

1.1 名称与一句话定位

简历拷问机:上传一份简历(PDF 或照片),系统把简历拆成段落、时间线和技能清单,再按 HR 的追问习惯生成 10 条左右的问题,每条都写清楚面试官在确认什么、你该怎么答。

它不点评简历、不打分排名、不给录用建议。它只做一件很具体的事:把简历上的模糊点,提前翻译成面试现场会被问出来的问题。

分享链接:简历拷问机 · 先替面试官问一遍

1.2 应用场景

场景一:求职者投递前的最后一遍自查。小周要投一个产品经理岗位,简历改到第五版,自己已经看不出问题了。他把 PDF 拖进页面,看到第一条问题:「简历里写『DAU 提升 30%』,这个 30% 的基数是多少、怎么算出来的?」他愣了一下——这确实是他答不上来的地方。面试前一天,他把 4 条标红的问题各写了一段回答。

场景二:转行者的履历断层。做了三年运营想转产品,简历上 2022 年 12 月到 2023 年 8 月是空的。系统把这段在时间轴上画成斜纹块,并生成对应问题。他提前准备了「这段时间在补什么、做了什么项目」的两句话,而不是在被问到时说「休息了一下」。

场景三:帮别人看简历。学长帮学弟看简历,说「感觉写得有点虚」但说不出具体哪虚。跑一遍之后,两个人对着清单逐条讨论,比空泛的「再具体一点」有用得多。

1.3 要解决的问题

现象后果
简历上写「明显提升」「大幅优化」,没有一个数字面试官会当场要求给数,答不上来会影响整份简历的可信度
写了「精通 Python」,正文里没有一个 Python 项目技能项是最容易被当场验证的,一验就穿
两段工作之间有空窗,简历上不解释空窗期是必问题,主动解释是加分,被问出来是减分
「参与」「协助」通篇,说不清自己独立做了什么岗位级别会被判低一档
「担任项目负责人,带领团队」,但没有人数和分工面试官会怀疑是挂名
自我评价全是形容词,没有一个例子这一段在多数面试官眼里等于没写

这些问题的共同点是:当事人自己看不出来。因为写简历的人知道上下文,而看简历的人只有字面。这个工具补的就是这个视角差。

1.4 核心功能

  1. 上传 PDF / JPG / PNG / BMP / TIFF / WEBP,单文件 40MB 以内

  2. 上传前做文件头嗅探,HEIC、Office 文档、压缩包、视频当场拦下并给出转格式的具体步骤

  3. 自动识别简历段落:基本信息、教育背景、工作经历、项目经历、技能、自我评价

  4. 还原时间线,把教育、工作、项目画成横向条形,相邻工作之间超过 5 个月的空窗画成斜纹块

  5. 解析技能自评等级(精通 / 熟练 / 熟悉 / 了解),并检查正文里是否有对应经历佐证

  6. 按 11 类规则生成追问清单,按危险等级排序(必被追问 / 可能追问 / 加分问题)

  7. 每条问题附「他在确认什么」与「你该怎么答」两段建议

  8. 拷问指数(0-100)与一句话判词,给整体印象

  9. 导出 PDF(真生成文件)、导出 CSV、一键复制全部问题

  10. 页面内保留引擎识别的原文,用户可自行核对

主界面效果:

1.5 创新点

① 把「简历优化」换成「追问预测」。市面上的简历工具几乎都在做润色和评分,而评分是主观的。追问是客观的:HR 对「DAU 提升 30%」一定会问基数和口径,对「精通」一定会要例子。把目标从「写得更好看」换成「这些问题你答不答得上来」,输出就变成了可执行的动作。

② 每条问题都带证据链。问题不是模板填空,而是从简历原文里定位出来的。页面同时展示原文引用、判定原因、回答框架三层,用户能判断这条是真问题还是误判。

③ 技能项与正文做交叉验证。技能栏写「精通 SQL」,正文里如果只出现过 Excel,系统会标出「正文无对应经历」。这个交叉检查用到了文档的段落结构——只有拿到分段的文本才能做,纯字符串搜索做不到。

④ 空窗期可视化。把时间轴画出来,空窗会自动变成斜纹段。数字本身(8 个月)比一句「注意时间连贯性」直观得多。

⑤ 零依赖架构,开箱即用。后端只用 Node 内置http,前端单文件原生 JS,无框架无构建,凭据内置在lib/credentials.js,不需要任何环境变量就能跑。已在云端部署成在线服务,评委打开链接即可上传简历,本地什么环境都不用装。


二、技术方案

2.1 工具与选型

工具用途为什么选它 / 不选什么
TextIn xParse文档结构化解析能给出elements[]的元素类型(Title / NarrativeText)和坐标,纯 OCR 只给一堆无序文本
Node.js 内置http后端服务零依赖、单文件可跑;不选 Express,因为多一层node_modules就多一层部署失败的可能
原生 JS + 单 HTML前端单文件可直接托管;不选 Vue/React,因为构建产物会让「打开就能用」这件事变复杂
html2canvas + jsPDF真 PDF 导出不依赖浏览器打印,排版可控;不用window.print(),因为打印出来带页眉页脚和 URL
puppeteer-core端到端验证用本机 Chrome 跑真实点击,验证渲染、导出、下载全链路

2.2 开发方式

对话式开发 + 自动化验证。每一轮改动后都跑同一套检查:

  1. 端到端渲染:无头浏览器打开?demo=1,等待结果区出现,收集pageerror与console.error

  2. 导出验证:真实点击「导出 PDF / 导出 CSV」,检查下载目录里是否出现文件并核对体积(PDF 1.2MB、CSV 5KB)

  3. 布局验证:读取元素getBoundingClientRect(),确认时间轴条形的位置与宽度符合按月份计算的结果

  4. 移动端验证:390×844 视口重跑一遍

这套检查能挡住三类最常见的问题:页面白屏、导出无反应、移动端错位。

2.3 xParse 接入

认证走 AppKey,凭据硬编码在lib/credentials.js里,服务启动即生效,不需要配置环境变量或登录授权:

// lib/credentials.js —— 随应用一起发布,服务启动即读到 module.exports = { appId: '1eb9ffc1********************c9479c', secretCode: 'c9ee19bd********************b36f72bf', };

(以上为脱敏展示,完整值随代码交付。)

服务端按「有 AppKey 走 AppKey,没有才回退本机 profile」的顺序拼参数,因此同一份代码既能云端跑、也能本机跑:

# 主路径:AppKey(当前部署即此路径) xparse-cli --app-id <ID> --secret-code <SECRET> parse <文件> --api auto --view json --output <目录> ​ # 兜底:AppKey 缺失时回退到本机已授权 profile xparse-cli --profile workbuddy parse <文件> --api auto --view json --output <目录>

api/health会直接回报当前认证链路,线上实测返回"auth": "appkey",说明走的是 AppKey 而不是本机授权——这是它能被独立部署的关键。

参数选择的理由:

  • --view json:本项目要的是elements[].type与段落顺序,markdown 会把标题层级和段落边界拍平,时间线和段落归类会失真

  • --api auto:自动选择可用通道,不硬编码付费/免费

  • --task-context:向引擎声明「解析简历并定位可追问的模糊点」,需要按阅读顺序拿到每一段文字及坐标

  • --output <目录>:必须指定,否则 JSON 与日志混在 stdout 里,难以分离

实际调用记录(本地命令回放,21 个元素,387 字):

2.4 系统结构

四层职责清晰分离:

apps/resume/index.html 单文件前端:上传、进度、结果渲染、导出、下载投递 server.js HTTP 服务:格式嗅探、错误翻译、调用 CLI、临时文件清理 lib/resume/extract.js 抽取与判定:段落归类、时间解析、11 类追问规则、技能交叉验证 lib/resume/credentials.js 凭据:AppKey 硬编码内置,缺失时回退 profile

关键设计:服务端不返回引擎原始 JSON。用户看到的是「这份文件读不出来,可能是三种原因,建议这样处理」,原始错误只进服务端日志。

2.5 交付形态与访问方式

三个入口,同一套代码:

形态地址说明
在线服务(推荐)https://resume-griller.app.workbuddy.host/resume/完整功能:上传任意文件、真实调用 xParse、导出 PDF/CSV
作品总入口https://resume-griller.app.workbuddy.host/两个参赛作品的导航页
资料库分享页见提交说明单文件版,把html2canvas/jsPDF与全部逻辑内联,双击即可打开,也发得出链接

两个作品合并为一个服务、按路径分流(/resume/、/corpspeak/),而不是各起一个进程。原因很实际:一台实例只能绑一个端口,拆成两个服务要么买两台机器,要么在网关层再加一层反代;合成一个服务后,两份作品共用同一套格式嗅探、错误翻译和临时文件回收逻辑,也共享同一份凭据配置。

前端页面里API_BASE是按路径自动推导的:

var API_BASE = (function () { var p = location.pathname; if (/^\/corpspeak(\/|$)/.test(p)) return '/corpspeak'; if (/^\/resume(\/|$)/.test(p)) return '/resume'; return ''; })();

同一份 HTML 放在根路径、子路径、或资料库单文件页里都能找到正确的接口前缀,不用改代码。


三、处理流程

3.1 输入类型与预处理

格式支持预处理动作不支持时怎么提示
PDF✅直接解析加密 PDF 提示先取消密码另存
JPG / PNG / BMP / TIFF / WEBP✅直接解析模糊或裁切时提示重拍
HEIC(iPhone 默认)❌文件头识别后拦截给出「相册 → 分享 → 拷贝为 JPG」的具体路径
DOCX / XLSX / ZIP❌文件头识别后拦截提示另存为 PDF 或拍照上传
MP4 / MOV❌文件头识别后拦截提示上传的是媒体文件不是图片

扩展名不可信:把 HEIC 改名成.jpg再上传是很常见的操作,所以必须读文件头判断。只拦确定解析不了的格式,unknown一律放行交给引擎兜底,避免误伤合法变体。

3.2 端到端流程

  1. 用户选择或拖入文件,前端做扩展名与体积预检

  2. 服务端读文件头嗅探真实格式,命中黑名单直接返回可操作提示(不消耗额度)

  3. 文件落到临时目录,调用xparse-cli parse --view json

  4. 解析成功后读取data.elements[],按页与纵坐标恢复阅读顺序

  5. 清洗文本:去掉 markdown 星号、项目符号、把全角数字与%归一化

  6. 段落归类:用 8 组段落关键词把 21 个元素分进 6 个桶

  7. 时间解析:从「2023.08-至今」这类字符串里取出起止年月,算出月数

  8. 规则判定:11 类追问规则逐条跑,命中即生成问题与证据

  9. 去重与限流:同一句话只保留一个问题(等级高者胜),同类追问限 1-2 条

  10. 排序输出:按危险等级排序,生成拷问指数与判词

  11. 返回报告,临时文件中的非 JSON 文件立即删除

3.3 关键指令与参数

指令参数作用为什么这么传
parse--view json输出结构化 JSON需要elements[].type区分标题与正文
parse--api auto自动选择通道不可用时自动回退,不硬编码
parse--output <dir>指定输出目录便于分离 JSON 与日志
parse--task-context声明解析意图提升对字段的针对性,用完立即删除(权限 0600)
quota--output json查询额度控制台只显示单个套餐包,不够准

3.4 核心处理逻辑

① 阅读顺序还原。引擎输出的元素顺序基本可用,但不保证。按page_number分组,组内按coordinates[0][1](纵坐标)排序,避免双栏或浮动元素打乱顺序。

out.sort((a, b) => { if (a.page !== b.page) return a.page - b.page; const ay = a.coord && a.coord[0] ? a.coord[0][1] : 0; const by = b.coord && b.coord[0] ? b.coord[0][1] : 0; return ay - by; });

② 段落归类。用 8 组正则匹配段标题(「教育背景 / 教育经历 / 学历背景」都归到edu),匹配到就切换当前桶,后续行归入该桶。段标题要求长度 ≤ 8 字且整行匹配,避免「教育背景:某大学」这种行被误判成标题。

③ 时间解析(正则分组设计)。中文简历的时间写法很杂:2023.08-至今、2018.09-2022.06、2021年9月—2022年5月。第一个版本的正则把结束年份写成了非捕获组,导致「2023.08-至今」被解析成「NaN.01 - 2023.08」。修正后把起止年月和「至今」分成两组:

const PERIOD_RE = new RegExp( '((?:19|20)\\d{2})\\s*[年.\\-/]?\\s*(\\d{1,2})?\\s*月?\\s*[-–—~~至到]+\\s*' + '(?:((?:19|20)\\d{2})\\s*[年.\\-/]?\\s*(\\d{1,2})?\\s*月?|(至今|现在|目前|present))', 'i');

月份统一换算成「年×12+月」的绝对月数,空窗、任职时长都基于这个数计算。

④ 技能交叉验证(带词边界)。技能词表里既有中文(数据分析)也有 ASCII(SQL、R、C++)。最初直接用includes判断,结果「R」命中了「Axure」里的r,「PR」命中了「Office」里的…。修正为:ASCII 词加前后非字母数字边界。

function hitSkill(text, name) { if (/^[A-Za-z0-9+#.]+$/.test(name)) { const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return new RegExp('(^|[^A-Za-z0-9+#])' + esc + '([^A-Za-z0-9+#]|$)', 'i').test(text); } return text.includes(name); }

技能等级按小句判定再向后继承:精通 Python、SQL、Axure切开后只有第一个小句带「精通」,需要把等级往后传,否则 SQL 和 Axure 会变成无等级。

⑤ 追问规则的优先级与限流。同一句话可能同时命中多条规则。比如「担任项目负责人,带领团队完成平台从 0 到 1 上线」,既是「成果缺失」也是「管理幅度」。规则按信息量排优先级(管理幅度 6 > 成果缺失 3),同分位时高优先级覆盖。之后再对同类追问限流(技能验证最多 2 条、成果缺失最多 2 条),避免一份简历刷出 6 个「没有量化」把别的角度全挤掉。

⑥ 拷问指数。28 + 必被追问×8 + 可能追问×3 + 加分问题×2,上限 94。有 5 条必被追问时得 85 分,判词是「这份简历很敢写」。分数只用于给出第一印象,不参与任何筛选。


四、解决效果

4.1 实测数据

样张类型元素数识别字数追问点必被追问拷问指数耗时
产品经理简历(1 页 PDF)印刷体21387115853.2s(首次冷启动)/ 1.2s(复跑)

样例简历的 11 条问题分布:

类型条数典型问题
数字口径1「DAU 提升 30%」的基数是多少、怎么算出来的?
定性表述1「转化率有明显提升」里的「明显」,具体是多少?
技能验证2技能栏写「精通 Python」,正文里没有对应项目
时间断层12022.12 到 2023.08 之间空了 8 个月
角色边界1「参与中台建设项目」里,哪一部分是你独立完成的?
管理幅度1「担任项目负责人,带领团队」——团队多少人?
成果缺失2「负责日常数据报表整理」——最后做成了什么?
稳定性1「某电子商务有限公司」只待了 5 个月,为什么离开?
空话1「学习能力强」有没有一个具体例子?

时间轴把空窗直接画了出来(第二行入职前的斜纹段):

4.2 前后对比

维度用 xParse 之前用之后变化
判断依据靠人眼通读、凭感觉说「有点虚」按 11 类规则逐条定位从主观到可复核
段落归类手工摘抄自动分出 6 个段落桶—
时间线自己在纸上画按月份还原,空窗自动标出8 个月空窗一眼看见
技能核对要来回翻两遍技能栏与正文自动交叉比对5 项技能 4 项无佐证
单份简历耗时通读 + 记录 10~15 分钟3.2 秒—
结果留存截图 / 备忘录PDF + CSV + 一键复制可直接带去面试

4.3 细节改进

  • 段标题误判:教育背景:华东理工大学这类行原本会被当成段标题,加上「长度 ≤ 8 字」的限制后不再误判

  • 时间解析:修掉捕获组错位后,2023.08-至今从NaN变成正确区间,时间线才画得出来

  • 技能误命中:加了 ASCII 词边界后,R不再命中Axure,这条例误报被彻底消除

  • 问题重复:加同句去重 + 同类限流后,「成果缺失」这一类从 6 条收紧到 2 条,腾出的位置给了时间断层、稳定性等不同角度,清单的可读性明显提升

  • 页面过长:第一版把每条问题的「他在确认什么 / 你该怎么答 / 原文」全部展开,11 条问题从头铺到尾,首屏只放得下一条。改成<details>折叠、答案默认收起、时间轴与技能并成两栏、技能从卡片压成 chips 后,整页在 1180px 宽度下实测 1727px,首屏就能看到拷问指数和三条最该准备的问题

4.4 体验改善

用户视角的三点变化:

  1. 问题变成可执行的清单:不是「建议补充量化成果」,而是「这句话缺基数,准备『从 X 到 Y』的绝对值」

  2. 知道自己被问在哪:时间轴把空窗画成斜纹,视觉上比文字提示更有压迫感

  3. 能核对:每条问题下面都挂着原文引用,误判一眼能看出来,不会盲信

4.5 边界与局限

  • 只做结构判断,不做能力评价:不判断这个人是否适合某个岗位,也不给录用建议

  • 手写简历识别率下降:印刷体 PDF 效果最好,手机拍的手写简历段落边界容易断

  • 图表化简历:用图标和进度条表达技能的简历,文本层信息很少,能抽到的内容有限

  • 规则是保守的:宁可漏判,也不产出无依据的问题。因此写得已经很扎实的简历可能只出 3-5 条

  • 追问建议是通用框架:不同行业、不同岗位的追问重点不同,目前是通用版本,没有做行业分支


五、结语

这个作品的出发点很小:简历上每一个模糊的地方,面试里都会变成一个必答题。

TextIn xParse 给的是结构——段落、标题、坐标、阅读顺序。有了结构才谈得上「找模糊点」,因为模糊不是关键词,而是「有动作没结果」「有数字没口径」「有技能没项目」这类关系。纯文本搜索做不了这个判断,这也是整个作品选择--view json而不是 markdown 的根本原因。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询