# Guide des proxies curl_cffi : Configuration, Authentification, Rotation et Async

curl_cffi vous offre une poignée de main TLS en forme de navigateur. Un proxy vous fournit une IP de sortie propre. Voici exactement comment les connecter ensemble — et pourquoi le préfixe https:// dans votre dictionnaire de proxies génère ErrCode 35.

curl_cffi est la liaison Python à un fork de curl-impersonate : il reproduit les empreintes TLS/JA3 et HTTP/2 d'un vrai navigateur au lieu de s'annoncer comme urllib3. Cela résout un axe de blocage. L'autre est l'IP de sortie, c'est là qu'intervient un proxy curl_cffi — et où la documentation devient mince. La section proxy officielle fait environ quinze lignes, et un problème GitHub de février 2023 figure toujours dans le top cinq pour ce sujet. Ce guide couvre toute la surface : le paramètre `proxy`, le dictionnaire de style requests et ses vrais noms de clés, `proxy_auth`, les sessions, la rotation par requête, async, SOCKS5 — et les chaînes d'erreur exactes que vous collerez dans une boîte de recherche.

## Syntaxe du proxy curl_cffi : préférer proxy= au dictionnaire proxies

curl_cffi accepte deux formes. La native est une chaîne unique `proxy=`, ajoutée en v0.6.0 ; le dictionnaire `proxies=` existe pour la compatibilité avec requests, et la documentation recommande le paramètre unique sauf si vous avez vraiment besoin de proxies différents par schéma. En interne, ils se réduisent à la même chose — `proxy="..."` devient `{"all": "..."}` — et les deux fonctionnent sur les aides du module, sur `Session`, sur `AsyncSession` et sur les requêtes individuelles.

```python
# pip install curl_cffi --upgrade    (Python 3.10+ since v0.14)
import curl_cffi

PROXY = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"

# Native form — one string, applies to every scheme
r = curl_cffi.get(
    "https://tls.browserleaks.com/json",
    impersonate="chrome",
    proxy=PROXY,
    timeout=30,
)
print(r.status_code, r.json()["ja3n_hash"])

# requests-compatible form
r = curl_cffi.get(
    "https://httpbin.org/ip",
    impersonate="chrome",
    proxies={"http": PROXY, "https": PROXY},
    timeout=30,
)
print(r.json())  # {'origin': '<proxy exit IP>'}
```

Quatre choses à savoir sur ce dictionnaire, car aucune n'est évidente à partir du README :

- **Les clés valides sont `all`, `http`, `https`, `ws` et `wss`.** `all` est le fourre-tout ; les clés websocket ne comptent que pour le client WebSocket.
- **Les clés par hôte fonctionnent aussi.** `https://api.example.com` ou `all://example.com` routent juste cet hôte via un proxy donné — utile pour envoyer un domaine difficile via des IP résidentielles et laisser le reste direct.
- **Vous ne pouvez pas passer les deux.** `proxy=` plus `proxies=` sur le même appel génère `TypeError: Cannot specify both 'proxy' and 'proxies'`, et la même vérification s'exécute au niveau de la session.
- **Les variables d'environnement sont respectées** — `http_proxy`, `https_proxy`, `ws_proxy`, `wss_proxy`. Passez `trust_env=False` à une `Session` lorsqu'une variable d'entreprise détourne votre scraper.

Une note sur les imports : depuis la v0.10.0, le package est directement appelable (`curl_cffi.get`, `curl_cffi.Session`). Les anciens tutoriels utilisent `from curl_cffi import requests`, ce qui fonctionne toujours mais se lit mal à côté de la vraie bibliothèque requests — et explique pourquoi la moitié des extraits en ligne ressemblent à un projet différent.

## Le piège https:// : ErrCode 35 et WRONG_VERSION_NUMBER

Cette seule erreur génère plus de questions sur les proxies curl_cffi que tout le reste combiné. Le problème #6 dans le suivi du projet — ouvert et fermé le même jour en février 2023 — figure toujours sur la première page, car l'erreur qu'il produit ressemble à un bug TLS plutôt qu'à une faute de configuration :

```python
# WRONG: this asks curl to open a TLS connection *to the proxy itself*
proxies = {"https": "https://USER:PASS@residentialpremium.quantumproxies.io:8080"}

# Failed to perform, ErrCode: 35, Reason:
# 'error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER'

# RIGHT: plain HTTP CONNECT, then the TLS tunnel runs through to the target
proxies = {"https": "http://USER:PASS@residentialpremium.quantumproxies.io:8080"}

# Or skip the dict entirely
proxy = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"
```

