Python requests库send方法常见问题:如何解决SSL证书验证失败?

一、问题现象与背景

当开发者使用requests.send()或相关方法发起HTTPS请求时,常会遇到类似以下的报错:

requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): 
Max retries exceeded with url: /api (Caused by SSLError(SSLCertVerificationError(1, 
'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local 
issuer certificate (_ssl.c:1123)')))

这种错误在访问自签名证书网站、过期证书或配置不规范的服务器时尤为常见。根据Cloudflare统计,约15%的API请求故障源于SSL/TLS证书问题。

二、根本原因分析

  • 证书链不完整:服务器未提供完整的中间证书链
  • CA根证书过期:如2020年Let's Encrypt根证书过期事件
  • 主机名不匹配:证书Subject Alternative Name(SAN)与请求域名不符
  • 系统证书库缺失:Python使用的OpenSSL无法找到信任锚
  • 时间不同步:客户端系统时间与证书有效期不匹配

三、六种解决方案对比

方法代码示例安全性适用场景
禁用验证(不推荐)
requests.get(url, verify=False)
❌ 高风险 临时测试环境
自定义CA包
requests.get(url, verify='/path/to/cacert.pem')
✅ 安全 企业内网环境
更新证书库
pip install certifi
✅ 安全 证书库过时
添加证书到系统
export SSL_CERT_FILE=/path/to/cert
✅ 安全 Linux服务器
使用会话配置
session = requests.Session()
session.verify = False
❌ 高风险 批量请求场景
证书捆绑方案
openssl s_client -showcerts -connect host:port
✅ 安全 自签名证书

四、生产环境最佳实践

对于关键业务系统,建议采用分层验证策略

  1. 使用certifi.where()定位当前证书库路径
  2. 通过openssl命令导出服务器证书链:
    openssl s_client -connect example.com:443 -showcerts < /dev/null
  3. 将证书合并到PEM文件,包含:
    - 服务器证书
    - 中间证书
    - 根证书
  4. 在代码中指定验证路径:
    DEFAULT_CA_BUNDLE = os.path.join(os.path.dirname(__file__), 'custom_ca.pem')
    requests.get(url, verify=DEFAULT_CA_BUNDLE)

五、进阶调试技巧

当标准解决方案无效时,可尝试:

  • 使用urllib3的详细日志:
    import logging
    logging.basicConfig(level=logging.DEBUG)
  • 检查证书有效期:
    openssl x509 -in certificate.crt -noout -dates
  • 验证OCSP装订状态:
    openssl s_client -connect example.com:443 -status

根据Mozilla TLS Observatory数据,正确配置的证书验证可将中间人攻击风险降低92%,因此切勿长期使用verify=False方案。