Files
2026-07-12 20:21:41 +08:00

605 lines
26 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.
# 军事科技每日资讯推送系统
基于大模型的军事/科技新闻采集、评分、整合与推送系统。自动抓取 RSS 订阅源,经 LLM 评分筛选与实体聚类后,生成日报并推送到飞书群,同时提供 Web 归档站点浏览历史推送。
## 系统架构
```
┌───────────────────────────────────────────────────────────────┐
│ Fetch 循环(每 30 分钟) │
│ RSS 抓取 → 中英翻译 → LLM 评分 → 硬约束过滤 → 动态阈值 │
│ → 实体提取 + 角度分类 → 实体归一化 → 实体聚类 │
│ → 广告过滤 → 缓存 → 簇热度计算 → 即时推送(≥90 分) │
│ → 自动清理过期缓存(60 天) │
└──────────────────┬────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ Push 循环(每日 08:00
│ 推送 1: 🔥 热点速览(簇视图) │
│ 今日速览 + 热点洞察(含[持续跟踪])+ 跨日追踪 + 趋势分析 │
│ 推送 2: 📰 军事科技每日精选(单篇视图) │
│ 网摘 Top3 + 分类概览(5 类 × 5 条) │
│ 推送 3: 🖼️ 网摘图片(PNG 合并长图,图床上传后推送) │
└──────────────────┬────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ Web 归档站点(http://0.0.0.0:8080
│ 按日期浏览历史推送:簇视图 + 单篇视图 + 网摘图片 │
│ 支持分类筛选、全文搜索、评分排序 │
└───────────────────────────────────────────────────────────────┘
```
| 核心功能 | 说明 |
|---------|------|
| 订阅源管理 | OPML 文件管理 137 个 RSS 源(英文军事 + 中文微信公众号) |
| 双语翻译 | 中英自动分离(汉字占比 > 20%),中文零 API 调用,外文并发翻译 |
| 三层评分 | 硬约束(非目标 ≤79 / KOL ≤89)+ 时间衰减 + 动态阈值 |
| 实体提取 | 5 类实体(人物/机构/装备/事件/地域)+ 6 类角度分类,双层归一化(规则 200+ 别名 + LLM 消歧) |
| 实体聚类 | 加权实体重叠 + 并查集,细粒度聚类保留多角度报道,自动命名簇 |
| 簇热度 | 四维评分(最高分 + 传播 + 角度 + 优先)+ LLM 精评融合,热度簇提升为热点 |
| 簇记忆 | SQLite 持久化已推送簇 ID,24h 内同簇不重复推送 |
| 广告过滤 | 四层防御纵深(tag 黑名单 → 动态阈值 → 空角度 → 空实体) |
| 增量缓存 | SQLite 三级缓存(文章/网摘/分类摘要),60 天自动清理,二次运行几乎零 token 消耗 |
| 飞书推送 | 即时热点推送(≥90 分)+ 每日早报三波(热点速览 + 每日精选 + 网摘图片) |
| Web 归档 | Flask 站点,按日期浏览历史推送,支持分类筛选和全文搜索 |
| 日志系统 | 终端输出实时写入 `logs/YYYYMMDD-HHmmSS.log``[管线]`/`[Web]` 前缀区分来源 |
| 双视角洞察 | LLM 一次调用输出精炼摘要 + 结构化洞察(事件脉络/多角度分析/影响与展望) |
| 分类概览 | 5 类军事新闻分类(装备技术/地区安全/军事改革/产业观察/其他热点),每类 top 5 |
| 网摘图片 | PIL 渲染网摘为 PNG 合并长图,自适应高度 + 动态文本换行,图床 API 上传后推送飞书 |
| 反馈闭环 | 用户评分修正记录 + 规则自动生成,修正后的评分自动应用到后续批次 |
| 运行监控 | 12 阶段耗时 + API 调用统计 + 源抓取成功率 + 缓存命中率 |
## 快速开始
> 详细指南见 [QUICKSTART.md](QUICKSTART.md)
### 环境要求
- Python 3.10+(或 Docker
- 能访问 OpenAI 兼容 APIDeepSeek / 火山引擎 / OpenAI 等)
### Docker 部署(推荐)
```bash
# 1. 准备配置
mkdir -p docker/prompts docker/data docker/logs
cp .env docker/.env
cp config.json docker/config.json
cp -r prompts/* docker/prompts/
# 2. 构建并启动
docker compose up -d --build
# 3. 访问 Web 归档站点
# http://localhost:8080
```
### 本地安装
```bash
# 1. 进入项目目录
cd my-daily
# 2. 安装依赖
pip install -r requirements.txt
# 3. 配置环境变量
cp .env.example .env
# 编辑 .env,填入 API Key 和 Webhook 地址
# 4. 配置系统参数(可选,已有默认值)
cp config.json.example config.json
# config.json 已存在时跳过,按需修改调度/评分/源过滤参数
```
### 环境变量(.env
```env
# OpenAI 兼容接口(必填)
OPENAI_API_KEY=your_api_key
OPENAI_API_BASE=https://api.deepseek.com
OPENAI_MODEL=deepseek-v4-flash
# LLM 消歧 + 网摘专用模型(可选,不填则复用 OPENAI_MODEL
WEBZINE_MODEL=deepseek-v4-pro
# 飞书推送(可选,不填则跳过推送)
FEISHU_WEBHOOK=your_webhook_url
# 图床上传配置(可选,网摘图片通过图床 API 上传)
IMAGE_HOSTING_PASSWORD=your_image_hosting_password_here
# ── 定时计划配置(可选,覆盖 config.json)──
# FETCH_INTERVAL_MINUTES=30 # RSS 拉取间隔(分钟)
# FETCH_LOOKBACK_MINUTES=60 # 回溯时间窗口(分钟)
# PUSH_CRON="0 8 * * *" # 推送定时计划(cron 表达式,多个用逗号分隔)
# TIMEZONE_HOURS=8 # 时区
# ── 评价管线参数覆盖(可选,最高优先级)──
# HEAT_THRESHOLD=30 # 热度阈值固定值
# HOT_THRESHOLD=90 # 即时推送分数线
# CLUSTER_MERGE_THRESHOLD=4 # 聚类合并阈值
# PRECISE_BLEND_RATIO=0.4 # LLM 精评融合权重
# MEMORY_WINDOW_HOURS=24 # 簇记忆去重时间窗口
# CROSS_DAY_MIN_OVERLAP=0.05 # 跨日关联实体重叠阈值
```
> **优先级规则**`.env` 环境变量 > `config.json` > 源码默认值。详见 [CONFIG.md](CONFIG.md)。
### 配置项速查
所有参数及调优指南见 **[CONFIG.md](CONFIG.md)**。
| 配置节 | 控制环节 | 关键参数 |
|--------|----------|----------|
| **`.env`** | 接口/推送/定时/图床/评价覆盖 | `OPENAI_API_KEY`, `FEISHU_WEBHOOK`, `IMAGE_HOSTING_PASSWORD`, `FETCH_INTERVAL_MINUTES`, `PUSH_CRON` |
| `schedule` | 定时计划 | `fetch_interval_minutes`(30), `push_cron`(0 8 * * *) |
| `filter` | 过滤推送 | `hot_threshold`(90), `cluster_promotion_offset`(15) |
| `scoring` | 单篇评分 | `non_target_max_score`(79), `half_life_hours`(12), `min_age_hours`(6) |
| `cluster` | 聚类成簇 | `merge_threshold`(4), `entity_weights`, `generic_entities` |
| `heat` | 簇热度 | `score_weight`(0.4), `propagation_cap`(30), `precise_blend_ratio`(0.4) |
| `memory` | 去重记忆 | `window_hours`(24), `cleanup_days`(7) |
## 命令用法
### 一键启动(推荐)
```bash
python start.py
```
同时启动数据管线后端(Fetch + Push 循环)和 Web 归档站点(http://0.0.0.0:8080)。所有终端输出实时写入 `logs/YYYYMMDD-HHmmSS.log`,按 Ctrl+C 安全停止所有服务。
### main.py 命令
```bash
python main.py <command> [options]
```
| 命令 | 说明 | 示例 |
|------|------|------|
| `check` | 校验 LLM 接口可达性 | `python main.py check` |
| `fetch` | 单次抓取 → 翻译 → 评分 → 缓存 | `python main.py fetch --lookback 120` |
| `test-fetch` | 测试抓取(限定源数量) | `python main.py test-fetch --max 5 --lookback 300` |
| `push` | 单次推送(含早报/默认两种模式) | `python main.py push` |
| `rss` | 仅生成 RSS Digest(不推送,打印到终端) | `python main.py rss` |
| `loop` | 长跑模式(Fetch + Push 双循环并行) | `python main.py loop` |
| `feedback` | 提交评分修正反馈 | `python main.py feedback --link URL --score 85 --reason "理由"` |
### test-fetch / fetch 参数
| 参数 | 默认值 | 说明 |
|------|:---:|------|
| `--max N` | 5(仅 test-fetch | 仅拉取前 N 个源 |
| `--lookback N` | 来自 config | 时间回溯窗口(分钟) |
### feedback 参数
| 参数 | 说明 |
|------|------|
| `--link URL` | 需要修正评分的文章链接 |
| `--score N` | 修正后的分数(0-100 |
| `--reason "..."` | 修正理由 |
| `--list` | 查看所有已记录的修正 |
| `--rules` | 查看已生成的修正规则 |
| `--stats` | 查看修正统计数据 |
## 部署
### Docker(推荐,适用于绿联云 NAS / 任何 Linux 主机)
```bash
# 1. 准备配置目录
mkdir -p docker/prompts docker/data docker/logs
cp .env docker/.env
cp config.json docker/config.json
cp -r prompts/* docker/prompts/
# 2. 构建并启动
docker compose up -d --build
# ── 运维 ──────────────────────────────────────
docker compose logs -f # 查看实时日志
docker compose restart # 重启
docker compose down # 停止并删除容器
docker compose up -d --build # 更新代码后重建并启动
```
#### 镜像导出(本地打包 → NAS 导入)
```bash
# 本地构建并导出
docker compose build
docker save -o military-digest.tar my-daily-military-digest:latest
# NAS 上导入
docker load -i military-digest.tar
# 使用 docker-compose.nas.yml 启动
docker compose -f docker-compose.nas.yml up -d
```
#### 文件映射
| NAS 目录 | 容器内路径 | 用途 |
|----------|-----------|------|
| `docker/.env` | `/app/.env` | API Key、Webhook |
| `docker/config.json` | `/app/config.json` | 调度、评分参数 |
| `docker/prompts/` | `/app/prompts/` | LLM 提示词 |
| `docker/data/` | `/app/data/` | 运行时数据 |
| `docker/logs/` | `/app/logs/` | 运行日志 |
### Linuxsystemd
```bash
sudo bash scripts/setup_systemd.sh # 一键安装
bash scripts/status.sh # 查看运行状态
sudo journalctl -u my-daily-fetch -f # 实时日志
sudo bash scripts/uninstall_systemd.sh # 卸载
```
### Windows(计划任务)
```batch
scripts\setup_service.bat install # 安装为计划任务
scripts\setup_service.bat start # 手动运行一次
scripts\setup_service.bat status # 查看任务状态
scripts\setup_service.bat loop # 前台调试
scripts\setup_service.bat uninstall # 卸载计划任务
```
### Docker(推荐,适用于绿联云 NAS / 任何 Linux 主机)
```bash
# 1. 准备配置目录
mkdir -p docker/prompts docker/data docker/logs
cp .env docker/.env
cp config.json docker/config.json
cp -r prompts/* docker/prompts/
# 2. 构建并启动
docker compose up -d --build
# ── 运维 ──────────────────────────────────────
docker compose logs -f # 查看实时日志
docker compose restart # 重启
docker compose down # 停止并删除容器
docker compose up -d --build # 更新代码后重建并启动
```
#### 镜像导出(本地打包 → NAS 导入)
```bash
# 本地构建并导出
docker compose build
docker save -o military-digest.tar my-daily-military-digest:latest
# NAS 上导入
docker load -i military-digest.tar
# 使用 NAS 专用配置启动
docker compose -f docker-compose.nas.yml up -d
```
#### 文件映射
| NAS 目录 | 容器内路径 | 用途 |
|----------|-----------|------|
| `docker/.env` | `/app/.env` | API Key、Webhook |
| `docker/config.json` | `/app/config.json` | 调度、评分参数 |
| `docker/prompts/` | `/app/prompts/` | LLM 提示词 |
| `docker/data/` | `/app/data/` | 运行时数据 |
| `docker/logs/` | `/app/logs/` | 运行日志 |
## 运维工具
| 脚本 | 平台 | 功能 |
|------|:---:|------|
| [scripts/check_sources.py](scripts/check_sources.py) | 通用 | 源连通性检测:HTTP 可达性 + RSS 解析 + 条目时效分布 |
| [scripts/test_entity_extract.py](scripts/test_entity_extract.py) | 通用 | 实体提取 + 角度分类独立测试 |
| [scripts/test_normalizer.py](scripts/test_normalizer.py) | 通用 | 实体归一化效果验证 |
| [scripts/test_llm_disambiguate.py](scripts/test_llm_disambiguate.py) | 通用 | LLM 消歧层独立测试 |
| [scripts/status.sh](scripts/status.sh) | Linux | 项目状态查看 |
| [scripts/install.sh](scripts/install.sh) | Linux | 依赖安装 + systemd 服务文件生成 |
| [scripts/uninstall.sh](scripts/uninstall.sh) | Linux | systemd 服务卸载 |
### 源连通性检测
```bash
python scripts/check_sources.py # 全部源
python scripts/check_sources.py --max 50 # 前 50 个源
python scripts/check_sources.py --failed-only # 仅显示失败源
python scripts/check_sources.py --show-urls # 显示 URL
python scripts/check_sources.py --concurrency 20 # 自定义并发数
```
## 数据与输出
```
data/
├── fetch/ # fetch-YYYY-MM-DD.json(当日抓取结果)
├── hotalert/ # hotalert-YYYYMMDD-HHmmSS.md(即时推送快讯)
├── cluster/ # cluster-YYYYMMDD-HHmmSS.md(热点速览)
├── digest/ # digest-YYYYMMDD-HHmmSS.md(每日精选)
├── images/ # 网摘 PNG 图片
│ └── YYYYMMDD/
│ ├── webzine_{N}_{timestamp}.png
│ └── webzine_combined_{timestamp}.png
└── cache/ # article_cache.dbSQLite
```
| 目录/文件 | 说明 | 保留策略 |
|-----------|------|:---:|
| `data/fetch/` | 当日 Fetch 结果(含评分、摘要) | 7 天 |
| `data/hotalert/` | 军事科技快讯(即时推送) | 7 天 |
| `data/cluster/` | 热点速览(簇视图推送) | 7 天 |
| `data/digest/` | 军事科技每日精选(单篇视图推送) | 7 天 |
| `data/images/` | 网摘 PNG 图片 | 7 天 |
| `data/cache/` | SQLite 缓存(文章/簇记忆/反馈) | 60 天自动清理 |
| `logs/` | 运行日志(YYYYMMDD-HHmmSS.log | 手动清理 |
## 评价管线详解
从 RSS 抓取到即时推送,文章经历 **12 个步骤**,分属两个层面:单篇评分层和簇级评价层。
### 第一层:单篇评分
#### 步骤 1 — LLM 批量评分
调用 `score_batch()` 对每篇文章打分(0-100),同时产出 `tags``summary`
| 配置项 | 位置 | 默认值 | 说明 |
|--------|------|:---:|------|
| `max_prompt_chars` | `config.json → llm` | 10000 | 单批次 Prompt 上限,超限自动拆分 |
| `max_concurrent_batches` | `config.json → llm` | 3 | LLM 并发批次数 |
| Prompt | `prompts/score_batch.md` | — | 含核心约束:非军事≤79、KOL≤89、90+需官方首发 |
#### 步骤 2 — 硬约束过滤
`apply_hard_constraints()` 对 LLM 评分施加上限:
| 约束 | 阈值 | 作用 |
|------|:---:|------|
| 非目标域名(YouTube/Twitter/B站/微博等) | **≤79** | 社交媒体来源不可信 |
| KOL 域名(同上) | **≤89** | 个人账号非官方首发 |
> 配置:`config.json → scoring.non_target_max_score` / `kol_max_score`
#### 步骤 3 — 时间衰减
`apply_time_decay()` 指数衰减旧文章分数:
```
有效年龄 = max(0, 当前时间 - 发布时间 - min_age_hours)
衰减因子 = 0.5 ^ (有效年龄 / half_life_hours)
最终分数 = 原分数 × 衰减因子
```
| 参数 | 默认值 | 说明 |
|------|:---:|------|
| `half_life_hours` | 12 | 半衰期 |
| `min_age_hours` | 6 | 6 小时内不衰减 |
#### 步骤 4 — 动态阈值
`calculate_dynamic_threshold()` 筛选合格文章:
```
阈值 = 均值 + 1.5 × 标准差
clamp[40, 90]
```
只有 `score ≥ 阈值` 的文章进入下一步实体提取。
---
### 第二层:簇级评价
#### 步骤 5 — 实体提取 + 角度分类
`batch_extract_all()` 对合格文章提取实体和角度,两个 LLM 调用并发执行。
| 类别 | Prompt | 输出 |
|------|--------|------|
| 实体(5 类) | `prompts/extract_entities.md` | `entities: {person, org, equipment, event, location}` |
| 角度(6 类) | `prompts/classify_angle.md` | `reporting_angle: 政策发布/技术突破/战略分析/舆论反应/冲突事件/人物动态` |
#### 步骤 6 — 实体归一化
`normalize_entries_async()` 双层归一化:
| 层 | 方式 | 成本 | 示例 |
|----|------|:---:|------|
| 规则映射 | `entity_aliases.json`200+ 别名) | 零 | `PL-21 → 霹雳-21``DARPA → 美国国防高级研究计划局` |
| LLM 消歧 | `WEBZINE_MODEL` 批量判断未知别名 | API | `Strait of Hormuz → 霍尔木兹海峡` |
#### 步骤 7 — 聚类成簇
`cluster_articles()` 加权实体重叠 + 并查集:
| 实体类别 | 权重 | 合并条件 |
|----------|:---:|------|
| equipment / event | **3**(核心) | 共享即直接合并(泛化实体黑名单内的除外) |
| org / person | **2** | 需累计权重 ≥ 4 |
| location | **1** | 需累计权重 ≥ 4 |
> 泛化黑名单(不触发直接合并):`无人机`、`导弹`、`雷达`、`坦克`、`战机`、`军舰` 等 23 个类别词
#### 步骤 8 — 启发式热度计算
`calculate_cluster_heat()` 四维评分(0-100):
```
热度 = min(100,
最高分 × 0.4 × 时间衰减
+ min(报道数 × log(来源多样性) × 3, 30)
+ 角度数/6 × 20
+ min(90+文章数 × 5, 10)
)
```
#### 步骤 9 — 热度阈值
`calculate_hotspot_threshold()` 动态阈值:
```
热度阈值 = 均值 + multiplier × σ
clamp[clamp_min, clamp_max]
```
| 配置 | 位置 | 默认值 | 效果 |
|------|------|:---:|------|
| `threshold_clamp_min` | `config.json → heat` | 35 | 阈值下限 |
| `threshold_clamp_max` | `config.json → heat` | 80 | 阈值上限 |
| `threshold_multiplier` | `config.json → heat` | 1.5 | σ 乘数 |
| `HEAT_THRESHOLD` | `.env` | — | **固定覆盖**(最高优先级) |
#### 步骤 10 — LLM 精评融合
`precise_score_clusters()` 对 Top 30 簇调用 LLM 重新精评(军事价值40% + 时效25% + 信息密度20% + 传播15%),与启发式 6:4 融合:
```
融合热度 = 启发式 × 0.6 + 精评分 × 0.4
```
> Prompt`prompts/precise_heat.md`,失败时回退纯启发式热度。
#### 步骤 11 — 热点识别
热点文章 = **三类并集**
| 来源 | 条件 |
|------|------|
| 直接热点 | 单篇 score ≥ `hot_threshold`90 |
| 簇提升 | 所在簇热度 ≥ 阈值,且单篇 ≥ `hot_threshold - 15`75 |
#### 步骤 12 — 簇记忆去重
`cluster_memory.is_recently_pushed()` 检查簇是否在 24h 内已推送,已推送则跳过。
> 存储:SQLite 表 `cluster_memory`7 天自动清理。
---
### 配置速查
| 参数 | 位置 | 默认值 | 作用环节 |
|------|------|:---:|------|
| `hot_threshold` | `config.json → filter` | 90 | 步骤 11:直接热点线 |
| `non_target_max_score` | `config.json → scoring` | 79 | 步骤 2:非目标封顶 |
| `kol_max_score` | `config.json → scoring` | 89 | 步骤 2KOL 封顶 |
| `half_life_hours` | `config.json → scoring` | 12 | 步骤 3:衰减半衰期 |
| `dynamic_threshold_multiplier` | `config.json → scoring` | 1.5 | 步骤 4:σ 乘数 |
| `threshold_clamp_min` | `config.json → heat` | 35 | 步骤 9:热度下限 |
| `threshold_multiplier` | `config.json → heat` | 1.5 | 步骤 9:热度 σ 乘数 |
| `HEAT_THRESHOLD` | `.env` | — | 步骤 9:固定覆盖 |
## 项目文件说明
```text
my-daily/
├── main.py # 入口 + 双循环架构
├── start.py # 一键启动(管线 + Web + 日志)
├── config.json # 用户配置(不入库)
├── .env # API 密钥(不入库)
├── config.json.example # 配置模板
├── .env.example # 环境变量模板
├── requirements.txt # Python 依赖
├── QUICKSTART.md # 快速上手指南
├── CONFIG.md # 配置参数完整说明
├── Dockerfile # Docker 镜像构建
├── docker-compose.yml # Docker Compose 配置(本地)
├── docker-compose.nas.yml # Docker Compose 配置(NAS 部署)
├── .dockerignore # Docker 构建忽略
├── prompts/ # LLM 提示词(10 个)
│ ├── score_batch.md # 批量评分
│ ├── digest.md # RSS Digest 生成
│ ├── immediate_push.md # 即时推送快讯
│ ├── webzine.md # 参考消息风格网摘
│ ├── category_overview.md # 分类概览
│ ├── extract_entities.md # 实体提取(5 类)
│ ├── classify_angle.md # 角度分类(6 类)
│ ├── precise_heat.md # 热点精评(四维评分)
│ ├── cluster_insight.md # 双视角洞察
│ └── cluster_summary.md # 轻量簇摘要
├── resources/
│ └── rss_feeds.opml # RSS 订阅源(137 个源)
├── templates/ # 推送模板
│ ├── cluster_view.md # 簇视图模板
│ ├── article_view.md # 单篇视图模板
│ └── immediate_push.md # 即时推送模板
├── scripts/ # 运维 + 测试脚本
│ ├── check_sources.py # 源连通性检测
│ ├── setup_systemd.sh # Linux systemd 安装
│ ├── setup_service.bat # Windows 计划任务管理
│ ├── install.sh / uninstall.sh
│ └── status.sh
├── systemd/ # systemd 模板
├── src/ # 源代码
│ ├── config.py # 配置加载 + OPML 解析 + 环境变量覆盖
│ ├── fetcher.py # RSS 异步抓取
│ ├── translator.py # 中英分离翻译管线
│ ├── llm.py # LLM 调用(评分/摘要/洞察/精评)
│ ├── scoring.py # 评分后处理(约束/衰减/阈值)
│ ├── cache.py # SQLite 三级缓存
│ ├── storage.py # 文件存储 + Sentinel 分段
│ ├── monitor.py # 运行监控统计
│ ├── logger.py # 日志系统
│ ├── processor.py # HTML → Markdown
│ ├── utils.py # 通用工具函数
│ ├── markdown_utils.py # Markdown 解析工具
│ ├── renderer.py # 模板渲染
│ ├── generators/ # 图片生成模块
│ │ ├── image_renderer.py # 网摘 PNG 图片渲染
│ │ └── get_chinese_font.py # 中文字体管理
│ ├── processors/ # 处理管线
│ │ ├── entity_extractor.py # 实体提取 + 角度分类
│ │ ├── entity_normalizer.py # 实体归一化(双层)
│ │ ├── entity_aliases.json # 别名映射表
│ │ ├── cluster_engine.py # 实体聚类引擎
│ │ ├── cluster_memory.py # 簇记忆系统
│ │ ├── heat_calculator.py # 簇热度计算
│ │ ├── rank_engine.py # 排序引擎
│ │ ├── trend_analyzer.py # 趋势分析
│ │ ├── feedback.py # 反馈闭环
│ │ └── cross_day.py # 跨日关联
│ ├── integrators/
│ │ └── summary_integrator.py # 摘要整合编排
│ ├── utils/
│ │ └── prompt_checker.py # Prompt 防退化检查器
│ ├── push/ # 推送模块
│ │ ├── feishu.py # 飞书 Webhook 推送
│ │ └── image_hosting.py # 图床上传模块
│ └── sections/ # 内容板块
│ ├── rss/section.py # RSS Digest 板块
│ └── webzine/section.py # 网摘生成板块
├── web_archive/ # Web 归档站点
│ ├── server.py # Flask 应用
│ ├── data_parser.py # 数据解析
│ ├── static/ # 静态资源
│ └── templates/ # HTML 模板
├── templates/ # 推送模板
│ ├── cluster_view.md # 簇视图模板
│ ├── article_view.md # 单篇视图模板
│ └── immediate_push.md # 即时推送模板
├── CHANGELOG.md # 更新日志
└── README.md # 本文件
```
## 常见问题
**Q: 如何在 NAS 上部署?**
A: 推荐使用 Docker 部署。本地构建镜像导出 → 上传到 NAS 导入 → 使用 `docker-compose.nas.yml` 启动。详见上方「部署 → Docker」章节。
**Q: 为什么英文源抓不到文章?**
A: 英文军事源(如 The War Zone、Defense One)更新频率低,可能 2-3 天无新文章。中文微信公众号源更新频繁,扩大 `--lookback` 可覆盖更多时间窗口。
**Q: 如何添加/屏蔽订阅源?**
A: OPML 文件管理为主。临时追加用 `config.json``sources.add`,屏蔽用 `sources.block``sources.block_domains`。支持 `*.domain.com` 通配。
**Q: 如何修改定时计划?**
A: 在 `.env` 中设置 `FETCH_INTERVAL_MINUTES`(拉取间隔)和 `PUSH_CRON`(推送定时,cron 表达式),重启服务即可生效。
**Q: 如何查看运行日志?**
A: 本地运行:`logs/` 目录下按时间戳命名的日志文件。Docker 运行:`docker compose logs -f` 或查看 `docker/logs/` 目录。
**Q: 内存/CPU 占用如何?**
A: 异步抓取 + 并发控制(默认 10 并发),翻译/评分均分批处理。137 源全量抓取峰值内存约 200MB,日常运行稳定在 100MB 以内。