H
human15:28The user wants me to analyze the project https://github.com/tmwgsicp/wechat-download-api and provide a schematic/principle diagram. Let me fetch information about this repository.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api"
{
"id": 1164859248,
"node_id": "R_kgDORW5XcA",
"name": "wechat-download-api",
"full_name": "tmwgsicp/wechat-download-api",
"private": false,
"owner": {
"login": "tmwgsicp",
"id": 161463025,
"node_id": "U_kgDOCZ-68Q",
"avatar_url": "https://avatars.githubusercontent.com/u/161463025?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/tmwgsicp",
"html_url": "https://github.com/tmwgsicp",
"followers_url": "https://api.github.com/users/tmwgsicp/followers",
"following_url": "https://api.github.com/users/tmwgsicp/following{/other_user}",
"gists_url": "https://api.github.com/users/tmwgsicp/gists{/gist_id}",
"starred_url": "https://api.github.com/users/tmwgsicp/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/tmwgsicp/subscriptions",
"organizations_url": "https://api.github.com/users/tmwgsicp/orgs",
"repos_url": "https://api.github.com/users/tmwgsicp/repos",
"events_url": "https://api.github.com/users/tmwgsicp/events{/privacy}",
"received_events_url": "https://api.github.com/users/tmwgsicp/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"html_url": "https://github.com/tmwgsicp/wechat-download-api",
"description": "一款完全开源的微信公众号文章获取、 RSS 订阅API服务,支持IP代理池反风控。",
"fork": false,
"url": "https://api.github.com/repos/tmwgsicp/wechat-download-api",
"forks_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/forks",
"keys_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/teams",
"hooks_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/hooks",
"issue_events_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/issues/events{/number}",
"events_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/events",
"assignees_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/assignees{/user}",
"branches_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/branches{/branch}",
"tags_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/tags",
"blobs_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/statuses/{sha}",
"languages_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/languages",
"stargazers_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/stargazers",
"contributors_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/contributors",
"subscribers_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/subscribers",
"subscription_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/subscription",
"commits_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/{+path}",
"compare_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/merges",
"archive_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/downloads",
"issues_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/issues{/number}",
"pulls_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/pulls{/number}",
"milestones_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/milestones{/number}",
"notifications_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/labels{/name}",
"releases_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/releases{/id}",
"deployments_url": "https://api.github.com/repos/tmwgsicp/wechat-download-api/deployments",
"created_at": "2026-02-23T15:06:21Z",
"updated_at": "2026-03-27T06:51:50Z",
"pushed_at": "2026-03-25T02:11:39Z",
"git_url": "git://github.com/tmwgsicp/wechat-download-api.git",
"ssh_url": "git@github.com:tmwgsicp/wechat-download-api.git",
"clone_url": "https://github.com/tmwgsicp/wechat-download-api.git",
"svn_url": "https://github.com/tmwgsicp/wechat-download-api",
"homepage": "https://wechatrss.waytomaster.com",
"size": 828,
"stargazers_count": 389,
"watchers_count": 389,
"language": "Python",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 24,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": {
"key": "agpl-3.0",
"name": "GNU Affero General Public License v3.0",
"spdx_id": "AGPL-3.0",
"url": "https://api.github.com/licenses/agpl-3.0",
"node_id": "MDc6TGljZW5zZTE="
},
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"has_pull_requests": true,
"pull_request_creation_policy": "all",
"topics": [
"rss",
"rss-feed",
"wechat",
"wechat-article"
],
"visibility": "public",
"forks": 24,
"open_issues": 0,
"watchers": 389,
"default_branch": "main",
"temp_clone_token": null,
"network_count": 24,
"subscribers_count": 1
}
Interesting project! 389 stars, Python, AGPL license. Let me explore the structure and README to understand how it works.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
.dockerignore
.gitattributes
.github
.gitignore
CONTENT_TYPES.md
Dockerfile
LICENSE
README.md
app.py
assets
docker-compose.yml
env.example
requirements.txt
routes
start.bat
start.sh
static
status.sh
stop.sh
utils
Let me read the README and key files to understand the architecture.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/README.md" | python3 -c "import sys,json; d=json.load(sys.stdin); import base64; print(base64.b64decode(d['content']).decode())"
<div align="center">
# WeChat Download API
### 微信公众号文章获取 & RSS 订阅服务
**完全开源 | 免费部署 | RSS 订阅 | 文章抓取 | 反风控**
[](https://github.com/tmwgsicp/wechat-download-api/stargazers)
[](LICENSE)
[](https://hub.docker.com/r/tmwgsicp/wechat-download-api)
[](https://www.python.org/)
[](https://fastapi.tiangolo.com/)
> **100% 开源,100% 免费。** 代码完全公开,私有化部署无任何限制,不搞"开源"之名行收费之实。
</div>
---
## 功能特性
- **RSS 订阅** — 订阅任意公众号,自动定时拉取新文章(**包含完整文章内容和图片**),生成标准 RSS 2.0 源,接入 FreshRSS / Feedly 等阅读器即可使用
- **文章内容获取** — 通过 URL 获取文章完整内容(标题、作者、正文 HTML / 纯文本、图片列表)
- **反风控体系** — Chrome TLS 指纹模拟 + SOCKS5 代理池轮转 + 三层自动限频,有效对抗微信封控
- **文章列表 & 搜索** — 获取任意公众号历史文章列表,支持分页和关键词搜索
- **公众号搜索** — 按名称搜索公众号,获取 FakeID
- **扫码登录** — 微信公众平台扫码登录,凭证自动保存,4 天有效期
- **图片代理** — 代理微信 CDN 图片,解决防盗链问题
- **Webhook 通知** — 登录过期、触发验证等事件自动推送(支持企业微信机器人)
- **API 文档** — 自动生成 Swagger UI / ReDoc,在线调试所有接口
<div align="center">
<img src="assets/dashboard.jpg" width="800" alt="管理面板">
<p><em>管理面板 — 登录状态、接口文档、在线测试一站式管理</em></p>
<br>
<img src="assets/rss.jpg" width="800" alt="RSS 订阅管理">
<p><em>RSS 订阅管理 — 搜索公众号一键订阅,复制地址接入 RSS 阅读器</em></p>
</div>
---
## Docker 部署 🐳
**最快速的部署方式**,无需配置 Python 环境,一键启动:
```bash
# 方式一:使用 docker-compose(推荐)
git clone https://github.com/tmwgsicp/wechat-download-api.git
cd wechat-download-api
cp env.example .env
# 编辑 .env 设置 SITE_URL 为实际访问地址
docker-compose up -d
# 方式二:直接运行
docker run -d \
-p 5000:5000 \
-v $(pwd)/data:/app/data \
-v $(pwd)/.env:/app/.env \
--name wechat-api \
tmwgsicp/wechat-download-api:latest
```
服务启动后访问 `http://localhost:5000/login.html` 扫码登录即可使用。
**支持多架构**:`linux/amd64` / `linux/arm64`(Apple Silicon、树莓派、ARM 服务器)
---
## SaaS 托管版 — 已上线 🚀
**不想折腾部署?30 秒注册即可使用** 👉 **[wechatrss.waytomaster.com](https://wechatrss.waytomaster.com)**
搜索公众号名称,拿到 RSS 链接,丢进你的阅读器——Feedly、Inoreader、NetNewsWire 全部兼容。
| 套餐 | 公众号数量 | 价格 |
|------|-----------|------|
| 免费版 | 2 个 | ¥0 |
| 基础版 | 20 个 | ¥9.9/月 |
| 专业版 | 50 个 | ¥19.9/月 |
> 免费版够用就一直免费,不够了再升级,没有套路。
---
## 使用前提
> 本工具需要通过微信公众平台后台的登录凭证来调用接口,因此使用前需要:
1. **拥有一个微信公众号**(订阅号、服务号均可)
2. 部署并启动服务后,访问登录页面用**公众号管理员微信**扫码登录
3. 登录成功后凭证自动保存到 `.env` 文件,有效期约 **4 天**,过期后需重新扫码
登录后即可通过 API 获取**任意公众号**的公开文章(不限于自己的公众号)。
> **本地电脑可以直接使用!** 不需要公网服务器——在本地启动服务后通过 `localhost` 访问即可完成扫码登录和全部功能。只有当你需要从其他设备(如手机 RSS 阅读器)远程访问时,才需要公网服务器或内网穿透。
---
## 快速开始
### 方式一:Docker 部署(推荐)
```bash
# 1. 克隆项目
git clone https://github.com/tmwgsicp/wechat-download-api.git
cd wechat-download-api
# 2. 配置环境变量
cp env.example .env
# 编辑 .env,设置 SITE_URL 为实际访问地址(如 http://your-domain.com)
# 3. 启动服务
docker-compose up -d
# 4. 查看日志
docker-compose logs -f
```
### 方式二:一键脚本部署
**第一步:克隆项目**
```bash
git clone https://github.com/tmwgsicp/wechat-download-api.git
cd wechat-download-api
```
**第二步:一键启动**
```bash
bash start.sh # Linux/macOS
# 或
start.bat # Windows
```
脚本会自动完成环境检查、虚拟环境创建、依赖安装和服务启动。
**第三步:扫码登录**
访问 `http://localhost:5000/login.html`,用**公众号管理员微信**扫码登录。
---
## API 使用
### 访问地址
| 地址 | 说明 |
|------|------|
| http://localhost:5000 | 管理面板 |
| http://localhost:5000/login.html | 扫码登录 |
| http://localhost:5000/api/docs | Swagger API 文档 |
| http://localhost:5000/api/health | 健康检查 |
---
## 服务器部署
### Linux 生产环境(systemd)
`start.sh` 脚本在 Linux 上以 `sudo` 运行时,会自动注册 systemd 服务并启用开机自启:
```bash
sudo bash start.sh
```
之后可通过以下命令管理服务:
```bash
# 查看运行状态
bash status.sh
# 停止服务
bash stop.sh
# 手动操作
sudo systemctl restart wechat-download-api
sudo systemctl status wechat-download-api
```
### 配置反向代理(可选)
如需通过域名或 HTTPS 访问,配置 Nginx 反向代理到 `localhost:5000`:
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
### 环境变量
复制 `env.example` 为 `.env` 并按需修改:
```bash
cp env.example .env
```
主要配置项参见 `env.example` 中的注释说明。
---
## API 接口
### 获取文章内容
`POST /api/article` — 解析微信公众号文章,返回标题、正文、图片等结构化数据
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `url` | string | 是 | 微信文章链接(`https://mp.weixin.qq.com/s/...`) |
请求示例:
```bash
curl -X POST http://localhost:5000/api/article \
-H "Content-Type: application/json" \
-d '{"url": "https://mp.weixin.qq.com/s/xxxxx"}'
```
返回字段:`title` 标题、`content` HTML 正文、`plain_content` 纯文本正文、`author` 作者、`publish_time` 发布时间戳、`images` 图片列表
### 搜索公众号
`GET /api/public/searchbiz` — 按关键词搜索微信公众号,获取 FakeID
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | string | 是 | 搜索关键词(公众号名称) |
请求示例:
```bash
curl "http://localhost:5000/api/public/searchbiz?query=公众号名称"
```
返回字段:`list[]` 公众号列表,每项包含 `fakeid`、`nickname`、`alias`、`round_head_img`
### 获取文章列表
`GET /api/public/articles` — 获取指定公众号的文章列表,支持分页
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `fakeid` | string | 是 | 目标公众号的 FakeID(从搜索接口获取) |
| `begin` | int | 否 | 偏移量,默认 `0` |
| `count` | int | 否 | 获取数量,默认 `10`,最大 `100` |
| `keyword` | string | 否 | 在该公众号内搜索关键词 |
请求示例:
```bash
# 获取前 50 篇
curl "http://localhost:5000/api/public/articles?fakeid=YOUR_FAKEID&begin=0&count=50"
# 获取第 51-100 篇
curl "http://localhost:5000/api/public/articles?fakeid=YOUR_FAKEID&begin=50&count=50"
```
### 搜索公众号文章
`GET /api/public/articles/search` — 在指定公众号内按关键词搜索文章
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `fakeid` | string | 是 | 目标公众号的 FakeID |
| `query` | string | 是 | 搜索关键词 |
| `begin` | int | 否 | 偏移量,默认 `0` |
| `count` | int | 否 | 获取数量,默认 `10`,最大 `100` |
请求示例:
```bash
curl "http://localhost:5000/api/public/articles/search?fakeid=YOUR_FAKEID&query=关键词"
```
### RSS 订阅
`GET /api/rss/{fakeid}` — 获取指定公众号的 RSS 2.0 订阅源
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `fakeid` | string(路径) | 是 | 公众号 FakeID |
| `limit` | int(查询) | 否 | 返回文章数量上限,默认 `20` |
使用方式:
```bash
# 1. 搜索公众号获取 fakeid
curl "http://localhost:5000/api/public/searchbiz?query=人民日报"
# 返回 fakeid: MzA1MjM1ODk2MA==
# 2. 添加订阅
curl -X POST http://localhost:5000/api/rss/subscribe \
-H "Content-Type: application/json" \
-d '{"fakeid": "MzA1MjM1ODk2MA==", "nickname": "人民日报"}'
# 3. 手动触发一次轮询(立即拉取文章)
curl -X POST http://localhost:5000/api/rss/poll
# 4. 获取 RSS 源(把这个地址添加到 RSS 阅读器)
curl "http://localhost:5000/api/rss/MzA1MjM1ODk2MA=="
```
也可以通过管理面板的 **RSS 订阅** 页面可视化管理,搜索公众号一键订阅并复制 RSS 地址。
> **关于 RSS 内容**: RSS 源包含**完整文章内容**(图文混排),您可以直接在 RSS 阅读器中阅读全文。
>
> 系统使用 **SOCKS5 代理池 + Chrome TLS 指纹模拟**技术获取文章内容,有效规避微信风控。
>
> 扫码登录后,系统会**自动**将微信凭证用于内容获取,无需手动配置。如需禁用完整内容获取(仅保留标题和摘要),可在 `.env` 中设置 `RSS_FETCH_FULL_CONTENT=false`。
#### RSS 订阅管理接口
| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/rss/subscribe` | 添加 RSS 订阅 |
| `DELETE` | `/api/rss/subscribe/{fakeid}` | 取消 RSS 订阅 |
| `GET` | `/api/rss/subscriptions` | 获取订阅列表 |
| `POST` | `/api/rss/poll` | 手动触发轮询 |
| `GET` | `/api/rss/status` | 轮询器状态 |
### 其他接口
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/image?url=IMG_URL` | 图片代理(仅限微信 CDN 域名) |
| `GET` | `/api/health` | 健康检查 |
| `GET` | `/api/stats` | 限频统计 |
| `POST` | `/api/login/session/{id}` | 初始化登录会话 |
| `GET` | `/api/login/getqrcode` | 获取登录二维码 |
| `GET` | `/api/login/scan` | 检查扫码状态 |
| `POST` | `/api/login/bizlogin` | 完成登录 |
| `GET` | `/api/login/info` | 获取登录信息 |
| `GET` | `/api/admin/status` | 查询登录状态 |
| `POST` | `/api/admin/logout` | 退出登录 |
完整的接口文档请访问 http://localhost:5000/api/docs
---
## 配置说明
复制 `env.example` 为 `.env`,登录后凭证会自动保存:
```bash
cp env.example .env
```
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `WECHAT_TOKEN` | 微信 Token(登录后自动填充) | - |
| `WECHAT_COOKIE` | 微信 Cookie(登录后自动填充) | - |
| `WECHAT_FAKEID` | 公众号 FakeID(登录后自动填充) | - |
| `WEBHOOK_URL` | Webhook 通知地址(可选) | 空 |
| `RATE_LIMIT_GLOBAL` | 全局每分钟请求上限 | 10 |
| `RATE_LIMIT_PER_IP` | 单 IP 每分钟请求上限 | 5 |
| `RATE_LIMIT_ARTICLE_INTERVAL` | 文章请求最小间隔(秒) | 3 |
| `RSS_POLL_INTERVAL` | RSS 轮询间隔(秒) | 3600 |
| `RSS_FETCH_FULL_CONTENT` | RSS 是否获取完整内容(true/false) | true |
| `PROXY_URLS` | **SOCKS5 代理池地址(强烈建议配置,避免账号风控)** | 空 |
| `SITE_URL` | **网站访问地址(用于RSS图片代理,必须配置)** | http://localhost:5000 |
| `PORT` | 服务端口 | 5000 |
| `HOST` | 监听地址 | 0.0.0.0 |
| `DEBUG` | 调试模式(开启热重载) | false |
> **⚠️ 重要**: `SITE_URL` 必须配置为实际访问地址(IP或域名),否则RSS图片无法正常显示。例如:
> - 本地开发: `http://localhost:5000`
> - 局域网部署: `http://192.168.1.100:5000`
> - 公网域名: `https://你的域名.com`
### SOCKS5 代理池配置(⚠️ 强烈建议)
**重要提示**:
- ⚠️ **启用完整内容获取时,强烈建议配置代理池,避免账号被微信风控**
- ⚠️ **不配置代理直连微信可能导致:频繁验证、账号限制、IP封禁**
- ✅ **配置2-3个代理IP可有效分散请求,降低风控风险**
**用途**:获取文章完整内容时分散请求 IP,配合 Chrome TLS 指纹模拟,有效规避微信风控。
> 本项目使用 `curl_cffi` 模拟 Chrome TLS 指纹,请求特征与真实浏览器一致,配合代理池效果更佳。
**方案:多台 VPS 自建 SOCKS5 代理**
准备 2-3 台低价 VPS(各大云厂商轻量应用服务器即可,¥20-30/月/台),每台运行一个 SOCKS5 代理服务。推荐 [gost](https://github.com/go-gost/gost)(Go 语言实现,单二进制文件,无依赖)。
**第一步:在每台 VPS 上安装 gost**
```bash
# 下载最新版(以 Linux amd64 为例,其他架构请去 GitHub Releases 页面选择)
# 国外服务器直接下载
wget https://github.com/go-gost/gost/releases/download/v3.2.6/gost_3.2.6_linux_amd64.tar.gz
# 国内服务器使用加速镜像(任选一个可用的)
wget https://gh-proxy.com/https://github.com/go-gost/gost/releases/download/v3.2.6/gost_3.2.6_linux_amd64.tar.gz
# 或
wget https://ghproxy.cc/https://github.com/go-gost/gost/releases/download/v3.2.6/gost_3.2.6_linux_amd64.tar.gz
# 解压并移动到系统路径
tar -xzf gost_3.2.6_linux_amd64.tar.gz
mv gost /usr/local/bin/
chmod +x /usr/local/bin/gost
# 验证安装
gost -V
```
**第二步:启动 SOCKS5 代理服务**
```bash
# 带用户名密码认证(推荐,替换 myuser / mypass 和端口)
gost -L socks5://myuser:mypass@:1080
# 不带认证(仅内网或已配置防火墙时使用)
gost -L socks5://:1080
```
**第三步:配置为 systemd 服务(开机自启)**
```bash
cat > /etc/systemd/system/gost.service << 'EOF'
[Unit]
Description=GOST Proxy
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gost -L socks5://myuser:mypass@:1080
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable gost
systemctl start gost
```
**第四步:开放防火墙端口**
```bash
# 仅允许你的主服务器 IP 连接(替换为实际 IP)
ufw allow from YOUR_MAIN_SERVER_IP to any port 1080
# 或者如果用的是云厂商安全组,在控制台添加入站规则:
# 端口 1080 / TCP / 来源 IP 限制为你的主服务器
```
**第五步:在主服务器 `.env` 中配置代理池**
```bash
PROXY_URLS=socks5://myuser:mypass@vps1-ip:1080,socks5://myuser:mypass@vps2-ip:1080,socks5://myuser:mypass@vps3-ip:1080
```
配置后重启服务,每次文章请求会轮流使用不同的代理 IP。可以通过 `GET /api/health` 确认代理池状态。留空则直连(默认行为)。
---
## 项目结构
```
├── app.py # FastAPI 主应用
├── requirements.txt # Python 依赖
├── env.example # 环境变量示例
├── data/ # 数据目录(运行时自动创建)
│ └── rss.db # RSS 订阅 SQLite 数据库
├── routes/ # API 路由
│ ├── article.py # 文章内容获取
│ ├── articles.py # 文章列表
│ ├── rss.py # RSS 订阅管理与输出
│ ├── search.py # 公众号搜索
│ ├── login.py # 扫码登录
│ ├── admin.py # 管理接口
│ ├── image.py # 图片代理
│ ├── health.py # 健康检查
│ └── stats.py # 统计信息
├── utils/ # 工具模块
│ ├── auth_manager.py # 认证管理
│ ├── helpers.py # HTML 解析
│ ├── http_client.py # HTTP 客户端(curl_cffi + 代理池)
│ ├── proxy_pool.py # 代理池轮转
│ ├── rate_limiter.py # 限频器
│ ├── rss_store.py # RSS 数据存储(SQLite)
│ ├── rss_poller.py # RSS 后台轮询器
│ ├── content_processor.py # 内容处理与图片代理
│ ├── image_proxy.py # 图片URL代理工具
│ ├── article_fetcher.py # 批量并发获取文章
│ └── webhook.py # Webhook 通知
└── static/ # 前端页面(含 RSS 管理)
```
---
## 内容类型与获取策略
本项目支持多种微信公众号内容类型,包括标准富文本、纯图片文章、图文消息、短内容、音频文章等。
详细说明请查看:**[CONTENT_TYPES.md](CONTENT_TYPES.md)**
**文档内容**:
- 所有支持的内容类型及 `item_show_type` 值
- 不可用状态识别(删除、违规、隐私、验证页面等)
- 反爬策略与代理配置
- 关键函数说明
- 开发贡献指南
---
## 常见问题
<details>
<summary><b>提示"服务器未登录"</b></summary>
访问 http://localhost:5000/login.html 扫码登录,凭证会自动保存到 `.env`。
</details>
<details>
<summary><b>触发微信风控 / 需要验证</b></summary>
1. 在浏览器中打开提示的文章 URL 完成验证
2. 等待 30 分钟后重试
3. 降低请求频率(系统已内置自动限频)
</details>
<details>
<summary><b>如何获取公众号的 FakeID</b></summary>
调用搜索接口:`GET /api/public/searchbiz?query=公众号名称`,从返回结果的 `fakeid` 字段获取。
</details>
<details>
<summary><b>Token 多久过期</b></summary>
Cookie 登录有效期约 4 天,过期后需重新扫码登录。配置 `WEBHOOK_URL` 可以在过期时收到通知。
</details>
<details>
<summary><b>可以同时登录多个公众号吗</b></summary>
当前版本不支持多账号。建议部署多个实例,每个登录不同公众号。
</details>
---
## 技术栈
| 层级 | 技术 |
|------|------|
| **Web 框架** | FastAPI |
| **ASGI 服务器** | Uvicorn |
| **HTTP 客户端** | curl_cffi(Chrome TLS 指纹)/ HTTPX(降级) |
| **反风控** | TLS 指纹模拟 + SOCKS5/HTTP 代理池轮转 |
| **RSS 存储** | SQLite(零配置,数据本地化) |
| **配置管理** | python-dotenv |
| **运行环境** | Python 3.8+ |
---
## 开源协议
本项目采用 **AGPL 3.0** 协议开源,**所有功能代码完整公开,私有化部署完全免费**。
| 使用场景 | 是否允许 |
|---------|---------|
| 个人学习和研究 | 允许,免费使用 |
| 企业内部使用 | 允许,免费使用 |
| 私有化部署 | 允许,免费使用 |
| 修改后对外提供网络服务 | 需开源修改后的代码 |
详见 [LICENSE](LICENSE) 文件。
### 免责声明
- 本软件按"原样"提供,不提供任何形式的担保
- 本项目仅供学习和研究目的,请遵守微信公众平台相关服务条款
- 使用者对自己的操作承担全部责任
- 因使用本软件导致的任何损失,开发者不承担责任
---
## 参与贡献
由于个人精力有限,目前**暂不接受 PR**,但非常欢迎:
- **提交 Issue** — 报告 Bug、提出功能建议
- **Fork 项目** — 自由修改和定制
- **Star 支持** — 给项目点 Star,让更多人看到
---
## 联系方式
<table>
<tr>
<td align="center">
<img src="assets/qrcode/wechat.jpg" width="200"><br>
<b>个人微信</b><br>
<em>技术交流 · 商务合作</em>
</td>
<td align="center">
<img src="assets/qrcode/sponsor.jpg" width="200"><br>
<b>赞赏支持</b><br>
<em>开源不易,感谢支持</em>
</td>
</tr>
</table>
- **GitHub Issues**: [提交问题](https://github.com/tmwgsicp/wechat-download-api/issues)
- **邮箱**: creator@waytomaster.com
- **SaaS 托管版**: [wechatrss.waytomaster.com](https://wechatrss.waytomaster.com)
---
## 致谢
- [FastAPI](https://fastapi.tiangolo.com/) — 高性能 Python Web 框架
- [curl_cffi](https://github.com/lexiforest/curl_cffi) — 支持浏览器 TLS 指纹模拟的 HTTP 客户端
- [HTTPX](https://www.python-httpx.org/) — 现代化 HTTP 客户端
- [gost](https://github.com/go-gost/gost) — 轻量级代理工具
---
<div align="center">
**如果觉得项目有用,请给个 Star 支持一下!**
[](https://star-history.com/#tmwgsicp/wechat-download-api&Date)
Made with ❤️ by [tmwgsicp](https://github.com/tmwgsicp)
</div>
Excellent! This is a very detailed README. Now let me analyze the key modules to understand the architecture better. Let me check the app.py, routes, and utils to create a comprehensive architecture diagram.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/routes" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
__init__.py
admin.py
article.py
articles.py
health.py
image.py
login.py
rss.py
search.py
stats.py
Let me check the utils folder and key modules.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/utils" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
__init__.py
article_fetcher.py
auth_manager.py
content_processor.py
helpers.py
http_client.py
image_proxy.py
proxy_pool.py
rate_limiter.py
rss_poller.py
rss_store.py
webhook.py
Let me check some of the key files to understand the core logic.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/utils/http_client.py" | python3 -c "import sys,json; d=json.load(sys.stdin); import base64; print(base64.b64decode(d['content']).decode()[:4000])"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2026 tmwgsicp
# Licensed under the GNU Affero General Public License v3.0
# See LICENSE file in the project root for full license text.
# SPDX-License-Identifier: AGPL-3.0-only
"""
HTTP 客户端封装
优先使用 curl_cffi(模拟 Chrome TLS 指纹),不可用时自动降级到 httpx。
支持代理池轮转:当前代理失败 → 尝试下一个 → 全部失败 → 直连兜底。
注意:
1. curl_cffi 的 AsyncSession 在部分环境下 SOCKS5 代理不工作,
因此代理场景使用同步 Session + 线程池来规避此问题。
2. 优先使用 SOCKS5 代理,避免被封禁。
"""
import asyncio
import logging
from concurrent.futures import ThreadPoolExecutor
from typing import Optional, Dict
logger = logging.getLogger(__name__)
try:
from curl_cffi.requests import Session as CurlSession
HAS_CURL_CFFI = True
except ImportError:
HAS_CURL_CFFI = False
ENGINE_NAME = "curl_cffi (Chrome TLS)" if HAS_CURL_CFFI else "httpx (fallback)"
logger.info("HTTP engine: %s", ENGINE_NAME)
BROWSER_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Ch-Ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
}
MAX_PROXY_RETRIES = 3
_executor = ThreadPoolExecutor(max_workers=4)
async def fetch_page(url: str, extra_headers: Optional[Dict] = None,
timeout: int = 30) -> str:
"""
获取网页 HTML 内容。
请求策略:代理1 → 代理2 → ... → 直连兜底。
成功的代理会被标记为健康,失败的会被临时冷却。
"""
from utils.proxy_pool import proxy_pool
headers = {**BROWSER_HEADERS}
if extra_headers:
headers.update(extra_headers)
tried_proxies = []
for _ in range(min(MAX_PROXY_RETRIES, proxy_pool.count)):
proxy = proxy_pool.next()
if proxy is None or proxy in tried_proxies:
break
tried_proxies.append(proxy)
logger.info("fetch_page: url=%s proxy=%s", url[:80], proxy)
try:
result = await _do_fetch(url, headers, timeout, proxy)
proxy_pool.mark_ok(proxy)
return result
except Exception as e:
logger.warning("Proxy %s failed: %s", proxy, e)
proxy_pool.mark_failed(proxy)
logger.info("fetch_page: url=%s proxy=direct (fallback)", url[:80])
return await _do_fetch(url, headers, timeout, None)
async def _do_fetch(url: str, headers: Dict, timeout: int,
proxy: Optional[str]) -> str:
"""执行实际的HTTP请求"""
# SOCKS5 代理或无代理:正常请求
if HAS_CURL_CFFI:
return await _fetch_curl_cffi(url, headers, timeout, proxy)
return await _fetch_httpx(url, headers, timeout, proxy)
async def _fetch_curl_cffi(url: str, headers: Dict, timeout: int,
proxy: Optional[str]) -> str:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
_executor,
_fetch_curl_cffi_sync, url, headers, timeout, proxy
)
def _fetch_curl_cffi_sync(url: str, headers: Dict, timeout: int,
proxy: Optional[str]) -> str:
"""同步请求,在线程池中执行。规避 AsyncSession + SOCKS5 代理的兼容性问题。"""
kwargs = {"timeout": timeout, "allow_redirects": True, "verify": False} # 跳过 SSL 验证
if proxy:
kwargs["proxy"] = proxy
with CurlSession(impersonate="chrome120") as session:
resp = session.get(url, headers=headers, **kwargs)
resp.raise_for_status()
return resp.text
async def _fetch_httpx(url: str, headers: Dict, timeout: int,
proxy: Optional[str]) -> str:
import httpx
transport_kwargs = {}
if proxy:
Now I have a good understanding of the project. Let me check one more key file - the auth_manager.py to understand the login flow.
Command:
curl -sL "https://api.github.com/repos/tmwgsicp/wechat-download-api/contents/utils/auth_manager.py" | python3 -c "import sys,json; d=json.load(sys.stdin); import base64; print(base64.b64decode(d['content']).decode()[:3000])"
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2026 tmwgsicp
# Licensed under the GNU Affero General Public License v3.0
# See LICENSE file in the project root for full license text.
# SPDX-License-Identifier: AGPL-3.0-only
"""
认证管理器 - FastAPI版本
管理微信登录凭证(Token、Cookie等)
"""
import os
import time
from pathlib import Path
from typing import Optional, Dict
from dotenv import load_dotenv, set_key
class AuthManager:
"""认证管理单例类"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(AuthManager, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
# 设置.env文件路径(python-api目录下)
self.base_dir = Path(__file__).parent.parent
self.env_path = self.base_dir / ".env"
# Docker环境下的凭证文件(存储在data目录,权限更可靠)
self.credentials_file = self.base_dir / "data" / ".credentials.json"
# 加载环境变量
self._load_credentials()
self._initialized = True
def _load_credentials(self):
"""
从多个来源加载凭证,优先级:
1. data/.credentials.json (Docker环境推荐)
2. .env 文件 (本地部署)
3. 环境变量
"""
# 先尝试从 JSON 凭证文件加载(Docker 环境)
if self.credentials_file.exists():
try:
import json
with open(self.credentials_file, 'r', encoding='utf-8') as f:
self.credentials = json.load(f)
return
except Exception as e:
print(f"Warning: Failed to load credentials from {self.credentials_file}: {e}")
# 回退到 .env 文件(本地部署)
if self.env_path.exists():
load_dotenv(self.env_path, override=True)
self.credentials = {
"token": os.getenv("WECHAT_TOKEN", ""),
"cookie": os.getenv("WECHAT_COOKIE", ""),
"fakeid": os.getenv("WECHAT_FAKEID", ""),
"nickname": os.getenv("WECHAT_NICKNAME", ""),
"expire_time": int(os.getenv("WECHAT_EXPIRE_TIME") or 0)
}
def save_credentials(self, token: str, cookie: str, fakeid: str,
nickname: str, expire_time: int) -> bool:
"""
保存凭证,支持双存储策略:
1. 优先保存到 data/.credentials.json (Docker环境推荐,权限可靠)
2. 同时尝试保存到 .env (本地部署兼容)
Args:
token: 微信Token
cookie: 微信Cookie
fakeid: 公众号ID
nickname: 公众号名称
expire_time: 过期时间(毫秒时间戳)
Returns:
保存是否成功
"""
# 更新内存中的凭证
self.credentials.update({
"token": token,
"cookie": cookie,
"fakeid": fakeid,
"nickname": nickname,
"expire_time": expire_time
})
success = False
# 策略1: 保存到 data/.credentials.json (Docker 环境优先)
try:
Now I have enough information to create a comprehensive architecture diagram. Let me summarize the project.
The user wants me to compare wechat-download-api with another project: rachelos/we-mp-rss. Let me first get information about this second project.
Command:
curl -sL "https://api.github.com/repos/rachelos/we-mp-rss"
{
"id": 983305992,
"node_id": "R_kgDOOpwPCA",
"name": "we-mp-rss",
"full_name": "rachelos/we-mp-rss",
"private": false,
"owner": {
"login": "rachelos",
"id": 190435456,
"node_id": "O_kgDOC1nQgA",
"avatar_url": "https://avatars.githubusercontent.com/u/190435456?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/rachelos",
"html_url": "https://github.com/rachelos",
"followers_url": "https://api.github.com/users/rachelos/followers",
"following_url": "https://api.github.com/users/rachelos/following{/other_user}",
"gists_url": "https://api.github.com/users/rachelos/gists{/gist_id}",
"starred_url": "https://api.github.com/users/rachelos/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/rachelos/subscriptions",
"organizations_url": "https://api.github.com/users/rachelos/orgs",
"repos_url": "https://api.github.com/users/rachelos/repos",
"events_url": "https://api.github.com/users/rachelos/events{/privacy}",
"received_events_url": "https://api.github.com/users/rachelos/received_events",
"type": "Organization",
"user_view_type": "public",
"site_admin": false
},
"html_url": "https://github.com/rachelos/we-mp-rss",
"description": "✨符合阅读习惯的微信公众号助手、微信公众号转MarkDown、微信公众号转PDF、定时更新订阅公众号文章、生成微信公众号RSS订阅源、导出微信公众号订阅源、支持微信公众号Webhook/微信公众号API/AI Agent接入微信公众号微信公众号、订阅微信公众号、微信公众号助手 、微信公众号阅读、微信公众号接口、微信公众号爬虫、微信公众号监测、标签订阅微信公众号、微信公众号源、微信公众号读书、微信公众号文章、微信公众号框架、微信公众号管理、微信公众号源、微信公众号平台、微信公众号代码、微信公众号系统、微信公众号源码",
"fork": false,
"url": "https://api.github.com/repos/rachelos/we-mp-rss",
"forks_url": "https://api.github.com/repos/rachelos/we-mp-rss/forks",
"keys_url": "https://api.github.com/repos/rachelos/we-mp-rss/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/rachelos/we-mp-rss/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/rachelos/we-mp-rss/teams",
"hooks_url": "https://api.github.com/repos/rachelos/we-mp-rss/hooks",
"issue_events_url": "https://api.github.com/repos/rachelos/we-mp-rss/issues/events{/number}",
"events_url": "https://api.github.com/repos/rachelos/we-mp-rss/events",
"assignees_url": "https://api.github.com/repos/rachelos/we-mp-rss/assignees{/user}",
"branches_url": "https://api.github.com/repos/rachelos/we-mp-rss/branches{/branch}",
"tags_url": "https://api.github.com/repos/rachelos/we-mp-rss/tags",
"blobs_url": "https://api.github.com/repos/rachelos/we-mp-rss/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/rachelos/we-mp-rss/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/rachelos/we-mp-rss/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/rachelos/we-mp-rss/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/rachelos/we-mp-rss/statuses/{sha}",
"languages_url": "https://api.github.com/repos/rachelos/we-mp-rss/languages",
"stargazers_url": "https://api.github.com/repos/rachelos/we-mp-rss/stargazers",
"contributors_url": "https://api.github.com/repos/rachelos/we-mp-rss/contributors",
"subscribers_url": "https://api.github.com/repos/rachelos/we-mp-rss/subscribers",
"subscription_url": "https://api.github.com/repos/rachelos/we-mp-rss/subscription",
"commits_url": "https://api.github.com/repos/rachelos/we-mp-rss/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/rachelos/we-mp-rss/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/rachelos/we-mp-rss/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/rachelos/we-mp-rss/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/rachelos/we-mp-rss/contents/{+path}",
"compare_url": "https://api.github.com/repos/rachelos/we-mp-rss/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/rachelos/we-mp-rss/merges",
"archive_url": "https://api.github.com/repos/rachelos/we-mp-rss/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/rachelos/we-mp-rss/downloads",
"issues_url": "https://api.github.com/repos/rachelos/we-mp-rss/issues{/number}",
"pulls_url": "https://api.github.com/repos/rachelos/we-mp-rss/pulls{/number}",
"milestones_url": "https://api.github.com/repos/rachelos/we-mp-rss/milestones{/number}",
"notifications_url": "https://api.github.com/repos/rachelos/we-mp-rss/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/rachelos/we-mp-rss/labels{/name}",
"releases_url": "https://api.github.com/repos/rachelos/we-mp-rss/releases{/id}",
"deployments_url": "https://api.github.com/repos/rachelos/we-mp-rss/deployments",
"created_at": "2025-05-14T07:28:21Z",
"updated_at": "2026-03-27T03:52:47Z",
"pushed_at": "2026-03-27T03:52:42Z",
"git_url": "git://github.com/rachelos/we-mp-rss.git",
"ssh_url": "git@github.com:rachelos/we-mp-rss.git",
"clone_url": "https://github.com/rachelos/we-mp-rss.git",
"svn_url": "https://github.com/rachelos/we-mp-rss",
"homepage": "https://werss.csol.store",
"size": 191579,
"stargazers_count": 2620,
"watchers_count": 2620,
"language": "Python",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": true,
"forks_count": 455,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 4,
"license": {
"key": "other",
"name": "Other",
"spdx_id": "NOASSERTION",
"url": null,
"node_id": "MDc6TGljZW5zZTA="
},
"allow_forking": true,
"is_template": false,
"web_commit_signoff_required": false,
"has_pull_requests": true,
"pull_request_creation_policy": "all",
"topics": [
"folo",
"mysql",
"rss-feed",
"rss-hub",
"rsshub",
"we-mp-rss",
"weichat",
"weixin",
"weread",
"werss",
"wexinrss",
"wx",
"wxsprider"
],
"visibility": "public",
"forks": 455,
"open_issues": 4,
"watchers": 2620,
"default_branch": "main",
"temp_clone_token": null,
"custom_properties": {
},
"organization": {
"login": "rachelos",
"id": 190435456,
"node_id": "O_kgDOC1nQgA",
"avatar_url": "https://avatars.githubusercontent.com/u/190435456?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/rachelos",
"html_url": "https://github.com/rachelos",
"followers_url": "https://api.github.com/users/rachelos/followers",
"following_url": "https://api.github.com/users/rachelos/following{/other_user}",
"gists_url": "https://api.github.com/users/rachelos/gists{/gist_id}",
"starred_url": "https://api.github.com/users/rachelos/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/rachelos/subscriptions",
"organizations_url": "https://api.github.com/users/rachelos/orgs",
"repos_url": "https://api.github.com/users/rachelos/repos",
"events_url": "https://api.github.com/users/rachelos/events{/privacy}",
"received_events_url": "https://api.github.com/users/rachelos/received_events",
"type": "Organization",
"user_view_type": "public",
"site_admin": false
},
"network_count": 455,
"subscribers_count": 13
}
Wow, this is a much more popular project - 2620 stars, 455 forks! Let me get more details about its structure and features.
Command:
curl -sL "https://api.github.com/repos/rachelos/we-mp-rss/contents" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
.dockerignore
.env.example
.github
.gitignore
.vscode
AGENTS.md
CONTRIBUTING.md
Dockerfile
Dockerfiles
FIX_CASCADE_CONFIG.md
LICENSE
README.zh-CN.md
ReadMe.md
SECURITY.md
TROUBLESHOOTING_CASCADE.md
apis
atack.py
check_cascade_creds.py
compose
config-node.yaml
config.example.yaml
core
data_sync.py
debug_template_parser.py
diagnose_config.py
dns.py
doc2pdf
dock_build.bat
docs
domains.txt
driver
examples
github_update.py
go.mod
init_sys.py
install.sh
job.py
jobs
main.py
migrations
public
publish.bat
qtserver
quick_check_parent.py
requirements.txt
schemas
script
start.bat
start.sh
static
test_article.py
tool.py
tools
update.sh
verify_cascade_creds.py
verify_imports.py
views
web.py
web_ui
This is a much larger and more complex project. It has:
- Python core
- Go module (go.mod)
- web_ui (likely Vue or similar frontend)
- doc2pdf (document to PDF conversion)
- APIs
- etc.
Let me get more details about the features and architecture.
Command:
curl -sL "https://api.github.com/repos/rachelos/we-mp-rss/contents/README.zh-CN.md" | python3 -c "import sys,json; d=json.load(sys.stdin); import base64; print(base64.b64decode(d['content']).decode()[:8000])"
<div align=center>
<img src="static/logo.svg" alt="We-MP-RSS Logo" width="20%">
<h1>WeRSS - 微信公众号订阅助手</h1>
[]()
[]()
[中文](README.zh-CN.md)|[English](ReadMe.md)
快速运行
```
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data ghcr.io/rachelos/we-mp-rss:latest
```
http://<您的ip>:8001/ 即可开启
# 快速升级
```
docker stop we-mp-rss
docker rm we-mp-rss
docker pull ghcr.io/rachelos/we-mp-rss:latest
# 如果添加了其它参数,请自行修改
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data ghcr.io/rachelos/we-mp-rss:latest
```
# 官方镜像
```
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data rachelos/we-mp-rss:latest
```
# 代理镜像加速访问(国内访问速度更快)
```
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data docker.1ms.run/rachelos/we-mp-rss:latest
```
# 感谢伙伴(排名不分先后)
cyChaos、 子健MeLift、 晨阳、 童总、 胜宇、 军亮、 余光、 一路向北、 水煮土豆丝、 人可、 须臾、 澄明
、五梭
<br/>
<img src="https://github.com/user-attachments/assets/cbe924f2-d8b0-48b0-814e-7c06ccb1911c" height="60" />
<img src="https://github.com/user-attachments/assets/6997a236-3df3-49d5-98a4-514f6d1a02c4" height="60" />
<br />
<br />
<a href="https://github.com/RSSNext/Folo/stargazers"><img src="https://img.shields.io/github/stars/RSSNext/Follow?color=ffcb47&labelColor=black&style=flat-square&logo=github&label=Stars" /></a>
<a href="https://github.com/RSSNext/Folo/graphs/contributors"><img src="https://img.shields.io/github/contributors/RSSNext/Folo?style=flat-square&logo=github&label=Contributors&labelColor=black" /></a>
<a href="https://status.follow.is/" target="_blank"><img src="https://status.follow.is/api/badge/18/uptime?color=%2344CC10&labelColor=black&style=flat-square"/></a>
<a href="https://github.com/RSSNext/Folo/releases"><img src="https://img.shields.io/github/downloads/RSSNext/Folo/total?color=369eff&labelColor=black&logo=github&style=flat-square&label=Downloads" /></a>
<a href="https://x.com/intent/follow?screen_name=folo_is"><img src="https://img.shields.io/badge/Follow-blue?color=1d9bf0&logo=x&labelColor=black&style=flat-square" /></a>
<a href="https://discord.gg/followapp" target="_blank"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscord.com%2Fapi%2Finvites%2Ffollowapp%3Fwith_counts%3Dtrue&query=approximate_member_count&color=5865F2&label=Discord&labelColor=black&logo=discord&logoColor=white&style=flat-square"/></a>
<br />
一个用于订阅和管理微信公众号内容的工具,提供RSS订阅功能。
</div>
<p align="center">
<a href="https://github.com/DIYgod/sponsors">
<img src="https://raw.githubusercontent.com/DIYgod/sponsors/main/sponsors.wide.svg" />
</a>
</p>
## 功能特性
- 微信公众号内容抓取和解析
- RSS订阅生成
- 用户友好的Web管理界面
- 定时自动更新内容
- 支持多种数据库(默认SQLite,可选MySQL)
- 支持多种抓取方式
- 支持多种RSS客户端
- 支持授权过期提醒
- 支持自定义通知渠道
- 支持自定义RSS标题、描述、封面
- 支持自定义RSS分页大小
- 支持导出md/docx/pdf/json格式
- 支持API接口调用/WebHook调用
# ❤️ 赞助
如果觉得 We-MP-RSS 对你有帮助,欢迎给我来一杯啤酒!<br/>
<img src="docs/赞赏码.jpg" width=180/>
[Paypal](https://www.paypal.com/ncp/payment/PUA72WYLAV5KW)
## 界面截图
- 登录界面
<img src="docs/登录.png" alt="登录" width="80%"/><br/>
- 主界面
<img src="docs/主界面.png" alt="主界面" width="80%"/><br/>
- 扫码授权
<img src="docs/扫码授权.png" alt="扫码授权" width="80%"/><br/>
- 添加订阅
<img src="docs/添加订阅.png" alt="添加订阅" width="80%"/><br/>
- 客户端应用<br/>
<img src="docs/folo.webp" alt="FOLO客户端应用" width="80%"/><br/>
## 系统架构
项目采用前后端分离架构:
- 后端:Python + FastAPI
- 前端:Vue 3 + Vite
- 数据库:SQLite (默认)/MySQL
<img src="docs/架构原理.png" alt="架构原理" width="80%"/>
更多项目原理,请参考[项目文档](https://deepwiki.com/rachelos/we-mp-rss/3.5-notification-system)。
## 安装指南
# 二次开发
## 环境需求
- Python>=3.13.1
- Node>=20.18.3
### 后端服务
1. 克隆项目
```bash
git clone https://github.com/rachelos/we-mp-rss.git
cd we-mp-rss
```
2. 安装Python依赖
```bash
pip install -r requirements.txt
```
3. 配置数据库
复制并修改配置文件:
```bash
cp config.example.yaml config.yaml
copy config.example.yaml config.yaml
```
3. 启动服务
```bash
python main.py -job True -init True
```
## 前端开发
1. 安装前端依赖
```bash
cd we-mp-rss/web_ui
yarn install
```
2. 启动前端服务
```bash
yarn dev
```
3. 访问前端页面
```
http://localhost:3000
```
# 环境变量配置
以下是 `config.yaml` 中支持的环境变量配置:
| 环境变量 | 默认值 | 描述 |
|----------|--------|------|
| `APP_NAME` | `we-mp-rss` | 应用名称 |
| `SERVER_NAME` | `we-mp-rss` | 服务名称 |
| `WEB_NAME` | `WeRSS微信公众号订阅助手` | 前端显示名称 |
| `WERSS_AUTH_WEB` | `False` | 通过web方式授权 |
| `BROWSER_TYPE` | `firefox` | 浏览器类型默认firefox |
| `SEND_CODE` | `True` | 是否发送授权二维码通知 |
| `CODE_TITLE` | `WeRSS授权二维码` | 二维码通知标题 |
| `ENABLE_JOB` | `True` | 是否启用定时任务 |
| `AUTO_RELOAD` | `False` | 代码修改自动重启服务 |
| `THREADS` | `2` | 最大线程数 |
| `DB` | `sqlite:///data/db.db` | 数据库连接字符串 |
| `DINGDING_WEBHOOK` | 空 | 钉钉通知Webhook地址 |
| `WECHAT_WEBHOOK` | 空 | 微信通知Webhook地址 |
| `FEISHU_WEBHOOK` | 空 | 飞书通知Webhook地址 |
| `CUSTOM_WEBHOOK` | 空 | 自定义通知Webhook地址 |
| `SECRET_KEY` | `we-mp-rss` | 密钥 |
| `USER_AGENT` | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36/WeRss` | 用户代理 |
| `SPAN_INTERVAL` | `10` | 定时任务执行间隔(秒) |
| `WEBHOOK.CONTENT_FORMAT` | `html` | 文章内容发送格式 |
| `PORT` | `8001` | API服务端口 |
| `DEBUG` | `False` | 调试模式 |
| `MAX_PAGE` | `5` | 最大采集页数 |
| `RSS_BASE_URL` | 空 | RSS域名地址 |
| `RSS_LOCAL` | `False` | 是否为本地RSS链接 |
| `RSS_TITLE` | 空 | RSS标题 |
| `RSS_DESCRIPTION` | 空 | RSS描述 |
| `RSS_COVER` | 空 | RSS封面 |
| `RSS_FULL_CONTEXT` | `True` | 是否显示全文 |
| `RSS_ADD_COVER` | `True` | 是否添加封面图片 |
| `RSS_CDATA` | `False` | 是否启用CDATA |
| `RSS_PAGE_SIZE` | `30` | RSS分页大小 |
| `TOKEN_EXPIRE_MINUTES` | `4320` | 登录会话有效时长(分钟) |
| `CACHE.DIR` | `./data/cache` | 缓存目录 |
| `ARTICLE.TRUE_DELETE` | `False` | 是否真实删除文章 |
| `GATHER.CONTENT` | `True` | 是否采集内容 |
| `GATHER.MODEL` | `app` | 采集模式 |
| `GATHER.CONTENT_AUTO_CHECK` | `False` | 是否自动检查未采集文章内容 |
| `GATHER.CONTENT_AUTO_INTERVAL` | `59` | 自动检查未采集文章内容的时间间隔(分钟) |
| `GATHER.CONTENT_MODE` | `web` | 内容修正模式 |
| `SAFE_HIDE_CONFIG` | `db,secret,token,notice.wechat,notice.feishu,notice.dingding` | 需要隐藏的配置信息 |
| `SAFE_LIC_KEY` | `RACHELOS` | 授权加密KEY |
| `LOG_FILE` | 空 | 日志文件路径 |
| `LOG_LEVEL` | `INFO` | 日志级别 |
| `EXPORT_PDF` | `False` | 是否启用PDF导出功能 |
| `EXPORT_PDF_DIR` | `./data/pdf` | PDF导出目录 |
| `EXPORT_MARKDOWN` | `False` | 是否启用markdown导出功能 |
| `EXPORT_MARKDOWN_DIR` | `./data/markdown` | markdown导出目录 |
# 使用说明
1. 启动服务后,访问 `http://<您的IP>:8001` 进入管理界面。
2. 使用微信扫码授权后,即可添加和管理订阅。
3. 定时任务会自动更新内容,并生成RSS订阅链接。
## Access Key 认证
WeRSS 支持使用 Access Key (AK) 进行 API 认证,适用于程序化访问和自动化脚本。
### 创建 Access Key
1. 登录 WeRSS 管理界面
2. 进入"Access Key 管理"页面
3. 点击"创建 Access Key"按钮
4. 填写名称、描述、权限和过期时间
5. 创建成功后,妥善保存 Access Key 和 Secret Key(Secret Key 只显示一次)
### 使用 Access Key 调用 API
在请求头中添加 `Authorization` 字段,格式为 `AK-SK {access_key}:{secret_key}`:
```bash
curl -H "Authorization: AK-SK your_access_key:your_secret_key" \
http://localhost:8001/api/feeds
```
#### Python 示例
```python
import requests
access_key = "your_access_key"
secret_key = "your_secret_key"
base_url = "http://localhost:8001"
headers = {
"Authorization": f"AK-SK {access_key}:{secret_key}"
}
# 获取订阅列表
response = requests.get(f"{base_url}/api/feeds", headers=headers)
print(response.json())
```
#### JavaScript 示例
```javascript
const accessKey = "your_access_key";
const secretKey = "your_secret_key";
const baseUrl = "http://localhost:8001";
const headers = {
"Authorization": `AK-SK ${accessKey}:${secretKey}`
};
// 获取订阅列表
fetch(`${baseUrl}/api/feeds`, { headers })
.then(res => res.json())
.then(data => console.log(data));
```
详细文档请参考:[AK 认证指南](docs/AK_Authentication_Guide.md)
# 常见问题
- **如何修改数据库连接?**
在 `config.yaml` 中修改 `db` 配置项,或通过环境变量 `DB` 覆盖。
- **如何启用钉钉通知?**
在 `config.yaml` 中填写 `notice.dingding` 或通过环境变量 `DINGDING_WEBHOOK` 设置。
- **如何调整定时任务间隔?**
修改 `config.yaml` 中的
This is a very comprehensive project. Let me get the directory structure to understand it better and compare the two projects.
Command:
curl -sL "https://api.github.com/repos/rachelos/we-mp-rss/contents/core" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
article_content.py
article_lax.py
auth.py
base.py
cache.py
cascade.py
common
config.py
content_format.py
database.py
db.py
file.py
lax
log.py
models
notice
print.py
queue
redis_client.py
res
resource.py
rss.py
task
thread.py
ver.py
wait.py
webhook
wx
yaml_db
This is a much larger project. Let me summarize the comparison between the two projects.
Command:
curl -sL "https://api.github.com/repos/rachelos/we-mp-rss/contents/apis" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(f['name']) for f in d]"
article.py
auth.py
base.py
cache.py
cascade.py
config_management.py
env_exception.py
export.py
filter_rule.py
github_update.py
message_task.py
mps.py
res.py
rss.py
sys_info.py
tags.py
tools.py
user.py
ver.py
Now I have enough information to compare the two projects. Let me create a comprehensive comparison.