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'.
- C'est un défi, pas une erreur. Cloudflare marque les réponses défiées avec l'en-tête
cf-mitigated: challenge. Dans notre test, elles sont arrivées en tant que HTTP 403. - Les en-têtes ne le répareront pas. 'Enable JavaScript and cookies to continue' signifie que la page veut un moteur JavaScript. Aucune chaîne User-Agent n'en fournit un.
- Serveur contre ordinateur portable est principalement une question de réputation IP plus l'empreinte TLS. Une IP résidentielle seule n'a pas passé Indeed ou Glassdoor dans notre test.
- Ce qui est passé : le Web Unlocker a réussi Glassdoor (après un navigateur sans tête, 17,2 s) et Indeed (première tentative, 2,6 s). StockX a toujours renvoyé 'Just a moment...' et a été signalé comme
js_challenge. - Épingler une session. Réutiliser l'autorisation du navigateur a réduit une deuxième requête Glassdoor de 14,0 s à 1,4 s dans notre test.
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.
- L'IP. Sur votre ordinateur portable, le script part d'une adresse ISP domestique. Sur une VM, il part d'une plage de fournisseur d'hébergement, que les défenses anti-bot évaluent comme probablement automatisée avant de lire quoi que ce soit d'autre. Notre serveur de datacenter à Francfort a été refusé par Glassdoor, Indeed, Zillow et Leboncoin, entre autres.
- L'empreinte TLS. La poignée de main de Python ressemble à Python sur les deux machines, mais sur une IP domestique, un site peut la tolérer et sur une IP d'hébergement, il le fera rarement. Notre explication sur curl_cffi vs requests montre combien de la poignée de main une bibliothèque contrôle.
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 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 :
- La poignée de main dit toujours Python. Une bibliothèque construite sur requests se connecte via la pile TLS de Python, donc le ClientHello ne correspond pas au navigateur que son User-Agent prétend être. Ce décalage est visible avant que tout JavaScript ne s'exécute.
- Le défi n'est plus seulement arithmétique. Les défis gérés choisissent le contrôle à partir des signaux de la requête, et les détections JavaScript de Cloudflare lisent l'environnement du navigateur. Un interpréteur qui évalue un script n'est pas un navigateur qui rend une page.
- L'autorisation est liée au client. La documentation sur l'autorisation de Cloudflare indique qu'un cookie cf_clearance est lié au visiteur et à l'appareil auquel il a été délivré, pour empêcher la réutilisation entre machines. En copier un depuis votre navigateur dans un script sur une autre machine est conçu pour échouer.
- L'IP est toujours la vôtre. Résoudre la page ne change pas une IP de plage d'hébergement, et certains sites les refusent avant que tout défi ne soit servi.
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.
| Site | Simple, serveur de datacenter | Simple, proxy résidentiel | Web 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) | 403 | 403 | 403 'Just a moment...', x-qp-block-class js_challenge, vendor cloudflare, 47,8 s |
| DoorDash (page de ville) | 403 | 403 | 403, 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-<id> 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.

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.