Files
atlas/blogwatcher-daily/SKILL.md

22 KiB
Raw Permalink Blame History

name, description, version, category, tags, metadata
name description version category tags metadata
blogwatcher-daily RSS 订阅监控 + 文章管理 + 分类导出。使用 RSSHub + feedparser 抓取订阅,自动去重入库 SQLite;支持按单个订阅或分类更新/查看/标记已读/删除文章,可将文章按 txt/markdown/html 导出。附「自然语言操作指南」,AI 智能体可直接把用户口语映射到 CLI 命令。 1.6 custom
rss
blog
monitoring
automation
export
category
author last_updated platform custom_skill_path installation_note
Hermes Agent 2026-09-01 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 数据库(自动创建)

订阅文件格式(subscriptions.txt)

分类|频道名|URL[|enabled]
  • 新格式(推荐):AI技术|Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg
  • 旧格式(仍兼容):频道名|URL(读入时自动归为 未分类,无需手动迁移)
  • # 开头的行视为注释
  • 判定规则:若 parts[1] 以 http:// / https:// 开头,视为旧格式;否则新格式

示例(同一文件可混用):

# 新格式
AI技术|Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg
AI技术|灵姐说AI|http://192.168.3.45:1200/youtube/channel/UCMenHpvUet8myDntrTh7Ckw
电子书|SaltTiger|https://salttiger.com/feed/
新闻热点|BBC中文网|http://192.168.3.45:1200/youtube/channel/UCb3TZ4SD_Ys3j4z0-8o6auA

# 旧格式(自动归为「未分类」)
Jon Law|http://192.168.3.45:1200/youtube/channel/UCQM_HxoKmza1simMUchYfVA

使用方法

扫描订阅(默认)

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

添加订阅

python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --add "频道名" "URL" [--category "分类"]
  • 省略 --category 时,分类默认为 未分类
  • 分类是任意字符串(中英文皆可),后续可通过手工编辑 subscriptions.txt 调整

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

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

列出订阅

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

输出按分类分组,序号 [N] 是全局 1-based(跨分类连续),可直接用于 --update N / --articles N / --sub N 等命令:

📡 当前订阅 (37 个 · 9 个分类):

── 【AI技术】(7 个) ──
  [1] Tech With Tim
      http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg
  [3] 小白AI笔记
      ...

── 【电子书】(1 个) ──
  [25] SaltTiger
      https://salttiger.com/feed/

强制回扫(--all)

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

更新单个订阅(--update SUB)

只抓某一个订阅,不动其它源。SUB 可传订阅名或 --list 中的序号(详见下方「订阅标识(SUB)」)。

# 按名字更新
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --update "Tech With Tim"

# 按 --list 里的序号更新(例如第 3 个订阅)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --update 3

# 强制回扫单个订阅最新 10 篇(叠加 --all)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --update 3 --all
  • 抓取失败 → 打印错误、退出 0(不打断脚本管道)
  • 订阅未找到 → 退出 1

查看某订阅的文章(--articles SUB)

从数据库读文章(不联网),可选只看未读:

# 该订阅全部文章(按 fetched_at 降序)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --articles "Engadget"

# 只看未读
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --articles "Engadget" --unread

# 也可用序号
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --articles 22 --unread

输出示例:

📄 [Engadget] 未读文章 (244 篇):

  [1] 🆕 How long is a new Fire TV Stick actually supposed to last?
      🔗 https://www.engadget.com/...
      📅 Sat, 30 Aug 2026 12:00:00 GMT
  ...
  • 已读用 📖,未读用 🆕
  • 每篇左侧的 [N] 是订阅内 1-based 序号,可直接用于下方 --delete --article-id N

标记已读(--mark-read [SUB])

不带参数 → 全库标记;带 SUB → 只标记指定订阅。

# 全库标记为已读
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --mark-read

# 只标记某个订阅
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --mark-read "Tech With Tim"

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

删除文章(--delete SUB [--article-id N])

删除该订阅下所有文章或单篇。序号 N 与 --articles 显示顺序一致。

# 删除某订阅下所有文章
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --delete "Jon Law"

# 删除某订阅下第 2 篇(先用 --articles 查序号)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --delete "Jon Law" --article-id 2

# 序号也支持
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --delete 2 --article-id 1
  • 空库删除是幂等 no-op,退出 0
  • 序号越界 → 退出 1,不改动 DB
  • 删除不可撤销,请配合 --articles SUB 先看清再删

订阅标识(SUB)

