关注 up 主的新视频自动下载、自动入库——用 RSS + yt-dlp 打造全自动内容订阅管线
你关注了十几个 YouTube 频道和 B 站 up 主,每次都要手动去各个平台检查有没有更新,有更新了再手动下载。能不能像 RSS 订阅博客一样,订阅视频频道——有新视频自动下载、自动归档到 Jellyfin、自动发通知告诉你?答案是能,而且不需要自己写爬虫。这套方案的核心是 RSSHub(把任意内容源转成 RSS)+ yt-dlp 订阅模式 + Jellyfin 自动入库 + 钉钉/微信通知。四个开源工具串联,零代码实现全自动。
TL;DR:全自动链路:RSSHub 生成频道的 RSS 地址 → yt-dlp 读取 RSS,检测到新视频自动下载 →
--exec参数下载后自动移动到 Jellyfin 媒体目录 → 触发 Jellyfin 刷新 → 钉钉 Webhook 通知。核心命令:yt-dlp --download-archive archive.txt --exec "python notify.py %(id)s" "RSS_URL"。配合 cron 或 GitHub Actions 定时执行,全程无人值守。
目录
- 一、RSSHub:把任何平台变成 RSS
- 二、yt-dlp 的 RSS 订阅模式
- 三、下载后自动入库 Jellyfin
- 四、钉钉/微信/Telegram 通知推送
- 五、整条管线的串联脚本
- 六、部署到 NAS / VPS / GitHub Actions
- 七、进阶:下载后自动 AI 生成字幕
- 八、合规与温馨提示
一、RSSHub:把任何平台变成 RSS
1.1 RSSHub 是什么
RSSHub 是一个开源项目,口号是"万物皆可 RSS"。它把几百个网站和平台的内容转成标准 RSS 格式:
YouTube 频道 → RSS
B 站 up 主 → RSS
抖音用户 → RSS
知乎专栏 → RSS
微博用户 → RSS
GitHub Releases → RSS
... 总计 300+ 源
1.2 部署 RSSHub
# Docker 部署(推荐)
docker run -d \
--name rsshub \
-p 1200:1200 \
-e CACHE_EXPIRE=3600 \
diygod/rsshub:latest
# 访问 http://你的IP:1200
1.3 生成 RSS 地址
YouTube 频道:
http://rsshub:1200/youtube/channel/UCxxx
(UCxxx 是频道的 Channel ID,在频道页面的 URL 里)
B 站 up 主:
http://rsshub:1200/bilibili/user/video/123456
(123456 是 up 主的 UID)
B 站番剧:
http://rsshub:1200/bilibili/bangumi/media/28231846
抖音用户:
http://rsshub:1200/douyin/user/xxxxx
Twitter 用户:
http://rsshub:1200/twitter/user/username
获取 YouTube Channel ID:
# 方法 1:从频道页面 URL
# https://www.youtube.com/@Fireship → 查看页面源码 → 搜索 "channelId"
# 方法 2:用 yt-dlp
yt-dlp --print channel_id "https://www.youtube.com/@Fireship"
# 方法 3:在线工具
# https://commentpicker.com/youtube-channel-id.php
1.4 验证 RSS
# 确认 RSS 能正常访问
curl http://rsshub:1200/youtube/channel/UCxxx | head -50
输出应该是一个标准 RSS XML,包含视频标题、链接、发布时间等信息。
二、yt-dlp 的 RSS 订阅模式
2.1 基础用法
yt-dlp 可以直接读取 RSS 作为输入源。它会把 RSS 中的每个条目当作一个视频链接处理:
# 下载 RSS 中所有未下载过的视频
yt-dlp \
--download-archive archive.txt \
--output "/downloads/%(uploader)s/%(title)s.%(ext)s" \
"http://rsshub:1200/youtube/channel/UCxxx"
关键参数:
| 参数 | 作用 |
|---|---|
--download-archive archive.txt |
记录已下载的视频 ID,下次跳过 |
--playlist-end 5 |
只处理最近 5 条(限制首次运行时的下载量) |
--dateafter 20260101 |
只下载此日期之后的视频 |
--match-filter "duration < 3600" |
只下载时长 < 1 小时的视频 |
2.2 多频道订阅配置
#!/bin/bash
# download_all.sh - 定时执行此脚本
ARCHIVE_FILE="/downloads/archive.txt"
# 频道列表
declare -A CHANNELS=(
["YouTube-Fireship"]="http://rsshub:1200/youtube/channel/UCsBjURrPoezykLs9EqgamOA"
["YouTube-ThePrimeagen"]="http://rsshub:1200/youtube/channel/UC8ENHE5xdFSwx71u3fDH5Xw"
["B站-阮一峰"]="http://rsshub:1200/bilibili/user/video/123456"
["B站-科技美学"]="http://rsshub:1200/bilibili/user/video/789012"
)
for name in "${!CHANNELS[@]}"; do
url="${CHANNELS[$name]}"
echo "=== 检查: $name ==="
yt-dlp \
--download-archive "$ARCHIVE_FILE" \
--playlist-end 3 \
--output "/downloads/$name/%(upload_date)s - %(title).100s.%(ext)s" \
--format "bestvideo[height<=1080]+bestaudio/best[height<=1080]" \
--merge-output-format mp4 \
--sleep-interval 2 \
--no-overwrites \
--exec "python /scripts/notify.py %(id)s '%(title)s' '$name'" \
"$url"
done
echo "全部检查完成"
2.3 防止首次运行下载太多
首次运行如果不加限制,yt-dlp 会尝试下载 RSS 中的所有历史视频(可能有几百个)。限制方法:
# 方法 1:只下载最近 3 个
--playlist-end 3
# 方法 2:只下载某个日期之后的
--dateafter 20260101
# 方法 3:先手动把已有的视频 ID 加入 archive.txt
# 格式:youtube VIDEO_ID(每行一个)
三、下载后自动入库 Jellyfin
3.1 下载时直接放到 Jellyfin 目录
最简单的方式:把 yt-dlp 的输出目录直接设为 Jellyfin 的媒体库目录:
# Jellyfin 媒体目录结构
/media/
├── YouTube/
│ ├── Fireship/
│ └── ThePrimeagen/
└── Bilibili/
└── 阮一峰/
# yt-dlp 输出模板
--output "/media/YouTube/Fireship/%(title)s.%(ext)s"
3.2 下载后自动触发 Jellyfin 扫描
#!/usr/bin/env python3
# notify.py - yt-dlp 的 --exec 脚本
import sys
import requests
import os
JELLYFIN_URL = os.getenv('JELLYFIN_URL', 'http://localhost:8096')
JELLYFIN_API_KEY = os.getenv('JELLYFIN_API_KEY')
DINGTALK_WEBHOOK = os.getenv('DINGTALK_WEBHOOK')
def notify_jellyfin_refresh():
"""通知 Jellyfin 扫描新文件"""
headers = {'X-MediaBrowser-Token': JELLYFIN_API_KEY}
resp = requests.post(
f'{JELLYFIN_URL}/Library/Refresh',
headers=headers
)
if resp.status_code == 204:
print("Jellyfin 扫描已触发")
else:
print(f"Jellyfin 扫描失败: {resp.status_code}")
def notify_dingtalk(title, content):
"""发送钉钉通知"""
if not DINGTALK_WEBHOOK:
return
requests.post(DINGTALK_WEBHOOK, json={
'msgtype': 'markdown',
'markdown': {
'title': title,
'text': f'## {title}\n\n{content}'
}
})
if __name__ == '__main__':
# yt-dlp 的 --exec 会传入 %(id)s %(title)s
video_id = sys.argv[1] if len(sys.argv) > 1 else '未知'
title = sys.argv[2] if len(sys.argv) > 2 else '未知视频'
channel = sys.argv[3] if len(sys.argv) > 3 else '未知频道'
# 触发 Jellyfin 扫描
notify_jellyfin_refresh()
# 发送通知
notify_dingtalk(
f'新视频下载完成',
f'- **频道**: {channel}\n'
f'- **标题**: {title}\n'
f'- **ID**: {video_id}\n'
f'- 已自动归档到 Jellyfin,打开即可观看'
)
print(f"通知已发送: {title}")
四、钉钉/微信/Telegram 通知推送
4.1 钉钉机器人
# 添加钉钉机器人:
# 钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义 → 获取 Webhook URL
DINGTALK_URL = 'https://oapi.dingtalk.com/robot/send?access_token=xxx'
def send_dingtalk(title, text):
import requests
requests.post(DINGTALK_URL, json={
'msgtype': 'markdown',
'markdown': {
'title': title,
'text': f'## {title}\n\n{text}\n\n---\n自动下载管线'
}
})
4.2 Telegram Bot
# 创建 Bot:和 @BotFather 对话 → /newbot → 获取 Token
# 获取 Chat ID:给 Bot 发消息后访问
# https://api.telegram.org/bot<TOKEN>/getUpdates
def send_telegram(bot_token, chat_id, text):
import requests
requests.post(
f'https://api.telegram.org/bot{bot_token}/sendMessage',
json={'chat_id': chat_id, 'text': text, 'parse_mode': 'Markdown'}
)
4.3 微信公众号(Server 酱)
# 注册:https://sct.ftqq.com/ → 获取 SendKey
def send_wechat(send_key, title, content):
import requests
requests.post(
f'https://sctapi.ftqq.com/{send_key}.send',
data={'title': title, 'desp': content}
)
五、整条管线的串联脚本
#!/bin/bash
# pipeline.sh - 完整的自动下载管线
# 放到 crontab 中:0 */4 * * * /scripts/pipeline.sh
set -e
LOG_FILE="/var/log/video-pipeline.log"
ARCHIVE_FILE="/data/archive.txt"
DOWNLOAD_DIR="/data/downloads"
NOTIFY_SCRIPT="/scripts/notify.py"
echo "=== $(date) 开始执行 ===" >> "$LOG_FILE"
# 频道配置文件(每行:频道名|RSS地址)
CHANNELS_FILE="/scripts/channels.txt"
while IFS='|' read -r name url; do
# 跳过注释和空行
[[ "$name" =~ ^#.*$ ]] && continue
[[ -z "$name" ]] && continue
echo "检查: $name" >> "$LOG_FILE"
yt-dlp \
--download-archive "$ARCHIVE_FILE" \
--playlist-end 3 \
--output "$DOWNLOAD_DIR/$name/%(upload_date)s - %(title).100s.%(ext)s" \
--format "bestvideo[height<=1080]+bestaudio/best[height<=1080]" \
--merge-output-format mp4 \
--sleep-interval 2 \
--no-overwrites \
--exec "python $NOTIFY_SCRIPT %(id)s '%(title)s' '$name'" \
"$url" >> "$LOG_FILE" 2>&1
done < "$CHANNELS_FILE"
echo "=== $(date) 执行完成 ===" >> "$LOG_FILE"
# channels.txt 示例
YouTube-Fireship|http://rsshub:1200/youtube/channel/UCsBjURrPoezykLs9EqgamOA
YouTube-ThePrimeagen|http://rsshub:1200/youtube/channel/UC8ENHE5xdFSwx71u3fDH5Xw
B站-阮一峰|http://rsshub:1200/bilibili/user/video/123456
B站-科技美学|http://rsshub:1200/bilibili/user/video/789012
Crontab 定时
# 每 4 小时检查一次
0 */4 * * * /bin/bash /scripts/pipeline.sh
# 或者每天早 8 点和晚 8 点
0 8,20 * * * /bin/bash /scripts/pipeline.sh
六、部署到 NAS / VPS / GitHub Actions
6.1 Docker Compose 全家桶
# docker-compose.yml
version: '3.8'
services:
rsshub:
image: diygod/rsshub:latest
container_name: rsshub
restart: unless-stopped
ports:
- "1200:1200"
environment:
- CACHE_EXPIRE=3600
downloader:
build: ./downloader
container_name: video-downloader
restart: unless-stopped
volumes:
- ./downloads:/downloads
- ./archive.txt:/archive.txt
- ./scripts:/scripts
- ./channels.txt:/channels.txt
environment:
- JELLYFIN_URL=http://jellyfin:8096
- JELLYFIN_API_KEY=${JELLYFIN_API_KEY}
- DINGTALK_WEBHOOK=${DINGTALK_WEBHOOK}
# 不自动启动,由 cron 容器触发
cron:
image: alpine:latest
container_name: video-cron
restart: unless-stopped
volumes:
- ./scripts/crontab:/etc/crontabs/root
command: crond -f -l 2
6.2 GitHub Actions 方案
如果不想维护服务器,用 GitHub Actions(详见 GitHub Actions 自动化下载文章):
name: RSS 视频下载
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
jobs:
download:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 yt-dlp
run: pip install yt-dlp
- name: 下载新视频
run: bash scripts/pipeline.sh
- name: 上传到 Releases
uses: softprops/action-gh-release@v2
with:
files: downloads/*.mp4
七、进阶:下载后自动 AI 生成字幕
在前面管线的基础上,加上 Whisper 自动生成字幕:
# 在 notify.py 中添加
def auto_generate_subtitle(video_path):
"""下载后自动生成字幕"""
from faster_whisper import WhisperModel
model = WhisperModel("medium", device="cuda", compute_type="float16")
# 提取音频
audio_path = video_path.replace('.mp4', '.wav')
subprocess.run([
'ffmpeg', '-i', video_path, '-vn',
'-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1',
audio_path, '-y'
], capture_output=True)
# 识别
segments, _ = model.transcribe(audio_path, beam_size=5)
srt_path = video_path.replace('.mp4', '.srt')
with open(srt_path, 'w', encoding='utf-8') as f:
for i, seg in enumerate(segments, 1):
start = format_srt_time(seg.start)
end = format_srt_time(seg.end)
f.write(f"{i}\n{start} --> {end}\n{seg.text.strip()}\n\n")
os.remove(audio_path)
print(f"字幕已生成: {srt_path}")
完整链路变成:
RSS 检测到新视频
→ yt-dlp 下载
→ FFmpeg 提取音频
→ Whisper 生成字幕
→ 移动到 Jellyfin 目录
→ Jellyfin 自动扫描
→ 钉钉推送通知(含字幕生成状态)
八、合规与温馨提示
- RSSHub 是合法的开源工具,但使用它爬取内容需遵守目标平台的 robots.txt 和 ToS
- 自动下载管线仅用于个人学习研究和下载已获授权的内容
- 定时高频访问平台 API 可能触发反爬机制,请合理设置检查间隔(建议 >= 2 小时)
- 更多讨论见 下载视频算侵权吗?聊聊个人备份与版权的那条线
这套管线我跑了快一年,最大的感受是:搭建的时候折腾一天,之后每天节省 20 分钟。 现在我的手机每天早上会收到一条钉钉消息:"昨晚下载了 3 个新视频,已归档到 Jellyfin"。打开 Jellyfin,最新的内容就在首页等着我。这种"信息主动来找你"的体验,用过之后就再也回不去手动刷网页的日子了。
本文由 VidDown 技术博客原创发布。VidDown 桌面客户端内置了订阅管理功能——添加关注的频道后,自动检测更新并一键下载。访问 VidDown 了解更多。