网页图文转 PDF (Web2PDF) v0.2.2
把网页(URL)或用户粘贴的图文内容,变成排版干净、图片完整的 PDF 文件,直接交付下载。
> 本 skill 自带可执行脚本:scripts/page-to-pdf.js(Node.js 渲染入口,约 12KB)、scripts/self-check.js(环境自检)、package.json(main/bin 已声明 scripts/page-to-pdf.js)。无需下载任何浏览器引擎即可运行——脚本自动探测系统已装的 Edge / Chrome 做零依赖直打。
> 边界声明:本 skill 只处理「网页 / 网页内容 → PDF」。若目标是本地文档(Word / PPT / Markdown / Excel)转 PDF,不归本 skill,引导用户走对应文档转换流程。
When to use
- 用户给了一个网页链接,要「存成 PDF / 转成 PDF / 保存网页 / 网页打印 / 导出页面」。
- 用户贴了一段图文内容(文字 + 图片),要「做成 PDF」。
- 用户要「把这篇网页 / 这篇文章保存下来慢慢看」。
触发词(清晰路由用)
中文:
网页转PDF · 网页转成PDF · 网页保存为PDF · 网页存成PDF · 网页存PDF · 保存网页 · 网页下载 · 网页打印 · 页面导出PDF · 链接转PDF · URL转PDF · 这篇文章存成PDF · 把网页存下来 · 把页面做成PDF · 存成PDF(伴随链接出现)
英文:
web to pdf · webpage to pdf · save webpage as pdf · export page as pdf · url to pdf · print to pdf · convert webpage to pdf
负向判定(不触发): 目标是本地文件转换(word转pdf / ppt转pdf / md转pdf)→ 不归本 skill;没有 URL 也没有任何图文内容,纯口头描述「我想做个pdf」→ 先追问内容来源。
意图路由(第一步先做)
| 维度 | 分支 | 判定依据 |
|---|---|---|
| 输入形态 | 模式A:URL模式 | 输入含 http(s):// 链接 → 直接抓网页 |
| 模式B:内容模式 | 无链接,只有图文内容 → 组稿成 HTML 再打印 | |
| 输出偏好 | 原版式(默认) | 用户没说 → 所见即所得直打,保留页面排版与配色 |
| 阅读版(可选) | 用户明确要「干净版 / 去广告 / 只留正文」→ 按阅读版组稿:去除原网页导航(技能/插件/登录/发布入口)、推荐区图标、页脚,仅保留正文图文后重排 | |
| 特殊场景 | 懒加载页面 / 登录墙 | 见 references/faq-troubleshooting.md |
路由完成后,先跟用户确认一句:「按原版式把整页存成 PDF,对吗?」——除非用户指令已明确。避免在「原样保存」和「干净阅读版」之间猜错方向白跑一趟。
执行逻辑(核心五步)
flowchart LR
A[① 解析输入] --> B[② 探测渲染引擎]
B --> C[③ 渲染并打印 PDF]
C --> D[④ 校验产物]
D --> E[⑤ 交付下载]
Step ① 解析输入
- 模式A:提取 URL;注意 URL 编码(含中文/空格要 encode);确认协议是 http/https。
- 模式B:收集用户图文内容 → 按
references/content-html-template.md的规范组稿为本地 HTML 文件(内嵌图片用 base64,避免外链图片失效)。 - 明确输出偏好(原版式 / 阅读版),写入渲染参数。
Step ② 探测渲染引擎(按优先级)
- 引擎A:系统 Edge/Chrome headless(默认,零依赖)
- 脚本自动探测 Windows / macOS / Linux 常见路径(Edge 优先,Chrome 兼容同参数)。
- 命中即用,无需安装任何东西。 - 引擎B:Playwright(msedge channel)(懒加载/动态页必保)
- 首选playwright-core:几 MB 的纯驱动,通过channel: 'msedge'直接驱动系统 Edge,无需下载 Chromium。
- 安装(脚本会自动探测本地node_modules与~/.workbuddy/binaries/node/workspace/node_modules):
bash npm install playwright-core # 或完整版(含自带 Chromium,~150MB) npm install playwright && npx playwright install chromium
- 启用条件:页面滚动触发懒加载(IntersectionObserver /img[data-src]/img[loading="lazy"])或动态渲染、需自定义滚动加载。
Step ③ 渲染并打印 PDF
- 调
node scripts/page-to-pdf.js <url|htmlFile> <outPdf> [options]: - 模式A → 直接传 URL。
- 模式B → 先写 HTML 文件,再传
file://路径。 - 脚本默认行为:
--headless=new+--no-pdf-header-footer+--virtual-time-budget=15000(虚拟时间预算,处理定时器与常见懒加载)+ 独立--user-data-dir(避免与用户已打开的浏览器冲突)。 - 懒加载/图片多场景默认加
--check-lazy:Edge 成功后自动用 Playwright 检测页面是否含data-src或loading="lazy"图,命中则自动切引擎B 滚动重打——图片完整必保。已实测:懒加载 10 张图 100% 命中。 - 阅读版需求时:先在内存里做正文提取并按阅读版组稿——去除原网页导航(技能/插件/登录/发布入口)、推荐区图标、页脚与广告,仅保留正文图文,重排为干净 HTML 后再打印;交付说明里声明「已去除导航/推荐区/页脚,仅保留正文图文」。
Step ④ 校验产物(诚实声明的前提)
必须逐项确认,缺一项就在交付里如实说明:
- [ ] PDF 文件存在且大小 > 0(脚本返回 ok: true)。
- [ ] 页数 >= 1(用脚本输出的 pages,或 python/fitz 复核)。
- [ ] 图片完整性抽查:PDF 体积与页面图片数量大致匹配;若页面有大量图片但 PDF 异常小(如 < 50KB),判定图片可能未加载 → 提示用户并给「引擎B 重试」选项。
Step ⑤ 交付下载
- 用
present_files交付 PDF(自动打开预览 + 卡片下载)。 - 说明输出路径与页数/体积;提示可再跑一次换「阅读版」或「引擎B」得到不同效果。
输出文件约定
- 命名:
<页面标题或主题>_<YYYYMMDD>.pdf,放当前工作区outputs/下。 - 绝不覆盖用户已有文件;重名则加序号。
质量闸门(安全底线)
- 诚实声明:PDF 必须真跑过脚本且校验通过才说「已生成」;没跑成功就说「转换失败 + 原因」。
- 不绕过访问控制:登录墙 / 付费墙 / 验证码页面,直接告知「该页面需要登录,无法完整抓取」,不破解、不伪造 Cookie。
- 图片完整性如实报告:图片缺失不隐瞒,给出原因(懒加载未触发 / 防盗链 / 外链失效)与补救选项。
- 版权边界:交付物仅供个人保存、学习、内部传阅;不用于商业再分发,涉及明显侵权内容提示用户注意。
- 声明假设:阅读版组稿后,明确说明「已去除原网页导航、推荐区与页脚,仅保留正文图文」。
脚本参考
# 运行环境自检(无需浏览器,证明脚本可运行 + 探测浏览器/Playwright 是否就绪)
node scripts/self-check.js
# 模式A:网页直打(原版式)
node scripts/page-to-pdf.js "https://example.com/article" outputs/article.pdf
# 模式A:网页直打 + 更长加载时间(图片多/慢站)
node scripts/page-to-pdf.js "https://example.com" outputs/p.pdf --timeout 30000
# 模式A:懒加载/图片多站点(必保图片完整)—— 推荐默认加 --check-lazy
node scripts/page-to-pdf.js "https://news.example.com/article" outputs/p.pdf --check-lazy
# 模式B:本地组稿 HTML(file://)
node scripts/page-to-pdf.js "D:/work/article.html" outputs/article.pdf
# 全参数
node scripts/page-to-pdf.js <input> <output> [--viewport WxH] [--timeout MS] [--keep-header] [--check-lazy]