--update / --articles / --mark-read / --delete 的 SUB 参数按以下顺序匹配:

  1. 数字 → 视为 --list 中的 1-based 序号
  2. 精确名称(区分大小写)
  3. 忽略大小写精确匹配
  4. 忽略大小写子串匹配

匹配到多条时脚本报错并列出候选:AI 智能体应向用户回问「你指的是 X 还是 Y?」或改用序号消歧。

导出文章(--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

# 单订阅最新 20 篇 → Markdown(按订阅名或 --list 序号)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export markdown --sub "异次元软件世界" --limit 20

# 【分类导出】某分类下所有订阅的全部文章
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export markdown --category "AI技术"

# 【分类导出】某分类下所有订阅今日文章
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export markdown --category "AI技术" --date $(date +%F)

# 【分类导出】某分类下所有订阅最新 30 篇
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
  --export html --category "新闻热点" --limit 30 > news.html

过滤规则(--date / --limit / --sub / --category 的组合语义)

参数组合 行为
--export FMT(默认) 今天(本地时区)的所有文章
--export FMT --date X 指定日期 X 的所有文章
--export FMT --limit N 忽略日期,取全库最新 N 篇
--export FMT --date X --limit N 日期 X 的最新 N 篇
--export FMT --sub SUB 该订阅全部文章(--sub 也会让默认日期失效)
--export FMT --sub SUB --limit N 该订阅最新 N 篇
--export FMT --sub SUB --date X 该订阅在日期 X 的文章
--export FMT --sub SUB --date X --limit N 该订阅在日期 X 的最新 N 篇
--export FMT --category C 该分类下所有订阅的全部文章(分类过滤同样让默认日期失效)
--export FMT --category C --date X 该分类下所有订阅在日期 X 的文章
--export FMT --category C --limit N 该分类下所有订阅的最新 N 篇
--export FMT --category C --date X --limit N 该分类下在日期 X 的最新 N 篇

--sub 和 --category 互斥(前者指定单个订阅,后者指定一组)。

--sub 的 SUB 参数解析规则:序号 → 精确 → 忽略大小写精确 → 忽略大小写子串,详见「订阅标识(SUB)」。

--category 的分类参数解析规则:精确 → 忽略大小写精确 → 忽略大小写子串(子串匹配跨多个分类时会打印警告,但仍会返回全部命中的订阅)。

参数速查

参数 说明
--export {txt,markdown,html} 触发导出模式并指定格式
--date YYYY-MM-DD 目标日期(本地时区,默认今天)
--limit N 文章数上限;单独使用时忽略日期
--sub SUB 只导出该订阅的文章(SUB=--list 序号或名称)
--category CATEGORY 只导出该分类下所有订阅的文章(与 --sub 互斥)

输出去向

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

格式细节

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

自然语言操作指南(AI 智能体路由表)

用户通常不会记 CLI 参数。下面是常见口语意图 → 应执行命令的映射,AI 智能体应据此选择命令。

命令前缀统一是:

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

下表省略前缀,只列参数。

1. 抓取 / 更新

用户可能说 命令 备注
"刷新所有博客"、"扫一下今天有什么新的"、"跑一下每日抓取"、"更新一下" (无参数) 全量扫描所有订阅
"更新 Tech With Tim"、"抓一下 Engadget 最新的"、"看看第 3 个订阅有没有更新" --update "SUB" SUB = 名字或序号
"强制回扫 X 最新 10 篇"、"忽略已读重新抓 X"、"重新拉 X 全部" --update "SUB" --all 忽略去重、强抓 10 篇
"强制回扫所有订阅"、"忽略已读全部重抓" --all 全量强抓

2. 查看订阅

用户可能说 命令
"列出所有订阅"、"我订了哪些博客"、"有哪些频道"、"看订阅列表" --list

3. 查看文章

用户可能说 命令
"看看 Engadget 里都有什么文章"、"X 的所有文章"、"列出 X 的文章" --articles "SUB"
"X 还没看的"、"X 有哪些未读"、"X 的未读列表"、"哪些没读过" --articles "SUB" --unread
"第 3 个订阅有什么文章" --articles 3

AI 智能体判断"未读"意图的关键词:未读 / 没看 / 还没读 / 新的 / unread / 待看。

4. 标记已读

用户可能说 命令
"全部标为已读"、"清一下未读"、"我都看过了"、"全部当作看过" --mark-read
"把 X 都当作看过了"、"X 全部标为已读"、"这个频道我不感兴趣了先标已读" --mark-read "SUB"

5. 删除文章

用户可能说 命令 危险度
"删掉 X 的所有文章"、"清空 X 的历史"、"重置 X" --delete "SUB" 高 — 建议先 --articles SUB 确认
"删除 X 里第 3 篇"、"把 X 的第 2 条去掉"、"X 的 [5] 号删了" --delete "SUB" --article-id N 中 — 序号来自 --articles 输出

重要:删除不可逆。AI 智能体收到"删除 / 清空 / 移除"类意图时,除非用户明确肯定,应先执行 --articles SUB 展示待删内容再回问一次。

6. 添加订阅

用户可能说 命令
"订阅 X"、"添加频道 X"、"加个 RSS"、"帮我关注 X"(附 URL) --add "NAME" "URL"
"订阅 X 到 AI 分类"、"把 X 加到新闻热点"、"关注 X(分类 = Y)" --add "NAME" "URL" --category "Y"
  • YouTube URL、RSSHub URL、原生 RSS URL 都直接传,脚本内部自动路由。
  • 未指定分类时默认归为 未分类,事后可手工编辑 subscriptions.txt 调整。

7. 导出

用户可能说 命令
"把今天的文章导出成 markdown"、"生成今日摘要 md" --export markdown
"导出 2026-08-28 的 HTML"、"我要那天的网页版" --export html --date 2026-08-28
"最新 20 篇导出为 txt"、"给我全库最新 20 篇" --export txt --limit 20
"把异次元软件世界的文章导成 markdown"、"只导 Engadget 的" --export markdown --sub "SUB"
"导出异次元最新 20 篇 md"、"给我 Slashdot 最近 20 条 html" --export markdown --sub "SUB" --limit 20
"把 X 昨天的文章导出成 html" --export html --sub "SUB" --date YYYY-MM-DD
"今天的文章导 html 到桌面" --export html > ~/Desktop/$(date +%Y-%m-%d).html
"导出所有 AI 技术频道的文章"、"AI 分类全部导出"、"给我看看 AI 分类的东西" --export markdown --category "AI技术"
"导出所有 AI 技术频道今日的文章"、"AI 分类今天的" --export markdown --category "AI技术" --date $(date +%F)
"AI 分类最新 30 篇"、"电子书类最新 10 篇" --export markdown --category "AI技术" --limit 30
"把新闻热点导成 html 存桌面" --export html --category "新闻热点" > ~/Desktop/news-$(date +%Y-%m-%d).html

AI 智能体判断"分类"意图的关键词:分类 / 类别 / 类型 / 归类 / 一类 / 那类 / category。 常见分类举例:AI技术 软件工具 新闻热点 科技资讯 技术博客 电子书 学习教育 生活方式 未分类。用户口语中说"AI 类"通常指分类名 AI技术,说"新闻类"通常指 新闻热点。若不确定,先跑一次 --list 让用户/自己看清可用分类,再定夺。

订阅标识(SUB)如何解析

--update / --articles / --mark-read / --delete 的 SUB 参数按下列顺序匹配:

  1. 数字 → 视为 --list 中的 1-based 序号
  2. 精确名称(区分大小写)
  3. 忽略大小写精确
  4. 忽略大小写子串

匹配到多条时脚本会打印候选并退出 1。此时 AI 智能体应:

  • 向用户回问「你指的是 X 还是 Y?」
  • 或改用 --list 里的序号做无歧义定位

匹配不到时也退出 1。

AI 智能体决策要点

  1. 看/查询类(无副作用):--list、--articles。可直接执行。
  2. 抓取类(可能改 DB):--update、无参扫描、--all。安全,直接跑。
  3. 写入类(会改 DB):--mark-read、--add(含 --category)。可直接执行,但结束后简报结果。
  4. 删除类(不可逆):--delete。先展示要删的内容再执行,除非用户在同一轮明确肯定。
  5. 导出类(只读):--export(含 --sub / --category)。stdout 输出,AI 应帮用户决定重定向到哪个文件。
  6. SUB 消歧:能用序号就优先用序号(--list 是廉价的),避免多义。
  7. 分类消歧:用户说"AI 类的"、"新闻类的"这种口语,先用 --list 拉一下可用分类,再选最匹配的作为 --category 值;不确定就回问用户。

添加订阅示例

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" \
  --category "AI技术"

Bilibili

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

普通 RSS

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

省略 --category 时新订阅会归入「未分类」,之后可手工编辑 subscriptions.txt 调整分类。

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 新格式,或 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') 过滤 + 可选 feed_url 按单订阅过滤(--sub)+ 可选 feed_url IN (...) 按分类内多订阅过滤(--category)+ 可选 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 内嵌,单文件独立可用