九月份又到了简历投递的高峰期,很多人卡住的地方不是“没东西可写”,而是“写出来的东西不好看”。用 Word 调一个两栏排版,改个行距整页就塌了;去模板站下载一份,导出时水印和付费弹窗先来一遍;好不容易排好版,换个岗位改两句描述,格式又全乱。于是最近“用豆包做简历”这类内容开始被大量搜索,逻辑很简单:把排版这件事交给 AI,人只管把经历讲清楚。
但这里要先给一个明确判断:所谓“一分钟做出好看的个人简历”,一分钟能拿到的是一份可用的草稿和一版能渲染的版式代码,不是一份能直接投递的终稿。真正决定简历质量的,仍然是你的经历密度、成果量化和岗位匹配度。豆包这类 AI 助手真正降低的成本有两块:一是把散落的经历整理成结构化文本,省掉“不知道怎么写”的启动成本;二是把排版从“拖拽图形界面”变成“生成 HTML/CSS 代码”,省掉反复对齐的成本。
这篇文章会把整条链路拆开讲:怎么用结构化提示词让豆包产出可信的简历素材,怎么让它输出一份单页 A4、可直接打印的 HTML 模板,怎么在本地渲染成 PDF,怎么检查 ATS(简历筛选系统)友好度,以及过程中最容易踩的坑在哪里。读完你应该能做到:不依赖任何付费模板站,用一份可版本管理的 HTML 文件,维护自己所有版本的简历。
1. 先界定边界:豆包做简历能做什么、不能做什么
很多人第一次尝试的姿势是打开对话框输入“帮我写一份简历”,然后得到一份充满“具备良好的沟通能力和团队协作精神”的模板文本,看完就放弃了。问题不在模型,在于提问方式把 AI 当成了“代笔”,而它更擅长的是“结构化和改写”。所以第一步必须把能力边界划清楚,否则后面所有操作都是白费。
1.1 它能稳定交付的三件事
第一件是信息结构化。你把自己过去两三年的项目经历、职责、技术栈,用口语化的方式一股脑丢进去,让它整理成“公司—时间—角色—项目—动作—结果”的固定结构。这件事人做起来很烦,因为要反复回忆和归类,AI 做起来很快,而且不会漏掉你提到的细节。
第二件是表达改写与量化提示。你写“负责订单系统的开发”,它可以改写成“负责订单履约链路开发,覆盖下单、拆单、发货三个环节”。注意,它只能改写你已经提供的事实,如果原文里没有数字,它给你的数字大概率是编的,这一点后面会专门讲怎么防。
第三件是版式代码生成。这是被严重低估的能力。你让它输出 HTML + CSS,它给你的是一份文本文件,可以放进 Git 仓库,可以复制出十个岗位定制版本,可以用浏览器打印成矢量 PDF。相比在 Word 里按着 Alt 键微调表格边框,这种方式的稳定性和可复用性完全不是一个量级。
1.2 它不能替你做、也不该让它做的事
不能替你编造经历。这是最需要警惕的一条。AI 在被要求“让简历更亮眼”时,很容易生成看起来合理但你并未做过的内容,比如把“参与”写成“主导”,把“了解”写成“精通”,甚至补上一个你没做过的性能优化数字。带着这种简历去面试,技术追问两轮就会露馅,风险远大于收益。
不能替你判断岗位匹配度。它不知道你投的这家公司到底要什么,只能根据你贴进去的 JD 做表层关键词对齐。真正的匹配判断,需要你自己把 JD 拆成“必须项、加分项、无关项”,再回头检查简历里有没有对应证据。
不能保证一次输出的代码就能直接用。模型单次输出有长度限制,复杂的 HTML/CSS 很容易在中途被截断,或者代码块没有闭合。这一点在实操章节会给出具体的应对方法:分文件生成、先生成结构再生成样式。
1.3 时间成本的真实预期
把“一分钟”拆开看会更诚实:让豆包产出一版简历骨架,大概 1 分钟;把你自己的真实经历填进去并核对真实性,20 到 40 分钟;调整版式、修掉渲染问题、导出并检查 PDF,10 到 20 分钟。第一次做总共 40 到 60 分钟,之后每维护一个岗位定制版本,大概 10 分钟。所以正确的期待是:第一次投入一小时,换来一套可长期复用的简历工程。
2. 简历的本质是信息结构,不是排版
在动手之前,有必要把一件事说透:为什么很多人排了半天版,简历还是不好看。原因通常不是配色和字体,而是信息层级混乱——招聘方扫一眼,三秒内找不到“你是谁、做过什么、和这个岗位有什么关系”,再精美的版式也救不了。
2.1 招聘方的真实阅读路径
技术岗简历的实际阅读过程,通常是这样的顺序:先看当前公司和年限,再看技术栈关键词是否命中,然后扫最近一段项目的职责和成果,最后才看教育背景和其他。整个过程可能只有二十秒。这意味着你的版式必须服务于这个路径:最重要的信息放在左上到中部的视觉热区,最近的工作经历放在最上面,技术栈要让关键词自然出现在前几行里。
如果按照这个标准回头看很多模板,会发现它们的问题很明显:左侧一栏占了三分之一宽度,放的却是“兴趣爱好、自我评价、软件技能进度条”这类低价值信息,而真正的项目经历被挤在右侧窄栏里,字号小到需要凑近看。这类模板在视觉上很“设计感”,在信息传递上是负分。
2.2 单栏还是双栏,打印还是投递
这里给一个明确建议:默认用单栏。原因是大多数中大型公司的简历初筛会经过解析系统,双栏布局、文本框、表格嵌套都容易导致解析顺序错乱,把“项目经历”解析到“教育背景”后面去,或者干脆丢字段。单栏、语义化标签、从上到下的自然文档流,是兼容性最好的方案。
只有在两种情况下可以考虑双栏:一是你投递的是设计、市场这类允许附作品集链接的岗位,且确定走人工筛选;二是你有明确的线下宣讲会场景,需要打印纸质版。即便如此,也建议把双栏做成“左窄右宽”的辅助信息栏,主内容仍然占主体宽度。
2.3 一份可直接复用的信息结构
不管后面用什么工具渲染,简历的信息骨架基本是固定的下面这几块。把这套结构先想清楚,再让 AI 填充,效率会高很多。
| 模块 | 必备内容 | 常见错误 | 建议篇幅占比 |
|---|---|---|---|
| 基本信息 | 姓名、岗位意向、手机、邮箱、城市 | 写身份证号、家庭住址、婚育状况 | 5% |
| 技术栈 | 语言、框架、中间件、工具 | 用进度条表示熟练度 | 10% |
| 工作经历 | 公司、时间、职位、职责概述 | 只写职责不写结果 | 15% |
| 项目经历 | 项目名、角色、技术方案、个人贡献、量化结果 | 把团队成果写成个人成果 | 45% |
| 教育背景 | 学校、专业、学历、时间 | 课程列表堆两行 | 10% |
| 其他 | 证书、开源、博客、竞赛 | 写“精通 Office”这类无效信息 | 15% |
这张表的价值在于,它把“简历怎么写”这个模糊问题,变成了“每个模块填什么、不能填什么”的具体任务。后面给豆包的提示词,本质就是让它在填这张表。
3. 环境准备与工具选型
这个流程对环境的依赖其实很低,但把工具选对能省掉后面大量返工。下面这套是通用推荐,具体版本以你本机实际情况为准,不必强行对齐。
3.1 你需要准备的东西
- 一个豆包账号:网页端即可,桌面客户端也可以。长文对话和代码生成建议在桌面端或网页端做,屏幕大、方便复制代码块。
- 一个现代浏览器:Chrome 或 Edge 都行。浏览器在这里承担两个职责:预览 HTML 效果、通过打印功能导出 PDF。
- 一个文本编辑器:VS Code 即可,用来保存和编辑生成的 HTML/CSS 文件。
- 可选的 Python 环境:如果你希望用脚本批量导出 PDF,需要 Python 3.9 以上版本和 pip。不装也完全不影响主流程,用浏览器的“打印为 PDF”就够了。
- 可选的 Git:如果你要维护多个岗位版本的简历,强烈建议用 Git 管理,每次投递打一个 tag。
3.2 为什么不用 Word 或在线模板站
简单对比一下三条路径的差异,你就明白为什么值得换一种方式:
| 方案 | 修改成本 | 多版本管理 | 导出质量 | 解析兼容性 |
|---|---|---|---|---|
| Word 手动排版 | 高,改内容常引起排版塌陷 | 差,靠另存为文件名区分 | 中,容易分页错位 | 中 |
| 在线模板站 | 中,受模板限制 | 差,多数需付费解锁 | 中,可能带水印 | 低到中 |
| HTML + CSS 渲染 | 低,改文本不影响样式 | 好,可用 Git 分支管理 | 高,矢量 PDF | 高,语义化标签友好 |
HTML 方案真正的优势在于内容与样式分离。你的经历是文本,版式是 CSS,改经历不用碰样式,换样式不用动经历。这在需要针对不同岗位做七八个版本时,价值非常明显。
3.3 一个容易被忽略的前置动作
在打开豆包之前,先花五分钟做一件事:把你所有的工作经历、项目、技术点,用最粗糙的方式列在一个文本文件里,不需要润色,不需要通顺,想到什么写什么。包括你觉得“太琐碎不值得写”的内容,比如某次线上问题排查、某个内部工具的搭建、某次代码评审推动的规范落地。
这一步的意义在于:AI 只能在你的输入基础上做加工,输入越干瘪,输出越空洞。很多人抱怨 AI 写的简历千篇一律,其实是自己给的信息量太少,模型只能拿通用话术填补空白。有了这份原始素材,后面的提示词才有东西可加工。
4. 第一步:用结构化提示词把经历变成简历素材库
现在进入实操。第一个环节不是生成版式,而是把原始素材加工成结构化的简历数据。这一步做扎实,后面改版式就是纯粹的体力活。
4.1 提示词的基本结构
给豆包写简历提示词,建议遵循“角色 + 输入 + 约束 + 输出格式”四段式。不要写成一句“帮我写简历”,也不要写成几千字的长篇大论。下面这份模板可以直接复制修改使用,注意把方括号里的内容替换成你自己的信息。
你是一位有十年经验的技术招聘官,熟悉互联网后端岗位的简历筛选标准。 【我的原始素材】 [把第 3.3 步整理的粗糙素材整段粘贴进来,包含公司、时间、项目、技术栈、职责、你记得的结果] 【目标岗位】 岗位名称:[后端开发工程师] 岗位描述:[粘贴 JD 原文,如果没有就写目标公司的技术方向] 【硬性约束】 1. 只能使用我素材中出现的事实,禁止补充任何我没有提到的项目、技术、数字。 2. 如果某个成果缺少量化数据,用【待补充:xxx】占位,不要自己编造。 3. 每段经历用 STAR 结构表达,但不要写出"情境/任务/行动/结果"这些标签。 4. 每条描述控制在 40 字以内,动词开头,避免"负责""参与"这类模糊词。 5. 技术栈只保留我素材中真实出现过的,按"语言 / 框架 / 存储 / 中间件 / 工具"分组。 【输出格式】 请输出 JSON,结构如下: { "basic": {"name": "", "job_intent": "", "phone": "", "email": "", "city": ""}, "skills": {"language": [], "framework": [], "storage": [], "middleware": [], "tool": []}, "work": [{"company": "", "period": "", "title": "", "points": []}], "projects": [{"name": "", "role": "", "stack": [], "points": []}], "education": [{"school": "", "major": "", "degree": "", "period": ""}], "others": [] } 只输出 JSON,不要解释,不要加 Markdown 代码块以外的内容。这份提示词里有几个关键设计,值得单独说明。“禁止补充事实”是最重要的一条,它把模型的发挥空间限制在你的真实经历内。【待补充:xxx】占位机制则是把“量化数据”这件事显式地还给你,模型不编数,你看到占位符就知道哪条描述还缺结果,比让模型猜一个“提升 30%”要安全得多。输出 JSON是为了后续可以程序化处理,比如用脚本渲染成 HTML。
4.2 拿到 JSON 之后必须做的三件事
模型返回 JSON 后,不要急着进入下一步。先做三件事,这三件事决定了这份简历能不能用。
第一,逐条核对真实性。把每一条描述读一遍,问自己“如果面试官追问这条的细节,我能答上来吗”。答不上来的,要么删掉,要么改成你确实做过的版本。这里不要心软,一条经不起追问的描述,可能让整场面试的信任度归零。
第二,补齐所有占位符。把所有【待补充】替换成真实数据。如果没有精确数字,可以用范围或者相对值表达,比如“将接口平均响应时间从秒级降到百毫秒级”“覆盖日均十万级调用”,前提是这些描述你能解释清楚怎么测的。
第三,删掉低价值条目。模型整理时往往会保留一些“看起来相关但没什么信息量”的内容,比如“参与需求评审”“编写技术文档”。这类描述除非你能说清楚具体贡献,否则删掉更干净。
下面是处理完成后的 JSON 示例,可以看到它的结构是干净的,每一条都能对应到真实经历:
{ "basic": { "name": "张明", "job_intent": "后端开发工程师", "phone": "138****0000", "email": "zhangming@example.com", "city": "杭州" }, "skills": { "language": ["Java", "Python", "SQL"], "framework": ["Spring Boot", "MyBatis", "Spring Cloud"], "storage": ["MySQL", "Redis"], "middleware": ["Kafka", "RabbitMQ"], "tool": ["Git", "Docker", "Jenkins", "Linux"] }, "work": [ { "company": "某电商科技公司", "period": "2022.07 - 至今", "title": "后端开发工程师", "points": [ "承接订单履约链路开发,覆盖下单、拆单、发货三个核心环节", "将订单查询接口平均响应时间从【待补充】降至【待补充】", "推动订单状态机重构,减少状态不一致导致的客诉工单" ] } ], "projects": [ { "name": "订单履约系统重构", "role": "核心开发", "stack": ["Spring Boot", "MySQL", "Redis", "Kafka"], "points": [ "设计订单状态机,用事件驱动方式替代原有定时轮询同步", "引入 Redis 缓存热点订单详情,降低数据库读压力", "补充履约链路的异常补偿任务,减少人工介入处理" ] } ], "education": [ { "school": "某大学", "major": "计算机科学与技术", "degree": "本科", "period": "2018.09 - 2022.06" } ], "others": ["技术博客持续更新", "开源项目贡献"] }这份 JSON 就是你的简历数据源,是一个纯文本、可 diff、可版本管理的文件。后面所有版式调整,都只在模板层面发生,这个文件不需要动。
4.3 一个提高产出质量的追问技巧
模型的第一次输出通常只是及格线。想让它写得更好,不要重新开一个对话,而是在同一轮对话里追问。有效的追问有三类:一是要求它指出问题,比如“站在招聘官角度,这份简历最大的三个短板是什么”;二是要求它对比,比如“把这两条描述改写成更具体的版本,并说明改写理由”;三是要求它收敛,比如“把所有超过 45 字的描述压缩到 40 字以内,保留量化数据”。
这三类追问的共同点是:让模型从“生成”切换到“评审”。生成模式下的模型倾向于堆砌形容词,评审模式下的模型更愿意指出具体问题。这个切换在实际使用中效果差别很大,值得养成习惯。
5. 第二步:让豆包输出可渲染的 HTML/CSS 简历模板
拿到结构化数据之后,接下来生成版式。这里要强调一个顺序问题:先让模型生成 HTML 结构,再单独生成 CSS,不要一次性要求“给我一个完整的带样式的简历页面”。原因很直接,单次输出长度有限,HTML 加 CSS 混在一起生成,很容易在后面被截断,导致标签不闭合,你拿到一段跑不起来的代码,排查成本比重新生成还高。
5.1 生成 HTML 结构的提示词
根据我提供的 JSON 简历数据,生成一份单页 A4 简历的 HTML 结构。 要求: 1. 只输出 HTML 骨架和语义化标签,样式全部留空,用 class 名表达用途。 2. 使用语义化标签:header、section、article、h1、h2、ul、li,不要用 table 做布局。 3. 严格单栏,从上到下顺序为:基本信息、技术栈、工作经历、项目经历、教育背景、其他。 4. 不要内联样式,不要 style 标签,不要任何 JavaScript。 5. 所有中文内容直接写入,不要用占位符代替我的真实数据。 6. 输出完整的 html 文档,包含 <!DOCTYPE html> 和闭合标签。 我的数据如下: [粘贴第 4.2 步整理好的 JSON]生成的 HTML 大概会是下面这个形态。注意它的 class 命名是有语义的,比如resume-header、section-title、item-meta,这些命名会在 CSS 阶段被复用。
<!-- 文件路径:resume.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>张明 - 后端开发工程师</title> <link rel="stylesheet" href="resume.css"> </head> <body> <main class="resume"> <header class="resume-header"> <h1 class="name">张明</h1> <p class="job-intent">求职意向:后端开发工程师</p> <ul class="contact-list"> <li class="contact-item">138****0000</li> <li class="contact-item">zhangming@example.com</li> <li class="contact-item">杭州</li> </ul> </header> <section class="section"> <h2 class="section-title">技术栈</h2> <ul class="skill-list"> <li class="skill-item"><span class="skill-label">语言</span>Java、Python、SQL</li> <li class="skill-item"><span class="skill-label">框架</span>Spring Boot、MyBatis、Spring Cloud</li> <li class="skill-item"><span class="skill-label">存储</span>MySQL、Redis</li> <li class="skill-item"><span class="skill-label">中间件</span>Kafka、RabbitMQ</li> <li class="skill-item"><span class="skill-label">工具</span>Git、Docker、Jenkins、Linux</li> </ul> </section> <section class="section"> <h2 class="section-title">工作经历</h2> <article class="item"> <div class="item-head"> <h3 class="item-title">某电商科技公司</h3> <span class="item-meta">2022.07 - 至今</span> </div> <p class="item-subtitle">后端开发工程师</p> <ul class="item-points"> <li>承接订单履约链路开发,覆盖下单、拆单、发货三个核心环节</li> <li>将订单查询接口平均响应时间从 800ms 降至 120ms</li> <li>推动订单状态机重构,减少状态不一致导致的客诉工单</li> </ul> </article> </section> <section class="section"> <h2 class="section-title">项目经历</h2> <article class="item"> <div class="item-head"> <h3 class="item-title">订单履约系统重构</h3> <span class="item-meta">核心开发</span> </div> <p class="item-subtitle">Spring Boot / MySQL / Redis / Kafka</p> <ul class="item-points"> <li>设计订单状态机,用事件驱动方式替代原有定时轮询同步</li> <li>引入 Redis 缓存热点订单详情,降低数据库读压力</li> <li>补充履约链路的异常补偿任务,减少人工介入处理</li> </ul> </article> </section> <section class="section"> <h2 class="section-title">教育背景</h2> <article class="item"> <div class="item-head"> <h3 class="item-title">某大学</h3> <span class="item-meta">2018.09 - 2022.06</span> </div> <p class="item-subtitle">计算机科学与技术 · 本科</p> </article> </section> <section class="section"> <h2 class="section-title">其他</h2> <ul class="item-points"> <li>技术博客持续更新</li> <li>开源项目贡献</li> </ul> </section> </main> </body> </html>5.2 生成 CSS 的提示词与关键约束
拿到 HTML 之后,再单独提一次要求生成样式。这时候提示词要明确打印场景,因为屏幕好看和打印好看是两件事。
你是资深前端工程师,为下面这份 HTML 简历编写 CSS。 要求: 1. 目标尺寸 A4(210mm × 297mm),内容控制在一页内,溢出时缩小字号而不是裁切。 2. 屏幕预览时居中显示,带浅色背景衬托纸张边界;打印时去掉背景和阴影。 3. 正文字号 10.5pt,行高 1.6;姓名 22pt;章节标题 12pt。 4. 章节标题下方加一条 1px 分隔线,颜色 #d0d0d0。 5. 字体栈使用系统中文字体优先,保证跨机器不乱码。 6. 使用 @page 规则设置页边距为 12mm。 7. 不使用 JavaScript,不使用外部字体文件,不使用图片。 8. 输出完整 CSS,带注释说明每个模块的作用。对应的 CSS 实现大致如下。这里的几个细节值得注意:@page控制打印边距,@media print里去掉屏幕装饰,字体栈把中文字体放在前面,避免英文衬线字体抢先生效导致中文回退。
/* 文件路径:resume.css */ /* 页面基础设置 */ @page { size: A4; margin: 12mm; } :root { --text-main: #1f2329; --text-sub: #5c6370; --line-color: #d0d0d0; --paper-width: 210mm; --paper-min-height: 297mm; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif; font-size: 10.5pt; line-height: 1.6; color: var(--text-main); background: #f0f2f5; display: flex; justify-content: center; padding: 24px 0; } /* 纸张容器:屏幕上模拟一张 A4 纸 */ .resume { width: var(--paper-width); min-height: var(--paper-min-height); padding: 14mm 12mm; background: #ffffff; box-shadow: 0 2px 16px rgba(0, 0, 0, 0.12); } /* 头部信息 */ .resume-header { border-bottom: 2px solid var(--text-main); padding-bottom: 8px; margin-bottom: 14px; } .name { font-size: 22pt; font-weight: 700; letter-spacing: 2px; line-height: 1.2; } .job-intent { font-size: 11pt; color: var(--text-sub); margin-top: 4px; } .contact-list { list-style: none; display: flex; flex-wrap: wrap; gap: 16px; margin-top: 6px; font-size: 10pt; color: var(--text-sub); } /* 章节通用样式 */ .section { margin-bottom: 12px; } .section-title { font-size: 12pt; font-weight: 700; padding-bottom: 3px; border-bottom: 1px solid var(--line-color); margin-bottom: 8px; } /* 条目样式 */ .item { margin-bottom: 10px; } .item-head { display: flex; justify-content: space-between; align-items: baseline; } .item-title { font-size: 11pt; font-weight: 600; } .item-meta { font-size: 9.5pt; color: var(--text-sub); white-space: nowrap; } .item-subtitle { font-size: 10pt; color: var(--text-sub); margin-top: 2px; } .item-points { margin-top: 4px; padding-left: 16px; } .item-points li { margin-bottom: 2px; } /* 技术栈标签 */ .skill-list { list-style: none; } .skill-label { display: inline-block; min-width: 52px; font-weight: 600; color: var(--text-main); } /* 打印样式:去掉屏幕装饰,保证分页干净 */ @media print { body { background: #ffffff; padding: 0; display: block; } .resume { width: auto; min-height: auto; padding: 0; box-shadow: none; } .section { page-break-inside: avoid; } .item { page-break-inside: avoid; } a { color: inherit; text-decoration: none; } }5.3 内容超出一页时怎么办
这是最常见的问题。简历一页原则对工作三年内的候选人基本适用,超页时不要急着删内容,按下面的顺序调整:先压缩行高和段间距,从 1.6 降到 1.45 通常能省出三到五行;再合并低价值条目,比如“其他”模块里的两三条可以并成一条;然后精简项目数量,保留最近两个最相关的项目;最后才考虑缩小字号,10.5pt 降到 10pt 是底线,再小打印出来会吃力。
如果反过来,内容太少撑不满一页,也不要强行拉大字号或者加空行。正确做法是回头补充内容,比如把项目里的技术选型理由、遇到的问题和解决思路写进去。一页纸留白过多,传递的信号是经历单薄,这个印象很难靠排版挽回。
6. 第三步:本地渲染、导出 PDF 与打印适配
代码有了,接下来要在本地跑起来,并且导出成投递用的 PDF。这一步的目标很明确:产出一份矢量、可复制文字、A4 尺寸准确的 PDF 文件。
6.1 用浏览器导出,最省事的方式
把resume.html和resume.css放在同一个目录下,双击打开 HTML 文件,在浏览器里预览效果。确认排版无误后,按Ctrl + P(macOS 是Command + P)打开打印面板,做几个关键设置:
- 目标打印机选择“另存为 PDF”
- 纸张尺寸选 A4
- 边距选“默认”或“无”,具体看你 CSS 里
@page的设置,两者不要同时加边距 - 勾选“背景图形”,否则你设置的背景色和分隔线可能不显示
- 缩放选 100%,不要选“适合页面”,否则字体会被缩放
导出后打开 PDF,检查三件事:文字能不能选中复制(能选说明是矢量文字,不是图片)、页码是不是只有一页、中文有没有变成方块。
6.2 用 Python 脚本批量导出
如果你要维护多个岗位版本,手动点打印会很烦。用 Playwright 写个小脚本,改一行命令就能批量生成。先安装依赖:
pip install playwright playwright install chromium然后写导出脚本。注意page.pdf()只在 Chromium 内核下可用,且需要 headless 模式,这个方法是官方支持的,参数含义在下面有说明。
# 文件路径:tools/html2pdf.py from pathlib import Path from playwright.sync_api import sync_playwright def html_to_pdf(html_path: str, pdf_path: str) -> None: """把本地 HTML 文件渲染成 A4 尺寸的 PDF。""" src = Path(html_path).resolve() out = Path(pdf_path).resolve() if not src.exists(): raise FileNotFoundError(f"HTML 文件不存在: {src}") with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() # 等待字体和样式加载完成,避免导出时字体回退 page.goto(src.as_uri(), wait_until="networkidle") page.pdf( path=str(out), format="A4", print_background=True, prefer_css_page_size=True, margin={"top": "0mm", "bottom": "0mm", "left": "0mm", "right": "0mm"}, ) browser.close() size_kb = out.stat().st_size / 1024 print(f"已生成: {out} ({size_kb:.1f} KB)") if __name__ == "__main__": html_to_pdf("resume.html", "dist/resume-backend.pdf")这里有两个参数需要解释。prefer_css_page_size=True让脚本优先使用 CSS 里@page定义的尺寸和边距,而不是命令行传的格式参数,这样可以避免边距被加两次。print_background=True对应浏览器里勾选“背景图形”,如果不加,你 CSS 里设置的背景色和某些边框可能不渲染。
运行方式:
python tools/html2pdf.py预期输出类似:
已生成: /path/to/project/dist/resume-backend.pdf (42.3 KB)如果报playwright._impl._errors.Error,八成是浏览器内核没装好,重新执行playwright install chromium即可。如果导出的 PDF 内容被裁掉一部分,检查你的.resume容器是否设置了固定的min-height加上内边距,导致总高度超过 A4 的可打印区域。
6.3 投递前的最后检查清单
导出之后,别急着上传。按下面这几条快速过一遍,能挡掉大部分低级问题:
- 用另一台设备打开 PDF,确认中文字体正常显示,没有变成方块或宋体回退
- 全选文字复制到一个空白文档里,确认内容顺序是从上到下、没有错乱
- 检查页脚有没有多余的空白页,有的话说明内容高度超了一点点
- 确认文件命名规范,建议用“姓名-岗位-年限.pdf”,方便 HR 归档
- 手机上也打开看一遍,有些公司的初筛是在移动端完成的
7. 第四步:针对岗位做定向优化与筛选系统友好度检查
简历写完之后,最重要的一步是定制。海投同一份简历的转化率通常很低,而定制并不需要重写,只需要在现有结构上做加减法。
7.1 用 JD 做关键词对齐
把目标岗位的 JD 原文贴给豆包,让它做一件事:拆解出必须项、加分项、无关项,然后逐条对照我的简历,标出哪些要求我没有证据支撑。这个提示词的关键是要求它输出“证据位置”,而不是简单说“你缺少 XX 经验”。
下面是我目标岗位的 JD,以及我的简历内容。 请完成两件事: 1. 把 JD 拆成三类:【必须项】【加分项】【无关项】,逐条列出。 2. 针对每个【必须项】,在我的简历中查找是否有对应的经历证据。 如果有,指出具体在哪一段哪一句;如果没有,标注"无证据"。 要求:不要建议我编造经历。对于"无证据"的项,只标注出来,由我自己判断是否补充。拿到结果后,你会清楚地看到哪几条必须项是空的。如果空的是你确实做过的内容,说明简历没写出来,需要补;如果空的是你确实没做过的,那就别硬凑,把精力放在把已有的证据写得更扎实上。这个判断只有你自己能做,AI 给不了。
7.2 让关键词自然出现
关键词要出现,但不能堆砌。一个实用的判断标准是:每个关键词都应该出现在一个有具体语境的句子里。比如“Kafka”这个词,写在技术栈列表里只是罗列,写在“用 Kafka 承接订单状态变更事件,支撑日均百万级消息流转”里才是证据。
技术上还有一点值得注意:解析系统读取的是文本层,如果你的简历用图片、图标字体、文本框来实现某些信息,解析时可能直接丢失。所以安全做法是所有关键信息都用纯文本呈现,图标只做装饰,不放关键内容。
7.3 多版本管理与命名约定
当你开始维护多个版本时,文件管理会变成新问题。一个简单有效的做法是用目录区分:
resume/ ├── data/ │ ├── base.json # 主数据源,所有版本从这里派生 │ └── target-backend.json # 后端岗位定制数据 ├── templates/ │ ├── resume.html │ └── resume.css ├── dist/ │ ├── 张明-后端开发-3年.pdf │ └── 张明-基础架构-3年.pdf └── tools/ └── html2pdf.py如果配合 Git,每次投递前打一个 tag,比如git tag apply-2024-09-company-a,后面收到面试通知时能快速定位当时投的是哪一版。这个习惯看起来麻烦,实际用起来能省掉“我到底投的哪版”的反复确认。
8. 常见问题与排查思路
下面是这个流程里出现频率最高的问题。遇到报错时按表格从左到右的顺序排查,通常能定位到原因。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型输出的 HTML 代码块不完整 | 单次输出长度受限,被截断 | 检查最后一个标签是否闭合 | 拆成两次生成,先 HTML 后 CSS;或让它从断点处续写 |
| 页面打开是纯文本,没有样式 | CSS 文件路径错误或未保存 | 打开浏览器控制台看 404 | 确认 HTML 中link的href与文件名一致 |
| 导出的 PDF 出现空白第二页 | 内容高度略微超过一页 | 打印预览查看分页位置 | 减小margin-bottom、压缩行高、删掉一两条低价值描述 |
| 中文显示成方块或乱码 | 字体栈缺失,或系统无对应字体 | 换一台机器打开对比 | 补全字体栈,优先使用系统自带中文字体 |
| 打印时背景色和分隔线消失 | 未勾选背景图形 | 打印预览对比屏幕效果 | 浏览器勾选“背景图形”,脚本加print_background=True |
| 导出的 PDF 文字无法选中 | 用截图或图片方式生成 | 尝试全选复制 | 改用浏览器打印或 Playwright 导出矢量 PDF |
| 双栏模板解析后顺序错乱 | 布局使用表格或多列浮动 | 复制全文粘贴到记事本看顺序 | 改为单栏语义化标签布局 |
| 内容超出一页且删不掉 | 项目描述过长 | 统计每个模块的字数占比 | 项目描述压到 3 到 4 条,每条不超过 40 字 |
| 脚本报找不到浏览器内核 | Playwright 依赖未安装 | 看报错关键词Executable doesn't exist | 执行playwright install chromium |
| 生成内容出现明显虚构经历 | 提示词未限制事实来源 | 逐条对照原始素材 | 在提示词中加入“禁止补充未提及内容”的硬约束 |
除了这张表,还有一个非技术但更关键的问题值得单独说:模型给的描述太“顺”了,反而不可信。如果你看到一条描述读起来特别流畅、特别完整、特别像招聘广告,就要警惕它是不是被过度润色了。真实的技术工作往往带着具体的粗糙感,比如“为了解决重复扣款问题,在支付回调里加了幂等键校验”,这种描述比“优化支付链路,显著提升系统稳定性”可信得多。
9. 最佳实践与工程化建议
这套流程跑通之后,可以再往前走一步,把它做成一套可长期维护的东西。下面几条是从实际使用中总结出来的,按重要性排序。
9.1 数据与模板分离,是这套方案的核心价值
一定要守住这条线:base.json里只放事实,HTML 和 CSS 里只放结构和样式。任何时候需要改内容,只动 JSON;需要换风格,只动 CSS。这样你的简历就从一个“文档”变成了一个“项目”,改版成本从半小时降到两分钟。很多人用 AI 生成简历最后放弃,就是因为每次改内容都要重新生成整页,改完还得重新检查排版,来回几次就烦了。
9.2 真实性是不可协商的底线
在前面几处都提到了,这里再强调一次。AI 生成简历最大的风险不是不好看,是它会在你不注意的时候替你做了一些你没做过的事。防范方法有三个层次:提示词层面加硬约束;输出后逐条核对;面试前把简历上的每一条都过一遍,确保能展开讲三分钟。第三层最重要,因为面试官的问法往往就是“这条你具体怎么做的”。
9.3 隐私信息要主动脱敏
如果你把简历内容发给任何在线服务,注意不要包含身份证号、家庭住址、银行卡、前公司的内部系统名和未公开的业务数据。手机号可以用中间四位打码的版本用于演示,正式投递时再换成真实号码。这不是过度谨慎,而是基本的信息安全习惯,尤其是简历里经常会出现前公司的架构细节。
9.4 版本控制与投递记录
用 Git 管理简历的好处不只是回滚。你可以为每个岗位开一个分支,改完导出 PDF 后合并回主干,同时用 commit message 记录投递时间和公司。三个月后回看,你能清楚知道哪一版描述带来了面试机会,哪一版石沉大海,这些数据比任何“简历优化技巧”都更有参考价值。
9.5 排版上的几条硬建议
- 字号不要低于 10pt,行高不要低于 1.4,否则打印出来阅读吃力
- 一页之内不要出现三种以上的字号,层级靠字重和颜色区分
- 分隔线只用一种颜色和粗细,多了会显得杂乱
- 项目符号统一用一种样式,不要混用圆点、方块和短横线
- 页边距不要小于 10mm,太窄会显得拥挤,打印也容易被裁
9.6 把简历当成一个持续迭代的项目
最有效的用法不是“找工作的时候写一次”,而是平时就维护。每完成一个项目、解决一个有意思的问题,花两分钟往base.json里加一条。等到需要投递时,你面对的是一个已经积累了半年素材的数据库,只需要挑选和裁剪,而不是从零回忆。这个习惯的复利效应很高,尤其是在工作两三年之后,早期项目的细节会迅速模糊。
10. 这套流程之外,还有哪些值得继续深入的方向
回到最开始那个判断:一分钟能拿到的是草稿和版式,真正让简历起作用的是内容密度和匹配度。豆包在这个流程里承担的是“加工”和“成型”两个环节,它把原本需要审美和耐心的排版工作,变成了可复制、可版本管理的代码文件。这个转变本身,比任何具体的模板都更有价值。
如果你想继续往前走,有几个方向值得投入。一是把base.json的 schema 固化下来,写一个校验脚本,确保每次修改后必填字段不缺失、描述长度不超标,这本质上是在给简历做 lint。二是把导出流程接到 CI 上,每次 push 自动生成 PDF 并归档,适合需要频繁投递的时期。三是积累一份“素材库”,把平时写的技术笔记、故障复盘、方案文档都存下来,写简历时直接从里面摘,这比临时回忆要高效得多。
最后提醒一句:不管工具多顺手,投递前一定亲自把 PDF 从头到尾读一遍。工具能保证格式不错位,保证不了内容不出错,而一个错别字或者一条经不起追问的描述,代价可能是整场面试。这份 HTML 文件建议放进你的代码仓库里,和别的项目一起维护,用起来会比放在桌面某个文件夹里顺手得多。