Skip to content
Advanced / 8 min read

Comment créer un rotateur de proxys de suivi de position avec les proxys résidentiels IPRoyal et Python

Un tutoriel axé sur le code pour router un outil de suivi de position Python à travers des proxys résidentiels SOCKS5 IPRoyal, avec ciblage par pays, sessions persistantes, limites de concurrence et gestion des nouvelles tentatives pour des vérifications SERP fiables par pays.

SOCKS5 HTTP(S) Scraping Web Suivi SEO

Aperçu

Les outils de suivi de position font une seule chose, encore et encore : interroger un moteur de recherche depuis un emplacement donné et enregistrer la position à laquelle un domaine apparaît. Ce schéma se casse rapidement sans proxys. Les moteurs de recherche localisent les résultats selon l'adresse IP du demandeur, et ils limitent ou bloquent les IP qui les interrogent à grande échelle.

Ce tutoriel montre comment exécuter un outil de suivi de position Python via des proxys résidentiels IPRoyal en SOCKS5 : construire l'URL de proxy, garder les sessions persistantes lorsque vous avez besoin de la pagination, effectuer une rotation lorsque ce n'est pas le cas, et gérer les échecs que vous rencontrerez réellement en production.

IPRoyal propose des proxys résidentiels, ISP et datacenter via HTTP, SOCKS5 et un tunneling de type Shadowsocks, annonce un pool de plus de 32 M d'IP et facture le trafic résidentiel à l'usage, avec des forfaits mensuels fixes disponibles sur certains produits. L'hôte de passerelle, le port, le nom d'utilisateur et le mot de passe se trouvent dans votre tableau de bord IPRoyal.

Quel type de proxy convient au suivi de position ?

Type de proxy Idéal pour le suivi de position Compromis
Résidentiel (rotatif) SERP au niveau du pays et de la ville ; le vaste pool de plus de 32 M d'IP répartit le volume de requêtes Réponses plus lentes ; l'IP de sortie change entre les requêtes
ISP (résidentiel statique) Vérifications reproductibles depuis la même adresse Empreinte géographique plus réduite que le résidentiel
Datacenter Travaux légers et à gros volume qui ne relèvent pas du scraping de SERP Rapide et peu coûteux, mais facilement identifiable par les moteurs de recherche

Rotation vs sessions persistantes

  • Effectuez une rotation à chaque requête lorsque vous n'avez besoin que d'une page de résultats par mot-clé.
  • Maintenez une session persistante (de quelques minutes jusqu'à environ 30 minutes) lorsque vous suivez la pagination, développez des blocs de questions associées ou avez besoin de la même IP de sortie pour une séquence de requêtes.
  • Ciblez le pays qui correspond à la locale sur laquelle vous faites votre rapport. Une vérification de position de-DE via une IP de sortie américaine n'est pas une vérification de position allemande.

Le ciblage de session et de pays est contrôlé via le nom d'utilisateur du proxy ou un sous-domaine de passerelle. Copiez le modèle exact depuis votre tableau de bord IPRoyal plutôt que de deviner ; le code ci-dessous utilise les espaces réservés -session- et -country-, très répandus.

Prérequis

  • Python 3.9 ou plus récent
  • Les extras SOCKS pour Requests : pip install "requests[socks]"
  • L'hôte de passerelle, le port, le nom d'utilisateur et le mot de passe IPRoyal depuis votre tableau de bord
  • Un fichier de mots-clés avec une ligne par mot-clé : requête, code pays et locale

Étapes

  1. Exportez vos identifiants pour qu'ils ne figurent jamais dans le fichier source.
export IPROYAL_HOST="gateway-host-from-dashboard"
export IPROYAL_PORT="gateway-port-from-dashboard"
export IPROYAL_USER="your-username"
export IPROYAL_PASS="your-password"

Sous Windows PowerShell, utilisez plutôt $env:IPROYAL_HOST = "...".

  1. Construisez une URL de proxy par requête, avec ciblage optionnel du pays et de la session. Utilisez le schéma socks5h pour que le DNS soit résolu au niveau du proxy, et non sur votre machine.
import os
import random
import string
import time

import requests

PROXY_HOST = os.environ['IPROYAL_HOST']
PROXY_PORT = os.environ['IPROYAL_PORT']
PROXY_USER = os.environ['IPROYAL_USER']
PROXY_PASS = os.environ['IPROYAL_PASS']

LOCALE_HEADERS = {
    'en-US': {'Accept-Language': 'en-US,en;q=0.9'},
    'de-DE': {'Accept-Language': 'de-DE,de;q=0.9'},
    'ja-JP': {'Accept-Language': 'ja-JP,ja;q=0.9'},
}


def new_session_id() -> str:
    """Jeton court et unique utilisé pour épingler une session persistante."""
    return ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))


def proxy_url(country: str, session_id: str | None = None) -> str:
    user = PROXY_USER
    if session_id:
        user = f'{user}-session-{session_id}'
    if country:
        user = f'{user}-country-{country.lower()}'
    return f'socks5h://{user}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}'
  1. Enveloppez la requête dans des nouvelles tentatives avec backoff exponentiel. Une vérification échouée ne doit pas interrompre une exécution de 2 000 mots-clés.
