088、requests 库深度使用:Session、适配器、重试机制与 SSL 证书处理

发布时间:2026/6/30 0:44:07
088、requests 库深度使用:Session、适配器、重试机制与 SSL 证书处理 088、requests 库深度使用Session、适配器、重试机制与 SSL 证书处理上周帮同事排查一个线上爬虫报错日志里全是ConnectionError和SSLError服务端那边说“我们证书没问题啊”结果折腾了两天发现是 requests 默认的重试策略太弱加上目标服务器用了自签名证书。这种坑我踩过不止一次今天把 requests 库几个容易翻车的深度用法掰开揉碎讲清楚。Session 对象别每次都新建连接很多人写爬虫喜欢这样importrequests resprequests.get(https://api.example.com/data)每次调用都会新建 TCP 连接、完成 SSL 握手频繁请求时性能惨不忍睹。更隐蔽的问题是——如果你需要维持 cookies 或自定义 headers每次都得手动传一遍。正确的做法是用 Sessionimportrequests sessionrequests.Session()session.headers.update({User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36,Accept:application/json})# 这里踩过坑Session 的 headers 是持久化的但如果你在单个请求里传了同名 header会覆盖 session 级别的resp1session.get(https://api.example.com/login,params{user:admin})resp2session.get(https://api.example.com/profile)# 自动携带 cookiesSession 底层维护了一个连接池urllib3 的 PoolManager默认最多保持 10 个连接。如果你并发请求量大记得调大这个值sessionrequests.Session()adapterrequests.adapters.HTTPAdapter(pool_connections20,pool_maxsize50)session.mount(https://,adapter)session.mount(http://,adapter)mount方法的作用是把适配器绑定到特定协议前缀上。这里https://和http://分别挂载别只挂一个否则另一个协议会走默认适配器。适配器Adapter定制你的 HTTP 行为适配器是 requests 里容易被忽略但极其强大的组件。它本质上是 urllib3 的封装层控制着连接池、重试、超时等底层行为。除了调连接池大小适配器还能干更骚的事——比如给特定域名单独配置超时fromrequests.adaptersimportHTTPAdapterclassTimeoutAdapter(HTTPAdapter):def__init__(self,timeoutNone,*args,**kwargs):self.timeouttimeoutsuper().__init__(*args,**kwargs)defsend(self,request,**kwargs):kwargs.setdefault(timeout,self.timeout)returnsuper().send(request,**kwargs)sessionrequests.Session()# 给内网 API 设置 30 秒超时外网 API 用默认session.mount(https://internal-api.company.com,TimeoutAdapter(timeout30))session.mount(https://api.github.com,TimeoutAdapter(timeout10))别这样写直接在requests.get()里传timeout参数那只是单次请求生效。用适配器可以全局控制维护起来省心得多。重试机制别让网络波动搞崩你的程序requests 默认不重试遇到网络错误直接抛异常。生产环境里这等于自杀——网络抖动、DNS 解析失败、服务端限流随便一个就能让脚本崩溃。正确做法是给适配器挂载重试策略fromrequests.adaptersimportHTTPAdapterfromurllib3.util.retryimportRetry retry_strategyRetry(total3,# 总重试次数包括第一次请求backoff_factor1,# 退避因子重试间隔 backoff_factor * (2 ** (重试次数 - 1))status_forcelist[429,500,502,503,504],# 哪些状态码触发重试allowed_methods[GET,POST,PUT],# 哪些 HTTP 方法允许重试raise_on_statusFalse# 别这样写设为 True 的话重试耗尽后还会抛异常但异常信息不友好)adapterHTTPAdapter(max_retriesretry_strategy)sessionrequests.Session()session.mount(https://,adapter)session.mount(http://,adapter)这里踩过坑backoff_factor的默认值是 0意味着重试间隔为 0 秒等于瞬间重试对缓解服务端压力毫无帮助。建议至少设为 1这样第一次重试等待 1 秒第二次 2 秒第三次 4 秒。status_forcelist里我加了 429Too Many Requests因为很多 API 限流后会返回这个状态码重试时配合退避策略能有效避免被封。SSL 证书处理自签名证书与证书验证SSL 错误是 requests 里最让人头疼的问题之一。常见场景自签名证书内网服务常用requests 默认会验证失败证书过期生产环境偶尔会遇到证书链不完整某些中间件配置不当忽略证书验证仅限测试环境resprequests.get(https://internal-service:8443,verifyFalse)# 别这样写生产环境绝对不要用 verifyFalse等于裸奔更安全的做法是捕获requests.packages.urllib3.exceptions.InsecureRequestWarning警告importurllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)# 但这样只是不显示警告安全性依然没保障使用自定义 CA 证书内网服务如果用了自签名证书可以把 CA 证书文件放在项目里resprequests.get(https://internal-service:8443,verify/path/to/ca-bundle.crt)如果证书是 PEM 格式的字符串可以这样importcertifiimportssl# 把自定义证书追加到 certifi 的默认证书包后面custom_caopen(/path/to/custom-ca.pem).read()withopen(certifi.where(),a)asf:f.write(custom_ca)# 之后所有请求都会信任这个 CAresprequests.get(https://internal-service:8443)这里踩过坑直接修改 certifi 的证书文件是全局生效的如果多个项目共用同一个 Python 环境可能会互相影响。建议用环境变量REQUESTS_CA_BUNDLE指定自定义证书路径importos os.environ[REQUESTS_CA_BUNDLE]/path/to/custom-ca-bundle.crt客户端证书认证双向 SSL有些高安全要求的服务需要客户端提供证书resprequests.get(https://secure-service:443,cert(/path/to/client.crt,/path/to/client.key),verify/path/to/ca-bundle.crt)cert参数可以传元组(证书文件, 私钥文件)也可以传单个文件路径如果证书和私钥合并在一起。实战组合一个健壮的 Session 封装把上面这些整合起来写一个生产可用的 Session 工厂importrequestsfromrequests.adaptersimportHTTPAdapterfromurllib3.util.retryimportRetryimportosdefcreate_robust_session(pool_connections20,pool_maxsize50,max_retries3,backoff_factor1,status_forcelistNone,timeout30,ca_bundleNone):ifstatus_forcelistisNone:status_forcelist[429,500,502,503,504]retry_strategyRetry(totalmax_retries,backoff_factorbackoff_factor,status_forceliststatus_forcelist,allowed_methods[GET,POST,PUT,DELETE],raise_on_statusFalse)adapterHTTPAdapter(pool_connectionspool_connections,pool_maxsizepool_maxsize,max_retriesretry_strategy)sessionrequests.Session()session.mount(https://,adapter)session.mount(http://,adapter)# 默认超时session.requestlambdamethod,url,**kwargs:(kwargs.setdefault(timeout,timeout),super(requests.Session,session).request(method,url,**kwargs))[1]# 自定义 CA 证书ifca_bundle:session.verifyca_bundleelifos.environ.get(REQUESTS_CA_BUNDLE):session.verifyos.environ[REQUESTS_CA_BUNDLE]returnsession# 使用示例sessioncreate_robust_session(pool_connections30,pool_maxsize100,max_retries5,backoff_factor2,timeout15)try:respsession.get(https://api.example.com/data)resp.raise_for_status()# 别忘记检查状态码exceptrequests.exceptions.RequestExceptionase:print(f请求失败:{e})个人经验性建议永远不要在生产环境用verifyFalse。如果遇到 SSL 错误先排查证书问题而不是跳过验证。我见过太多人图省事直接关验证结果被中间人攻击搞崩了系统。重试策略要配合业务场景。写操作POST/PUT重试要谨慎最好实现幂等性检查。读操作GET可以放心重试但注意不要无限重试设置total上限。连接池大小不是越大越好。调大pool_maxsize能提高并发能力但也会占用更多内存和文件描述符。Linux 系统默认ulimit -n是 1024别超过这个数。日志里记录 SSL 证书信息。调试 SSL 问题时用requests.get(..., verifyFalse)临时测试可以但记得在日志里打印证书指纹方便后续排查。用mount做精细化控制。不同 API 可能有不同的重试策略和超时要求别用一个 Session 打天下。给内网服务、外网 API、第三方服务分别挂载不同的适配器。最后说一句requests 库虽然简单但底层 urllib3 的能力远超你的想象。花时间理解 Session、适配器、重试机制这些概念比背一百个 API 参数更有价值。