# Technical Documentation: wechat-article/wechat-article-exporter

> ℹ️ **Provenance:** Hybrid Fusion: `wechat-article/wechat-article-exporter` + `wechat-article/docs` · [CodeWiki Reference](https://codewiki.google/github.com/wechat-article/wechat-article-exporter) · Recency: Active (< 180 days)

## 1. Project Overview & Quickstart (wechat-article/wechat-article-exporter)

# wechat-article-exporter

![GitHub stars]
![GitHub forks]
![GitHub License]
![Package Version]


> [!WARNING]
> **本项目已停止维护（2026-07-30）。**
>
> 项目所依赖的微信上游核心接口已被官方关闭，且大概率不会再开放。这是整个项目赖以工作的地基，无法通过修改代码绕过 —— 开箱即用的一键同步公众号文章已不复存在。完整说明见 [关于本项目停止维护的说明 #200](https://github.com/wechat-article/wechat-article-exporter/issues/200)。
>
> - 仓库不会删除，将转为**只读归档**。代码、文档与 issue 讨论均留在原处，任何人都可以继续阅读、fork、学习和二次开发。
> - **不再接受新的 issue 和 PR**，也不会再有新版本，请不要等待「明天可能修好」。
> - 在线网站的域名将于 **2026-10-30 到期**，届时将无法访问，请在此之前导出你需要保留的数据。
> - **你已经同步过的文章都还是你的**，它们存放在你自己的设备上，不受任何接口影响。


一款在线的 **微信公众号文章批量下载** 工具，支持导出阅读量与评论数据，无需搭建任何环境，~~可通过 [在线网站] 使用~~（域名 2026-10-30 到期，届时将无法访问），同时也支持 docker 私有化部署和 Cloudflare 部署。

支持下载各种文件格式，其中 HTML 格式可100%还原文章排版与样式。


## :books: 如何使用？

该工具的使用教程已移至 [文档站点](https://docs.mptext.top)。


## :dart: 特性

- [x] 搜索公众号，支持关键字搜索
- [x] 支持导出 html/json/excel/txt/md/docx 格式(html 格式打包了图片和样式文件，能够保证100%还原文章样式)
- [x] 缓存文章列表数据，减少接口请求次数
- [x] 支持文章过滤，包括作者、标题、发布时间、原创标识、所属合集等
- [x] 支持合集下载
- [x] 支持图片分享消息
- [x] 支持视频分享消息
- [x] 支持导出评论、评论回复、阅读量、转发量等数据 (需要抓包获取 credentials 信息，[查看操作步骤](https://docs.mptext.top/advanced/wxdown-service.html))
- [x] 支持 Docker 部署
- [x] 支持 Cloudflare 部署
- [x] ~~开放 API 接口~~（公开 API 已下线，不再对外提供服务）


## 「取阅」

同一作者的另一个项目 —— **[取阅](https://feed.zoro.build)**。

实时监听并推送公众号群发文章。


## :heart: 感谢

- 感谢 [Deno Deploy]、[Cloudflare Workers] 提供免费托管服务
- 感谢 [WeChat_Article] 项目提供原理思路


## :star: 支持

如果你觉得本项目帮助到了你，请给作者一个免费的 Star，感谢你的支持！


## :bulb: 原理

在公众号后台写文章时支持搜索其他公众号的文章功能，以此来实现抓取指定公众号所有文章的目的。


## :memo: 许可

MIT

## :red_circle: 声明

本程序承诺，不会利用您扫码登录的公众号进行任何形式的私有爬虫，也就是说不存在把你的账号作为公共账号为别人爬取文章的行为，也不存在类似账号池的东西。

您的公众号只会服务于您自己的抓取文章的目的。

通过本程序获取的公众号文章内容，版权归文章原作者所有，请合理使用。若发现侵权行为，请联系我们处理。


## :chart_with_upwards_trend: Star 历史

[![Star History Chart]][Star History Chart Link]





[GitHub stars]: https://img.shields.io/github/stars/wechat-article/wechat-article-exporter?style=social&label=Star&style=plastic

[GitHub forks]: https://img.shields.io/github/forks/wechat-article/wechat-article-exporter?style=social&label=Fork&style=plastic

[GitHub License]: https://img.shields.io/github/license/wechat-article/wechat-article-exporter?label=License

[Package Version]: https://img.shields.io/github/package-json/v/wechat-article/wechat-article-exporter


[Deno Deploy]: https://deno.com/deploy

[Cloudflare Workers]: https://workers.cloudflare.com

[Wechat_Article]: https://github.com/1061700625/WeChat_Article

[Star History Chart]: https://api.star-history.com/svg?repos=wechat-article/wechat-article-exporter&type=Timeline

[Star History Chart Link]: https://star-history.com/#wechat-article/wechat-article-exporter&Timeline

[在线网站]: https://down.mptext.top

## 2. Official Technical Reference & Guides (wechat-article/docs)

## File: README.md

# docs
微信公众号文章系列工具文档

---

## File: CLAUDE.md

# CLAUDE.md

## 项目概述

这是 **微信公众号文章下载工具** 的文档站点，基于 [VitePress](https://vitepress.dev/) 构建。  
仓库地址：`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
```

## 常用命令

```bash
# 启动开发服务器
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": "git@github.com:wechat-article/docs.git",
  "author": "jock",
  "license": "UNLICENSED",
  "packageManager": "yarn@1.22.22",
  "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 方案](advanced/private-deploy#docker-compose-mkcert-自签名证书)


## 如何获取评论和阅读量数据？

阅读量、点赞数、转发量和评论数据需要通过微信用户身份才能获取，本工具将这些身份信息统称为 **Credentials**。

获取流程：
1. 使用 [wxdown-service](advanced/wxdown-service) 程序启动本地代理
2. 在微信客户端中打开目标公众号的一篇文章
3. 程序自动抓取 Credentials 并同步到网站

::: warning 注意
Credentials 有效期约 **25 分钟**，过期后需要在微信中重新打开文章页面以刷新。
:::

详细操作请查看 [抓取 Credentials](advanced/wxdown-service)。


## 已加载消息数和消息总数为什么不一致？



首先需要理解 **消息** 和 **文章** 的区别：



如上图所示，红框中的是 **2 条消息**，但每条消息可能包含多篇文章（类似于一期推送中的多篇图文）。

- **已加载消息数**：实际从微信接口拉取到的消息数量，该数值是准确的
- **消息总数**：微信接口返回的 `total_count` 字段，作为参考值，不一定完全准确

## 文章下载页面的文章数量为什么不一样？



这是因为默认启用了「隐藏已删除文章」选项。已删除的文章在同步时仍会被获取到，但在文章列表中默认隐藏。

关闭该选项后，两个数字会保持一致：



## 请求出现 `429` 状态码

`429` 表示请求过于频繁，通常是公共代理节点的当日额度用完了。

**解决方案：**
- 搭建自己的 [私有代理节点](get-started/private-proxy)（推荐）
- 等待次日北京时间 8:00 额度自动刷新

参考 [GitHub Issue #119](https://github.com/wechat-article/wechat-article-exporter/issues/119)

## 目标公众号 Credential 未设置

该提示出现在抓取阅读量或评论数据时，表示对应公众号的 Credential 尚未获取或已过期。

请参考 [抓取 Credential](advanced/wxdown-service) 教程获取 Credential 后再进行抓取。

## 无法打开此文件夹，因为其中含有系统文件

导出时不能选择系统保护的目录（如「下载」、「文档」等系统目录）。请手动创建一个新目录（如桌面上新建一个「导出文件」文件夹），然后选择该目录进行导出。

## 如何清理数据

工具中的文章数据（除单篇文章下载外）都存储在浏览器本地的 IndexedDB 数据库中，有以下几种清理方式：

### 方法 1：删除指定公众号数据

在【公众号管理】页面，选择对应的公众号，点击【删除】按钮即可清除该公众号的所有缓存数据。



### 方法 2：通过开发者工具清除全部数据

适用于需要清除所有公众号数据的情况：

1. 按 `F12` 或右键页面选择「检查」，打开浏览器开发者工具
2. 切换到「Application」（应用）标签页
3. 在左侧展开「IndexedDB」
4. 找到网站对应的数据库，点击【删除数据库】



### 方法 3：通过浏览器设置清除站点数据

此方法会清除该网站的所有本地数据（包括 IndexedDB、Cookie、登录状态等）：

1. 在 Chrome 地址栏输入 `chrome://settings/content/all?searchSubpage=mptext.top` 并回车
2. 找到 `down.mptext.top` 域名
3. 点击右侧的垃圾桶图标删除该站点的所有数据



## 如何修改 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：**

通过终端启动：

```bash
open -a "Google Chrome" --args --user-data-dir="/Volumes/Data/ChromeData"
```

**Linux：**

```bash
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）：**

```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：**

```bash
# 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）：


### 第二步：捕获网络请求

1. 切换到「Network」（网络）面板
2. 点击清空按钮清除已有记录
3. 在页面上进行你的操作（如抓取评论）
4. 操作完成后，查看网络面板中的请求和响应内容



如果不确定如何分析，可以将请求详情截图发到 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.top`
- `http://localhost`
- `http://127.0.0.1`

如果私有部署后使用其他域名访问，需要 [搭建自己的代理节点](../get-started/private-proxy)。
:::

## 本地运行

适合开发者调试或临时使用，需要 Node.js >= 22 环境。

1. 拉取代码

```shell
git clone git@github.com:wechat-article/wechat-article-exporter.git
cd wechat-article-exporter
```

2. 安装依赖

```shell
yarn
```

3. 启动开发服务器

```shell
yarn dev
```

启动后打开 `http://localhost:3000` 即可使用。

## Docker 运行（推荐）

适合长期使用，无需搭建开发环境。

### 拉取镜像

```shell
docker pull ghcr.io/wechat-article/wechat-article-exporter:latest
```

### 启动容器

容器暴露端口 3000，内部数据目录为 `/app/.data`。

::: code-group
```shell [Linux]
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:latest
```

```shell [macOS / Windows]
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:latest
```
:::

::: tip 提示
`-v ./.data:/app/.data` 会将容器内的数据映射到当前目录的 `.data` 文件夹，确保容器重启后数据不丢失。
:::

启动后打开 `http://localhost:3000` 即可使用。


## docker compose + mkcert 自签名证书

### 使用`mkcert`生成自签名证书

> `mkcert`的安装及使用可以查看[官方文档](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#mkcert)

```shell
# 在系统中安装根证书(只需执行一次)
mkcert -install
```

执行以下代码生成本地 IP 对应的证书文件:
```shell
# 这里可以列出你最终访问的ip
mkcert localhost 127.0.0.1 ::1
```

该命令会生成2个`pem`文件，分别对应证书和密钥文件，如下图所示:


将这两个文件重命名为`cert.pem`和`key.pem`，并放在`certs`目录中。

### 拷贝`nginx.conf`和`docker-compose.yaml`文件

::: code-group
```nginx [nginx.conf]
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;
    }
}
```

```yaml [docker-compose.yml]
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`目录，最终的目录结构如下:
```text
app
├── certs
│   ├── cert.pem
│   └── key.pem
├── docker-compose.yml
└── nginx.conf
```
在该目录中执行`docker compose up -d`启动，然后就可以通过`https://localhost`(或者你的本地ip)访问 https 网站程序了。


## Vercel 快速部署

### 一键部署

点击下方按钮一键部署到 Vercel：

[](https://vercel.com/new/clone?repository-url=https://github.com/wechat-article/wechat-article-exporter)

### 手动部署

1. Fork 本项目到你的 GitHub 账号

2. 在 Vercel 中导入项目
   - 访问 [Vercel](https://vercel.com)
   - 点击 "Add New..." → "Project"
   - 选择你 Fork 的项目仓库

3. 配置项目
   - Framework Preset: 选择 `Nuxt.js`
   - Root Directory: 保持默认 `./`
   - Build Command: 保持默认或使用 `yarn build`
   - Output Directory: 保持默认 `.output/public`

4. 配置环境变量（可选）
   - 在项目设置中添加所需的环境变量（见下方环境变量说明）

5. 点击 "Deploy" 完成部署


## 环境变量

以下环境变量均为可选项。本地运行时在项目根目录创建 `.env` 文件即可；Docker 运行时通过 `--env-file .env` 传入。

### 数据存储（KV）

登录会话等数据通过 KV 存储持久化，不同部署方式配置不同：

```dotenv
# 本地 / Docker 部署（基于文件系统）
NITRO_KV_DRIVER=fs
NITRO_KV_BASE=.data/kv

# Cloudflare 部署（使用 Cloudflare KV）
# NITRO_KV_DRIVER=cloudflare-kv-binding
```

### 调试模式

```dotenv
# 调试微信代理请求（仅开发环境生效）
NUXT_DEBUG_MP_REQUEST=true
```

### AG-Grid 企业版授权

```dotenv
# 文章列表使用 AG-Grid 展示，企业版授权可解锁更多功能
NUXT_AGGRID_LICENSE=
```

### 网站统计（umami）

```dotenv
# https://umami.nuxt.dev/api/configuration
NUXT_UMAMI_ID=
NUXT_UMAMI_HOST=
```

### 错误监控（Sentry）

```dotenv
# 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，执行：



```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，然后执行：

```shell
# 克隆项目
git clone https://github.com/wechat-article/wxdown-service.git
cd wxdown-service

# 安装依赖
pip install -r requirements.txt

# 启动程序
python main.py
```

### 2. 安装证书

```shell
sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain ~/.mitmproxy/mitmproxy-ca-cert.pem
```

### 3. 设置系统代理

打开「系统设置」→「网络」→「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 点我查看节点代码
```js
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);
        }
    }
}
```
:::


目前该节点代码可部署在以下平台：

- [Cloudflare Worker](https://workers.cloudflare.com/)
- [Deno Deploy](https://deno.com/deploy)

> 注意：
> 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](https://www.spaceship.com/zh/domain-search/?tab=domains&query=) 处购买，支持支付宝支付。
可以选择`.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，操作简单且完全免费。

查看 [搭建私有代理节点](private-proxy) 教程。

---

## 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 中，且与域名绑定，旧域名下缓存的数据已无法访问。如有重要数据，建议使用 [私有部署](../advanced/private-deploy) 方案以确保域名一致性。
:::

## 当前地址

- **工具地址**：https://down.mptext.top
- **文档地址**：https://docs.mptext.top
- **GitHub**：https://github.com/wechat-article

---

## 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

用于存储文章列表接口的数据，减少接口请求次数。

对象数据结构如下:

```ts
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 文件，因为大部分文章的样式文件都相同，所以缓存该文件对于减少下载速度有重要意义。

对象数据结构如下:

```ts
interface Asset {
    /**
     * css文件路径 (keyPath)
     */
    url: string

    /**
     * 文件对象
     */
    file: File
}
```


### `info` store

用于统计公众号已缓存信息。

对象数据结构如下:

```ts
interface Info {
    /**
     * 公众号id (keyPath)
     */
    fakeid: string
    
    /**
     * 文章是否已全部加载
     * 
     * 公众号文章的加载逻辑是从最新的文章开始往前加载，越早的文章越靠后
     */
    completed: boolean

    /**
     * 缓存的消息数
     * 
     * 一条消息可能会包含多篇文章
     * 分页查询采用的是消息条数，而不是文章条数
     */
    count: number

    /**
     * 缓存的文章数
     */
    articles: number
}
```

## version: 2

### `api` store

用于统计接口调用情况，帮助分析微信接口频率限制规则。

该项目涉及到的可能会被微信限制调用频率的有如下接口:

- `/api/searchbiz` 公众号列表 (调用频次相对较低，不容易出现限制)
- `/api/appmsgpublish` 历史文章列表 (调用频次高，很容易出现限频)

### 注意

1. 文章下载不涉及到微信接口调用，因为文章链接是公开可访问的，不需要携带cookie
2. 即使`/api/appmsgpublish`接口被限频，仍可以通过带关键字进行调用


对象数据结构如下:

```ts
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

用于统计代理使用情况。

对象数据结构如下:

```ts
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`参数为系统当前的时间戳:
```js
const random = new Date().getTime()
```

该链接返回的二维码图片内部编码了一个 url 地址，如下所示:
```
http://mp.weixin.qq.com/mp/scanlogin?action=index&qrticket=b5e94c8689978a087986ef0bb00fe2ea&scanscene=0#wechat_redirect
```


页面加载时，首先调用`getIngorePassList`接口，参数如下:
```http request
POST /cgi-bin/bizlogin
Host: https://mp.weixin.qq.com

action=prelogin
```
根据返回结果，会调用`report`接口，如下:
```http request
POST /cgi-bin/webreport
Host: https://mp.weixin.qq.com

reportJson={"devicetype":1,"newsessionid":"172059629456827","optype":1,"page_state":3,"log_id":19015}
```

`newsessionid`的取值逻辑如下:
```js
this.sessionid = new Date().getTime() + "" + Math.floor(Math.random() * 100);
```

然后调用`this.getQrcode()`方法获取二维码，如下:
```http request
POST /cgi-bin/bizlogin?action=startlogin
Host: https://mp.weixin.qq.com

userlang=zh_CN
redirect_url=
login_type=3
sessionid=sessionid
```
接口返回:
```json
{"base_resp":{"err_msg":"ok","ret":0}}
```
表示正常。

此时页面会把`this.hasStartLogin`标志改为`true`，表示登录流程已开始。同时调用`this.refreshQrcode()`获取二维码图片并展示

未扫码：
```json
{
  "acct_size": 0,
  "base_resp": {
    "err_msg": "ok",
    "ret": 0
  },
  "binduin": 0,
  "status": 0,
  "user_category": 0
}
```

已扫码：
```json
{
  "acct_size": 19,
  "base_resp": {
    "err_msg": "ok",
    "ret": 0
  },
  "binduin": 0,
  "status": 4,
  "user_category": 0
}
```

已登录：
```json
{
  "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 左右出现
```json
{
  "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 本文需要的工具
1. 一个微信 **订阅号** 或 **服务号** (没有的话，可以去 [微信公众平台](https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN) 免费注册)
2. [微信公众号文章导出工具](https://down.mptext.top/)
:::

:::warning 注意
如果目标公众号关闭了搜索功能，则本文的方法无效。


:::

## 1. 注册公众号

::: tip 提示
若你已经有一个可用的 **订阅号** 或者 **服务号** 的话可跳过该步骤。
:::


前往 [微信公众平台](https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN) 进行注册，公众号(原订阅号)和服务号皆可。




## 2. 扫码登录网站

注册完公众号之后，进入网站的 [登录页面](https://down.mptext.top/)，用微信扫描页面上的二维码，选择自己注册的 **公众号** 进行登录。

::: tip 提示
注意，这里必须选择 **公众号** 或者 **服务号** 进行登录，不能使用 **小程序** 登录，否则后续无法获取数据。
:::




## 3. 添加公众号，开始抓取文章链接

在 [公众号管理](https://down.mptext.top/dashboard/account) 页面添加一个公众号，通过同步按钮拉取该公众号的所有文章链接。




## 4. 导出链接

在 [文章下载](https://down.mptext.top/dashboard/article) 页面选择目标公众号，勾选对应的文章，通过右上角的【导出】选择导出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
