夜雨聆风学习资料网

ARTICLE · 1272860

使用 Playwright 将网页转 PDF,三种方式别选错了

使用 Playwright 将网页转 PDF,三种方式别选错了

最近在折腾用 Playwright 把页面转成 PDF,踩了不少坑,也理清了一些东西。可能有人会问:Playwright 转 PDF,内容从哪来有讲究吗?

有,而且讲究很大。内容来源有好几种情况,选不对方式,轻则样式全丢,重则一片空白。

今天就把这三种方式捋清楚,看完你大概会有一句感慨:原来坑都在第一步。

一、整件事就三步,分歧全在中间那步

先说个反常识的:Playwright 转 PDF 的代码,从头到尾就那么三行核心。

with sync_playwright() as p:    browser = p.chromium.launch()          # 启动浏览器    page = browser.new_page()              # 开个页面    page.pdf(path="out.pdf", format="A4")  # 导出 PDF

看出来了吗?真正会变的,只有中间"页面内容怎么来的"这一步。

而这一步有三种写法,对应三种完全不同的机制。选错了,后面全是坑。

二、方式一:加载本地文件

HTML 已经在硬盘上了,用 file:// 直接打开:

page.goto('file:///D:/docs/input.html', wait_until='networkidle')page.pdf(path='out.pdf', format='A4')

适合:已经生成好的静态 HTML,比如测试框架吐出来的报告,想固化成 PDF 存档或发邮件。

好处是省事——不用起 HTTP 服务,也不用管网络。

但有个细节得提醒:Windows 上路径必须写成 file:///D:/...,三根斜杠加正斜杠。手写容易错,尤其路径里有中文或空格的时候。

别自己遭罪,一行搞定:

from pathlib import Pathuri = Path('D:/docs/input.html').resolve().as_uri()page.goto(uri, wait_until='networkidle')

三、方式二:直接塞 HTML 字符串

内容不是文件,而是接口返回的、或者在代码里拼出来的字符串,那就直接用 set_content:

page.set_content(html_content, wait_until='networkidle')page.pdf(path='out.pdf', format='A4')

它快。纯内存操作,不读盘不发请求,微服务里做高并发文档生成首选。

但它有个要命的坑——相对路径全废。

set_content 只是把字符串塞进当前文档,不导航。此时页面的 document.baseURI 还停在 about:blank,于是这些全都加载不出来:

<linkrel="stylesheet"href="./style.css">   <!-- 废 --><imgsrc="./images/logo.png">                <!-- 废 -->

解法是:样式全内联,图片转 base64。或者干脆注入一个 `` 告诉它该去哪找。

记住一句话:用 set_content,就得让 HTML 自己养活自己。

四、方式三:访问线上网址

想转的是线上页面,那就直接 goto:

page.goto("https://example.com/dashboard", wait_until='networkidle')page.pdf(path='out.pdf', format='A4')

它最真实。渲染结果跟你在浏览器里看到的几乎一模一样,跨域、Cookie、登录态全都正常。

适合:归档线上文档、把看板拍成 PDF 存档。

代价是它不受你控制。远端改版、反爬、登录墙,任何一个都能让结果变样。要登录的页面,得先把登录态带上:

context = browser.new_context(storage_state='state.json')page = context.new_page()

五、一张表看清楚怎么选

维度
方式一:本地文件
方式二:HTML 字符串
方式三:线上网址
相对路径
✅ 正常
❌ 全废
✅ 正常
外部 CSS/JS
✅ 自动加载
❌ 要内联
✅ 自动加载
速度
中等
最快
看网速
需要外网
否
否
是
可控性
高
最高
低

一句话原则:

有文件、有相对路径 → goto file://有字符串、自包含 → set_content要抓线上 → goto https://

六、这三种方式,共通的坑

不管选哪种,下面几个坑你大概率都会撞上。

1. page.pdf() 只认 Chromium,而且必须无头

browser = p.chromium.launch()   # ✅ 默认就是 headless

Firefox、WebKit 都不支持。开了有头模式,pdf() 直接报错——这是 Chromium 协议本身的限制,不是 Playwright 的问题。

2. networkidle 不等于"渲染完成"

这是个高频误解。含图表、字体的页面,资源加载完了但渲染可能还没结束,导出来就是空白页。

等一个明确的信号更靠谱:

page.wait_for_selector("#chart svg", state="visible")

3. 容器里中文变方框

Docker 镜像里通常没装中文字体,导出全是豆腐块。构建时加上:

RUN apt-get install -y fonts-noto-cjk fonts-wqy-zenhei

4. 忘了 print_background=True

这是最容易被漏掉的一项。不加它,页面的背景色、代码块底色都不会出现在 PDF 里——你会得到一份"漂白了"的文档。

page.pdf(path="out.pdf", format="A4", print_background=True)

5. PDF 走的是打印样式

@media print 生效,@media screen 不生效。页面上那些导航栏、按钮,打印时想藏掉就这么写:

@media print { .no-print { display: none; } }

七、写在最后

回头看,Playwright 转 PDF 这件事,代码是真的简单。

真正的门槛全在理解和选择上:

  • 搞清楚 goto 是导航、set_content 是注入,很多诡异现象一秒就解释通了

  • 用 goto(file://) 还是 set_content,取决于你的内容有没有落脚点

  • print_background、networkidle、中文字体这些坑,踩过一次就记住了

工具不难,难的是知道它什么时候会咬你。

这篇算是把三种方式的脾气都摸了一遍,希望你能少走点弯路。

完整可运行示例代码我整理好了,关注公众号后台回复「html2pdf」自取。

转 PDF 这事,真没那么难。

觉得有用的话,点个赞再走吧。


#Python #Playwright #PDF #网页导出 #自动化

相关学习资料