La clé nomme le protocole de la *cible* ; la valeur nomme comment vous atteignez le *proxy*. Un proxy HTTPS-sur-HTTP normal prend un `CONNECT` en texte clair, puis tunnelise votre trafic chiffré sans le toucher — donc l'URL du proxy commence par `http://` même lorsque chaque URL que vous récupérez est HTTPS. Les proxies HTTPS-sur-HTTPS existent mais sont rares et doivent être explicitement pris en charge par la passerelle. Requests formule l'échec de manière bien plus utile — *votre proxy semble n'utiliser que HTTP et non HTTPS* — ce qui explique pourquoi une configuration identique peut ressembler à un bug spécifique à curl_cffi. Les versions récentes avertissent et lient le problème #6, mais ce n'est qu'un avertissement : la requête échoue toujours.

## Authentification : Identifiants URL ou proxy_auth

Les passerelles authentifiées acceptent la forme intégrée habituelle, `http://USER:PASS@host:port`, avec le piège habituel : un `@`, `:` ou `/` non échappé dans le mot de passe divise l'URL au mauvais endroit et produit un échec d'authentification qui ressemble à un proxy mort. curl_cffi offre une échappatoire que requests n'a pas — un tuple `proxy_auth` passé à libcurl comme options de nom d'utilisateur et de mot de passe séparées, donc aucun encodage n'est impliqué.

```python
import curl_cffi
from urllib.parse import quote

# Option A — credentials in the URL, password URL-encoded
pw = quote("p@ss:word", safe="")
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy=f"http://USER:{pw}@residentialpremium.quantumproxies.io:8080",
    impersonate="chrome",
    timeout=30,
)

# Option B — keep credentials out of the URL entirely
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy="http://residentialpremium.quantumproxies.io:8080",
    proxy_auth=("USER", "PASS"),
    impersonate="chrome",
    timeout=30,
)
print(r.json())
```

Une troisième option élimine entièrement cette classe de bug : la liste blanche d'IP. Chaque [plan résidentiel QuantumProxies](https://quantumproxies.io/residential-proxies) vous permet d'autoriser l'IP de votre serveur au lieu d'envoyer user:pass, donc l'URL du proxy devient un simple `http://residentialboson.quantumproxies.io:PORT` — rien à encoder, aucun secret dans votre arbre source. Si les identifiants eux-mêmes sont rejetés, notre guide sur [chaque cause de 407 Proxy Authentication Required](https://quantumproxies.io/blog/fix-407-proxy-authentication-required) couvre le reste.

![Une requête curl_cffi avec une poignée de main TLS imitant Chrome qui passe par une passerelle de proxy rotatif : un CONNECT en clair ouvre un tunnel, la poignée de main le traverse intacte, et la cible voit le ClientHello et les paramètres HTTP/2 de Chrome arriver depuis une IP de sortie résidentielle propre.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776553/blog/generated/qp-b10-006-diag1.png)
*Le proxy tunnelise la poignée de main plutôt que de la terminer, donc l'imitation du navigateur survit au saut et la cible voit une IP de sortie propre.*

## Sessions, cookies et le détail de la réutilisation des identifiants

Une `Session` conserve les cookies, le pool de connexions et vos paramètres par défaut en un seul endroit, ce qui est ce que vous voulez pour tout ce qui est multi-étapes. Définissez `impersonate` et `proxy` une fois et chaque requête les hérite :

```python
from curl_cffi import Session

with Session(
    impersonate="chrome",
    proxy="http://USER-sid-a1b2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
    timeout=30,
    retry=3,
) as s:
    s.get("https://httpbin.org/cookies/set/foo/bar")
    r = s.get("https://httpbin.org/cookies")
    print(r.json(), s.cookies.get_dict())
```

Deux comportements méritent d'être mentionnés. Premièrement, chaque fois qu'un proxy est configuré, curl_cffi active l'option proxy-credential-no-reuse de libcurl : une nouvelle connexion est forcée lorsque le nom d'utilisateur du proxy change, et le cache de session TLS est indexé sur l'adresse du proxy, donc une IP de sortie précédente ne peut pas fuir dans une requête ultérieure via une session réutilisée. Si vous encodez des ID de session persistante dans le nom d'utilisateur, comme le font la plupart des passerelles rotatives, vous obtenez cette isolation gratuitement. Deuxièmement, `retry` (un entier, ou une `RetryStrategy` de `curl_cffi.requests` avec délai, backoff et jitter) ne se relance que sur une exception de transport. Il ne réessaie **pas** un 403 ou 429 comme le fait `status_forcelist` d'urllib3 — cette boucle est toujours à écrire. Les documents de compatibilité listent les réessais comme non pris en charge, ce qui est obsolète : le paramètre est arrivé en v0.15.0.

