我的知识记录

Python HTML网页转PDF:WeasyPrint保留CSS样式

用Python的WeasyPrint把HTML模板或在线网页转成PDF,完整保留CSS样式、图片、字体,适合生成发票、合同、报表等带排版的文档。

场景痛点

业务系统里的订单、发票、对账单都是HTML页面,要导出成PDF归档或发邮件,用浏览器手动另存PDF样式经常崩。WeasyPrint是纯Python的HTML→PDF引擎,支持CSS3、@page分页、本地图片,写一套HTML模板,业务数据填进去就能批量生成带样式的PDF。

用到的库

pip install weasyprint

完整代码

# -*- coding: utf-8 -*-
"""
用 WeasyPrint 把 HTML 字符串或本地 HTML 文件转成 PDF。
支持内联 CSS、本地图片、@page 分页控制。
"""
from weasyprint import HTML, CSS


HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<style>
@page {
size: A4;
margin: 2cm;
@bottom-center { content: "第 " counter(page) " 页 / 共 " counter(pages) " 页"; }
}
body { font-family: "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
font-size: 12pt; color: #222; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; }
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
th, td { border: 1px solid #999; padding: 6px 10px; text-align: left; }
.total { font-size: 14pt; font-weight: bold; color: #c0392b; }
</style>
</head>
<body>
<div class="header"><h1>月度对账单</h1></div>
<p>客户名称:{{customer}}<br>对账周期:{{period}}</p>
<table>
<tr><th>日期</th><th>单号</th><th>金额(元)</th></tr>
{% for item in items %}
<tr><td>{{item.date}}</td><td>{{item.no}}</td><td>{{item.amount}}</td></tr>
{% endfor %}
</table>
<p class="total">合计:{{total}} 元</p>
</body>
</html>
"""


def render_html_to_pdf(html_content: str, output_pdf: str, base_url: str = "."):
"""
html_content: 完整 HTML 字符串
base_url: 相对资源(图片/CSS)的基准目录
"""
HTML(string=html_content, base_url=base_url).write_pdf(output_pdf)
print(f"PDF 已生成:{output_pdf}")


def fill_template(template: str, data: dict) -> str:
"""简单做个变量替换,不引入 jinja2 依赖。"""
result = template
for k, v in data.items():
result = result.replace("{{" + k + "}}", str(v))
return result


if __name__ == "__main__":
# 实际项目里用 Jinja2 渲染模板更方便,这里演示简单替换
html = HTML_TEMPLATE
html = html.replace("{{customer}}", "宝鸡稳驰供应链")
html = html.replace("{{period}}", "2026年8月")
html = html.replace("{{total}}", "128,500.00")

items = [
("2026-08-03", "YD20260803001", "12,300.00"),
("2026-08-12", "YD20260812007", "45,800.00"),
("2026-08-25", "YD20260825013", "70,400.00"),
]
rows = "".join(
f"<tr><td>{d}</td><td>{n}</td><td>{a}</td></tr>" for d, n, a in items
)
# 上面模板用了 {% for %},简单替换场景直接把 table 行替换掉
import re
html = re.sub(r"\{% for item in items %\}.*?\{% endfor %}",
rows, html, flags=re.S)

render_html_to_pdf(html, "invoice.pdf", base_url=".")

代码讲解

  • HTML(string=...).write_pdf(...) 是WeasyPrint最核心的调用,传入HTML字符串直接出PDF,不需要中间文件。
  • base_url 参数很重要:HTML里引用了<img src="logo.png">这种相对路径资源时,WeasyPrint会基于base_url去找图片。
  • @page CSS规则控制纸张大小、页边距、页眉页脚,@bottom-center 自动加页码,这是WeasyPrint比wkhtmltopdf强的地方。
  • 中文字体在font-family里列多个候选,Linux上用WenQuanYi Micro Hei,Windows上用Microsoft YaHei,WeasyPrint会自动找系统已安装的字体。
  • 真实项目建议装jinja2做模板渲染,比字符串replace清晰得多。

运行结果

脚本跑完生成invoice.pdf,打开看到居中标题"月度对账单"、客户信息、三行表格、红色合计金额,底部自动有"第1页/共1页",样式和浏览器里看HTML基本一致。

注意事项

  • WeasyPrint在Windows上首次安装可能提示缺cairo/pango,pip新版本已带wheel,正常pip install weasyprint即可;Linux上要装libpangolibcairo等系统库。
  • 不支持JavaScript动态渲染,要转需要JS执行的网页,先无头浏览器截图或者用playwright。
  • CSS只支持到CSS3子集,复杂动画、flexbox某些属性可能渲染不全,做打印样式表时尽量用table和float。
  • 在线网页转本地PDF用HTML(url="https://example.com").write_pdf("out.pdf"),但注意网站可能反爬、需要登录。

Python HTML网页转PDF:WeasyPrint保留CSS样式

标签:

更新时间:2026-09-14 21:37:08

上一篇:Python多张图片合成PDF:Pillow一键打包教程

下一篇:Python获取PDF页数与元信息 标题作者教程