如何批量下载微信公众号文章并保留 100% 原版式:wechat-article-exporter 实战指南
如何批量下载微信公众号文章并保留 100% 原版式:wechat-article-exporter 实战指南
【免费下载链接】wechat-article-exporter一款在线的 微信公众号文章批量下载 工具,支持导出阅读量与评论数据,无需搭建任何环境,可通过 在线网站 使用,支持 docker 私有化部署和 Cloudflare 部署。 支持下载各种文件格式,其中 HTML 格式可100%还原文章排版与样式。项目地址: https://gitcode.com/gh_mirrors/we/wechat-article-exporter
批量下载微信公众号文章、还原原版式排版、导出阅读量与评论数据,还能用 Docker 或 Cloudflare 一键私有化部署——这套能力都装在wechat-article-exporter里。本文不堆代码,按"你想干一件什么事"的顺序,把它的抓取原理、数据流向、导出机制和三种部署方式讲清楚,让你读完就能判断它值不值得托管、又该怎么跑起来。
一次扫码,为什么能一直抓别人的文章
核心机制是一句话:你扫自己的公众号登录一次,它就借你这个合法身份,去调公众号后台的"搜索并引用其他公众号文章"接口。
微信公众平台在"写文章"流程里允许运营者搜索别的公众号历史文章并引用,背后就是searchbiz(按关键字搜公众号)和appmsgpublish(按公众号fakeid分页拉历史文章)这两个接口。本项目把它们程序化了:你用微信扫一个二维码登录自己的公众号(登录流程见 server/api/web/login/),服务端把微信返回的set-cookie存进自己的 CookieStore,再给浏览器下发一个唯一的auth-key。之后所有对微信平台的请求都由 server/utils/proxy-request.ts 统一转发——它原样带上Referer、Origin和一个伪装成浏览器管理端的User-Agent,并把 cookie 存回、回传。
这里有个容易被忽略但很关键的细节:登录态是有保质期的。cookie 在 KV 存储里只保留 4 天(expirationTtl: 60 * 60 * 24 * 4,见 server/kv/cookie.ts),到期就得重新扫码。接口返回200003时程序会判定"会话过期"并清空当前登录账号(见 apis/index.ts)。所以"扫一次码"不是"扫一次管一年",而是"扫一次、用四天"。
⚠️ 客观说一句:这是借用你本人账号的会话去调接口,是否合规取决于微信平台条款,本项目本身不做背书,请自行评估。
从"找到一个号"到"拿到一篇正文"的数据路径
登录只是拿到钥匙,真正干活的是"搜索 → 列表 → 正文"这条流水线,它分三步把数据搬进你的浏览器本地。
第一步是搜索公众号。前端调searchbiz按关键字返回公众号列表,你选中一个添加为管理对象,程序随即自动同步它的第一页文章。注意公众号的"翻页结束"判断和普通文章不一样:只有当第一页结果就少于 5 个、或后续页彻底为空时才算抓完(见 apis/index.ts 的getAccountList)。
第二步是分页拉文章列表。appmsgpublish按fakeid一页 20 条地返回历史文章(ARTICLE_LIST_PAGE_SIZE = 20,见 config/index.ts),begin从 0 开始递增,当某次返回 0 条就表示加载完毕。每次拉到的列表都会写进本地 IndexedDB 缓存(updateArticleCache),这样下次不用重复请求接口。列表页基于 ag-grid 实现,支持按作者、标题、发布时间、原创标识、所属合集等维度过滤。
第三步是抓正文 HTML。对每篇mp.weixin.qq.com/s/xxx链接,程序通过代理节点抓取正文并写入缓存。这里有个贴心的修复:单篇链接本身推不出公众号fakeid,下载器会先把 HTML 抓下来、从window.cgiDataNew里解析出真实的bizuin再回填(fixSingleFakeidTask,见 utils/download/Downloader.ts),所以你在单篇页面直接粘链接也能下。
抓到正文后,程序还会做状态校验:已下架/作者删除的文章、内容违规的页面都会有专门的处理与标记(仓库samples/目录里就存了"作者已删除""内容违规"等样例页),避免把无效内容当成有效正文存进去。消息类型也不限于普通图文——ITEM_SHOW_TYPE映射覆盖了普通图文(0)、视频分享(5)、音乐分享(6)、图片分享(8)、文本分享(10)、文章分享(11)、短文(17)等(见 config/index.ts),意味着目标号发的是图片分享或文本分享时同样能正常导出。
正文只是骨架:阅读量、评论为什么必须抓包
列表和正文能直接拿到,但阅读量、点赞、分享、留言这些数据藏在需要额外凭证的接口里,这就是项目里"credentials 机制"存在的原因。
credentials 是一组微信接口鉴权参数,包含biz(公众号标识)、uin(用户标识)、key、pass_ticket、wap_sid2、appmsg_token和完整cookie(字段定义见 types/credential.d.ts)。它们不是登录扫码能拿到的,得靠抓包:页面提供"抓取 Credentials"面板(components/global/CredentialsDialog.vue),支持两种抓包方式——配套监听程序,或直接用仓库自带的 mitmproxy 插件:
mitmdump -s credential.py -q把手机代理指到这个抓包端点、打开微信阅读一篇目标文章,插件就会捕获并回传上面那组参数。拿到后,下载器会在抓取阅读量时把pass_ticket和wap_sid2拼进 cookie 重新请求一遍文章 HTML,再从window.cgiDataNew的appmsg_bar_data里解析出read_num(阅读)、old_like_count(赞)、share_count(分享)等数值写入元数据缓存。评论与评论回复则走appmsg_comment接口分页拉取(每页 100 条,靠buffer+continue_flag翻页),并发会被强制降到 2 以避开风控。
⏰ 两个限制要心里有数:key默认按25 分钟管理(CREDENTIAL_LIVE_MINUTES = 25),过期就得重新抓包;付费文章的完整正文也必须带 credentials 才能取到(is_pay_subscribe === 1时自动切换为带凭证请求)。
六种导出里,HTML 为什么能做到 100% 还原
拿到正文后,导出由 utils/download/Exporter.ts 统一调度,支持 html / json / excel / txt / markdown / docx 六种格式(外加 PDF)。它们的实现差异很大,用一个表看清楚:
| 格式 | 实现方式 | 还原度 / 备注 |
|---|---|---|
| html | 本地化资源 + 重写 DOM | 打包图片与样式,100% 还原排版 |
| excel | exceljs 生成 .xlsx | 含阅读/点赞/原创/合集等列 |
| json | 序列化整篇文章对象 | 可含正文与留言 |
| txt / markdown | 渲染后转纯文本 / Turndown | 去样式、去广告 |
| docx | 浏览器端 html-docx-js | 适合二次编辑 |
HTML 这条管线最复杂,分三步。先提取资源:解析已缓存正文,收集所有<img>、<link rel="stylesheet">以及 CSS 里background/background-image: url(...)三类远程地址。再并发下载这些图片与样式文件写入资源缓存。最后本地化重写:把远程路径替换成./assets/uuid.ext,同时删掉顶部广告、底部推荐、二维码和script等无用 DOM,移除#js_content上隐藏正文的style,从oriCreateTime渲染真实发布时间、渲染 IP 属地与"标题已修改"标记,把卡券、语音、视频、小程序等特殊元素降级为[图片]、[视频]、[小程序]等文本占位,并可选地渲染阅读量与留言。
最终每篇文章是一个index.html+assets/目录,双击即可离线浏览,排版与原文一致。Markdown 则走 Turndown(ATX 标题、-列表、fenced 代码块),并显式移除style/script/link等标签,避免 CSS 与 data URI 图标混进正文。
导出目录名支持模板变量,例如${YYYY}-${MM}-${DD}_${title},可替换${account}、${title}、${aid}、${author}及年月日时分(exportDirName逐一替换,见 utils/download/Exporter.ts),无法用于文件名的字符会被过滤。批量下载合集时,程序先逐篇抓正文,再用 JSZip 按"日期 + 标题"目录打包成 zip(见 composables/useBatchDownload.ts)。
并发、代理、重试:大批量抓取怎么不被风控
单次抓取靠代理、批量抓取靠调度。所有下载任务统一由 utils/download/BaseDownloader.ts 管理,采用 Promise.race 并发模型:维护一个进行中的任务集合,在不超并发上限且队列非空时持续启动新任务。默认参数如下(见 utils/download/constants.ts):
| 参数 | 默认值 | 作用 |
|---|---|---|
| timeout | 20 秒 | 单资源下载超时 |
| maxRetries | 3 次 | 失败重试次数 |
| maxFailures | 3 次 | 连续失败进入冷却的阈值 |
| cooldownPeriod | 30 秒 | 代理冷却时间 |
代理调度由 utils/download/ProxyManager.ts 负责:每次优先选"失败次数最少、最近未使用"的代理;某代理连续失败达到阈值就进 30 秒冷却池;若全部在冷却,则回退到最早使用的那一个并重置计数。下载失败采用指数退避(2^n秒延迟)重试。并发数默认按代理数量自动计算(bestConcurrencyCount),宁可慢一点也不触发风控。
代理来源分两级:优先用你在"设置 → 代理节点"里填的私有代理列表(需以http/https开头,支持自动拼接?url=...,可配Authorization);没配置时回退到内置的公共代理池(config/public-proxy.ts),由 6 个域名 × 16 个二级子域共 96 个节点组成。公共池的额度是有限且需要"抢"的,追求稳定就建议填自己的节点。
三种跑起来的方式:在线、Docker、Cloudflare
如果你只是想试试,直接用在线站点即可,无需装任何东西。想完全掌控数据与请求,就自托管——它有两条路子,差异主要在存储与 PDF 支持。
Docker 私有化是功能最全的方式。仓库提供 Dockerfile,两阶段构建:构建层node:22-alpine锁 Yarn 1.22.22 装依赖并nuxt build,构建时设NITRO_KV_DRIVER=fs、NITRO_KV_BASE=.data/kv,用本地文件系统模拟 KV;运行层node:22-slim通过 apt 装 Chromium、Noto CJK 中文字体与 CA 证书,设PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium(Puppeteer 直接复用系统 Chromium、跳过下载),以非 root 的node用户监听0.0.0.0:3000。构建并运行:
# 构建镜像(版本自动取 package.json) yarn docker:build # 挂载 KV 目录持久化登录态与文章缓存,否则容器重建后登录失效 docker run -d -p 3000:3000 -v /本地目录:/app/.data/kv \ ghcr.io/wechat-article/wechat-article-exporter:<version>正因运行层内嵌了 Puppeteer + Chromium,Docker 是唯一支持 PDF 导出的自托管方式(本地nuxt dev亦可);KV 数据务必挂卷,免得重建容器后登录态丢失。
Cloudflare Workers走的是轻量路线,配置在 wrangler.toml。要点:yarn build:cf用NITRO_PRESET=cloudflare_module+NITRO_KV_DRIVER=cloudflare-kv-binding构建;KV 绑定名固定为KV,与 nuxt.config.ts 中storage.kv.binding保持一致;cookie:*与member:*数据存在 KV 里;构建后wrangler deploy上线。这里有个坑已在配置里规避——Cloudflare 驱动下不能再设base,否则键会变成.data/kv:member:xxx导致读不到数据。同时 nuxt.config.ts 开了experimental.wasm,让 QuickJS-WASM 沙箱在主 Worker 内执行 cgi 脚本,无需 Dynamic Workers,能显著降低成本。
📦 两种部署要分清一件事:Workers 环境没有 Puppeteer,PDF 导出不可用;而公开的会员/限速层(NUXT_PUBLIC_MEMBERSHIP_ENABLED)只作用于官方托管站点,fork 私有部署时membership.enabled默认关闭,无限速,开放 API 的 markdown / text / json 格式也全部放开。
结语与许可
一句话记住它的设计:用"一次扫码换取一个完整归档通道",把抓取、渲染、资源打包全部下沉到浏览器端(File System Access API),服务端只负责转发与存 cookie,从而保持轻量,也让 HTML 导出能做到 100% 还原排版。抓 credentials、维护代理池、跑 QuickJS 沙箱这三件事,分别对应"互动数据""稳定抓取""结构化解析"三条能力线,理解它们就理解了整个项目。
项目采用MIT 许可证,可自由使用、修改与二次分发;作者承诺不利用你扫码的账号做公共爬虫、不存在账号池,你抓到的内容版权归原作者所有,请合理使用。它同时致谢了 Deno Deploy、Cloudflare Workers 的免费托管,以及 WeChat_Article 项目提供的原理思路。若需自行 clone,仓库地址为https://gitcode.com/gh_mirrors/we/wechat-article-exporter。
【免费下载链接】wechat-article-exporter一款在线的 微信公众号文章批量下载 工具,支持导出阅读量与评论数据,无需搭建任何环境,可通过 在线网站 使用,支持 docker 私有化部署和 Cloudflare 部署。 支持下载各种文件格式,其中 HTML 格式可100%还原文章排版与样式。项目地址: https://gitcode.com/gh_mirrors/we/wechat-article-exporter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考