练习:API 小客户端 🌐
目标
在真实的开发世界里,程序很少是"自己跟自己玩"的——它们要跟服务器通信,获取数据、发送数据。这就是 API(Application Programming Interface)的用处。
在这个练习中,你要写一个小程序,从一个公开的测试 API 获取数据,然后对数据进行筛选和格式化输出。
我们使用 JSONPlaceholder——这是一个专门给开发者练手的免费 API,不需要注册,不需要密钥,直接请求就行。
背景知识:API 是什么?
你可以把 API 想象成一个餐厅的菜单:
- 你(客户端)看着菜单说:"我要一份红烧肉"(发请求)
- 厨房(服务器)收到订单,做好菜(处理请求)
- 服务员把菜端给你(返回数据)
你不需要知道厨房怎么做菜,你只需要知道菜单上有什么、怎么点。
JSONPlaceholder 的"菜单"长这样:
| 地址 | 返回什么 |
|---|---|
/posts | 100 篇帖子 |
/posts/1 | 第 1 篇帖子 |
/posts?userId=1 | 用户 1 的所有帖子 |
/users | 10 个用户 |
/comments?postId=1 | 帖子 1 的所有评论 |
每个"菜"返回的都是 JSON 格式的数据——你已经很熟悉了,就是对象/字典。
任务清单
你的 API 客户端需要做到:
- ✅ 从
https://jsonplaceholder.typicode.com/posts获取所有帖子 - ✅ 筛选出某个用户(比如 userId = 1)的帖子
- ✅ 格式化输出每篇帖子的标题和正文摘要(正文只显示前 50 个字符)
- ✅ 统计该用户一共有多少篇帖子
- 🌟 加分项:再获取该用户的信息(
/users/1),在报告里显示用户名
骨架代码
分步提示
提示 1:怎么发 HTTP 请求?
这是整个练习最"新"的部分。别慌,其实就两三行代码的事。
提示 2:返回的数据长什么样?
一篇帖子的 JSON 结构是这样的:
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis..."
}
所以筛选 userId === 1 的帖子,就是过滤这个字段。
提示 3:字符串截断
正文可能很长,我们只显示前 50 个字符加省略号:
提示 4:错误处理
网络请求可能失败(网断了、服务器挂了、URL 写错了)。你应该用 try/except(Python)或 try/catch(JS)把请求包起来:
完整参考答案
🔑 点击展开 Python 完整答案
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def fetch_posts():
try:
response = requests.get(f"{BASE_URL}/posts")
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"获取帖子失败: {e}")
return None
def fetch_user(user_id):
try:
response = requests.get(f"{BASE_URL}/users/{user_id}")
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"获取用户信息失败: {e}")
return None
def filter_by_user(posts, user_id):
return [p for p in posts if p["userId"] == user_id]
def truncate(text, max_len=50):
text = text.replace("\n", " ")
if len(text) > max_len:
return text[:max_len] + "..."
return text
def display_posts(posts):
for i, post in enumerate(posts, 1):
print(f"[{i}] {post['title']}")
print(f" {truncate(post['body'])}")
print()
def main():
print("正在获取数据...")
posts = fetch_posts()
if posts is None:
print("获取数据失败!请检查网络连接。")
return
user_id = 1
user = fetch_user(user_id)
user_posts = filter_by_user(posts, user_id)
if user:
print(f"\n{user['name']} (@{user['username']}) 的帖子")
else:
print(f"\n用户 {user_id} 的帖子")
print(f"共 {len(user_posts)} 篇")
print("=" * 50)
display_posts(user_posts)
print("=" * 50)
print("获取完毕!")
main()
🔑 点击展开 JavaScript 完整答案
const BASE_URL = "https://jsonplaceholder.typicode.com"
async function fetchPosts() {
try {
const response = await fetch(`${BASE_URL}/posts`)
if (!response.ok) {
throw new Error("HTTP 错误: " + response.status)
}
return await response.json()
} catch (error) {
console.log("获取帖子失败: " + error.message)
return null
}
}
async function fetchUser(userId) {
try {
const response = await fetch(`${BASE_URL}/users/${userId}`)
if (!response.ok) {
throw new Error("HTTP 错误: " + response.status)
}
return await response.json()
} catch (error) {
console.log("获取用户信息失败: " + error.message)
return null
}
}
function filterByUser(posts, userId) {
return posts.filter((p) => p.userId === userId)
}
function truncate(text, maxLen = 50) {
const cleaned = text.replace(/\n/g, " ")
if (cleaned.length > maxLen) {
return cleaned.slice(0, maxLen) + "..."
}
return cleaned
}
function displayPosts(posts) {
posts.forEach((post, i) => {
console.log(`[${i + 1}] ${post.title}`)
console.log(` ${truncate(post.body)}`)
console.log()
})
}
async function main() {
console.log("正在获取数据...")
const posts = await fetchPosts()
if (!posts) {
console.log("获取数据失败!请检查网络连接。")
return
}
const userId = 1
const user = await fetchUser(userId)
const userPosts = filterByUser(posts, userId)
if (user) {
console.log(`\n${user.name} (@${user.username}) 的帖子`)
} else {
console.log(`\n用户 ${userId} 的帖子`)
}
console.log(`共 ${userPosts.length} 篇`)
console.log("=".repeat(50))
displayPosts(userPosts)
console.log("=".repeat(50))
console.log("获取完毕!")
}
main()
基础版完成之后,挑战这些进阶任务:
- 获取评论 — 对每篇帖子,再获取它的评论(
/comments?postId=X),显示评论数 - 并发请求 — 同时获取帖子和用户信息,而不是一个等一个(Python 用
asyncio,JS 用Promise.all()) - 命令行交互 — 让用户输入 userId,动态查询不同用户的帖子
- 缓存机制 — 已经获取过的数据存起来,下次直接用,不重复请求
问问 AI:"什么是 API 的 rate limiting(速率限制)?如果我的程序短时间内发了太多请求,会发生什么?我应该怎么处理?"
这是一个在实际开发中必须了解的概念。JSONPlaceholder 很宽容,但大多数正式 API 都有请求频率限制。
你已经完成了三个练习项目! 从本地计算、数据处理到网络请求,你已经体验了编程中最常见的三大场景。回头看看你写的代码——几个月前你还什么都不会呢,现在已经能写出像样的程序了。这就是进步。
继续保持,前方还有更精彩的内容等着你。