def fetch_serp(query: str, country: str, locale: str,
               session_id: str | None = None, attempts: int = 3) -> str | None:
    proxy = proxy_url(country, session_id)
    proxies = {'http': proxy, 'https': proxy}
    headers = {
        'User-Agent': 'Mozilla/5.0 (compatible; RankTracker/1.0)',
        **LOCALE_HEADERS.get(locale, {}),
    }

    for attempt in range(1, attempts + 1):
        try:
            response = requests.get(
                'https://your-permitted-search-endpoint/search',
                params={'q': query, 'hl': locale},
                proxies=proxies,
                headers=headers,
                timeout=30,
            )
            response.raise_for_status()
            return response.text
        except requests.RequestException as exc:
            print(f'échec de la tentative {attempt} pour {query!r} : {exc}')
            time.sleep(2 ** attempt)

    return None

Pointez l'URL vers une interface de recherche que vous êtes autorisé à interroger, comme une API officielle ou votre propre source de données autorisée. Le scraping d'un moteur de recherche peut enfreindre ses conditions d'utilisation, vérifiez donc avant de passer à l'échelle.

  1. Attribuez une session persistante par mot-clé afin que la pagination et les requêtes de suivi réutilisent la même IP de sortie.
def check_keyword(row: dict) -> dict:
    session_id = new_session_id()
    html = fetch_serp(row['query'], row['country'], row['locale'], session_id=session_id)
    return {
        'query': row['query'],
        'country': row['country'],
        'position': rank_position(html, row['domain']) if html else None,
        'status': 'ok' if html else 'failed',
    }
  1. Extrayez les positions avec l'analyseur que vous utilisez déjà.
def rank_position(html: str | None, domain: str) -> int | None:
    if not html:
        return None
    # Analysez avec lxml, selectolax ou BeautifulSoup, faites correspondre les URL de résultats
    # à `domain`, et renvoyez l'index (base 1) de la première correspondance.
    ...
  1. Limitez la concurrence. Les pools résidentiels sont vastes mais pas illimités, et marteler un endpoint depuis de nombreuses IP à la fois est le moyen le plus rapide de se faire bloquer.
from concurrent.futures import ThreadPoolExecutor


def run_batch(keywords: list[dict], workers: int = 8) -> list[dict]:
    with ThreadPoolExecutor(max_workers=workers) as pool:
        return list(pool.map(check_keyword, keywords))
  • Commencez avec 5 à 8 workers et augmentez le nombre uniquement tant que votre taux de réussite reste au-dessus d'environ 95 %.
  • Ajoutez un léger décalage aléatoire entre les nouvelles tentatives pour qu'elles n'arrivent pas en cadence synchronisée.
  1. Stockez le HTML brut pendant une courte période afin de pouvoir réanalyser l'historique sans scraper à nouveau.
import json
from pathlib import Path


def save_result(result: dict) -> None:
    out = Path('rank-results')
    out.mkdir(exist_ok=True)
    name = result['query'][:40].replace('/', '_')
    with (out / f'{name}.json').open('w', encoding='utf-8') as handle:
        json.dump(result, handle, ensure_ascii=False)
  1. Planifiez l'exécution et journalisez quelle configuration de proxy a produit chaque résultat. Lorsqu'un classement change, vous voulez savoir si c'est la SERP qui a bougé ou le proxy.
# Exemple d'entrée cron : exécute le tracker à 06:00 et 18:00 heure locale
0 6,18 * * * cd /srv/rank-tracker && /usr/bin/python3 track.py >> logs/rank.log 2>&1

Dépannage

  • 407 Proxy Authentication Required : le nom d'utilisateur ou le mot de passe est incorrect, ou un caractère spécial du mot de passe n'est pas encodé. Encodez en pourcentage @, :, / et # avant de placer le mot de passe dans l'URL.
  • Missing dependencies for SOCKS support : Requests a besoin de l'extra SOCKS. Exécutez pip install "requests[socks]" dans le même environnement virtuel que votre script.
  • Les résultats affichent le mauvais pays : le ciblage par pays fait partie du nom d'utilisateur du proxy. Affichez proxy_url(...) pour un mot-clé et confirmez que le code pays attendu apparaît avant de blâmer le moteur de recherche.
  • Chaque requête utilise la même IP : vous réutilisez un ID de session. Omettez le composant de session pour laisser le fournisseur effectuer la rotation.
  • Tout un pays se fait soudainement bloquer : réduisez la concurrence pour ce pays, effectuez une rotation plus agressive et vérifiez si l'endpoint a commencé à renvoyer des pages de consentement ou de captcha au lieu des résultats.
  • Plus lent que prévu : le routage résidentiel ajoute de la latence. Augmentez le timeout à 30-45 secondes pour les proxys résidentiels ou ISP et réservez les proxys datacenter aux tâches hors SERP.

Résumé

Le suivi de position réussit ou échoue selon l'hygiène des proxys. Utilisez des proxys résidentiels ou ISP IPRoyal avec le schéma socks5h pour un DNS distant, épinglez une session par mot-clé lorsque vous avez besoin de continuité, effectuez une rotation lorsque ce n'est pas le cas, et plafonnez la concurrence pour que le pool continue de fonctionner. Avec les nouvelles tentatives, le stockage des réponses brutes et un journal du proxy utilisé, vous pouvez distinguer un véritable changement de classement d'un artefact de proxy.