Files
atlas/blogwatcher-daily/SKILL.md
2026-08-29 21:18:59 +08:00

9.0 KiB
Raw Blame History

name, description, version, category, tags, metadata
name description version category tags metadata
blogwatcher-daily RSS 订阅监控 + 文章导出。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite;可将文章按 txt/markdown/html 三种格式导出。 1.3 custom
rss
blog
monitoring
automation
export
author last_updated platform custom_skill_path installation_note
Hermes Agent 2026-08-29 macos, ubuntu /Users/weishen/.hermes/skills/custom/ blogwatcher-daily 脚本在 Mac mini 本地运行(依赖 feedparser)。需要 RSSHub 服务(http://192.168.3.45:1200)访问 YouTube/Bilibili 等被墙源。

Blogwatcher Daily

RSS 订阅监控自动化,抓取后去重入库,可按 txt / markdown / html 三种格式导出。

依赖

pip3 install feedparser

feedparser 是 Python 最成熟的 RSS 解析库,支持 RSS 1.0/2.0/Atom、任意编码、畸形 XML。HTML 导出使用 Python 标准库 html.escape,无第三方依赖。

实际抓取架构(重要)

YouTube 频道 URL
    ↓ 脚本自动识别并转为 RSSHub 格式
http://192.168.3.45:1200/youtube/channel/{id}
    ↓
curl → RSSHub → YouTube

普通 RSS(Engadget, Slashdot 等)
    ↓ 脚本直接访问(绕过 RSSHub /rss/ 路由)
原始 RSS URL
    ↓
curl → 目标网站

关键发现:RSSHub 的 /rss/{url} 路由不稳定(返回 RSSHub 欢迎页), 因此普通 RSS 源直接访问,不走 RSSHub 代理。

脚本内部 build_fetch_url() 根据 URL 类型自动选择路由:

  • YouTube → RSSHub
  • 已有的 RSSHub URL → 直接使用
  • 其他 → 直接访问原始 URL

目录结构

~/.hermes/skills/custom/blogwatcher-daily/
├── SKILL.md                    # 本文件
├── scripts/
│   └── blogwatcher-daily.py    # 扫描入库 + 订阅管理 + 文章导出(单文件)
├── subscriptions.txt           # 订阅列表(name|URL)
└── blogwatcher.db              # SQLite 数据库(自动创建)

使用方法

扫描订阅(默认)

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py
  • 扫描所有订阅,新增文章存入数据库
  • 控制台打印每个订阅新增文章数量及总计

添加订阅

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --add "频道名" "URL"

YouTube 频道自动转换:直接贴 YouTube 频道 URL 或 feed URL, 脚本自动识别并转为 RSSHub 格式,无需手动拼接。

# 以下三种方式效果相同:
--add "Tech With Tim" "https://www.youtube.com/channel/UC4JX40jDee_tINbkjycV4Sg"
--add "Tech With Tim" "https://www.youtube.com/feeds/videos.xml?channel_id=UC4JX40jDee_tINbkjycV4Sg"
--add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg"

列出订阅

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --list

强制回扫(--all)

# 强制抓取每频道10篇(忽略已读状态)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --all
  • 每频道最多10篇
  • 用途:历史内容回扫、测试订阅状态

标记已读

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --mark-read

导出文章(--export)

从 SQLite 里读取文章并输出到 stdout,格式通过 --export {txt,markdown,html} 选择:

# 今天所有文章 → Markdown
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --export markdown

# 指定日期 → HTML
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export html --date 2026-08-28 > digest.html

# 全库最新 20 篇 → 纯文本
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export txt --limit 20

过滤规则(--date 与 --limit 的组合语义)

参数组合 行为
--export FMT(默认) 今天(本地时区)的所有文章
--export FMT --date X 指定日期 X 的所有文章
--export FMT --limit N 忽略日期,取全库最新 N 篇
--export FMT --date X --limit N 日期 X 的最新 N 篇

参数速查

参数 说明
--export {txt,markdown,html} 触发导出模式并指定格式
--date YYYY-MM-DD 目标日期(本地时区,默认今天)
--limit N 文章数上限;单独使用时忽略日期

输出去向

  • 正文 → stdout(可直接 > file.md 重定向)
  • 进度 → stderr(📊 N 篇文章 → FMT)

格式细节

  • txt:分频道分组,纯文本无标记,适合终端查看或管道
  • markdown:## 【频道】 + - [title](link) + 描述引用块
  • html:完整 <!DOCTYPE html> 单文件,内嵌简洁 CSS,标题/URL/描述全部经 html.escape 转义(防 XSS)

添加订阅示例

YouTube 频道

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg"

Bilibili

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --add "B站频道" "http://192.168.3.45:1200/bilibili/user/{uid}"

普通 RSS

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --add "博客名" "http://192.168.3.45:1200/rss/https://example.com/feed.xml"

RSSHub vs 直接 RSS

脚本自动判断路由,不需要手动选择:

源类型 路由方式 示例
YouTube 频道/用户/feed RSSHub 自动识别并代理
RSSHub URL(已存储) 直接使用 http://192.168.3.45:1200/youtube/channel/...
普通 RSS(Engadget, Slashdot 等) 直接访问 https://www.engadget.com/rss.xml

⚠️ RSSHub 的 /rss/{url} 代理路由不稳定(实测试返回欢迎页),普通 RSS 不要走 RSSHub。

配置

配置项 默认值
RSSHub 地址 http://192.168.3.45:1200(可设置 RSSHUB_URL 环境变量覆盖)
数据库 ~/.hermes/skills/custom/blogwatcher-daily/blogwatcher.db
订阅列表 ~/.hermes/skills/custom/blogwatcher-daily/subscriptions.txt

Cron Job 设置

推荐每天早上 6:00 自动扫描:

cronjob --create \
  --name "Blogwatcher Daily" \
  --schedule "0 6 * * *" \
  --repeat 999 \
  --deliver "telegram:5038825565" \
  --prompt "使用 blogwatcher-daily 技能执行每日 RSS 扫描。

执行步骤:
1. 加载 blogwatcher-daily 技能
2. 运行扫描:python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py
3. 汇报本次新增文章数量"

如需扫描 + 落盘 HTML 摘要(例如放进 Obsidian vault 或自建 dashboard),可用原生 crontab 串起来:

0 6 * * *  python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py && \
           python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
             --export html > ~/blogwatcher/$(date +\%Y-\%m-\%d).html

数据流程

扫描入库(默认命令)

  1. 加载订阅:从 subscriptions.txt 读取所有 name|URL 对
  2. URL 路由:build_fetch_url() 自动判断:
    • YouTube → 转为 http://192.168.3.45:1200/youtube/channel/{id}
    • RSSHub URL → 直接使用
    • 其他 → 直接访问原始 URL(绕过不稳定的 /rss/ 路由)
  3. 抓取:curl 获取 XML,SSL 跳过验证
  4. 解析:feedparser 提取 title、link、description、pub_date
  5. 去重:按 link 去重,已存在则跳过
  6. 存储:新文章存入 SQLite
  7. 输出:控制台打印每个订阅新增数量与总计(不生成文件)

导出(--export)

  1. 查询:fetch_articles() 按 date(fetched_at, 'localtime') 过滤 + 可选 LIMIT N
  2. 分组:_group_by_channel() 按 channel_title 分桶
  3. 格式化:FORMATTERS[fmt] 分发到 format_txt / format_markdown / format_html
  4. 输出:正文写 stdout,📊 N 篇文章 → FMT 进度写 stderr

已知问题

问题 说明 状态
How to of the Day wikiHow 完全封了爬虫 ❌ 不可用
How-To Geek 远程服务器关闭连接 ❌ 不可用

feedparser 已修复:電腦玩物 ✅、阿榮福利味 ✅、异次元软件世界 ✅、Slashdot ✅

注意事项

  • 首次使用需 pip3 install feedparser
  • YouTube 路由需要 RSSHub 容器内配置 HTTP_PROXY/HTTPS_PROXY 环境变量
  • 每次最多取每源 10 篇(TED Talks 等除外,取 20 篇)
  • 数据库自动创建,无需手动初始化
  • OPML 文件可用 Python 解析后批量导入(参考 /wiki-ingest 流程中的 OPML 解析代码)
  • 导出的"今天"按本地时区(SQLite date(fetched_at, 'localtime')),不是 UTC
  • HTML 导出使用 html.escape 转义标题/链接/描述,可安全嵌入网页;CSS 内嵌,单文件独立可用