FastAPI可通过Accept头或query参数实现单endpoint返回JSON/CVS/XLSX多格式,需匹配Content-Type、Content-Disposition及编码处理。

FastAPI 本身不直接内置 CSV 或 XLSX 响应支持,但可以通过 内容协商(Content Negotiation) + 手动构造响应 实现单个 endpoint 返回多种格式(JSON / CSV / XLSX),关键在于识别客户端期望的格式(通过 Accept 请求头或查询参数),然后动态生成对应内容并设置正确 Content-Type 和 Content-Disposition。
1. 基于 Accept 头自动选择格式
这是最符合 REST 规范的方式。客户端在请求头中声明想要的格式:
-
Accept: application/json→ 返回 JSON(FastAPI 默认行为) -
Accept: text/csv→ 返回 CSV -
Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet→ 返回 XLSX
在 FastAPI 中,用 Request 获取 accept 头,并按优先级匹配(支持 q= 权重):
from fastapi import Request, Response from fastapi.responses import JSONResponse, PlainTextResponse from starlette.responses import StreamingResponse import csv import io from openpyxl import Workbook@app.get("/data") async def get_data(request: Request): accept = request.headers.get("accept", "")
简单解析(生产环境建议用 python-mimeparse 或类似库)
mime_types = [m.split(";")[0].strip() for m in accept.split(",")] data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}] if "application/json" in mime_types: return JSONResponse(data) elif "text/csv" in mime_types: output = io.StringIO() writer = csv.DictWriter(output, fieldnames=["name", "age"]) writer.writeheader() writer.writerows(data) output.seek(0) return PlainTextResponse( output.getvalue(), media_type="text/csv", headers={"Content-Disposition": 'attachment; filename="data.csv"'} ) elif "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" in mime_types: wb = Workbook() ws = wb.active ws.append(["name", "age"]) for row in data: ws.append([row["name"], row["age"]]) stream = io.BytesIO() wb.save(stream) stream.seek(0) return StreamingResponse( stream, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="data.xlsx"'} ) else: return JSONResponse({"error": "Unsupported Accept type"}, status_code=406)2. 用 query 参数显式指定 format(更简单、更常用)
对前端或测试更友好,比如:
/data?format=csv。适合快速落地:
- 定义
format: str = Query("json", regex="^(json|csv|xlsx)$") - 统一校验输入,避免 MIME 解析复杂度
- 便于文档生成(Swagger UI 可交互选择)
from fastapi import Query@app.get("/data") def get_data(format: str = Query("json", regex="^(json|csv|xlsx)$")): data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
if format == "json": return data # FastAPI 自动序列化 elif format == "csv": output = io.StringIO() writer = csv.DictWriter(output, fieldnames=["name", "age"]) writer.writeheader() writer.writerows(data) return Response( output.getvalue(), media_type="text/csv", headers={"Content-Disposition": 'attachment; filename="data.csv"'} ) elif format == "xlsx": wb = Workbook() ws = wb.active ws.append(["name", "age"]) for d in data: ws.append([d["name"], d["age"]]) stream = io.BytesIO() wb.save(stream) stream.seek(0) return StreamingResponse( stream, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="data.xlsx"'} )3. 注意事项与优化建议
-
CSV 中文导出需加 BOM:Windows Excel 默认用 GBK 打开无 BOM 的 UTF-8 CSV 会乱码。可在
io.StringIO()前改用io.BytesIO()+.encode("utf-8-sig") -
XLSX 依赖 openpyxl:安装
pip install openpyxl;如需大文件流式写入,考虑xlsxwriter或pyxlsb -
性能敏感场景避免内存堆积:大数据量时,用
StreamingResponse+ 生成器逐行写入 CSV/XLSX,而非全量构建字符串/字节流 - 统一错误处理:格式不支持、数据为空、字段缺失等,建议封装成通用异常处理器返回标准错误响应
4. 可选:用依赖项封装格式逻辑
把格式选择和响应构造抽成依赖项,提升复用性:
from fastapi import Dependsdef get_export_response(data: list, format: str): if format == "json": return JSONResponse(data) elif format == "csv":
... 构造 CSV 响应
elif format == "xlsx": # ... 构造 XLSX 响应@app.get("/data") def get_data(format: str = Query("json", regex="^...$")): data = fetch_data() return get_export_response(data, format)
不复杂但容易忽略的是:格式切换不只是“返回不同内容”,更要配对正确的
Content-Type、Content-Disposition和字符编码处理。只要明确客户端怎么传意图、服务端怎么分发、每种格式怎么安全高效生成,一个 endpoint 支持多格式就非常自然。










