FastAPI 与 Flask、Django 的框架选型对比(视频下载总结器实践)

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 并发对当前用户量已经富余。真要扩容,加实例就能线性上去。

九、踩过的坑

跑了一段时间,我把踩过的坑按排查顺序整理成一个清单,遇到问题照着查:

  1. 看是不是 async 里混了同步阻塞调用,把整个事件循环卡死;
  2. 看依赖版本是不是被意外升级,Pydantic v1 和 v2 的写法完全不兼容;
  3. 看任务是不是丢在进程重启里,BackgroundTasks 不会持久化;
  4. 看 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 的典型场景。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部