跳到主要内容

练习:API 小客户端 🌐

目标

在真实的开发世界里,程序很少是"自己跟自己玩"的——它们要跟服务器通信,获取数据、发送数据。这就是 API(Application Programming Interface)的用处。

在这个练习中,你要写一个小程序,从一个公开的测试 API 获取数据,然后对数据进行筛选和格式化输出。

我们使用 JSONPlaceholder——这是一个专门给开发者练手的免费 API,不需要注册,不需要密钥,直接请求就行。

背景知识:API 是什么?

你可以把 API 想象成一个餐厅的菜单

  • 你(客户端)看着菜单说:"我要一份红烧肉"(发请求)
  • 厨房(服务器)收到订单,做好菜(处理请求)
  • 服务员把菜端给你(返回数据)

你不需要知道厨房怎么做菜,你只需要知道菜单上有什么怎么点

JSONPlaceholder 的"菜单"长这样:

地址返回什么
/posts100 篇帖子
/posts/1第 1 篇帖子
/posts?userId=1用户 1 的所有帖子
/users10 个用户
/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()
🏋️AI 练习

基础版完成之后,挑战这些进阶任务:

  1. 获取评论 — 对每篇帖子,再获取它的评论(/comments?postId=X),显示评论数
  2. 并发请求 — 同时获取帖子和用户信息,而不是一个等一个(Python 用 asyncio,JS 用 Promise.all()
  3. 命令行交互 — 让用户输入 userId,动态查询不同用户的帖子
  4. 缓存机制 — 已经获取过的数据存起来,下次直接用,不重复请求
问问 AI

问问 AI:"什么是 API 的 rate limiting(速率限制)?如果我的程序短时间内发了太多请求,会发生什么?我应该怎么处理?"

这是一个在实际开发中必须了解的概念。JSONPlaceholder 很宽容,但大多数正式 API 都有请求频率限制。


你已经完成了三个练习项目! 从本地计算、数据处理到网络请求,你已经体验了编程中最常见的三大场景。回头看看你写的代码——几个月前你还什么都不会呢,现在已经能写出像样的程序了。这就是进步。

继续保持,前方还有更精彩的内容等着你。