# Tutoriel API Web Unlocker : Python et Node.js, Bloqué vs Débloqué

Code fonctionnel en Python et Node.js pour une API de web unlocker : la même requête contre un site qui bloque les clients simples et un qui ne le fait pas, comment lire les rapports de l'unlocker, et un assistant qui ne paie pour l'unlocker que lorsqu'un blocage est détecté.

Une API de web unlocker rejoue une requête HTTP à partir d'une IP résidentielle sous une empreinte TLS de navigateur, réessayant avec une nouvelle IP et passant à un vrai navigateur lorsque le site répond avec un défi. Ce tutoriel d'API de web unlocker le montre en Python et Node.js, sur un site qui bloque les clients simples et un qui ne le fait pas.

Chaque résultat cité ci-dessous a été mesuré le 29 septembre 2026 : la même URL récupérée avec curl simple depuis un serveur de datacenter à Francfort, avec curl simple via un proxy résidentiel avec une sortie aux États-Unis, et via le Web Unlocker de QuantumProxies.io avec une sortie aux États-Unis. Là où un site n'avait pas besoin de l'unlocker, nous le disons, car la requête la moins chère est celle qui fonctionne sans.

- **Deux façons d'entrer :** un proxy de transfert à `unlock.quantumproxies.io:9000` pour le code qui parle déjà le protocole proxy, ou `POST /api/v1/scraper/unlock` avec votre clé API pour tout le reste.
- **Ce que vous obtenez en retour :** le statut, les en-têtes et le corps de l'origine, plus les en-têtes `x-qp-*` indiquant combien de tentatives ont été nécessaires, si un navigateur a été utilisé, et quel type de mur l'a arrêté si c'était le cas.
- **Quand vous n'en avez pas besoin :** Wikipedia, GitHub, docs.python.org, Hacker News et books.toscrape.com ont tous répondu aux requêtes simples dans notre test. Essayez d'abord en simple et n'escaladez que sur un blocage détecté.

## Ce dont vous avez besoin avant de commencer

