第3章 3.1 网络请求:requests 封装
上章我们用手把手做了一个「小明的个人主页」,实现了页面跳转和数据传递。但你有没有发现一个问题——我们一直都是自己造数据:
# 上一章的代码
friends = ["小红", "小明", "小刚"]
真实世界里,数据从哪来?从网上来。这就像你做饭需要的食材,总不能自己在家种吧——得去超市买。
这一章,我们来学怎么「买菜」——也就是从网络获取数据。
🎯 开场 3 分钟:为什么要学这个?
想象一个场景:
你想做一个「今日天气」小工具,打开 App 就能看到今天天气怎么样。
但天气数据不在你手机里,在气象局的服务器上。你得想办法「打电话」问他们:「今天北京天气咋样?」人家告诉你,你再显示出来。
这就是网络请求——你的程序向外面的服务器要数据。
痛点来了:
- ❌ 你可能写过
requests.get(url),但返回一堆乱七八糟的东西,不知道咋取数据 - ❌ 每次请求都要写一堆重复代码(\n\n
\n\n
\n\n设置超时、处理错误、判断返回状态) - ❌ 多人合作时,大家各写各的,代码风格一团糟
学完这章,你能:✅ 用几行代码从任意 API 拿数据,✅ 封装一个自己的「网络工具箱」,✅ 写出一个能跑起来的天气查询小工具。
🧱 基础 25 分钟:核心概念
3.1.1 什么是网络请求?——「打电话」模型
类比时间到:
想象你点外卖。你做这几件事:
- 拨号(发起请求)——告诉商家我要点餐
- 报地址(传参数)——告诉他送到哪
- 等餐(等待响应)——商家准备好送过来
- 收货(拿到数据)——你拿到外卖,吃掉(处理数据)
网络请求也是一样的:
import requests
# 1. 拨号(发起请求)
response = requests.get("https://api.example.com/weather?city=北京")
# 2. 等待后,response 里装着服务器返回的东西
print(response.status_code) # 状态码:200 表示成功
print(response.json()) # 把 JSON 数据转成 Python 字典
解释一下:
- requests.get() 就是「打电话」
- ?city=北京 是你告诉服务器「我要查北京的天气」
- response.json() 是把服务器返回的「外卖」打开,变成你能用的 Python 数据
3.1.2 请求方法:GET 和 POST 区别
记住一个生活类比:
- GET = 查资料(只看不改)——去图书馆翻书,不会把书弄脏
- POST = 填表格(要写入数据)——填申请表,工作人员会记录你的信息
# GET 请求:获取数据(查)
response = requests.get("https://api.example.com/users/1")
# POST 请求:提交数据(改)
data = {"name": "小明", "age": 18}
response = requests.post("https://api.example.com/users", json=data)
3.1.3 状态码:服务器的「回话」
每次请求完,服务器都会给你一个状态码,告诉你「这事办得咋样」:
| 状态码 | 含义 | 类比 |
|---|---|---|
| 200 | 成功 | 「好的,已收到」 |
| 404 | 没找到 | 「这人搬走了」 |
| 500 | 服务器挂了 | 「店关门了」 |
response = requests.get("https://api.example.com/data")
if response.status_code == 200:
print("请求成功!")
print(response.json())
else:
print(f"出错了,状态码:{response.status_code}")
3.1.4 请求头和请求体:寄快递的细节
想象寄快递:
- URL = 收货地址
- 请求头(Headers) = 快递单上的备注(我是谁、我用啥包装)
- 请求体(Body) = 包裹里的东西
# 模拟浏览器访问(有些 API 需要这个,否则会拒绝)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json"
}
# 带请求头的请求
response = requests.get(
"https://api.example.com/data",
headers=headers,
timeout=10 # 等10秒还没返回就放弃(防卡死)
)
3.1.5 JSON:数据的「标准包装」
JSON 就像是网络上数据传输的「标准快递盒」——大家都在用,所以 Python 和服务器能互相看懂。
# 服务器返回的 JSON 长这样:
# {"code": 200, "data": {"temp": 25, "city": "北京"}}
response = requests.get("https://api.example.com/weather?city=北京")
weather = response.json() # 直接转成 Python 字典
print(weather["data"]["temp"]) # 输出:25
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):获取一句话
目标:调用一个免费 API,拿到一句随机鸡汤文。
import requests
# 调用一言 API(一个免费的随机句子接口)
url = "https://v1.hitokoto.cn"
response = requests.get(url, timeout=5)
if response.status_code == 200:
data = response.json()
print(f"「{data['hitokoto']}」—— {data['from']}")
else:
print("获取失败")
预期输出:
「人生苦短,我用Python。」—— Python
一句话解释:用 requests.get() 拿到 API 返回的 JSON,从里面取出 hitokoto(句子)和 from(出处)。
项目 2(15 分钟):天气查询小工具
目标:输入城市名,查询该城市的天气。
这回我们用和风天气 API(有免费额度,注册后拿 key)。
import requests
def get_weather(city_name, api_key="你的KEY"):
"""查询城市天气"""
url = f"https://free-api.heweather.net/s6/weather/now"
params = {
"location": city_name,
"key": api_key
}
try:
response = requests.get(url, params=params, timeout=5)
if response.status_code != 200:
return "请求失败"
weather_data = response.json()
# 检查 API 返回状态
if weather_data["HeWeather6"][0]["status"] != "ok":
return "城市未找到"
# 解析数据
now = weather_data["HeWeather6"][0]["now"]
city = weather_data["HeWeather6"][0]["basic"]["location"]
return f"{city} | 天气:{now['cond_txt']} | 温度:{now['tmp']}°C | {now['wind_dir']}{now['wind_sc']}级"
except requests.exceptions.Timeout:
return "请求超时,网络有点慢"
except Exception as e:
return f"出错了:{e}"
# 测试
if __name__ == "__main__":
print("=== 天气查询工具 ===")
city = input("请输入城市名(直接回车查北京):").strip()
if not city:
city = "北京"
result = get_weather(city, api_key="你的和风key")
print(result)
预期输出(假设输入"上海"):
=== 天气查询工具 ===
请输入城市名(直接回车查北京):上海
上海 | 天气:多云 | 温度:22°C | 东南风3级
一句话解释:把城市名作为参数发给天气 API,解析返回的 JSON,提取关键信息展示。
项目 3(15 分钟):批量获取豆瓣电影信息
目标:输入多个电影名,批量爬取它们的评分和简介。
import requests
import time
def search_douban(movie_name):
"""搜索豆瓣电影信息"""
url = "https://douban.uieee.com/v2/movie/search"
params = {"q": movie_name}
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(url, params=params, headers=headers, timeout=5)
if response.status_code != 200:
return None
data = response.json()
# 取第一个搜索结果
if data["subjects"]:
movie = data["subjects"][0]
return {
"title": movie["title"],
"score": movie["rate"] or "暂无评分",
"year": movie["year"]
}
return None
except Exception as e:
print(f"搜索「{movie_name}」出错:{e}")
return None
def main():
# 小明的观影清单
movie_list = ["肖申克的救赎", "霸王别姬", "阿甘正传", "这个杀手不太冷", "千与千寻"]
print("=== 小明的观影清单查询 ===\n")
for movie in movie_list:
result = search_douban(movie)
if result:
print(f"📽️ {result['title']} ({result['year']})")
print(f" 评分:{'⭐' * int(float(result['score']))} {result['score']}\n")
else:
print(f"⚠️ 没找到「{movie}」\n")
time.sleep(1) # 休息1秒,别把人家服务器打崩
print("查询完成!")
if __name__ == "__main__":
main()
预期输出:
=== 小明的观影清单查询 ===
📽️ 肖申克的救赎 (1994)
分:⭐⭐⭐⭐⭐ 9.7
📽️ 霸王别姬 (1993)
分:⭐⭐⭐⭐⭐ 9.6
📽️ 阿甘正传 (1994)
分:⭐⭐⭐⭐⭐ 9.5
...
一句话解释:循环查每个电影名,每个请求间隔 1 秒防止被封,返回结果存成字典。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:JSON 解析失败
# ❌ 错误:response.text 是字符串,不是字典
data = response.text["code"]
# ✅ 正确:用 .json() 转成字典
data = response.json()
print(data["code"])
坑 2:请求超时导致程序卡死
# ❌ 错误:没设置超时,可能永远等下去
response = requests.get(url)
# ✅ 正确:设置超时,超过就报错
response = requests.get(url, timeout=5)
坑 3:中文 URL 没编码
# ❌ 错误:直接传中文 URL 可能出问题
url = "https://api.com/search?word=你好"
# ✅ 正确:用 urlencode 编码
from urllib.parse import urlencode
params = {"word": "你好"}
url = "https://api.com/search?" + urlencode(params)
坑 4:忽略了返回的 status_code
# ❌ 错误:直接用数据,不检查状态
data = response.json() # 万一返回错误页面呢?
# ✅ 正确:先检查再使用
if response.status_code == 200:
data = response.json()
else:
print(f"请求失败:{response.status_code}")
坑 5:没处理网络异常
# ❌ 错误:网络出错程序直接崩溃
data = requests.get(url).json()
# ✅ 正确:用 try-except 包裹
try:
data = requests.get(url, timeout=5).json()
except requests.exceptions.Timeout:
print("请求超时")
except requests.exceptions.ConnectionError:
print("网络连接失败")
except Exception as e:
print(f"其他错误:{e}")
性能小技巧:复用 Session
如果你要发很多请求,用 Session 能复用 TCP 连接,快 3-5 倍:
import requests
# ❌ 普通方式:每次都新建连接
for _ in range(10):
requests.get("https://api.example.com/data")
# ✅ 用 Session:复用连接
with requests.Session() as session:
for _ in range(10):
session.get("https://api.example.com/data")
调试技巧:打印完整请求信息
import requests
response = requests.get("https://api.example.com/test", timeout=5)
# 打印这些,方便定位问题
print("状态码:", response.status_code)
print("响应头:", response.headers)
print("响应体:", response.text[:500]) # 只看前500字符
✏️ 练习题
练习 1(1 分钟):改 URL
# 下面代码是项目1的,改成调用这个接口:https://api.btstu.cn/yan/api.php
# 预期输出:一句话
import requests
url = "https://api.btstu.cn/yan/api.php"
response = requests.get(url, timeout=5)
print(response.text.strip())
练习 2(2 分钟):加判断
在项目 1 的代码里,加一个 if 判断,当状态码不是 200 时打印「接口挂了」。
练习 3(3 分钟):换成查城市
把项目 2 的城市改成你自己所在的城市,看看结果对不对。
练习 4(3 分钟):增加搜索词
修改项目 3 的 movie_list,加入你喜欢的 3 部电影,跑一下看看结果。
练习 5(5 分钟):分析报错
假设你运行项目 2 时遇到这个报错:
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
请分析原因并修复(提示:检查 API 返回的是不是 JSON)。
作业:做一个「豆瓣 Top250 电影爬虫」
需求描述:写一个工具,自动抓取豆瓣 Top250 电影榜单的前 10 部,输出电影名、评分、评价人数。
功能点:
1. 用 requests 调用豆瓣 API
2. 设置合理的请求头模拟浏览器
3. 解析返回的 JSON,提取目标字段
4. 把结果格式化输出
加分项:
1. 加入异常处理(网络超时、解析失败)
2. 把结果保存到本地 CSV 文件
验收标准:
- 能跑起来
- 输出前 10 部电影的「名称 + 评分 + 评价人数」
- 代码有注释
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
这章学了啥:
- ✅
requests.get()和requests.post()发网络请求 - ✅ 状态码判断、JSON 解析、请求头设置
- ✅ 用
try-except处理网络异常 - ✅ 封装自己的网络请求工具函数
延伸资源:
- requests 官方文档——最权威的参考资料
- Python 网络编程——标准库方式
- 《Python 编程:从入门到实践》——第 17 章有网络请求实战
互动钩子:你在做项目时遇到过什么奇葩的接口?比如返回乱码、数据格式奇葩、或者接口直接 500?评论区聊聊,老粉优先回复!
下章剧透:现在你学会了「发请求」,但如果一个页面想通知另一个页面「我这里有新数据了,刷新一下」怎么办?下一章,我们来解决这个「跨页面通讯」的问题……

评论(0)