feat: 实现微信公众号新闻和视频同步服务
- 使用 draft API 同步文章(适配个人订阅号) - 使用 material API 同步视频(含详情获取) - 自动建表(videos)、UPSERT 已有 articles 表 - 同步删除:微信端删除的素材自动从数据库移除 - APScheduler 定时调度,支持 --once 手动触发 - Docker + docker-compose 部署配置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
175
README.md
175
README.md
@@ -1,3 +1,176 @@
|
||||
# UpdateDB
|
||||
|
||||
定时爬取公众号的视频与文章数据,更新到数据库
|
||||
定时从微信公众号平台获取视频与文章数据,同步到远端 PostgreSQL 数据库。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Python 3.11
|
||||
- httpx (HTTP 客户端)
|
||||
- psycopg2-binary (PostgreSQL 驱动)
|
||||
- APScheduler (定时调度)
|
||||
- Docker 部署
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
UpdateDB/
|
||||
main.py # 入口:启动调度器,支持 --once 手动触发
|
||||
config.py # 加载 .env 配置
|
||||
wechat.py # 微信 API 客户端(token 管理、素材获取)
|
||||
db.py # 数据库连接、建表、UPSERT
|
||||
sync.py # 同步编排逻辑
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.env_example # 环境变量模板
|
||||
api_docs/ # 微信 API 文档
|
||||
```
|
||||
|
||||
## 数据库变更总结
|
||||
|
||||
### 新建表:`videos`
|
||||
|
||||
程序启动时自动创建(`CREATE TABLE IF NOT EXISTS`),**不会影响已有表**。
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS videos (
|
||||
id SERIAL PRIMARY KEY, -- 自增主键
|
||||
media_id VARCHAR(128) UNIQUE, -- 微信素材 ID(唯一约束)
|
||||
name TEXT, -- 视频文件名(来自列表接口)
|
||||
url TEXT, -- 视频播放 URL(来自列表接口)
|
||||
title TEXT, -- 视频标题(来自详情接口)
|
||||
description TEXT, -- 视频描述(来自详情接口)
|
||||
down_url TEXT, -- 视频下载地址(来自详情接口,可能过期)
|
||||
wechat_update_time TIMESTAMP, -- 微信端更新时间
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
**后端读取时注意**:
|
||||
- `url` 和 `down_url` 都可能过期失效,建议每次使用前检查可用性,或定期触发同步刷新
|
||||
- `title` 和 `description` 可能为 NULL(首次同步时详情接口调用失败)
|
||||
- `url` 是播放地址,`down_url` 是下载地址,两者不同
|
||||
|
||||
### 未修改的表
|
||||
|
||||
#### `articles`(原有表,未改结构)
|
||||
|
||||
程序往这些**已有字段**写入数据,未新增/修改任何列:
|
||||
|
||||
| 数据库字段 | 写入内容 | 微信 API 来源 | 注意事项 |
|
||||
|---|---|---|---|
|
||||
| `title` | 文章标题 | `news_item.title` | 截断到 200 字符 |
|
||||
| `content` | 文章 HTML 内容 | `news_item.content` | 原始 HTML,微信已去除 JS |
|
||||
| `cover_url` | 封面图 URL | `news_item.thumb_url` | 截断到 500 字符,可能为 NULL |
|
||||
| `author` | 作者 | `news_item.author` | 截断到 100 字符,可能为 NULL |
|
||||
| `publish_date` | 发布日期 | `item.update_time` 转 date | 仅日期,不含时分秒 |
|
||||
| `source_url` | 文章原文链接 | `news_item.url` | 截断到 1000 字符,可能为 NULL |
|
||||
| `wechat_article_id` | 去重标识 | `{media_id}_{index}` | 格式见下方说明 |
|
||||
|
||||
**未写入的字段**(保持原有值/NULL):`category`、`tags`(微信 API 无此数据)
|
||||
|
||||
#### `sync_states`(原有表,未改结构)
|
||||
|
||||
使用已有的 key-value 结构记录同步状态:
|
||||
|
||||
| key | value 示例 |
|
||||
|---|---|
|
||||
| `wechat_news_sync` | `{"status":"idle","count":42,"last_sync":"2026-04-09T..."}` |
|
||||
| `wechat_video_sync` | `{"status":"idle","count":5,"last_sync":"2026-04-09T..."}` |
|
||||
|
||||
value 是 JSON 字符串,后端可用 `json.loads()` 解析。
|
||||
|
||||
### 同步删除机制
|
||||
|
||||
每次同步完成后,程序会对比微信端和数据库的 `media_id`:
|
||||
- 数据库中有、微信端没有的记录 → **自动删除**
|
||||
- 涉及 `articles` 和 `videos` 两张表
|
||||
- articles 的删除依据:`wechat_article_id` 中提取的 `media_id`(按最后一个 `_` 分割)
|
||||
|
||||
### `wechat_article_id` 格式说明
|
||||
|
||||
```
|
||||
格式:{media_id}_{article_index}
|
||||
示例:abc123def456_0 (单图文)
|
||||
abc123def456_1 (多图文中的第 2 篇)
|
||||
```
|
||||
|
||||
- `media_id`:微信素材 ID,一个 media_id 对应一条图文素材
|
||||
- `article_index`:图文内的文章序号(从 0 开始),多图文时会有 0、1、2...
|
||||
- 后端查询某篇具体文章:`WHERE wechat_article_id = '{media_id}_{idx}'`
|
||||
- 后端查询某条图文素材的所有文章:`WHERE wechat_article_id LIKE '{media_id}_%'`
|
||||
|
||||
### 数据写入策略
|
||||
|
||||
- **articles**:`SELECT` 检查 `wechat_article_id` 是否存在 → 存在则 `UPDATE`,不存在则 `INSERT`
|
||||
- **videos**:`INSERT ... ON CONFLICT (media_id) DO UPDATE`,仅在 `wechat_update_time` 更新时覆盖
|
||||
- **sync_states**:`INSERT ... ON CONFLICT (key) DO UPDATE`,每次同步更新状态
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 本地开发
|
||||
|
||||
1. 安装依赖:
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
2. 复制配置文件并填写:
|
||||
|
||||
```bash
|
||||
cp .env_example .env
|
||||
# 编辑 .env,填入 app_secret 和数据库信息
|
||||
```
|
||||
|
||||
3. 运行:
|
||||
|
||||
```bash
|
||||
# 启动定时同步(默认每 48 小时)
|
||||
python main.py
|
||||
|
||||
# 只运行一次
|
||||
python main.py --once
|
||||
```
|
||||
|
||||
### Docker 部署
|
||||
|
||||
1. 在 VPS 上克隆仓库,创建 `.env` 文件:
|
||||
|
||||
```bash
|
||||
cp .env_example .env
|
||||
# 编辑 .env
|
||||
```
|
||||
|
||||
2. 构建并启动:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
3. 查看日志:
|
||||
|
||||
```bash
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `app_id` | 是 | 微信公众号 AppID |
|
||||
| `app_secret` | 是 | 微信公众号 AppSecret |
|
||||
| `db_host` | 是 | PostgreSQL 主机 |
|
||||
| `db_port` | 是 | PostgreSQL 端口 |
|
||||
| `db_name` | 是 | 数据库名 |
|
||||
| `db_user` | 是 | 数据库用户 |
|
||||
| `db_pw` | 是 | 数据库密码 |
|
||||
| `sync_interval_hours` | 否 | 同步间隔(小时),默认 48 |
|
||||
|
||||
## 同步逻辑
|
||||
|
||||
1. 调用微信稳定版 Token 接口获取 access_token(自动缓存和刷新)
|
||||
2. 分页获取所有 news(图文)和 video(视频)素材
|
||||
3. 对视频素材,额外调用详情接口获取下载地址
|
||||
4. 使用 UPSERT 写入数据库(INSERT ON CONFLICT DO UPDATE)
|
||||
5. news 和 video 独立同步,一个失败不影响另一个
|
||||
|
||||
Reference in New Issue
Block a user