Scraping

Comment savoir quand un site vous sert du contenu faux ou bloqué

L'échec de scraping dangereux n'est pas le 403 que vous voyez, c'est le 200 OK plein de déchets. Comment détecter les blocages doux, les honeypots et les données empoisonnées.

Chris Collins

Chris Collins

31 juillet 2026 · 9 min de lecture

L’échec de scraping auquel tout le monde se prépare est l’évident : un 403, un 429, une connexion qui tombe en timeout. Vous pouvez le voir, le compter et le réessayer. L’échec qui empoisonne vraiment votre jeu de données est l’inverse : un 200 OK qui a l’air parfaitement réussi et ne contient rien de ce que vous vouliez. Une page de blocage déguisée en réponse normale. Un écran de défi. Une coquille vide. Une page de données fabriquée exprès pour donner de mauvaises informations à un bot.

Un scraper qui renvoie une erreur est agaçant. Un scraper qui « réussit » avec des déchets est dangereux, parce que vous ne le découvrez pas avant que les mauvaises données soient déjà dans votre entrepôt, dans un rapport ou dans un modèle. C’est l’autre moitié de pourquoi les scrapers se font bloquer : les systèmes anti-bot modernes préfèrent de plus en plus tromper en silence plutôt que rejeter bruyamment, précisément parce qu’un échec silencieux vaut plus pour eux qu’un échec visible. Voici comment l’attraper.

Le code de statut n’est pas la vérité

La première habitude à casser est de faire confiance aux codes de statut HTTP comme signal de succès. Un 200 signifie que le serveur a envoyé une réponse, pas qu’il a envoyé la réponse que vous avez demandée. Les sites renvoient régulièrement des pages de blocage, des CAPTCHA et des murs de consentement avec un 200, parce que cela garde les scrapers naïfs contents et silencieux tout en ne leur servant rien. Traitez le code de statut comme un signal faible et validez le corps à chaque requête.

Les catégories de 200 trompeur méritent d’être nommées, car chacune a un indice différent :

  • Blocages doux et interstitiels. Une page de défi ou de « verify you are human » renvoyée avec un 200, souvent bien plus petite qu’une vraie page.
  • Écrans de défi et CAPTCHA. Servis en ligne là où le contenu devrait être. Lié à comment les défenses anti-bot ont évolué.
  • Contenu dégradé ou tronqué. Un mur de login, un stub « enable JavaScript », ou un squelette vide là où les données étaient censées se rendre.
  • Soft-fails de limite de débit. Des résultats périmés, mis en cache ou vides renvoyés au lieu d’une erreur une fois que vous franchissez un seuil.
  • Mauvaise géo ou personnalisation. La bonne page pour le mauvais pays, la mauvaise devise ou l’état déconnecté, techniquement valide, silencieusement inutile.
  • Honeypots et données empoisonnées. Contenu servi délibérément aux bots : faux prix, liens pièges, ou enregistrements d’apparence plausible avec des valeurs impossibles.

Validez le contenu, pas seulement la livraison

La défense la plus efficace est une assertion de contenu à chaque réponse : une vérification bon marché que la page contient ce qu’une vraie page doit contenir. Choisissez un invariant qu’un résultat authentique a toujours et qu’une page de blocage n’a jamais : un élément précis, un champ requis, une longueur minimale plausible, et faites échouer la requête s’il manque.

def is_valid_product_page(html, parsed):
# Une vraie page produit a toujours ceci. Une page de blocage, aucun.
if len(html) < 2000: # les pages de blocage sont souvent minuscules
return False
if parsed.select_one("h1.product-title") is None:
return False # l'element ancre a disparu
if parsed.select_one('[data-price]') is None:
return False # le champ pour lequel on venait manque
return True

Le changement clé est qu’un élément ancre absent est un échec, pas un résultat vide. Si le sélecteur dont vous dépendez est absent, n’enregistrez pas une ligne vide et ne passez pas à la suite, traitez la réponse comme un blocage doux et réessayez avec une identité fraîche exactement comme vous le feriez pour un 403. Enregistrer des blancs, c’est ainsi qu’un blocage devient en silence des milliers de lignes vides que personne ne remarque avant bien plus tard.

Surveillez la forme de vos réponses, pas seulement les individuelles

Les vérifications individuelles attrapent les déchets évidents. Les vérifications distributionnelles attrapent la dérive subtile, et ce sont elles qui séparent un pipeline robuste d’un pipeline fragile.

  • Taille de réponse. Les pages de blocage et de défi tendent à être petites et uniformes. Un effondrement soudain de la taille moyenne de page sur un lot, ou un pic de réponses regroupées sur un décompte d’octets exact, est une signature de blocage même si chacune renvoie 200.
  • Hachage des réponses. Hachez une version normalisée de chaque corps de réponse. Quand le même hash se répète soudain sur beaucoup d’URLs différentes, on vous sert une page de blocage encore et encore, pas du contenu réel et varié.
  • Taux de remplissage des champs. Suivez le pourcentage d’enregistrements où chaque champ s’est vraiment peuplé. Un champ qui était rempli à 98 pour cent hier et à 4 pour cent aujourd’hui n’est pas devenu plus rare, vous avez commencé à recevoir des pages tronquées.
  • Taux de succès par hôte. Une baisse sur une cible précise, tandis que les autres restent stables, c’est cette cible qui change de posture envers vous, digne d’être attrapée avant qu’elle ne gâche une course entière. C’est le même signal par cible qui compte au monitoring d’un pipeline de scraping à l’échelle.

