| name | daily-news |
| description | TechDaily 科技日报自动生成与部署。从 RSS 抓取 AI 科技新闻,生成精美 HTML 日报 + TTS 音频播报,部署到 news.techdou.com。触发词:日报、daily news、新闻播报、早报、TechDaily、科技日报、新闻部署。 |
Daily News — TechDaily 科技日报
项目仓库: ~/Project/news.techdou.com
线上地址: https://news.techdou.com
定时任务: 每天 10:00 (Asia/Shanghai),isolated session,超时 900s
架构概览
┌─ 00:00 agent cron ─→ switch-to-pending.sh ─→ symlink→pending.html
│
RSS (daily.juya.uk) │
↓ fetch + parse │
pipeline.py ────────────┘─ 10:00 agent cron
↓ assemble.py (template-head + template-tail)
完整 HTML (~140KB)
↓ + TTS 音频 (mmx speech synthesize)
deploy_site() → public/YYYY/MM/DD/ → git push → 服务器 git pull
↓ sudo ln -sfn YYYY/MM/DD/index.html /var/www/news.techdou.com/index.html
↓
gen-archive.sh → 更新 archive.html
部署双轨说明:deploy_site()(pipeline.py,cron 用)与 deploy.sh(手动备用)是两套等价实现,都做「git push + ssh sync + ln -sfn + gen-archive」。以 pipeline.py 为准,deploy.sh 仅在手动触发时使用。
项目结构
~/Project/news.techdou.com/
├── SKILL.md # 本文件
├── README.md
├── scripts/
│ ├── pipeline.py # 全自动 Pipeline(主入口)
│ ├── assemble.py # HTML 组装器(template-head + content + template-tail)
│ ├── deploy.sh # 部署到服务器(手动备用入口)
│ ├── switch-to-pending.sh # 凌晨 cron 调用:根 / 切到 pending 页
│ └── gen-archive.sh # 生成 archive.html
├── templates/
│ ├── template-head.html # CSS 模板(68KB,固定)
│ ├── template-tail.html # JS 模板(37KB,固定)
│ └── no-update.html # 待更新页源模板(复制为 public/pending.html)
├── docs/
│ ├── content-schema.md # content.json 字段说明
│ └── rss-structure.md # RSS 结构参考
└── public/ # 部署到服务器的静态文件
├── 2026/06/DD/ # 每日日报 (index.html + audio.mp3)
├── archive.html # 往期回顾
├── pending.html # 待更新占位页(凌晨/RSS失败时根 / 指向它)
├── pet.html / pet.js # 吉祥物
└── assets/ # logo、favicon、宠物素材
首页状态机
根 / 显示什么,由服务器 /var/www/news.techdou.com/index.html 这个 symlink 决定(nginx 仅做静态映射)。三种状态靠改 symlink 指向切换:
| 状态 | symlink 指向 | 触发器 |
|---|
| 🌙 凌晨待更新(00:00–日报发布前) | pending.html | 凌晨 agent cron 调 switch-to-pending.sh |
| 📰 当日日报(发布后) | YYYY/MM/DD/index.html | pipeline.py deploy_site() 成功后 ln -sfn |
| ⚠️ RSS 失败/无今日内容 | pending.html | pipeline.py deploy_no_update() |
设计要点:pending.html 是常驻在 public/ 根目录的占位页(内容由 templates/no-update.html 复制),随 git 同步到服务器。切换只需一条 ln -sfn,占位页不再复制进日期目录——避免被 gen-archive.sh 误收为「正常日报」。
每日循环:00:00 切 pending → 10:00 pipeline 生成日报并切到当日 → 次日 00:00 又切 pending,周而复始。
每日发布流程
全自动 Pipeline(推荐)
python3 ~/Project/news.techdou.com/scripts/pipeline.py --date 2026-06-27
自动完成:RSS 抓取 → 解析 → 播报稿 → TTS → HTML 生成(assemble.py 完整模板)→ 部署 → 归档
可选参数
| 参数 | 说明 |
|---|
--date YYYY-MM-DD | 指定日期(默认最新) |
--skip-tts | 跳过音频生成 |
--skip-deploy | 跳过部署 |
--parse-only | 仅解析 RSS,输出 JSON |
手动部署(如需)
bash ~/Project/news.techdou.com/scripts/deploy.sh 2026-06-27
bash ~/Project/news.techdou.com/scripts/gen-archive.sh
bash ~/Project/news.techdou.com/scripts/switch-to-pending.sh
修改了 templates/no-update.html 后,记得 cp 一份到 public/pending.html,否则线上待更新页不会更新。
TTS 音频规范
| 项目 | 值 |
|---|
| 工具 | mmx speech synthesize |
| 音色 | Podcast_girl (MiniMax) |
| 格式 | mp3, 32kHz, 128kbps |
| 时长 | 220-300 秒 |
| 开头 | "欢迎收听 TechDaily 每日科技快报" |
| 结尾 | "感谢收听,我们明天再见" |
| 输入 | 必须 --text-file(stdin 会截断) |
HTML 模板架构
pipeline.py 内部调用 assemble.py 生成 HTML:
- pipeline.py 解析 RSS → 结构化数据
- 构建 content.json 格式(briefs/stories/sidebar/categories)
- 调用
assemble.assemble(content, head, tail, rss_bodies)
- assemble.py 从
templates/template-head.html + 动态 body + templates/template-tail.html 拼装
- 自动注入:audio URL、body data-date、GLOSSARY JSON
模板文件不要改,除非明确要做 UI 升级。
部署流程(GitHub 版本管理)
Mac 本地生成 → public/YYYY/MM/DD/ → git commit + push GitHub → 服务器 git pull + rsync webroot
- 本地:pipeline.py 生成到
public/ 目录
- GitHub:
github.com/techdou/techdaily 做版本管理
- 服务器:
/var/www/news.techdou.com-repo/ git pull → rsync 到 webroot
- 同步脚本:服务器
/var/www/sync-from-git.sh
音频版本管理
audio.mp3 纳入 git 版本管理(不再排除)。原因:
- 云服务器存储有限(60GB SSD),GitHub 做可靠备份
- rsync
--delete 会删除 webroot 中 git 没有的文件,纳入管理避免丢失
- 旧日报的音频可随时从 git 恢复
sync-from-git.sh 关键设计
服务器端 /var/www/sync-from-git.sh 的 rsync 规则:
rsync -a --delete \
--exclude='.git' \
--exclude='/index.html' \
$REPO/public/ $WEBROOT/
用 /index.html 锚定根目录,子目录的 YYYY/MM/DD/index.html 不受影响。
audio.mp3 已纳入 git 管理,正常同步。
错误处理
| 场景 | 处理 |
|---|
| RSS 不可达 | deploy_no_update() 切 symlink → pending.html + 1h 后自动重试 |
| 无今日内容 | deploy_no_update() 切 symlink → pending.html |
| TTS 失败 | 重试 3 次(间隔 10s),仍失败 → 检查服务器已有音频 → 只部署 HTML |
| 部署失败 | 保留本地,通知用户 |
Cron 配置
两个 agent cron 协作,构成「凌晨切 pending → 上午发日报」的每日循环:
{
"name": "daily-news-pending",
"schedule": { "kind": "cron", "expr": "0 0 * * *", "tz": "Asia/Shanghai" },
"payload": { "kind": "agentTurn", "message": "运行 bash ~/Project/news.techdou.com/scripts/switch-to-pending.sh 切换首页到待更新状态" },
"sessionTarget": "isolated"
}
{
"name": "daily-news",
"schedule": { "kind": "cron", "expr": "0 10 * * *", "tz":
凌晨 cron 已配好基建(脚本就绪),agent cron 任务由用户自行在 agent 平台注册。
若当日 10:00 pipeline 走 RSS 失败分支,symlink 会被 deploy_no_update() 切到 pending,无需凌晨 cron 介入。
关键教训
只留「未来还会踩同一坑」的元认知教训。一次性 bug 修复细节 → 看 git commit message。
| 日期 | 教训 |
|---|
| 08-24 | 部署提交夹带脚本重写 = 回归源头:07-31 的 deploy commit 重写 pipeline.py 时把 07-11/07-13 的两道正则修复同时退回,静默潜伏 25 天。教训:脚本改动单独 commit;正则旁加 CRITICAL 注释。 |
| 08-24 | 无链接新闻回归:RSS 源部分新闻无超链接(如 glm-5.3-turbo),正则必须链接可选。已加对账哨兵:overview 与 stories 条数不匹配 → 抛错+写 alert,禁止静默发布残缺日报。 |
| 08-24 | Cloudflare 对 mp3 缓存 24h,同日重新生成音频必须带 ?v=mtime 后缀换 URL;assemble.py 的 audio URL 不能硬拼,必须从 content['audio_url'] 透传。 |
| 07-11 | RSS 源可能不给某些新闻配超链接 → story 正则不能强制要求 <a> 标签,链接改为可选提取(pipeline.py + assemble.py 两处同步修复) |
| 07-13 | 跨标签正则穿透:宽松正则 <h2>...</h3> 会跨 tag 匹配,渲染出错。修复要点:(1) 解析前先砍掉概览段(html_content = html_content[html_content.find('<hr>')+4:]);(2) 正则用反向引用 <h([23])>...</h\1> 强制首尾标签级别一致。两层防御:先剥离上下文,再修正则。 |
| 07-13 | 部署链路约束:服务器 daily.juya.uk 被 403 + 无 GitHub 凭证 → 必须 Mac 本地 curl 抓 RSS → scp 上传 → 手工 rsync 上线。cron 跑在服务器上时不能直接拉 RSS。 |
| 06-28 | 首页状态机:占位页用常驻 public/pending.html + symlink 切换,不要复制进日期目录。首页 = 服务器 /var/www/.../index.html symlink,凌晨 cron 切 pending、pipeline 成功切当日,是「状态机」而非「覆盖文件」。 |