Python requests库高级用法:Session管理与连接池优化

2 阅读3分钟

摘要: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连接池数量,对应不同的host
  • pool_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管理做好了,稳定性和效率都会有质的提升。