"在新闻的田野里拾穗。"
本地部署的 AI 新闻采编工作流。每天从 Google News 拉新闻 → 规则粗筛 → AI 精排合并翻译 → DuckDuckGo 验证守门 → AI 整理润色 → 中文报告。只保留 24 小时内的真热新闻,没有就不出报。
命名取自 glean(拾穗)—— 在收割后的田地里捡拾散落的麦穗。这正是这个工具做的事:在海量新闻噪音里捡有价值的、新鲜的、可信的信号。
- 6 个分类:AI / 科技 / 财经 / 宏观 / 潮流 / 人文,每类有语义定义注入 AI prompt,避免串类
- 四层时效控制:
- RSS 拉取按
published时间过滤(超 30h 丢弃) - AI 精排 prompt 注入今天日期,排除过时事件
- DuckDuckGo 当日搜索做守门员(搜不到 = 整条丢弃)
- 全部淘汰则不出报告
- RSS 拉取按
- 混合筛选:规则便宜先压候选,AI 精排只看 top;token 可控
- 同事件合并:相似标题自动聚合,AI 还会跨源识别合并,一条新闻列出所有来源链接
- 全中文输出:英文标题/摘要由 AI 翻译重写
- DDG 补全 + AI 整理 + 质量控制:摘要短的搜索补全,AI 整理成 30-80 字;含色情/博彩词的片段直接拦截
- 防限流:RSS 抓取限并发 + 抖动 + 指数退避重试
- 实时日志:浏览器里看流水线进度,AI 输出 token 流式可见
- Token 报告:每次运行结束打印用量汇总,可选成本估算
- OpenAI 兼容:DeepSeek / Qwen / Moonshot / 火山 / OneAPI 都能用
# 首次运行:自动创建 venv、装依赖、生成 .env
./run.sh
# 编辑 .env 填 API key
vim .env
# 再跑一次
./run.sh浏览器打开 http://127.0.0.1:8765,点【立即采编】。
.
├── source.txt # 采编源(Google News RSS 关键词)
├── rules.yaml # 筛选规则 + 时效配置 + AI 配置
├── .env # API key 等
├── run.sh # 一键启动
├── app/
│ ├── main.py # FastAPI 入口 + SSE 实时推送 + 状态管理
│ ├── fetcher.py # 异步 RSS 抓取(限并发/重试/时效过滤)
│ ├── filter.py # 规则粗筛 + AI 精排(流式,含合并/翻译/时效)
│ ├── scraper.py # DDG 验证守门员 + AI 整理
│ ├── reporter.py # 报告 JSON 持久化
│ ├── rules.py # rules.yaml 加载
│ ├── logger.py # 全局事件广播(日志/AI 流/状态)
│ ├── stats.py # Token 统计
│ ├── static/style.css # 全部样式
│ └── templates/ # base / index / report
└── data/reports/ # 历史报告 JSON
source.txt ──┐
▼
┌──────────────────────────┐
│ 1. RSS 抓取 │ 并发 5、指数退避重试、published > 30h 丢
└──────────────────────────┘
▼
┌──────────────────────────┐
│ 2. 规则粗筛 │ 关键词加分 + 来源权重 + 黑名单 + 相似度去重
└──────────────────────────┘
▼
┌──────────────────────────┐
│ 3. AI 精排(按分类) │ 注入今天日期 + 分类语义;翻译中文 + 合并同事件
└──────────────────────────┘
▼
┌──────────────────────────┐
│ 4. DDG 验证 + AI 整理 │ 搜不到 / 含污染词 / AI 判空 → 整条丢弃
└──────────────────────────┘
▼
┌──────────────────────────┐
│ 5. 保存报告 │ 无新闻则不出,状态显示"今天无新闻"
└──────────────────────────┘
每行一条:分类 | 关键词 | 语言 | 时效
| 字段 | 说明 |
|---|---|
| 分类 | AI / 科技 / 财经 / 宏观 / 潮流 / 人文(或任意自定义) |
| 关键词 | Google News 搜索词,建议带动作词如 "发布" "release" 减少噪音 |
| 语言 | zh 中文 / en 英文 |
| 时效 | 1h / 12h / 1d / 7d(推荐统一 1d) |
AI | 大模型 发布 | zh | 1d
科技 | humanoid robot | en | 1d
财经 | 美股 | zh | 1d
宏观 | Fed rate decision | en | 1d
潮流 | viral trend | en | 1d
人文 | archaeology discovery | en | 1d
# 分类语义定义:AI 精排时严格按这里筛选,跑错类的丢弃
category_definitions:
AI: |
AI 技术与行业动向:新模型/产品发布、研究突破、开源、智能体、监管、行业变局。
严格排除:纯股价/融资/财报(财经类)。
科技: |
前沿科技:能源/航天/机器人/生物/量子物理/新材料/武器装备。
严格排除:纯财报/股价;纯软件 AI 模型。
# ... 其他分类
# RSS 抓取
fetch:
concurrency: 5 # 同时并发数,太高会被 Google News 限流
retries: 3 # 失败后总尝试次数,指数退避 1s/2s/4s
max_age_hours: 30 # published 超过此小时数直接丢弃
# 规则粗筛
block_keywords: # 命中即丢
boost_keywords: # 关键词加分(kw: 权重)
source_weights: # 域名权重(bloomberg.com: 2.0)
min_score: 0.8 # 低于此分数淘汰
dedup_similarity: 0.6 # 相似度去重(trigram Jaccard,0~1)
# AI
ai:
enabled: true
model: gpt-4o-mini
candidates_per_category: 25 # 每类送 AI 的候选数上限
pick_per_category: 10 # 每类最终挑出数
fetch_detail: true # DDG 验证开关
enrich_timelimit: d # DDG 时效(d/w/m/y/null)
polish_detail: true # AI 整理 DDG 内容
enrich_blocklist: # 含这些词的搜索片段直接丢
- 小姐
- escort
- casino
# ...
# pricing: # 可选成本估算
# input: 0.15
# output: 0.60
# currency: "$"修改 rules.yaml 和 source.txt 无需重启,下次点采编就重新加载。
OPENAI_API_KEY=sk-xxxxx
OPENAI_BASE_URL=https://api.openai.com/v1 # 可选
OPENAI_MODEL=gpt-4o-mini # 可选,覆盖 rules.yaml
PORT=8765 # 可选任何 OpenAI 兼容协议的服务都行,只改 .env:
| 服务 | OPENAI_BASE_URL | OPENAI_MODEL |
|---|---|---|
| OpenAI 官方 | https://api.openai.com/v1 |
gpt-4o-mini / gpt-4o |
| DeepSeek | https://api.deepseek.com/v1 |
deepseek-chat |
| 阿里 Qwen | https://dashscope.aliyuncs.com/compatible-mode/v1 |
qwen3.6-flash / qwen-plus |
| Moonshot | https://api.moonshot.cn/v1 |
moonshot-v1-8k |
| 火山方舟 | https://ark.cn-beijing.volces.com/api/v3 |
你的 endpoint id |
| 本地 OneAPI | http://127.0.0.1:3000/v1 |
任意已配模型 |
- 阅读宽度 720px,长文舒适
- 每条新闻:中文标题 + ★1-5 重要度徽章 + AI 推荐理由 + 加粗中文摘要 + 斜体 DDG 补充 + 多个来源标签
- 多源合并的有绿色
N 源合并徽章 - 关键词显示为米黄小药丸 chip
- 顶部 Token 卡片:调用次数、入/出/合计、按分类细分、可选成本估算
- 历史报告可一键删除
服务一直跑着,cron 触发:
# crontab -e 每天早上 8 点
0 8 * * * curl -s -X POST http://127.0.0.1:8765/run >/dev/null服务守护用 pm2 / tmux / launchd plist 任选。
| 路径 | 方法 | 说明 |
|---|---|---|
/ |
GET | 首页(实时日志 + 报告列表) |
/run |
POST | 触发采编(异步后台跑) |
/status |
GET | 当前状态 JSON |
/stream |
GET | SSE 实时事件流 |
/api/reports |
GET | 报告列表 JSON |
/reports/{name} |
GET | 报告 HTML 页 |
/reports/{name}/delete |
POST | 删除报告 |
FastAPI + Jinja2 + httpx + feedparser + openai SDK + ddgs。无前端框架,纯 SSE + 原生 JS。
- 宁缺毋滥:四层时效层层把关,宁可不出报告也不展示过时/重复/低质内容。看到"今天无新闻"是预期行为,不是 bug。
- 规则 + AI 两层筛选:纯规则不够聪明,纯 AI 太贵。规则把候选压到每类 25 条,AI 只挑最终 top。
- DDG 当守门员而非补充工具:原设计是"摘要短的才搜补全",现在升级为"所有 AI 选出的都要 DDG 在 1 天内搜得到"。这能过滤掉过时/伪热点/营销稿。代价是 token 用量翻倍。
- 同事件双重合并:规则层用字符 trigram Jaccard 处理近重复,AI 层处理语义重复。
- AI 整理 DDG 而不是直接展示:原始片段经常是英文广告语,AI 整理后短、中文、聚焦;遇到色情/无关内容自动返回空,触发丢弃。
- 报告存 JSON 不存 DB:本地工具单用户,文件就够;后期想接 Slack/邮件推送也好处理。
- SSE 而不是 WebSocket:单向推送够用,浏览器原生支持,无须额外库。
以 74 个源、qwen3.6-flash 为例:
- 总耗时:约 90~180 秒(取决于网络与 DDG 响应)
- Token:约 50k~150k(其中 60% 在 AI 精排,40% 在 DDG 整理)
- 报告条数:通常 15~40 条(严格时效过滤后)
想省钱:把 enrich_timelimit 改 w、polish_detail 改 false,或换更便宜的模型。
想多内容:source.txt 加更多关键词、enrich_timelimit 改 w 或 m、pick_per_category 调大。

