🐍

第3课:自己的 API

用 Python Flask 搭建后端服务

2 课时 Python 实战 需要 pip

← → 键切换 · 点击底部导航跳转

1 / 24

🎯 这节课做什么

🐍 自己写一个后端 API,浏览器直接访问
之前调用别人的 API,现在自己写一个!
🐍

Flask 起步

Python 最简单 Web 框架

🔌

写接口

3 个 API 接口,返回 JSON

📥

接收参数

?tag=坚持 按主题筛选

🔗

前端联调

写 HTML 调用自己的后端

2 / 24

📁 这节课两个文件

🐍

app.py(73行)

Flask 后端
3 个 API 接口

🌐

test.html(103行)

前端测试页面
调用后端的 API

先写后端,再写前端测试。两个文件都放在 lesson-03-backend/ 目录下

3 / 24

⬇️ 先把依赖装上

pip install flask flask-cors
📦

flask

Web 框架,处理 HTTP 请求和路由

🔗

flask-cors

允许前端跨域访问(后面解释)

注意 在终端里运行,确保看到 "Successfully installed"

4 / 24

🐍 app.py ① — 导入模块(第1-7行)

"""第3课:自己的 API — Flask 后端"""

from flask import Flask, jsonify, request
from flask_cors import CORS
import random
1-3"""..."""文档字符串,说明这个文件的作用
5from flask import Flask, jsonify, request⚠️ 从 Flask 导入3样东西:
Flask = 框架本身
jsonify = 把字典转成 JSON 返回
request = 拿到浏览器发送的请求数据
6from flask_cors import CORS⚠️ 导入跨域支持
不同端口号之间的通信需要它
7import randomPython 内置的随机库
random.choice() 随机选一条金句
5 / 24

🐍 app.py ② — 初始化 Flask(第9-10行)

app = Flask(__name__)
CORS(app)  # 允许前端跨域访问
💡

app = Flask(__name__)

创建 Flask 应用
__name__ 是 Python 的特殊变量
= 告诉 Flask 当前文件的名字
Flask 用它来找到资源路径

💡

CORS(app)

CORS = 跨域资源共享
前端的地址是 localhost 后打开 HTML
后端的地址是 localhost:5000
不同端口 = 不同"域"
CORS 允许它们互相通信

6 / 24

🐍 app.py ③ — 金句数据(第12-26行)

QUOTES = [
  {"quote": "不积跬步,无以至千里", "author": "荀子", "tag": "坚持"},
  {"quote": "学而不思则罔...", "author": "孔子", "tag": "学习"},
  ... 共12条金句
]
13-26QUOTES = [ ... ]⚠️ 一个大写的变量名 = Python 约定
全大写表示"这个数据是常量,不要改"
每条数据:{ "quote", "author", "tag" }⚠️ 比之前多了一个 tag 字段!
用来按主题筛选(坚持/学习/创新/时间/人生)
📝

12 条金句 × 5 个标签

坚持4条 · 学习3条 · 创新2条 · 时间1条 · 人生2条

💡

为什么加 tag 字段?

上节课的 API 只能随机
这节课我们可以按主题筛选
这和按 Arduino 传感器类型筛选一个道理

7 / 24

🐍 app.py ④ — 首页路由(第28-31行)

@app.route('/')
def home():
    return "🐍 我的 API 正在运行!<br>试试访问 <a href='/api/quote'>/api/quote</a>"
29@app.route('/')⚠️ 装饰器 = 告诉 Flask:当用户访问 "/" 这个路径时
执行下面这个函数
30def home():函数名可以随便起,但要有意义
31return "..."返回字符串 → 浏览器直接显示这段文字
里面含 <a> 链接,点它跳到 /api/quote
💡

@app.route() 怎么工作?

@app.route('/') = "当用户访问 http://localhost:5000/"
@app.route('/api/quote') = "当用户访问 http://localhost:5000/api/quote"

类似 Arduino 的中断:
"当引脚 2 收到信号时 → 执行这个函数"

8 / 24

🐍 app.py ⑤ — 获取金句(第33-47行)

@app.route('/api/quote')
def get_quote():
    """GET /api/quote?tag=坚持"""
    tag = request.args.get('tag', '')  # 获取URL参数

    if tag:  # 如果有指定主题
        filtered = [q for q in QUOTES if q['tag'] == tag]
        if not filtered:
            return jsonify({"error": f"没有找到标签为「{tag}」的金句"}), 404
        return jsonify(random.choice(filtered))
    else:  # 没有指定主题 → 随机
        return jsonify(random.choice(QUOTES))
