Flask、Django、FastAPI 三个框架摆在一起,选型的第一仗就打在这里。当时团队里有人熟 Flask,有人推 Django,理由都是”生态成熟、上手快”。但我的业务有个硬约束:用户提交视频链接后,要同时处理下载、字幕转录、AI 总结,还要把进度用 SSE 流式推给前端,整条链路都是 IO 密集的异步场景。我先拿 Flask 写了个原型,并发一上来线程池就吃紧,又回头评估 Django,发现全家桶里我用得上的不到三成。最后把 FastAPI 定下来,跑了半年,这套选择被证明是对的。这篇把三个框架的对比、代码实测和踩过的坑都摊开讲。
一、Flask 够用吗
Flask 是 Python 里比较普及的轻量框架,我早期也认真评估过它,毕竟社区大、资料多、起服务快。但实际写需求时撞到两个硬伤:异步支持要靠第三方库硬凑,接口文档得手写。对一个小团队来说,这两件事占的工时比想象中大得多。
1. 异步支持弱
Flask 是 WSGI 同步框架,一个请求占一个线程。拿 AI 总结接口来说,DeepSeek 调用一次要等好几秒,期间这个线程就干等着:
# Flask:同步处理
@app.route('/api/summary')
def summary():
result = call_deepseek(...) # 阻塞 5s
return jsonify(result)
并发一多,线程池被占满,后来的请求只能排队,这是我在原型阶段就踩到的坑。换成 FastAPI 的 async 版本,等待期间事件循环可以继续处理别的请求,同样的硬件吞吐完全不同:
# FastAPI:原生 async
@app.post('/api/summary')
async def summary(req: SummaryRequest):
result = await deepseek_client.summarize(...) # 异步等待
return result
我后来用压测工具跑过一轮:同样的 AI 接口,FastAPI 的并发承载能力比 Flask 高出好几倍,这个差距在我们这种 IO 密集场景里是决定性的。
2. 没有自动 OpenAPI
Flask 的接口文档基本靠手写或第三方插件,前端同学每次联调都要追着问参数格式。FastAPI 这边是声明即所得:
# Flask:手写文档
@app.route('/api/summary', methods=['POST'])
def summary():
"""总结视频"""
pass # 文档靠注释或外部工具
# FastAPI:自动生成
@app.post('/api/summary', summary='总结视频',
response_model=SummaryResponse)
async def summary(req: SummaryRequest) -> SummaryResponse:
return await summary_service(req)
我们前端是原生 JS,没有后端给的类型定义,OpenAPI 自动生成后直接拉下来转成 TS 类型,联调时少吵了很多架。光是这一项,就值得我把框架换掉。
二、Django 太重
Django 我确实认真考虑过,它的 ORM、Admin、鉴权都是开箱即用,社区里求答案也方便。但对照我们的项目形态,问题很明显:
- 纯 API + 静态前端,不需要 Django Template;
- 用 SQLAlchemy 更灵活,Django ORM 与框架强绑定;
- Admin 后台不必要(运营量小);
- 部署更重(WSGI + 静态文件分离复杂)。
我把两边的组件摊开比了一下:
| 组件 | Django | FastAPI 选 |
|---|---|---|
| Web 框架 | Django | FastAPI |
| ORM | Django ORM | SQLAlchemy |
| Admin | Django Admin | 暂不需要 |
| Auth | Django Auth | 自研 + JWT |
| 文档 | DRF | 自动 OpenAPI |
结论很直接:Django 那些”电池”我用不上,反而要为了用它去迁就它的约定。项目小、迭代快的时候,框架强绑定是负担不是优势。而且当时 Django 的 async 支持还在演进,SSE 流式这种场景做起来远不如 FastAPI 顺手。
三、FastAPI 的核心优势
1. 异步原生
FastAPI 建立在 Starlette 的 asyncio 之上,写异步不用装任何插件。我的下载和总结接口里,经常要并发发起多个 IO 调用:
from fastapi import FastAPI
import asyncio
import httpx
app = FastAPI()
async def fetch(url: str) -> str:
async with httpx.AsyncClient() as client:
r = await client.get(url)
return r.text
@app.post('/api/summarize')
async def summarize(url: str):
# 并发执行多个异步任务
text, summary, mindmap = await asyncio.gather(
fetch(url),
ai.summarize(url),
ai.mindmap(url)
)
return {'text': text, 'summary': summary, 'mindmap': mindmap}
这段代码解决的是”多个独立 IO 并行”的问题。asyncio.gather 把三个异步任务同时丢进事件循环,总耗时只等于其中较慢的一路,而不是三者相加。我实际测过,一次完整总结从串行的十几秒压到并发后的四五秒,用户感知特别明显。
2. 类型安全
Pydantic 把参数校验从”手写 if 判断”变成了”声明字段约束”:
from pydantic import BaseModel, Field
class SummaryRequest(BaseModel):
url: str = Field(..., description="视频链接",
pattern=r"^https?://")
language: str = Field("zh", description="总结语言")
max_length: int = Field(500, ge=100, le=2000)
class SummaryResponse(BaseModel):
title: str
summary: str
mindmap: str
key_points: list[str]
duration: int
# 自动校验、自动文档
@app.post('/api/summarize', response_model=SummaryResponse)
async def summarize(req: SummaryRequest) -> SummaryResponse:
return await service.summarize(req)
Pydantic 自动:
- 校验请求参数;
- 序列化响应;
- 生成 OpenAPI Schema。
举一个具体收益:非法 URL、超长的 max_length,全都在进业务代码之前被拦下来,不用每个接口里写一堆 try/except 和 if 判断。字段约束和校验逻辑写在一处,改起来也只有一个地方。
3. 自动 OpenAPI 文档
部署完后,接口文档自动挂在 /docs:
https://example.com/docs
前端直接用:
npx openapi-typescript https://api.example.com/openapi.json -o types.ts
这一步把前后端联调的成本降下来了——后端加一个字段,前端跑一遍命令就拿到新的类型,不用手动同步。
4. 依赖注入
FastAPI 的 Depends 让我把”拿数据库连接、拿当前用户”这类公共逻辑抽出来,接口只关心自己的事:
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db() -> Session:
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post('/api/summary')
async def create_summary(
req: SummaryRequest,
db: Session = Depends(get_db),
user: User = Depends(get_current_user)
):
return await service.create(req, user, db)
依赖注入让代码解耦、易测。
写单测时这个设计帮了大忙:我可以直接注入一个 mock 的 db 或 user,不用真的起服务、连数据库。耦合少了,测试覆盖起来才不费劲。
5. 后台任务
视频处理这种耗时操作,接口应该”提交即返回”,后台慢慢跑:
from fastapi import BackgroundTasks
@app.post('/api/summary')
async def create_summary(
req: SummaryRequest,
bg: BackgroundTasks
):
job_id = await service.create_job(req)
bg.add_task(worker.run, job_id) # 后台执行
return {'job_id': job_id}
适合”提交任务立即返回 + 异步处理”。
这里要提醒一句:FastAPI 自带的 BackgroundTasks 只适合轻量任务,进程一重启任务就没了。我们的下载和总结任务最终换成了持久化的任务队列,这个坑放在第九节讲。
四、对比维度详细
把三个框架放同一张表里,各维度差距一目了然:
| 维度 | Flask | Django | FastAPI |
|---|---|---|---|
| 学习曲线 | 低 | 中 | 中 |
| 性能 | 中 | 中 | 高 |
| 异步支持 | 弱 | 弱 | 原生 |
| 类型提示 | 可选 | 弱 | 必须 |
| OpenAPI | 第三方 | 需 DRF | 内置 |
| 依赖注入 | 第三方 | 内置 | 内置 |
| 文档 | 第三方 | 第三方 | 自动 |
| 社区 | 大 | 巨大 | 中等 |
| 部署 | 简单 | 复杂 | 简单 |
| 模板 | Jinja2 | Django | 不用 |
这张表基本复现了我选型时的判断过程:FastAPI 在异步、类型、文档三个我们看重的维度全优,代价是社区比另外两家小,学习曲线略陡。
五、FastAPI 不适合的场景
选型不能只看优点,我也列了 FastAPI 不适合的场景,防止以后被套进错误项目:
| 场景 | 原因 |
|---|---|
| 纯 CMS | Django Admin 更高效 |
| 短平快 MVP | Flask 启动更快 |
| 大量同步阻塞 IO | 异步优势用不上 |
| 团队不熟 async | 强类型 async 是双刃剑 |
这张表不是给 FastAPI 泼冷水,而是提醒团队:如果哪天接了个内容管理系统,别惯性用 FastAPI,Django 的 Admin 能省一半开发量。工具选型永远是对着场景说话。
六、平台的具体应用
1. 视频下载接口
下载接口核心是把”获取视频元信息”做成一个轻量请求,方便前端先展示标题和封面:
@app.post('/api/video/info')
async def get_video_info(req: VideoInfoRequest):
"""获取视频元信息(标题/时长/缩略图)"""
info = await downloader.get_info(req.url)
return VideoInfoResponse(**info)
2. 总结接口(SSE 流式)
AI 总结是长耗时操作,直接同步返回会让用户对着空白页面等十几秒。我用 SSE 把结果逐个推出去:
from sse_starlette.sse import EventSourceResponse
@app.post('/api/summary/stream')
async def stream_summary(req: SummaryRequest):
"""流式返回总结(首字延迟 < 500ms)"""
async def event_generator():
async for chunk in ai_service.stream_summarize(req):
yield {'event': 'message', 'data': chunk}
return EventSourceResponse(event_generator())
这个接口让用户看到文字一行一行冒出来,首字延迟压到 500ms 以内。在 Flask 里做同样的事要挂第三方库,事件循环还得自己管理,FastAPI 这边就是一个生成器函数的事。
3. 思维导图
总结完再让模型生成 Markdown 结构的思维导图,前端用 markmap 渲染:
@app.post('/api/mindmap')
async def generate_mindmap(req: MindmapRequest):
"""根据总结生成 Markdown 思维导图"""
md = await ai_service.generate_mindmap(req.summary)
return MindmapResponse(markdown=md)
4. 鉴权
JWT 鉴权用 FastAPI 的 security 依赖封装,几行代码搞定:
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> User:
token = credentials.credentials
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user = await user_service.get(payload['user_id'])
if not user:
raise HTTPException(401, "Invalid token")
return user
七、平台选 FastAPI 的几个关键理由
1. AI 场景天然异步
我们这个产品的调用链全是网络 IO:yt-dlp 下载、DeepSeek 调用、Whisper 转录。异步在这里的收益可以量化:
# 同步实现:5 个视频 5 × 5s = 25s
for video in videos:
summary = await ai.summarize(video)
# 异步实现:5 个视频 5s(并发)
summaries = await asyncio.gather(*[ai.summarize(v) for v in videos])
同样的五个视频,同步串行要 25 秒,并发只要 5 秒。批处理任务里这个差距直接决定用户要不要等着。
2. SSE 流式支持好
AI 总结用户体验要好(首字延迟 < 500ms),SSE 是必选:
from sse_starlette.sse import EventSourceResponse
async def stream_summary(req):
async def event_gen():
async for chunk in ai.stream(req):
yield {'data': chunk}
return EventSourceResponse(event_gen())
Flask 要用第三方库实现,Django + DRF 麻烦。
3. 自动 OpenAPI 给前端
平台前端用原生 JS,需要类型安全。FastAPI 自动生成 OpenAPI,前端可以:
npx openapi-typescript https://api.example.com/openapi.json -o types.ts
零手写类型定义。
4. 与 Pydantic 集成
有些校验逻辑直接写在 schema 里,比如自动识别平台:
class VideoInfo(BaseModel):
url: HttpUrl
platform: Literal['youtube', 'bilibili', 'douyin', 'xiaohongshu']
@validator('platform', pre=True)
def auto_detect(cls, v, values):
if v != 'auto': return v
url = str(values.get('url', ''))
if 'youtube.com' in url: return 'youtube'
if 'bilibili.com' in url: return 'bilibili'
# ... 自动识别
return 'unknown'
校验逻辑直接写在 schema 里。
参数进来的时候平台已经判断好了,业务层不用再解析 URL,少一层啰嗦。
八、性能数据
上线前我用单机 4C8G 做了压测,记下几组参考值:
| 场景 | 性能 |
|---|---|
| 简单查询 | 5000 QPS |
| 视频信息获取 | 500 QPS |
| AI 总结 | 50 QPS(受 DeepSeek 限流) |
| 流式响应 | 200 并发 |
说明两点:简单查询的 QPS 高得离谱,但实际业务里瓶颈不在框架,而在下游的 DeepSeek 限流和网络带宽;流式响应 200 并发对当前用户量已经富余。真要扩容,加实例就能线性上去。
九、踩过的坑
跑了一段时间,我把踩过的坑按排查顺序整理成一个清单,遇到问题照着查:
- 看是不是 async 里混了同步阻塞调用,把整个事件循环卡死;
- 看依赖版本是不是被意外升级,Pydantic v1 和 v2 的写法完全不兼容;
- 看任务是不是丢在进程重启里,BackgroundTasks 不会持久化;
- 看 OpenAPI 文件是不是太大,拖慢了前端类型生成工具。
具体细节如下:
- async 传染:一个 async 函数调同步函数会阻塞事件循环。所有 IO 都用
await。 - Pydantic v2 升级:v1 → v2 breaking change,迁移成本高。锁定版本。
- 依赖注入作用域:Depends 嵌套层级深时性能损耗。扁平化设计。
- BackgroundTasks 不可靠:重启丢任务。要持久化任务队列(用 Redis/Celery)。
- OpenAPI 生成太详细:每个 endpoint 都有完整 schema,前端生成文件 1MB+。可以 hide 部分 endpoint。
第一个坑最隐蔽:某个接口里顺手调了个同步的 requests 调用,线上偶发性全线超时,排查了很久才发现是事件循环被阻塞。从那以后我定了一条规矩:代码审查时专门盯 async 函数里有没有同步阻塞调用。
十、迁移成本
选 FastAPI 还有个考虑是退路。如果未来框架要换,成本可控:
- 业务逻辑可抽到
services/目录,框架无关; - SQLAlchemy/Pydantic 都是通用库;
- 路由层是与框架紧密相关的,迁移成本可控。
我们的业务代码从一开始就集中在 services 层,路由只做薄薄一层转发。真要迁,重写路由和少量依赖注入,业务逻辑一行都不用动。
常见问题(FAQ)
Q1:FastAPI 性能真的比 Flask 高很多吗?
IO 密集场景高 3-5 倍。CPU 密集差距不大。
Q2:团队不熟 async 能上手吗?
能,FastAPI 文档好,async 学习成本可控。
Q3:FastAPI 适合高并发 WebSocket 吗?
适合。WebSocket 是 async 的典型场景。