项目定位
shayu-parser 是我把短视频解析能力从 MCP 工具里抽出来之后做的一层独立服务。它的目标很直接:用户丢进来一段分享文本或 URL,后端自动识别平台,然后返回标题、作者、封面、媒体地址、请求头和可播放/可下载的辅助信息。
它覆盖的平台比较多:抖音、B 站、小红书、快手、好看视频、微博、今日头条、豆包 AI、即梦 AI、最右、皮皮虾、皮皮搞笑,以及 Kuku 第三方兜底。
这个项目的边界也很明确:只处理公开分享页,不绕过登录、会员、DRM、付费内容或平台访问控制。很多直链本身有过期时间、Referer 约束和地域策略,所以服务返回的是“当下可用的播放材料”,不是永久资源。
架构
Static Frontend / Android / Other Client
|
| POST /api/v1/extract
v
FastAPI main.py
|
v
services/extractor.py
|
v
services/registry.py
|
+--> platforms/douyin.py
+--> platforms/bilibili.py
+--> platforms/xiaohongshu.py
+--> platforms/kuaishou.py
+--> platforms/haokan.py
+--> platforms/...
后端入口是 FastAPI。main.py 负责 CORS、健康检查、平台列表、单条解析、批量解析、媒体代理和 B 站 DASH 合并接口。
真正的调度逻辑在 services/extractor.py:
- 清理输入文本。
- 如果是
auto模式,按 host 自动识别平台。 - 查询短期内存缓存,避免同一链接反复打平台页面。
- 调用注册表中的平台解析器。
- 本地解析失败时,按配置走 Kuku 兜底。
- 统一补充
platform_name、platform_capabilities、media_urls、compliance_note。
平台路由由 services/registry.py 管理。每个平台都是一个 PlatformParser,里面记录 key、名称、hosts、支持模式、是否需要 headers,以及实际 parser 函数。新增平台时不需要改 API 层,只要注册进去即可。
接口合同
核心接口是:
GET /health
GET /api/v1/platforms
POST /api/v1/extract
POST /api/v1/extract/batch
GET /api/v1/media-proxy
GET /api/v1/remux
单条解析请求大概是:
{
"url": "https://v.douyin.com/xxxx/",
"platform": "auto",
"timeout_seconds": 20,
"client": "web"
}
成功响应会尽量统一成这些字段:
{
"success": true,
"api_version": "v1",
"request_id": "...",
"duration_ms": 123.45,
"platform": "douyin",
"platform_name": "抖音",
"mode": "video",
"title": "...",
"cover": "...",
"author": "...",
"video_url": "...",
"audio_url": "",
"images": [],
"media_urls": ["..."],
"request_headers": {},
"source_type": "local_parser",
"fallback_used": false,
"cache_hit": false,
"single_file": true
}
这个结构有两个好处。第一,Web、Android、MCP 或其它调用方可以先读通用字段,不需要知道每个平台的内部差异。第二,平台特殊字段仍然能保留下来,比如 B 站 DASH 的 audio_url 和 request_headers。
平台解析器
每个平台解析器都保持同一个函数形态:
def parse_xxx_url(input_value: str, timeout_seconds: float) -> dict:
...
这样注册表只需要保存函数引用,API 层完全不关心平台细节。
抖音解析走公开分享页 SSR 数据,提取 video.play_addr token 后探测 aweme/v1/play。B 站优先尝试公开视频的合流 durl,拿不到时返回 DASH 音视频分离轨。小红书、快手、皮皮虾一类平台则要同时考虑视频、图文、实况图等模式。
如果某个平台本地解析失败,extractor 会把失败原因塞进 fallback_reason,再尝试 Kuku 兜底。这样调用方能知道结果来自本地 parser 还是第三方兜底。
媒体代理
浏览器直接播放解析到的直链时,经常会遇到两个问题:
- CDN 要求 Referer 或 User-Agent。
- 浏览器
<video>标签没法给每个平台单独附加复杂请求头。
所以项目里有 /api/v1/media-proxy。它会:
- 校验媒体 URL,只允许 http/https。
- 拦截 localhost、内网 IP、回环地址、link-local 地址。
- 对每一次 3xx 跳转继续校验目标地址。
- 根据 CDN 域名推断 Referer。
- 透传 Range,保证播放器拖动进度条时能按需加载。
这块是安全边界,不能只把用户传来的 URL 丢给 requests.get。否则公网部署时很容易变成 SSRF 入口。
B 站 remux
B 站公开网页经常只返回 DASH:视频 .m4s 和音频 .m4s 分开。Web 播放器能处理,但普通下载器或 Android 下载体验就不太好。
/api/v1/remux 做的事情是:先对 video_url 和 audio_url 做和媒体代理一样的外部 URL 校验,然后调用 ffmpeg:
ffmpeg -hide_banner -loglevel error -y \
-headers "User-Agent: ...\r\nReferer: ...\r\n" -i <video_url> \
-headers "User-Agent: ...\r\nReferer: ...\r\n" -i <audio_url> \
-map 0:v:0 -map 1:a:0 -c copy -movflags +faststart output.mp4
它不是重新编码,只是 stream copy,所以速度和画质都比较可控。但 remux 会占服务器带宽、临时磁盘和 CPU/IO,所以项目里加了进程内并发闸门,默认同时 2 个任务,超限返回 429 remux_busy。
缓存和批量
解析成功结果有短期内存缓存,默认 TTL 120 秒,最多 256 条。这样同一个链接在 Web 页面里反复点、Android 重试、批量任务重复提交时,不会每次都去打平台页面。
批量接口 /api/v1/extract/batch 用 asyncio.Semaphore 控制并发。每条失败不会拖垮整个批次,响应里会返回 success_count、failed_count 和每条结果。
我觉得这个项目有意思的点
shayu-parser 不是把一堆解析脚本简单堆起来,而是把“平台差异”压在 platforms/ 下面,把“产品接口”稳定在 /api/v1/* 上面。
真正可维护的地方在这几条:
- 每个平台一份 parser,坏了只修自己。
- 注册表描述平台能力,让前端和客户端可以动态展示。
- 统一响应字段,让调用方不需要到处写平台判断。
- 媒体代理和 remux 单独做安全校验,不把下载链路混进 parser。
- 对外明确只支持公开内容,不把项目设计成绕过访问控制的工具。
这个项目后面最适合继续演进的方向,是给平台解析器补更多 fixture 测试,把 remux 做成异步任务队列,再给公网部署加认证、限流和日志脱敏。