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

266 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: blogwatcher-daily
description: RSS 订阅监控 + 文章导出。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite;可将文章按 txt/markdown/html 三种格式导出。
version: 1.3
category: custom
tags: [rss, blog, monitoring, automation, export]
metadata:
author: Hermes Agent
last_updated: 2026-08-29
platform: macos, ubuntu
custom_skill_path: /Users/weishen/.hermes/skills/custom/
installation_note: "blogwatcher-daily 脚本在 Mac mini 本地运行(依赖 feedparser)。需要 RSSHub 服务(http://192.168.3.45:1200)访问 YouTube/Bilibili 等被墙源。"
---
# Blogwatcher Daily
RSS 订阅监控自动化,抓取后去重入库,可按 txt / markdown / html 三种格式导出。
## 依赖
```bash
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 数据库(自动创建)
```
## 使用方法
### 扫描订阅(默认)
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py
```
- 扫描所有订阅,新增文章存入数据库
- 控制台打印每个订阅新增文章数量及总计
### 添加订阅
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
--add "频道名" "URL"
```
**YouTube 频道自动转换**:直接贴 YouTube 频道 URL 或 feed URL,
脚本自动识别并转为 RSSHub 格式,无需手动拼接。
```bash
# 以下三种方式效果相同:
--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"
```
### 列出订阅
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --list
```
### 强制回扫(`--all`)
```bash
# 强制抓取每频道10篇(忽略已读状态)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --all
```
- 每频道最多10篇
- 用途:历史内容回扫、测试订阅状态
### 标记已读
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --mark-read
```
### 导出文章(`--export`)
从 SQLite 里读取文章并输出到 stdout,格式通过 `--export {txt,markdown,html}` 选择:
```bash
# 今天所有文章 → 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 频道
```bash
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
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
--add "B站频道" "http://192.168.3.45:1200/bilibili/user/{uid}"
```
### 普通 RSS
```bash
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 自动扫描:
```bash
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 串起来:
```cron
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 内嵌,单文件独立可用