跳转到主要内容
Guide

如何修复 Cloudflare 403 Forbidden Python Requests 报错

2026-06-058 分钟阅读

如果你曾把浏览器里的网址复制到 Python 脚本中,却收到一整页写着 Access Denied 的 HTML,那你就遇到了 cloudflare 403 forbidden python requests 这个问题。同一个页面在 Chrome 里加载完美,但只要 requests、httpx 或 aiohttp 一访问,Cloudflare 就返回 HTTP 403 Forbidden——有时还伴随着令人头疼的 error 1020 access denied。感觉就像服务器在专门针对你的代码,某种意义上确实如此。

好消息是:这并不是随机发生的,而且大多数情况下你不需要一整套无头浏览器就能修复。本指南会逐步讲清楚:为什么 Cloudflare 拦截普通的 Python 客户端却放行浏览器、你可以按工作量从小到大尝试的手动修复方法、每种方法在哪里会失效,以及当真正的 managed challenge 或 Turnstile 挡在面前时,自动化场景下最可靠的解决路径。

Cloudflare 403 到底意味着什么

来自 Cloudflare 保护站点的 403 Forbidden 很少意味着你的凭证错误或页面不存在。它的真正含义是:Cloudflare 的 bot 管理层检查了你的请求,判定它来自自动化程序而非真实浏览器,于是拒绝将其转发到源服务器。同样的逻辑也会产生 error 1020 access denied,这是 Cloudflare 在防火墙(WAF)规则拦截请求时使用的代码——在 Python requests 这类原始 HTTP 客户端中,它通常表现为一个普通的 403。

关键在于:Cloudflare 在你的代码还没说完话之前就已经做出了这个判断。早在它读取你的请求头或 URL 路径之前,它就已经根据你建立连接的方式给你打了分。这正是为什么仅仅设置一个聪明的 User-Agent,几乎从来都修不好顽固的 403。

为什么浏览器能通过而 Python requests 被拦截

Cloudflare 会在多个层面对你的客户端进行指纹识别,而默认的 Python 技术栈几乎会同时在大部分层面败下阵来:

  • TLS / JA3-JA4 指纹:在 HTTPS 握手过程中,你的客户端会发送一个 ClientHello,列出它支持的加密套件、TLS 扩展以及它们的确切顺序。Python 的 requests 基于 OpenSSL,它产生的指纹与 Chrome 的 BoringSSL 握手完全不同。Cloudflare 会对其做哈希(JA3/JA4)并与已知浏览器比对——不匹配就是即时 403,发生在读取任何请求头之前。
  • HTTP/2 指纹:真实浏览器使用 HTTP/2 时带有特征性的 SETTINGS 帧、窗口大小和伪头部顺序。大多数 Python 客户端默认使用 HTTP/1.1 或非浏览器的 HTTP/2 配置,这是又一个破绽。
  • 不一致的浏览器请求头:浏览器会发送一套连贯的请求头——User-Agent、sec-ch-ua、sec-ch-ua-platform、Accept、Accept-Language、Accept-Encoding——并按特定顺序排列。而 requests 只发送一份简短且明显的列表(如果你忘了修改,UA 里还会带着 python-requests)。
  • 不执行 JavaScript:managed 和 JS challenge 要求客户端运行 JavaScript 并返回结果。纯 HTTP 客户端做不到,因此永远拿不到证明它已通过验证的 cf_clearance cookie。
  • IP 信誉:来自云服务商的数据中心 IP 信任分很低。一个干净的住宅 IP 会提高你的成功率;一个被标记的数据中心 IP 段则会降低它。

手动修复 1:发送真实、完整的浏览器请求头

先从成本最低的修复开始。许多较轻量的 Cloudflare 配置只做基础的请求头与信誉检查,一套完整且一致的请求头就足以通过。复制真实 Chrome 请求所发送的请求头(可以在浏览器的 DevTools 网络面板中获取),并复用同一个 session 以便在请求之间保留 cookie。

保留 cookie 的重要性超出很多人的预期:如果你在请求之间丢弃 cookie,Cloudflare 会把每次调用都当作一个全新访客来评估,并每次都重新发起验证,这会大大增加触发 1020 的概率。

headers.py
import requests

session = requests.Session()
session.headers.update({
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/126.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "sec-ch-ua": '"Chromium";v="126", "Google Chrome";v="126", "Not.A/Brand";v="24"',
    "sec-ch-ua-platform": '"Windows"',
    "Upgrade-Insecure-Requests": "1",
})

resp = session.get("https://target-site.com")
print(resp.status_code)

手动修复 2:伪装浏览器的 TLS 指纹

如果完美的请求头仍然返回 403,那罪魁祸首就是你的 TLS/JA3 指纹。标准的 requests 和 httpx 库无法改变它,因为指纹来自底层的 OpenSSL 技术栈,而不是你的 Python 代码。解决办法是换用一个能复刻真实浏览器握手的客户端。

curl_cffi 是 2026 年的首选:它封装了 curl-impersonate,使用 BoringSSL,并复刻 Chrome 确切的 TLS 与 HTTP/2 指纹。tls-client 是另一个流行的替代方案(基于 Go)。两者都能攻破让 requests 直接碰壁的纯 TLS 防线。

impersonate.py
# pip install curl_cffi
from curl_cffi import requests as cffi

# impersonate a real Chrome TLS + HTTP/2 fingerprint
resp = cffi.get(
    "https://target-site.com",
    impersonate="chrome",
)
print(resp.status_code)
print(resp.text[:500])

手动修复 3:住宅代理(以及这一切在哪里失效)

