requests.get() 报 connectionreseterror 是因高防 waf 在 tcp/tls 握手阶段主动 rst 连接,主因是 tls 指纹异常、ip 行为异常或 http/2 协商失败;需改用 tls-client 或 curl_cffi 模拟真实浏览器指纹,并确保 ip、dns、sni、host 四者一致。

为什么 requests.get() 会直接收到 ConnectionResetError
高防网站(如 Cloudflare、阿里云WAF、腾讯云EdgeOne)通常在 TCP 层或 TLS 握手阶段就拦截异常连接,而不是等 HTTP 请求发完再返回 403。所以你看到的 ConnectionResetError: [Errno 104] Connection reset by peer 往往发生在 requests.get() 底层 socket.connect() 或 SSL handshake 阶段,根本没走到发 GET / HTTP/1.1 这一步。
这意味着:单纯加 time.sleep() 或换 User-Agent 没用——对方还没让你“发出请求”,就已经把 TCP 连接 RST 掉了。
- 真实触发点常是 TLS 指纹异常(比如 Python 默认的 OpenSSL 版本、ciphers 顺序、ALPN 协议列表和主流浏览器明显不同)
- IP 初始连接行为异常(首次连接就带 Cookie、发多个 pipelined 请求、TLS SNI 域名和 Host 不一致)
- HTTP/2 主动协商失败(部分 WAF 会因不支持 h2 或 SETTINGS 帧异常直接断连)
用 tls-client 或 curl_cffi 绕过 TLS 指纹检测
Python 原生 requests 基于 urllib3 + OpenSSL,指纹固定、不可控。必须换底层能模拟真实浏览器 TLS 握手流程的库。
推荐两个生产可用方案:
-
tls-client:Go 写的 TLS 库封装,支持完整 Chrome 指纹(包括 JA3、ALPN、SNI、ciphers 顺序),通过python-tls-client包调用。启动快,适合高频轮询 -
curl_cffi:基于 libcurl + cffi,复用系统级 curl(可编译带 nghttp2),天然支持 HTTP/2 和真实浏览器 TLS 参数。对 Cloudflare 最稳,但进程初始化稍慢
示例(curl_cffi):
from curl_cffi import requests
<p>resp = requests.get(
"<a href="https://www.php.cn/link/b4f1013b4a92d53a20b4492ca202ff2b">https://www.php.cn/link/b4f1013b4a92d53a20b4492ca202ff2b</a>",
impersonate="chrome110", # 关键:指定浏览器指纹代号
headers={"Accept": "text/html,application/xhtml+xml"},
timeout=15,
)
</p>
注意:impersonate 参数不是字符串随意写,必须从 curl_cffi.requests.impersonate.BROWSER_MAP 里选,比如 "chrome110"、"firefox102"。填错会导致指纹生成失败,退化为默认 curl 行为,照样被重置。
连接前必须做的三件事:IP、DNS、SNI 同步
很多开发者只改 User-Agent 和 headers,却忽略更底层的网络一致性。WAF 会交叉校验以下三点:
- TCP 连接目标 IP 是否和 DNS 解析结果一致(防止 hosts 劫持或 CDN 回源 IP 直连)
- SNI 域名是否和 TLS Client Hello 中的 Server Name 一致
- HTTP Host header 是否和 SNI、原始 URL 域名完全匹配(大小写、端口、trailing slash 都算差异)
错误做法:requests.get("https://a.com", headers={"Host": "b.com"}) —— SNI 是 a.com,Host 是 b.com,WAF 立刻 RST。
正确做法(以 curl_cffi 为例):
# 必须确保三者统一
url = "https://shop.example.com"
resp = requests.get(
url,
impersonate="chrome110",
# 不手动设 Host header!由库自动根据 url 生成
# 如需自定义域名解析,用 resolve 参数(而非改 Host)
# resolve="shop.example.com:443:203.208.39.123"
)
如果要用自定义 DNS(比如走干净代理 IP),必须用 resolve 参数传入 IP,而不是靠 /etc/hosts 或修改 Host header——后者无法影响 TLS SNI。
请求频率不是越慢越好,而是要模拟人类会话节奏
单纯全局 time.sleep(3) 容易被识别为低频扫描器。WAF 更关注连接模式:
- 首次访问是否带有效 Referer(比如从搜索引擎结果页跳转)
- 是否在 1–2 秒内连续发多个资源请求(JS/CSS/图片)
- Session 是否维持 Cookie + TLS session resumption(避免每次握手都新建密钥)
curl_cffi 默认启用 TLS 会话复用,但需要复用同一个 requests.Session() 实例:
session = requests.Session(impersonate="chrome110")
session.get("https://site.com/login") # 首次握手,建立 TLS session
session.get("https://site.com/dashboard") # 复用 session ticket,更快且更像真人
Referer 可以伪造,但必须合理:https://www.google.com/search?q=site%3Asite.com 比空 Referer 或随机字符串更安全;后续请求的 Referer 应该是上一页 URL,而不是全写首页。
真正容易被忽略的是:WAF 对“新 IP + 首次连接 + 首页 GET”最敏感。如果必须批量采集,先用一个干净 IP 访问一次真实浏览器能过的登录页(哪怕只是截图),再切回爬虫——这个“暖流”动作能显著降低 RST 概率。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











