最头疼的不是单个功能,而是把”下载视频、提取字幕、AI 总结、生成思维导图、收钱”这么长一条链路装进一个还能维护的 Web 应用里。最初我动过用 Django 全家桶的念头,评估完发现大部分内置能力我用不上,反而要把异步和 SSE 生生抠出来;换 Flask 又得自己拼一堆胶水。最后我把后端落在 FastAPI 上,配合原生 HTML/JS 前端和 SQLite,一套轻量组合把四个核心模块串了起来。下面按架构分层展开,说清楚每一环的取舍和踩过的坑。
一、项目定位
这个产品要解决的,其实是三类用户的真实痛点。有人想把 YouTube、B 站、抖音、小红书上的视频存到本地慢慢看;有人只想快速拿到视频的核心内容,不想看完整场;还有一部分用户愿意为省时间和自动整理付费。我把这三件事拆成了三个独立入口,互不干扰,运营上也好分别埋点统计。
- 多平台下载:YouTube/B站/抖音/小红书一键下载;
- AI 总结:视频字幕自动总结,生成要点和思维导图;
- 付费变现:通过 VIP 订阅 + 每日配额盈利。
功能拆成三条主线之后,代码结构就清晰了:每条主线对应一组独立的路由和服务,后续加功能(比如再接入一个平台)不会互相踩脚。
二、整体技术栈
定技术栈之前,我列了一张候选表,把每一层的选项和选它的理由写清楚,再逐项过。这一层决定了后面所有代码的写法,当时来回改了两版才定稿:
| 层 | 技术 | 理由 |
|---|---|---|
| 后端 | FastAPI | 异步、SSE 友好、自动 OpenAPI |
| 数据库 | SQLite | 零运维、适合小型项目 |
| 视频下载 | yt-dlp | 业界广泛使用的开源下载器 |
| AI | DeepSeek | 中文好、价格低 |
| 鉴权 | JWT | 简单、无状态 |
| 支付 | Stripe | 全球支付、SDK 完善 |
| 前端 | 原生 HTML/JS | 无构建、热更新 |
这套组合里我唯一犹豫过的是数据库。项目初期用户量小、单机部署,SQLite 完全够用;等数据规模上来再平迁 PostgreSQL 也不迟,因为 ORM 层是 SQLAlchemy,抽象一致。确定下来后,我把技术栈写进部署文档,后面所有讨论都以这张表为准,团队里再冒出”要不要换 MongoDB”这种反复时,直接拿它当依据。
三、模块划分
单文件堆逻辑的项目我写过太多,改一个功能要翻半天。所以这次从第一天就按目录分层,app 下分成 core、api、services、models、schemas、db、utils 七块,职责各管一段:
backend/
├── app/
│ ├── main.py # FastAPI 入口
│ ├── core/ # 配置、日志、安全
│ ├── api/ # 路由
│ │ ├── video.py # 视频相关
│ │ ├── summary.py # 总结相关
│ │ ├── auth.py # 登录/注册
│ │ └── payment.py # 支付
│ ├── services/ # 业务服务
│ │ ├── downloader.py # yt-dlp 封装
│ │ ├── transcriber.py # 字幕提取
│ │ ├── ai_summary.py # AI 总结
│ │ ├── mindmap.py # 思维导图
│ │ └── payment.py # Stripe 封装
│ ├── models/ # SQLAlchemy 模型
│ ├── schemas/ # Pydantic Schema
│ ├── db/ # 数据库
│ └── utils/ # 工具
├── data/ # 视频/字幕文件
└── requirements.txt
这里有个小经验:路由层只做参数接收和响应包装,真正的逻辑全部下沉到 services。这样万一将来要把下载器从 yt-dlp 换成别的实现,只动 services/downloader.py 一个文件,路由和数据库都感知不到变化。
四、核心数据流
架构图画得再漂亮,不如把一次完整请求的数据流写出来。我把它钉在文档最前面,写代码时时刻对着它,防止漏掉中间环节:
用户提交视频链接
↓
Downloader 服务(yt-dlp 拉视频 + 字幕)
↓
Transcriber 服务(无字幕时用 Whisper 转录)
↓
AI Summary 服务(DeepSeek 总结 + 思维导图)
↓
结果存 SQLite + 缓存
↓
SSE 实时推送给前端
这条链路里最容易出错的是”无字幕兜底”这一步。很多视频根本没有字幕文件,我最初想当然直接调 Whisper 转录,结果遇到长视频时处理要几分钟,用户以为卡死了。后来我把进度拆成”下载中→提取字幕→AI 总结”三段,每段都通过 SSE 推给前端,用户能看到走到哪一步,体验立刻不一样。
五、为什么选 FastAPI
框架选型我认真对比过 Flask 和 Django。单看功能三套都能做,差别集中在异步支持、文档自动化和类型安全这三件事上:
| 维度 | FastAPI | Flask | Django |
|---|---|---|---|
| 异步原生 | ✅ | ❌ | ❌ |
| 自动 OpenAPI | ✅ | ❌ | 需 DRF |
| 类型提示 | ✅ | 弱 | 弱 |
| 性能 | 优 | 中 | 中 |
| 适合场景 | API + 异步 | 简单 | 全栈 |
平台选 FastAPI 关键原因:
- 异步支持好(SSE 流式、并发下载);
- 自动生成 OpenAPI 文档(前端可直接生成类型);
- 类型安全(Pydantic)。
我在项目里体会最深的还是异步。下载和 AI 调用全是网络 IO,同步框架一个请求占一个线程,压测一上去线程池就被打爆;FastAPI 的 async/await 让我用协程同时跑下载和总结,单台机器就能扛住几十路并发。光是这一点,在我眼里就基本定了。
六、核心接口
入口文件保持得越薄越好。下面是 main.py 的全部内容,只做三件事:创建应用、挂 CORS 中间件、注册路由:
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="AI 万能视频下载总结器", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 自动生成的文档
# /docs (Swagger UI)
# /redoc
CORS 这里踩过坑:allowcredentials=True 时 alloworigins 不能用 “*”,必须写具体域名,否则浏览器直接拦截跨域请求。开发期我把本地前端地址加进去,上线前再收紧。写完后不需要任何额外配置,打开 /docs 就能在 Swagger UI 里试接口,联调效率高了不少。
七、为什么用 SQLite
选数据库时我列了三层判断:要不要单独部署、性能够不够、迁移成本高不高。结论是 SQLite 全过:
| 优势 | 说明 |
|---|---|
| 零运维 | 单文件、备份就是拷文件 |
| 性能够用 | 单机 < 10 万 QPS |
| 部署简单 | Docker 镜像小 |
适用场景:
- 单机部署;
- 用户量 < 1 万;
- 写少读多(Web 应用典型)。
数据规模上来后可平滑迁移到 PostgreSQL(SQLAlchemy 抽象层一致)。
实际上手之后发现一个细节:SQLite 默认模式在高并发写入时容易报 “database is locked”,解决办法是打开 WAL 模式,这个在踩坑一节里专门讲了。单机小项目上 PostgreSQL 的运维成本是实打实的负担,而 SQLite 的读写性能在万级用户内完全兜得住,所以我一直强调技术选型要对着场景说话。
八、为什么用 yt-dlp
下载这块我没有给每个平台写独立爬虫,理由很直接:反爬策略天天变,自己维护十来个平台就是给自己挖坑。yt-dlp 是 youtube-dl 的活跃分支,社区替我把脏活干了:
- 支持 1000+ 网站(YouTube/B站/抖音/小红书/Twitter/…);
- 持续更新(适配平台反爬变化);
- Python 原生、可程序化调用;
- 活跃社区。
平台用 yt-dlp 而不是各平台 SDK 的原因:统一接口、多平台支持、社区维护。
代价也不是没有——yt-dlp 更新频繁,平台改一次接口就得跟着升一次版本,否则下载直接报错。我把升级做成了每月例行任务,并在 requirements.txt 里锁住版本下限,防止 CI 环境装到旧版。
九、为什么用 DeepSeek
AI 总结这环,我对比过好几个模型。中文内容的理解质量、接口成本、响应速度,三个指标综合下来,DeepSeek 更贴合这个场景:
| 优势 | 说明 |
|---|---|
| 中文理解 | 中文场景表现优于 GPT |
| 价格低 | 约 GPT-4 的 1/30 |
| API 兼容 | 兼容 OpenAI SDK |
| 速度快 | 响应时间短 |
任务分层:
- 简单总结:DeepSeek;
- 复杂推理:GPT-4(按需切换)。
API 兼容这一点省了我不少事:代码里只维护一个 OpenAI 风格的客户端,切换模型只改 base_url 和 model 名。成本账也算得过来,同一个视频的总结任务,DeepSeek 的单价只有 GPT-4 的几十分之一,对按次收费的产品来说毛利空间完全不一样。
十、为什么用 Stripe
支付是最不能自己造轮子的模块,牵扯到银行卡数据安全,出一次事故就够喝一壶。Stripe 的成熟度在这里体现得很直接:
- 全球支付支持(信用卡/Apple Pay/Google Pay);
- Webhook 完善;
- SDK 成熟;
- 合规安全(PCI DSS)。
平台用 Stripe Checkout 托管支付页,前端不接触卡号,安全性最高。
我最看重的是 Checkout 托管支付页:卡号直接输在 Stripe 的页面上,我的服务器和数据库从来不碰支付敏感信息,PCI 合规的负担被大幅简化。唯一要处理的坑是 Webhook 重放——同一个支付事件 Stripe 可能回调多次,我在接收端按事件 ID 做了幂等去重,下面踩坑一节会展开。
十一、为什么用 JWT 而非 Session
鉴权方案我在 JWT 和 Session 之间摇摆过。Session 需要服务端存状态,CORS 场景下 cookie 配置还容易出幺蛾子;JWT 把状态塞进 token 本身:
- 无状态:服务端不存 session;
- 跨域友好:CORS 场景无 cookie 困扰;
- 移动端友好:iOS/Android 原生支持。
前端是纯静态页面,接口域名和页面域名分开部署,Session 方案的 cookie 要处理 SameSite 和 Secure 的组合,稍不注意登录态就丢。换成 JWT 后,前端把 token 放 localStorage,请求头带 Authorization 就完事。代价是 token 过期要处理续签,我在踩坑里记了”静默续签”的解法。
十二、为什么用 SSE 而非 WebSocket
AI 总结是典型”服务端→客户端”单向流,选通信协议时我只对比了两种:
| 协议 | 适用 | 平台选 |
|---|---|---|
| SSE | 单向流(服务端推) | ✅ AI 总结进度 |
| WebSocket | 双向 | ❌ |
AI 总结是”服务端→客户端”单向流,SSE 完美匹配。
WebSocket 是双向的,我们的场景里用户几乎不往服务器推东西,引入它只是增加心跳、重连、消息帧格式这些复杂度。SSE 用普通 HTTP 就能跑,配合 EventSource 前端原生支持,断线还能自动重连,实现成本低一个量级。
十三、为什么用 markmap
思维导图这块,我一开始想过后端生成图片,测完发现渲染慢、还要维护图片存储,果断放弃。markmap 换了个思路——前端直接渲染:
- 接收 Markdown 文本;
- 实时渲染为可交互树状图;
- 支持导出 PNG/SVG;
- 前端纯 JS,零后端渲染。
平台让 AI 直接输出 Markdown 思维导图,markmap 前端解析。
这个方案的妙处在于 AI 本身就是文字模型,让它按 Markdown 列表层级输出,天然就是思维导图的数据结构,后端一行渲染代码都不用写。用户还能在导图里展开、收起节点,比静态图片好用得多。
十四、整体部署
部署方案我压到最小:一台云主机,docker-compose 起两个容器——后端服务和一个 nginx。配置文件长这样:
# docker-compose.yml
services:
backend:
image: video-summarizer:latest
ports:
- "8000:8000"
volumes:
- ./data:/app/data
environment:
DATABASE_URL: sqlite:///./data/app.db
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./frontend:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
单台 4C8G 云主机即可运行。
两个关键点:一是视频文件目录通过 volume 挂在宿主机,重部署容器不丢数据;二是 API key 走环境变量注入,绝不写进镜像。实测单台 4C8G 跑满业务绰绰有余,成本控制在很低的水位。
十五、性能与扩展
上线前我做了压测,单机数据记下来,心里大概有个底:
- 1000 并发用户;
- 500 次/小时 AI 总结(DeepSeek 限制);
- 100 GB 视频存储。
扩展路径:
- 用户多 → 多实例 + 负载均衡;
- 视频多 → OSS + CDN;
- AI 调用多 → 多 DeepSeek 账号负载。
瓶颈其实不在服务器,在 DeepSeek 的限流,所以我给 AI 调用设计了排队和账号池。真到瓶颈那天,SQLite 换 PostgreSQL、文件挪 OSS,改动的都只是 service 层,不影响整体架构。
十六、与商业 SaaS 的差异
用户经常拿我们和平台自带的 AI 总结功能比。我把差异整理成表,沟通时直接甩给对方看:
| 维度 | 自建 | B 站 AI 总结 |
|---|---|---|
| 多平台 | ✅ | ❌(仅 B 站) |
| 思维导图 | ✅ | ❌ |
| 自定义 prompt | ✅ | ❌ |
| 部署灵活 | ✅ | ❌ |
| 维护成本 | 自负 | 零 |
结论很清楚:自建换来的是多平台覆盖和可定制性,代价是所有反爬、限流、升级问题都得自己扛。对愿意折腾的人来说这笔交易划算,对只想省事的人则相反。
十七、踩过的坑
写代码半年,坑比功能多,我把有代表性的都记下来了:
- yt-dlp 频繁更新:平台改了反爬就要更新 yt-dlp。每月拉新版。
- DeepSeek 限流:每秒 5 次。批量任务要排队。
- 视频文件大:4K 视频 5GB。限制上传/下载大小。
- SQLite 锁竞争:高并发写会”database is locked”。改用 WAL 模式。
- Stripe Webhook 重放:同一事件可能多次回调。事件 ID 幂等去重。
- JWT 续签:用户 token 过期要”静默续签”,不要强制跳登录。
- 跨域 Cookie:SameSite=None + Secure 配置错就 session 丢失。
这些坑每个都对应一次线上故障。比如 SQLite 的锁竞争,是我在批量导入脚本并发跑时第一次遇到,翻文档才知道要开 WAL;Stripe Webhook 重放则是支付记录出现了重复,排查半天才定位到回调幂等。提前把这些写成清单,后面接手的人能少踩一半雷。
十八、未来规划
产品还在持续迭代,我按优先级排了四件事:
- 支持多语言(i18n);
- 浏览器插件(直接 B 站/YouTube 页面调用);
- 团队协作(共享总结);
- 自定义 prompt 模板市场。
浏览器插件排第二,是因为它能显著降低使用门槛——用户在视频页面点一下就能触发下载总结,不用复制粘贴链接。模板市场则想激活社区,让用户自己贡献总结风格,这算是中期的一个发力点。
常见问题(FAQ)
Q1:为什么不用 Django?
Django 适合全栈(含 ORM/Admin/模板),本项目是纯 API + 静态前端,Django 太重。
Q2:DeepSeek 会不会被限流?
会。生产配 3-5 个账号做负载均衡。
Q3:视频能下载到本地吗?
能,但要注意版权。平台仅做”个人学习用途”。