第7章 7.2 RESTful API 设计
🎯 开场 3 分钟:为什么要学这个?
上一章我们搞懂了 HTTP 协议——浏览器和服务器是怎么「对话」的。你现在应该知道:请求是客户端发起的,响应是服务器返回的,header 里藏着各种元信息。
但有个问题:上一章我们写的代码,都是浏览器主动访问服务器固定页面。如果我想做一件事——让程序(比如 App、小程序、另一个服务器)能够「命令」我的服务器做事呢?
举个例子:
你打开一个待办清单 App,点一下「完成」按钮,App 马上显示 ✅ 已完成。这背后发生了什么?
答案是:你的 App 偷偷给服务器发了一条「命令」,告诉它「把第 42 号待办标记为已完成」。
这种「程序对程序」的命令传递方式,就是 API(应用程序接口)。而 RESTful API 是目前最流行的「命令格式约定」。
学完这一章,你就能写出这样的接口——让任何程序都能「指挥」你的 Python 程序做事。
🧱 基础 25 分钟:核心概念
什\n\n
\n\n
\n\n么是 RESTful API?——「餐厅点餐」的类比
想象你走进一家餐厅:
- 服务员(API):站在你和厨房之间,接收你的「命令」
- 菜单(REST 规则):你只能说菜单上有的词汇,不能乱点
- 厨房(服务器后端):真正干活的地方
RESTful 就是这样一套「点餐规则」:
- 你不能说「给我做个宫保鸡丁,要加花生,不要辣椒」,你只能说「点宫保鸡丁」
- 服务员听懂了,去厨房,厨房做好,端回来给你
说白了:RESTful 就是一套「怎么用 HTTP 命令来操作数据」的约定。
核心概念 1:URL 是「名词」,不是「动词」
❌ 旧式 API(像在说话):
GET /getUser?id=1 # "去拿用户"
POST /deleteUser?id=1 # "删除用户"
✅ RESTful API(像在点名):
GET /users/1 # "我要看 1 号用户"
DELETE /users/1 # "删掉 1 号用户"
POST /users # "新建一个用户"
PUT /users/1 # "更新 1 号用户"
生活类比:就像你去图书馆,你不说「帮我找书」,而是说「我要借《活着》」——你点的是东西本身,不是动作。
核心概念 2:HTTP 方法是「动作」
| 方法 | 含义 | 类比 |
|---|---|---|
| GET | 读取/查看 | 翻看菜单 |
| POST | 新建 | 点新菜 |
| PUT | 完整更新 | 换一道菜 |
| DELETE | 删除 | 退菜 |
核心概念 3:状态码是「回报」
服务器处理完你的命令,会返回一个「几号状态」:
| 状态码 | 含义 | 什么时候用 |
|---|---|---|
| 200 | 成功 | 正常返回数据 |
| 201 | 已创建 | 新建数据成功 |
| 400 | 请求错误 | 你发的命令有语法问题 |
| 404 | 找不到 | 要的东西不存在 |
| 500 | 服务器挂了 | 厨房着火了 |
生活类比:就像点餐后服务员喊「67 号!」——200 就是「您的菜好了」,400 就是「您点的这道菜我们没有」,500 就是「厨房今天停电了」。
核心概念 4:JSON 是「餐具」
RESTful API 用 JSON 格式来传递数据。JSON 就像一个标准化的「餐盘」——不管你点的是什么菜,都用同一种盘子装。
# Python 里的 JSON 就是字典格式
user = {
"name": "张三",
"age": 25,
"hobby": ["跑步", "读书"]
}
第一个可运行的 API——用 Flask 搭一个「留言板」
为什么要用 Flask? 它是 Python 里最轻量的 Web 框架,就像「自行车」——简单、好学、够用。
安装 Flask:
pip install flask
创建 app.py,写一个最简单的 API:
from flask import Flask, jsonify, request
app = Flask(__name__)
# 模拟一个数据库(就是一张「便利贴」)
messages = []
@app.route('/messages', methods=['GET'])
def get_messages():
"""获取所有留言"""
return jsonify(messages)
@app.route('/messages', methods=['POST'])
def add_message():
"""添加一条留言"""
data = request.get_json() # 接收 JSON 数据
messages.append(data)
return jsonify({"status": "ok", "message": "留言已添加"})
if __name__ == '__main__':
app.run(debug=True, port=5000)
运行:
python app.py
测试(另开一个终端):
curl -X GET http://localhost:5000/messages
curl -X POST http://localhost:5000/messages \
-H "Content-Type: application/json" \
-d '{"user": "小明", "content": "今天天气真好"}'
解释:这段代码做了三件事:
1. app = Flask(__name__) —— 创建一个 Flask 应用实例
2. @app.route('/messages', methods=['GET']) —— 定义「看留言」的入口
3. request.get_json() —— 读取用户发来的 JSON 数据
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):个人名片管理——CRUD 全家桶
需求:做一个「名片夹」API,能增删改查名片。
from flask import Flask, jsonify, request
app = Flask(__name__)
# 模拟数据库
cards = [
{"id": 1, "name": "张三", "phone": "13800138000"},
{"id": 2, "name": "李四", "phone": "13900139000"},
]
@app.route('/cards', methods=['GET'])
def get_cards():
"""获取所有名片"""
return jsonify(cards)
@app.route('/cards/<int:card_id>', methods=['GET'])
def get_card(card_id):
"""获取单张名片"""
card = next((c for c in cards if c["id"] == card_id), None)
if card:
return jsonify(card)
return jsonify({"error": "名片不存在"}), 404
@app.route('/cards', methods=['POST'])
def create_card():
"""新建名片"""
data = request.get_json()
new_id = max(c["id"] for c in cards) + 1
new_card = {"id": new_id, "name": data["name"], "phone": data["phone"]}
cards.append(new_card)
return jsonify(new_card), 201
@app.route('/cards/<int:card_id>', methods=['DELETE'])
def delete_card(card_id):
"""删除名片"""
global cards
cards = [c for c in cards if c["id"] != card_id]
return jsonify({"status": "deleted"})
if __name__ == '__main__':
app.run(debug=True, port=5000)
测试方法:
# 查看所有名片
curl http://localhost:5000/cards
# 查看 ID 为 1 的名片
curl http://localhost:5000/cards/1
# 新建一张名片
curl -X POST http://localhost:5000/cards \
-H "Content-Type: application/json" \
-d '{"name": "王五", "phone": "13700137000"}'
# 删除 ID 为 1 的名片
curl -X DELETE http://localhost:5000/cards/1
预期输出:返回对应的 JSON 数据。
一句话解释:这个项目展示了 RESTful 的核心——用不同的 HTTP 方法操作同一个资源 /cards。
项目 2(15 分钟):天气查询小工具——对接真实 API
需求:做一个天气查询工具,用户输入城市名,我们调用「天气 API」获取数据并返回。
这个项目用到了真实的外部 API,体验一下「程序对程序」调用是什么感觉。
首先去 心知天气 或 OpenWeatherMap 申请一个免费 API Key(就像去餐厅要会员卡)。
from flask import Flask, jsonify, request
import requests
app = Flask(__name__)
# 这里填你的 API Key(免费版够用)
API_KEY = "your_api_key_here"
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
@app.route('/weather', methods=['GET'])
def get_weather():
"""查询天气"""
city = request.args.get('city', 'Beijing') # 默认北京
# 调用外部 API
params = {
'q': city,
'appid': API_KEY,
'units': 'metric' # 摄氏温度
}
try:
response = requests.get(BASE_URL, params=params)
data = response.json()
if response.status_code == 200:
result = {
"city": data["name"],
"temperature": data["main"]["temp"],
"description": data["weather"][0]["description"],
"humidity": data["main"]["humidity"]
}
return jsonify(result)
else:
return jsonify({"error": data.get("message", "查询失败")}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, port=5000)
测试方法:
curl "http://localhost:5000/weather?city=Shanghai"
预期输出:
{
"city": "Shanghai",
"temperature": 22.5,
"description": "few clouds",
"humidity": 65
}
一句话解释:这个项目展示了 RESTful API 的真实用途——你的程序作为「中间人」,一边接收用户请求,一边去调用别人的 API。
项目 3(15 分钟):待办清单 API——数据持久化 + 基础鉴权
需求:做一个待办清单 API,支持创建、查看、完成、删除,还能把数据保存到文件里(重启不丢数据)。
from flask import Flask, jsonify, request
import json
import os
app = Flask(__name__)
DATA_FILE = 'todos.json'
# 加载数据(如果文件存在)
def load_todos():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return []
# 保存数据
def save_todos(todos):
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
todos = load_todos()
@app.route('/todos', methods=['GET'])
def get_todos():
"""获取所有待办"""
return jsonify(todos)
@app.route('/todos', methods=['POST'])
def create_todo():
"""创建待办"""
data = request.get_json()
new_id = max([t["id"] for t in todos], default=0) + 1
new_todo = {
"id": new_id,
"title": data["title"],
"done": False
}
todos.append(new_todo)
save_todos(todos)
return jsonify(new_todo), 201
@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
"""更新待办(标记完成)"""
data = request.get_json()
for todo in todos:
if todo["id"] == todo_id:
todo["done"] = data.get("done", todo["done"])
save_todos(todos)
return jsonify(todo)
return jsonify({"error": "待办不存在"}), 404
@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
"""删除待办"""
global todos
todos = [t for t in todos if t["id"] != todo_id]
save_todos(todos)
return jsonify({"status": "deleted"})
if __name__ == '__main__':
app.run(debug=True, port=5000)
测试方法:
# 创建待办
curl -X POST http://localhost:5000/todos \
-H "Content-Type: application/json" \
-d '{"title": "买牛奶"}'
# 查看所有
curl http://localhost:5000/todos
# 标记完成
curl -X PUT http://localhost:5000/todos/1 \
-H "Content-Type: application/json" \
-d '{"done": true}'
# 删除
curl -X DELETE http://localhost:5000/todos/1
预期输出:JSON 格式的待办数据,todos.json 文件里也会保存一份。
一句话解释:这个项目加入了文件持久化——数据不会因为程序重启而丢失,这才是真正能用的工具。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:POST 和 PUT 分不清
❌ 错误:用 POST 做更新
# 语义错误:POST 应该是「新建」,不是「更新」
@app.route('/users/<int:user_id>', methods=['POST'])
def update_user(user_id):
...
✅ 正确:更新用 PUT
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
...
坑 2:状态码乱返回
❌ 错误:删除成功返回 200
def delete_user(user_id):
user = find_user(user_id)
delete(user)
return jsonify({"status": "ok"}) # 语义不清
✅ 正确:按 REST 规范返回 204(无内容)
def delete_user(user_id):
delete(user)
return '', 204 # 明确表示「删除成功,没有返回内容」
坑 3:JSON 数据读取方式搞混
# 如果前端发来的是 JSON body,用这个:
data = request.get_json()
# 如果前端发来的是 URL 参数 ?name=张三,用这个:
name = request.args.get('name')
# 如果是表单提交(form-data),用这个:
name = request.form.get('name')
坑 4:不处理异常,服务器直接崩溃
❌ 危险代码:
@app.route('/users/<int:user_id>')
def get_user(user_id):
user = db.query(user_id) # 如果 user_id 无效,直接报错 500
return jsonify(user)
✅ 正确做法:
@app.route('/users/<int:user_id>')
def get_user(user_id):
user = db.query(user_id)
if not user:
return jsonify({"error": "用户不存在"}), 404
return jsonify(user)
坑 5:调试模式,生产环境忘记关
# ❌ 上线后 debug=True 很危险,别人能看到你的代码
app.run(debug=True, port=5000)
# ✅ 生产环境
app.run(debug=False, port=5000)
性能小贴士:使用 jsonify 而不是 str(dict)
# ❌ 慢,而且 Content-Type 不对
return str({"status": "ok"})
# ✅ 快,自动设置正确的 Content-Type
return jsonify({"status": "ok"})
调试技巧:加日志
import logging
# 设置日志
logging.basicConfig(level=logging.INFO)
@app.route('/todos', methods=['POST'])
def create_todo():
data = request.get_json()
logging.info(f"收到新建待办请求: {data}")
# ... 业务逻辑
logging.info(f"新建成功: {new_todo}")
return jsonify(new_todo), 201
✏️ 练习题 + 作业题
练习题(10 分钟)
练习 1(2 分钟):改个端口
- 输入:在项目 1 代码中,把端口从 5000 改成 8000
- 预期输出:curl 命令能访问 http://localhost:8000/cards
- 提示:只改 app.run() 那行的 port 参数
练习 2(2 分钟):加个「学历」字段
- 输入:在项目 1 的 /cards POST 端点,添加 "education": "大学" 字段
- 预期输出:新建名片时能保存学历
- 提示:在 create_card 函数里,加一个 data.get("education", "")
练习 3(3 分钟):换个外部 API
- 输入:用「心知天气」API(https://www.seniverse.com/)替代项目 2 的 OpenWeatherMap
- 预期输出:同样能查询天气,只是 URL 和参数不同
- 提示:看心知天气的文档,把 BASE_URL 和 params 改掉
练习 4(3 分钟):项目 2 + 3 串起来
- 输入:在项目 3 的待办清单里,每创建一个待办,就自动查询一下「创建地点」的天气
- 预期输出:创建待办时返回待办信息 + 当地天气
- 提示:参考项目 2 的天气查询函数,在 create_todo 里调用它
练习 5(挑战题,看截图分析)
- 题目:运行项目 3 时,访问 curl -X POST http://localhost:5000/todos -d '{"title": "test"}' 报错
- 输入:收到的错误信息是「415 Unsupported Media Type」
- 预期输出:改成正确的方式后能成功创建
- 提示:检查 header,Content-Type 很重要
作业题(30 分钟 - 2 小时)
作业:做一个「图书管理 RESTful API」
需求描述:做一个图书馆管理系统 API,支持对图书的 CRUD 操作。
功能点:
1. GET /books —— 查看所有图书
2. POST /books —— 添加新书(书名、作者、 ISBN)
3. GET /books/<id> —— 查看某本书
4. PUT /books/<id> —— 更新图书信息
5. DELETE /books/<id> —— 删除图书
加分项(选做):
1. 添加「借阅」功能:POST /books/<id>/borrow,标记某本书被借出
2. 数据保存到 JSON 文件
验收标准:
- 能跑起来
- 用 curl 测试每个接口都有正确返回
- 代码有注释
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学了 3 件事:
1. RESTful 是一种「用 URL 表示资源、用 HTTP 方法表示动作」的 API 设计规范
2. Flask 是 Python 里最简单好用的 Web 框架,10 行代码就能搭一个 API
3. 真实项目中,数据持久化和错误处理比功能本身更重要
延伸学习资源:
- Flask 官方文档 —— 权威且有中文
- 《Python 网络数据采集》—— 教你用 Python 抓网页数据
- RESTful API 设计最佳实践 —— 深入理解 REST
互动钩子:你在工作中需要对接别人的 API 吗?遇到过什么坑?评论区聊聊,老粉优先回复!
预告:下一章我们要解决一个很现实的问题——「如果 API 报错了怎么办?服务器崩了怎么办?」下一章「异常处理与日志」,教你写出「打不死」的程序。

评论(0)