【锦图简历】程序对简历扫描件的识别流程
在做简历上传功能时我低估了一个场景扫描件 PDF——用户从 scanner 或微信保存的 PDF肉眼看正常文本层却是空的。此时pdf-parse一类库几乎抽不出字用户却认为「我上传没问题」。下面是我们在线上用的分层提取 OCR 回退思路供同类文档上传场景参考。1. 先判断有没有可提取的文本层流程第一步不是 OCR而是尽量便宜地拿文本优先Poppler pdftotext结构化参数行阈值、单元格分隔回退pdf-parse多种提取模式统计「有效字符数」去空白后低于阈值如 80 字→ 判定为扫描件这样能避免对可复制 PDF 做昂贵的 OCR。2. 扫描件渲染 → OCR → 规整判定为扫描件后PDF Buffer → pdftoppm / pdf-to-img按页渲染DPI 建议 300 左右 → 图像预处理灰度、对比度、锐化、小图放大 → Tesseractchi_simengPSM 3/11/6 择优 → 文本后处理去乱码行、拆粘连章节 → 简历规整章节换行、列表符、经历行 → 规则诊断 / AI 分析PSM 说明简版3全自动分页适合整页简历11稀疏文本适合 bullet 列表6单块文本部分模板反而更好可对同一页跑多种 PSM用质量评分有效字符比 章节词命中选最优而不是写死一种。3. 工程踩坑真实遇到过坑现象处理Docker 缺语言包OCR 全乱码镜像预装chi_sim、eng多页 OCR 超时用户以为卡死限最大页数 流式进度Nginx 60s 断连上传到一半失败调proxy_read_timeoutOCR 阶段发 keepalive双栏 Word 模板左栏技能与右栏经历串行宽图分列 OCR 后处理去噪另文详述DPI 过低小字号中文漏字150 → 300小图再放大4. 进度与体验OCR 单页可能 2040 秒整份 90 秒不罕见。不要只给一个 spinner。我们采用NDJSON 流式响应步骤例如extract → ocr → normalize → done每步推送进度百分比与人话文案「正在识别扫描版文字…」。OCR 阶段长时间无业务输出时额外发心跳行避免代理认为连接空闲而断开。5. 代码结构示意不必照搬关键是阶段可观测// 伪代码上传解析入口asyncfunctionparseResumeDocument(buffer,fileName){onStep(extract);consttextLayerawaittryExtractText(buffer);if(hasEnoughText(textLayer)){onStep(normalize);returnformat(textLayer);}onStep(ocr);constocrTextawaitocrPdfPages(buffer);// poppler render tesseractonStep(normalize);returnformat(cleanupOcr(ocrText));}6. 结论扫描 PDF 在中文求职场景里不是边缘 case是常态之一后端要能自动回退 OCR并在 UI 上让用户等得明白OCR 之后还要规整 人工校对尤其双栏模板我们在产品锦图简历里按上述链路实现简历上传Word / 可复制 PDF / 扫描 PDF / 图片。若你也在做文档类 ToC 工具欢迎评论区交流 Poppler 与 Tesseract 在容器里的打包方式。