用 Hermes Agent + Python 搭一个本地财经简报系统:6 小时踩 13 个坑的全记录
TL;DR:自己用 Hermes Agent + Python + RSS + yfinance + Telegram 搭一套完全本地化的每日财经简报系统。每天 8 点准时收到一份 1000 字的 Telegram 推送:4 大资产板块(黄金/美股/原油/加密)实时数据 + LLM 自动归因 + 风险预警。零商业 API 费用,零数据外泄,全栈可控。
这是我的第一篇博客,记录一次"从 0 搭一个真实有用的系统"的全过程。我没按理想路径写,而是按踩坑顺序写 —— 因为按教程跑通的人都能写,但真实工作中最有用的是踩坑记录。
📖 目录
[TOC]
🎯 一、为什么自己搭
市面上财经 newsletter 服务一抓一大把,订阅就行。但有几个绕不过去的痛点:
| 痛点 | 商业服务 | 自己搭 |
|---|---|---|
| 数据源 | 不可控,倾向某种叙事 | ✅ 自己挑 RSS 源 |
| 推送时机 | 服务方决定 | ✅ cron 任意定 |
| 板块结构 | 写死的 | ✅ markdown + skill 自由改 |
| 关键词偏好 | 无法配置 | ✅ 词典 + 权重 |
| 数据驻留 | 第三方拿走你的关注偏好 | ✅ 全本地 |
| 成本 | 一年几百到几千 | ✅ ~0(用免费 LLM) |
自己搭的代价是 6 小时学习成本,但搭完之后任何一处都能改。
🏗️ 二、最终架构概览
┌─────────────────────────────────────────────────────┐
│ Hermes Agent (本地 LLM CLI, 内置 cron + skill + MCP)│
│ │
│ Cron 任务:每天 8:00 触发 │
│ ↓ │
│ Skill: daily-economic-news (定义推送格式) │
│ ↓ │
│ LLM 调用 shell: │
│ $ python fetch.py --report ← 生成 markdown 报告 │
│ $ cat reports/YYYY-MM-DD.md ← LLM 真实读取报告 │
│ ↓ │
│ LLM 按"数据+新闻+预警"格式生成综述 │
│ ↓ │
│ send_message → Telegram │
└─────────────────────────────────────────────────────┘
↑ fetch.py 内部
│
┌──────────┴──────────────────────────────────┐
│ 11 个 RSS 源(央行+国际+中文) │
│ + 4 个资产板块(gold/stocks/oil/crypto.py)│
│ + SQLite 去重 + 关键词打分 │
│ + 多档时间窗口(央行 168h / 中文 96h / ... )│
└─────────────────────────────────────────────┘
核心思想:
- Python 脚本干重活(采集 / 打分 / 去重 / 计算技术指标)
- LLM 当"分析师" (读报告 + 跨板块归因 + 写预警)
- Hermes 当调度器(cron 触发 + skill 注入 + 推送渠道)
🚀 三、踩坑实录
坑 1:blogwatcher-cli 的 SSRF 防护(30 分钟)
Hermes 自带 80+ skill,其中 blogwatcher 看起来正合需求:"Monitor blogs and RSS/Atom feeds via blogwatcher-cli tool"。
curl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_linux_amd64.tar.gz \
| sudo tar xz -C /usr/local/bin blogwatcher-cli
blogwatcher-cli add "BBC Business" https://feeds.bbci.co.uk/news/business/rss.xml
blogwatcher-cli scan
报错:
proxyconnect tcp: dial tcp 127.0.0.1:7890:
... is not authorized by the client: "127.0.0.1" address is loopback
诊断 1 小时,发现是 Go 标准库 net/http 的"防 SSRF"行为:Go 不允许 HTTPS 请求通过 loopback 或 RFC1918 地址(10.x、172.16-31.x、192.168.x)的代理。我换成 WSL 里 Windows 主机的真实局域网 IP 测试:
http_proxy=http://<WSL_IP>:7890 blogwatcher-cli scan
# 报错变成:
# ... is not authorized by the client: "192.168.X.X" address is private
报错措辞从 loopback 换成 private —— 这个限制是死的。所有家用网络的代理都在 RFC1918 段里,blogwatcher-cli 在国内 WSL 里根本无法工作。
但同样的代理 curl 完全 OK:
curl -v -x http://127.0.0.1:7890 https://www.google.com
# HTTP/1.1 200 Connection established
⚠️ 教训:选库别看名字漂亮就用,先验证它对你的网络环境是否可用。Python 的 feedparser 没有这个限制,30 行代码就能替代:
import feedparser
feed = feedparser.parse("https://feeds.bbci.co.uk/news/business/rss.xml")
print("status:", feed.get("status"))
print("entries:", len(feed.entries))
print("first:", feed.entries[0].title)
# status: 200, entries: 47, first: Spirit Airlines shutting down...
完美。换路线,自己写。
坑 2:WSL 代理配置(20 分钟)
WSL2 在 Windows 11 较新版本支持 mirrored 网络模式:
ip addr show
# inet 10.255.255.254/32 scope global lo ← mirrored 模式特征
# eth1 inet 192.168.X.X/24 ← 与 Windows 主机同网段
mirrored 模式下,WSL 里的 127.0.0.1 真的指向 Windows 的 127.0.0.1,所以 http://127.0.0.1:7890 理论上可用。
💡 关键前提:Clash 客户端必须开启 "Allow LAN" :
- Clash for Windows / Clash Verge:General → Allow LAN 打开
- 不开的话,WSL 来源被识别为非"严格本地",连接会被拒绝
确认通了:
curl --connect-timeout 5 -I -x http://127.0.0.1:7890 https://www.google.com
# HTTP/1.1 200 Connection established
坑 3:reported 标记的"消费式" bug(30 分钟)
数据采集层第一版 fetch.py 用了"消费式"标记:
def get_unreported(conn, min_score):
return conn.execute(
"SELECT * FROM articles WHERE reported = 0 AND score >= ?", ...
)
def mark_reported(conn, ids):
conn.executemany(
"UPDATE articles SET reported = 1 WHERE id = ?", ...
)
# 使用:python fetch.py --report --mark-reported
跨日去重的初衷是:今天报告过的,明天不要再出现。
但有 bug:同一天内多次触发时,第二次跑就拿不到第一次的内容了。
我调试时手动跑过几次 --mark-reported,把 26 条全 mark 完。然后真正的 8:00 cron 跑时,get_unreported 只返回 1 条(早 03:16 到 08:00 之间唯一一条新增的新闻),LLM 据此生成的简报"今日只有 1 条新闻",明显不对。
修复:把"已报告"判断从 boolean 改成时间窗口:
def get_unreported(conn, min_score, hours_window=24):
"""获取过去 hours_window 小时内、score 达标的所有文章。
同一天多次触发时不会丢内容。"""
return conn.execute("""
SELECT * FROM articles
WHERE score >= ?
AND datetime(fetched_at) >= datetime('now', ?)
ORDER BY tier, score DESC, published DESC
""", (min_score, f"-{hours_window} hours"))
副作用:上次报告里出现过的新闻这次还可能出现。但只要在 24h 窗口内,被 score 排序自然排到底,不影响 LLM 抓 top N。
💡 教训:消费式标记是常见反模式。任何"用户多次触发同一动作"的场景都该用幂等设计(时间窗口或状态机)。
坑 4:新浪 Au99.99 symbol 失效(40 分钟)
最初想用上海黄金交易所 Au99.99(人民币计价,国内主流)。新浪财经免费接口:
curl --noproxy '*' "http://hq.sinajs.cn/list=gds_AU99_99,hf_GC,DINIW"
输出:
var hq_str_gds_AU99_99=""; ← 空了!symbol 失效
var hq_str_hf_GC="4629.775,..."; ← 国际金有数据
var hq_str_DINIW="...,98.2092,..." ← 美元指数有
gds_AU99_99 这个 symbol 不知什么时候被新浪下线了,没有公开的迁移说明。不死磕沪金,用国际金 hf_GC(COMEX 黄金)替代,加上美元指数 DINIW,再用 yfinance 拉历史数据算 MA5/MA20。
新浪接口的注意点(全是坑):
def fetch_sina_no_proxy(symbols):
with httpx.Client(
timeout=10,
proxy=None, # ⚠️ 关键 1:显式禁用代理
trust_env=False, # ⚠️ 关键 2:忽略环境变量代理
headers={
"Referer": "https://finance.sina.com.cn", # ⚠️ 关键 3
"User-Agent": "Mozilla/5.0",
}
) as c:
r = c.get(f"http://hq.sinajs.cn/list={symbols}")
r.encoding = "gbk" # ⚠️ 关键 4:GBK 不是 UTF-8
return r.text
trust_env=False 是核心。httpx 默认会读 https_proxy / http_proxy 环境变量,但新浪是国内站,走代理会失败(被代理服务器丢去境外节点然后被新浪屏蔽)。
🔑 核心原则:访问国内站不走代理,访问国外站走代理。代码里要显式区分。
坑 5:yfinance 偶发 delisted(20 分钟)
yfinance 是 Python 拉雅虎财经数据的库,免费、覆盖广(美股 / 期货 / 加密 / 汇率全有)。但偶尔会报 delisted:
$GC=F: possibly delisted; no price data found (period=10d)
GC=F(COMEX 黄金主力合约)明明每天都在交易,但 yfinance 第一次调用偶尔会返回这个错误。第二次调用又能拿到数据。
修复:加重试 + 双源兜底:
def fetch_one(symbol, retry=2):
for attempt in range(retry + 1):
try:
import yfinance as yf
t = yf.Ticker(symbol)
hist = t.history(period="30d")
if hist.empty or len(hist) < 2:
log.warning(f"{symbol} 数据不足,重试 {attempt+1}")
time.sleep(1)
continue
return parse_hist(hist)
except Exception as e:
log.warning(f"{symbol} 第 {attempt+1} 次失败: {e}")
time.sleep(1)
return {}
加密板块更稳,主源用 CoinGecko 免费 API(不要 key、限速 30 次/分),yfinance 兜底。
坑 6:LLM hallucination —— 不读 markdown 编内容(40 分钟)
最经典的一坑。Hermes Skill v1 这样写:
## 执行流程
1. 执行:python3 ~/news-fetcher/fetch.py --report
2. 读取:cat ~/news-fetcher/reports/$(date +%Y-%m-%d).md
3. 向用户简要汇报
跑完一次,Telegram 收到推送:
> ⚠️ 今日新闻量偏少(共 1 条 MarketWatch 文章)
📰 今日核心三件事:
1. 越南 4 月通胀超预期升温...
2. 美国通胀卷土重来...
3. 社安金资金争议升温 ← 报告里根本没有这条!LLM 在编!
LLM 没去 cat 那个 markdown 文件,直接从 fetch.py 的 stdout 日志里看到 "新增 0 条到 DB",然后自己脑补了三件事,第三件完全是 hallucination。
修复 SKILL.md,加上明确的"两步走"约束:
### 步骤 1:生成报告
执行:cd /home/USERNAME/news-fetcher && /home/USERNAME/.venv/bin/python fetch.py --report
⚠️ fetch.py 的 stdout 是日志(包含 "新增 N 条" 这种 sqlite 去重统计,
**不代表当日新闻数量**),不要把日志当报告内容。
### 步骤 2:读取真实报告内容(必须)
执行:cat /home/USERNAME/news-fetcher/reports/$(date +%Y-%m-%d).md
**所有后续分析必须基于此命令的输出内容**。
### 严格禁止
- ❌ 跳过步骤 2(必须真实 cat 报告)
- ❌ 在「今日核心三件事」中编造不在报告里的条目
再触发一次,LLM 老老实实读了 markdown 文件,三件事全部出自报告,无幻觉。
💡 教训:LLM 给定指令的边界要明确。指令"读取报告"还不够,必须明确"必须 cat 这个文件,不要从其他来源推测"。LLM 在面对二义性时倾向于"猜更省事"。
坑 7:cron 不读 .bashrc
hermes cron run a72cc351f796
# 报错:ModuleNotFoundError: No module named 'feedparser'
cron 任务起的子进程不会读 ~/.bashrc,所以:
- venv 没激活
- 代理环境变量没注入
这个坑两个层面都要解决:
层面 1:venv —— 用绝对路径调 Python:
# ❌ 不要这么写(cron 下找不到依赖)
python3 fetch.py --report
# ✅ 用绝对路径
/home/USERNAME/.venv/bin/python fetch.py --report
层面 2:代理 —— 在 SKILL.md 里显式 export 或者用 wrapper 脚本:
# wrapper.sh
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
/home/USERNAME/.venv/bin/python /home/USERNAME/news-fetcher/fetch.py --report
⚠️ 重要:本文中所有
~/...写法都是为了脱敏,实际部署务必用/home/YOUR_USERNAME/...完整绝对路径。~在某些 cron 环境下不会展开。
坑 8:中文 RSS 源大批阵亡(30 分钟)
国际源都是英文,加几个中文源。批量 curl 测试 16 个候选:
| 源 | 状态 | 条数 |
|---|---|---|
| 财新 | ❌ XML 解析失败 | 0 |
| 华尔街见闻 | ❌ 空响应 | 0 |
| 第一财经 | ❌ 非标准 RSS | 0 |
| 新浪财经 | ❌ HTTP 404 | 0 |
| 21 世纪经济 | ❌ HTTP 404 | 0 |
| 36 氪 | ✅ | 30 |
| 经济观察网 | ✅ | 26 |
| 雪球热门 | ✅ | 20 |
| FT 中文 | ✅ | 20 |
| BBC 中文 | ✅ 综合 | 38(信号密度低) |
| DW 德国之声 | ✅ 综合 | 51(信号密度低) |
| RFI 华语 | ❌ HTTP 301 | 0 |
国内财经媒体大量停掉了 RSS(财新、华尔街见闻、新浪、21 世纪、第一财经……),剩下能用的不多。最终纳入 4 个:36 氪、经济观察网、雪球热门、FT 中文。
📌 现状提醒:上面列表是 2026-05 的实测,你用的时候可能某些源又复活、某些挂掉。先 curl 测一遍再加进配置。
坑 9:FT 中文 0 条入选 —— 时间窗口杀手(15 分钟)
加进 sources.yaml 后跑 fetch.py:
2026-05-03 抓取: FT中文网
20 条 -> 过滤后 0 条 ← 全部被过滤掉?
诊断:
# 打印每条的时间和分数
[3] 德国、西班牙 4 月通胀率低于预期
pub=2026-04-29 16:00 (4 天前)
in_window=False (24h 窗口卡住了)
score=4 (其实够分)
FT 中文 RSS 更新慢,最新一条都是 4 天前的。24h 窗口下全部被时间过滤。但内容仍有价值("通胀数据"这类宏观信号一周内都有意义)。
修复:给中文源单独配更长时间窗口:
- name: FT中文网
url: https://www.ftchinese.com/rss/news
hours_back: 96 # 4 天窗口
- name: 36氪
hours_back: 72 # 3 天窗口
- name: 经济观察网
hours_back: 72
- name: 雪球今日话题
hours_back: 72
# 央行类用更长(一周一次声明)
- name: Federal Reserve
hours_back: 168 # 7 天窗口
bypass_keywords: true # 央行任何 release 都直接入选
💡 教训:同一个时间窗口阈值未必适合所有源。配置驱动 + 单源覆盖比"统一阈值 + 写死代码"灵活得多。
🛠️ 四、最终代码结构
4.1 项目目录
~/news-fetcher/
├── fetch.py # 主脚本:抓 RSS + 整合资产板块 + 生成 Markdown
├── gold.py # 黄金板块(COMEX hf_GC + DXY + yfinance MA)
├── stocks.py # 美股板块(S&P / Nasdaq / Dow / VIX)
├── oil.py # 原油板块(WTI / Brent,价差信号)
├── crypto.py # 加密板块(CoinGecko 主 + yfinance 兜底)
├── sources.yaml # RSS 源配置 + 关键词词典 + 时间窗口
├── data/news.db # SQLite 去重数据库
├── reports/ # 每日生成的 Markdown 简报
└── logs/ # 各模块日志
~/.hermes/
└── skills/research/daily-economic-news/SKILL.md # LLM 指令
4.2 sources.yaml 核心配置
sources:
- name: BBC Business
url: https://feeds.bbci.co.uk/news/business/rss.xml
weight: 0
tier: market
bypass_keywords: false
- name: Federal Reserve
url: https://www.federalreserve.gov/feeds/press_all.xml
weight: 5
tier: macro
bypass_keywords: true # 央行任何 release 都直接入选
hours_back: 168 # 7 天窗口
- name: FT中文网
url: https://www.ftchinese.com/rss/news
weight: 1
tier: market
hours_back: 96
# ... 其他源(共 11 个)
keywords:
high: # 命中加 3 分
- 央行
- 加息
- 降息
- Fed
- FOMC
- inflation
- tariff
- sanction
medium: # 命中加 2 分
- 股市
- 汇率
- 原油
- OPEC
- 黄金
company: # 命中加 1 分
- IPO
- 破产
- merger
- earnings
- bailout
filter:
hours_back: 24 # 默认窗口
min_score: 3 # 入选门槛
max_per_source: 8 # 每源最多入选条数
4.3 fetch.py 核心打分逻辑
def score_article(title, summary, kw_cfg):
"""关键词命中打分"""
text = (title + " " + summary).lower()
score = 0
for k in kw_cfg["high"]:
if k.lower() in text: score += 3
for k in kw_cfg["medium"]:
if k.lower() in text: score += 2
for k in kw_cfg["company"]:
if k.lower() in text: score += 1
return score
def fetch_source(src, cutoff, kw_cfg):
"""抓取单个 RSS 源"""
# 单源覆盖时间窗(央行等用更长回溯)
if src.get("hours_back"):
cutoff = datetime.now(tz.utc) - timedelta(hours=src["hours_back"])
feed = feedparser.parse(src["url"])
items = []
for entry in feed.entries:
pub = parse_published(entry)
if pub and pub < cutoff:
continue
# 央行类 bypass 关键词
if src.get("bypass_keywords"):
score = src["weight"]
else:
score = score_article(entry.title, entry.summary, kw_cfg) \
+ src.get("weight", 0)
items.append({
"id": article_id(entry.title, entry.link),
"title": entry.title,
"link": entry.link,
"published": pub.isoformat() if pub else "",
"source": src["name"],
"tier": src.get("tier", "market"),
"score": score,
})
items.sort(key=lambda x: -x["score"])
return items[:max_per_source]
4.4 资产板块注入
最干净的做法:fetch.py 在生成报告时调用四个独立脚本,不是 import。这样每个脚本可独立跑、独立调试:
PYBIN = "/home/USERNAME/.venv/bin/python"
asset_modules = [
("gold.py", "💰 黄金市场"),
("stocks.py", "📊 美股指数"),
("oil.py", "🛢️ 原油市场"),
("crypto.py", "₿ 加密货币"),
]
for script, title in asset_modules:
try:
md = subprocess.check_output(
[PYBIN, str(ROOT / script), "--markdown"],
timeout=30, text=True,
stderr=subprocess.DEVNULL,
)
lines.append(md)
except Exception as e:
log.warning(f"获取 {script} 数据失败: {e}")
lines.append(f"\n## {title}\n\n_数据获取失败_\n")
4.5 Hermes Skill 关键片段
完整的 SKILL.md 约 130 行,核心是强制 LLM 按格式输出:
## 执行流程(严格按顺序)
### 步骤 1:生成报告
执行:cd ~/news-fetcher && /home/USERNAME/.venv/bin/python fetch.py --report
### 步骤 2:读取真实报告(必须)
执行:cat ~/news-fetcher/reports/$(date +%Y-%m-%d).md
**所有后续分析必须基于此命令的输出**。
### 步骤 3:生成 Telegram 推送(严格按下方模板)
按以下结构生成。**4 个资产板块每个都必须独立成段,
结构为「数据 + 相关新闻总结 + 预警」三件套**:
```
📊 今日财经简报(YYYY-MM-DD)
📈 数据要点:宏观 N1 / 市场 N2 / 公司 N3,共 N 条
━━━━━━━━━━━━━━━━━━━━
💰 黄金板块
━━━━━━━━━━━━━━━━━━━━
COMEX $X.XX/oz 涨跌箭头 涨跌幅% | 振幅 X.XX%
DXY 美元指数 X.XXXX 涨跌箭头 涨跌幅%
MA5 $X | MA20 $X(标注现价相对均线位置)
📰 相关新闻:
[2-3 句中文归因,基于报告里的新闻]
⚠️ 预警:
[关键阻力/支撑 + 触发条件,1-2 句]
━━━━━━━━━━━━━━━━━━━━
📊 美股板块 / 🛢️ 原油板块 / ₿ 加密板块(同样三件套)
━━━━━━━━━━━━━━━━━━━━
📄 完整报告:~/news-fetcher/reports/YYYY-MM-DD.md
⚠️ 本简报全文均为信号提示,非投资建议。
```
## 严格禁止
- ❌ 用裸 python3 或 /usr/bin/python3.12(cron 找不到依赖)
- ❌ 跳过步骤 2(必须真实 cat 报告,不能从 stdout 推测)
- ❌ 在「相关新闻」段落里编造不在报告中的新闻
- ❌ 4 个资产板块任意一个跳过
📨 五、最终成品效果
每天 8:00 准时收到 Telegram 推送:
📊 今日财经简报(2026-05-03)
📈 数据要点:宏观 15 / 市场 19 / 公司 8,共 42 条
━━━━━━━━━━━━━━━━━━━━
💰 黄金板块
━━━━━━━━━━━━━━━━━━━━
COMEX $4629.77/oz 🔺 +0.08% | 振幅 2.23%
DXY 美元指数 98.2092 🔺 +0.11%
MA5 $4614.26 | MA20 $4718.21(↑高于MA5,↓低于MA20)
📰 相关新闻:
伊朗霍尔木兹海峡封锁持续收紧,叠加中美贸易摩擦反复,
令黄金避险需求获得支撑;美元指数微幅走强对金价形成
轻微压制,多空因素交织下金价以小幅震荡为主。
⚠️ 预警:
金价现报 $4629.77,短线高于 MA5 偏强,但受制于
MA20 中期压力;若美元指数突破 99,需警惕金价回调。
若 FOMC 释放鹰派信号则有望突破 MA20。
━━━━━━━━━━━━━━━━━━━━
📊 美股板块
━━━━━━━━━━━━━━━━━━━━
S&P 500 7230.12 🔺 +0.29%
Nasdaq 25114.44 🔺 +0.89%
Dow 49499.27 🔻 -0.31%
VIX 16.99 🔺 +0.59%
📰 相关新闻:
纳指领涨与道指下跌形成明显分化,科技股财报季表现
超预期叠加 AI 概念持续高烧推动资金向成长股迁移……
⚠️ 预警:
VIX 16.99 处正常区间但需保持警惕;S&P 已高于 MA20,
留意道指若持续走弱可能暗示板块轮动已至末期;
5 月财报季进入尾声,盈利预期修正需密切关注。
━━━━━━━━━━━━━━━━━━━━
🛢️ 原油板块(数据+新闻+预警)
━━━━━━━━━━━━━━━━━━━━
[省略]
━━━━━━━━━━━━━━━━━━━━
₿ 加密板块(数据+新闻+预警)
━━━━━━━━━━━━━━━━━━━━
[省略]
📄 完整报告:~/news-fetcher/reports/2026-05-03.md
⚠️ 本简报全文均为信号提示,非投资建议。
每板块「数据 + 新闻归因 + 预警」三件套,4 板块独立成段。预警段写出了真正的交易者视角 —— 具体阻力支撑位 + 触发条件。LLM 在做"分析师"的活,不是复读新闻。
更难得的是跨板块归因:
加密板块:…当前加密与美股(尤其纳指)联动明显,若纳指出现回调需警惕加密跟跌风险…
LLM 主动把 4 个独立板块的数据联系起来分析。这是当初定 SKILL.md "驱动因子参考" 段落时埋下的伏笔 —— 每个板块下面写 5 条"该资产的核心驱动因子",LLM 看到后就有了跨板块联动的依据。
📊 六、踩坑清单(全 13 个)
| 序号 | 坑 | 解决方案 | 耗时 |
|---|---|---|---|
| 1 | blogwatcher-cli 的 SSRF 防护 | 换 Python feedparser | 30 min |
| 2 | WSL 代理 + Allow LAN | Clash 开 Allow LAN | 20 min |
| 3 | 新浪 Au99.99 symbol 失效 | 换 hf_GC | 40 min |
| 4 | 新浪国内站不能走代理 | trust_env=False | 20 min |
| 5 | httpx 默认读环境变量代理 | 同上 | - |
| 6 | yfinance 偶发 delisted | 重试 + 双源兜底 | 20 min |
| 7 | fetch.py reported 标记的消费式 bug | 改时间窗口 | 30 min |
| 8 | LLM 不读 markdown hallucinate | SKILL.md 强制约束 | 40 min |
| 9 | cron 不读 .bashrc | 用绝对路径 | 15 min |
| 10 | FT 中文 RSS 更新慢 | 单源时间窗口 96h | 15 min |
| 11 | 关键词词典中英文不对称 | 加 30+ 中文财经词 | 20 min |
| 12 | 推送格式信息密度低 | 4 板块独立成段重构 | 30 min |
| 13 | weixin 推送 asyncio bug | hermes 内部问题,已知放弃 | - |
💎 七、架构优势
7.1 配置驱动
| 想做的事 | 改哪 |
|---|---|
| 新增 RSS 源 | sources.yaml 加一条 |
| 调关键词权重 | sources.yaml keywords 节 |
| 调时间窗口 | sources.yaml 单源 hours_back |
| 改推送格式 | SKILL.md 几句话 |
| 改触发时间 | hermes cron edit 一条命令 |
| 换推送渠道 | --deliver telegram/discord/... |
| 加新资产板块 | 写个 xxx.py + fetch.py 加一行 |
0 行代码改动就能做大部分调整。
7.2 数据 / 逻辑 / 调度三层分离
配置层:sources.yaml
数据层:fetch.py + 4 个资产模块
逻辑层:SKILL.md (LLM 行为规范)
调度层:hermes cron
推送层:hermes deliver target
每一层独立可改、独立可测。
7.3 零商业 API 成本
- RSS 源:全部公开免费
- 黄金 / 美元指数:新浪财经免费接口
- 美股 / 原油 / 加密:yfinance + CoinGecko(免费)
- LLM:Hermes 配置的免费试用渠道
7.4 全本地化
所有数据在你机器上,没有任何"我点了什么、关心什么"被第三方收集。
🚀 八、后续路线图
按价值排序的扩展方向:
- 加美债收益率(10Y / 2Y)—— 是金价 / 股市的核心驱动因子,缺了不全
- 加港股 / 中概互联 —— yfinance
^HSI/KWEB,30 分钟即可 - 沪金 Au99.99 —— 换 SGE 官方接口或东财,待探查
- 周报功能 —— 周日 cron 让 LLM 综合 7 天报告写"本周财经主线"
- 历史回测 —— 累积 1 个月数据后,用 LLM 对比"过去 30 天的预警准确率"
- 加宏观日历 —— CPI / PMI / NFP 等数据发布日提前预警
🎬 九、写在最后
为什么写这篇:技术博客大多是"理想路径"(按教程走一遍就成),但真实工作中最有用的是"踩坑记录"。这套系统从 0 到生产用了 6 小时,期间踩了 13 个坑,每一个都是一个 stack overflow 没有现成答案的具体问题。把它们串起来记录下来,给同样想搭一套的人省点时间。
值得吗:6 小时 vs 一年几百块订阅费,从纯经济角度未必值。但从"完全可控、信息无偏、数据所有权" 的角度,绝对值。更重要的是这个过程中你掌握了一整套"LLM agent + Python + 配置驱动 + 自动化调度" 的能力,下次想搭别的(个人健康监测、量化策略监控、内容采集 …… 任何"每天定时拉数据 + LLM 分析 + 推送"的场景)几乎是套模板。
这是 LLM-as-tool 的好用例:LLM 不能 24h 监控市场,但能每天定时把一堆原始数据 + 新闻整合成"分析师视角"的简报。这就是它最该干的事 —— 你给它结构化的数据 + 明确的格式约束,它给你高密度的归因和预警。整个系统里 LLM 只是个"专业写手",重活脏活全在 Python 脚本里。
📝 十、关键 takeaway
如果你只能记住三点:
- 配置驱动 + 数据/逻辑/调度三层分离 —— 让系统对扩展友好
- LLM 指令必须明确,禁止行为要列出来 —— hallucination 不是 bug 而是"指令二义性"的产物
- 同样的功能,不同库行为差异巨大 —— 别看名字漂亮就用,先验证它对你的环境是否可用
🎁 完整代码
完整代码(fetch.py / gold.py / stocks.py / oil.py / crypto.py / sources.yaml / SKILL.md)我整理后会放到 GitHub,链接将更新在评论区。如果你想第一时间拿到,点赞 + 收藏 + 关注,更新会推送给你。
如果有任何问题欢迎评论区交流,我会一一回复。
写于 2026-05 本文为原创,转载请注明来源。 如对你有帮助,点赞 / 收藏 / 关注三连支持一下,是我持续输出的动力 🙏