Cloudflare "Just a Moment" en Python : Pourquoi requests échoue

La page 'Just a moment...' de Cloudflare est un défi JavaScript, et Python requests ne peut pas exécuter JavaScript. Voici comment le détecter, pourquoi cela affecte plus les serveurs que les ordinateurs portables, pourquoi les astuces à la cloudscraper ne suffisent plus, et ce qui a fonctionné lors de notre test du 29 septembre 2026.

'Just a moment...' est la page de défi de Cloudflare. Elle demande au client d'exécuter JavaScript et de prouver qu'il est un navigateur avant que le site ne réponde. Python requests ne peut pas exécuter JavaScript, et depuis un serveur de datacenter, son IP et sa poignée de main TLS semblent déjà automatisées. Ce qui passe est un vrai navigateur sur une IP propre, ou un web unlocker qui en exécute un.

Ce guide montre comment détecter le défi en Python, pourquoi le même script réussit sur votre ordinateur portable et échoue sur votre serveur, pourquoi les astuces de l'ère cloudscraper ne suffisent plus, et ce qui a réellement fonctionné lorsque nous l'avons testé le 29 septembre 2026. Cela inclut le site où rien de ce que nous avons essayé n'a dépassé 'Just a moment'.

Ce que la page "Just a moment..." est réellement

Cloudflare l'appelle une page de défi interstitielle : une porte pleine page entre le visiteur et le site pendant que Cloudflare décide si le visiteur est automatisé. Sa documentation sur les pages de défi décrit trois types. Un défi non interactif nécessite seulement que le navigateur exécute le JavaScript injecté. Un défi géré laisse Cloudflare choisir le type de défi à partir des signaux de la requête. Un défi interactif demande au visiteur de faire quelque chose. Lorsque le contrôle est réussi, la requête originale continue vers le site et le navigateur reçoit un cookie cf_clearance qui lui permet de passer la porte pendant un certain temps.

Un client HTTP Python n'arrive jamais aussi loin. Il télécharge la page de défi, n'a pas de moteur pour exécuter le script, et vous remet le HTML de la porte au lieu de la page demandée.

Pourquoi le défi revient en tant que 403, et comment le détecter

Dans notre test, chaque page de défi est arrivée avec le statut 403, c'est pourquoi raise_for_status() lance 403 Client Error: Forbidden et les gens cherchent 'cloudflare just a moment 403'. Le statut seul ne peut pas distinguer un défi de tout autre refus. Le signal fiable est un en-tête : Cloudflare documente qu'une réponse défiée porte cf-mitigated: challenge, quelle que soit la ressource demandée. Vérifiez cela d'abord, puis le titre en dernier recours :

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'

Ne réessayez pas une requête défiée en boucle serrée avec le même client. Chaque nouvelle tentative récupère la même porte, et certains sites répondent aux échecs répétés par des règles plus strictes. C'est ainsi que les scrapers finissent coincés dans une boucle 'Just a moment'.

"Enable JavaScript and cookies to continue" et "your browser does not support JavaScript"

Ces chaînes sont le texte de repli d'une page de défi, affichées aux clients qui n'exécutent pas son script. Lorsque Python requests vous en montre une, le site ne se plaint pas de vos en-têtes. Il vous dit, littéralement, qu'il a besoin d'un moteur JavaScript et d'un conteneur de cookies qui conserve ce que le script définit.

C'est exactement ce que notre exécution résidentielle simple a reçu d'Indeed et Glassdoor : un 403 avec 'Enable JavaScript and cookies to continue', depuis une IP domestique propre. Ajouter un User-Agent Chrome, un en-tête Accept-Language ou un Referer ne change rien, car la page ne les lit pas pour décider. Elle attend que le script s'exécute.

Pourquoi votre scraper fonctionne localement mais est bloqué par Cloudflare sur un serveur

C'est la version la plus courante de la plainte : le script fonctionne sur un ordinateur portable, puis obtient 'Just a moment' dès qu'il s'exécute sur une VM cloud. Deux choses changent entre ces machines.

