fix: 优化后端数据更新机制

This commit is contained in:
Daniel
2026-03-03 13:02:28 +08:00
parent 7284a1a60d
commit fa6f7407f0
20 changed files with 592 additions and 201 deletions

View File

@@ -54,10 +54,20 @@ pip install -r requirements.txt
**事件脉络不更新时**:多半是未启动 `npm run gdelt`。只跑 `npm run api` 时,事件脉络会显示空或仅有缓存。
## 写库流水线(与 server/README 第五节一致)
RSS 与主入口均走统一流水线 `pipeline.run_full_pipeline`
1. **抓取** → 2. **AI 清洗**(标题/摘要/分类)→ 3. **去重**news_content.content_hash→ 4. **映射到前端库字段**situation_update、combat_losses、key_location 等)→ 5. **更新表** → 6. **有新增时 POST /api/crawler/notify**
- `npm run crawler`main.py`npm run gdelt`realtime_conflict_service的 RSS 分支都调用该流水线。
- 实现见 `crawler/pipeline.py`
## 数据流
```
GDELT API → 抓取(60s) → SQLite (gdelt_events, conflict_stats) → POST /api/crawler/notify
RSS → 抓取 → 清洗 → 去重 → 写 news_content / situation_update / 战损等 → POST /api/crawler/notify
Node 更新 situation.updated_at + WebSocket 广播
@@ -79,6 +89,83 @@ GDELT API → 抓取(60s) → SQLite (gdelt_events, conflict_stats) → POST /ap
- `OLLAMA_MODEL`: AI 分类模型,默认 `llama3.1`
- `PARSER_AI_DISABLED`: 设为 `1` 则禁用 AI 分类,仅用规则
- `CLEANER_AI_DISABLED`: 设为 `1` 则禁用 AI 清洗,仅用规则截断
- `FETCH_FULL_ARTICLE`: 设为 `0` 则不再抓取正文,仅用标题+摘要做 AI 提取(默认 `1` 抓取正文)
- `ARTICLE_FETCH_LIMIT`: 每轮为多少条新资讯抓取正文,默认 10
- `ARTICLE_FETCH_TIMEOUT`: 单篇正文请求超时(秒),默认 12
- `ARTICLE_MAX_BODY_CHARS`: 正文最大字符数,默认 6000
- `EXTRACT_TEXT_MAX_LEN`: 送入 AI 提取的原文最大长度,默认 4000
**增量与地点**:战损一律按**增量**处理——AI 只填本则报道的「本次/此次」新增数,不填累计总数;合并时与库内当前值叠加。双方攻击地点通过 `key_location_updates` 更新(美军基地被打击 side=us伊朗设施被打击 side=iran会写入 `key_location` 的 status/damage_level。
---
## 优化后验证效果示例
以下为「正文抓取 + AI 精确提取 + 增量与地点更新」优化后,单条新闻从输入到前端展示的完整示例,便于对照验证。
### 1. 示例输入(新闻摘要/全文片段)
```
伊朗向伊拉克阿萨德空军基地发射 12 枚弹道导弹,造成此次袭击中 2 名美军人员死亡、14 人受伤,
另有 1 架战机在跑道受损。乌代德基地未遭直接命中。同日以色列对伊朗伊斯法罕一处设施发动打击。
```
### 2. AI 提取输出(增量 + 攻击地点)
```json
{
"summary": "伊朗导弹袭击伊拉克阿萨德基地致美军 2 死 14 伤1 架战机受损;以军打击伊斯法罕。",
"category": "alert",
"severity": "high",
"us_personnel_killed": 2,
"us_personnel_wounded": 14,
"us_aircraft": 1,
"us_bases_damaged": 1,
"key_location_updates": [
{ "name_keywords": "阿萨德|asad|al-asad", "side": "us", "status": "attacked", "damage_level": 2 },
{ "name_keywords": "伊斯法罕|isfahan", "side": "iran", "status": "attacked", "damage_level": 1 }
]
}
```
说明:战损为**本则报道的新增数**(此次 2 死、14 伤、1 架战机),不是累计总数;地点为双方遭袭设施(美军基地 side=us伊朗设施 side=iran
### 3. 合并后数据库变化
| 表/字段 | 合并前 | 本则增量 | 合并后 |
|--------|--------|----------|--------|
| combat_losses.us.personnel_killed | 127 | +2 | 129 |
| combat_losses.us.personnel_wounded | 384 | +14 | 398 |
| combat_losses.us.aircraft | 2 | +1 | 3 |
| combat_losses.us.bases_damaged | 27 | +1 | 28 |
| key_locationname 含「阿萨德」) | status=operational | — | status=attacked, damage_level=2 |
| key_locationname 含「伊斯法罕」) | status=operational | — | status=attacked, damage_level=1 |
若 AI 误提「累计 2847 人丧生」并填成 personnel_killed=2847单次合并会被上限截断如最多 +500避免一次写入导致数据剧增。
### 4. 前端验证效果
- **事件脉络**出现一条新条目summary 为上述 12 句概括category=alert、severity=high。
- **装备毁伤面板**:美军「阵亡」+2、「受伤」+14、「战机」+1基地毁/损数字随 bases_damaged +1 更新。
- **地图**:阿萨德基地、伊斯法罕对应点位显示为「遭袭」状态(脉冲/标色随现有地图逻辑)。
- **API**`GET /api/situation``usForces.combatLosses``usForces.keyLocations`(含 status/damage_level为更新后值`lastUpdated` 为合并后时间。
### 5. 快速自测命令
```bash
# 仅测提取逻辑(不写库):用示例文本调 AI 提取,看是否得到增量 + key_location_updates
cd crawler && python3 -c "
from extractor_ai import extract_from_news
text = '''伊朗向伊拉克阿萨德空军基地发射导弹,此次袭击造成 2 名美军死亡、14 人受伤1 架战机受损。'''
out = extract_from_news(text)
print('combat_losses_delta:', out.get('combat_losses_delta'))
print('key_location_updates:', out.get('key_location_updates'))
"
```
期望:`combat_losses_delta.us` 含 personnel_killed=2、personnel_wounded=14、aircraft=1 等增量;`key_location_updates` 含阿萨德 side=us 等条目。
---
## 冲突强度 (impact_score)
@@ -93,6 +180,75 @@ GDELT API → 抓取(60s) → SQLite (gdelt_events, conflict_stats) → POST /ap
- `GET http://localhost:8000/events`返回事件列表与冲突统计Python 服务直连)
- `GET http://localhost:3001/api/events`:从 Node 读取(推荐,含 WebSocket 同步)
## 本地验证链路
按下面任选一种方式,确认「抓取 → 清洗 → 去重 → 映射 → 写表 → 通知」整条链路正常。
### 方式一:最小验证(不启动前端)
1. **启动 API必须**
```bash
npm run api
```
保持运行,默认 `http://localhost:3001`。
2. **安装爬虫依赖并跑一轮流水线**
```bash
cd crawler && pip install -r requirements.txt
python -c "
from pipeline import run_full_pipeline
from config import DB_PATH, API_BASE
n_fetched, n_news, n_panel = run_full_pipeline(db_path=DB_PATH, api_base=API_BASE, translate=True, notify=True)
print('抓取:', n_fetched, '去重新增:', n_news, '面板写入:', n_panel)
"
```
- 有网络且有关键词命中时,应看到非零数字;无网络或全被过滤则为 `0 0 0`。
- 若报错 `module 'socket' has no attribute 'settimeout'`,已修复为 `setdefaulttimeout`,请拉取最新代码。
3. **查库确认**
```bash
sqlite3 server/data.db "SELECT COUNT(*) FROM situation_update; SELECT COUNT(*) FROM news_content;"
```
或浏览器打开 `http://localhost:3001/api/db/dashboard`,看 `situation_update`、`news_content` 是否有数据。
4. **确认态势接口**
```bash
curl -s http://localhost:3001/api/situation | head -c 500
```
应包含 `lastUpdated`、`recentUpdates` 等。
### 方式二:用现有验证脚本(推荐)
1. 终端 1`npm run api`
2. 终端 2可选`npm run gdelt`(会定时跑 RSS + GDELT
3. 执行验证脚本:
```bash
./scripts/verify-pipeline.sh
```
若爬虫未启动想一并测爬虫,可:
```bash
./scripts/verify-pipeline.sh --start-crawler
```
脚本会检查API 健康、态势数据、爬虫状态、资讯表、战损字段、通知接口。
### 方式三:只测 RSS 抓取(不写库)
```bash
npm run crawler:test
```
输出为「RSS 抓取: N 条」。0 条时检查网络或 `config.py` 里 `RSS_FEEDS` / `KEYWORDS`。
### 常见问题
| 现象 | 可能原因 |
|------|----------|
| 抓取 0 条 | 网络不通、RSS 被墙、关键词无一命中 |
| `situation_update` 为空 | 去重后无新增,或未跑流水线(只跑了 `fetch_all` 未跑 `run_full_pipeline` |
| 前端事件脉络不刷新 | 未启动 `npm run api` 或 WebSocket 未连上(需通过 Vite 代理访问前端) |
| 翻译/AI 清洗很慢或报错 | 设 `TRANSLATE_DISABLED=1` 或 `CLEANER_AI_DISABLED=1` 可跳过,用规则兜底 |
---
## 故障排查
| 现象 | 可能原因 | 排查 |