Un schéma courant dans les conversations de support : quelqu’un passe aux proxies résidentiels, le taux de blocage s’améliore, puis un sous-ensemble tenace de cibles continue à les refuser malgré tout. L’IP est propre, la géographie est correcte, et les requêtes se font quand même contester. Presque toujours, la réponse se trouve dans les en-têtes, car l’adresse vous amène à la porte et les en-têtes décident si ce qui franchit le seuil ressemble à un navigateur.
L’erreur qui sous-tend la plupart des problèmes d’en-têtes est de les considérer comme une liste de valeurs à définir correctement. Ce n’est pas le cas. Il s’agit d’un ensemble d’affirmations qui doivent concorder entre elles et avec tout le reste de votre connexion, et une requête construite à partir de pièces mal assorties est plus suspecte qu’une requête sans aucun effort.
La cohérence prime sur toute valeur individuelle
Commençons ici, car cela recadre tout ce qui suit. Un serveur n’évalue pas votre User-Agent isolément. Il voit un ensemble : la chaîne UA, les client hints qui l’accompagnent, le jeu d’en-têtes et son ordre, la valeur Accept-Language, la négociation TLS sous-jacente, et l’IP d’où provient le tout. Les vrais navigateurs produisent des ensembles cohérents en interne car le même logiciel a généré l’ensemble.
Les scrapers en produisent des incohérents par accident. Un UA Chrome accompagné du jeu d’en-têtes d’une bibliothèque HTTP Python, un UA Windows sur une empreinte TLS appartenant à un outil Linux, une sortie américaine envoyant Accept-Language: de-DE, ou un navigateur prétendu qui ne demande jamais les ressources qu’un navigateur demanderait. Aucun de ces éléments ne dit individuellement « bot », mais la contradiction, si, et les contradictions sont bien plus faciles à détecter de manière fiable que n’importe quel signal isolé. C’est la même logique que l’appariement appareil-réseau dans les navigateurs antidétection : chaque couche doit raconter la même histoire.
L’objectif n’est donc pas la chaîne UA la plus convaincante. C’est une requête où l’UA, les en-têtes, l’empreinte TLS et l’adresse de sortie décrivent tous le même visiteur plausible.
Ce qu’un vrai navigateur envoie réellement
Si vous ne définissez que User-Agent, vous êtes déjà incohérent, car aucun navigateur n’envoie un UA et rien d’autre. Une requête Chrome moderne pour une page transporte, au minimum, un ensemble de ce type :
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua: "Chromium";v="141", "Not?A_Brand";v="24", "Google Chrome";v="141"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
Trois éléments méritent d’être compris plutôt que simplement copiés.
Les client hints doivent correspondre à l’UA. La liste de marques sec-ch-ua, sec-ch-ua-platform et sec-ch-ua-mobile sont une reformulation structurée de ce que la chaîne UA affirme déjà. Si votre UA indique Chrome sur Windows et que votre indice de plateforme indique macOS, ou que la version dans la liste des marques diverge de la version dans l’UA, vous vous êtes contredit dans deux en-têtes adjacents. N’envoyer aucun client hint tout en prétendant être un Chrome récent constitue également une incohérence, car un vrai Chrome les envoie.
Les en-têtes Sec-Fetch décrivent le contexte. Ils indiquent au serveur quel type de requête il s’agit : une navigation de premier niveau, une récupération de sous-ressource, une XHR de même origine. Un chargement de page est Dest: document, Mode: navigate, Site: none lorsqu’ouvert directement ou same-origin en suivant un lien interne. Une XHR vers une API est Dest: empty, Mode: cors. Se tromper là-dessus est particulièrement révélateur car ces en-têtes sont automatiques dans un navigateur et faciles à oublier dans un script.
Accept-Encoding est une affirmation que vous devez honorer. N’annoncez br et zstd que si votre client peut réellement les décompresser. Certaines bibliothèques annoncent des encodages qu’elles échouent ensuite à gérer, ce qui produit soit des erreurs, soit un repli différent de ce qu’un navigateur négocierait.
Faites correspondre Accept-Language à votre pays de sortie
Ce point est spécifique au travail avec les proxies et constitue l’incohérence auto-infligée la plus courante. Si vous sortez via une IP résidentielle allemande et envoyez Accept-Language: en-US, vous avez décrit un visiteur dont le navigateur est configuré en anglais américain, assis sur une connexion domestique allemande. Cela arrive dans la vraie vie, mais c’est suffisamment inhabituel pour constituer un signal, et plus concrètement, cela peut changer ce que vous recevez en retour : de nombreux sites servent du contenu en fonction de cet en-tête, donc une tâche de collecte géociblée peut récupérer la mauvaise langue tout en semblant fonctionner.
Liez la langue à la sortie, idéalement au même endroit où vous choisissez le pays, afin que les deux ne puissent jamais diverger :
import requests
MARKETS = {
"us": "en-US,en;q=0.9",
"de": "de-DE,de;q=0.9,en;q=0.8",
"fr": "fr-FR,fr;q=0.9,en;q=0.8",
"br": "pt-BR,pt;q=0.9,en;q=0.8",
}
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36")
def fetch(url, country):
proxy = f"http://customer-USERNAME-country-{country}:PASSWORD@p.shifter.io:443"
headers = {
"User-Agent": UA,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,*/*;q=0.8",
"Accept-Language": MARKETS[country], # suit la sortie, toujours
"Accept-Encoding": "gzip, deflate, br",
"sec-ch-ua": '"Chromium";v="141", "Not?A_Brand";v="24", '
'"Google Chrome";v="141"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"', # concorde avec l'UA
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Upgrade-Insecure-Requests": "1",
}
return requests.get(url, headers=headers,
proxies={"http": proxy, "https": proxy}, timeout=20)
La même rigueur s’applique au fuseau horaire et à la locale lorsque vous pilotez un navigateur, où ces éléments sont observables séparément et doivent également correspondre à la sortie, ce qui devient plus exigeant lorsque vous travaillez avec un ciblage au niveau de la ville.
Ne faites pas tourner le User-Agent de manière aléatoire
C’est un conseil qui circule largement et qui fait plus de mal que de bien. Choisir un UA aléatoire par requête produit un schéma qu’aucune population réelle ne présente : une IP qui est Chrome sur Windows, puis Safari sur un Mac, puis Firefox sur Linux, en l’espace d’une minute. Pire encore, si vous maintenez une session persistante pour qu’une seule adresse serve un flux à plusieurs étapes, changer l’UA en cours de route signifie que le même visiteur a apparemment changé d’appareil entre le clic sur la recherche et la consultation d’un résultat.
Le modèle cohérent consiste en une identité par session. Choisissez un UA plausible, conservez-le pendant toute la durée de cette session, et gardez tout le reste cohérent avec lui. Si vous voulez de la variété au sein de votre parc, variez entre les sessions plutôt qu’entre les requêtes, et variez dans des proportions réalistes plutôt qu’uniformément entre tous les navigateurs ayant jamais existé. Gardez également les versions à jour : un UA revendiquant une version de navigateur vieille de trois ans est lui-même anormal, car les installations réelles se mettent à jour.
Les en-têtes ne sont qu’une couche
Il vaut la peine d’être honnête quant au plafond ici. Des en-têtes parfaits ne font pas ressembler un client Python à Chrome, car la couche sous-jacente diffère toujours. La négociation TLS et les paramètres HTTP/2 que produit votre client forment une empreinte qui leur est propre, et un UA Chrome sur une empreinte qui indique Python est exactement la contradiction évoquée au début. Cette incohérence fait l’objet de l’article sur l’empreinte TLS et HTTP/2, et c’est pourquoi certaines cibles fortement défendues restent hors de portée pour les clients HTTP simples quel que soit le travail sur les en-têtes, ce qui constitue l’un des cas justifiant de recourir à un navigateur headless.
L’ordre des en-têtes compte pour la même raison. Les navigateurs émettent les en-têtes dans un ordre stable ; de nombreuses bibliothèques HTTP les émettent par ordre alphabétique ou dans l’ordre d’insertion, ce qui constitue une autre manière dont l’ensemble peut trahir son origine même lorsque chaque valeur est correcte. Certains clients permettent de contrôler l’ordre, et là où cela compte, faire correspondre l’ordre d’un vrai navigateur vaut l’effort. Le catalogue plus large de ces signaux se trouve dans les empreintes qui peuvent bloquer l’extraction de données, et les versions courantes auto-infligées sont dans les erreurs qui déclenchent la détection.
Une brève liste de contrôle
Envoyez un jeu complet d’en-têtes de navigateur, pas seulement un User-Agent. Faites en sorte que les client hints concordent avec l’UA sur la marque, la version, la plateforme et l’indicateur mobile. Définissez les en-têtes Sec-Fetch pour décrire le type de requête réel. Liez Accept-Language au pays de sortie et gardez-les dans le même chemin de code. N’annoncez que les encodages que vous pouvez décoder. Maintenez une identité par session plutôt que de faire tourner à chaque requête, et gardez les versions d’UA à jour. Vérifiez ensuite si votre empreinte TLS concorde avec le navigateur que vous prétendez être, car c’est la couche que les en-têtes ne peuvent pas corriger.
Si une cible continue à vous refuser après tout cela, le problème s’est déplacé ailleurs : le rythme, la réputation de l’IP, ou des signaux comportementaux, qui constituent l’ensemble plus large couvert dans éviter les blocages et scraper des sites fortement protégés.
L’essentiel
Les en-têtes constituent la seconde moitié de l’identité qu’entame votre proxy. Une adresse résidentielle propre rend la connexion banale ; un ensemble d’en-têtes cohérent rend la requête banale, et la cohérence est tout l’enjeu. Chaque affirmation doit concorder avec chaque autre affirmation : les client hints avec l’UA, la langue avec le pays de sortie, les métadonnées de fetch avec le type de requête, les encodages avec vos capacités réelles, et l’ensemble avec l’empreinte TLS sous-jacente. Une identité par session, maintenue stable, l’emporte toujours sur une rotation astucieuse.
Cette couche de connexion est ce que fournissent les proxies résidentiels, de vraies adresses de qualité domestique avec ciblage par pays et par ville afin que la géographie que revendiquent vos en-têtes soit la géographie par laquelle vous sortez réellement, facturés par GB afin qu’ajuster les requêtes pour récupérer moins vous coûte moins cher.