La colonne résidentielle de notre test montre la limite de corriger uniquement l'IP. Zillow et Leboncoin ont servi la vraie page à une requête simple via un proxy résidentiel. Indeed et Glassdoor ont toujours répondu avec le défi JavaScript. Votre ordinateur portable peut rencontrer le même mur sur un site plus strict. Il le rencontre simplement moins souvent.

Pourquoi un scraper Python passe Cloudflare depuis un ordinateur portable et obtient le défi Just a moment depuis un serveur cloud : IP domestique contre IP d'hébergement, même empreinte TLS Python, pas de moteur JavaScript dans les deux cas
Même script, deux machines. L'IP change ; la poignée de main TLS Python et le moteur JavaScript manquant ne changent pas.

Pourquoi les astuces de l'ère cloudscraper cessent de fonctionner

cloudscraper a été construit pour l'ancienne page 'I'm Under Attack' de Cloudflare. Cette page vérifiait principalement si le client pouvait exécuter un calcul JavaScript, donc la bibliothèque récupérait le défi, résolvait le script dans un interpréteur intégré, renvoyait la réponse et conservait le cookie, le tout au-dessus de requests. Pendant longtemps, cela suffisait, et c'est la raison pour laquelle 'alternative à cloudscraper' est maintenant une recherche courante.

Plusieurs choses ont évolué depuis, et elles ne dépendent d'aucune version de bibliothèque :

Au 29 septembre 2026, le dernier push du dépôt cloudscraper date de juin 2025. Ce n'est pas un verdict sur un site particulier, et nous ne l'avons pas évalué pour cet article. Cela signifie que vous devriez le tester contre vos propres cibles plutôt que de supposer qu'il fonctionne. Les alternatives corrigent chacune une partie du problème. curl_cffi reproduit une poignée de main TLS de navigateur mais n'exécute pas de JavaScript. Playwright ou un autre vrai navigateur exécute le défi mais a toujours besoin d'une IP propre, et est lourd à opérer en volume. Le unlocker combine les deux et vous indique quand aucun des deux n'a fonctionné.

Ce qui passe : le chemin du web unlocker

Le Web Unlocker de QuantumProxies.io gère un GET en étapes. D'abord, jusqu'à trois tentatives TLS, chacune sur une sortie résidentielle fraîche sous une empreinte de navigateur différente. Si chaque tentative revient défiée, il passe à un navigateur sans tête qui exécute le défi. Si la page est toujours bloquée après cela, vous obtenez un statut d'erreur avec x-qp-unlocker-blocked et une classe de blocage, jamais une page de défi déguisée en 200. Depuis Python, c'est un paramètre de proxy :

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"))

Les en-têtes x-qp-* sont lus par le unlocker et jamais transmis au site. Si vous préférez ne pas vous occuper du certificat du tout, le point de terminaison REST POST https://app.quantumproxies.io/api/v1/scraper/unlock prend l'URL dans un corps JSON avec votre clé API et renvoie blocked, blockClass et vendor dans la réponse. Notre tutoriel API web unlocker passe en revue les deux.

Mesuré : deux murs de type Cloudflare qui sont passés, un qui ne l'a pas été

Mesuré le 29 septembre 2026. Les requêtes simples étaient curl avec un User-Agent Chrome, depuis un serveur de datacenter à Francfort et via un proxy résidentiel avec une sortie aux États-Unis. Le unlocker a utilisé le niveau résidentiel avec une sortie aux États-Unis. Les temps sont en temps réel pour une requête.

