公众号文章转 PDF
把一个微信公众号链接变成一份能直接阅读和归档的 PDF。三步:抓取 → 排版 → (可选)入 ima。
何时使用
- 用户丢来一个
mp.weixin.qq.com/s/...链接,要求转 PDF、导出、存档 - 用户要求把某篇公众号文章保存到 ima 知识库
- 需要把长文(万字级)做成带目录层级、代码块、提示框的中文 PDF
环境准备(首次运行)
PDF 用 PyMuPDF 离线生成,不依赖浏览器(Chrome/Edge 无头模式在本机常被已打开的浏览器实例占住而失败,别走这条路)。
# 创建隔离虚拟环境(只用一次,之后直接复用)
"C:/Users/liebe/.workbuddy/binaries/python/versions/3.13.12/python.exe" -m venv "C:/Users/liebe/.workbuddy/binaries/python/envs/default"
"C:/Users/liebe/.workbuddy/binaries/python/envs/default/Scripts/pip.exe" install pymupdf
# 仅在需要上传到 ima 时安装
"C:/Users/liebe/.workbuddy/binaries/python/envs/default/Scripts/pip.exe" install cos-python-sdk-v5
Python 可执行文件统一用:C:/Users/liebe/.workbuddy/binaries/python/envs/default/Scripts/python.exe
工作流
Step 1 · 抓取原文
用 WebFetch 拉取链接,prompt 明确要求不要总结、直接输出原文:
请完整提取这篇文章的:标题、作者/公众号名称、发布时间、正文全部内容
(保留段落结构和小标题)。不要总结,直接输出原文。
WebFetch 会转 HTML → Markdown,通常够用。若正文被截断或丢失(常见于超长图文),改用浏览器技能或让用户提供复制文本。
抓完后先确认拿到了:标题、作者/公众号、发布时间、正文全文。内容不全就不要往下走。
Step 2 · 写成结构化中间文件 content.txt
这是质量的关键一步。把抓取的内容按标记语法落到 content.txt(UTF-8),每行一条记录:
@@H1 文章主标题
@@META 原作者:X | 公众号:Y | 发布时间:Z
@@SEC SECTION 01
@@H2 一级章节标题
@@H3 1.1 二级小节
@@P 正文段落,支持 <b>加粗</b>
@@LABEL 小标签文字
@@PRE 等宽模板块第一行
@@NOTE 黄色提示框内容
@@LI 列表项(连续多行自动合并成一个 ul)
@@END 结束标记
完整语法与注意事项见 references/content-markup.md——写文件前先读它。
要点速记:
- 代码块/模板用
@@PRE,连续多行每行都加@@PRE前缀;块内空行写@@PRE(裸空行) <b>…</b>是唯一保留的 HTML,会被渲染成粗体;正文里的其他<>会被自动转义- 不要自作聪明精简原文,用户要的是完整归档
Step 3 · 渲染 PDF
cd <工作目录>
"C:/Users/liebe/.workbuddy/binaries/python/envs/default/Scripts/python.exe" \
"C:/Users/liebe/.workbuddy/skills/wechat-article-to-pdf/scripts/build_pdf.py" \
content.txt "输出文件名.pdf" --header "页眉文字"
--header 建议用文章短标题(如 Seedance 2.5 提示词指南),会印在每页左下角,右下角是页码。
脚本会自动:A4 页面 + 52pt 页边距、自动分页、嵌入中文字体(PyMuPDF 内置 china-s,无需外部字体文件)、压缩输出。
Windows 下若输出 PDF 已存在且被占用,先 rm -f 再生成,否则会报权限错误。
Step 4 · 校验内容完整性(必做)
不要只看文件生成成功就交差。用关键词抽查,确认没有内容被吞掉:
"C:/Users/liebe/.workbuddy/binaries/python/envs/default/Scripts/python.exe" -c "
import pymupdf
d = pymupdf.open('输出文件名.pdf')
full = '\n'.join(p.get_text() for p in d)
nospace = ''.join(full.split())
print('pages:', d.page_count, '| chars:', len(full))
for k in ['关键词1','关键词2','最后一节的标题']:
ok = (k in full) or (k.replace(' ', '') in nospace)
print(('OK ' if ok else 'MISS'), k)
"
比对时保留空格版本。PDF 提取文本会在部分字符间插入空格,'CASE A' 直接匹配会假阴性——所以要么用原始文本比对,要么关键词也去掉空格再比。
抽查清单:文章标题、每个章节标题、正文里几个独特的专有名词/模板片段、最后一个章节的收尾内容。
已知坑:@@PRE 多行块解析时,如果块内空行后面紧跟的不是 @@ 起首的行,就属于块内分隔而非块结束——build_pdf.py 已正确处理,别改回"见空行即断块"的写法,那会只保留模板首行。
Step 5 · 存入 ima 知识库(可选,用户要求时才做)
完整流程(含 create_media → COS 上传 → add_knowledge 三步的字段说明)见 references/ima-workflow.md——执行前先读它。
摘要:
mcp__ima-mcp__get_addable_knowledge_base_list列出可入库的知识库,跟用户确认目标(或选唯一/默认那个)mcp__ima-mcp__create_media换取上传凭据(返回 media_id + COS 临时密钥)scripts/upload_cos.py把 PDF 传到 COSmcp__ima-mcp__add_knowledge提交入库
Step 6 · 交付
- 清理临时文件(
_stage*.pdf、预览 png、调试脚本) - 写一份简短的 overview 说明产物位置与页数
- 用 present_files 把 PDF 呈现给用户
排版样式一览
| 标记 | 渲染效果 |
|---|---|
| @@H1 / @@META | 大标题 + 灰色元信息,下方 1.5pt 分割线 |
| @@SEC | 黑底白字全宽分节带(如 SECTION 01) |
| @@H2 / @@H3 | 章节标题(H3 带蓝色左侧竖线) |
| @@LABEL | 蓝底蓝字小标签 |
| @@PRE | 灰底蓝左边框的等宽模板块 |
| @@NOTE | 黄底橙边框提示框 |
| @@LI | 圆点列表 |
| @@END | 居中灰色 END OF GUIDE 收尾 |
想换配色改 scripts/build_pdf.py 里的 CSS 常量即可。
故障排查
| 现象 | 原因 / 解法 |
|---|---|
| Chrome headless 打印无输出、exit code 非零 | 浏览器已被现有会话占用。改走 PyMuPDF(本 skill 的默认路线) |
| PDF 里中文是空白或方块 | CSS 里字体必须是 "china-s",不要用 simsun/msyh 等系统字体名 |
| PermissionError 写 PDF | 目标 PDF 正被 PDF 阅读器打开,先关闭或 rm -f 后重跑 |
| 模板块只剩第一行 | @@PRE 块解析被截断,见 Step 4 的已知坑 |
| 生成的 PDF 缺内容 | 回到 Step 2 检查 content.txt 是否本身就缺失,别在 PDF 环节找原因 |
| OSError: [safe-delete] 操作失败 | 所在环境拦截了 Path.unlink()。build_pdf.py 已把中间文件放进系统临时目录规避,不要在脚本里加回主动删除逻辑 |
| 校验时关键词明明有却报 MISS | PDF 提取文本插了空格,见 Step 4 的比对说明 |
微信扫一扫