HTML简历Playwright转PDF
如何用 HTML + Playwright 构建精美简历并转换为 PDF
本文记录了从 Markdown 简历转换为 PDF 的探索过程,最终采用 HTML 模板 + Playwright 渲染的方案,实现了高度保真的简历输出。
目录
背景与痛点
最初有一份 Markdown 格式的简历,直接用 Markdown 转 PDF 工具(如 pandoc、md2pdf)转换后,样式往往会出现以下问题:
- 字体、间距与预期不符
- 代码块、表格渲染混乱
- 无法精细控制分页位置
- 生成的 PDF 在不同设备上显示不一致
核心诉求:希望拥有一套可以所见即所得的简历系统,编辑方便,输出专业,且能一键生成 PDF。
方案选型
方案一:Markdown → PDF(放弃)
| 工具 | 问题 |
|---|---|
| WeasyPrint | 对现代 CSS 支持有限,布局经常错乱 |
| Pandoc + PDF | 需要 LaTeX 环境,配置复杂 |
| md2pdf | 样式单一,定制性差 |
方案二:HTML 模板 + Playwright(采用)
利用 Playwright 控制真实浏览器进行渲染,可以完美支持:
- 现代 CSS3 特性(Flexbox、Grid、动画)
- 自定义字体(Google Fonts)
- 外部图片资源
- 任意 PDF 尺寸与边距设置
优势:
1. 浏览器渲染引擎保证视觉效果与编辑器完全一致
2. 支持 print_background=True 保留背景色
3. 可通过 JavaScript 动态获取页面高度,实现自适应 PDF 尺寸
4. 支持等待网络字体加载,避免字体替换导致的布局抖动
技术实现
环境准备
# 安装 Playwright
pip install playwright
# 安装 Chromium 浏览器
python -m playwright install chromium
# 国内镜像(可选)
pip install playwright -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m playwright install chromium
项目结构
resume/
├── resume.html # 简历 HTML 模板
├── photo.jpg # 简历照片
├── html_to_pdf.py # PDF 转换脚本
└── resume.pdf # 生成的 PDF 文件
HTML 模板设计
简历模板采用模块化结构,使用 CSS 变量统一管理配色:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>元敏富 - 个人简历</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
:root {
--bg-color: #f0f0f0;
--black: #111111;
--white: #ffffff;
--yellow: #ffcc00;
--blue: #1890ff;
--red: #ff4d4f;
--green: #52c41a;
--gray-bg: #f5f5f5;
--border-thick: 3px;
--border-radius: 8px;
}
body {
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-color);
color: var(--black);
line-height: 1.6;
padding: 40px 20px;
}
.resume-container {
max-width: 900px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 24px;
}
.brutal-box {
background-color: var(--white);
border: var(--border-thick) solid var(--black);
border-radius: var(--border-radius);
padding: 24px 30px;
}
</style>
</head>
<body>
<div class="resume-container">
<!-- 头部信息 -->
<div class="brutal-box header-box">
<div class="header-info">
<h1>元敏富</h1>
<div class="subtitle">中央民族大学 · 计算机科学与技术 · 本科</div>
<div class="contact-grid">
<div><i class="fas fa-phone"></i> 17534312005</div>
<div><i class="fas fa-envelope"></i> min742015@gmail.com</div>
<!-- 更多联系方式... -->
</div>
</div>
<img src="photo.jpg" alt="头像" class="avatar">
</div>
<!-- 专业技能 -->
<div class="brutal-box">
<div class="section-title"><i class="fas fa-code"></i> 专业技能</div>
<div class="skills-grid">
<span class="tag">Python</span>
<span class="tag">Java</span>
<!-- 更多技能标签... -->
</div>
</div>
<!-- 项目经验 -->
<div class="brutal-box">
<div class="section-title"><i class="fas fa-rocket"></i> 项目经验</div>
<div class="item-card">
<div class="item-header">
<div class="item-title">虚拟恋人 Agent</div>
<div class="item-badge">AI 应用</div>
</div>
<div class="item-desc">项目描述...</div>
<ul class="item-list">
<li>核心亮点 1</li>
<li>核心亮点 2</li>
</ul>
</div>
</div>
</div>
</body>
</html>
核心代码
HTML 转 PDF 脚本(html_to_pdf_playwright.py)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HTML 简历转 PDF 脚本 - 使用 Playwright + Chromium
"""
import sys
import os
import asyncio
from playwright.async_api import async_playwright
async def html_to_pdf(html_file_path):
html_path = os.path.abspath(html_file_path)
if not os.path.exists(html_path):
print(f"错误: 文件不存在 {html_path}")
sys.exit(1)
base_dir = os.path.dirname(html_path)
filename = os.path.splitext(os.path.basename(html_path))[0]
pdf_path = os.path.join(base_dir, f"{filename}.pdf")
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
# 加载本地 HTML 文件
file_url = "file:///" + html_path.replace("\\", "/")
await page.goto(file_url, wait_until="networkidle")
# 等待字体和样式完全加载
await page.wait_for_timeout(2000)
# 获取页面实际内容高度(关键!解决分页空白问题)
content_height = await page.evaluate("""
() => {
const body = document.body;
const html = document.documentElement;
return Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
}
""")
# 生成 PDF - 宽度固定 A4 高度自适应
await page.pdf(
path=pdf_path,
width="210mm",
height=f"{content_height}px",
print_background=True,
margin={
"top": "0",
"right": "0",
"bottom": "0",
"left": "0"
}
)
await browser.close()
print(f"✅ PDF 已生成: {pdf_path}")
def main():
if len(sys.argv) < 2:
default_html = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resume.html")
if os.path.exists(default_html):
asyncio.run(html_to_pdf(default_html))
else:
print("用法: python html_to_pdf_playwright.py <html文件路径>")
sys.exit(1)
else:
asyncio.run(html_to_pdf(sys.argv[1]))
if __name__ == "__main__":
main()
使用方法
# 转换单个文件
python html_to_pdf_playwright.py resume.html
# 或直接运行(会自动处理同目录下的 resume.html)
python html_to_pdf_playwright.py
踩坑记录
坑 1:PDF 分页导致大量空白
问题描述:使用固定 A4 尺寸时,第一页底部和第二页顶部之间出现大量空白。
原因:固定 format="A4" 会在 297mm 处强制分页,内容被截断。
解决方案:动态获取页面实际高度,设置 height=f"{content_height}px":
# 之前(有问题)
await page.pdf(
path=pdf_path,
format="A4", # 固定尺寸,分页位置不可控
...
)
# 之后(修复)
await page.pdf(
path=pdf_path,
width="210mm",
height=f"{content_height}px", # 高度自适应,无分页
...
)
坑 2:网络图片在 PDF 中不显示
解决方案:
- 将图片替换为本地文件(与 HTML 同目录)
- 或确保网络稳定 + 设置 wait_until="networkidle"
坑 3:字体加载延迟导致布局抖动
解决方案:增加等待时间
await page.wait_for_timeout(2000) # 等待字体完全加载
坑 4:Windows 路径问题
问题:file:/// 协议需要正确处理反斜杠
解决方案:
file_url = "file:///" + html_path.replace("\\", "/")
设计亮点
1. 头像放置在顶部信息右侧
通过 CSS order 属性调整 Flex 布局顺序,无需改变 HTML 结构:
.avatar {
order: 2; /* 右侧 */
}
.header-info {
order: 1; /* 左侧 */
}
2. 密集型顶部信息区
将个人信息、教育背景、专业技能整合到一个模块,减少视觉层级:
┌─────────────────────────────────────────────────────────────┐
│ 元敏富 ┌──────┐ │
│ 中央民族大学 · 计算机 · 本科 │ 头像 │ │
│ 📞 175... ✉️ min... 📍 北京 │ │ │
└─────────────────────────────────────────────────────────────┘
3. 卡片式项目经验
每个项目采用统一卡片结构,带有醒目的标签和结果展示:
┌─────────────────────────────────────────────────────┐
│ 项目名称 [AI应用] │
├─────────────────────────────────────────────────────┤
│ 项目描述... │
│ │
│ [Python] [FastAPI] [LangChain] │
│ │
│ ■ 核心亮点 1 │
│ ■ 核心亮点 2 │
│ ■ 核心亮点 3 │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 🏆 项目成果/数据展示 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
最终效果
生成的文件
| 文件 | 说明 |
|---|---|
resume.html |
简历 HTML 模板,可直接在浏览器中预览和编辑 |
photo.jpg |
简历照片(1:1 比例) |
html_to_pdf_playwright.py |
PDF 转换脚本 |
resume.pdf |
最终输出的 PDF 文件 |
工作流程
编辑 resume.html → 浏览器预览效果 → python html_to_pdf_playwright.py → 生成 resume.pdf
总结
通过 HTML + Playwright 的方案,我们实现了:
- 所见即所得:编辑 HTML 后浏览器直接预览,效果即最终 PDF 效果
- 高度可定制:CSS 完全可控,任意调整样式
- PDF 精准输出:高度自适应,无分页空白问题
- 现代化设计:支持 Flexbox、Grid、动画、图标字体等
相比传统 Markdown/LaTeX 转 PDF 的方案,这种方式更灵活、更直观,非常适合对简历视觉效果有较高要求的开发者使用。
本文档生成于 2026年5月
评论区
共 0 条