Rien de tout cela n’a besoin de machine learning. Ce sont des compteurs et des lignes de base simples, et ils sont la différence entre repérer un blocage dans les cent premières requêtes et le repérer après un million.

Signatures de pages de blocage connues

Tenez une petite bibliothèque de phrases et de marqueurs qui n’apparaissent jamais que sur des pages de blocage, de défi ou d’erreur, et signalez toute réponse qui les contient quel que soit le code de statut.

BLOCK_MARKERS = (
"verify you are human",
"unusual traffic",
"access denied",
"enable javascript to continue",
"request blocked",
)
def looks_blocked(text):
low = text.lower()
return any(marker in low for marker in BLOCK_MARKERS)

Gardez-la courte et spécifique pour qu’elle ne fasse pas de faux positifs sur du vrai contenu qui parle par hasard de ces sujets, et faites-la grandir à mesure que vous rencontrez de nouvelles défenses. Une page de défi que vous pouvez reconnaître est un défi que vous pouvez réessayer au lieu de le stocker.

Honeypots et données empoisonnées

La catégorie la plus vicieuse est le contenu conçu pour avoir l’air réel. Deux défenses comptent ici.

D’abord, ne suivez pas les liens pièges. Les liens honeypot sont couramment cachés aux humains avec display:none, visibility:hidden, taille zéro, positionnement hors écran, ou aria-hidden, et n’existent que pour attraper un bot qui suit chaque ancre. Un crawler qui respecte la visibilité, et ignore les liens qu’un humain ne pourrait jamais cliquer, en esquive la plupart.

Ensuite, faites un contrôle de bon sens des données elles-mêmes. Les enregistrements empoisonnés sont construits pour passer un parseur naïf mais tendent à violer des règles de domaine basiques : des prix nuls ou absurdement élevés, des dates dans le futur, des quantités qui ne peuvent exister, des valeurs hors de toute fourchette plausible. Validez contre les fourchettes que votre domaine autorise vraiment, et mettez en quarantaine les enregistrements qui les violent au lieu de leur faire confiance parce que le HTML s’est parsé proprement.

Requêtes canari

L’alerte précoce la plus fiable est un canari : récupérez périodiquement une page dont vous connaissez déjà le contenu correct, et affirmez qu’il correspond toujours. Quand votre canari commence à renvoyer une page de blocage ou les mauvaises données, vous savez que la cible s’est retournée contre vous, immédiatement et sans ambiguïté, sans avoir à le déduire d’un lent déclin de la qualité des données. Faites tourner des canaris par cible et par géo, puisqu’un site peut bloquer les IP de sortie d’un pays tout en laissant un autre intact.

Où s’insèrent les proxys

Des IP propres réduisent la fréquence à laquelle on vous bloque doucement au départ, un pool avec une bonne réputation passe là où les adresses marquées se font silencieusement servir une page de défi. Cela baisse le taux de réponses trompeuses que vous avez à attraper, mais n’élimine pas le besoin de les attraper, car les blocages sont de plus en plus invisibles par conception et aucune IP n’est immunisée. Les deux marchent ensemble : la détection vous dit qu’une réponse était un blocage doux, et un pool résidentiel rotatif vous donne une identité fraîche pour la réessayer, exactement comme vous traiteriez un échec dur. Réinjectez chaque blocage doux détecté dans votre logique de réessai et de rotation (sticky vs rotatif), et traitez un pic persistant de taux de blocage par cible comme un signal de recul plutôt que de martèlement (éviter les blocages et scraper de façon responsable s’appliquent tous deux).

En résumé

Supposez que la réponse ment jusqu’à preuve du contraire. Validez le contenu à chaque requête contre un invariant qu’une vraie page a toujours, et traitez une ancre absente comme un échec à réessayer, pas une ligne vide à stocker. Surveillez la distribution des tailles de réponse, des hachages et des taux de remplissage pour qu’un blocage qui renvoie 200 apparaisse quand même comme une anomalie. Tenez une courte liste de signatures de pages de blocage connues, refusez de suivre les liens pièges cachés, faites un contrôle de bon sens des données contre des règles de domaine, et faites tourner des canaris pour apprendre à l’instant où une cible se retourne contre vous.

Faites cela, et l’échec silencieux, celui qui corrompt en silence un jeu de données pendant des semaines, cesse d’être silencieux. Associez-le à un pool résidentiel propre pour être bloqué doucement moins souvent au départ, et les forfaits au Go vous laissent valider cela contre vos propres cibles sans vous engager d’avance.

Prêt à commencer ?

Essayez les proxies résidentiels de Shifter, 205M+ IPs, 195+ pays, à partir de $0.75/GB.

Commencer