129 lines
5.2 KiB
Markdown
129 lines
5.2 KiB
Markdown
# sreweekly.py
|
||
|
||
SRE Weekly 抓取与文章提取脚本。单文件、无外部服务依赖,专为 n8n 定时任务设计:脚本自身**只负责抓取与落盘**,不发送邮件、不推送通知。
|
||
|
||
## 功能概览
|
||
|
||
- 通过 RSS (`https://sreweekly.com/feed/`) 扫描新一期 SRE Weekly
|
||
- **回溯抓取历史期刊**(RSS 只暴露最近 ~10 期,`--backfill` 直接走站点 URL 抓任意老期)
|
||
- 把每期 `content:encoded` 原样保存为 HTML
|
||
- 解析出每期的所有文章条目(标题 / 链接 / 简介 / 引用 / 作者)
|
||
- 用 [`trafilatura`](https://github.com/adbar/trafilatura) 抓文章原文并转 Markdown
|
||
- 用 `manifest.json` 记录每期状态,避免重复处理
|
||
- 每篇文章生成一个独立 `.md` 文件,含 feed 简介 + 抓取的正文
|
||
|
||
## 安装依赖
|
||
|
||
```bash
|
||
pip3 install feedparser trafilatura
|
||
```
|
||
|
||
`trafilatura` 仅在抓文章正文时才必需;若加 `--skip-fetch` 只用 feed 简介,可省略。
|
||
|
||
## 目录结构
|
||
|
||
默认工作目录 `~/Workspace/nexus/sreweekly/`(可用 `--dir` 覆盖):
|
||
|
||
```
|
||
sreweekly/
|
||
├── manifest.json # 状态记录
|
||
├── html/<id>-<日期>.html # 每期原始 HTML(feed 版 或 backfill 站点整页)
|
||
├── pages/<id>.html # backfill 抓到的站点整页缓存
|
||
├── articles/<id>/ # 每篇文章原始 HTML 缓存 + index.json 抓取日志
|
||
│ ├── 01-<slug>.html
|
||
│ └── index.json
|
||
└── markdown/<id>/ # 每期文章的 markdown 输出
|
||
├── 01-<slug>.md
|
||
└── 02-<slug>.md
|
||
```
|
||
|
||
## 用法
|
||
|
||
```bash
|
||
# 扫描 feed,新增期落盘并登记 manifest
|
||
python3 sreweekly.py
|
||
|
||
# 只探测有无新期(供 n8n 分支判断),输出 NEW:533,532 或 NONE
|
||
python3 sreweekly.py --check
|
||
|
||
# 提取指定期文章为 markdown(默认联网抓正文)
|
||
python3 sreweekly.py --extract 533
|
||
python3 sreweekly.py --extract 433..464 # 期号区间(缺失的期告警跳过,不阻断)
|
||
python3 sreweekly.py --extract latest # 最新一期
|
||
python3 sreweekly.py --extract pending # 所有未提取的期(n8n 每日推荐)
|
||
python3 sreweekly.py --extract all # 全部(配合 --force 覆盖)
|
||
|
||
# 查看状态
|
||
python3 sreweekly.py --status
|
||
python3 sreweekly.py --issues
|
||
|
||
# 回溯抓取历史期刊(绕过 RSS 只有最近 ~10 期的限制)
|
||
python3 sreweekly.py --backfill 430..533 # 抓期号 430 到 533 之间所有未登记的期
|
||
python3 sreweekly.py --backfill 1..533 # 全站回溯(500+ 期,约 12-15 分钟)
|
||
python3 sreweekly.py --backfill 450 # 只抓单期
|
||
# 回溯完再走标准提取流程:
|
||
python3 sreweekly.py --extract pending
|
||
# 或只提取回溯的那段区间:
|
||
python3 sreweekly.py --extract 430..533
|
||
```
|
||
|
||
## 选项
|
||
|
||
| 选项 | 说明 |
|
||
| --- | --- |
|
||
| `--feed URL` | RSS 地址,默认 `https://sreweekly.com/feed/` |
|
||
| `--dir PATH` | 工作目录,默认 `~/Workspace/nexus/sreweekly/` |
|
||
| `--force` | 重新提取已提取过的期,覆盖旧 markdown |
|
||
| `--skip-fetch` | 不抓文章正文,只使用 feed 简介 |
|
||
| `--force-fetch` | 忽略 `articles/` / `pages/` 本地缓存,重新联网抓 HTML |
|
||
| `--sleep 秒数` | 每次抓取间隔(默认 `1.5`,`--backfill` 也遵守此参数) |
|
||
|
||
## 典型 n8n 流程
|
||
|
||
1. **Cron 触发** → `python3 sreweekly.py --check`
|
||
2. 判断输出:`NEW:...` 走后续,`NONE` 结束
|
||
3. `python3 sreweekly.py`(扫描落盘新期)
|
||
4. `python3 sreweekly.py --extract pending`(抓正文 + 生成 markdown)
|
||
5. 下游节点读取 `markdown/<id>/*.md` 汇总成邮件/推送
|
||
|
||
## Markdown 输出示例
|
||
|
||
每篇文章一个 `.md` 文件:
|
||
|
||
```markdown
|
||
# 文章标题
|
||
|
||
- **期号**: SRE Weekly Issue #533(2024-05-20)
|
||
- **作者**: Jane Doe
|
||
- **链接**: https://example.com/post
|
||
|
||
## 简介
|
||
(来自 feed 的原始描述与引用)
|
||
|
||
## 正文
|
||
(trafilatura 抽取的原文 markdown;失败时显示错误信息)
|
||
```
|
||
|
||
## Manifest 字段
|
||
|
||
`manifest.json` 中每期条目字段:
|
||
|
||
| 字段 | 含义 |
|
||
| --- | --- |
|
||
| `id` / `title` / `url` / `pub_date` | 期号、标题、原文链接、发布日期 |
|
||
| `html_file` | 落盘 HTML 相对路径 |
|
||
| `fetched_at` / `extracted_at` | 抓取与提取时间戳 |
|
||
| `extracted` | 是否已提取为 markdown |
|
||
| `article_count` | 该期文章数 |
|
||
| `markdown_dir` | markdown 输出目录(相对) |
|
||
| `articles_fetched` / `articles_failed` | 正文抓取成功/失败数 |
|
||
| `source` | 数据来源标记:`backfill` 表示由 `--backfill` 从站点整页抓取(feed 抓取无此字段) |
|
||
|
||
## 故障排查
|
||
|
||
- **feed 抓取失败或为空**:检查 `--feed` 网络可达性,或 feed 结构变更
|
||
- **`trafilatura returned empty`**:某些站点反爬/JS 渲染,正文段落会写入错误提示但不影响其它文章
|
||
- **重跑某期**:`--extract <id> --force`;重新联网抓正文再加 `--force-fetch`
|
||
- **回溯抓不到某期**:脚本会打印 `⚠️ #<id> 抓取失败:...` 并继续下一期,最后汇总失败期号;对失败期号单独 `--backfill <id> --force-fetch` 重试即可
|
||
- **回溯抓到的期日期显示当天**:说明页面 `<a class="updated">` 结构变了,`extract_pub_date_from_page` 未命中,会 fallback 到今天——不影响提取,但排序会乱
|