[Pointez curl_cffi vers une passerelle résidentielle rotative](https://quantumproxies.io/rotating-proxies)

## Rotation par requête et async

curl_cffi annonce asyncio avec rotation de proxy à chaque requête, et c'est littéral : un argument `proxy=` sur un appel individuel remplace ce que la session contient. Vous avez rarement besoin d'une liste de proxies pour l'exploiter — une passerelle rotative attribue un nouveau serveur de sortie côté serveur à chaque connexion, donc un seul point de terminaison plus la concurrence est déjà une rotation. Là où vous voulez du contrôle (une IP stable par travailleur, par compte, par panier), mettez un jeton de session dans le nom d'utilisateur et laissez la passerelle épingler cette sortie.

```python
import asyncio
from curl_cffi import AsyncSession

GATE = "residentialpremium.quantumproxies.io:8080"
URLS = ["https://httpbin.org/ip"] * 20

async def fetch(session, url, worker):
    # one sticky exit IP per worker; drop the -sid-...-ttl-30m suffix for full rotation
    proxy = f"http://USER-sid-{worker}-ttl-30m:PASS@{GATE}"
    r = await session.get(url, proxy=proxy, timeout=30)
    return r.status_code, r.json()["origin"]

async def main():
    async with AsyncSession(impersonate="chrome", max_clients=10) as s:
        return await asyncio.gather(
            *(fetch(s, u, i % 5) for i, u in enumerate(URLS))
        )

for status, ip in asyncio.run(main()):
    print(status, ip)
```

`max_clients` limite les poignées curl simultanées dans le pool (10 par défaut), donc c'est votre véritable réglage de concurrence — ajouter un sémaphore à un rassemblement non borné est l'erreur habituelle. La même logique de dimensionnement s'applique à tout client async, que nous avons couvert dans [le scraping Python async avec httpx et aiohttp](https://quantumproxies.io/blog/python-async-scraping-httpx-aiohttp-proxies). Que ce soit pour tourner par requête ou épingler une session dépend de si le site suit l'état à travers les requêtes ; les compromis sont dans [sessions persistantes vs proxies rotatifs](https://quantumproxies.io/blog/sticky-sessions-vs-rotating-proxies).

![L'erreur de proxy curl_cffi derrière ErrCode 35 : une URL de proxy en https:// pousse curl à ouvrir une connexion TLS vers le proxy lui-même et à échouer avec WRONG_VERSION_NUMBER, alors que http:// envoie un CONNECT en clair et tunnelise le TLS jusqu'à la cible. La carte oppose les configurations qui fonctionnent aux erreurs habituelles, comme les mots de passe non échappés.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785776553/blog/generated/qp-b10-006-diag2.png)
*La plupart des échecs de proxy curl_cffi sont l'une des cinq choses — et quatre d'entre elles sont un seul caractère dans une chaîne.*

## SOCKS5, HTTP/3 et les interrupteurs de sécurité

SOCKS n'a besoin d'aucune installation supplémentaire — libcurl est compilé, donc contrairement à requests, il n'y a pas de `[socks]` extra à retenir. Utilisez `socks5h://USER:PASS@residentialpremium.quantumproxies.io:1080` : le `h` pousse la résolution DNS vers le proxy, ce qui empêche les fuites de votre propre réseau et résout les noms d'hôte géo-clôturés depuis l'emplacement de sortie. curl_cffi détecte le préfixe `socks` et saute le drapeau de tunnelisation HTTP, puisque le protocole SOCKS gère cela lui-même. Chaque plan ici expose des points de terminaison HTTP et [SOCKS5](https://quantumproxies.io/socks5-proxies) sur la même passerelle, donc le changement est un échange de schéma plutôt qu'une nouvelle commande.

- **HTTP/3 sur un proxy** est arrivé en v0.15.0 aux côtés des empreintes http/3, mais il nécessite un serveur SOCKS5 qui parle UDP, pas une passerelle HTTP ordinaire. Niche jusqu'à ce que votre cible récompense QUIC.
- **Renforcement SSRF.** La même version a publié un avis : si vous récupérez des URL fournies par d'autres personnes, les redirections peuvent être dirigées vers votre réseau interne. Réglez `allow_redirects="safe"`, ou désactivez les redirections.
- **Débogage.** La v0.15 a expédié une CLI : `curl-cffi get tls.browserleaks.com/json --impersonate chrome` vous dit en une ligne si l'imitation atterrit, avant de blâmer le proxy.

## Quand curl_cffi plus un proxy suffit

Plus souvent que les gens ne le pensent. Si la cible sert du JSON à partir d'une API interne ou du HTML rendu par le serveur, et que le seul obstacle est une vérification d'empreinte, une poignée de main assortie plus une sortie résidentielle le résout à une fraction du coût et de la latence d'un navigateur. La FAQ du projet est franche sur le plafond : les empreintes sont un facteur parmi plusieurs, aux côtés de la qualité de l'IP, du taux de requêtes et des vérifications JavaScript, et les niveaux de protection plus élevés nécessitent à la fois un meilleur pool de proxies et une véritable automatisation de navigateur. Lorsque l'imitation est correctement configurée et que vous êtes toujours bloqué, la variable restante est presque toujours l'IP de sortie — isoler cela en cinq minutes est le sujet de [curl_cffi vs requests](https://quantumproxies.io/blog/curl-cffi-vs-requests). Si vous préférez ne pas en utiliser, l'[API Scraper](https://quantumproxies.io/scraper-api) gère les empreintes, les proxies et le rendu JS optionnel derrière un seul appel.

## Questions fréquemment posées

### Comment utiliser un proxy avec curl_cffi ?

Passez `proxy="http://USER:PASS@host:port"` à toute méthode de requête, session ou session async. Le dictionnaire de style requests `proxies={"http": ..., "https": ...}` fonctionne également, mais le projet recommande le paramètre unique sauf si vous avez besoin de proxies différents par schéma. Passer les deux génère un TypeError.

### Pourquoi curl_cffi génère-t-il ErrCode 35 WRONG_VERSION_NUMBER ?

Parce que l'URL du proxy commence par `https://`. Un proxy standard s'attend à une requête CONNECT en texte clair, puis tunnelise votre TLS ; un préfixe `https://` fait que curl essaie de faire une poignée de main TLS avec le proxy lui-même, qui répond en HTTP simple. Changez la valeur en `http://` — la clé `https` se réfère à la cible, pas au saut.

### curl_cffi prend-il en charge les proxies SOCKS5 ?

Oui, nativement — libcurl est inclus, donc il n'y a pas d'extra optionnel à installer. Utilisez le schéma `socks5h://` pour que les noms d'hôte soient résolus par le proxy plutôt que par votre machine. SOCKS4, SOCKS4a et `socks5://` simple sont également acceptés ; la bibliothèque saute la tunnelisation HTTP pour tout proxy dont le schéma commence par socks.

### curl_cffi peut-il faire tourner les proxies à chaque requête ?

Oui. Un argument `proxy=` sur un appel individuel remplace le défaut de session, y compris à l'intérieur d'une `AsyncSession`, ce que le README signifie par asyncio avec rotation par requête. Avec une passerelle rotative, vous n'avez souvent besoin d'aucune logique : le même point de terminaison distribue une IP de sortie différente par connexion.

### curl_cffi peut-il contourner Cloudflare ?

Parfois. Il supprime l'indice d'empreinte TLS et HTTP/2, ce qui est suffisant pour les niveaux de protection de base. Il ne peut pas exécuter de défis JavaScript, résoudre Turnstile, ou réparer une IP de centre de données qu'une base de données de réputation a déjà signalée. Considérez l'imitation comme l'un des trois besoins, pas la réponse.

Toute la configuration est plus petite que sa réputation : une chaîne `proxy`, `impersonate` définie une fois sur la session, un délai d'attente à chaque appel, et des identifiants soit encodés dans l'URL soit passés comme un tuple `proxy_auth`. Obtenez ces éléments correctement et la variable restante est la qualité de l'IP — une poignée de main Chrome parfaite depuis une adresse de centre de données signalée reste une adresse de centre de données signalée. Notre décomposition de [l'empreinte JA3 et JA4](https://quantumproxies.io/blog/tls-ja3-fingerprinting-scraping) explique pourquoi les deux vérifications sont indépendantes.

[Obtenez des IP résidentielles qui correspondent à votre imitation](https://quantumproxies.io/residential-proxies)
