花拾录
← 返回知识库

抓某些站点报 CERTIFICATE_VERIFY_FAILED,请求根本发不出去

编程语言导入2026/09/220 阅读0 评论

采集脚本明明昨天还好好的,今天一跑就抛异常,而且异常发生在"请求发出去之前"——连接压根没建立。这篇文章讲 CERTIFICATE_VERIFY_FAILED 的原因,以及关闭证书校验时你必须知道的代价。

现象

抓某些站点时,直接抛出证书验证失败:

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

关键字是 CERTIFICATE_VERIFY_FAILED 和 unable to get local issuer certificate。请求根本发不出去——不是服务器返回了错误,而是 TLS 握手阶段就失败了,连 HTTP 请求都还没发出。

根因

原因是站点证书无效,或者证书链不完整,而客户端默认做严格校验。

TLS 握手时,客户端会验证三件事:证书是否由可信 CA 签发、证书链是否完整(能一路追溯到根证书)、域名是否与证书匹配。当服务端的证书链缺了中间证书(很常见——服务端只发了叶证书,没发中间证书),浏览器有时能靠缓存补上,但严格的客户端就会直接拒绝。

unable to get local issuer certificate 这句错误,字面意思就是"找不到签发这张证书的上级证书"——正是证书链不完整导致的。

解决

在采集脚本里显式使用不校验的 SSL 上下文:

import ssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager

class NoVerifyAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize, block=False):
        ctx = ssl.create_default_context()
        ctx.check_hostname = False          # 不校验域名
        ctx.verify_mode = ssl.CERT_NONE     # 不校验证书链
        self.poolmanager = PoolManager(
            num_pools=connections, maxsize=maxsize, ssl_context=ctx)

session = requests.Session()
session.mount("https://", NoVerifyAdapter())
resp = session.get(url, timeout=15)

如果只是验证单个请求,也可以直接用:

resp = requests.get(url, verify=False)   # 注意:会打 InsecureRequestWarning

但这里必须强调一句:只对明确可信的采集目标这么干,并且要清楚这是降低了安全性。

关闭证书校验意味着:你无法察觉中间人攻击、无法确认对端身份。在验证过的、你确定可信的目标上临时使用是可以的;把它当成全局默认配置,就是在给自己埋雷。

如果不想降低安全性,还有更稳妥的替代方案:手动把站点的根证书/中间证书加进信任链。

# 方案:把可信的 CA 证书文件传给 verify
resp = requests.get(url, verify="/path/to/trusted-ca-bundle.pem")

这样既能通过校验,又不放弃验证。

延伸与预防

CERTIFICATE_VERIFY_FAILED 的本质是"对端身份无法确认",解决它有三档策略,安全性依次是:

  1. 补全信任链(最安全)——把正确的 CA 证书加进信任库;
  2. 针对单个目标关闭校验(可控)——只对确认可信的目标,并在代码里注释清楚原因;
  3. 全局关闭校验(不推荐)——所有请求都不验证,等于给中间人敞开大门。

排查证书问题时,openssl 是最直接的取证工具,输出里能看到服务端实际发了几张证书、证书链是否完整、是否过期:

openssl s_client -connect <host>:443 -showcerts </dev/null
# 关注输出的 "Certificate chain" 段:
#   只有一张证书 -> 中间证书缺失,很可能就是它导致校验失败
#   verify error 编号 -> 直接告诉你失败的具体原因

如果确认是"服务端少发了中间证书",那么正确的处理优先级是:先联系站点方修(如果可控);其次把缺的中间证书加进本地信任链;只有在目标确实可信、又无法修的情况下,才针对单个请求关闭校验。

预防上,遇到证书错误先别急着 verify=False:先确认这个站点的证书是不是真的有问题(用 openssl s_client -connect host:443 看证书链),再决定用哪一档策略。还有一个容易忽略的点——固定的采集目标可以在脚本里保留一份"信任配置",哪些目标需要特殊处理、为什么,都写清楚,避免后人一看到报错就条件反射地全局关校验。关闭证书校验是一把双刃剑,务必把范围限制在可控目标上——这是这个坑最值得记住的一句话。

评论(0)

  • 还没有评论,来抢沙发~

相关文章