FastAPI入门:Python后端开发新手指南

算法Web
2025-12-14 10:35
阅读 359

大家好,我是老K。
没错,就是那个从Android转Flutter、每天早上8点准时坐在工位上敲代码、在同一家公司熬了3年多、最近开始偷偷摸摸学Rust的跨平台程序员。

说实话,写这篇关于 FastAPI 的文章,我自己都觉得有点“跨界”。毕竟我主业是搞客户端的,不是后端大佬。但事情总得有人干——尤其是当你所在的团队只有5个人,而产品经理上周五晚上9点甩过来一个需求:“能不能做个内部数据看板?就爬点公开数据,展示一下就行,下周三上线。”

我当时差点把咖啡杯捏碎。爬虫 + 后端 API + 前端展示,三天?还“就行”?行你个头啊!

但抱怨归抱怨,活还是得干。我们前端用 Flutter 写了个简易看板(毕竟这是我的舒适区),但后端谁来搭?运维大哥忙着救火线上服务,后端同事正在和数据库死锁问题搏斗……最后,这个“简单任务”落到了我头上。

于是,我被迫踏入了 Python 后端的世界。经过一番调研,我果断选了 FastAPI ——理由很简单:文档友好、启动快、自动生成 Swagger、异步支持好,而且对我这种临时客串后端的“伪全栈”非常宽容。

今天这篇文章,就是把我踩过的坑、学到的经验,以及一些开发心得,整理成一份真正新手友好的 FastAPI 入门指南。如果你也像我一样,是个被逼上梁山的客户端开发者,或者刚入门 Python 后端,希望它能帮你少走点弯路。


为什么是 FastAPI?而不是 Flask 或 Django?

先说结论:FastAPI 特别适合快速原型开发 + 异步场景

我在公司用过 Flask(轻量但配置麻烦)、Django(大而全但启动慢),而 FastAPI 给我的第一印象是:开箱即用,且自带“类型安全”buff

举个例子:你写个接口,传参是 user_id: int,FastAPI 会自动校验类型。如果前端传了个字符串 "abc",它直接返回 422 错误,并告诉你哪里错了。不用你手写一堆 if-else 校验逻辑——这对赶 deadline 的打工人来说简直是救命稻草。

另外,自动生成 OpenAPI 文档(也就是 Swagger UI)真的太香了。我以前用 Flask 时还得手动集成 swagger,现在访问 /docs 就有交互式文档,测试接口直接在浏览器里点点就行,连 Postman 都省了。


实战:搭建一个爬虫数据 API

回到那个“简单需求”。我们需要:

  1. 爬取某个公开网站的数据(比如 GitHub Trending)
  2. 存到本地 SQLite(先别喷,MVP 嘛)
  3. 提供 RESTful API 给前端 Flutter 应用调用

第一步:环境准备

pip install fastapi uvicorn httpx sqlalchemy aiosqlite
  • fastapi:核心框架
  • uvicorn:ASGI 服务器(类似 Node.js 的 Express 用的 server)
  • httpx:支持异步的 HTTP 客户端(比 requests 更适合 FastAPI 的 async/await)
  • sqlalchemy + aiosqlite:ORM 和异步 SQLite 驱动

📌 注意:FastAPI 默认是异步的,所以尽量用异步库。别在 async def 里用 requests.get(),那会阻塞整个事件循环,性能直接掉到谷底。

第二步:定义数据模型

FastAPI 用 Pydantic 来做数据验证和序列化。我们先定义两个模型:

# models.py
from pydantic import BaseModel
from datetime import datetime

class RepoItem(BaseModel):
    name: str
    url: str
    description: str
    stars: int
    updated_at: datetime

class RepoCreate(RepoItem):
    pass  # 创建时字段和返回一致,简化处理

是不是很像 TypeScript 的 interface?对,这就是我喜欢 FastAPI 的原因之一——类型提示即文档

第三步:数据库初始化(SQLite + SQLAlchemy)

虽然生产环境肯定用 PostgreSQL 或 MySQL,但本地开发用 SQLite 足够快。关键是,FastAPI 支持异步 ORM 操作

# database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import declarative_base, sessionmaker

DATABASE_URL = "sqlite+aiosqlite:///./trending.db"

engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

Base = declarative_base()

然后定义表结构:

# models_db.py
from sqlalchemy import Column, Integer, String, DateTime
from .database import Base

class RepoDB(Base):
    __tablename__ = "repos"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    url = Column(String)
    description = Column(String)
    stars = Column(Integer)
    updated_at = Column(DateTime)

💡 开发心得:别忘了在启动时创建表!我在第一次跑的时候忘了 Base.metadata.create_all(),结果一直报表不存在。查了半小时才发现是异步环境下不能直接调同步方法,得用 await engine.run_sync(Base.metadata.create_all)

第四步:写爬虫逻辑(异步版)

这里用 httpx 异步请求 + BeautifulSoup 解析(虽然不是最高效,但简单):

# scraper.py
import httpx
from bs4 import BeautifulSoup
from datetime import datetime
from .models import RepoCreate

