1. 本课定位:是什么、为何重要
第 33 课认清了 HTTP 的URL / 方法 / 状态码 / Header / Body。
本课把这些约定,落成Python 里可维护的客户端代码。
| 概念 | 一句话 |
|---|---|
requests | 第三方 HTTP 客户端库:发请求、收响应,比标准库urllib更短、更清晰 |
| Response 对象 | 一次调用的结果:状态码、头、正文;.json()再变成 dict/list |
为何重要:调天气、支付、GitHub、公司内部 API,几乎都是「写客户端」;写不稳(无超时、乱拼密钥、不看状态码)会在生产里埋雷。
对比已学:
| 已学(Day33 / urllib) | 本课(requests) |
|---|---|
手写Request+urlopen+ 自己json.loads | requests.get/post+r.json() |
| 概念上的五件套 | 五件套对应到函数参数与属性 |
| 知道要超时 | 强制把timeout写成习惯 |
| 知道 4xx/5xx | raise_for_status/ 异常类型 |
pipinstallrequests2. 本质、约束与常见坑
本质:
一次requests.get/post(...)≈ 组装请求(方法、URL、头、体)→ 经网络发出 → 得到Response。
默认同步阻塞:这行不返回,后面的代码不会跑(这是第 35~36 课并发的伏笔)。
约束:
- 必须能访问目标主机;证书、代理、防火墙会导致失败。
- 默认不会永远重试;失败要你自己处理。
- 对方返回的不一定是 JSON(错误页常是 HTML)。
r.json()得到的是新的Python 对象,只存在本机内存,不改服务器数据。
常见坑:
| 坑 | 现象 | 正确做法 |
|---|---|---|
不写timeout | 网络卡住时进程挂死 | 几乎每次调用都带timeout= |
只r.json()不看状态码 | 404 HTML 导致 JSON 解析炸 | 先raise_for_status()或判断status_code |
json=与data=混用 | Content-Type / 服务端解析不对 | JSON 用json=;表单常用data= |
| 密钥写进源码 | 泄露进 Git | 环境变量 / 配置文件(不进仓库) |
在asyncio里直接requests.get | 堵死事件循环 | 异步用httpx/aiohttp(第 36 课) |
| 无限猛重试 | 打爆对方、自己被封 | 有限次数 + 间隔;尊重 429 |
3. 最小调用:GET / POST
importrequests r=requests.get("https://httpbingo.org/get",params={"q":"python"},timeout=20,)print(r.status_code)print(r.json().get("args"))r2=requests.post("https://httpbingo.org/post",json={"name":"bob"},timeout=20,)print(r2.json().get("json")orr2.json().get("data"))预期输出(形态):
200 {'q': ['python']} {'name': 'bob'}调用前:没有 Response。
调用后:可读status_code、.text、.json()等。
4. 传参对照(params / json / data / headers)
| 参数 | 数据放哪 | 典型用途 |
|---|---|---|
params= | URL 查询串 | GET 过滤、分页 |
json= | Body,且设为 JSON | POST/PUT API |
data= | Body(常表单编码) | 传统 form |
headers= | 请求头 | Token、UA、Accept |
timeout= | (不是 Body) | 最长等待秒数 |
小对照:
# 查询串:.../get?a=1requests.get(url,params={"a":1},timeout=20)# JSON Bodyrequests.post(url,json={"a":1},timeout=20)# 表单 Body(application/x-www-form-urlencoded)requests.post(url,data={"a":1},timeout=20)同一份{"a":1},位置不同,服务端收到的「槽位」就不同——对应 Day33 的 query vs Body。
5. 响应怎么读(属性分类)
| 类别 | 常用 | 含义 |
|---|---|---|
| 状态 | status_code、ok、raise_for_status() | 成败与是否抛错 |
| 头 | headers | 对方回的元数据 |
| 正文 | text、content、json() | 字符串 / 字节 / 解析后的对象 |
r=requests.get("https://httpbingo.org/get",params={"x":1},timeout=20)print(r.status_code,r.ok)r.raise_for_status()data=r.json()# 新 dict,不是改服务器print(type(data),data.get("args"))okvsraise_for_status:
| 写法 | 行为 |
|---|---|
if r.ok: | 2xx 为真,自行分支 |
r.raise_for_status() | 4xx/5xx 抛HTTPError,适合「失败就别往下走」 |
6. 能力分组:发送 · 稳健 · 会话 · 身份
6.1 发送类
requests.get/post/put/delete/patch- 或统一
requests.request("GET", url, ...)
6.2 稳健类(强烈建议形成肌肉记忆)
try:r=requests.get(url,timeout=10)r.raise_for_status()data=r.json()exceptrequests.Timeout:print("超时")exceptrequests.HTTPErrorase:print("HTTP 错误",e.response.status_code)exceptrequests.RequestExceptionase:print("网络或请求层错误",e)超时演示(访问约 5 秒延迟、timeout=2):
importrequeststry:requests.get("https://httpbingo.org/delay/5",timeout=2)exceptrequests.Timeout:print("Timeout")预期输出:
Timeout重试(教学版,有限次数 + 间隔):
importtimeimportrequests url="https://httpbingo.org/status/200"forattemptinrange(1,4):resp=requests.get(url,timeout=15)print(f"attempt{attempt}: status={resp.status_code}")ifresp.ok:breaktime.sleep(0.5)预期输出:
attempt 1: status=200收到429应降速,而不是加大并发硬刚。
6.3 会话类 Session
每次requests.get | Session | |
|---|---|---|
| Cookie | 默认不跨请求保持 | 可自动保持 |
| 默认头 | 每次传 | session.headers.update(...)一次 |
| 连接 | 相对一次性 | 更易复用(性能细节入门了解即可) |
session=requests.Session()session.headers.update({"User-Agent":"python-lab-day34/1.0"})r=session.get("https://httpbingo.org/get",params={"page":1},timeout=20)6.4 身份与密钥
importosimportrequests key=os.environ.get("DEMO_API_KEY","")headers={"Authorization":f"Bearer{key}"}ifkeyelse{}r=requests.get("https://httpbingo.org/headers",headers=headers,timeout=15)错误:Authorization = "Bearer sk-xxxx"写死在仓库。
正确:环境变量注入;文档里只写变量名。
7. 落地场景(故事 + 写法)
| 场景 | 方法 | 关键参数 |
|---|---|---|
| 查公开仓库信息 | GET | URL 路径参数 / 查询串 |
| 提交注册 JSON | POST | json={...} |
| 带令牌访问 | GET/POST | headers=Authorization |
| 对方偶发 503 | GET | 有限重试 + timeout |
教学仍可用回声服务验证「参数有没有发对」;真实项目换成业务 Base URL 即可。
8. 综合实践(一键脚本)
mkdir-p~/python-lab/src/day34cd~/python-lab/src/day34 pipinstallrequestsWindows 推荐 Cygwin 或 WSL。
cat>requests_demo.py<<'EOF' # Day34: requests client import os import time import requests def main(): session = requests.Session() session.headers.update({"User-Agent": "python-lab-day34/1.0"}) print("--- GET params ---") r = session.get( "https://httpbingo.org/get", params={"q": "python", "page": 1}, timeout=20, ) r.raise_for_status() data = r.json() print("status:", r.status_code) print("args:", data.get("args")) print("--- POST json ---") r2 = session.post( "https://httpbingo.org/post", json={"name": "bob", "role": "student"}, timeout=20, ) r2.raise_for_status() body = r2.json() print("echo:", body.get("json") or body.get("data")) print("--- timeout demo ---") try: session.get("https://httpbingo.org/delay/5", timeout=2) except requests.Timeout: print("Timeout:", "Timeout") print("--- retry pattern ---") for attempt in range(1, 4): resp = session.get("https://httpbingo.org/status/200", timeout=15) print(f"attempt {attempt}: status={resp.status_code}") if resp.ok: break time.sleep(0.3) key = os.environ.get("DEMO_API_KEY", "") print("--- auth header style ---") print("DEMO_API_KEY set:", bool(key)) headers = {} if key: headers["Authorization"] = f"Bearer {key}" r3 = session.get("https://httpbingo.org/headers", headers=headers, timeout=15) r3.raise_for_status() hdrs = r3.json().get("headers") or {} has_auth = any(k.lower() == "authorization" for k in hdrs) print("Authorization in request:", has_auth or bool(key)) if __name__ == "__main__": main() EOFpython3 requests_demo.py实测输出:
--- GET params --- status: 200 args: {'page': ['1'], 'q': ['python']} --- POST json --- echo: {'name': 'bob', 'role': 'student'} --- timeout demo --- Timeout: Timeout --- retry pattern --- attempt 1: status=200 --- auth header style --- DEMO_API_KEY set: False Authorization in request: False总结与学习路线
requests把 Day33 的 HTTP 五件套变成参数与 Response 属性。- timeout + 状态处理 + 参数绑定式传参 + 密钥不进库是客户端底线。
Session适合连续调用;异步场景不要硬塞同步requests。
建议学习路径:
- 基础— 会
get/post,能打印status_code与json - 操作— 分清
params/json/data/headers - 进阶—
raise_for_status、Timeout、有限重试、Session - 实战— 用环境变量做 Bearer;封装小组件
get_json(url)
思维拔高:好的客户端默认不信任网络——任何远程调用都可能慢、失败、返回怪数据。
下一课:并发模型怎么选(多个请求如何重叠等待)。
文档:Requests: HTTP for Humans
小练笔
题 1
为什么几乎总要设置timeout?
题 2
params={"a": 1}和json={"a": 1}区别?
题 3
判断:把 API Key 写进源码提交到 Git 是好习惯。
题 4
r.ok与r.raise_for_status()使用上的主要差别?
题 5
判断:r.json()会修改服务器上的数据。
小练笔参考答案
题 1
避免网络异常时程序无限等待。
题 2
params进入 URL 查询串;json进入请求 Body(JSON,并带合适 Content-Type)。
题 3
错。
题 4
ok只给布尔值由你分支;raise_for_status在 4xx/5xx 时直接抛HTTPError。
题 5
错。只是把响应正文解析成本地 Python 对象。