上周有个同事找我,说他花了两个小时把一份在线技术文档截图、拼接、导出,最后交给客户一份歪歪扭扭的PDF。
这种时间损耗每天都在发生。在线文档、竞品页面、新闻报道、招聘信息——需要留存的网页内容越来越多,但大多数人的解决方案还停留在"截图+拼图"或者"打印另存为",排版乱、图片缺、链接死,存下来的东西根本没法看。
今天介绍的这个方案,基于 Playwright 这个开源库,能把任意网页——包括需要登录的页面、动态渲染的内容——完整转成一份格式干净的PDF,免费、无广告、无次数限制。
01为什么"打印成PDF"总是不够用?
浏览器自带的"打印→另存为PDF"是最常见的方案,但它有几个硬伤:
动态内容丢失。 很多页面依赖JavaScript渲染,打印时内容还没加载完,导出的PDF是空白或残缺的。
样式崩坏。 网页的CSS并不是为打印设计的,导出后字体错位、图片溢出、导航栏横穿正文的情况极为常见。
无法批量处理。 如果你需要每天存档10个页面,手动操作根本不现实。
市面上也有一些在线转换工具,比如 ilovepdf、Smallpdf,但免费版有文件大小限制,而且你的页面内容实际上是上传到了别人的服务器——涉及内部资料时这是个不小的风险。
02Playwright 是什么?为什么它能做到?
Playwright 是微软开源的浏览器自动化框架,GitHub Star 数已超过 67k,原本是为自动化测试设计的,但它内置了一个极其强大的能力:用无头浏览器完整渲染页面,然后导出PDF。
和截图工具不同,Playwright 启动的是一个真实的 Chromium 内核浏览器,JavaScript 会完整执行,懒加载的图片会等待加载完成,动态渲染的内容一样能被捕获。
这是它和 wkhtmltopdf、Puppeteer 同类工具相比最大的优势:渲染结果和你在浏览器里看到的几乎一致。
03五分钟上手:从安装到导出
第一步:安装
pip install playwright
playwright install chromium
只需要装 Chromium 这一个内核就够用了,不需要全部安装。
第二步:最简单的转换脚本
from playwright.sync_api import sync_playwright
def url_to_pdf(url: str, output_path: str):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(url, wait_until="networkidle")
page.pdf(path=output_path, format="A4", print_background=True)
browser.close()
url_to_pdf("https://example.com", "output.pdf")
wait_until="networkidle" 这个参数是关键——它会等到页面所有网络请求都完成后再截取,确保动态内容不会缺失。
第三步:处理需要登录的页面
这是很多人不知道的功能。Playwright 支持注入 Cookie,可以直接访问登录后的页面:
page.context.add_cookies([{
"name": "session_token",
"value": "你的token值",
"domain": "example.com",
"path": "/"
}])
page.goto("https://example.com/dashboard")
从浏览器开发者工具里复制 Cookie,粘贴进来,就能导出那些需要登录才能看到的内容。
04它比 Puppeteer 更适合这件事
很多人听到"网页转PDF"会第一时间想到 Puppeteer,毕竟它更早出名,教程也更多。
但实际对比下来,Playwright 在这个场景里有两个明显优势:
第一,等待机制更智能。Puppeteer 的 waitForNavigation 在单页应用(SPA)里经常失效,而 Playwright 的 networkidle 状态检测更可靠,导出成功率更高。
第二,PDF选项更丰富。Playwright 的 page.pdf() 支持设置页边距、页眉页脚、纸张尺寸、背景色打印等参数,Puppeteer 的同类接口选项相对有限。
如果你之前用 Puppeteer 转PDF遇到过内容缺失的问题,换 Playwright 试一次,大概率能解决。
05批量转换:把它变成真正的生产力工具
单个页面转换只是起点。稍微扩展一下,就能做到批量处理:
import asyncio
from playwright.async_api import async_playwright
urls = [
("https://site1.com/article", "article1.pdf"),
("https://site2.com/report", "report.pdf"),
("https://site3.com/docs", "docs.pdf"),
]
async def batch_convert(urls):
async with async_playwright() as p:
browser = await p.chromium.launch()
tasks = []
for url, path in urls:
page = await browser.new_page()
tasks.append(convert(page, url, path))
await asyncio.gather(*tasks)
await browser.close()
async def convert(page, url, path):
await page.goto(url, wait_until="networkidle")
await page.pdf(path=path, format="A4", print_background=True)
asyncio.run(batch_convert(urls))
异步并发执行,10个页面的转换时间和1个差不多。配合定时任务,可以做到每天自动存档指定页面,完全不需要人工介入。
06适合哪些人用?
- 产品和运营:定期存档竞品页面、活动页,留作历史对比
- 研究和分析:抓取新闻、报告、招聘信息,整理成可检索的PDF库
- 开发者:把在线API文档、技术规范转成本地文档,断网也能查
- 法务和合规:需要对网页内容进行存证时,导出带时间戳的PDF留档
07现在就可以开始
Playwright 的 GitHub 地址是 microsoft/playwright,文档完整,社区活跃,遇到问题基本都能搜到答案。
如果你不想写代码,也可以搜索基于 Playwright 封装的命令行工具 playwright-pdf,直接在终端里 playwright-pdf https://example.com output.pdf 一行搞定,连脚本都不用写。
真正的效率工具,不是让你做得更快,而是让某类问题从此消失。
把这篇文章发给还在截图拼PDF的同事,他会感谢你的。
夜雨聆风