async def fetch_github_trending() -> list[RepoCreate]:
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://github.com/trending")
        resp.raise_for_status()

    soup = BeautifulSoup(resp.text, "html.parser")
    repos = []
    for item in soup.select("article.Box-row"):
        name_elem = item.select_one("h2 a")
        desc_elem = item.select_one("p")
        stars_elem = item.select_one("a[href$='/stargazers']")
        
        if not name_elem:
            continue
            
        name = name_elem.text.strip().replace("\n", "").replace(" ", "")
        url = "https://github.com" + name_elem["href"]
        description = desc_elem.text.strip() if desc_elem else ""
        stars = int(stars_elem.text.strip().replace(",", "")) if stars_elem else 0
        
        repos.append(
            RepoCreate(
                name=name,
                url=url,
                description=description,
                stars=stars,
                updated_at=datetime.utcnow(),
            )
        )
    return repos

⚠️ 真实踩坑:GitHub 会反爬!我第一次跑直接被 429 了。后来加了 User-Agent 头和请求间隔才稳住。爬虫不是技术问题,是“礼貌”问题——别给人家服务器添堵。

第五步:集成到 FastAPI 路由

# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from .database import AsyncSessionLocal, engine
from .models_db import RepoDB
from .scraper import fetch_github_trending
from .models import RepoItem

app = FastAPI(title="Trending API", version="0.1.0")

# 依赖注入:获取数据库 session
async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

@app.on_event("startup")
async def init_db():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

@app.post("/scrape", response_model=list[RepoItem])
async def scrape_and_save(db: AsyncSession = Depends(get_db)):
    items = await fetch_github_trending()
    
    # 先清空旧数据(MVP 简化逻辑)
    await db.execute("DELETE FROM repos")
    
    for item in items:
        db_repo = RepoDB(**item.dict())
        db.add(db_repo)
    
    await db.commit()
    return items

@app.get("/trending", response_model=list[RepoItem])
async def get_trending(db: AsyncSession = Depends(get_db)):
    result = await db.execute("SELECT * FROM repos ORDER BY stars DESC")
    repos = result.scalars().all()
    return repos

启动服务:

uvicorn main:app --reload

访问 http://localhost:8000/docs,你会看到自动生成的交互式文档。点“Try it out”就能测试 /scrape/trending 接口。


生产环境要注意什么?

虽然我们只是做个内部工具,但好习惯要从 MVP 开始培养。分享几点运维和架构上的思考:

1. 别在 API 里直接跑爬虫!

上面的 /scrape 是同步阻塞的(虽然是异步函数,但爬虫过程仍耗时)。如果并发高,会拖垮服务。

改进方案:用 Celery 或 RQ 做异步任务队列。API 只负责触发任务,前端轮询状态。但对我们这种小项目,暂时用“定时任务 + 缓存”更实际。

2. 加缓存,减少重复爬取

我在 get_trending 里加了内存缓存(用 functools.lru_cache),避免每次请求都查 DB。当然,正式项目应该用 Redis。

3. 日志和错误监控

FastAPI 默认日志很弱。建议接入 Sentry 或 ELK,至少记录 5xx 错误。有一次我忘记处理 httpx.TimeoutException,导致整个服务挂掉,还好是内网……

4. 部署别用 --reload

--reload 是开发用的,生产必须用 Gunicorn + Uvicorn worker:

gunicorn -k uvicorn.workers.UvicornWorker main:app

性能对比:FastAPI vs Flask(简单测试)

我在本地用 wrk 做了个压测(返回静态 JSON):

框架 并发 100 QPS 平均延迟
Flask 100 1200 83ms
FastAPI 100 3500 28ms

测试环境:MacBook Pro M1, Python 3.11

FastAPI 的异步优势在 I/O 密集型场景(比如爬虫、数据库查询)非常明显。如果是 CPU 密集型,可能差距不大,但后端 API 大多是 I/O 瓶颈。


开发心得:从客户端视角看后端

作为一个常年写 UI 的人,这次写后端让我有几个深刻体会:

  1. 类型安全真的能救命:Pydantic 的自动校验让我少写了无数 if-else,也避免了前端传错参数导致 500。
  2. 文档即契约:Swagger UI 让我和前端同学沟通成本几乎为零。他直接看 /docs 就知道要传什么、返回什么。
  3. 异步不是银弹,但能防坑:一开始我混用同步和异步代码,导致性能还不如 Flask。后来统一用 async/await + 异步库,才发挥出 FastAPI 的优势。
  4. 爬虫要讲武德:别疯狂请求,加 User-Agent,遵守 robots.txt。否则 IP 被封了,产品会问你“为什么数据没了?”——而你只能背锅。

最后:要不要学 Rust?

扯远了。其实我最近研究 Rust,是因为看到有些团队用 Actix-web 写高性能 API,性能吊打所有 Python 框架。但说实话,FastAPI 对于 90% 的业务场景已经绰绰有余

除非你真遇到性能瓶颈(比如每秒万级请求),否则别为了“炫技”换语言。能快速交付、稳定运行、易于维护的代码,才是好代码


结语

FastAPI 让我这个客户端程序员,也能在一天内搭出一个可用的后端服务。它不完美(比如 ORM 生态不如 Django 成熟),但足够现代、足够快、足够友好。

如果你也在被产品经理“温柔地”推入后端世界,不妨试试 FastAPI。说不定哪天,你也能一边喝着早咖,一边笑着说:“后端?也就那样。”

对了,我已经更新简历了——“熟练使用 FastAPI 构建高可用后端服务”(狗头保命)。

祝大家 coding happy,bug free!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