摘要:requests是Python最流行的HTTP库,但大多数人只用到了基础功能。本文深入讲解Session复用、连接池调优、重试机制、超时策略等高级技巧,帮你写出生产级的HTTP请求代码。
为什么要用Session?
很多人写爬虫或调接口,习惯直接 requests.get()。这样每次请求都会新建TCP连接,效率很低。
# ❌ 每次都新建连接
for url in urls:
resp = requests.get(url)
# ✅ 复用连接,性能提升3-5倍
session = requests.Session()
for url in urls:
resp = session.get(url)
Session的好处不只是连接复用,它还能自动管理Cookie、统一设置Header:
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
})
# 登录后Cookie自动保持
session.post('https://example.com/login', data={'user': 'xxx', 'pwd': 'xxx'})
# 后续请求自动带Cookie
resp = session.get('https://example.com/dashboard')
连接池调优
requests底层用的是urllib3的连接池。默认每个host最多保持10个连接,对于高并发场景不够用。
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
# 自定义连接池大小
adapter = HTTPAdapter(
pool_connections=20, # 连接池数量(不同host)
pool_maxsize=50, # 每个host最大连接数
pool_block=False, # 连接池满时不阻塞
)
session.mount('http://', adapter)
session.mount('https://', adapter)
参数说明:
pool_connections:缓存的urllib3连接池数量,对应不同的hostpool_maxsize:每个连接池中保持的最大连接数pool_block:为True时,连接池满了会阻塞等待;False则创建新连接(但不会被缓存)
自动重试机制
网络请求难免失败,手动写重试逻辑很繁琐。urllib3内置了Retry策略:
retry_strategy = Retry(
total=3, # 最多重试3次
backoff_factor=1, # 退避因子:1s, 2s, 4s
status_forcelist=[429, 500, 502, 503, 504], # 这些状态码触发重试
allowed_methods=["GET", "POST"], # 允许重试的方法
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.mount('http://', adapter)
session.mount('https://', adapter)
# 现在请求会自动重试
resp = session.get('https://api.example.com/data')
backoff_factor 的退避时间计算公式:{backoff_factor} * (2 ** ({重试次数} - 1))
所以 backoff_factor=1 时,重试间隔是:1s → 2s → 4s。
超时策略
永远不要发没有超时的请求,否则程序可能永远挂起:
# 基础用法:统一超时
resp = session.get(url, timeout=10)
# 进阶用法:分别设置连接超时和读取超时
resp = session.get(url, timeout=(3, 10))
# 3秒内必须建立连接,10秒内必须收到响应
# 最佳实践:封装一个带默认超时的Session
class TimeoutSession(requests.Session):
def __init__(self, timeout=(5, 30)):
super().__init__()
self.timeout = timeout
def request(self, method, url, **kwargs):
kwargs.setdefault('timeout', self.timeout)
return super().request(method, url, **kwargs)
生产级封装
把上面的技巧整合起来,封装一个生产可用的HTTP客户端:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session(
retries=3,
backoff=1,
pool_size=20,
timeout=(5, 30),
headers=None,
):
session = requests.Session()
retry = Retry(
total=retries,
backoff_factor=backoff,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE"],
)
adapter = HTTPAdapter(
max_retries=retry,
pool_connections=pool_size,
pool_maxsize=pool_size,
)
session.mount('http://', adapter)
session.mount('https://', adapter)
if headers:
session.headers.update(headers)
# 猴子补丁:默认超时
original_request = session.request
def request_with_timeout(*args, **kwargs):
kwargs.setdefault('timeout', timeout)
return original_request(*args, **kwargs)
session.request = request_with_timeout
return session
# 使用
client = create_session(headers={
'User-Agent': 'MyApp/1.0',
})
resp = client.get('https://api.example.com/data')
性能对比
我做了一个简单的基准测试,请求同一个API 100次:
| 方式 | 耗时 | 说明 |
|---|---|---|
| 直接requests.get() | 12.3s | 每次新建连接 |
| Session复用 | 3.8s | 连接复用 |
| Session + 连接池调优 | 3.2s | 更大的连接池 |
Session复用带来了约3倍的性能提升,这在爬虫和批量API调用场景下非常可观。
总结
- 永远用Session代替裸requests调用
- 根据并发量调整连接池大小
- 配置自动重试,处理瞬时故障
- 设置合理的超时,避免程序挂起
- 封装成工具函数,团队统一使用
这些技巧看起来简单,但在生产环境中能帮你避免很多坑。特别是爬虫场景,Session管理做好了,稳定性和效率都会有质的提升。