SiteSimple, serveur de datacenterSimple, proxy résidentielWeb Unlocker
Glassdoor (avis d'entreprise)403, page 'Sécurité'403, 'Enable JavaScript and cookies to continue'200, 2 tentatives + navigateur sans tête, 17,2 s
Indeed (recherche d'emploi)403, page 'Vérification de sécurité'403, 'Enable JavaScript and cookies to continue'200, 1 tentative, pas de navigateur, 2,6 s
StockX (page produit)403403403 'Just a moment...', x-qp-block-class js_challenge, vendor cloudflare, 47,8 s
DoorDash (page de ville)403403403, x-qp-block-class ip_reputation, vendor cloudflare

Glassdoor est le chemin complet. Deux tentatives TLS ont été défiées, puis le navigateur sans tête a exécuté le défi et a renvoyé la page des avis. Indeed est le cas intéressant : le défi que la requête résidentielle simple a reçu n'est jamais apparu pour la première tentative du unlocker. Une empreinte TLS de navigateur sur une sortie résidentielle fraîche a suffi, sans navigateur.

StockX est l'échec honnête. Le unlocker a escaladé, a passé 47,8 secondes, et la page était toujours 'Just a moment...'. Elle est revenue en tant que 403 avec x-qp-block-class: js_challenge et x-qp-vendor: cloudflare, pas en tant que 200 que votre analyseur aurait essayé de lire. DoorDash, également derrière Cloudflare, a été refusé sur l'IP (ip_reputation) plutôt que défié. Certaines configurations Cloudflare ne s'ouvrent pour aucun des outils de cet article, et il est préférable de le savoir à partir d'un en-tête plutôt que d'un ensemble de données vide.

Contournez les défis JavaScript avec le Web Unlocker

Réutiliser l'autorisation : pourquoi la deuxième requête est rapide

L'escalade du navigateur est la partie lente, et vous ne voulez pas la payer pour chaque page. Lorsque le navigateur sans tête passe un défi, il gagne une autorisation. Envoyez la même valeur x-qp-session, ou conservez le même -session-&lt;id&gt; dans le nom d'utilisateur du proxy, sur les requêtes suivantes et elles partent de la même sortie, réutilisant cette autorisation au lieu de relancer le navigateur. L'en-tête x-qp-clearance vous indique ce qui s'est passé à chaque appel : minted lorsqu'une nouvelle autorisation a été gagnée, hit lorsqu'une autorisation stockée a été réutilisée, miss lorsqu'il n'y avait rien à réutiliser.

Dans un test Glassdoor le 29 septembre 2026, avec la session épinglée dans le nom d'utilisateur du proxy et deux connexions distinctes, la première requête est passée par le navigateur en 14,0 secondes (minted) et la seconde est revenue en 1,4 seconde (hit). L'épinglage est important en raison de la façon dont Cloudflare lie l'autorisation : elle est liée au visiteur et à l'appareil qui l'a gagnée, et elle reste valide pour la durée configurée par le site à moins que Cloudflare ne décide que la session semble suspecte. Changez la sortie ou l'empreinte et vous êtes à nouveau un nouveau visiteur.

Chronologie de deux requêtes dans une session unlocker épinglée : la première escalade vers un navigateur sans tête et frappe une autorisation Cloudflare en 14,0 secondes, la seconde la réutilise et revient en 1,4 seconde
La première requête paie pour le navigateur ; les requêtes ultérieures dans la même session épinglée réutilisent l'autorisation.

Quand les requêtes simples sont encore suffisantes sur un site Cloudflare

De nombreux sites fonctionnent derrière Cloudflare uniquement en tant que CDN, ou avec des règles de bot qui laissent passer le trafic ordinaire, et les requêtes simples fonctionnent sur eux. Être 'sur Cloudflare' n'est pas une raison de recourir à un navigateur. Le signal est la réponse. S'il n'y a pas d'en-tête cf-mitigated: challenge, pas de titre 'Just a moment' et pas de 403, conservez la requête simple, et n'escaladez que les URL qui sont réellement défiées. L'assistant plain-first dans notre tutoriel fait exactement cela.

Deux problèmes voisins ont leurs propres guides. Une page qui dit 'Error 1020 Access denied' est une règle de pare-feu vous refusant, pas un défi, et aucune quantité de JavaScript ne le corrigera ; voir ce qui déclenche l'erreur 1020 de Cloudflare et ce qui la corrige. Pour une vue d'ensemble des défenses de Cloudflare au-delà de Python, lisez comment contourner les défis Cloudflare en 2026. Si la page derrière le mur est Glassdoor, notre guide pour extraire les salaires et avis de Glassdoor couvre le côté données.

Questions fréquemment posées

Pourquoi mon scraper fonctionne-t-il localement mais est-il bloqué par Cloudflare dans le cloud ?

Parce que l'IP change. À la maison, votre script part d'une adresse ISP résidentielle ; sur une VM cloud, il part d'une plage d'hébergement que les défenses anti-bot méfient à vue. La poignée de main TLS Python est la même sur les deux, et sur un site plus strict, elle est défiée même depuis la maison. Dans notre test, une IP résidentielle seule n'a pas passé Indeed ou Glassdoor.

Pourquoi la page "Just a moment" de Cloudflare renvoie-t-elle un 403 ?

La page de défi est servie à la place de la page demandée, et dans notre test, elle est arrivée avec le statut 403, donc requests soulève '403 Client Error: Forbidden'. Le statut n'est pas spécifique aux défis. Vérifiez l'en-tête de réponse cf-mitigated, que Cloudflare documente comme étant défini sur challenge pour chaque réponse de page de défi, ou le titre 'Just a moment'.

Est-ce que cloudscraper fonctionne encore, et quelle est une bonne alternative ?

Il peut encore fonctionner sur des sites avec des paramètres indulgents, alors testez-le contre vos cibles. Il fonctionne sur la pile TLS de Python et résout les scripts dans un interpréteur, tandis que les défis actuels examinent également la poignée de main et l'environnement du navigateur. Alternatives : curl_cffi pour une poignée de main de navigateur, un vrai navigateur pour le JavaScript, ou un web unlocker qui fait les deux.

Est-ce que Python requests peut passer un défi Cloudflare seul ?

Non. requests n'exécute pas JavaScript, donc il ne peut pas compléter un défi qui dépend de l'exécution du script de la page. Il peut récupérer des pages de sites Cloudflare qui ne vous défient pas. Une fois que cf-mitigated: challenge apparaît, vous avez besoin d'un vrai navigateur, directement ou via un service qui en exécute un, et idéalement d'une IP résidentielle propre.

Le web unlocker peut-il résoudre Cloudflare Turnstile ou d'autres captchas ?

Les défis JavaScript non interactifs sont résolus par l'escalade du navigateur sans tête. Les défis interactifs qui nécessitent une action humaine, comme une case à cocher, un curseur, un appui prolongé ou une grille d'images, ne sont pas résolus. La requête échoue au lieu de retourner le défi en tant que 200, et le point de terminaison REST l'étiquette avec la classe de blocage captcha et le fournisseur, afin que votre code puisse décider de sauter l'URL, de ralentir ou de réessayer plus tard.

Combien de temps dure une autorisation Cloudflare ?

Aussi longtemps que le site le configure. Cloudflare appelle ce paramètre Challenge Passage, et l'autorisation peut se terminer prématurément si Cloudflare décide que la session semble suspecte. Le cookie cf_clearance est lié au visiteur et à l'appareil qui l'a gagné, donc il n'aide que si les requêtes ultérieures conservent la même sortie et empreinte, ce que fait l'épinglage de x-qp-session.

La version courte : 'Just a moment...' demande un navigateur, donc aucune astuce d'en-tête dans requests ne le satisfera. Détectez-le avec cf-mitigated, conservez les requêtes simples pour tout ce qui n'est pas défié, envoyez les URL défiées via quelque chose qui exécute un vrai navigateur depuis une IP propre, et épinglez une session pour que vous ne payiez pour ce navigateur qu'une fois plutôt que sur chaque page.

Essayez le Web Unlocker sur des sorties résidentielles