如果请求头和 TLS 伪装都正确,但在大规模抓取时仍被拦截,那剩下的因素就是 IP 信誉。通过轮换的住宅或移动代理路由,可以把你被标记的数据中心 IP 替换成看起来像普通家庭用户的地址,从而提高你的信任分和成功率。

但这里有一道硬性上限:curl_cffi、tls-client 和住宅代理只能解决被动指纹识别这一层。一旦 Cloudflare 决定真正下发一个 managed challenge、JS challenge 或 Turnstile 组件,它们就都无能为力了。这些验证需要执行 JavaScript 来计算令牌并获取 cf_clearance cookie——而无论一个 HTTP 客户端把 Chrome 的握手伪装得多么逼真,它都无法运行那段 JavaScript。这正是大多数抓取管线卡住的地方:方案能用上几周,然后目标站点收紧配置,每个请求又重新 403 了。

可靠的修复:用 API 解决 challenge

一旦遇到真正的 challenge,可靠的做法就是把它交给一个能替你完成浏览器工作、并返回你的普通 session 所需凭证的服务。NSLSolver 的 Challenge 端点正是这样做的:你 POST 目标 URL(可选附带代理),它会清除 Cloudflare 的 managed/JS challenge,并返回 cf_clearance cookie 以及与之匹配的 user_agent。随后你把两者附加到自己的 requests session 上,就能以原生速度继续抓取——你的管线里不必运行任何无头浏览器。

这套契约很小且精确。向 https://api.nslsolver.com/solve 发送一个带 X-API-Key 的 POST 请求。响应中的 cookies 字段是一个以 cf_clearance 为键的字典,user agent 以 user_agent(蛇形命名)返回。关键一点:你在后续请求中发送的 user_agent 必须与绑定在 cf_clearance cookie 上的那个一致——Cloudflare 会把它们绑定在一起,所以请使用 API 返回的值,而不是你自己的。

nslsolver_challenge.py
import requests

API_KEY = "nsl_YOUR_API_KEY"
TARGET = "https://target-site.com"

# 1) Ask NSLSolver to clear the Cloudflare challenge
resp = requests.post(
    "https://api.nslsolver.com/solve",
    headers={"X-API-Key": API_KEY},
    json={
        "type": "challenge",
        "url": TARGET,
        "proxy": "http://user:pass@host:port",  # optional
    },
    timeout=120,
)
data = resp.json()

# 2) Reuse the cf_clearance cookie + matching user_agent
session = requests.Session()
session.cookies.update(data["cookies"])          # dict keyed by cf_clearance
session.headers["User-Agent"] = data["user_agent"]

# 3) Now your normal requests pass straight through
page = session.get(TARGET)
print(page.status_code)   # 200
print(page.text[:500])

什么时候用哪种方案

先尝试手动修复——它们免费且快速。先加上完整的浏览器请求头,如果还不够,就切换到 curl_cffi 来匹配 TLS 指纹,再叠加住宅代理。对于只做被动指纹识别的站点,这套组合往往就足够了,而且不花一分钱。

一旦面对真正的 managed challenge、Checking your browser 过渡页或 Turnstile 组件——也就是任何请求头或 TLS 技巧都无法取胜的场景——就切换到 NSLSolver 的 Challenge 端点。定价为按量付费,Challenge 每 1,000 次解算 $0.50(Turnstile 为 $0.40/1,000,Kasada 为 $1.50/1,000),成功率 99.9%,失败的解算不收费。新账户注册即送 100 次免费请求,无需信用卡、无需加密货币,因此你可以在花费任何费用之前,先确认它能否清除你的具体目标。

常见问题

为什么浏览器能正常打开同一个页面,而我的 Python requests 脚本却收到 403?

因为 Cloudflare 在 HTTP 层之下就对你的客户端进行指纹识别。requests 库使用 OpenSSL,它产生的 TLS/JA3 握手与任何真实浏览器都不匹配,因此 Cloudflare 甚至在读取你的请求头或 URL 之前就返回 403。浏览器则会自动通过这道指纹检查。

error 1020 access denied 和 403 Forbidden 是一回事吗?

它们密切相关。Error 1020 表示一条 Cloudflare 防火墙(WAF)规则拦截了你的请求,而且它几乎总是以 HTTP 403 状态返回。在 Python requests 这类原始客户端中,你通常只会看到 403 和 Access Denied 的 HTML 正文,而不是带样式的 1020 页面。

单靠 curl_cffi 或 tls-client 能绕过 Cloudflare 吗?

只有当防护是被动指纹识别时才行。curl_cffi 和 tls-client 修复的是 TLS/JA3 和 HTTP/2 指纹,这能搞定许多站点。但它们无法运行 JavaScript,因此无法通过 managed/JS challenge 或 Turnstile 组件——对于这些场景,你需要能执行浏览器步骤并返回 cf_clearance cookie 的工具。

解决 Cloudflare challenge 之后,怎样让 requests 持续可用?

把返回的 cookie 和 user agent 附加到一个持久的 session 上并复用它。使用 NSLSolver 时,调用 session.cookies.update(data["cookies"]) 并设置 session.headers["User-Agent"] = data["user_agent"]。user_agent 必须与绑定在 cf_clearance cookie 上的那个一致,因此请始终使用 API 返回的值。

别再和 Cloudflare 403 死磕

一次 API 调用即可清除 Cloudflare 的 managed challenge,拿回你的 Python session 所需的 cf_clearance cookie。注册即送 100 次免费请求——无需信用卡、无需加密货币。