Files
my-daily/README.md
T
2026-07-12 20:11:56 +08:00

22 KiB
Raw Blame History

军事科技每日资讯推送系统

基于大模型的军事/科技新闻采集、评分、整合与推送系统。自动抓取 RSS 订阅源,经 LLM 评分筛选与实体聚类后,生成日报并推送到飞书群。

系统架构

┌───────────────────────────────────────────────────────────────┐
│           Fetch 循环(每 30 分钟)                              │
│  RSS 抓取 → 中英翻译 → LLM 评分 → 硬约束过滤 → 动态阈值         │
│  → 实体提取 + 角度分类 → 实体归一化 → 实体聚类                   │
│  → 广告过滤 → 缓存 → 簇热度计算 → 即时推送(≥90 分)             │
└──────────────────┬────────────────────────────────────────────┘
                   ↓
┌───────────────────────────────────────────────────────────────┐
│           Push 循环(每日 08:00                               │
│  推送 1: 🔥 热点速览(簇视图)                                   │
│    今日速览 + 热点洞察(含[持续跟踪])+ 跨日追踪 + 趋势分析        │
│  推送 2: 📰 军事科技每日精选(单篇视图)                           │
│    网摘 Top3 + 分类概览(5 类 × 5 条)                          │
│  推送 3: 🖼️ 网摘图片(PNG 合并长图,图床上传后推送)              │
└───────────────────────────────────────────────────────────────┘
核心功能 说明
订阅源管理 OPML 文件管理 137 个 RSS 源(英文军事 + 中文微信公众号)
双语翻译 中英自动分离(汉字占比 > 20%),中文零 API 调用,外文并发翻译
三层评分 硬约束(非目标 ≤79 / KOL ≤89)+ 时间衰减 + 动态阈值
实体提取 5 类实体(人物/机构/装备/事件/地域)+ 6 类角度分类,双层归一化(规则 200+ 别名 + LLM 消歧)
实体聚类 加权实体重叠 + 并查集,细粒度聚类保留多角度报道,自动命名簇
簇热度 四维评分(最高分 + 传播 + 角度 + 优先)+ LLM 精评融合,热度簇提升为热点
簇记忆 SQLite 持久化已推送簇 ID,24h 内同簇不重复推送
广告过滤 四层防御纵深(tag 黑名单 → 动态阈值 → 空角度 → 空实体)
增量缓存 SQLite 三级缓存(文章/网摘/分类摘要),二次运行几乎零 token 消耗
飞书推送 即时热点推送(≥90 分)+ 每日早报三波(热点速览 + 每日精选 + 网摘图片)
双视角洞察 LLM 一次调用输出精炼摘要 + 结构化洞察(事件脉络/多角度分析/影响与展望)
分类概览 5 类军事新闻分类(装备技术/地区安全/军事改革/产业观察/其他热点),每类 top 5
网摘图片 PIL 渲染网摘为 PNG 合并长图,自适应高度 + 动态文本换行,图床 API 上传后推送飞书
反馈闭环 用户评分修正记录 + 规则自动生成,修正后的评分自动应用到后续批次
运行监控 12 阶段耗时 + API 调用统计 + 源抓取成功率 + 缓存命中率

快速开始

环境要求

  • Python 3.10+
  • 能访问 OpenAI 兼容 APIDeepSeek / 火山引擎 / OpenAI 等)

安装

# 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

# 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

配置节 控制环节 关键参数
.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)

命令用法

一键启动(推荐)

python start.py

同时启动数据管线后端(Fetch + Push 循环)和 Web 归档站点(http://0.0.0.0:8080)。所有终端输出实时写入 logs/YYYYMMDD-HHmmSS.log,按 Ctrl+C 安全停止所有服务。

main.py 命令

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 查看修正统计数据

部署

Linuxsystemd

sudo bash scripts/setup_systemd.sh        # 一键安装
bash scripts/status.sh                     # 查看运行状态
sudo journalctl -u my-daily-fetch -f       # 实时日志
sudo bash scripts/uninstall_systemd.sh     # 卸载

Windows(计划任务)

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 主机)

# 1. 准备目录和配置
mkdir -p docker/data docker/cache
cp .env docker/.env

# 2. 构建并启动
docker compose up -d --build

# ── 运维 ──────────────────────────────────────
docker compose logs -f          # 查看实时日志
docker compose restart           # 重启
docker compose down              # 停止并删除容器
docker compose up -d --build     # 更新代码后重建并启动

目录结构(Docker 部署)

my-daily/
├── docker/              # Docker 持久化目录
│   ├── .env             # 配置文件(API Key, Webhook 等)
│   ├── data/            # 运行时数据(fetch/push/image 文件)
│   └── cache/           # SQLite 缓存(避免重复消费 API
├── Dockerfile           # 镜像构建文件
├── docker-compose.yml   # Docker Compose 配置
└── .dockerignore        # 构建忽略文件

