网页转A4高清PDF(webpage-to-a4-pdf)
> 作者:浅见
把网页原封不动地变成可以直接打印/归档的 PDF。
核心原则:不重排版。 不套用自己的 CSS、不改动字体行距、不删减页面元素——只把整页等比缩放到目标纸张宽度,然后自动分页。用户要的就是"跟浏览器里看到的一样"。
它解决什么问题
| 常见做法 | 问题 |
|---|---|
| 浏览器直接"打印为 PDF" | 常把宽屏布局挤变形、侧栏/背景丢失、分页处把文字切两半 |
| 先截图再塞进 PDF | 分辨率不够(96 DPI 发虚)、分页位置乱切 |
| 让 AI 重新排版成文档 | 排版变了,用户要的是原版式留档 |
本技能的做法:桌面视口整页渲染 → 按目标像素宽全页高清截图 → 逐行墨量分析找整行空白处分页 → 按纸张尺寸精确落版。结果:布局原样、分辨率精确达标、分页不切断文字。
前置依赖
- Edge 或 Chrome(Windows 上一般已有 Edge)
- Node 18+(需要全局
WebSocket,用于 CDP 控制浏览器) - Python 3.8+ 与
Pillow、reportlab
首次运行若缺 Python 包,先装:
"<PYTHON>" -m pip install Pillow reportlab
参数说明
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
--input / -i |
是 | - | 输入路径:单个 URL / 含 URL 的文件(.txt/.csv/.json/.md/.url/.webloc/.html) / 整个文件夹(批量,递归扫描其中所有清单文件) |
--out / -o |
是 | - | 输出路径:文件夹(批量时按来源子目录归类);若只有 1 条且以 .pdf 结尾,则视为具体文件名 |
--dpi |
否 | 300 |
分辨率,见下方建议表 |
--page-size |
否 | A4 |
纸张:A4/A3/A2/A5/A6/B4/B5/LETTER/LEGAL/TABLOID,或自定义 宽x高(mm),如 210x297 |
--margin-mm |
否 | 0 |
页边距(mm)。0=满版;打印建议 8~12 |
--viewport-width |
否 | 1280 |
桌面视口 CSS 宽度。页面在更宽屏上才展开完整时调大到 1440/1600 |
--wait-ms |
否 | 2500 |
加载后额外等待毫秒。动态渲染/慢站调到 4000~6000 |
--format |
否 | png |
页图编码:png(无损,纯文字/表格页面通常反而更小,推荐)/ jpeg(页面含大量照片、渐变底色、彩色图表时才更划算) |
--quality |
否 | 90 |
--format jpeg 时的质量 1-100(关闭色度抽样以保住文字锐度) |
--name-from |
否 | auto |
输出文件名来源:auto(h1→title) / title / h1 / url |
--recursive/--no-recursive |
否 | 递归 | 批量时是否递归子文件夹 |
--limit N |
否 | 0 |
只处理前 N 条(先小批量试跑用),0=不限 |
--overwrite |
否 | 关 | 同名直接覆盖;默认自动追加 -2、-3 |
--report |
否 | <out>/_report.csv |
处理报告 CSV 路径 |
--dry-run |
否 | 关 | 只列出将要处理的 URL,不实际处理 |
分辨率(DPI)选择建议
| DPI | 适用场景 | 说明 |
|---|---|---|
| 96 | 仅屏幕浏览 | 最小最快,不建议打印 |
| 150 | 草稿 / 内部传阅 | 文字略软 |
| 200 | 普通打印 | 日常够用 |
| 300 | 标准归档(默认推荐) | 打印清晰、体积可控,A4 单页约 0.5–1.5MB |
| 400 | 精细归档 | 页面含细小表格线、小字号时推荐 |
| 600 | 档案级 | 体积很大(单页数 MB 级),仅在有特殊要求时用 |
页面尺寸选择建议
| 尺寸 | 适用 |
|---|---|
| A4 (210×297mm) | 默认。国内文档、评审材料、归档 |
| A3 (297×420mm) | 宽表格、流程图、需要保留更多横向细节 |
| A5 (148×210mm) | 小册子、便携阅读 |
| Letter / Legal | 境外标准、国际材料 |
自定义 宽x高 |
特殊装帧,如 200x260 |
> 页面很宽(横向内容多)时,与其提高 DPI,不如换大一号纸张(A4→A3),细节保留更完整。
调用示例
下面 <SKILL_DIR> 指本技能目录,<PYTHON> 指带 Pillow/reportlab 的解释器。
示例 1 — 单个网页 → A4 300 DPI(最常用)
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" \
--input "https://std.samr.gov.cn/gb/search/gbDetailed?id=31D62EBFE94C4790E06397BE0A0A8AB3" \
--out "C:\Users\zhang\Desktop\1" \
--dpi 300 --page-size A4
示例 2 — 批量处理整个文件夹(核心能力)
把一堆 .txt/.csv/.json 清单文件丢进一个文件夹(可含子目录),一次性全部转成 PDF,
输出目录会按来源子目录原样归类,并生成 _report.csv:
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" \
--input "D:\待归档\标准链接" \
--out "C:\Users\zhang\Desktop\1" \
--dpi 300 --page-size A4 --margin-mm 10 --recursive
示例 3 — 指定单个清单文件
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" \
--input "<SKILL_DIR>/examples/urls.txt" \
--out "C:\Users\zhang\Desktop\1"
示例 4 — 宽表格页面:换 A3 + 400 DPI + 留页边距
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" \
--input "https://example.com/wide-table-page" \
--out "C:\Users\zhang\Desktop\1\bigtable.pdf" \
--page-size A3 --dpi 400 --margin-mm 10
示例 5 — 先试跑看看会处理哪些(不联网)
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" --input "D:\待归档\标准链接" --out "C:\Users\zhang\Desktop\1" --dry-run
示例 6 — 大批量先跑 3 条验证效果,再全量
"<PYTHON>" "<SKILL_DIR>/scripts/webpage_to_pdf.py" -i "D:\待归档\标准链接" -o "C:\Users\zhang\Desktop\1" --limit 3
主代理执行建议
- 先确认输入形态:是单个 URL、清单文件还是文件夹。拿不准就先跑
--dry-run列出 URL 给用户确认。 - 大批量(>20 条)先
--limit 3试跑,确认版式/清晰度没问题再全量,避免白跑。 - 页面渲染不全时(内容被截断、图表空):调大
--viewport-width 1440或--wait-ms 5000重跑。 - 想减小体积(实测,A4 / 300 DPI / 2 页的标准详情页):
- 首选 降 DPI:300 → 200 DPI 体积约降一半,日常打印几乎看不出差别。
- 优先保持png:纯文字页面 PNG 反而更小(实测 PNG 1.17MB vs JPEG q88 1.92MB),且无损。
- 只有页面是大量照片 / 渐变底色 / 彩色图表时,才切--format jpeg --quality 85。 - 收尾:用
present_files打开生成的 PDF(批量时打开报告 CSV + 若干成品)让用户确认。
输出说明
- 文件名优先级:页面
<h1>→ 文档流中第一个标题(很多站点正文标题是 h2~h4,h1 是空的,这层很关键)→<title>,并做 Windows 非法字符清洗。 - 批量时若多个页面标题相同(例如站点所有页
<title>都一样),会自动追加 URL 短标识区分(如?id=的尾段),不会互相覆盖成-2。 - 每份 PDF 内嵌元数据:标题=页面标题,作者=浅见,主题=规格说明。
- 批量结束会在输出目录生成
_report.csv:来源文件 / 来源子目录 / URL / 页面标题 / 输出PDF / 规格 / 页数 / 状态 / 备注。
实现要点(维护时看)
scripts/capture_batch.js:Node + CDP。整批只启动一个 Edge 实例逐条navigate,先按 1280×900 滚动一遍触发懒加载,再按deviceScaleFactor = 目标像素宽 / 内容宽全页截图(captureBeyondViewport),输出宽度恒等于目标像素宽。scripts/webpage_to_pdf.py:解析输入(URL/文件/文件夹)→ 调截图 → 逐行墨量分页 → reportlab 落版。- 分页判空必须用绝对 0 墨量,不能用"最大墨量的百分比"——整幅深色横条会把
max拉满,导致阈值失效、在文字行中间下刀。 - 分页时在理想位置上下 400px 内找整行纯白的切点,找不到才退回理想位置。
已知限制
- 输出为位图 PDF(非矢量),文字不可选中复制;如需可检索文本,需另做 OCR。
- 需要登录或有强反爬的页面,无头浏览器可能拿到的是登录页/验证页——
--dry-run无解,需人工确认或改用带 Cookie 的方案。 - 页面若超过约 1.4 亿像素(例如 600 DPI + 超长页面),会自动降采样以避免浏览器崩溃。