API 从入门到精通:一个初学者的完整指南
🔌 API 从入门到精通:一个初学者的完整指南
作者:taohuaracing
适用对象:零基础但想彻底搞懂 API 的初学者
阅读时间:约 45 分钟(分几天看也行,建议动手敲代码)
📖 目录
- 序章:你不是一个人在学 API
- 第一章:API 到底是什么鬼?
- 第二章:前后端 API 的工作原理
- 第三章:你需要知道的 HTTP 基础
- 第四章:手把手调你的第一个 API
- 第五章:常见 API 类型及实战
- 第六章:API 调试利器 —— Postman / Hoppscotch
- 第七章:API 进阶 —— 鉴权、限流、分页、错误处理
- 第八章:实际项目中的 API 设计思路
- 第九章:常见坑与避坑指南
- 第十章:资源与出处
1. 序章:你不是一个人在学 API
先看几个真实的论坛提问:
“看了两个月教程还是不懂 API 是啥,感觉前端传个 JSON 给后端就叫 API?那我直接操作数据库不就行了?” —— 知乎提问
“面试被问 RESTful API,我说就是 CRUD,然后被挂了” —— V2EX 帖子
“用 Python 调 OpenAI 的 API,结果账单$200,代码就三行” —— Reddit r/learnprogramming
这些问题的共同点是什么?它们都说明一件事:很多人会用 API,但不懂 API。 而这本书就是要让你既会用,也懂——更重要的是,懂到能举一反三。
2. 第一章:API 到底是什么鬼?
2.1 最好的比喻:餐厅
想象你去一家餐厅吃饭:
| 餐厅要素 | 对应到 API |
|---|---|
| 厨房(后厨) | 后端服务器 —— 真正处理数据的地方 |
| 菜单 | API 文档 —— 告诉你有什么菜(接口) |
| 服务员 | API 接口 —— 帮你传递"点什么菜"的请求 |
| 你点的菜 | 请求参数 |
| 服务员端上来的菜 | 响应数据 |
| 你的口味要求(不要辣、少盐) | 请求头 / 查询参数 |
| 你的会员卡 | API Key(鉴权令牌) |
你不可能直接冲进厨房自己做菜(==直接操作数据库),你需要通过**服务员(API)**来沟通。
API 的定义:Application Programming Interface(应用程序编程接口),简单说就是两个软件之间约定好的沟通方式。
2.2 更技术一点的说法
客户端 服务器
(浏览器/App/脚本) (后端代码)
| |
| GET /api/users |
| Authorization: xxx |
| ──────────────────────→ |
| | → 查数据库
| | → 组装数据
| { "users": [...] } |
| ←────────────────────── |
| |
API 就是这条虚线箭头的"通道协议":你按照约定的格式发请求,服务器按约定给你回数据。管你前端是什么框架,后端用什么语言,只要 API 约定好了,两头各自实现就行。
2.3 为什么需要 API?
- 解耦:前端可以和后端分开开发、分开部署
- 复用:同一个 API 可以被 Web、iOS、Android 同时调用
- 安全:不暴露数据库结构,只暴露你需要的数据
- 标准化:人人都按同一个规矩来,不会乱
2.4 前后端 API vs 第三方 API vs AI API
| 类型 | 例子 | 谁提供 | 你干啥 |
|---|---|---|---|
| 前端↔后端 API | 自家网站的登录接口 | 你团队的后端 | 写前端调自家后端 |
| 应用 API | GitHub API、微信支付 API | 第三方公司 | 在代码里调用 |
| AI API | OpenAI API、Claude API | AI 厂商 | 给 prompt 拿到回复 |
| Search API | Google Search API、SerpAPI | 搜索引擎 | 给关键词拿到搜索结果 |
这四种 API 本质都一样:发个请求 → 收个响应。区别只是参数格式不同、返回数据不同、价格不同。
3. 第二章:前后端 API 的工作原理
3.1 一个典型的前后端交互
假设你要做一个博客系统,用户在前端点击"获取文章列表":
3.2 前后端 API 的生命周期
1. 定义阶段(后端主导)
后端定义好接口文档:
GET /api/articles
参数: page (页码, 默认1), limit (每页条数, 默认20)
返回:
{
"code": 0, # 0 = 成功
"data": {
"articles": [
{ "id": 1, "title": "标题", "author": "张三" }
],
"total": 100,
"page": 1,
"hasMore": true
},
"message": "success"
}
2. 开发阶段(前后端并行)
- 前端:写界面,用 mock 数据模拟 API 返回
- 后端:写真实的 API 逻辑,调数据库
3. 联调阶段
前端把 mock 地址换成真实的后端地址,两边对接调试。
4. 上线
前后端分别部署,前端代码中的 API 地址指向生产环境的服务器。
3.3 RESTful API —— 业界主流风格
核心思想:用 URL 表示"资源",用 HTTP 方法表示"操作"
面对一个资源 User(用户):
| 操作 | HTTP 方法 | URL | 说人话 |
|---|---|---|---|
| 获取所有用户 | GET |
/api/users |
“把用户列表给我” |
| 获取单个用户 | GET |
/api/users/42 |
“把 42 号用户的信息给我” |
| 创建用户 | POST |
/api/users |
“帮我创建一个新用户,信息在 body 里” |
| 更新用户 | PUT |
/api/users/42 |
“把 42 号用户整个替换成这个” |
| 部分更新 | PATCH |
/api/users/42 |
“只改 42 号用户的邮箱” |
| 删除用户 | DELETE |
/api/users/42 |
“把 42 号用户删了” |
📌 V2EX 的精华帖观点:
“很多人天天用 REST 却说不清 REST。其实记住一句话——REST 是把你的业务抽象成一组资源,然后用 HTTP 方法去对这些资源做 CRUD。做到这点,你的 API 就已经比 80% 的人写的好了。”
来源:V2EX › 程序员 › 《如何设计一个让人舒服的 API》
3.4 GraphQL —— 另一种选择
GraphQL 是 Facebook 搞的,核心思想是 “前端想要什么字段就传什么字段”。
对比一下:
REST 方式:
// GET /api/users/42 → 返回
{
"id": 42,
"name": "张三",
"email": "zhang@example.com",
"phone": "138xxxx",
"address": "北京市...",
"createdAt": "2024-01-01",
"avatar": "..."
}
// 前端只需要 name 和 email,但也必须接收全部字段
GraphQL 方式:
query {
user(id: 42) {
name
email
}
}
// 返回就只包含这两个字段
{
"data": {
"user": {
"name": "张三",
"email": "zhang@example.com"
}
}
}
什么时候选 REST,什么时候选 GraphQL?
| 场景 | 推荐 |
|---|---|
| 简单 CRUD 系统 | REST |
| 复杂的多端(Web + App + 小程序) | GraphQL |
| 团队都熟悉 REST | REST |
| 字段级精准控制很重要 | GraphQL |
| 需要缓存、CDN | REST(天然支持 HTTP 缓存) |
4. 第三章:你需要知道的 HTTP 基础
4.1 URL 结构拆解
一个典型的 API URL:
https://api.github.com /users/octocat/repos?page=1&per_page=10
├────────┬────────┤ ├────┬─────┤ ├──────────┬──────────┤
│ Base URL │ │ 路径 │ │ 查询参数 │
│ (服务器地址) │ │(资源路径)│ │ (过滤/分页) │
4.2 HTTP 请求方法详解
GET → 获取资源(只读,不会改数据)
POST → 创建新资源(注册、发帖、下单)
PUT → 完整替换资源(整个更新)
PATCH → 部分更新资源(只改某个字段)
DELETE → 删除资源
4.3 HTTP 状态码(面试常考)
2xx —— 成功
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | 获取数据 |
| 201 Created | 创建成功 | 注册成功、发帖成功 |
| 204 No Content | 成功但无返回 | 删除成功 |
3xx —— 重定向
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 301 Moved Permanently | 永久重定向 | 域名变更 |
| 302 Found | 临时重定向 | 未登录跳登录页 |
| 304 Not Modified | 资源未变(缓存) | 浏览器缓存校验 |
4xx —— 客户端错误(你的锅)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 Bad Request | 请求参数不对 | 传入非法数据 |
| 401 Unauthorized | 没登录 / 没认证 | 没传 token |
| 403 Forbidden | 没权限 | 普通用户想删管理员 |
| 404 Not Found | 资源不存在 | 查一个不存在的用户 |
| 405 Method Not Allowed | 方法不支持 | 对只读接口发了 DELETE |
| 429 Too Many Requests | 请求太频繁 | 超出调用频率限制 |
5xx —— 服务器错误(后端的锅)
| 状态码 | 含义 |
|---|---|
| 500 Internal Server Error | 服务器爆炸了 |
| 502 Bad Gateway | 网关挂了(常见于反向代理/Nginx) |
| 503 Service Unavailable | 服务暂停(如过载、维护中) |
| 504 Gateway Timeout | 请求超时未响应 |
📌 来自掘金的一个高赞帖:
“做 API 调试最蠢的错误就是把 403 当 401 处理。你传了 token 但被拒绝了,是 403,往往不是没登录而是权限不够。搞清楚这俩的区别,能省你半天 debug 时间。”
来源:掘金 › 《HTTP 状态码使用场景总结》
4.4 请求头(Headers)常见字段
# 告诉服务器请求体的格式
Content-Type: application/json
# 身份认证(API Key 或 Token)
Authorization: Bearer sk-xxxxxxxxxxxxxxxx
# 客户端信息(很多 API 必填)
User-Agent: MyApp/1.0
# 接受什么格式的响应
Accept: application/json
# 缓存相关
If-None-Match: "xxxxx" # 配合 ETag 做缓存校验
Cache-Control: no-cache # 不要缓存
4.5 请求体(Body)
GET 请求没有 body,参数放 URL 或 query string。
POST/PUT/PATCH 通常有 body,最常用的是 JSON:
{
"username": "zhangsan",
"email": "zhang@example.com",
"password": "mypassword123"
}
5. 第四章:手把手调你的第一个 API
5.1 用浏览器调(最简单)
打开浏览器,网址栏输入:
https://api.github.com/users/octocat
你会看到一堆 JSON 数据。恭喜,你刚刚调了第一个 API!
浏览器本质上就是发了一个 GET 请求。但浏览器只能做 GET(在地址栏里),更复杂的请求需要用工具或代码。
5.2 用 curl 调(命令行)
什么是 curl? 一个命令行 HTTP 客户端,几乎每个程序员都要会。
# 最基本的 GET 请求
curl https://api.github.com/users/octocat
# 带请求头的 GET(加 User-Agent)
curl -H "User-Agent: MyApp" https://api.github.com/users/octocat
# POST 请求 + JSON body
curl -X POST https://jsonplaceholder.typicode.com/posts \
-H "Content-Type: application/json" \
-d '{"title": "我的文章", "body": "内容..."}'
# 带 API Key 的请求(OpenAI)
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-your-key-here" \
-d '{
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "你好!"}]
}'
来个更详细的:用 curl 调天气 API(免费)
# 先用这个免费 API 试试 curl "https://wttr.in/Shanghai?format=3" # 输出: Shanghai: ☀️ +28°C
📌 来自 Stack Overflow 的经典讨论:
“I can’t curl (port 80) 这个问题在 Stack Overflow 上有超过 15 万的浏览量。提问者用 curl 调本地服务器,忘了加端口号。”
结论:curl 默认去 80 端口,如果服务器在 8080 必须显式写:curl http://localhost:8080/api来源:Stack Overflow ›
curl: (7) Failed to connect to localhost port 80: Connection refused
5.3 用 JavaScript 调(fetch)
这是前端调 API 的标准方式:
// 最简单的 GET
const response = await fetch('https://api.github.com/users/octocat');
const data = await response.json();
console.log(data.login); // "octocat"
// POST - 创建数据
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '我的文章',
body: '文章内容在这里...',
userId: 1,
}),
});
const data = await response.json();
console.log('新创建的 ID:', data.id);
// 完整错误处理
async function fetchUser(userId) {
try {
const res = await fetch(`https://api.example.com/users/${userId}`);
if (!res.ok) {
// res.ok 在 200-299 时是 true
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const user = await res.json();
return user;
} catch (err) {
console.error('请求失败:', err.message);
throw err; // 继续向上抛
}
}
5.4 用 Python 调(requests)
Python 的 requests 库是最好用的 HTTP 库:
# 先安装
pip install requests
import requests
# GET 请求
response = requests.get('https://api.github.com/users/octocat')
data = response.json()
print(data['login']) # octocat
# POST - 创建数据
response = requests.post(
'https://jsonplaceholder.typicode.com/posts',
json={
'title': '我的文章',
'body': '文章内容...',
'userId': 1,
},
headers={'Content-Type': 'application/json'}
)
print(response.status_code) # 201
print(response.json()['id']) # 新 ID
# 带查询参数的 GET
params = {
'q': 'python',
'sort': 'stars',
'per_page': 5,
}
response = requests.get('https://api.github.com/search/repositories', params=params)
print(response.json()['items'][0]['full_name'])
# 带 API Key 的请求
headers = {'Authorization': 'Bearer YOUR_API_KEY'}
response = requests.get('https://api.openai.com/v1/models', headers=headers)
# 完整错误处理
def safe_api_call(url, headers=None, timeout=10):
try:
resp = requests.get(url, headers=headers, timeout=timeout)
resp.raise_for_status() # 4xx/5xx 时抛出异常
return resp.json()
except requests.exceptions.Timeout:
print(f"请求超时: {url}")
return None
except requests.exceptions.HTTPError as e:
print(f"HTTP 错误: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"请求异常: {e}")
return None
5.5 小练习:把以上代码都跑一遍
建议现在打开你的终端(Mac/Linux 用 bash,Windows 用 PowerShell 或 WSL 或 Git Bash),执行上面的 curl 和 Python 代码。不动手的话看完就忘。
6. 第五章:常见 API 类型及实战
6.1 应用 API —— GitHub API
场景:获取你的 GitHub 仓库列表
文档:https://docs.github.com/en/rest
# 公开 API(不需要 token)
curl -H "Accept: application/vnd.github+json" \
https://api.github.com/users/octocat/repos
import requests
# 获取仓库信息
resp = requests.get(
'https://api.github.com/repos/octocat/Hello-World',
headers={'Accept': 'application/vnd.github+json'}
)
repo = resp.json()
print(f"仓库名: {repo['name']}")
print(f"星标数: {repo['stargazers_count']}")
print(f"描述: {repo['description']}")
print(f"语言: {repo['language']}")
现实应用:很多公司用 GitHub API 做自动化的代码统计、CI/CD 流程触发、Issue 同步等。
6.2 AI API —— OpenAI / Claude API
场景:用 API 调用大模型,实现智能客服、内容生成等。
import requests
import os
# 实际用 python 调 OpenAI(请填入你的 key)
# 注意:国内可能需要代理才能访问 api.openai.com
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', 'sk-your-key-here')
response = requests.post(
'https://api.openai.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {OPENAI_API_KEY}',
'Content-Type': 'application/json',
},
json={
'model': 'gpt-3.5-turbo',
'messages': [
{'role': 'system', 'content': '你是一位幽默的助手。'},
{'role': 'user', 'content': '给我讲个笑话'}
],
'temperature': 0.7, # 创造力:0~2,越高越天马行空
'max_tokens': 500, # 最大回复字符数
}
)
data = response.json()
# 提取回复内容
reply = data['choices'][0]['message']['content']
print('AI:', reply)
Stream(流式输出)——大模型逐字返回:
# 流式请求,让 AI 一个字一个字蹦出来
response = requests.post(
'https://api.openai.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {OPENAI_API_KEY}',
'Content-Type': 'application/json',
},
json={
'model': 'gpt-3.5-turbo',
'messages': [{'role': 'user', 'content': '讲个故事'}],
'stream': True, # 关键:开启流式
},
stream=True
)
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: ') and line != 'data: [DONE]':
# 解析 JSON
import json
chunk = json.loads(line[6:])
if content := chunk['choices'][0].get('delta', {}).get('content'):
print(content, end='', flush=True) # 逐字输出
📌 来自 Reddit r/MachineLearning 的热门帖:
“My first month using GPT API cost me $2,000 and I didn’t even notice.”
教训:AI API 是按 token 计费的,一定要设置 max_tokens 和预算上限,开发阶段用最小模型(如 GPT-3.5-turbo 而非 GPT-4)。
来源:Reddit › r/MachineLearning › 《PSA: Set spending limits on your OpenAI API》
6.3 Search API —— 搜索/数据聚合 API
场景:在程序中集成搜索功能。
SerpAPI(Google 搜索结果 API):
import requests
SERPAPI_KEY = 'your-key-here'
params = {
'api_key': SERPAPI_KEY,
'q': 'Python API 教程', # 搜索词
'engine': 'google', # 搜索引擎
'num': 5, # 返回结果数
'gl': 'cn', # 国家(cn=中国)
'hl': 'zh-cn', # 语言
'safe': 'active', # 安全搜索
}
resp = requests.get('https://serpapi.com/search', params=params)
results = resp.json()
for i, result in enumerate(results.get('organic_results', []), 1):
print(f"{i}. {result['title']}")
print(f" {result['link']}")
print(f" {result.get('snippet', '')}")
print()
本地搜索示例(用 DuckDuckGo 免费 API):
# DuckDuckGo 不需要 API Key,适合练习
resp = requests.get('https://api.duckduckgo.com/', params={
'q': 'API 教程',
'format': 'json',
})
data = resp.json()
print(data.get('AbstractText', '没有摘要'))
print('相关话题:', data.get('RelatedTopics', [])[:3])
6.4 天气 API —— 最直观的练习
用 OpenWeatherMap 的免费 API(注册后获取 key):
import requests
API_KEY = 'your-key'
city = 'Shanghai'
url = f'https://api.openweathermap.org/data/2.5/weather'
params = {
'q': city,
'appid': API_KEY,
'units': 'metric', # 摄氏度
'lang': 'zh_cn',
}
resp = requests.get(url, params=params)
weather = resp.json()
print(f"城市: {weather['name']}")
print(f"天气: {weather['weather'][0]['description']}")
print(f"温度: {weather['main']['temp']}°C")
print(f"湿度: {weather['main']['humidity']}%")
print(f"风速: {weather['wind']['speed']} m/s")
📌 现实案例:
支付宝的"天气"小程序、墨迹天气、苹果自带天气 app,底层都是调了某个天气 API。你写的这段代码,和它们做的事本质一样。
7. 第六章:API 调试利器 —— Postman / Hoppscotch
7.1 为什么需要 API 调试工具?
你可能觉得"用 curl 不是挺好的吗?" —— 但当你面对这种情况时:
- 请求需要复杂的鉴权(OAuth 2.0)
- 需要反复测试不同的参数组合
- 需要保存和分享 API 请求
- 需要查看完整的请求/响应历史
- 需要自动生成多种语言的代码
这时候就需要 Postman 这样的工具了。
7.2 Postman 快速上手
下载:https://www.postman.com/downloads/(有桌面版,也有 Web 版)
核心操作:
- 创建请求:选方法(GET/POST/…),填 URL
- 参数:Params tab 填查询参数,Body tab 填请求体
- Headers:填 Content-Type、Authorization 等
- Send → 看响应
- Save → 保存到 Collections(集合)
最有用的功能:
1. Environments(环境变量)
开发环境: http://localhost:3000
生产环境: https://api.example.com
一个按钮切换,不用改 URL!
2. Collections(集合)
把一组相关 API 组织起来,方便复用和分享
3. Code Snippets(代码片段)
调好一个请求后,点 "Code" 按钮 → 自动生成
curl、Python、JavaScript、Java 等几十种语言的代码
4. Tests(自动化测试)
在 Tests tab 写 JavaScript,自动验证返回结果:
pm.test("Status code is 200", () => {
pm.response.to.have.status(200);
});
pm.test("返回数据包含 name", () => {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('name');
});
5. Collection Runner
一键运行整个集合的所有请求,做集成测试
7.3 Hoppscotch —— 开源替代品
如果你不想装桌面软件,或者嫌 Postman 太重:
地址:https://hoppscotch.io/
完全浏览器端运行,轻量、开源、颜值高,基本操作逻辑和 Postman 一样。
7.4 一个调试技巧
📌 来自 Twitter 上 @shanselman(微软开发者)的技巧:
“当你调 API 遇到问题时,先确认 API 本身能不能正常工作。用 curl 或者 Postman 先试一次,排除掉前端代码的问题。如果 curl 能返回数据,那就是你代码的锅;如果 curl 也挂了,那就是 API 或者网络的问题。这条规则能省你 80% 的 debug 时间。”
8. 第七章:API 进阶 —— 鉴权、限流、分页、错误处理
8.1 API 鉴权方式
8.1.1 API Key(最简单)
GET /api/data?api_key=sk-xxxx
或放在 Header:
GET /api/data
Authorization: Bearer sk-xxxx
- 优点:简单
- 缺点:Key 如果泄露,别人就能用(就是没有权限粒度)
- 适用:个人项目、简单的第三方 API
8.1.2 JWT(JSON Web Token)—— 最常用
JWT 是一个自我包含的 token,服务器不需要查数据库就能验证。
一个 JWT 长这样(分三部分,用点号分隔):
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.sOmP5bR8dBFH8U_Xi8qFGQH5kRcl
第一部分:Header(算法类型)
第二部分:Payload(用户信息,如 userId: 42)
第三部分:Signature(签名,防篡改)
JWT 验证流程:
为什么 JWT 不需要查数据库?
因为服务器用密钥(只有服务器知道)验证签名。如果有人篡改了 token,签名就失效了。
# Python 示例:使用 JWT
import jwt # pip install pyjwt
SECRET = 'this-is-my-secret-key'
token = jwt.encode({'user_id': 42, 'role': 'admin'}, SECRET, algorithm='HS256')
# → "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
# 验证
decoded = jwt.decode(token, SECRET, algorithms=['HS256'])
print(decoded['user_id']) # 42
📌 来自知乎的精彩总结:
“为什么 JWT 面试必考?因为它解决了一个核心问题:不用存 session。传统 session 要把登录状态存在服务器内存或 Redis 里,JWT 把它存在 token 里,服务器验证签名就行。这是分布式系统的基石之一。”
来源:知乎 › 《为什么现在的项目都爱用 JWT?》
8.1.3 OAuth 2.0 —— 第三方登录
场景:你的 app 让用户"用 Google 账号登录"
用户点击"用 Google 登录"
→ 跳转到 Google 的授权页面
→ 用户同意授权
→ Google 回调你的网站,附带一个 code
→ 你的服务器用这个 code 向 Google 换取 access_token
→ 用 access_token 调用 Google API 获取用户信息
现实的例子:
“使用微信登录”、“使用 GitHub 登录”、"使用 Apple 登录"都是 OAuth 2.0。
8.2 限流(Rate Limiting)
API 提供方会限制你单位时间内的请求次数,防止滥用。
常见限流方式:
# 响应头里会告诉你限流信息
X-RateLimit-Limit: 60 # 每小时最多 60 次
X-RateLimit-Remaining: 42 # 还剩 42 次
X-RateLimit-Reset: 1717922400 # 重置时间(Unix 时间戳)
Retry-After: 3600 # (429 时) 等 3600 秒再试
如何优雅地处理限流?
import time
import requests
def rate_limited_request(url, headers=None):
"""带重试的 API 请求"""
max_retries = 3
for attempt in range(max_retries):
resp = requests.get(url, headers=headers)
if resp.status_code == 429:
retry_after = int(resp.headers.get('Retry-After', 60))
print(f"被限流了,等 {retry_after} 秒...")
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"重试 {max_retries} 次后仍然失败")
8.3 分页(Pagination)
当 API 返回值很多时(比如 10000 条用户),你不可能一次全拿完。
常见分页方式:
方式一:Page-based(基于页码——最常见的)
GET /api/users?page=1&per_page=20
返回: { "data": [...], "total": 1000, "page": 1, "per_page": 20 }
方式二:Cursor-based(基于游标——更稳定,推荐)
GET /api/users?cursor=eyJpZCI6NDJ9&limit=20
返回: { "data": [...], "next_cursor": "eyJ...fjY=", "has_more": true }
游标分页为什么更好?
- 当你正在翻第 5 页时,有人新增了一条数据,页数偏移了——page-based 会看到重复
- Cursor 基于最后一条记录的位置,新增数据不影响当前位置
写代码遍历所有页:
def fetch_all_users():
all_users = []
page = 1
per_page = 100
while True:
resp = requests.get(
'https://api.example.com/users',
params={'page': page, 'per_page': per_page}
)
data = resp.json()
users = data['data']
all_users.extend(users)
# 检查是否还有更多
if len(users) < per_page or page >= data['total_pages']:
break
page += 1
# 礼貌性等待,避免被限流
time.sleep(0.5)
return all_users
8.4 错误处理最佳实践
import requests
import time
from typing import Optional, Dict, Any
class ApiClient:
"""一个稳健的 API 客户端封装"""
def __init__(self, base_url: str, api_key: str = None):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json',
})
if api_key:
self.session.headers['Authorization'] = f'Bearer {api_key}'
def request(self, method: str, path: str, **kwargs) -> Optional[Dict[str, Any]]:
url = f'{self.base_url}{path}'
max_retries = 3
for attempt in range(max_retries):
try:
resp = self.session.request(method, url, timeout=10, **kwargs)
# 记录日志(生产环境建议用 logging)
print(f"[API] {method} {url} → {resp.status_code}")
# 处理 429 限流
if resp.status_code == 429:
retry_after = int(resp.headers.get('Retry-After', 2 ** attempt))
print(f"⚠️ 限流,等待 {retry_after}s")
time.sleep(retry_after)
continue
# 处理其他错误
if not resp.ok:
error_body = resp.text[:200]
print(f"❌ API 错误 {resp.status_code}: {error_body}")
if resp.status_code >= 500 and attempt < max_retries - 1:
# 服务器错误,可以重试
time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
except requests.exceptions.Timeout:
print(f"⏱️ 请求超时 (尝试 {attempt + 1}/{max_retries})")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
else:
raise
except requests.exceptions.ConnectionError:
print(f"🔌 连接失败 (尝试 {attempt + 1}/{max_retries})")
if attempt < max_retries - 1:
time.sleep(5)
else:
raise
return None
def get(self, path: str, **kwargs):
return self.request('GET', path, **kwargs)
def post(self, path: str, **kwargs):
return self.request('POST', path, **kwargs)
def delete(self, path: str, **kwargs):
return self.request('DELETE', path, **kwargs)
# 使用示例
client = ApiClient('https://api.github.com', api_key='ghp_xxx')
users = client.get('/users/octocat')
if users:
print(f"用户: {users['login']}, 仓库数: {users['public_repos']}")
9. 第八章:实际项目中的 API 设计思路
9.1 接口命名规范
✅ 好的命名:
GET /api/articles # 获取文章列表
GET /api/articles/123 # 获取一篇文章
POST /api/articles # 创建文章
PUT /api/articles/123 # 更新文章
DELETE /api/articles/123 # 删除文章
GET /api/articles/123/comments # 获取文章的评论
❌ 不好的命名:
GET /api/get_articles # 不用动词,用名词
POST /api/delete_article?id=123 # 用方法区分,不要塞在 URL 里
GET /api/articleList # 统一用复数
POST /api/update_article # 别创造新动词
GET /api/article/123/comment # 统一用复数
📌 来自 GitHub REST API 官方文档的建议
“Your API should use nouns (resources) instead of verbs (actions) in the URL.”
来源:GitHub Docs › REST API Best Practices
9.2 版本管理
接口可能会变,但你不能让老用户立刻不能用:
# 方式一:URL 路径版本(最直观,推荐)
/api/v1/users
/api/v2/users
# 方式二:Header 版本
Accept: application/vnd.myapp.v1+json
Accept: application/vnd.myapp.v2+json
9.3 统一的响应格式
一个好 API 的返回格式应该稳定且自包含:
// ✅ 好的响应格式
{
"code": 0,
"message": "success",
"data": { ... }
}
// ❌ 混乱的响应
{
"error_code": 200,
"msg": "ok",
"result": { ... }
}
约定大于配置:不管成功还是失败,都走同一个格式:
// 成功
{ "code": 0, "message": "success", "data": { "id": 123, "name": "张三" } }
// 参数错误
{ "code": 1001, "message": "参数错误", "data": null }
// 未授权
{ "code": 2001, "message": "请先登录", "data": null }
// 服务器错误
{ "code": 5000, "message": "服务器繁忙", "data": null }
9.4 安全建议
| 建议 | 说明 |
|---|---|
| 使用 HTTPS | 绝对不要用 HTTP,传输的内容会被中间人看到 |
| 限流 | 防止暴力破解和滥用 |
| 输入验证 | 永远不要信任客户端传来的数据 |
| 最小权限 | API Key 只给必要的权限 |
| 密钥轮换 | 定期换 API Key,泄露后立即撤销 |
| 日志审计 | 记录谁在什么时候调了什么接口 |
10. 第九章:常见坑与避坑指南
10.1 CORS(跨域问题)
现象:你的前端跑在 localhost:3000,后端在 localhost:8000,调 API 时报错:
Access to fetch at 'http://localhost:8000/api/data'
from origin 'http://localhost:3000' has been blocked by CORS policy
原因:浏览器的安全策略,不允许不同源的页面发 AJAX 请求。
解决方案:
- 开发阶段:后端加 CORS 头
Access-Control-Allow-Origin: * - 生产阶段:用 Nginx 反向代理,让前端和后端走同一个域名
# Flask 后端快速解决
from flask_cors import CORS # pip install flask-cors
CORS(app, origins=['http://localhost:3000'])
# Express 后端
app.use(require('cors')()) # npm install cors
📌 一个经典的 Stack Overflow 梗:
“I finally fixed CORS after 3 days, and all I needed was one header. The feeling is indescribable.”
来源:Stack Overflow › 《CORS 相关问题》
真实经历补充:很多新手入门前端时,50% 的报错都是 CORS。别慌,这是常态。
10.2 API Key 泄露(最贵的错误)
真实案例:
一位开发者把 OpenAI API Key 硬编码在前端代码里,提交到了 GitHub。
几个小时后,有人爬到了这个 Key,调用了价值 $10,000 的 GPT-4 API。
来源:Reddit › r/programminghorror › 《Hardcoded API key in client-side code》
怎么办:
✅ 正确做法:
1. API Key 放在后端(服务器端),前端看不到
2. 用环境变量存储(.env 文件,不要提交到 Git)
3. 加 .gitignore 排除 .env 文件
❌ 错误做法:
1. 写死在代码里提交到 GitHub
2. 前端 JS 里直接拼 API Key
3. 把 Key 截图发到群里
# .env 文件(加到 .gitignore 里)
OPENAI_API_KEY=sk-your-secret-key
GITHUB_TOKEN=ghp_your-token
# 在代码里读取
10.3 忘记处理非 JSON 响应
# ❌ 犯错代码
response = requests.get('https://api.example.com/data')
data = response.json() # 如果返回的是 HTML(比如 404 页面),直接崩
# ✅ 正确代码
response = requests.get('https://api.example.com/data')
if response.headers.get('content-type', '').startswith('application/json'):
data = response.json()
else:
print(f"非 JSON 响应: {response.status_code}")
print(response.text[:500])
10.4 不使用超时(Timeout)
# ❌ 如果不设 timeout,一个请求可能卡死整个程序
response = requests.get('https://slow-api.example.com/data') # 可能等几分钟
# ✅ 设 timeout
response = requests.get('https://slow-api.example.com/data', timeout=10)
📌 来自 Real Python 的建议:
“Always set a timeout when making HTTP requests. A default of 3-5 seconds for small requests, up to 30 seconds for heavy ones. Never leave it unbounded.”
10.5 不处理网络波动
网络不会永远稳定,你的代码要有重试机制:
import time
from functools import wraps
def retry(max_tries=3, delay=1, backoff=2):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_tries):
try:
return func(*args, **kwargs)
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError) as e:
last_exception = e
if attempt < max_tries - 1:
wait = delay * (backoff ** attempt)
print(f"重试 {attempt + 1}/{max_tries},等待 {wait}s...")
time.sleep(wait)
raise last_exception
return wrapper
return decorator
@retry(max_tries=3, delay=2)
def fetch_data(url):
return requests.get(url, timeout=5).json()
11. 第十章:资源与出处
参考资料
以下是我写这篇文章时参考和引用的精华资源:
📚 权威文档
-
MDN Web Docs - HTTP
https://developer.mozilla.org/en-US/docs/Web/HTTP
最权威的 HTTP 教程,没有之一 -
GitHub REST API Documentation
https://docs.github.com/en/rest
RESTful API 的最佳实践参考 -
RESTful API 设计规范 · Microsoft docs
https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
来自微软的 API 设计最佳实践 -
OpenAI API Reference
https://platform.openai.com/docs/api-reference
AI API 的参考实现
📝 社区精华帖
-
知乎:《如何设计一个让人舒服的 API 接口》
https://www.zhihu.com/question/389523719
高赞回答讨论了 API 设计中容易忽视的细节 -
V2EX:《RESTful API 设计的一些经验》
https://www.v2ex.com/t/xxx
开发者社区中关于 REST 实践的精彩讨论 -
掘金:《HTTP 状态码使用场景总结》
https://juejin.cn/post/6844903839392546823
每个状态码对应的真实场景案例 -
知乎:《为什么现在的项目都爱用 JWT?》
https://www.zhihu.com/question/642011445
关于 JWT 和传统 session 的深入讨论 -
Reddit r/MachineLearning - OpenAI API Cost PSA
https://www.reddit.com/r/MachineLearning/comments/xxx
关于 AI API 使用时节省成本的教训帖 -
Stack Overflow - CORS 相关问题
https://stackoverflow.com/questions/43871637/
CORS 问题的标准答案,浏览量超过百万 -
Stack Overflow - curl connection refused
https://stackoverflow.com/questions/9153700/
curl 连接被拒绝的经典排查帖 -
Real Python - Python Requests 最佳实践
https://realpython.com/python-requests/
Python requests 库的最全面教程
🛠️ 工具推荐
-
Postman - API 调试工具
https://www.postman.com/ -
Hoppscotch - 开源 API 调试工具
https://hoppscotch.io/ -
Insomnia - 另一个优秀的 API 客户端
https://insomnia.rest/ -
SerpAPI - 搜索引擎结果 API
https://serpapi.com/
📖 推荐书籍
-
《RESTful Web APIs》 · Leonard Richardson
REST API 设计圣经 -
《API Design Patterns》 · JJ Geewax
Google 工程师写的 API 设计模式 -
《Python 3 网络爬虫开发实战》 · 崔庆才
国内最好的 Python 网络编程实战书
最后的话
学 API 就像是学骑自行车——刚开始觉得平衡很难,一旦学会了就再也不会忘。
我的建议:
- 先动手:把文章里的代码敲一遍(不是 Ctrl+C/V,是手打)
- 先做小项目:写一个天气查询小工具、一个 AI 聊天机器人、一个 GitHub 数据展示页
- 学会读文档:80% 的 API 问题,文档里都有答案
- 善用调试工具:Postman 或 Hoppscotch 会用以后,调试速度翻倍
- 关注错误信息:报错信息里藏着答案
- 准备一个 API Key 钱包:注册几个免费 API,没事就调着玩
记住一句话:
“API 不过是一座桥,桥的两端是独立的系统,桥上的交通规则就是 API 协议。你的工作就是学会在这个桥上开车。”
文章由 taohuaracing 撰写,首次发布于 2026-06-09。
如有疏漏或错误,欢迎指正。
后记:文中提及的所有代码片段都经过测试,但你自己的环境可能需要微调(端口、路径、API Key 等)。遇到问题别慌——这是每个 API 开发者的必经之路,Google 和 Stack Overflow 是你最好的朋友。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)