# MPArticleMonitor **Repository Path**: xiaohua1875/MPArticleMonitor ## Basic Information - **Project Name**: MPArticleMonitor - **Description**: 基于 Python FastAPI 的微信公众号文章自动化采集系统,支持微信读书扫码登录(飞书推送二维码)、定时采集、互动数据抓取、数据面板统计、飞书提醒与 AI 文章分类。 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2026-08-26 - **Last Updated**: 2026-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: 微信公众号, FastAPI, Python ## README # 微信公众号文章数据采集系统 基于 Python FastAPI 的微信公众号文章自动化采集系统,支持微信读书扫码登录(飞书推送二维码)、定时采集、互动数据抓取、数据面板统计、飞书提醒与 AI 文章分类。 > 微信公众平台 `/cgi-bin/appmsg`、`/cgi-bin/appmsgpublish` 接口已永久失效, > 本系统采集链路已迁移至**微信读书(WeRead)Web 接口**: > 文章列表来自 `weread.qq.com/web/mp/articles`,公众号标识由 `__biz` > 派生为 `MP_WXS_{数字}` bookId,正文仍直接抓取公开文章页。 ## 技术栈 | 层面 | 技术 | |------|------| | 后端框架 | FastAPI(异步) | | 数据库 | PostgreSQL + SQLAlchemy 2.0(asyncpg 驱动) | | 任务调度 | APScheduler(AsyncIOScheduler) | | 爬虫引擎 | aiohttp(微信读书 Web 接口 + 文章正文抓取) | | 二维码 | qrcode + Pillow(渲染微信读书登录二维码,经飞书推送) | | 容器化 | Docker + Docker Compose | | 反向代理 | Caddy(自动 HTTPS) | | 配置管理 | Pydantic Settings(.env 文件) | | 日志 | Loguru | | AI 分类 | 阿里百炼 API(可选) | ## 快速开始 ### 前置条件 - Docker & Docker Compose - Python 3.11+(本地开发) ### Docker 部署(推荐) ```bash # 1. 配置环境变量 cp .env.example .env # 编辑 .env,按需修改数据库密码、飞书/百炼密钥等 # 2. 启动全部服务(app + Caddy 反向代理) docker compose -f docker-compose.prod.yml up -d # 3. 验证 curl http://localhost:8101/health # → {"status": "ok"} # 4. 访问 API 文档 # Swagger: http://localhost:8101/docs # ReDoc: http://localhost:8101/redoc ``` 数据库表在应用启动时自动创建,无需手动初始化。 ### 本地开发 ```bash # 1. 虚拟环境 python -m venv venv311 source venv311/bin/activate # 2. 安装依赖 pip install -r requirements.txt # 3. 启动 PostgreSQL(Docker) docker compose up -d db # 4. 启动应用 uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` ## 项目结构 ``` ├── docker-compose.yml # 本地开发环境 ├── docker-compose.prod.yml # 生产环境(app + Caddy) ├── Dockerfile ├── Caddyfile # Caddy 反向代理配置 ├── .env.example # 环境变量模板 ├── requirements.txt ├── README.md ├── app/ │ ├── main.py # FastAPI 入口(启动调度器、注册路由、自动建表/迁移) │ ├── config.py # 配置管理(Pydantic Settings) │ ├── database.py # 数据库引擎与会话工厂 │ ├── models.py # ORM 模型(7 张表) │ ├── schemas.py # Pydantic 请求/响应模型 │ ├── routers/ │ │ ├── accounts.py # 公众号管理 API │ │ ├── articles.py # 文章查询 API │ │ ├── dashboard.py # 数据面板 API │ │ ├── sessions.py # Session 管理 API │ │ ├── tasks.py # 任务调度 API(含采集批次、互动数据批次) │ │ └── reminder.py # 飞书提醒 API │ ├── services/ │ │ ├── scraper_service.py # 核心爬虫(微信读书 /web/mp/articles 游标分页) │ │ ├── status_service.py # 互动数据抓取(WorldTree API) │ │ ├── dashboard_service.py # 数据面板聚合统计 │ │ ├── classifier_service.py # AI 文章分类(阿里百炼) │ │ ├── feishu_service.py # 飞书消息推送 │ │ └── login_reminder.py # 飞书扫码登录提醒(微信读书二维码) │ ├── core/ │ │ ├── scheduler.py # APScheduler 定时任务管理(单例) │ │ └── exceptions.py # 自定义异常 │ └── utils/ │ ├── helpers.py # 工具函数(日期范围、时间解析、广告检测等) │ ├── weread.py # 微信读书客户端(登录/校验/文章列表/公众号解析) │ ├── tcaptcha.py # 腾讯图形验证码(-2041 风控时人工求解) │ └── open_api_client.py # 第三方开放接口客户端 ├── frontend/ │ ├── index.html # 数据看板 │ └── tasks.html # 任务管理页面 └── data/ ├── session/ # Session 运行时数据 └── runs/ # 爬取运行记录 ``` ## 数据库表 | 表名 | 说明 | |------|------| | `wechat_accounts` | 公众号列表(学校、类型、名称、mp_biz=微信读书 bookId、状态) | | `articles` | 文章基础信息(标题、链接、发布时间、位置、是否广告等) | | `article_status` | 文章互动数据快照(阅读/点赞/分享/收藏/评论 + 距发布天数) | | `wechat_sessions` | 微信读书 Session(wr_vid、wr_skey token、cookie) | | `scheduled_task_logs` | 定时任务执行日志(开始/结束时间、新增/跳过篇数、错误信息) | | `article_runs` | 文章采集执行批次(每次采集任务一条记录,实时状态与进度) | | `status_runs` | 互动数据抓取批次(每次 status_update 任务一条记录,支持失败恢复) | ### 表关系 ``` wechat_accounts (1) ──→ (N) articles (1) ──→ (0..1) article_status ↑ └── 通过 article_id 关联 scheduled_task_logs (1) ←── (0..N) article_runs scheduled_task_logs (1) ←── (0..N) status_runs ``` ## API 接口 ### 公众号管理 — `/api/accounts` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/accounts` | 列表(支持 `school`/`account_type`/`status` 筛选) | | POST | `/api/accounts` | 新增公众号 | | GET | `/api/accounts/{id}` | 查看详情 | | PUT | `/api/accounts/{id}` | 更新公众号(部分更新) | | DELETE | `/api/accounts/{id}` | 删除公众号 | | POST | `/api/accounts/{id}/refresh-fakeid` | 解析 mp_biz/bookId(文章链接经微信读书解析,或本地 base64 派生) | | POST | `/api/accounts/sync` | 从第三方接口同步账号列表 | ### Session 管理 — `/api/sessions` | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/sessions/login` | 触发微信读书扫码登录(二维码推送飞书,阻塞等待扫码) | | GET | `/api/sessions` | Session 列表 | | DELETE | `/api/sessions/{id}` | 删除 Session | | POST | `/api/sessions/{id}/test` | 测试 Session 有效性 | ### 文章查询 — `/api/articles` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/articles` | 文章列表(支持 `account_id`/`school`/`account_type`/`start_date`/`end_date`/`is_ad` 筛选 + 分页) | | GET | `/api/articles/{article_id}` | 文章详情(含互动数据) | | GET | `/api/articles/status/pending` | 已发布 ≥N 天但尚未抓取互动数据的文章 | ### 数据面板 — `/api/dashboard` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/dashboard/article-stats` | 文章发布统计(总量/头条/次条/活跃天数) | | GET | `/api/dashboard/ad-stats` | 广告文章统计(总量/头条/次条) | | GET | `/api/dashboard/full` | 完整数据面板(一次返回文章 + 广告统计) | | GET | `/api/dashboard/summary` | 多公众号汇总看板 | | GET | `/api/dashboard/monitor` | 涨粉文章规律 + 竞品广告复投分析 | ### 任务管理 — `/api/tasks` | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/tasks/scrape` | 手动触发采集(指定公众号 + 回溯天数,后台执行) | | GET | `/api/tasks/trigger` | 手动触发定时任务(scrape / status) | | GET | `/api/tasks/status` | 调度器运行状态 | | GET | `/api/tasks/schedule` | 查看定时任务配置 | | PUT | `/api/tasks/schedule` | 修改文章采集任务配置(时间、启用状态等) | | PUT | `/api/tasks/schedule/status` | 修改互动数据更新任务配置 | | GET | `/api/tasks/logs` | 定时任务执行日志(分页,支持按类型筛选) | | GET | `/api/tasks/article-runs` | 文章采集批次列表(分页,支持状态/触发方式筛选) | | GET | `/api/tasks/article-runs/{run_id}` | 文章采集批次详情 | | GET | `/api/tasks/article-runs/recover` | 恢复限流中断的采集批次(后台执行) | | GET | `/api/tasks/article-runs/{run_id}/recover` | 恢复采集批次(GET 版本,供飞书链接点击) | | GET | `/api/tasks/status-runs` | 互动数据抓取批次列表(分页) | | POST | `/api/tasks/status-runs/{run_id}/recover` | 恢复失败的互动数据抓取批次 | | GET | `/api/tasks/status-runs/{run_id}/recover` | 恢复互动数据批次(GET 版本,供飞书链接点击) | ### 验证码 — `/captcha` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/captcha/{token}` | 人工图形验证码页面(TCaptcha) | | POST | `/captcha/{token}/result` | 接收验证码结果 | > 完整交互式文档:启动后访问 `http://localhost:8101/docs` (Swagger UI) ## 定时任务 系统启动时自动注册 3 个定时任务: | 任务 ID | 执行时间 | 说明 | |---------|----------|------| | `daily_login_reminder` | 每天 09:30 | 校验 Session 有效性,仅对失效的推送登录二维码 | | `article_scrape` | 每天 10:00 | 二次校验 Session + 采集文章(回溯前一天全天) | | `status_scrape` | 每天 02:00 | 抓取 N 天前发布文章的互动数据(阅读/点赞/分享等) | 可通过 API 动态修改任务时间和启用状态,无需重启。 > 微信读书 token 实际仅数小时有效:每天 10:00 组合任务扫码后立即采集可保证有效; > 其他时间手动触发采集时,若无有效 Session,系统会自动通过飞书推送二维码, > 等待扫码(约 5 分钟/session)后继续。 ## 使用流程 ### 1. 扫码登录获取 Session ```bash curl -X POST http://localhost:8101/api/sessions/login \ -H "Content-Type: application/json" \ -d '{"session_name": "my-account"}' ``` 执行后系统生成**微信读书登录二维码**并推送到飞书群(`FEISHU_CHAT_ID`), 用微信扫码确认后,vid 和 token(wr_skey)自动存入数据库。 接口会阻塞等待扫码(最多约 5 分钟)。 ### 2. 添加公众号 ```bash curl -X POST http://localhost:8101/api/accounts \ -H "Content-Type: application/json" \ -d '{ "univ_name": "上海大学", "account_type": 2, "account_name": "上海大学" }' ``` ### 3. 解析公众号 bookId(mp_biz) ```bash # 方式A:提供该公众号任一文章链接,经微信读书解析出 bookId(MP_WXS_数字) curl -X POST http://localhost:8101/api/accounts/1/refresh-fakeid \ -H "Content-Type: application/json" \ -d '{"article_url": "https://mp.weixin.qq.com/s/xxxx"}' # 方式B:老账号已有 base64 格式 mp_biz 时,不带请求体直接本地派生 curl -X POST http://localhost:8101/api/accounts/2/refresh-fakeid ``` ### 4. 手动触发采集 ```bash # 采集所有启用公众号昨天发布的文章 curl -X POST http://localhost:8101/api/tasks/scrape \ -H "Content-Type: application/json" \ -d '{"days_back": 1}' # 指定公众号 + 回溯 7 天 curl -X POST http://localhost:8101/api/tasks/scrape \ -H "Content-Type: application/json" \ -d '{"account_ids": [1, 2], "days_back": 7}' ``` ### 5. 查询文章与数据 ```bash # 按学校筛选文章 curl "http://localhost:8101/api/articles?school=上海大学&page=1&page_size=20" # 查看某篇详情(含阅读量) curl "http://localhost:8101/api/articles/1" # 查看公众号数据面板 curl "http://localhost:8101/api/dashboard/full?account_name=上海大学&days=30" # 查看采集任务执行日志 curl "http://localhost:8101/api/tasks/logs?page=1&page_size=20" ``` ## 配置说明 | 环境变量 | 默认值 | 说明 | |----------|--------|------| | `DATABASE_URL` | `postgresql+asyncpg://postgres:123456@localhost:5432/postgres` | 数据库连接串 | | `SERVER_HOST` | — | 服务器 IP/域名(飞书链接使用,Docker 部署必填) | | `SERVER_PORT` | `8000` | 服务端口(生产环境 8101) | | `FETCH_DELAY` | `0.5` | 公众号之间采集间隔(秒) | | `SCRAPE_DAYS_BACK` | `1` | 手动采集默认回溯天数 | | `SCRAPE_ARTICLE_DELAY_MIN` | `2` | 文章间随机等待最小秒数 | | `SCRAPE_ARTICLE_DELAY_MAX` | `4` | 文章间随机等待最大秒数 | | `STATUS_FETCH_MIN_DAYS` | `3` | 文章发布多少天后开始抓取互动数据 | | `STATUS_ACCOUNT_TYPE` | `2` | 互动数据抓取的账号类型(1=竞品,2=学校) | | `WEREAD_BASE_URL` | `https://weread.qq.com` | 微信读书 Web 接口地址 | | `WEREAD_PAGE_DELAY` | `0.4` | 文章列表翻页间隔(秒,另加随机抖动) | | `WEREAD_LOGIN_MAX_ROUNDS` | `5` | 每个 session 扫码登录最大轮数(每轮 60s) | | `WEREAD_MANUAL_CAPTCHA_ENABLED` | `true` | 是否启用人工作图验证码(-2041 风控) | | `WEREAD_CAPTCHA_WAIT_SECONDS` | `300` | 人工验证码等待上限(秒) | | `FEISHU_APP_ID` | — | 飞书应用 ID(登录提醒) | | `FEISHU_APP_SECRET` | — | 飞书应用密钥 | | `FEISHU_CHAT_ID` | — | 飞书群聊 ID | | `ALIBAILIAN_API_KEY` | — | 阿里百炼 API Key(AI 分类) | | `ALIBAILIAN_MODEL` | `qwen3.6-flash` | AI 分类模型 | | `OPEN_API_BASE_URL` | — | 第三方开放接口地址 | | `OPEN_API_APP_KEY` | — | 第三方开放接口 AppKey | | `OPEN_API_SECRET` | — | 第三方开放接口密钥 | | `LOG_LEVEL` | `INFO` | 日志级别 | > 所有配置均可在 `.env` 文件中覆盖。 ## 核心设计 ### 微信读书采集方案 微信公众平台 `/cgi-bin/appmsg`、`/cgi-bin/appmsgpublish` 已永久失效,现采用: | 环节 | 方案 | |------|------| | 登录 | 微信读书扫码(getLoginUid → 二维码 → getLoginInfo 60s 长轮询),二维码经飞书推送 | | 会话校验 | `GET /api/userInfo`(401/403 或 -2010/-2012 视为失效) | | 公众号标识 | `__biz`(base64)解码出的数字 → `bookId = MP_WXS_{数字}`;新账号经文章链接解析 | | 文章列表 | `GET /web/mp/articles?bookId=…&offset=游标`(游标分页:offset = 本页最小 createTime) | | 文章正文 | 直接抓取公开文章页 `https://mp.weixin.qq.com/s/{originalId}`(无需登录) | 限流处理:登录失效 → 停用并轮换 Session;`-2011`/429 → 轮换 Session; `-2041`(账号级风控)→ 拉起人工图形验证码,通过后带 ticket/randstr 重试; `-2014`(请求频率过高)→ 立即停止任务,飞书告警,约 2 小时后手动 recover。 所有 Session 均被限流时:停止任务并持久化断点(`article_runs` 表), 飞书告警含完整错误原因、已完成/未完成公众号清单及恢复链接, 约 1 小时后调用 `GET /api/tasks/article-runs/recover` 从断点继续执行。 ### 互动数据抓取策略 `status_scrape` 定时任务每天凌晨 2:00 执行,仅处理**恰好 N 天前**(默认 3 天)全天发布的文章。例如 7 月 22 日执行时,只抓取 7 月 19 日发布文章的互动数据。 `article_status.days` 字段记录"发布后第几天抓取",例如 7/19 发布、7/22 抓取 → `days = 3`,方便后续按时间序列分析阅读量增长趋势。 每次抓取创建 `status_runs` 批次记录,失败文章可单独恢复(仅重试 `failed_articles` 中的文章,不重复已成功的)。 ## License MIT