Python中的Cloudflare“Just a Moment”:为什么requests会失败
Cloudflare的“Just a moment...”页面是一个JavaScript挑战,而Python requests无法运行JavaScript。以下是如何检测它,为什么它在服务器上比在笔记本电脑上更容易触发,为什么cloudscraper风格的技巧不再足够,以及我们在2026年9月29日测试中通过了什么。
“Just a moment...”是Cloudflare的挑战页面。它要求客户端运行JavaScript并证明它是一个浏览器,然后网站才会响应。Python requests无法运行JavaScript,并且从数据中心服务器发出的请求,其IP和TLS握手已经看起来是自动化的。通过的方法是使用干净IP的真实浏览器,或者运行一个web unlocker。
本指南展示了如何在Python中检测挑战,为什么相同的脚本在你的笔记本电脑上通过而在你的服务器上失败,为什么cloudscraper时代的技巧不再足够,以及我们在2026年9月29日测试中实际通过了什么。这包括我们尝试的所有方法都无法通过“Just a moment”的网站。
- 这是一个挑战,不是错误。 Cloudflare将被挑战的响应标记为
cf-mitigated: challenge头。在我们的测试中,它们以HTTP 403到达。 - 头信息无法解决。 'Enable JavaScript and cookies to continue' 意味着页面需要一个JavaScript引擎。没有User-Agent字符串能提供一个。
- 服务器与笔记本电脑主要是IP声誉加上TLS指纹。在我们的测试中,仅靠住宅IP无法通过Indeed或Glassdoor。
- 通过的方法: Web Unlocker通过了Glassdoor(在无头浏览器后,17.2秒)和Indeed(首次尝试,2.6秒)。
- 固定一个会话。 重用浏览器的清除将第二次Glassdoor请求从14.0秒减少到1.4秒。
“Just a moment...”页面实际上是什么
Cloudflare称其为插页式挑战页面:在Cloudflare决定访问者是否为自动化时,访客与网站之间的全页面门。其挑战页面文档描述了三种类型。非交互式挑战只需要浏览器运行注入的JavaScript。托管挑战让Cloudflare从请求的信号中选择挑战类型。交互式挑战要求访客做一些事情。当检查通过时,原始请求继续到网站,浏览器获得一个cf_clearance cookie,使其可以暂时跳过门。
Python HTTP客户端从未到达那一步。它下载挑战页面,没有引擎来运行脚本,并将门的HTML交给你,而不是你请求的页面。
为什么挑战返回403,以及如何检测它
在我们的测试中,每个挑战页面都以状态403到达,这就是为什么raise_for_status()抛出403 Client Error: Forbidden,人们搜索'cloudflare just a moment 403'。状态本身无法区分挑战和其他拒绝。可靠的信号是一个头信息:Cloudflare文档说明被挑战的响应带有cf-mitigated: challenge,无论你请求的资源是什么。首先检查这个,然后作为备用检查标题:
import re
import requests
URL = "https://www.glassdoor.com/Reviews/Google-Reviews-E9079.htm"
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
def title_of(html: str) -> str:
m = re.search(r"<title[^>]*>(.*?)</title>", html, re.I | re.S)
return m.group(1).strip() if m else ""
def is_cloudflare_challenge(r: requests.Response) -> bool:
if r.headers.get("cf-mitigated") == "challenge": # documented by Cloudflare
return True
if "just a moment" in title_of(r.text).lower():
return True
return "enable javascript and cookies to continue" in r.text[:20000].lower()
r = requests.get(URL, headers=HEADERS, timeout=30)
print(r.status_code, is_cloudflare_challenge(r), title_of(r.text))
# 29 Sep 2026, plain request through a residential proxy (US exit):
# 403, body asking to 'Enable JavaScript and cookies to continue'
不要用相同的客户端在紧密循环中重试被挑战的请求。每次重试都会获取相同的门,一些网站会对重复失败以更严格的规则回应。这就是爬虫陷入“Just a moment”循环的原因。
“Enable JavaScript and cookies to continue”和“your browser does not support JavaScript”
这些字符串是挑战页面的备用文本,显示给不执行其脚本的客户端。当Python requests显示其中一个时,网站并不是在抱怨你的头信息。它是在字面上告诉你,它需要一个JavaScript引擎和一个cookie jar来保存脚本设置的内容。
这正是我们从Indeed和Glassdoor的普通住宅运行中收到的:一个403,带有'Enable JavaScript and cookies to continue',来自一个干净的家庭IP。添加一个Chrome User-Agent、Accept-Language头或Referer没有任何改变,因为页面并不是在读取它们来决定。它在等待脚本运行。
为什么你的爬虫在本地工作,但在服务器上被Cloudflare阻止
这是最常见的抱怨版本:脚本在笔记本电脑上工作,然后一旦在云VM上运行就得到“Just a moment”。这两台机器之间有两件事改变。
- IP。在你的笔记本电脑上,脚本从家庭ISP地址离开。在VM上,它从托管提供商的范围离开,机器人防御在读取任何其他内容之前就将其评分为可能是自动化的。我们的法兰克福数据中心服务器被Glassdoor、Indeed、Zillow和Leboncoin等拒绝。
- TLS指纹。 Python的握手在两台机器上看起来像Python,但在家庭IP上网站可能会容忍它,而在托管IP上则很少会。我们关于curl_cffi vs requests的解释显示了库控制的握手有多少。
我们测试的住宅列显示了仅修复IP的限制。Zillow和Leboncoin通过住宅代理的普通请求提供了真实页面。Indeed和Glassdoor仍然以JavaScript挑战回应。你的笔记本电脑可以在更严格的网站上遇到同样的墙。它只是遇到的频率较低。

