Files
UpdateDB/README.md
Asukadaisiki 34a6c6d67e feat: 实现微信公众号新闻和视频同步服务
- 使用 draft API 同步文章(适配个人订阅号)
- 使用 material API 同步视频(含详情获取)
- 自动建表(videos)、UPSERT 已有 articles 表
- 同步删除:微信端删除的素材自动从数据库移除
- APScheduler 定时调度,支持 --once 手动触发
- Docker + docker-compose 部署配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 13:08:49 +08:00

177 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 独立同步,一个失败不影响另一个