在绿联云 NAS 上的操作

# 方案 ASSH 直接操作(NAS 开启 SSH 后)
# 将 my-daily 目录上传到 NAS,进入目录执行:
docker compose up -d --build

# 方案 B:绿联云 Docker 管理器(图形界面)
# 1. 在本机构建镜像并导出:
docker save -o military-digest.tar military-digest:latest

# 2. 将 tar 文件上传到 NAS
# 3. 在绿联云 Docker > 镜像管理 > 导入镜像
# 4. 创建容器时挂载卷:
#    - /path/to/docker/.env → /app/.env(只读)
#    - /path/to/docker/data → /app/data
#    - /path/to/docker/cache → /app/cache
# 5. 设置重启策略:always
# 6. 启动容器

运维工具

脚本 平台 功能
scripts/check_sources.py 通用 源连通性检测:HTTP 可达性 + RSS 解析 + 条目时效分布
scripts/test_entity_extract.py 通用 实体提取 + 角度分类独立测试
scripts/test_normalizer.py 通用 实体归一化效果验证
scripts/test_llm_disambiguate.py 通用 LLM 消歧层独立测试
scripts/status.sh Linux 项目状态查看
scripts/install.sh Linux 依赖安装 + systemd 服务文件生成
scripts/uninstall.sh Linux systemd 服务卸载

源连通性检测

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-YYYY-MM-DD.json 当日 Fetch 结果(含评分、摘要) 7 天
data/hotalert-YYYYMMDD-HHmmSS.md 军事科技快讯(即时推送) 7 天
data/cluster-YYYYMMDD-HHmmSS.md 热点速览(簇视图推送) 7 天
data/digest-YYYYMMDD-HHmmSS.md 军事科技每日精选(单篇视图推送) 7 天
data/images/YYYYMMDD/ 网摘 PNG 图片 7 天
data/article_cache.db SQLite 三级缓存(文章/网摘/分类摘要) 7 天自动清理
logs/YYYYMMDD-HHmmSS.log 运行日志(终端输出完整记录) 手动清理

评价管线详解

从 RSS 抓取到即时推送,文章经历 12 个步骤,分属两个层面:单篇评分层和簇级评价层。

第一层:单篇评分

步骤 1 — LLM 批量评分

调用 score_batch() 对每篇文章打分(0-100),同时产出 tagssummary

配置项 位置 默认值 说明
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.json200+ 别名) PL-21 → 霹雳-21DARPA → 美国国防高级研究计划局
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

Promptprompts/precise_heat.md,失败时回退纯启发式热度。

步骤 11 — 热点识别

热点文章 = 三类并集

来源 条件
直接热点 单篇 score ≥ hot_threshold90
簇提升 所在簇热度 ≥ 阈值,且单篇 ≥ hot_threshold - 1575

步骤 12 — 簇记忆去重

cluster_memory.is_recently_pushed() 检查簇是否在 24h 内已推送,已推送则跳过。

存储:SQLite 表 cluster_memory7 天自动清理。


配置速查

参数 位置 默认值 作用环节
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:固定覆盖

项目文件说明

my-daily/
├── main.py                     # 入口 + 双循环架构
├── start.py                    # 一键启动(含日志记录)
├── config.json                 # 用户配置(不入库)
├── .env                        # API 密钥(不入库)
├── config.json.example         # 配置模板
├── .env.example                # 环境变量模板
├── requirements.txt            # Python 依赖
├── CONFIG.md                   # 配置参数完整说明
├── Dockerfile                  # Docker 镜像构建
├── docker-compose.yml          # Docker Compose 配置
├── .dockerignore               # Docker 构建忽略
├── logs/                       # 运行日志(YYYYMMDD-HHmmSS.log
├── 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  # 网摘生成板块
├── CHANGELOG.md                # 更新日志
├── PROGRESS.md                 # 开发进展追踪
└── README.md                   # 本文件

常见问题

Q: 为什么英文源抓不到文章? A: 英文军事源(如 The War Zone、Defense One)更新频率低,可能 2-3 天无新文章。中文微信公众号源更新频繁,扩大 --lookback 可覆盖更多时间窗口。

Q: 如何添加/屏蔽订阅源? A: OPML 文件管理为主。临时追加用 config.jsonsources.add,屏蔽用 sources.blocksources.block_domains。支持 *.domain.com 通配。

Q: 如何修改定时计划? A: 在 .env 中设置 FETCH_INTERVAL_MINUTES(拉取间隔)和 PUSH_CRON(推送定时,cron 表达式),重启服务即可生效。

Q: 内存/CPU 占用如何? A: 异步抓取 + 并发控制(默认 10 并发),翻译/评分均分批处理。137 源全量抓取峰值内存约 200MB,日常运行稳定在 100MB 以内。