为什么cloudscraper时代的技巧不再有效
cloudscraper是为Cloudflare的旧版“I'm Under Attack”页面构建的。该页面主要检查客户端是否可以运行JavaScript计算,因此库获取挑战,在嵌入的解释器中解决脚本,发送答案并保留cookie,所有这些都在requests之上。很长一段时间这就足够了,这也是“cloudscraper替代方案”现在成为人们搜索的原因。
自那时以来,发生了几件事情,它们不依赖于任何一个库版本:
- 握手仍然显示Python。基于requests构建的库通过Python的TLS堆栈连接,因此ClientHello与其User-Agent声称的浏览器不匹配。这种不匹配在任何JavaScript运行之前就可见。
- 挑战不再只是算术。托管挑战从请求的信号中选择检查,Cloudflare的JavaScript检测读取浏览器环境。评估脚本的解释器不是渲染页面的浏览器。
- 清除与客户端绑定。 Cloudflare的清除文档表示cf_clearance cookie与其颁发给的访客和设备绑定,以防止跨机器重用。从你的浏览器复制一个到另一台机器上的脚本是设计上会失败的。
- IP仍然是你的。解决页面不会改变托管范围的IP,有些网站在提供任何挑战之前就拒绝这些。
截至2026年9月29日,cloudscraper库的最后一次提交是在2025年6月。这并不是对任何特定网站的判决,我们没有为本文对其进行基准测试。这确实意味着你应该针对自己的目标测试它,而不是假设它有效。每种替代方案都解决了部分问题。curl_cffi再现了浏览器TLS握手,但不运行JavaScript。Playwright或其他真实浏览器运行挑战,但仍需要一个干净的IP,并且在大规模操作时较重。解锁器结合了两者,并在两者都不起作用时告诉你。
通过的方法:web unlocker路径
QuantumProxies.io Web Unlocker分阶段处理GET。首先,最多三次TLS尝试,每次在不同浏览器指纹下的新鲜住宅出口上。如果每次尝试都被挑战,它会升级到运行挑战的无头浏览器。如果页面在此之后仍被阻止,你会收到一个错误状态,带有x-qp-unlocker-blocked和一个阻止类,而不是一个伪装成200的挑战页面。在Python中,它是一个代理设置:
import os
import time
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
user = os.environ["QP_UNLOCK_USER"] + "-country-us" # unlock-XXXXXXXX-country-us
PROXY = f"http://{user}:{os.environ['QP_UNLOCK_PASSWORD']}@unlock.quantumproxies.io:9000"
s = requests.Session()
s.proxies = {"http": PROXY, "https": PROXY}
s.verify = False # or the unlocker CA file from /api/v1/scraper/unlock/ca
s.headers["x-qp-session"] = "glassdoor-01" # same value on every call: same exit, reusable clearance
for attempt in (1, 2): # same page twice here; in practice, the next page
t0 = time.monotonic()
r = s.get(URL, timeout=120)
print(attempt, r.status_code, f"{time.monotonic() - t0:.1f}s",
"rendered:", r.headers.get("x-qp-unlocker-rendered"),
"clearance:", r.headers.get("x-qp-clearance"),
"block:", r.headers.get("x-qp-block-class"))
x-qp-*头由解锁器读取,从不转发到网站。如果你宁愿完全不处理证书,REST端点POST https://app.quantumproxies.io/api/v1/scraper/unlock在JSON体中接收URL和你的API密钥,并在响应中返回blocked、blockClass和vendor。我们的web unlocker API教程介绍了这两种方法。
已测量:通过的两个Cloudflare风格的墙
测量于2026年9月29日。普通请求是带有Chrome User-Agent的curl,从法兰克福数据中心服务器和通过美国出口的住宅代理发出。解锁器使用美国出口的住宅层。时间是一个请求的挂钟时间。
| 网站 | 普通,数据中心服务器 | 普通,住宅代理 | Web Unlocker |
|---|---|---|---|
| Glassdoor(公司评论) | 403,“安全”页面 | 403,“启用JavaScript和cookie以继续” | 200,2次尝试+无头浏览器,17.2秒 |
| Indeed(工作搜索) | 403,“安全检查”页面 | 403,“启用JavaScript和cookie以继续” | 200,1次尝试,无浏览器,2.6秒 |
Glassdoor是完整路径。两次TLS尝试被挑战,然后无头浏览器运行挑战并返回评论页面。Indeed是有趣的:普通住宅请求收到的挑战在解锁器的第一次尝试中从未出现。一个新鲜住宅出口上的浏览器TLS指纹就足够了,无需浏览器。
重用清除:为什么第二次请求很快
浏览器升级是慢的部分,你不想在每个页面上为此付费。当无头浏览器通过挑战时,它获得了一个清除。发送相同的x-qp-session值,或在代理用户名中保持相同的-session-<id>,在后续请求中它们从相同的出口离开,重用该清除而不是再次运行浏览器。x-qp-clearance头告诉你每次调用发生了什么:minted表示获得了新的清除,hit表示重用了存储的清除,miss表示没有可重用的。
在2026年9月29日的Glassdoor测试中,使用固定在代理用户名中的会话和两个单独的连接,第一次请求通过浏览器在14.0秒内完成(minted),第二次在1.4秒内返回(hit)。固定很重要,因为Cloudflare如何绑定清除:它与获得它的访客和设备绑定,并在网站配置的持续时间内保持有效,除非Cloudflare决定会话看起来可疑。改变出口或指纹,你又是一个新访客。

