requests 快速上手:会话、超时与重试
requests 的价值不在于「能发请求」,标准库也能;它把连接复用、Cookie 管理、编码猜测、重定向这些琐事收进了一套稳定接口,是同步阻塞但足够省心的选择。但它的默认值对采集并不安全:超时默认永不超时,会话默认不复用,网络抖动默认不重试。本章按「一次请求的最低配置」把这三件事补齐。
安装与最低起点
python -m pip install requests
requests 是同步阻塞库,调试方便、生态成熟,绝大多数采集任务够用;只有在确认瓶颈是网络等待、且目标站点允许的前提下,才值得换 httpx 之类的异步方案。
get 的关键参数
| 参数 | 作用 | 实践建议 |
|---|---|---|
params | 拼到 URL 的查询参数 | 传字典让它做 URL 编码,不要手工拼字符串 |
headers | 请求头 | 至少给出可识别的 User-Agent,注明用途与联系方式 |
timeout | 超时,秒或 (连接, 读取) 元组 | 每个请求都必须显式设置 |
allow_redirects | 是否自动跟随重定向 | 排查登录态时设为 False,直接看 302 的 Location |
verify | 是否校验证书 | 保持 True,不要为了方便关掉证书校验 |
import requests
resp = requests.get(
"https://example.com/api/list",
params={"page": 1, "size": 20}, # 自动 URL 编码,中文参数也安全
headers={"User-Agent": "Mozilla/5.0 (compatible; MyCollector/1.0)"},
timeout=(3, 10), # 连接 3 秒、读取 10 秒,必须显式写
allow_redirects=True, # 默认 True;排查登录态时改成 False
)
print(resp.status_code, resp.url) # 最终 URL 能看出参数编码与重定向结果
timeout=(3, 10) 到底管什么
| 写法 | 含义 | 结论 |
|---|---|---|
| 不写 | 永不超时,卡住就一直等 | 永远不要这样写 |
timeout=10 | 连接与读取各最多 10 秒 | 可以,但两类超时混在一起难定位 |
timeout=(3, 10) | 建连最多 3 秒,连上后读取最多 10 秒 | 推荐的默认写法 |
不写超时的后果不是「慢」,而是脚本可能永久挂死在半开连接上:任务既没完成也没报错,监控里看不到任何异常。采集量一大,这类挂死是最难排查的问题之一,成本远高于多敲几个字符。
Session:复用连接与 Cookie
| 用法 | 连接复用 | Cookie 保持 | 适用场景 |
|---|---|---|---|
requests.get() | 否 | 否 | 一次性请求、接口探活 |
requests.Session() | 是 | 是 | 同站点批量采集、需要登录态 |
Session + HTTPAdapter | 是 | 是 | 还需要自动重试与连接池上限 |
状态码判断与重试
raise_for_status() 在 4xx/5xx 时抛出 HTTPError,比手写 if resp.status_code != 200 更难漏判;注意它不把 3xx 当错误,判断依据是重定向后的最终状态。重试必须带退避,否则等于用更高频率继续撞墙。
| 参数 | 含义 | 建议值 |
|---|---|---|
total | 总重试次数 | 3,够覆盖抖动,再多就是给对方添压力 |
backoff_factor | 退避系数,等待 factor * 2^(n-1) 秒 | 0.5 ~ 1 |
status_forcelist | 哪些状态码触发重试 | [429, 500, 502, 503, 504] |
allowed_methods | 允许重试的方法 | ["GET", "HEAD"],写方法不要重试 |
异常分类与处理姿势
| 异常 | 触发条件 | 处理姿势 |
|---|---|---|
ConnectTimeout | 建连阶段超时 | 目标不可达或网络抖动,退避后重试 |
ReadTimeout | 已连接但读取响应超时 | 服务端处理慢,可重试但要降低频率 |
Timeout | 上面两者的父类 | 兜底捕获,日志里记下具体子类 |
ConnectionError | DNS 失败、连接被重置 | 有限次重试,仍失败则停止并告警 |
TooManyRedirects | 重定向次数超过上限 | 多为登录跳转循环,检查会话与 Location |
HTTPError | raise_for_status() 抛出 | 4xx 说明请求本身不对,别盲目重试 |
捕获顺序要先具体后宽泛:ConnectTimeout、ReadTimeout 写在 Timeout 前面,Timeout 写在 RequestException 前面,否则永远进不了细分分支。
可直接运行的完整脚本
"""带会话复用、显式超时、自动重试与频率控制的采集骨架。"""
import random
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
BASE_URL = "https://example.com"
UA = "Mozilla/5.0 (compatible; MyCollector/1.0; +https://example.com/bot)"
MIN_INTERVAL = 1.0 # 两次请求之间的最小间隔,合规要求,不要调成 0
def build_session() -> requests.Session:
"""会话挂上重试策略:只重试安全方法,并尊重 Retry-After。"""
retry = Retry(total=3, backoff_factor=0.5, respect_retry_after_header=True,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "HEAD"]) # 写方法一律不重试
session = requests.Session()
session.headers.update({"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"})
session.mount("https://", HTTPAdapter(max_retries=retry, pool_connections=10,
pool_maxsize=10))
return session
def main() -> None:
with build_session() as session:
for page in range(1, 6):
url = f"{BASE_URL}/api/list"
try:
resp = session.get(url, params={"page": page, "size": 20}, timeout=(3, 10))
resp.raise_for_status() # 4xx/5xx 抛 HTTPError
items = resp.json().get("items", [])
print(f"第 {page} 页拿到 {len(items)} 条")
except requests.exceptions.Timeout:
print(f"请求超时:{url}")
except requests.exceptions.HTTPError as exc:
print(f"状态码异常 {exc.response.status_code}:{url}")
except requests.exceptions.ConnectionError:
print(f"连接失败,检查网络或降低并发:{url}")
break # 网络已经断了,别继续
time.sleep(MIN_INTERVAL + random.uniform(0, 0.5)) # 固定间隔加随机抖动
if __name__ == "__main__":
main()
常见坑与要点对照
| 现象 | 常见原因 | 处理要点 |
|---|---|---|
| 脚本莫名挂死 | 没有设置超时 | 每个请求写 timeout=(3, 10) |
| 反复 429 | 重试不带退避、并发过高 | 设 backoff_factor,尊重 Retry-After |
| 采集几百页后变慢 | 每次新建会话、反复握手 | 复用 Session,必要时调整连接池上限 |
| 重试后重复写入数据 | 把写方法也放进了重试列表 | allowed_methods 只留 GET 与 HEAD |
并发不是越高越好:重试加多线程在对方看来可能接近攻击,先测出对方能承受的速率,再决定线程数与间隔。合规上同样如此:确认 robots.txt 与服务条款允许该路径,保持请求间隔,不采集个人隐私与受版权保护的付费内容,不绕过登录、付费墙、验证码等访问控制——重试与并发都建立在对方允许的前提之上。
小结:每个请求显式写 timeout=(3, 10),批量采集用 Session 复用连接与 Cookie,网络抖动交给 HTTPAdapter 加退避重试,异常按具体到宽泛逐层捕获,并在每次请求之间保留合规的间隔。