ssl-bad-ecpoint-经验帖

Python requests 遇到 BAD_ECPOINT 的终极解决方案

一句话总结

SSLError(1, '[SSL: BAD_ECPOINT] bad ecpoint') 发生在 TLS 握手层 ,与证书验证无关,verify=False 无效。根因是目标服务器使用 ECDH 压缩曲线,OpenSSL 1.1+ 拒绝握手。Windows schannel 无此限制所以本地正常。最终方案:用 curl_cffi 替代 requests,绕过 Python _ssl 模块。

问题场景

湖南生态环境厅网站 sthjt.hunan.gov.cn,在 Docker 容器(python:3.12-slim-bullseye,OpenSSL 1.1)中请求时直接报错:

复制代码
requests.exceptions.SSLError: HTTPSConnectionPool(host='sthjt.hunan.gov.cn', port=443):
Max retries exceeded (Caused by SSLError(SSLError(1, '[SSL: BAD_ECPOINT] bad ecpoint (_ssl.c:1000)')))

Windows 本地运行完全正常

根因分析

为什么 verify=False 无效?

SSL/TLS 握手分为两层:

复制代码
┌─────────────────────────────────┐
│  证书验证层 (verify=True/False)   │  ← verify=False 只跳过这一层
├─────────────────────────────────┤
│  TLS 握手层 (密钥交换/加密套件)    │  ← BAD_ECPOINT 发生在这里
└─────────────────────────────────┘

BAD_ECPOINT 发生在 TLS 握手阶段的 ECDH 密钥交换环节------服务器返回了压缩格式的椭圆曲线点(compressed EC point),OpenSSL 1.1.0+ 出于安全考虑拒绝接受。这是握手层面的拒绝,比证书验证更底层。

为什么 Windows 正常,Linux 报错?

Python 的 SSL/TLS 能力来自 CPython 编译时链接的 SSL 后端:

平台 SSL 后端 对压缩 EC 点的态度
Windows schannel(系统自带) 容忍
Linux (OpenSSL 1.1+) OpenSSL 拒绝
Linux (OpenSSL 3.x) OpenSSL 拒绝
macOS Secure Transport 容忍

关键点 :Python 的 _ssl 模块是 C 扩展,在 CPython 编译时就链接好了,运行时无法替换apt install opensslpip install pyOpenSSL 都改变不了 _ssl 的行为。

尝试过的无效方案

按时间顺序,以下方案均失败

方案 结果 原因
verify_ssl = False 错误在握手层,不在证书层
_LegacySSLAdapter + SECLEVEL=1 安全级别与 EC 点压缩是正交问题
_LegacySSLAdapter + SECLEVEL=0 + OP_LEGACY_SERVER_CONNECT 同上,OpenSSL 1.1 根本不允许压缩 EC 点
降级到 python:3.12-slim-bullseye(OpenSSL 1.1) OpenSSL 1.1.0 开始就拒绝压缩 EC 点了
云端浏览器 API 代理 API 服务端自身崩溃(api_server.py 内部报错)

最终方案:curl_cffi

原理

curl_cffi 通过 CFFI 直接调用 libcurl,完全不经过 Python 的 _ssl 模块 。TLS 握手由 curl 自己的 SSL 后端处理,从根本上规避了 Python _ssl 的限制。

此外它还支持 TLS 指纹伪装(模拟 Chrome/Firefox/Safari 的 JA3 指纹),顺带解决基于 TLS 指纹的反爬。

安装

bash 复制代码
pip install curl_cffi

支持 Python 3.10+,有预编译的 manylinux/musllinux wheel,无需编译。

代码改动

改动量极小------curl_cffi 的 API 与 requests 完全兼容:

python 复制代码
# 之前
import requests
session = requests.Session()

# 之后
from curl_cffi import requests
session = requests.Session(impersonate="chrome124")  # 模拟 Chrome 124 TLS 指纹

完整代码(湖南 pipeline 实际改动):

python 复制代码
from curl_cffi import requests

class HunanPipeline(BasePipeline):
    province = "湖南"

    def _create_session(self) -> requests.Session:
        """使用 curl_cffi Session(Chrome 指纹 + libcurl TLS,绕过 BAD_ECPOINT)"""
        session = requests.Session(impersonate="chrome124")
        session.headers.update({
            "User-Agent": "Mozilla/5.0 ... Chrome/124.0.0.0 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "zh-CN,zh;q=0.9",
        })
        return session

    def collect(self, session, year_filter, first_page_only=False):
        # ... session.get(url) 用法完全不变
        resp = session.get(url, timeout=30)

    def process_item(self, session, item):
        # ... 同样不变
        resp = session.get(item["url"], timeout=30)

不需要 verify_ssl = False不需要 _LegacySSLAdapter不需要改 Dockerfile 基础镜像。

注意事项

  1. curl_cffi 的异常类继承自自己的 curl_cffi.requests.errors.RequestsError不是 标准库的 requests.RequestException。如果基类用 except requests.RequestException 做重试,需要改为同时捕获两者的基类,或用 except Exception 兜底。
  2. impersonate 参数可选的浏览器版本:chrome110chrome120chrome124firefoxsafari 等。国内政府网站建议用 chrome124
  3. 详情页的超时时间建议 > 30s(政府网站响应可能较慢)。

推广适用场景

这个方案不仅适用于 BAD_ECPOINT,同样的思路可以解决其他 Python _ssl 层面的问题:

  • SSLError: unsupported protocol --- 服务器仅支持老旧 TLS 版本
  • SSLError: dh key too small --- DH 密钥长度不足
  • SSLError: no shared cipher --- 加密套件不兼容
  • TLS 指纹反爬(JA3 检测)

只要问题根因在 Python _ssl 模块层面,且无法通过升级/配置 OpenSSL 解决,就可以考虑用 curl_cffi 绕开。

参考

相关推荐
STQY燊桐启元(深圳)电子科技6 小时前
医疗精密电源散热方案:相变陶瓷片保障医疗设备长效稳定低干扰
大数据·网络·人工智能
CDN3608 小时前
节点智能调度实操:解决出海区域访问快慢不均、节点拥堵、跨洋延迟高,CDN 全网择优落地方案
运维·服务器·网络
roman_日积跬步-终至千里9 小时前
【数据治理(6)】DataOps 不是调度工具,而是让数据产品长期可信的运行机制
java·服务器·网络
可乐鸡翅yeah_11 小时前
个人 / 外包流媒体项目交付实战,M3U8 验收与问题闭环完整工作流
运维·网络·python·低代码·django·m3u8·在线播放
黄俊懿12 小时前
【架构师从入门到进阶】第五章:DNS&CDN&网关优化思路——第十节:网关安全-单向加密
网络·数据库·计算机网络·安全·架构·系统架构·架构师
Patrick在香港12 小时前
Python 拉取城巴开放数据:406 条路线,香港岛巴士网络的数字画像
网络·python·数据分析·php·数据可视化·香港·开放数据
一直C13 小时前
Linux系统编程|进程间通信IPC全套详解(1)(管道、信号)
linux·c语言·开发语言·网络·青少年编程·visual studio code
Experience-摆渡13 小时前
自建 IP 轮换方案实战:健康探测 + 熔断剔除 + 粘性会话
网络·网络协议·tcp/ip
luj_176813 小时前
合法避税与投资评估实战指南
c语言·开发语言·网络·经验分享·算法
库玛西13 小时前
Linux网络编程:HTTP/HTTPS协议核心技术全解析
linux·运维·服务器·网络·c++·http·https