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”的网站。

“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的限制。Zillow和Leboncoin通过住宅代理的普通请求提供了真实页面。Indeed和Glassdoor仍然以JavaScript挑战回应。你的笔记本电脑可以在更严格的网站上遇到同样的墙。它只是遇到的频率较低。

为什么Python爬虫在笔记本电脑上通过Cloudflare,而在云服务器上得到Just a moment挑战:家庭IP与托管IP,相同的Python TLS指纹,两者都没有JavaScript引擎
相同的脚本,两台机器。IP改变了;Python TLS握手和缺失的JavaScript引擎没有改变。

为什么cloudscraper时代的技巧不再有效

cloudscraper是为Cloudflare的旧版“I'm Under Attack”页面构建的。该页面主要检查客户端是否可以运行JavaScript计算,因此库获取挑战,在嵌入的解释器中解决脚本,发送答案并保留cookie,所有这些都在requests之上。很长一段时间这就足够了,这也是“cloudscraper替代方案”现在成为人们搜索的原因。

自那时以来,发生了几件事情,它们不依赖于任何一个库版本:

截至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指纹就足够了,无需浏览器。

通过Web Unlocker绕过JavaScript挑战

重用清除:为什么第二次请求很快

浏览器升级是慢的部分,你不想在每个页面上为此付费。当无头浏览器通过挑战时,它获得了一个清除。发送相同的x-qp-session值,或在代理用户名中保持相同的-session-<id>,在后续请求中它们从相同的出口离开,重用该清除而不是再次运行浏览器。x-qp-clearance头告诉你每次调用发生了什么:minted表示获得了新的清除,hit表示重用了存储的清除,miss表示没有可重用的。

在2026年9月29日的Glassdoor测试中,使用固定在代理用户名中的会话和两个单独的连接,第一次请求通过浏览器在14.0秒内完成(minted),第二次在1.4秒内返回(hit)。固定很重要,因为Cloudflare如何绑定清除:它与获得它的访客和设备绑定,并在网站配置的持续时间内保持有效,除非Cloudflare决定会话看起来可疑。改变出口或指纹,你又是一个新访客。

在一个固定的解锁器会话中的两个请求的时间线:第一个升级到无头浏览器并在14.0秒内铸造Cloudflare清除,第二个重用它并在1.4秒内返回
第一次请求为浏览器付费;同一固定会话中的后续请求重用清除。

当普通请求在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发送,并固定一个会话,这样你就只为那个浏览器付费一次,而不是每个页面。

在住宅出口上试用Web Unlocker