公众号文章抓取与归档(wechat-archive)
把微信公众号文章批量抓下来,存成「一篇一个 Markdown 文件」的本地归档。
三条铁律:
- 只存文字,不下载图片——图片保留为
链接形式。 - 抓完先识别正文——广告、文末预告、评论区、关联推荐、引导关注、页面脚部全部剔除。
- 命名固定:
文章名-YYYY-MM-DD.md,UTF-8 编码;日期取不到时写unknown-date。
特性:
- 零第三方依赖,纯 Python 标准库(Python 3.7+ 即可)。
- 断点续跑:批量每完成一篇写入
_state.json,中断重跑自动跳过已完成。 - 独立 5 项验证脚本:命名 / 完整性 / 乱码 / 交叉污染 / 计数。
- 跨平台:Windows、macOS、Linux、Termux(安卓)同一套脚本。
架构:两阶段设计
微信文章列表页需要登录态 + JS 渲染,纯 HTTP 抓不动,所以拆成两阶段:
Phase 1 采集索引(浏览器/人工)→ _index.json 或 urls.txt
Phase 2 逐篇抓取(纯 HTTP 脚本)→ *.md + _manifest.json + _state.json
Phase 3 结果验证(独立脚本) → _verify_report.json
Agent 只做调度,脚本干重活:批量抓取几百篇时,Agent 只读 _manifest.json 和 _verify_report.json 两个摘要文件,不逐篇读正文。
退出码语义(调用方必须按此判断,不看 stdout):
0= 全部成功2= 部分成功(有失败篇目或警告)→ 读 manifest 看哪篇失败1= 完全失败(URL 无效 / 被拦截 / 正文为空 / 参数错误)
反限流: 批量模式每篇间隔 1.5 秒。连续失败 ≥3 篇立即停手,等 10 分钟或换网络出口再跑。
自纠错: 清洗后正文若短于原块 30%,判定清洗过度,回退用原始内容(宁多勿缺,广告残留可后处理,正文丢了补不回来)。
安装
前置条件
- Python 3.7+,无需安装任何第三方包。
- 网络可直连
mp.weixin.qq.com(家宽/手机流量直连效果最好;数据中心/代理 IP 被微信拦截的概率更高)。
各平台安装 Python:
| 平台 | 命令 |
|------|------|
| Windows | winget install Python.Python.3.12(或官网安装包) |
| macOS | brew install python |
| Linux | 系统自带或 sudo apt install python3 |
| Termux | pkg install -y python;首次写入共享存储先 termux-setup-storage;长任务前 termux-wake-lock |
目录布局
把本仓库的 scripts/ 目录放到任意工作位置,例如:
~/wechat-archive/scripts/ ← 三个脚本
~/wechat-archive/out/ ← 抓取输出目录(可任意自定义)
Agent 集成方式(通用):把本文件(SKILL.md)放入 agent 的 skills 目录,或将其「Agent 职责边界」一节浓缩进 agent 的系统提示 / 规则文件(如 AGENTS.md)。触发词建议:抓取公众号、归档文章、公众号提取。
用法
OUT=~/wechat-archive/out # 输出目录(Windows 用 $env:OUT;或直接写绝对路径)
# 单篇
python scripts/fetch_article.py "https://mp.weixin.qq.com/s/XXXXX" --output $OUT
# 批量 A:JSON 索引(有采集能力时用 _index.json)
python scripts/fetch_article.py --batch $OUT/_index.json --output $OUT
# 批量 B:urls.txt(每行一个 URL,可选 TAB 追加标题/日期)
# 格式:<url> 或 <url>\t标题\tYYYY-MM-DD
python scripts/fetch_article.py --urls urls.txt --output $OUT
# 验证(5 项:命名/完整性/乱码/交叉污染/计数)
python scripts/verify.py --output $OUT
可选环境变量 WX_UA:覆盖默认 User-Agent(一般不用动)。
Phase 1:采集 URL 清单(三选一)
- 人工收集(推荐起步):微信里打开公众号 → 历史文章 → 每篇右上角「复制链接」→ 粘进
urls.txt。适合 <100 篇,抓取/验证全自动。 - 浏览器控制台:
crawl_index.py生成可执行的 JS 采集脚本 + 指令文件,在能登录列表页的浏览器里跑,输出 JSON 存为_raw_articles.json,再跑:过滤生成python scripts/crawl_index.py --url <列表页URL> --output <dir>_index.json(支持--max/--from/--to/--keyword)。 - 只要单篇 URL 收集流程:跳过列表页采集,架构不变。
列表页 URL(
profile_ext带__biz参数)通常几分钟过期且需登录态,不要尝试纯 HTTP 硬抓列表页——这正是两阶段设计存在的原因。
Agent 职责边界
- 收到文章 URL(或 urls.txt)→ 确保输出目录存在 → 跑
fetch_article.py→ 跑verify.py→ 只读_manifest.json和_verify_report.json汇报结果。 - 用户没指定输出目录 → 先问,不自作主张选目录。
- 验证 FAIL → 读 report 里 issues 列表定位到具体文件,如实汇报,不隐瞒。
- 禁止把整篇抓到的正文读进模型上下文(浪费 token 且无必要)。
输出文件说明
| 文件 | 说明 |
|------|------|
| 文章名-YYYY-MM-DD.md | 正文归档,含 frontmatter:title / account / author / date / source / fetched_at |
| _manifest.json | 批量抓取清单(done/failed/skipped + 每篇状态) |
| _state.json | 断点续跑状态(done/failed 列表) |
| _index.json | Phase 1 索引(account + articles 数组) |
| _verify_report.json | 5 项验证报告(overall: PASS/WARN/FAIL) |
错误恢复速查
| 症状 | 根因 | 处置 |
|------|------|------|
| blocked_by_wechat | 触发验证页/限流 | 停 10 分钟;换网络出口(Wi-Fi↔流量);降低批量节奏 |
| content_empty / markdown_too_short | 链接失效或页面改版 | 记录到 manifest,人工开链接确认 |
| fetch_failed | 网络/域名解析失败 | 检查网络权限;换流量重试 |
| 验证报告 pollution WARN | A 文末出现 B 标题 | 大概率文末关联推荐没滤净,人工核对涉事文件 |
| 中断后续跑 | _state.json 已记录进度 | 直接重跑同一命令,自动跳过 done |
验收自测清单
python -m py_compile scripts/*.py(Termux 用find scripts -name '*.py' -exec python -m py_compile {} \;)全部通过。- 用任意一篇真实公众号文章跑单篇:exit=0,目录里出现
文章名-YYYY-MM-DD.md,frontmatter 含 title/account/date/source。 - 故意混一条坏链接跑
--urls:exit=2,_manifest.json中好链接 ok、坏链接 failed。 python scripts/verify.py --output $OUT:exit=0 或 2(pollution 仅 WARN 可接受),_verify_report.json生成。- 重跑同一批量命令:全部 skipped(断点续跑生效)。
多平台适配说明
| 平台 | 差异点 |
|------|--------|
| Windows | 直接 python scripts/xxx.py;路径用反斜杠或正斜杠均可 |
| macOS / Linux | python3 scripts/xxx.py |
| Termux (安卓) | pkg install python;共享存储需 termux-setup-storage;长任务 termux-wake-lock;终端默认 UTF-8 |
| 无 Agent 环境 | 脚本独立可跑,不依赖任何 agent 框架;Agent 部分仅影响 Phase 1 的自动化程度 |
跨平台一致性:脚本仅用 Python 标准库(urllib、re、json、html、pathlib 风格 os.path),状态与控制台输出一律 ASCII([OK] / PASS / FAIL),中文内容只出现在文件内部(UTF-8),避免任何终端编码问题。
License
Apache-2.0。
Scan to join WeChat group