fix: 修复移动端报错

This commit is contained in:
Daniel
2026-03-03 11:14:34 +08:00
parent 4dd1f7e7dc
commit 7284a1a60d
15 changed files with 244 additions and 20 deletions

137
docs/CRAWLER_LOGIC.md Normal file
View File

@@ -0,0 +1,137 @@
# 爬虫逻辑梳理与数据校验
## 一、两条入口,数据流不同
### 1. 入口 A`npm run crawler`main.py
- **流程**RSS 抓取 → 关键词过滤 → 分类/严重度 → **直接写 situation_update** → 通知 API
- **不经过**翻译、news_content、AI 提取(战损/基地等)
- **写入表**`situation_update``situation.updated_at`
- **用途**:轻量、只给「事件脉络」喂新条目,不更新战损/基地/报复指数
```
RSS_FEEDS → fetch_all() → KEYWORDS 过滤 → parser_ai.classify_and_severity
→ write_updates(items) → situation_update INSERT + situation 表 touch
→ notify_api()
```
### 2. 入口 B`npm run gdelt`realtime_conflict_service.py
- **流程**RSS 抓取 → 翻译 → 清洗 → **news_content 去重** → situation_update → **AI 提取 → db_merge** → GDELT 事件(可选)→ 通知 API
- **写入表**`news_content``situation_update``situation`;提取后还有 `combat_losses``key_location``retaliation_*``wall_street_trend`
- **用途**:完整管线,前端「战损 / 军事基地 / 报复 / 美股」等数据都依赖这条
```
RSS → fetch_all() → translate_to_chinese → cleaner_ai → save_and_dedup → news_content
→ write_updates(new_items) → situation_update
→ _extract_and_merge_panel_data(new_items) → extract_from_news() → db_merge.merge()
→ (可选) fetch_gdelt_events() → gdelt_events, conflict_stats
→ _notify_node()
```
**结论**:要检查「抓回的数据是否有效」且包含战损/基地等,应跑 **入口 B**gdelt 服务);若只关心事件脉络条数,可看入口 A。
---
## 二、入口 B 逐步拆解(用于逐段校验)
### 2.1 RSS 抓取与过滤
| 步骤 | 位置 | 说明 |
|------|------|------|
| 源列表 | `config.RSS_FEEDS` | 多国媒体 RSS见 config.py |
| 抓取 | `scrapers.rss_scraper.fetch_all()` | feedparser单源超时 10s |
| 过滤 | `_matches_keywords(text)` | 标题+摘要 至少命中 `config.KEYWORDS` 中一个才保留 |
| 去重 | `(title[:80], link)` | 同一条不重复加入当次列表 |
| 分类 | `parser_ai.classify_and_severity(text)` | 得到 category、severityOllama 或规则) |
**校验**`npm run crawler:test` 看本次抓到的条数;若为 0查网络或放宽/检查 KEYWORDS。
### 2.2 翻译与清洗(仅入口 B
| 步骤 | 位置 | 说明 |
|------|------|------|
| 翻译 | `translate_utils.translate_to_chinese()` | 标题/摘要译成中文(依赖配置) |
| 清洗 | `cleaner_ai.clean_news_for_panel()` | 截断、清理;`ensure_category` / `ensure_severity` 合法化 |
### 2.3 落库news_content去重与 situation_update
| 步骤 | 位置 | 说明 |
|------|------|------|
| 去重 | `news_storage.save_and_dedup(items)` | 按 `content_hash(title, summary, url)` 判重,只插入新记录 |
| 表 | `news_content` | id, content_hash, title, summary, url, source, published_at, category, severity |
| 表 | `situation_update` | 仅对 **去重后的 new_items** 调用 `write_updates()`,供前端「事件脉络」 |
**校验**
- `news_content``SELECT COUNT(*), MAX(published_at) FROM news_content`
- `situation_update``SELECT COUNT(*), MAX(timestamp) FROM situation_update`
- 服务状态:`GET http://localhost:8000/crawler/status``last_fetch_items` / `last_fetch_inserted` / `last_fetch_error`
### 2.4 AI 提取与 db_merge战损 / 基地 / 报复等)
| 步骤 | 位置 | 说明 |
|------|------|------|
| 输入 | `_extract_and_merge_panel_data(new_items)` | 仅处理本次 **新增** 的 new_items前 limit 条DashScope 10 条,规则 25 条Ollama 10 条) |
| 文本 | 每条 `title + " " + summary`,长度 < 20 跳过 |
| 提取器选择 | 环境变量 | `DASHSCOPE_API_KEY` → extractor_dashscope`CLEANER_AI_DISABLED=1` → extractor_rules否则 extractor_aiOllama |
| 输出结构 | 见 panel_schema / 各 extractor | `situation_update?`, `combat_losses_delta?`, `retaliation?`, `wall_street?`, `key_location_updates?` |
| 合并 | `db_merge.merge(extracted)` | 见下表 |
**merge 映射概要**
| 提取字段 | 写入表/逻辑 |
|----------|-------------|
| situation_update | situation_update 表 INSERTid 为 hash |
| combat_losses_delta | combat_losses 表,按 side 增量叠加 |
| retaliation | retaliation_current 替换 + retaliation_history 追加 |
| wall_street | wall_street_trend 表 INSERT |
| key_location_updates | key_location 表 UPDATE status/damage_levelname LIKE 关键词) |
**校验**
- 战损:`SELECT * FROM combat_losses`
- 基地:`SELECT id, name, side, status, damage_level FROM key_location WHERE status != 'operational' OR damage_level > 0`
- 报复:`SELECT * FROM retaliation_current``retaliation_history` 最近几条
- 事件脉络:`SELECT id, timestamp, category, summary, severity FROM situation_update ORDER BY timestamp DESC LIMIT 20`
### 2.5 GDELT可选
- `GDELT_DISABLED=1` 时跳过 GDELT仅用 RSS可用 `_rss_to_gdelt_fallback()` 用 RSS 标题生成 gdelt_events。
- 未禁用时:`fetch_gdelt_events()` 拉 GDELT → 写 `gdelt_events``conflict_stats`
**校验**`SELECT COUNT(*), MAX(event_time) FROM gdelt_events``SELECT * FROM conflict_stats WHERE id=1`
---
## 三、如何检查「抓回的数据是否有效」
1. **确认跑的入口**
- 只跑 `npm run crawler`:只有 situation_update 会有新数据,战损/基地不会变。
-`npm run gdelt` 且服务常驻:才会既有 situation_update又有 combat_losses、key_location 等。
2. **看 DB 与 API**
- 同上:查 `news_content``situation_update``combat_losses``key_location``retaliation_*``gdelt_events``conflict_stats`
- 前端数据来源:`GET /api/situation`(见 server/situationData.js对照上述表即可。
3. **看提取是否触发**
-`combat_losses` / `key_location` 一直不更新:确认是入口 B、有 new_items、提取器未报错可对单条新闻跑 `extract_from_news(text)` 看是否产出 combat_losses_delta / key_location_updates。
4. **重跑历史提取(补数据)**
- `POST http://localhost:8000/crawler/backfill`:用当前 situation_update 最近 50 条重新做一次提取并 merge可用来修历史未提取的数据。
---
## 四、配置与环境变量(与数据有效性相关)
| 变量 | 作用 |
|------|------|
| DB_PATH | 与 server 共用的 SQLite 路径,必须一致 |
| API_BASE | 通知 Node 的地址merge 后通知前端 |
| DASHSCOPE_API_KEY | 有则用 DashScope 提取;无则用 Ollama 或规则 |
| CLEANER_AI_DISABLED=1 | 用规则提取extractor_rules不用 Ollama |
| GDELT_DISABLED=1 | 不用 GDELT仅 RSSRSS 可转 gdelt_events 占位 |
| CRAWL_INTERVAL | main.py 抓取间隔(秒) |
| RSS_INTERVAL_SEC / FETCH_INTERVAL_SEC | realtime 服务里 RSS / GDELT 间隔 |
按上述顺序对照「入口 → RSS → 去重 → situation_update → 提取 → merge → 表」即可逐段检查爬虫抓回的数据是否有效。