Technical Documentation: wechat-article/wechat-article-exporter
ℹ️ Provenance: Hybrid Fusion:
wechat-article/wechat-article-exporter+wechat-article/docs· CodeWiki Reference · Recency: Active (< 180 days)
1. Project Overview & Quickstart (wechat-article/wechat-article-exporter)
wechat-article-exporter
[!WARNING]
本项目已停止维护(2026-07-30)。项目所依赖的微信上游核心接口已被官方关闭,且大概率不会再开放。这是整个项目赖以工作的地基,无法通过修改代码绕过 —— 开箱即用的一键同步公众号文章已不复存在。完整说明见 关于本项目停止维护的说明 #200。
- 仓库不会删除,将转为只读归档。代码、文档与 issue 讨论均留在原处,任何人都可以继续阅读、fork、学习和二次开发。
- 不再接受新的 issue 和 PR,也不会再有新版本,请不要等待「明天可能修好」。
- 在线网站的域名将于 2026-10-30 到期,届时将无法访问,请在此之前导出你需要保留的数据。
- 你已经同步过的文章都还是你的,它们存放在你自己的设备上,不受任何接口影响。
一款在线的 微信公众号文章批量下载 工具,支持导出阅读量与评论数据,无需搭建任何环境,可通过 在线网站 使用(域名 2026-10-30 到期,届时将无法访问),同时也支持 docker 私有化部署和 Cloudflare 部署。
支持下载各种文件格式,其中 HTML 格式可100%还原文章排版与样式。
:books: 如何使用?
该工具的使用教程已移至 文档站点。
:dart: 特性
- 搜索公众号,支持关键字搜索
- 支持导出 html/json/excel/txt/md/docx 格式(html 格式打包了图片和样式文件,能够保证100%还原文章样式)
- 缓存文章列表数据,减少接口请求次数
- 支持文章过滤,包括作者、标题、发布时间、原创标识、所属合集等
- 支持合集下载
- 支持图片分享消息
- 支持视频分享消息
- 支持导出评论、评论回复、阅读量、转发量等数据 (需要抓包获取 credentials 信息,查看操作步骤)
- 支持 Docker 部署
- 支持 Cloudflare 部署
-
开放 API 接口(公开 API 已下线,不再对外提供服务)
「取阅」
同一作者的另一个项目 —— 取阅。
实时监听并推送公众号群发文章。
:heart: 感谢
- 感谢 Deno Deploy、Cloudflare Workers 提供免费托管服务
- 感谢 WeChat_Article 项目提供原理思路
:star: 支持
如果你觉得本项目帮助到了你,请给作者一个免费的 Star,感谢你的支持!
:bulb: 原理
在公众号后台写文章时支持搜索其他公众号的文章功能,以此来实现抓取指定公众号所有文章的目的。
:memo: 许可
MIT
:red_circle: 声明
本程序承诺,不会利用您扫码登录的公众号进行任何形式的私有爬虫,也就是说不存在把你的账号作为公共账号为别人爬取文章的行为,也不存在类似账号池的东西。
您的公众号只会服务于您自己的抓取文章的目的。
通过本程序获取的公众号文章内容,版权归文章原作者所有,请合理使用。若发现侵权行为,请联系我们处理。
:chart_with_upwards_trend: Star 历史
2. Official Technical Reference & Guides (wechat-article/docs)
File: README.md
docs
微信公众号文章系列工具文档
File: CLAUDE.md
CLAUDE.md
项目概述
这是 微信公众号文章下载工具 的文档站点,基于 VitePress 构建。
仓库地址:wechat-article/docs,主工具仓库:wechat-article/wechat-article-exporter。
该工具是一款开源免费的在线工具,支持批量下载微信公众号文章,导出为 Excel/JSON/HTML/TXT/Markdown/Word 等格式。
技术栈
- 静态站点生成器:VitePress (v1.6.3+)
- 前端框架:Vue 3
- 语言:中文 (zh-CN)
- 插件:
vitepress-plugin-lightbox- 图片灯箱效果vitepress-plugin-llms- LLM 友好的 llms.txt 生成medium-zoom- 图片缩放giscus- 基于 GitHub Discussions 的评论系统
项目结构
docs/
├── .vitepress/
│ ├── config.ts # VitePress 配置文件(导航、侧边栏、主题等)
│ ├── theme/
│ │ ├── Layout.vue # 自定义布局(暗色模式切换动画 + medium-zoom)
│ │ ├── Comments.vue # Giscus 评论组件
│ │ └── style.css # 自定义主题样式
│ └── dist/ # 构建产物(已 gitignore)
├── src/ # 文档源文件目录(VitePress srcDir)
│ ├── index.md # 首页
│ ├── faq.md # 常见问题
│ ├── get-started/ # 入门指南
│ ├── advanced/ # 进阶使用
│ ├── tutorials/ # 教程
│ ├── misc/ # 杂项(架构、域名、Features 等)
│ ├── others/ # 其他技术文档
│ ├── assets/ # 图片等静态资源
│ └── public/ # 公共静态文件(favicon、logo)
├── app/ # Docker 部署相关文件(已 gitignore)
├── package.json
└── CLAUDE.md常用命令
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
# 预览构建产物
npm run preview代码规范
- TypeScript 零容忍:遇到任何 TS 错误必须立即修复,无论是否由当前改动引入。不允许使用
@ts-ignore或@ts-expect-error来绕过,除非第三方库确实缺少类型声明 - 所有
.ts和.vue文件必须通过 TypeScript 类型检查
编写规范
- 所有文档内容使用 中文 撰写
- Markdown 图片使用 `` 相对路径引用
- 图片支持灯箱效果(通过 lightbox 插件自动处理)
- 代码块启用行号显示
- 图片启用懒加载
- 页面默认启用 Giscus 评论,可通过 frontmatter
comments: false关闭 - VitePress 的
srcDir设置为src,所有文档页面在src/目录下
Git 规范
- 主分支:
master - 提交信息使用简短描述(参考历史提交风格如 "update")
File: package.json
{
"name": "docs",
"version": "1.0.1",
"repository": "[email protected]:wechat-article/docs.git",
"author": "jock",
"license": "UNLICENSED",
"packageManager": "[email protected]",
"type": "module",
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview"
},
"devDependencies": {
"medium-zoom": "^1.1.0",
"vitepress": "^1.6.4",
"vitepress-plugin-lightbox": "^1.0.3",
"vitepress-plugin-llms": "^1.12.0",
"vue": "^3.5.32"
}
}
File: tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"jsx": "preserve",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/": ["./src/"]
}
},
"include": [
"src//*.ts",
"src//.vue",
".vitepress/**/.ts",
".vitepress/**/*.vue"
],
"exclude": [
"node_modules",
".vitepress/dist",
".vitepress/cache"
]
}
File: src/faq.md
常见问题 (FAQ)
[[toc]]
出现 `200002:invalid args` 如何解决?
该错误通常由以下原因导致,请逐一排查:
1. 登录账号类型不正确
扫码登录时必须选择 公众号 或 服务号,不能使用 小程序 登录:
2. 目标公众号未开启搜索
本工具基于公众号后台的文章搜索接口获取数据,目标公众号必须开启「允许通过名称搜索」选项,否则无法搜索到该公众号:
3. 登录已过期
登录凭证(auth-key)有效期为 4 天,过期后需要重新扫码登录。
登录页面为什么不显示二维码?
这通常出现在私有部署的网站上。微信返回的登录 Cookie 带有 Secure 属性,要求网站必须通过 HTTPS 访问才能正常携带 Cookie。
解决方案:
- 使用
localhost或127.0.0.1访问(不受此限制) - 为私有部署配置 HTTPS 证书,参考 Docker Compose + mkcert 方案
如何获取评论和阅读量数据?
阅读量、点赞数、转发量和评论数据需要通过微信用户身份才能获取,本工具将这些身份信息统称为 Credentials。
获取流程:
- 使用 wxdown-service 程序启动本地代理
- 在微信客户端中打开目标公众号的一篇文章
- 程序自动抓取 Credentials 并同步到网站
::: warning 注意
Credentials 有效期约 25 分钟,过期后需要在微信中重新打开文章页面以刷新。
:::
详细操作请查看 抓取 Credentials。
已加载消息数和消息总数为什么不一致?
首先需要理解 消息 和 文章 的区别:
如上图所示,红框中的是 2 条消息,但每条消息可能包含多篇文章(类似于一期推送中的多篇图文)。
- 已加载消息数:实际从微信接口拉取到的消息数量,该数值是准确的
- 消息总数:微信接口返回的
total_count字段,作为参考值,不一定完全准确
文章下载页面的文章数量为什么不一样?
这是因为默认启用了「隐藏已删除文章」选项。已删除的文章在同步时仍会被获取到,但在文章列表中默认隐藏。
关闭该选项后,两个数字会保持一致:
请求出现 `429` 状态码
429 表示请求过于频繁,通常是公共代理节点的当日额度用完了。
解决方案:
- 搭建自己的 私有代理节点(推荐)
- 等待次日北京时间 8:00 额度自动刷新
目标公众号 Credential 未设置
该提示出现在抓取阅读量或评论数据时,表示对应公众号的 Credential 尚未获取或已过期。
请参考 抓取 Credential 教程获取 Credential 后再进行抓取。
无法打开此文件夹,因为其中含有系统文件
导出时不能选择系统保护的目录(如「下载」、「文档」等系统目录)。请手动创建一个新目录(如桌面上新建一个「导出文件」文件夹),然后选择该目录进行导出。
如何清理数据
工具中的文章数据(除单篇文章下载外)都存储在浏览器本地的 IndexedDB 数据库中,有以下几种清理方式:
方法 1:删除指定公众号数据
在【公众号管理】页面,选择对应的公众号,点击【删除】按钮即可清除该公众号的所有缓存数据。
方法 2:通过开发者工具清除全部数据
适用于需要清除所有公众号数据的情况:
- 按
F12或右键页面选择「检查」,打开浏览器开发者工具 - 切换到「Application」(应用)标签页
- 在左侧展开「IndexedDB」
- 找到网站对应的数据库,点击【删除数据库】
方法 3:通过浏览器设置清除站点数据
此方法会清除该网站的所有本地数据(包括 IndexedDB、Cookie、登录状态等):
- 在 Chrome 地址栏输入
chrome://settings/content/all?searchSubpage=mptext.top并回车 - 找到
down.mptext.top域名 - 点击右侧的垃圾桶图标删除该站点的所有数据
如何修改 Chrome 浏览器 IndexedDB 数据库的存储位置?
由于本工具的所有公众号数据都存储在浏览器的 IndexedDB 中,当数据量较大时,可能会占用系统盘较多空间。可以通过以下两种方式修改 Chrome 的存储位置,将数据迁移到其他磁盘。
::: warning 注意
修改前请先关闭所有 Chrome 进程,并对原有数据做好备份,避免数据丢失。
:::
方法 1:使用 `--user-data-dir` 启动参数(推荐)
通过命令行参数指定一个新的用户数据目录,Chrome 会将所有用户数据(包括 IndexedDB)保存到该目录。
Windows:
右键 Chrome 快捷方式 → 属性 → 在「目标」末尾追加参数:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="D:\ChromeData"macOS:
通过终端启动:
open -a "Google Chrome" --args --user-data-dir="/Volumes/Data/ChromeData"Linux:
google-chrome --user-data-dir="/home/user/ChromeData"::: tip 提示
该方法相当于创建了一个全新的 Chrome 配置文件,原有的书签、扩展、登录信息等都不会同步过来。如需保留原数据,可将原 User Data 目录的内容复制到新目录。
:::
方法 2:使用符号链接(保留原配置)
将 IndexedDB 文件夹移动到其他磁盘,再通过符号链接指回原路径,这样 Chrome 仍然按原路径访问,但实际数据存储在新位置。
Chrome 的 IndexedDB 默认路径:
- Windows:
C:\Users\<用户名>\AppData\Local\Google\Chrome\User Data\Default\IndexedDB - macOS:
~/Library/Application Support/Google/Chrome/Default/IndexedDB - Linux:
~/.config/google-chrome/Default/IndexedDB
Windows(管理员身份运行 CMD):
:: 1. 将原目录移动到新位置
move "C:\Users\<用户名>\AppData\Local\Google\Chrome\User Data\Default\IndexedDB" "D:\ChromeIndexedDB"
:: 2. 创建符号链接
mklink /D "C:\Users\<用户名>\AppData\Local\Google\Chrome\User Data\Default\IndexedDB" "D:\ChromeIndexedDB"macOS / Linux:
# 1. 将原目录移动到新位置
mv ~/Library/Application\ Support/Google/Chrome/Default/IndexedDB /Volumes/Data/ChromeIndexedDB
# 2. 创建符号链接
ln -s /Volumes/Data/ChromeIndexedDB ~/Library/Application\ Support/Google/Chrome/Default/IndexedDB::: warning 注意
- 如果使用了多个 Chrome 用户配置(Profile),需要分别处理对应的
Profile 1、Profile 2等目录 - 确保新的存储位置所在磁盘始终可用,否则 Chrome 将无法读取数据(外接磁盘需常驻连接)
:::
如何检查请求失败原因?
当遇到数据获取失败时,可以通过浏览器开发者工具查看具体的网络请求和错误信息:
第一步:打开开发者工具
通过菜单打开:
或者在网页上右键,选择「检查」(Inspect):
第二步:捕获网络请求
- 切换到「Network」(网络)面板
- 点击清空按钮清除已有记录
- 在页面上进行你的操作(如抓取评论)
- 操作完成后,查看网络面板中的请求和响应内容
如果不确定如何分析,可以将请求详情截图发到 QQ 群中寻求帮助。
抓取或导出失败
大部分抓取和导出失败都是因为当天的公共代理额度用完了,可以自己检查下网站上面的【公共代理】页面,如果发现某个节点已经到了100%,就需要自己手动在【设置】页面的「代理节点」中删掉相关节点的域名。
可参考 https://github.com/wechat-article/wechat-article-exporter/issues/157
File: src/index.md
[Binary file]
File: src/advanced/api.md
API 使用说明
本工具提供 RESTful API 接口,方便第三方程序集成和自动化处理。
认证方式
所有 API 请求需要在 Header 中携带 X-Auth-Key 进行认证:
X-Auth-Key: 你的API密钥API 密钥在登录成功后自动生成,可在网站的 API 页面查看。密钥有效期与登录会话一致(4 天)。
API 端点
基础地址:https://down.mptext.top(或你的私有部署地址)
搜索公众号
GET /api/public/v1/account?keyword=关键词| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| keyword | string | 是 | 公众号名称关键词 |
获取文章列表
GET /api/public/v1/article?fakeid=xxx&begin=0&size=20| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| fakeid | string | 是 | 公众号的 fakeid |
| begin | number | 否 | 起始位置,默认 0 |
| size | number | 否 | 每页数量,默认 20(最大 20) |
| keyword | string | 否 | 文章标题搜索关键词 |
下载文章内容
GET /api/public/v1/download?url=文章URL&format=html| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | 微信文章 URL |
| format | string | 否 | 输出格式:html(默认)/ markdown / text / json |
通过文章 URL 获取公众号信息
GET /api/public/v1/accountbyurl?url=文章URL| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | 微信文章 URL |
验证 API 密钥
GET /api/public/v1/authkey返回 code: 0 表示密钥有效,code: -1 表示已过期。
Postman 在线文档
更详细的 API 文档和示例代码可查看:
https://documenter.getpostman.com/view/1276582/2sB3QDuXUa
在文档页面可选择你的目标编程语言,右侧会自动生成对应的请求代码示例:
File: src/advanced/private-deploy.md
私有部署
::: warning 注意
公共代理节点仅限于以下域名使用:
https://down.mptext.tophttp://localhosthttp://127.0.0.1
如果私有部署后使用其他域名访问,需要 搭建自己的代理节点。
:::
本地运行
适合开发者调试或临时使用,需要 Node.js >= 22 环境。
- 拉取代码
git clone [email protected]:wechat-article/wechat-article-exporter.git
cd wechat-article-exporter- 安装依赖
yarn- 启动开发服务器
yarn dev启动后打开 http://localhost:3000 即可使用。
Docker 运行(推荐)
适合长期使用,无需搭建开发环境。
拉取镜像
docker pull ghcr.io/wechat-article/wechat-article-exporter:latest启动容器
容器暴露端口 3000,内部数据目录为 /app/.data。
::: code-group
docker run -d \
--restart always \
--name wechat-article-exporter \
-e NODE_TLS_REJECT_UNAUTHORIZED=0 \
-p 3000:3000 \
-v ./.data:/app/.data \
ghcr.io/wechat-article/wechat-article-exporter:latestdocker run -d \
--restart always \
--name wechat-article-exporter \
-e NODE_TLS_REJECT_UNAUTHORIZED=0 \
-p 3000:3000 \
-v ./.data:/app/.data \
ghcr.io/wechat-article/wechat-article-exporter:latest:::
::: tip 提示-v ./.data:/app/.data 会将容器内的数据映射到当前目录的 .data 文件夹,确保容器重启后数据不丢失。
:::
启动后打开 http://localhost:3000 即可使用。
docker compose + mkcert 自签名证书
使用`mkcert`生成自签名证书
mkcert的安装及使用可以查看官方文档
# 在系统中安装根证书(只需执行一次)
mkcert -install执行以下代码生成本地 IP 对应的证书文件:
# 这里可以列出你最终访问的ip
mkcert localhost 127.0.0.1 ::1该命令会生成2个pem文件,分别对应证书和密钥文件,如下图所示:
将这两个文件重命名为cert.pem和key.pem,并放在certs目录中。
拷贝`nginx.conf`和`docker-compose.yaml`文件
::: code-group
server {
listen 80;
server_name localhost;
# HTTP 自动重定向到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name localhost;
# SSL 证书路径(根据你实际文件名调整)
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
# 推荐的 SSL 配置(增强安全性)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://app:3000; # 代理到应用容器
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}services:
app:
image: ghcr.io/wechat-article/wechat-article-exporter:latest
restart: always
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
volumes:
# 持久化 KV 数据(防止容器重启丢失)
- .data:/app/.data
nginx:
image: nginx:alpine
container_name: wechat-article-nginx
restart: always
ports:
- "80:80" # HTTP(会自动重定向到 HTTPS)
- "443:443" # HTTPS
volumes:
- ./certs:/etc/nginx/certs:ro # 挂载证书(只读)
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro # 挂载配置
depends_on:
- app:::
将以上所有文件放在一个目录中,比如app目录,最终的目录结构如下:
app
├── certs
│ ├── cert.pem
│ └── key.pem
├── docker-compose.yml
└── nginx.conf在该目录中执行docker compose up -d启动,然后就可以通过https://localhost(或者你的本地ip)访问 https 网站程序了。
Vercel 快速部署
一键部署
点击下方按钮一键部署到 Vercel:
手动部署
Fork 本项目到你的 GitHub 账号
在 Vercel 中导入项目
- 访问 Vercel
- 点击 "Add New..." → "Project"
- 选择你 Fork 的项目仓库
配置项目
- Framework Preset: 选择
Nuxt.js - Root Directory: 保持默认
./ - Build Command: 保持默认或使用
yarn build - Output Directory: 保持默认
.output/public
- Framework Preset: 选择
配置环境变量(可选)
- 在项目设置中添加所需的环境变量(见下方环境变量说明)
点击 "Deploy" 完成部署
环境变量
以下环境变量均为可选项。本地运行时在项目根目录创建 .env 文件即可;Docker 运行时通过 --env-file .env 传入。
数据存储(KV)
登录会话等数据通过 KV 存储持久化,不同部署方式配置不同:
# 本地 / Docker 部署(基于文件系统)
NITRO_KV_DRIVER=fs
NITRO_KV_BASE=.data/kv
# Cloudflare 部署(使用 Cloudflare KV)
# NITRO_KV_DRIVER=cloudflare-kv-binding调试模式
# 调试微信代理请求(仅开发环境生效)
NUXT_DEBUG_MP_REQUEST=trueAG-Grid 企业版授权
# 文章列表使用 AG-Grid 展示,企业版授权可解锁更多功能
NUXT_AGGRID_LICENSE=网站统计(umami)
# https://umami.nuxt.dev/api/configuration
NUXT_UMAMI_ID=
NUXT_UMAMI_HOST=错误监控(Sentry)
# https://docs.sentry.io/platforms/javascript/guides/nuxt/manual-setup/
NUXT_SENTRY_DSN=
NUXT_SENTRY_ORG=
NUXT_SENTRY_PROJECT=
NUXT_SENTRY_AUTH_TOKEN=File: src/advanced/wxdown-service.md
抓取 Credential
Credential 是微信用户的一些 cookie 相关信息,同一个微信对不用公众号有不一样的 Credential,所以每个公众号都需要先抓取 Credential 才能抓取阅读量和评论数据。
wxdown-service 是一个独立运行的控制台程序,用于自动抓取微信公众号的 Credential 数据(阅读量、评论等接口所需的认证信息)。该程序内置了 mitmproxy,无需额外安装依赖。
::: tip 提示
本程序代码开源,项目地址:https://github.com/wechat-article/wxdown-service
:::
工作原理
wxdown-service 通过在本地启动一个 HTTP 代理(mitmproxy),拦截微信客户端访问公众号文章时的请求,从中提取 Credential 参数(biz、uin、key、pass_ticket 等),并自动同步到网站。
下载软件
根据你的操作系统下载对应的版本:
下载地址:https://github.com/wechat-article/wxdown-service/releases
| 平台 | 文件 | 说明 |
|---|---|---|
| Windows | wxdown-service.exe |
可直接运行,无需安装 |
| macOS | 源码 | 需要 Python 环境运行 |
Windows 运行
下载并解压后程序如下图所示:
1. 启动软件
双击 wxdown-service.exe 启动,启动过程中可能需要管理员权限和网络访问权限,请全部允许。
启动成功后如下所示:
2. 安装证书
首次运行需要安装 mitmproxy 的 CA 证书,以便解密 HTTPS 请求。
方式一:命令行安装(推荐)
以管理员身份打开 cmd,执行:
certutil -addstore root %userprofile%\.mitmproxy\mitmproxy-ca-cert.cer执行结果如下:
方式二:手动安装
如果不熟悉命令行,可以在文件管理器中打开 %userprofile%\.mitmproxy\ 目录,找到 mitmproxy-ca-cert.cer 文件:
双击证书文件,按照向导安装:
选择「本地计算机」:
证书存储位置选择「受信任的根证书颁发机构」:
证书安装完成后,软件界面如下:
3. 设置操作系统代理
将系统代理设置为 127.0.0.1:65000:
设置成功后软件界面如下:
macOS 运行
macOS 暂无打包版本,需要从源码运行。
1. 安装依赖并启动
确保已安装 Python 3,然后执行:
# 克隆项目
git clone https://github.com/wechat-article/wxdown-service.git
cd wxdown-service
# 安装依赖
pip install -r requirements.txt
# 启动程序
python main.py2. 安装证书
sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain ~/.mitmproxy/mitmproxy-ca-cert.pem3. 设置系统代理
打开「系统设置」→「网络」→「Wi-Fi」→「代理」,将 HTTP 和 HTTPS 代理都设置为 127.0.0.1:65000:
设置成功后界面如下:
抓取 Credentials
wxdown-service 启动成功后,网站右上角的图标会变成绿色,表示已连接:
点击图标查看面板:
现在,在 电脑端微信客户端 中打开目标公众号的任意一篇文章:
::: warning 注意
必须在微信内置浏览器中打开文章,不能使用系统浏览器。
:::
如果网站上没有显示该公众号信息,点击微信浏览器左上角的 刷新按钮 重新加载文章页面即可。
抓取成功后,就可以在网站上抓取该公众号的阅读量和评论数据了。
注意事项
| 事项 | 说明 |
|---|---|
| 有效期 | Credentials 有效期约 25 分钟,过期后无法继续抓取阅读量和评论 |
| 数据缓存 | 抓取成功后数据会缓存在浏览器中,此时可以关闭 wxdown-service 程序 |
| 刷新方式 | 过期后只需在微信中重新打开/刷新一篇文章即可自动获取新的 Credentials |
| 持续抓取 | 如需长时间持续抓取,请保持 wxdown-service 运行且系统代理不要关闭 |
常见问题
Q: 证书安装后仍然无法抓取?
- 确认系统代理已正确设置为
127.0.0.1:65000 - 重启微信客户端后再试
- Windows 用户确认证书安装到了「受信任的根证书颁发机构」
Q: macOS 提示权限不足?
- 证书安装命令需要
sudo权限 - 如遇安全提示,在「系统设置」→「隐私与安全性」中允许运行
Q: 批量下载过程中 Credentials 过期了怎么办?
- 已下载的数据不受影响
- 在微信中重新打开一篇该公众号的文章刷新 Credentials,然后继续抓取即可
File: src/get-started/introduction.md
[Binary file]
File: src/get-started/private-proxy.md
搭建私有代理节点
私有代理节点部署在各种 serverless 环境(都是国外的平台,所以在使用时最好配合翻墙软件),可以加速资源的下载进度。但由于目前节点代码并没有进行身份验证,所以请避免部署后的节点地址遭到泄露。如若发现节点流量异常,请销毁节点重新进行搭建(即更换节点地址)。
如果使用的 Cloudflare 部署的节点,也可以使用自定义规则限制可访问的域名,详情见下面的安全设置。
::: details 点我查看节点代码
const UA =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36";
const PRESETS = {
mp: {
Referer: "https://mp.weixin.qq.com",
},
};
function error(msg, status = 400) {
return new Response(msg, {
status: status,
});
}
/**
* 解析请求
*/
async function parseRequest(req) {
const origin = req.headers.get("origin") || '*';
// 代理目标的请求参数
let targetURL = '';
let targetMethod = "GET";
let targetBody = '';
let targetHeaders = {};
let preset = '';
const method = req.method.toLowerCase();
if (method === "get") {
// GET
// ?url=${encodeURIComponent(https://example.com?a=b)}&method=GET&headers=${encodeURIComponent(JSON.stringify(headers))}
const {searchParams} = new URL(req.url);
if (searchParams.has("url")) {
targetURL = decodeURIComponent(searchParams.get("url"));
}
if (searchParams.has("method")) {
targetMethod = searchParams.get("method");
}
if (searchParams.has("body")) {
targetBody = decodeURIComponent(searchParams.get("body"));
}
if (searchParams.has("headers")) {
try {
targetHeaders = JSON.parse(
decodeURIComponent(searchParams.get("headers")),
);
} catch (_) {
throw new Error("headers not valid");
}
}
if (searchParams.has("preset")) {
preset = decodeURIComponent(searchParams.get("preset"));
}
} else if (method === "post") {
// POST
/**
* payload(json):
* {
* url: 'https://example.com',
* method: 'PUT',
* body: 'a=1&b=2',
* headers: {
* Cookie: 'name=root'
* },
* preset: '',
* }
*/
const payload = await req.json();
if (payload.url) {
targetURL = payload.url;
}
if (payload.method) {
targetMethod = payload.method;
}
if (payload.body) {
targetBody = payload.body;
}
if (payload.headers) {
targetHeaders = payload.headers;
}
if (payload.preset) {
preset = payload.preset;
}
} else {
throw new Error("Method not implemented");
}
if (!targetURL) {
throw new Error("URL not found");
}
if (!/^https?:\/\//.test(targetURL)) {
throw new Error("URL not valid");
}
if (targetMethod === "GET" && targetBody) {
throw new Error("GET method can't has body");
}
if (Object.prototype.toString.call(targetHeaders) !== "[object Object]") {
throw new Error("Headers not valid");
}
if (!targetHeaders["User-Agent"]) {
targetHeaders["User-Agent"] = UA;
}
// 增加预设
if (preset in PRESETS) {
Object.assign(targetHeaders, PRESETS[preset]);
}
return {
origin,
targetURL,
targetMethod,
targetBody,
targetHeaders,
};
}
/**
* 代理请求
*/
function wfetch(url, method, body, headers = {}) {
return fetch(url, {
method: method,
body: body || undefined,
headers: {
...headers,
},
});
}
export default {
async fetch(request) {
try {
const {
origin,
targetURL,
targetMethod,
targetBody,
targetHeaders,
} = await parseRequest(request);
// 代理请求
const response = await wfetch(
targetURL,
targetMethod,
targetBody,
targetHeaders,
);
return new Response(response.body, {
headers: {
"Access-Control-Allow-Origin": origin,
"Access-Control-Max-Age": "86400",
"Content-Type": response.headers.get("Content-Type"),
},
});
} catch (err) {
return error(err.message);
}
}
}:::
目前该节点代码可部署在以下平台:
注意:
Deno Deploy 改版后要求 绑定信用卡 才可使用之前的免费额度,所以 推荐使用 Cloudflare 平台搭建节点。
部署到 Cloudflare Workers
打开控制台的左侧【计算和AI】下面的【Workers 和 Pages】菜单,点击【创建应用程序】,创建一个新的 worker,如下所示:
选择【从 Hello World! 开始】
部署之后,点击【编辑代码】,将节点的代码替换为我们自己的代码(节点代码从本文档上面拷贝):
返回【URL not found】就表示节点部署成功了,我们的节点地址(url)就可以配置到网站中进行使用了。
绑定自定义域名
worker 自动生成的域名(比如上面例子中的
https://gentle-firefly-af03.markortese3.workers.dev)通常需要翻墙才能访问,通过配置自定义域名,可缓解 cloudflare 域名被墙的问题。实测下来,虽然用自定义域名不需要翻墙即可访问,但速度会有一些下降。如果有条件的话,还是开启翻墙软件进行使用。
1. 购买域名
若手里还没有自己的域名,则推荐从 SpaceShip 处购买,支持支付宝支付。
可以选择.site或者.online等后缀的域名,首年价格较低。
2. 将域名添加到 Cloudflare
在【账户主页】点击【加入域】,如下所示:
将域名填入,其他保留默认设置,点【继续】:
选择免费计划:
最后,需要修改 SpaceShip 上面的设置,如下:
打开 SpaceShip 上面你刚购买的那个域名的高级DNS设置页面,如下:
这两处设置对应 Cloudflare 上面要求修改的两处。
这两处设置完之后,需要等待一段时间,等 Cloudflare 更新就可以了。
之后如果想重新启用域名的 DNSSEC,可以在 Cloudflare 中点下面这里,如下所示:
会得到一些配置值,如下:
回到 SpaceShip,在【高级DNS】中添加一条DS记录,如下所示:
【关键标签】对应【密钥标记】
设置完之后等待就可以了。
到此,我们已成功的将我们的域名托管给 Cloudflare 进行管理了。
3. 给 worker 添加自定义域
在 worker 的设置里,添加【自定义域】,如下:
这里可以输入任意的二级域名,比如1235566.space是我刚注册的域名,我可以在前面添加00.表示一条访问节点地址,如下所示:
添加完之后,我就可以通过00.1235566.space访问这个代理节点了,如下:
你可以在同一个 worker 上面添加多个这样的【自定义域】,这样就会得到多个节点地址。
比如,公共代理节点的配置如下:
配置通配符路由(可选)
这样就不需要手动添加二级域名了。
安全设置
针对绑定了自定义域名的账户
如果想限制你的私有节点只能被特定域名使用,可以在 Cloudflare 控制台添加过滤规则,如下所示:
通过限制【引用方】,也就是 HTTP 请求中的referer必须为特定域名才可访问。
上面这个示例配置表示,只有https://www.example.com网站可以使用该节点,其他网站使用时会自动被阻止,一般会返回下面这样的错误:
部署到 Deno Deploy
视频版
文字版
在 Deno Deploy 控制台点击New Playground,创建一个项目,如下:
将上面的节点代码拷贝到左侧代码编辑区,并点击Save & Deploy按钮:
保存成功后,右侧会出现URL not found提示,表示代理搭建完成。
复制右侧地址栏中的地址( https://deep-boa-76.deno.dev ),配置进页面中即可使用。
File: src/get-started/proxy.md
关于代理节点
代理节点的作用
微信公众号文章的图片和内容存在跨域限制和防盗链机制,浏览器无法直接下载这些资源。代理节点充当中间转发层,帮助你绕过这些限制,实现文章内容和图片的正常下载。
系统采用 代理池 机制管理多个代理节点:
- 自动轮询:按最少失败次数 + 最久未使用的策略选择最优节点
- 故障转移:单个节点连续失败 5 次后自动进入 10 分钟冷却期
- 负载均衡:节点越多,并发下载越快
公共代理节点
目前公共代理节点全部部署在 Cloudflare Workers 上,共部署了 6 个账号:
根据实际部署情况,可能会有增删,具体可查看网站后台的【公共代理】页面
https://*.worker-proxy.asia
https://*.net-proxy.asia
https://*.1235566.space
https://*.worker-proxy.shop
https://*.worker-proxys.cyou
https://*.worker-proxy.cyou::: warning 公共代理使用限制
- 每个节点基于 Cloudflare Workers 免费计划,每天共 10 万次请求(所有用户共享)
- 额度用完后会返回
429状态码,需等待次日 UTC 0:00(北京时间 8:00)刷新 - 公共节点仅限官网 (down.mptext.top) 使用,私有部署请搭建自己的节点
:::
搭建私有代理
如果公共代理不够用,或者你需要私有部署,推荐搭建自己的代理节点。每个 Cloudflare 免费账号可创建多个 Worker,操作简单且完全免费。
查看 搭建私有代理节点 教程。
File: src/get-started/usage.md
[Binary file]
File: src/misc/architecture.md
整体架构
技术栈
| 层级 | 技术 | 说明 |
|---|---|---|
| 前端框架 | Nuxt 3 + Vue 3 | 服务端渲染 + 单页应用 |
| UI 样式 | Tailwind CSS | 原子化 CSS |
| 数据表格 | AG-Grid | 文章列表展示 |
| 本地存储 | IndexedDB (Dexie) | 文章数据、HTML 内容、元数据缓存 |
| 后端服务 | Nitro | Nuxt 的服务端引擎 |
| 数据持久化 | KV Storage | 登录会话等服务端数据 |
系统架构
┌─────────────────────────────────────────────────┐
│ 浏览器端 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Vue 组件 │ │ Composables│ │ IndexedDB │ │
│ │ (页面/UI) │→ │ (业务逻辑) │→ │ (本地数据缓存) │ │
│ └──────────┘ └─────┬────┘ └───────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ Downloader/Exporter │ │
│ │ (下载与导出引擎) │ │
│ └───────────┬───────────┘ │
└──────────────────────┼───────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────────┐ ┌─────────┐ ┌──────────┐
│ Nitro 服务端│ │代理节点池│ │微信公众号API│
│ (登录/搜索) │ │(CF Workers)│ │ │
└────────────┘ └─────────┘ └──────────┘核心模块
登录认证
用户通过微信扫码登录公众号后台,服务端获取并存储 Cookie 和 Token,生成 auth-key 返回客户端。后续所有对微信接口的请求都通过服务端代理,自动注入认证信息。
文章获取
通过公众号后台的文章搜索接口分页拉取文章列表(每页最多 20 条),文章链接和基本信息存储在浏览器的 IndexedDB 中。
内容下载
文章 HTML 内容通过代理节点池下载。ProxyManager 管理多个代理节点,采用最少失败 + 最久未用的策略选择最优节点,单节点连续失败 5 次后自动冷却 10 分钟。
数据导出
根据用户选择的格式,从 IndexedDB 中读取缓存数据进行转换和打包:
- Excel/JSON:直接从缓存数据生成
- HTML:解析文章中的图片和样式表,通过代理下载后替换 URL,打包为 ZIP
- Markdown/TXT/Word:将 HTML 通过 TurndownService 转换后输出
Credentials 系统
阅读量和评论数据需要微信用户身份才能获取。通过 wxdown-service 程序在本地拦截微信客户端的请求,提取认证参数并同步到网站。Credentials 有效期约 25 分钟。
数据流向
微信公众号后台 ──→ Nitro 服务端 ──→ 浏览器(文章列表)
│
微信文章页面 ──→ 代理节点 ──→ 浏览器(文章内容)──→ IndexedDB
│
导出引擎 ──→ 本地文件部署架构
| 部署方式 | 服务端 | 代理节点 | 数据存储 |
|---|---|---|---|
| 公共网站 | Cloudflare Pages | Cloudflare Workers | 浏览器 IndexedDB + Cloudflare KV |
| Docker | 本地容器 | 自建 Workers | 浏览器 IndexedDB + 文件系统 KV |
| Vercel | Vercel Functions | 自建 Workers | 浏览器 IndexedDB + Vercel KV |
File: src/misc/domain.md
关于更换域名的一些事
本文记录了项目域名变化的历史,帮助用户了解各个地址之间的关系。
域名变更历史
| 时间 | 域名 | 平台 | 状态 |
|---|---|---|---|
| 早期 | wechat-article-exporter.deno.dev |
Deno Deploy | 已重定向 |
| 中期 | exporter.wxdown.online |
Cloudflare | 已停用 |
| 现在 | down.mptext.top |
Cloudflare | 当前使用 |
文档站点:
| 时间 | 域名 | 状态 |
|---|---|---|
| 早期 | wechat-article-docs.deno.dev |
已停用 |
| 中期 | docs.wxdown.online |
已停用 |
| 现在 | docs.mptext.top |
当前使用 |
变更原因
- Deno Deploy → Cloudflare:
deno.dev域名在国内被屏蔽,迁移至 Cloudflare 以提升国内访问体验 - wxdown.online → mptext.top:旧 Cloudflare 账号无法登录,域名无法绑定新账号,因此启用新域名
旧域名状态
wxdown.online 域名已于 2026 年 4 月过期,相关网站已不可用。
::: warning 数据迁移提醒
由于工具数据存储在浏览器的 IndexedDB 中,且与域名绑定,旧域名下缓存的数据已无法访问。如有重要数据,建议使用 私有部署 方案以确保域名一致性。
:::
当前地址
File: src/misc/features.md
[Binary file]
File: src/misc/qq-group.md
交流群
目前在 QQ 上开通了交流群,有需要的小伙伴可以进来和大家交流使用体验。
494723496
File: src/others/database.md
缓存数据库设计
数据库名: wechat-article-exporter
version: 1
`article` store
用于存储文章列表接口的数据,减少接口请求次数。
对象数据结构如下:
import {AppMsgEx} from "../types";
declare const article: AppMsgEx
// key 采用【公众号id】与【文章id】组合的形式
const key = `${fakeid}:${article.aid}`
// value 除了文章相关字段外,增加了 fakeid 字段
type StoreObjectValue = AppMsgEx & {
fakeid: string
}需要的索引:
- fakeid
- fakeid_create_time (复合索引)
`asset` store
用于存储 css 文件,因为大部分文章的样式文件都相同,所以缓存该文件对于减少下载速度有重要意义。
对象数据结构如下:
interface Asset {
/**
* css文件路径 (keyPath)
*/
url: string
/**
* 文件对象
*/
file: File
}`info` store
用于统计公众号已缓存信息。
对象数据结构如下:
interface Info {
/**
* 公众号id (keyPath)
*/
fakeid: string
/**
* 文章是否已全部加载
*
* 公众号文章的加载逻辑是从最新的文章开始往前加载,越早的文章越靠后
*/
completed: boolean
/**
* 缓存的消息数
*
* 一条消息可能会包含多篇文章
* 分页查询采用的是消息条数,而不是文章条数
*/
count: number
/**
* 缓存的文章数
*/
articles: number
}version: 2
`api` store
用于统计接口调用情况,帮助分析微信接口频率限制规则。
该项目涉及到的可能会被微信限制调用频率的有如下接口:
/api/searchbiz公众号列表 (调用频次相对较低,不容易出现限制)/api/appmsgpublish历史文章列表 (调用频次高,很容易出现限频)
注意
- 文章下载不涉及到微信接口调用,因为文章链接是公开可访问的,不需要携带cookie
- 即使
/api/appmsgpublish接口被限频,仍可以通过带关键字进行调用
对象数据结构如下:
type ApiName = 'searchbiz' | 'appmsgpublish'
interface APICall {
/**
* 接口名称
*/
name: ApiName
/**
* 调用账号(nickname)
*/
account: string
/**
* 调用时间
*/
call_time: number
/**
* 调用结果是否正常
*
* true: 正常
* false: 被封禁
*/
is_normal: boolean
/**
* 请求参数
*/
payload: Record<string, any>
}需要的索引:
- account
- account_call_time (复合索引)
version: 3
`proxy` store
用于统计代理使用情况。
对象数据结构如下:
interface Proxy {
// 代理地址 (keyPath)
address: string
// 是否正在被使用
busy: boolean
// 是否处于冷静期
cooldown: boolean
// 使用次数
usageCount: number
// 成功次数
successCount: number
// 失败次数
failureCount: number
// 下载流量
traffic: number
}File: src/others/下载流量分析.md
流量分析
接口的流量
服务器主要是通过proxyMpRequest函数代理微信接口,包括:
- 登录流程
- 获取登录公众号头像/昵称(从 html 中提取)
- 获取公众号列表
- 获取文章列表
资源下载的流量
这部分是代理流量的消耗大户,资源采用代理池并行下载,包括:
- 下载文章 html 文本
- 下载 html 中的资源文件(图片/样式)
页面上图片的显示
通过service worker技术规避掉图片的防盗链,这样就可以直接在客户端下载显示,不需要消耗代理流量。
- 公众号头像显示
- 文章封面图的显示
File: src/others/公众号登录原理.md
微信公众号二维码登录原理
页面展示的二维码图片地址如下:
https://mp.weixin.qq.com/cgi-bin/scanloginqrcode?action=getqrcode&random=其中,random参数为系统当前的时间戳:
const random = new Date().getTime()该链接返回的二维码图片内部编码了一个 url 地址,如下所示:
http://mp.weixin.qq.com/mp/scanlogin?action=index&qrticket=b5e94c8689978a087986ef0bb00fe2ea&scanscene=0#wechat_redirect页面加载时,首先调用getIngorePassList接口,参数如下:
POST /cgi-bin/bizlogin
Host: https://mp.weixin.qq.com
action=prelogin根据返回结果,会调用report接口,如下:
POST /cgi-bin/webreport
Host: https://mp.weixin.qq.com
reportJson={"devicetype":1,"newsessionid":"172059629456827","optype":1,"page_state":3,"log_id":19015}newsessionid的取值逻辑如下:
this.sessionid = new Date().getTime() + "" + Math.floor(Math.random() * 100);然后调用this.getQrcode()方法获取二维码,如下:
POST /cgi-bin/bizlogin?action=startlogin
Host: https://mp.weixin.qq.com
userlang=zh_CN
redirect_url=
login_type=3
sessionid=sessionid接口返回:
{"base_resp":{"err_msg":"ok","ret":0}}表示正常。
此时页面会把this.hasStartLogin标志改为true,表示登录流程已开始。同时调用this.refreshQrcode()获取二维码图片并展示
未扫码:
{
"acct_size": 0,
"base_resp": {
"err_msg": "ok",
"ret": 0
},
"binduin": 0,
"status": 0,
"user_category": 0
}已扫码:
{
"acct_size": 19,
"base_resp": {
"err_msg": "ok",
"ret": 0
},
"binduin": 0,
"status": 4,
"user_category": 0
}已登录:
{
"acct_size": 19,
"base_resp": {
"err_msg": "ok",
"ret": 0
},
"binduin": 0,
"status": 1,
"user_category": 2
}File: src/others/微信接口频率限制.md
微信获取文章列表接口的频率限制研究
目前测试结果,短时间内获取超过 600 条文章数据就很容易触发200013 freq control限制,然后会导致 24 小时内不能再调用该接口。
目前正在确定这个时间范围是多少。
17:20 左右出现
{
"base_resp": {
"err_msg": "freq control",
"ret": 200013
}
}19:00 解封了
1~2个小时
注意:该接口被限制后仍然可以通过关键字搜索继续调用该接口,但不确定搜索是否也会触发
freq control限制。
几个因素:
- 数据量
- 请求次数
根据测试,数据量在达到 1100 时达到了限制
触发限流的示例:
- 20分钟请求了 1100 条数据量
cookie登录有效期4d
File: src/tutorials/export-article-links.md
如何批量导出某个公众号的全部文章链接?
:::info 本文需要的工具
- 一个微信 订阅号 或 服务号 (没有的话,可以去 微信公众平台 免费注册)
- 微信公众号文章导出工具
:::
:::warning 注意
如果目标公众号关闭了搜索功能,则本文的方法无效。
:::
1. 注册公众号
::: tip 提示
若你已经有一个可用的 订阅号 或者 服务号 的话可跳过该步骤。
:::
前往 微信公众平台 进行注册,公众号(原订阅号)和服务号皆可。
2. 扫码登录网站
注册完公众号之后,进入网站的 登录页面,用微信扫描页面上的二维码,选择自己注册的 公众号 进行登录。
::: tip 提示
注意,这里必须选择 公众号 或者 服务号 进行登录,不能使用 小程序 登录,否则后续无法获取数据。
:::
3. 添加公众号,开始抓取文章链接
在 公众号管理 页面添加一个公众号,通过同步按钮拉取该公众号的所有文章链接。
4. 导出链接
在 文章下载 页面选择目标公众号,勾选对应的文章,通过右上角的【导出】选择导出excel/json,如下图所示:
--- METRICS ---
- Files Extracted: 23
- Estimated Token Budget: ~16655 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/wechat-article/wechat-article-exporter