当普通请求在Cloudflare网站上仍然有效时
许多网站仅作为CDN运行在Cloudflare后面,或者具有允许普通流量通过的机器人规则,普通请求在它们上面有效。仅仅因为“在Cloudflare上”并不是使用浏览器的理由。信号是响应。如果没有cf-mitigated: challenge头,没有“Just a moment”标题,也没有403,请保持普通请求,并仅升级那些实际被挑战的URL。我们的教程中的plain-first助手正是这样做的。
两个相邻的问题有自己的指南。一个显示“Error 1020 Access denied”的页面是一个拒绝你的防火墙规则,而不是一个挑战,没有多少JavaScript可以修复它;请参阅是什么触发了Cloudflare错误1020以及如何修复它。对于Python之外的Cloudflare防御的更广泛图景,请阅读如何在2026年绕过Cloudflare挑战。如果墙后面的页面是Glassdoor,我们的Glassdoor薪资和评论爬取指南涵盖了数据方面。
常见问题
为什么我的爬虫在本地工作,但在云中被Cloudflare阻止?
因为IP改变了。在家里,你的脚本从住宅ISP地址离开;在云VM上,它从托管范围离开,机器人防御对其一见面就不信任。Python TLS握手在两者上是相同的,在更严格的网站上,即使从家里也会受到挑战。在我们的测试中,仅靠住宅IP无法通过Indeed或Glassdoor。
为什么Cloudflare的“Just a moment”页面返回403?
挑战页面是代替你请求的页面提供的,在我们的测试中,它带有状态403,因此requests会抛出“403 Client Error: Forbidden”。状态并不是特定于挑战。检查cf-mitigated响应头,Cloudflare文档说明在每个挑战页面响应上设置为challenge,或“Just a moment”标题。
cloudscraper仍然有效吗,有什么好的替代方案?
它可能仍然在设置宽松的网站上有效,因此针对你的目标测试它。它运行在Python的TLS堆栈上,并在解释器中解决脚本,而当前的挑战也会查看握手和浏览器环境。替代方案:curl_cffi用于浏览器握手,真实浏览器用于JavaScript,或同时执行这两者的web unlocker。
Python requests能否独立通过Cloudflare挑战?
不能。requests不执行JavaScript,因此无法完成依赖于运行页面脚本的挑战。它可以从没有挑战你的Cloudflare网站获取页面。一旦cf-mitigated: challenge出现,你需要一个真实的浏览器,直接或通过运行一个的服务,理想情况下还有一个干净的住宅IP。
Cloudflare清除持续多长时间?
只要网站配置的时间。Cloudflare称该设置为Challenge Passage,如果Cloudflare决定会话看起来可疑,清除可能会提前结束。cf_clearance cookie与获得它的访客和设备绑定,因此只有在后续请求保持相同的出口和指纹时才有帮助,这就是固定x-qp-session的作用。
简短版本:“Just a moment...”要求一个浏览器,因此requests中的头信息技巧无法满足它。用cf-mitigated检测它,保持普通请求用于所有未被挑战的内容,将被挑战的URL通过运行真实浏览器的东西从干净IP发送,并固定一个会话,这样你就只为那个浏览器付费一次,而不是每个页面。