- Un compte QuantumProxies.io avec de la bande passante Web Unlocker. L'unlocker facture à partir d'un solde prépayé en Go, séparé pour le niveau résidentiel et le niveau mobile (sorties opérateur 4G/5G).
- Pour le proxy de transfert : un sous-utilisateur proxy créé sur la [page Web Unlocker du tableau de bord](https://app.quantumproxies.io/unlock). Il ressemble à `unlock-XXXXXXXX` avec son propre mot de passe. Ce n'est pas votre clé API.
- Pour le point de terminaison REST : une clé API depuis le tableau de bord, envoyée comme `Authorization: Bearer`.
- Python 3.10 ou plus récent avec `requests`, et pour la partie Node.js Node 18 ou plus récent avec le package `undici`.

Gardez les deux secrets dans les variables d'environnement. Les exemples lisent `QP_UNLOCK_USER`, `QP_UNLOCK_PASSWORD` et `QP_API_KEY`. Si le mot de passe proxy contient des caractères tels que `@` ou `:`, encodez-le en URL avec `urllib.parse.quote` avant de le mettre dans une URL de proxy.

## Étape 1 : voir le blocage avec des requêtes Python simples

Commencez par l'échec, pour savoir ce que vous réparez. La cible est une recherche d'emploi Indeed. Depuis notre serveur de datacenter à Francfort, elle a répondu `403` avec une page intitulée 'Security Check - Indeed.com'. Via un proxy résidentiel simple, elle a encore répondu `403`, cette fois avec un corps demandant au client d'« Activer JavaScript et les cookies pour continuer ». Une IP domestique propre n'était pas suffisante.

```python
import re
import requests

URL = "https://www.indeed.com/jobs?q=data+engineer&l=Austin%2C+TX"
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 ""

r = requests.get(URL, headers=HEADERS, timeout=30)
print(r.status_code, "|", title_of(r.text))
# Our datacenter run on 29 Sep 2026 saw: 403 | Security Check - Indeed.com
```

Notre mesure a utilisé curl avec un User-Agent Chrome, pas `requests`, et il n'y a aucune raison de s'attendre à ce que `requests` fasse mieux. Une chaîne User-Agent est un en-tête. La couche anti-bot lit également la poignée de main TLS et les paramètres HTTP/2, et ceux-ci disent toujours Python quel que soit le User-Agent que vous définissez. C'est pourquoi échanger les en-têtes vous fait rarement passer la détection de bots dans Python requests ; le côté poignée de main est couvert dans notre guide sur [l'empreinte digitale TLS et pourquoi les IPs propres sont encore bloquées](https://quantumproxies.io/blog/tls-ja3-fingerprinting-scraping). Un `403` signifie que le serveur a compris la requête et l'a refusée ([RFC 9110, section 15.5.4](https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden)). Cela ne signifie pas que votre URL ou vos paramètres sont erronés.

## Étape 2 : le même appel via le proxy de transfert

Le proxy de transfert est le plus petit changement au code existant : passez-le comme un proxy et rien d'autre ne bouge. La cible est incluse dans le nom d'utilisateur. `-country-us` choisit le pays de sortie, `-tier-mobile` passe aux sorties mobiles, et `-profile-firefox` (ou chrome, safari, safariios, edge, brave, mobile) fixe une empreinte TLS au lieu de laisser les réessais la faire tourner.

```python
import os
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

user = os.environ["QP_UNLOCK_USER"] + "-country-us"   # e.g. unlock-XXXXXXXX-country-us
password = os.environ["QP_UNLOCK_PASSWORD"]
PROXY = f"http://{user}:{password}@unlock.quantumproxies.io:9000"

r = requests.get(
    URL,
    headers=HEADERS,
    proxies={"http": PROXY, "https": PROXY},
    verify=False,   # or verify="qp-unlocker-ca.pem", see below
    timeout=120,
)
print(r.status_code, "|", title_of(r.text))
print("attempts:", r.headers.get("x-qp-unlocker-attempts"),
      "rendered:", r.headers.get("x-qp-unlocker-rendered"))
```

Le 29 septembre, cela a renvoyé `200` avec la vraie page de résultats, intitulée 'Now Hiring: 2,000 Data Engineer Jobs in Austin, TX | Indeed', à la première tentative TLS, en 2,6 secondes, sans démarrer un navigateur.

Pourquoi `verify=False` ? Pour réécrire une requête HTTPS, l'unlocker doit terminer le TLS de votre client et présenter son propre certificat pour l'hôte cible. Vous avez deux options propres. Désactivez la vérification pour cette session uniquement, comme ci-dessus, ce que fait `curl -k`. Ou téléchargez le CA de l'unlocker une fois et faites-lui confiance explicitement, pour que la vérification reste activée :

```bash
curl -H "Authorization: Bearer $QP_API_KEY" \
  -o qp-unlocker-ca.pem https://app.quantumproxies.io/api/v1/scraper/unlock/ca
```

Ensuite, passez `verify="qp-unlocker-ca.pem"` au lieu de `False`. Les [docs d'utilisation avancée de requests](https://requests.readthedocs.io/en/latest/user/advanced/) couvrent à la fois l'argument `proxies` et les ensembles de CA personnalisés. Si vous ne voulez ni l'un ni l'autre, le point de terminaison REST ci-dessous n'a besoin d'aucun certificat.

## Étape 3 : le point de terminaison REST, pas de protocole proxy

Le point de terminaison REST exécute le même moteur derrière un appel HTTPS normal. Vous décrivez la requête que vous auriez faite (url, méthode, en-têtes, corps) et obtenez la réponse de l'origine en retour sous forme de JSON. Le seul champ requis est `url`.

```python
import os
import requests

API_KEY = os.environ["QP_API_KEY"]

resp = requests.post(
    "https://app.quantumproxies.io/api/v1/scraper/unlock",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "url": URL,
        "country": "us",
        # "tier": "mobile",        # mobile exits, billed on the mobile balance
        # "failOnBlock": True,     # HTTP 502 instead of 200 when every tier was blocked
    },
    timeout=130,
)
resp.raise_for_status()           # 402 = no unlocker GB left, 400 = bad input
data = resp.json()["payload"]

print(data["status"], "blocked:", data["blocked"])
print("attempts:", data["attempts"], "rendered:", data["rendered"],
      "escalated:", data["escalated"], "clearance:", data.get("clearance"))
if data["blocked"]:
    print("block class:", data.get("blockClass"), "vendor:", data.get("vendor"))
html = data.get("body") or ""     # text form; bodyBase64 is always present too
```

L'enveloppe de réponse est `{ type, message, payload }`. À l'intérieur de `payload`, vous obtenez `status` (le code de statut de l'origine), `headers`, `body` et `bodyBase64`, `finalUrl`, l'empreinte `profile`, `attempts`, `rendered`, `escalated`, `blocked` et, lorsque quelque chose a arrêté la requête, `blockClass` et `vendor`. Un objet `usage` rapporte les octets débités pour l'appel.

Un comportement à concevoir : par défaut, une requête qui est restée bloquée après chaque tentative revient quand même de l'API en tant que HTTP 200, avec `blocked: true` et la page de blocage dans `body` pour inspection. Elle est étiquetée, non déguisée. Si votre pipeline traite tout 200 comme un succès, définissez `failOnBlock: true` et vous obtenez un 502 portant les mêmes diagnostics à la place. D'autres champs à connaître : `method` et `body` pour les POST, `render: "html"` pour démarrer dans un navigateur, `waitForSelector`, et `returnCookies`.

![Un GET via le Web Unlocker : jusqu'à trois tentatives TLS sur de nouvelles IP de sortie et empreintes de navigateur, un navigateur sans tête uniquement si un défi JavaScript apparaît, puis soit la vraie page, soit un blocage étiqueté. La carte liste les en-têtes x-qp qui indiquent les tentatives, le rendu et la classe de blocage.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1790701775/blog/generated/qp-unl-g1-diag1.png)
*Un GET via l'unlocker : tentatives TLS d'abord, un vrai navigateur seulement lorsqu'un défi JavaScript apparaît, et un résultat étiqueté dans les deux cas.*

[Voyez comment le Web Unlocker gère les requêtes bloquées](https://quantumproxies.io/web-unlocker)

## Étape 4 : lire les en-têtes x-qp et gérer un bloc signalé

Via le proxy de transfert, l'unlocker rapporte sur la réponse elle-même. Voici les en-têtes qui valent la peine d'être enregistrés à chaque appel :

| En-tête | Ce qu'il vous dit |
| --- | --- |
| x-qp-unlocker-attempts | Combien de tentatives la requête a pris, réessais inclus |
| x-qp-unlocker-rendered / x-qp-unlocker-escalated | Réglé à 1 lorsqu'un navigateur sans tête a récupéré la page / lorsque la requête est passée du niveau TLS |
| x-qp-unlocker-profile | Quelle empreinte de navigateur la tentative retournée a utilisée |
| x-qp-clearance | Ce qui s'est passé avec le cache de dédouanement du navigateur lors de cet appel : minted, hit ou miss |
| x-qp-session / x-qp-country | La session de sortie et le pays effectivement utilisés |
| x-qp-unlocker-blocked | Présent uniquement lorsque la page était encore bloquée après chaque tentative |
| x-qp-block-class / x-qp-vendor | Quel type de mur c'était, et quel fournisseur anti-bot, lorsqu'il est reconnu |

Une page qui est encore bloquée n'arrive jamais comme un 200 silencieux. Si l'origine a refusé avec sa propre erreur (403, 429, 503), ce statut est relayé inchangé avec `x-qp-unlocker-blocked` défini. Si l'origine a servi une page de défi avec un 2xx, le proxy la transforme en 502 et met le code de l'origine dans `x-qp-origin-status`. Donc une vérification couvre les deux :

```python
class Blocked(Exception):
    pass

s = requests.Session()
s.proxies = {"http": PROXY, "https": PROXY}
s.verify = False  # or "qp-unlocker-ca.pem"

NEXT_STEP = {
    "js_challenge":  "a browser could not clear it; retry later in a pinned session",
    "captcha":       "interactive captcha; decide in your own code",
    "access_denied": "the site denied access; retry with a fresh session or test tier-mobile",
    "rate_limited":  "the site is rate limiting; slow down and retry later",
    "fingerprint":   "the client fingerprint was refused; retry with another profile",
    "geo":           "the content depends on location; set the country explicitly",
    "timeout":       "the target was too slow; raise x-qp-timeout or retry",
}

def unlock_get(url: str, session_id: str | None = None) -> requests.Response:
    headers = {"x-qp-session": session_id} if session_id else {}
    r = s.get(url, headers=headers, timeout=120)
    if "x-qp-unlocker-blocked" in r.headers:
        cls = r.headers.get("x-qp-block-class", "unknown")
        raise Blocked(
            f"{url}: HTTP {r.status_code}, class={cls}, "
            f"vendor={r.headers.get('x-qp-vendor')}, next: {NEXT_STEP.get(cls, 'inspect the body')}"
        )
    return r
```

## Étape 5 : la version Node.js (API web unlocker en JavaScript)

Dans Node, `fetch` prend un `dispatcher`. Utilisez `ProxyAgent` de `undici` pour le proxy de transfert, et importez `fetch` du même package pour que les deux versions correspondent. La [documentation de ProxyAgent](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md) liste les options utilisées ici : `token` pour la valeur Proxy-Authorization et `requestTls` pour les paramètres TLS vers la cible.

```javascript
// unlock.mjs  (npm i undici)
import { readFileSync } from "node:fs";
import { fetch, ProxyAgent } from "undici";

const user = process.env.QP_UNLOCK_USER + "-country-us";
const pass = process.env.QP_UNLOCK_PASSWORD;

const dispatcher = new ProxyAgent({
  uri: "http://unlock.quantumproxies.io:9000",
  token: "Basic " + Buffer.from(user + ":" + pass).toString("base64"),
  requestTls: { rejectUnauthorized: false },
  // verified instead: requestTls: { ca: readFileSync("qp-unlocker-ca.pem") },
});

const url = "https://www.indeed.com/jobs?q=data+engineer&l=Austin%2C+TX";
const res = await fetch(url, { dispatcher, headers: { "x-qp-session": "indeed-001" } });

if (res.headers.get("x-qp-unlocker-blocked")) {
  console.error("blocked:", res.status, res.headers.get("x-qp-block-class"), res.headers.get("x-qp-vendor"));
} else {
  const html = await res.text();
  console.log(res.status, res.headers.get("x-qp-unlocker-attempts"), html.length);
}
```

Le point de terminaison REST n'a besoin de rien au-delà du `fetch` intégré dans Node 18+ :

```javascript
const resp = await fetch("https://app.quantumproxies.io/api/v1/scraper/unlock", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.QP_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://www.indeed.com/jobs?q=data+engineer&l=Austin%2C+TX", country: "us" }),
});
const { payload } = await resp.json();
console.log(payload.status, payload.blocked, payload.blockClass ?? "-", payload.attempts);
```

Sur axios, le modèle habituel pour les cibles HTTPS derrière un proxy est de passer un agent de `https-proxy-agent` en tant que `httpsAgent` et de définir `proxy: false`, ou d'appeler le point de terminaison REST ci-dessus et de sauter complètement le protocole proxy.

## Étape 6 : quand vous n'avez pas besoin de l'unlocker

L'unlocker est facturé par Go sur chaque octet qu'il déplace, réessais et chargements de pages de navigateur inclus. Lui envoyer des pages qu'une requête simple récupère bien brûle le solde pour rien. Dans notre exécution, ceux-ci sont revenus bien sans aucune aide :

| Site | Simple, serveur de datacenter | Simple, proxy résidentiel | Web Unlocker |
| --- | --- | --- | --- |
| docs.python.org (page urllib.request) | 200 | 200 | 200, 1,6 s |
| books.toscrape.com | 200 | 200 | 200, 1,9 s |
| news.ycombinator.com | 200 | 200 | 200, 4,1 s |
| en.wikipedia.org (Web scraping) | 200 | 200 | 200, 1,9 s |
| github.com (dépôt scrapy) | 200 | 200 | 200, 2,0 s |
| bbc.com (Technologie) | 302 redirection | 302 redirection | 200, 1,4 s |

La ligne BBC n'est pas un blocage. Notre client de test n'a pas suivi les redirections, et le 302 est le site vous envoyant vers une autre URL. `requests` suit les redirections par défaut et atterrit sur la page.

Donc le bon modèle est d'abord en simple, unlocker sur un blocage détecté. La détection nécessite de l'attention. Le scan de marqueur dans notre propre harnais de test a signalé les pages Wikipedia et GitHub comme bloquées car leurs corps contiennent le mot 'captcha', même si les deux ont renvoyé la vraie page avec un 200. Vérifiez le code de statut, l'en-tête `cf-mitigated` de Cloudflare et le titre de la page, pas tout le corps :

```python
from urllib.parse import urlsplit

BLOCK_STATUSES = {403, 429, 503}
BLOCK_TITLES = (
    "just a moment",                        # Cloudflare challenge
    "attention required",                   # Cloudflare block page
    "access denied",                        # Akamai and others
    "security check",
    "access to this page has been denied",  # PerimeterX
)
needs_unlocker: set[str] = set()            # domains that blocked plain requests

def looks_blocked(r: requests.Response) -> bool:
    if r.status_code in BLOCK_STATUSES:
        return True
    if r.headers.get("cf-mitigated") == "challenge":
        return True
    return any(t in title_of(r.text).lower() for t in BLOCK_TITLES)

def fetch(url: str, session_id: str | None = None) -> tuple[requests.Response, str]:
    host = urlsplit(url).hostname or ""
    if host not in needs_unlocker:
        try:
            r = requests.get(url, headers=HEADERS, timeout=20)
            if not looks_blocked(r):
                return r, "plain"
        except requests.RequestException:
            pass  # a reset or a timeout counts as a block too
        needs_unlocker.add(host)
    return unlock_get(url, session_id), "unlocker"   # from step 4; raises Blocked

for u in ("https://docs.python.org/3/library/urllib.request.html", URL):
    resp, route = fetch(u)
    print(route, resp.status_code, title_of(resp.text)[:60])
```

La branche `except` est importante. La catégorie d'ordinateurs portables de Best Buy n'a jamais donné à nos clients simples une réponse HTTP du tout : la connexion a échoué à la fois depuis le serveur de datacenter et le proxy résidentiel. Via l'unlocker, la même URL a renvoyé `200` à la première tentative en 6,3 secondes. Un scraper qui ne vérifie que les codes de statut n'escaladerait jamais celui-là.

## Résultats mesurés : quatre sites qui avaient besoin de l'unlocker

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

| Site | Simple, serveur de datacenter | Simple, proxy résidentiel | Web Unlocker |
| --- | --- | --- | --- |
| Indeed (recherche d'emploi) | 403, page 'Security Check' | 403, 'Enable JavaScript and cookies to continue' | 200, 1 tentative, 2,6 s |
| Glassdoor (avis sur les entreprises) | 403, page 'Security' avec captcha | 403, 'Enable JavaScript and cookies to continue' | 200, 2 tentatives + navigateur, 17,2 s |
| Walmart (recherche) | 307 redirection, pas de page de résultats | 307 redirection, pas de page de résultats | 200, 1 tentative, 4,8 s |
| Best Buy (catégorie d'ordinateurs portables) | Connexion échouée, pas de réponse | Connexion échouée, pas de réponse | 200, 1 tentative, 6,3 s |

En bref : Indeed, Walmart et Best Buy ont réussi à la première tentative TLS sans navigateur. Glassdoor a eu besoin du chemin complet, deux tentatives TLS puis un navigateur sans tête pour répondre au défi JavaScript, c'est pourquoi cela a pris 17,2 secondes. Pour Walmart, nous n'avons pas enregistré où le 307 pointait. Ce que nous pouvons dire, c'est qu'aucun client simple n'a reçu la page de résultats et que l'unlocker l'a fait.

Les requêtes répétées à un site protégé par défi deviennent plus rapides lorsque vous épinglez une session. Envoyez la même valeur d'en-tête `x-qp-session`, ou gardez le même `-session-<id>` dans le nom d'utilisateur du proxy, et le cookie de dédouanement gagné par le navigateur est réutilisé. Dans un test Glassdoor le 29 septembre 2026, avec la session épinglée dans le nom d'utilisateur et deux connexions distinctes, la première requête a pris 14,0 secondes via le navigateur et la seconde 1,4 secondes, avec `x-qp-clearance: hit`. Notre [guide pour scraper les offres d'emploi Indeed](https://quantumproxies.io/blog/scrape-indeed-job-postings) couvre ce qu'il faut faire avec la page une fois que vous l'avez.

![La vérification de blocage du tutoriel, requête simple d'abord : une fonction looks_blocked signale une réponse par son code de statut, 403, 429 ou 503, par l'en-tête cf-mitigated: challenge ou par un titre de page de blocage, et ce n'est qu'alors que l'URL passe par le Web Unlocker. On évite d'analyser tout le corps, car cela signalait Wikipedia.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1790750868/blog/generated/qp-unl-g1-diag2.png)
*D'abord en simple, unlocker sur un blocage détecté. L'unlocker facture chaque octet qu'il déplace, donc la requête la moins chère est celle qui fonctionne sans.*

Si vous êtes arrivé ici à partir d'un 403 qui ne se produit qu'en dehors du navigateur, notre explication sur [pourquoi curl obtient un 403 lorsque le navigateur fonctionne](https://quantumproxies.io/blog/curl-403-but-browser-works) approfondit le diagnostic. Pour un aperçu site par site de la couche dont vous avez réellement besoin, voir [web unlocker vs proxy résidentiel, testé](https://quantumproxies.io/blog/web-unlocker-vs-residential-proxy).

## Questions fréquemment posées

### Existe-t-il une API de web unlocker gratuite ?

Les pièces open-source sont gratuites : requests, curl_cffi pour un TLS semblable à un navigateur, et Playwright pour un vrai navigateur. Les exécuter correctement est le travail, car vous avez également besoin d'IPs de sortie propres et d'un navigateur pour les défis JavaScript. Sur QuantumProxies.io, créer un compte est gratuit, et le trafic de l'unlocker est facturé par Go à partir d'un solde prépayé que vous rechargez dans le tableau de bord.

### Pouvez-vous scraper Glassdoor ou Zillow avec Python ?

Dans notre test du 29 septembre 2026, les requêtes simples vers une page d'avis Glassdoor ont obtenu un 403 à la fois d'un serveur de datacenter et d'un proxy résidentiel, et l'unlocker a renvoyé la page après une escalade de navigateur. Zillow a bloqué le serveur de datacenter mais a servi la page via un proxy résidentiel simple. Vérifiez les conditions de chaque site avant de collecter quoi que ce soit.

### Le web scraping peut-il être détecté ?

Oui. Les sites évaluent le type de réseau et l'historique de l'IP, la poignée de main TLS, les paramètres HTTP/2, l'ordre des en-têtes et, sur les pages de défi, ce que JavaScript rapporte sur le navigateur. Une requête Python simple échoue à plusieurs de ces tests à la fois. C'est pourquoi changer uniquement le User-Agent aide rarement, et pourquoi l'unlocker change l'IP, l'empreinte et, si nécessaire, tout le client.

### Dois-je installer un certificat pour utiliser le proxy de transfert ?

Seulement pour HTTPS via le proxy de transfert classique, car l'unlocker termine le TLS pour réécrire la requête. Soit vous sautez la vérification pour cette session (verify=False en Python, -k en curl) soit vous téléchargez le CA depuis /api/v1/scraper/unlock/ca avec votre clé API et vous lui faites confiance. Le point de terminaison REST n'a besoin d'aucun certificat.

### Comment garder la même IP sur plusieurs requêtes ?

Envoyez la même valeur d'en-tête x-qp-session sur chaque requête via le proxy de transfert, ou mettez le même -session-\<id> dans le nom d'utilisateur du proxy. Les appels partageant cette session partent de la même sortie, et un cookie de dédouanement gagné par le navigateur est réutilisé. Sur le point de terminaison REST, passez un sessionId avec la rotation réglée sur sticky. Une session sticky peut durer de 3 à 1 440 minutes.

Le modèle entier tient en une phrase : récupérez simplement, détectez un blocage par statut, en-tête et titre, n'envoyez que ces URL via l'unlocker, et enregistrez les en-têtes x-qp pour savoir combien chaque page vous a coûté. Commencez avec le niveau résidentiel, et gardez le niveau Mobile pour les cibles qui refusent également les plages résidentielles.

[Essayez le Web Unlocker sur des sorties résidentielles](https://quantumproxies.io/web-unlocker/residential)
