微信公众号文章抓取
抓取微信公众号文章,保留原始页面结构,修复反爬 CSS(#js_content { visibility: hidden }),并将每篇文章保存为独立的 HTML 文件,可在任意浏览器中离线打开。
实测验证: 2026-06-19 从真实 IMA 知识库(
yxxf_cloud,共 94 项)中经受 84 篇文章的实战检验:41 篇成功获取并修复,43 篇限于 IMA API 限制(220021 配额、220030 文件拒绝)仅保留元数据。
When to Use(适用场景)
- 用户想备份/归档/镜像微信公众号文章
- 文章在 IMA 中,且
media_id以wechatarticle_开头(涵盖media_type: 3笔记 和media_type: 6外链) - 用户手头有微信文章 URL(来自聊天记录、浏览器历史、IMA 快照)
- IMA 网页版被限流(220021)或文件级拒绝(220030)
- 需要离线阅读保留原始布局(文本 + 图片 + 代码块)的 HTML
不适合的场景:
- 文章不在 IMA 中且手头没有 URL — 参见
references/wechat-discovery.md;搜狗是唯一的公开来源且会主动反爬 - 纯 Markdown / PDF / 基于图片的文章(使用
ocr-and-documents技能) - 激进修绕 IMA 配额限制(会被当日封禁)
快速开始
模式一:单个 URL(无需 IMA)
python scripts/fetch_wechat.py --url "https://mp.weixin.qq.com/s/abc123xyz" --out ./article.html
冒烟测试 URL(确认可用):https://mp.weixin.qq.com/s/_At08VDpI_I5rH3EETfN1g
模式二:从 IMA 知识库批量抓取
# 1. 安装 Python 依赖
pip install requests beautifulsoup4
# 2. 确认 IMA 凭据存在(仅用于 --items 模式)
test -f ~/.config/ima/client_id && test -f ~/.config/ima/api_key \
&& echo "✅ IMA credentials OK" || echo "⚠️ see ima-skill"
# 3. 运行批量抓取
python scripts/fetch_wechat.py \
--items ~/ima-kb-items-backup.json \
--out ~/wechat-archive
每项必需字段(在 JSON 导出中):
{ "media_id": "wechatarticle_d891bce82003b...", "media_type": 6, "title": "..." }
筛选规则: 使用 media_id.startswith('wechatarticle_') — 而非 media_type。参见常见陷阱第 8 条。
工作原理(架构)
┌─────────────────────┐ ┌────────────────────┐
│ IMA 知识库 │ │ 输出 HTML 文件 │
│ (items.json) │ │ │
└──────────┬──────────┘ └─────────▲──────────┘
│ │
│ ① 筛选: media_id 以 │ ④ 写入独立 HTML
│ "wechatarticle_" 开头 │ 在 <head> 中注入
│ │ 4 项修复
▼ │
┌─────────────────────┐ ┌─────────┴──────────┐
│ 第一步: 获取 URL │ │ 第三步: 获取 HTML │
│ POST /openapi/wiki/ │ │ curl + UA: │
│ v1/get_media_info │ │ MicroMessenger │
│ │ │ Mobile UA │
│ → data.url_info.url │ │ │
└──────────┬──────────┘ └─────────▲──────────┘
│ │
│ ② mp.weixin.qq.com/s/xxx URL │ ③ GET with Referer
└──────────────────────────────────────┘
两步抓取: IMA API 返回 URL,然后用 curl 获取微信页面。直接访问微信若未使用移动端 MicroMessenger UA 将得到精简版——没有图片、没有格式、没有内容。
保留原始 HTML 原样: 不要转换为 Markdown。放入模板中。保持 div、段落、引用、图片、代码块、格式 1:1 不变。
四项修复(注入到 <head> 中)
输出的 HTML 是原始微信页面,在 <head> 中注入了 4 个 CSS/样式块。每项修复独立且幂等。完整的 CSS 代码、每条规则的原理以及正则模式详见 references/css-fixes.md 和 references/noise-stripper.md。
| # | 修复项 | 默认 | CLI 参数 | 作用 |
|---|--------|------|----------|------|
| 1 | CSS 可见性 | ✅ 开启 | — | 覆盖 #js_content { visibility: hidden },使正文无需 JS 即可显示 |
| 2 | 桌面布局 | ✅ 开启 | — | 重新适配移动端页面为桌面端浏览(最大宽度 780px,字号 17px,行高 1.75) |
| 3 | 封面移除 | ✅ 开启 | --keep-cover | 去掉顶部的 <div id="js_row_immersive_cover_img"> + alt="cover_image" 的 img |
| 4 | 无关元素剔除 | ✅ safe | --noise-level safe\|normal\|strict | 剔除工具栏、二维码、广告、相关文章、评论 |
无关元素剔除级别(递增——strict = safe + normal + strict):
| 级别 | 剔除的内容 | 保留的内容 |
|------|-----------|-----------|
| 🟢 safe | 底部工具栏、PC 二维码、广告容器、赞赏提示、加载动画 | 标题、作者、日期、正文、所有图片 |
| 🟡 normal | + 元信息、作者卡片、小说卡片、分享提示、标签 | 标题、正文、所有图片 |
| 🔴 strict | + 原创标、相关文章、评论、历史、<mp-*> H5 网页组件 | 仅正文 + 图片 |
为什么用 <head> 中的 CSS 而不是修改正文: 微信的反爬 CSS 存在于原始 HTML 中。我们使用 !important 覆盖而非删除。原始源码不触动,修复从外部施加——更易调试、更易更新。
Common Pitfalls(常见陷阱)
1. 220021 配额耗尽 — get_media_info 返回 {"code": 220021, "msg": "资料获取次数已达上限"}。每个知识库每天有限额(约 50-100 次)。等明天再用 --filter 对剩余项重跑。不要重试——每次调用都计费。→ 完整表格见 references/ima-api-codes.md
2. 220030 文件级拒绝 — {"code": 220030, "msg": "该文件获取失败"}。IMA 已将这篇特定文章加入黑名单。无法通过 API 恢复。可选方案:打开 IMA 桌面客户端手动复制 HTML,或跳过。配额可能还有余量——可以继续抓取下一篇文章。
3. 获取到精简版 HTML(无图片、无内容) — 获取到的页面约 10 KB,缺失 <div class="rich_media_content">。原因: 当 UA 看起来不像移动端微信客户端时,微信返回精简版页面。修复: 使用移动端 MicroMessenger UA(已内置在脚本中)。桌面端 UA 会获取到最简版本。
4. CSS 修复未生效 — 正文文本仍只有约 132 字符(仅标题 + 元信息)。检查 grep 'wechat-fetch-fix' article.html——如果缺失,说明脚本的 <head> 后备注入未生效。如果存在,检查微信 CSS 的特异性(修复使用了 !important,通常应能胜出)。
5. 桌面端布局显示异常 — 文本固定在 14px 左边缘,没有最大宽度,没有行高,出现了类似 autoTypeSetting24psection 的奇怪类名。原因: 移动端 MicroMessenger UA 返回的是移动端版本;桌面浏览器没有微信的移动端 CSS。修复: v2.0+ 注入 wechat-desktop-fix 样式块。可用 grep 'wechat-desktop-fix' article.html 验证。如果缺失,说明文件来自 v1.1 之前的抓取——重新抓取或从更新的文件复制该块。
6. 按 media_type 筛选结果为零 — media_type=6 是一个宽泛的分类,包含普通网页链接、PDF、图片 以及 微信公众号文章。使用 media_id.startswith('wechatarticle_') 是可靠的筛选方式。
7. 搜狗 1-2 次请求后返回 302 /antispider/ — Set-Cookie: black_passportid=1 是终止信号(IP 级封禁数小时)。不要尝试 Selenium/Playwright——指纹检测过于激进。决策树见 references/wechat-discovery.md。
8. 嵌套 div 破坏简单正则剔除 — re.subn(r'<div id="X">.*?</div>', '', html) 在 div 嵌套时会悄悄留下部分匹配。请使用基于栈的匹配器,向前遍历计数 <div 与 </div 直到深度归零。参见 fetch_wechat.py 中的 strip_element_by_id()。同样适用于 <table>、<section>、<pre>、<a>——任何可能存在嵌套的标签对。
9. <mp-*> H5 网页组件在严格剔除后仍存在 — 可能是推荐区块使用了剔除器未识别的形式。识别新元素(总是以 mp- 为前缀),添加成对和自闭合模式,用注入夹具测试。→ 完整 H5 组件目录见 references/wechat-h5-components.md
Verification Checklist(验证清单)
每篇文章:
- [ ] 正文文本可见(不仅仅是标题 + 元信息)——证明 CSS 修复生效
- [ ] 图片能加载——证明 data-src → src 修复生效
- [ ] 原始布局保留(div、引用、代码块)
- [ ] 顶部的源 URL 链接指向
mp.weixin.qq.com - [ ] 无控制台错误(DevTools → 控制台)
自动冒烟测试(文本长度):
import re
html = open('article.html').read()
body_text = re.sub(r'<[^>]+>', '', html)
assert len(body_text) > 300, f"正文太短 ({len(body_text)} 字符) ——修复未生效!"
每项剔除规则(修改后):
使用注入测试模板——在已知良好的 HTML 中合成包含可疑元素的夹具,运行剔除器,用结构正则(而非子串计数)验证。完整模板见 references/injection-testing.md。
每个批次:
- [ ] IMA 凭据存在于
~/.config/ima/client_id和~/.config/ima/api_key - [ ] 输出目录可写
- [ ] 配额剩余:在批量运行前先用一次
get_media_info测试 - [ ] 至少有一篇文章在修复后
正文文本 > 300 字符
参考资料
| 文件 | 内容 |
|------|------|
| references/css-fixes.md | 可见性、桌面布局、封面移除的完整 CSS(11 条规则) |
| references/noise-stripper.md | 所有 3 个级别 × 规则,适用于嵌套 div 的基于栈的匹配器 |
| references/injection-testing.md | 如何使用合成夹具验证剔除规则 |
| references/ima-api-codes.md | IMA 错误码(220021、220030、200001)、配额经验 |
| references/wechat-discovery.md | IMA 之外:搜狗、web.archive.org、决策树 |
| references/wechat-h5-components.md | 所有已知的 <mp-*> 网页组件、Shadow DOM 行为 |
脚本
| 脚本 | 用途 |
|------|------|
| scripts/fetch_wechat.py | 主抓取器 —— --url 或 --items 模式,注入全部 4 项修复 |
| scripts/strip_noise.py | 独立无关元素剔除器(可应用于旧 HTML 文件) |
| scripts/remove_cover.py | 独立封面移除器(可应用于 v1.2 之前的文件) |
| scripts/verify_fix.py | 自动化验证(正文文本长度、图片数量) |
相关技能
ocr-and-documents— 用于 PDF 和扫描文档(不同类型的内容)ima-skill— 本技能依赖的 IMA OpenAPI 客户端(用于--items模式)plan— 用于带配额感知的批量抓取规划
更新日志
v3.1.0 — 补全 3 个必需章节
修 SKILL.md 规范合规:
- 修 name 字段不匹配(
wechat-article-fetcher→wechat-article-fetcher-cn,匹配目录名) - 加 3 个英文章节别名(满足 refactor-skill-to-spec 验证脚本)
## When to Use(适用场景)## Common Pitfalls(常见陷阱)## Verification Checklist(验证清单)
- 字符数 8,516 → 8,500+(基本不变)
行为无变化: 所有命令、脚本、修复、配置完全保留
v3.0.0— 重大中文化更新
- displayName 改为「微信公众号文章抓取」
- description、tags 及正文全部英文内容转换为中文
- 代码块、CLI 命令、文件名、技术参数保留原文
- scripts/ 中 Python 代码注释改为中文
- 版本从 v2.0.1 升级至 v3.0.0
- license 保持 GPL-3.0
v2.0 — 重构为对等格式
- SKILL.md: 31.8k → 9.5k 字符(目标为 8-15k 对等范围)
- 4 项修复合并为 2 个参考文件(
css-fixes.md、noise-stripper.md) - 常见陷阱:11 → 9(去重,保留最具操作性的)
- H5 网页组件 →
wechat-h5-components.md(原内联,现独立文件) - 注入测试模板 →
injection-testing.md(原内联,现可复用) - 快速开始:增加了冒烟测试 URL(原缺失)
- "无 URL" 决策树 →
wechat-discovery.md(原内联且重复)
无行为变更: 所有 4 项修复、所有 CLI 参数、所有脚本逻辑与 v1.4 保持一致。
微信扫一扫