37request.args.get('tag', '')⚠️ 从 URL 获取 ?tag=xxx 参数
第二个参数 '' 是默认值(没传就空字符串)
41[q for q in QUOTES if q['tag'] == tag]⚠️ 列表推导式
从 QUOTES 中筛选出 tag 匹配的条目
43return jsonify(...), 404返回错误信息 + HTTP 状态码 404(没找到)
44return jsonify(random.choice(filtered))从筛选结果中随机选一条返回
47return jsonify(random.choice(QUOTES))没有 tag 参数 → 从全部中随机选
9 / 24

💡 列表推导式详解(第41行)

// 普通写法
filtered = []
for q in QUOTES:
    if q['tag'] == tag:
        filtered.append(q)
// 列表推导式(一行搞定)
filtered = [q for q in QUOTES if q['tag'] == tag]
🔍

拆解:

[q = 要放进新列表的东西
for q in QUOTES = 循环每一条
if q['tag'] == tag = 只选 tag 匹配的

📋

结果示例:

如果 tag="坚持"
filtered = [
{荀子, 坚持}, {老子, 坚持}, {周易, 坚持}]
共3条

10 / 24

🐍 app.py ⑥ — 接口2+3(第49-60行)

# 接口2:获取所有金句
@app.route('/api/quotes')
def get_all_quotes():
    return jsonify({"quotes": QUOTES, "total": len(QUOTES)})

# 接口3:获取所有标签
@app.route('/api/tags')
def get_tags():
    tags = list(set(q['tag'] for q in QUOTES))
    return jsonify({"tags": sorted(tags)})
53jsonify({"quotes": QUOTES, "total": len(QUOTES)})返回全部金句 + 总条数
59set(q['tag'] for q in QUOTES)set = 集合,自动去重
5个标签 → {"坚持", "学习", "创新", "时间", "人生"}
60sorted(tags)按字母排序 → 前端下拉框按顺序显示
💡

为什么需要 3 个接口?

接口1 = 获取单条金句(主要功能)
接口2 = 获取全部数据(浏览/调试)
接口3 = 获取标签列表(前端下拉框用)

11 / 24

🐍 app.py ⑦ — 启动(第62-73行)

if __name__ == '__main__':
    print("=" * 40)
    print(" 🐍 自己的 API 已启动!")
    print("=" * 40)
    print(f" 📍 http://localhost:5000")
    print(f" 📍 http://localhost:5000/api/quote")
    print(f" 📍 http://localhost:5000/api/quote?tag=坚持")
    ...
    app.run(host='0.0.0.0', port=5000, debug=True)
63if __name__ == '__main__':⚠️ Python 特殊写法
只有直接运行这个文件时才会执行
如果被别的文件导入,不会运行
64-71print(...)打印启动信息,告诉学生有哪些地址可以访问
73app.run(host='0.0.0.0', port=5000, debug=True)⚠️ 启动服务器
host='0.0.0.0' = 允许局域网访问
port=5000 = 端口号
debug=True = 修改代码后自动重启
12 / 24

▶️ 启动后端的方法

cd lesson-03-backend
python app.py

终端显示:

========================================
🐍 自己的 API 已启动!
========================================
📍 http://localhost:5000
📍 http://localhost:5000/api/quote
========================================
* Running on http://0.0.0.0:5000
🔴

常见错误:

ModuleNotFoundError: No module named 'flask'
→ 没装依赖,运行 pip install flask flask-cors

Address already in use
→ 端口被占用了,关掉其他 Python 进程

13 / 24

🔍 浏览器测试 API

打开浏览器,在地址栏输入:

http://localhost:5000/

→ 显示文字

http://localhost:5000/api/quote

→ 随机一条 JSON

http://localhost:5000/api/quote?tag=坚持

→ 筛选"坚持"的 JSON

http://localhost:5000/api/tags

→ 全部标签

14 / 24

🌐 test.html — 前端测试页面

简单的前端,调用自己的后端 API

<!-- 下拉框选主题 -->
<select id="tagSelect">
  <option value="">🎲 随机一句</option>
  <option value="坚持">💪 坚持</option>
  ...
</select>

<!-- 按钮 -->
<button id="fetchBtn">获取金句</button>

<!-- 展示 -->
<div id="quoteText">点击按钮...</div>
<div id="quoteAuthor"></div>
🆕

select 下拉框

让用户选择主题
JS 读 select.value 拿到选中的值

💡

和之前的前端区别

之前调用公网 API
现在调用 localhost:5000 自己的 API
代码几乎一样!

15 / 24

⚡ test.html JS — 调用自己的 API

async function fetchQuote() {
  const tag = tagSelect.value;  // 拿到选中的主题
  // 构造 URL
  const url = tag
    ? `http://localhost:5000/api/quote?tag=${tag}`
    : 'http://localhost:5000/api/quote';

  try {
    const response = await fetch(url);
    const data = await response.json();
    quoteText.textContent = data.quote;
    quoteAuthor.textContent = '—— ' + data.author;
  } catch (err) { ... }
}
73-75`http://localhost:5000/...${tag}`⚠️ 模板字符串(反引号)
${tag} 会被替换成选中的主题值
74?tag=${tag}拼出 URL:?tag=坚持
88data.quote注意字段名变了!
第2课用 data.content(公网API)
这节课用 data.quote(自己API)
16 / 24

💡 模板字符串(反引号 ``)

// ❌ 老方法(拼接字符串)
'http://...quote?tag=' + tag
// ✅ 模板字符串 ✨
`http://...quote?tag=${tag}`
💡

反引号 `` 的特点

• 使用 反引号 ` 而不是普通引号 ' 或 "
• 用 ${变量名} 插入变量
• 可以换行(不用写 + 号)

例子:
tag = "坚持"
\`...?tag=${tag}\` → "...?tag=坚持"

17 / 24

⚡ test.html JS 完整流程(第62-101行)

第62-67行

获取5个元素:quoteText, quoteAuthor, fetchBtn, tagSelect, errorMsg

第69-98行

async function fetchQuote()
→ 拿 tag → 构造 URL → fetch → 展示

第100行

fetchBtn.addEventListener('click', fetchQuote) — 和之前一样的绑定方式

📋

与第2课对比

相同点:
async/await · fetch · try/catch · finally · addEventListener

不同点:
URL 从公网 → localhost
新增 select 下拉框
字段名 data.quote vs data.content

18 / 24

🛠️ 动手步骤(60分钟)

先写后端,再写前端

1️⃣ 装依赖(5min)

pip install flask flask-cors

2️⃣ 写 app.py(20min)

导入 → 初始化 → 金句数据 → 3个路由 → 启动

3️⃣ 启动后端(5min)

python app.py → 查看终端输出

4️⃣ 浏览器测试(5min)

访问 /api/quote → 看到 JSON 数据

5️⃣ 写 test.html(15min)

前端页面 → select + fetch → 调用后端

6️⃣ 联调测试(10min)

选主题 → 获取金句 → 成功!

19 / 24

⚠️ 常见问题

后端没启动就点按钮

浏览器报错:net::ERR_CONNECTION_REFUSED
→ 先确认 python app.py 在运行

字段名写错了

data.quote 写成 data.content
→ 看调试面板里字段叫什么

忘记拼端口

localhost:5000 写成 localhost
→ Flask 默认 5000 端口

CORS 错误

Access-Control-Allow-Origin
→ 检查 app.py 里有 CORS(app)

20 / 24

📊 完整数据流程

🌐 test.html
选主题
→ GET →
🐍 Flask
http://localhost:5000
← JSON ←
🌐 test.html
展示金句
🔗

和上节课的对比

第2课:浏览器 → 公网 API (api.quotable.io) → 展示
第3课:浏览器 → 自己的 API (localhost:5000) → 展示

流程完全一样!只是 API 地址从公网换成了自己的电脑

21 / 24

📋 三个接口对比

路径方法参数返回
/api/quoteGET?tag=可选随机一条金句
/api/quotesGET全部12条
/api/tagsGET5个标签列表
💡

API 设计的思路

一个 API = 一个功能
把不同功能拆成不同的 URL 路径
/api/quote = 拿金句
/api/quotes = 拿所有金句
/api/tags = 拿标签

以后加功能:加新的路由就行,不影响旧的

22 / 24

🚀 还可以做什么

📝

加更多金句

在 QUOTES 列表里加条目,加新 tag

🔢

加计数接口

/api/stats → 返回各主题的金句数量

🔀

POST 接口

不只用 GET,试试 POST 传数据

🎨

美化前端

把 test.html 改成漂亮卡片

23 / 24
🎉

这节课学会了

Flask 创建 Web 应用

@app.route() 路由装饰器

jsonify() 返回 JSON

request.args.get() 接收 URL 参数

列表推导式 筛选数据

if __name__ == '__main__' 启动入口

CORS() 跨域支持

✅ 前端模板字符串 \`...${}\`

3 个 API 接口的设计

🎊 你有自己的 API 了!朋友们可以访问你的电脑 ✨

下节课预告 🤖 接入 AI,让金句实时生成!

24 / 24