第3章 3.1 网络请求:requests 封装

上章我们用手把手做了一个「小明的个人主页」,实现了页面跳转和数据传递。但你有没有发现一个问题——我们一直都是自己造数据:

# 上一章的代码
friends = ["小红", "小明", "小刚"]

真实世界里,数据从哪来?从网上来。这就像你做饭需要的食材,总不能自己在家种吧——得去超市买。

这一章,我们来学怎么「买菜」——也就是从网络获取数据


🎯 开场 3 分钟:为什么要学这个?

想象一个场景:

你想做一个「今日天气」小工具,打开 App 就能看到今天天气怎么样。

但天气数据不在你手机里,在气象局的服务器上。你得想办法「打电话」问他们:「今天北京天气咋样?」人家告诉你,你再显示出来。

这就是网络请求——你的程序向外面的服务器要数据。

痛点来了

  • ❌ 你可能写过 requests.get(url),但返回一堆乱七八糟的东西,不知道咋取数据
  • ❌ 每次请求都要写一堆重复代码(\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n设置超时、处理错误、判断返回状态)
  • ❌ 多人合作时,大家各写各的,代码风格一团糟

学完这章,你能:✅ 用几行代码从任意 API 拿数据,✅ 封装一个自己的「网络工具箱」,✅ 写出一个能跑起来的天气查询小工具。


🧱 基础 25 分钟:核心概念

3.1.1 什么是网络请求?——「打电话」模型

类比时间到:

想象你点外卖。你做这几件事:

  1. 拨号(发起请求)——告诉商家我要点餐
  2. 报地址(传参数)——告诉他送到哪
  3. 等餐(等待响应)——商家准备好送过来
  4. 收货(拿到数据)——你拿到外卖,吃掉(处理数据)

网络请求也是一样的

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 处理网络异常
  • ✅ 封装自己的网络请求工具函数

延伸资源


互动钩子:你在做项目时遇到过什么奇葩的接口?比如返回乱码、数据格式奇葩、或者接口直接 500?评论区聊聊,老粉优先回复!


下章剧透:现在你学会了「发请求」,但如果一个页面想通知另一个页面「我这里有新数据了,刷新一下」怎么办?下一章,我们来解决这个「跨页面通讯」的问题……

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。