[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-fr-scraping-serp-geocible-meilleures-pratiques":3},{"success":4,"payload":5,"related":59,"errors":126},true,{"id":6,"slug":7,"title":8,"excerpt":9,"body_html":10,"meta_title":11,"meta_description":12,"canonical_url":13,"canonical_override":14,"og_title":11,"og_description":12,"og_image_url":15,"faq_json":16,"key_takeaways":14,"allow_index":26,"noindex_google_only":26,"allow_follow":4,"published_at":27,"updated_at":27,"content_updated_at":28,"featured_image_url":29,"featured_image_alt":30,"card_image_url":31,"reading_time_minutes":32,"author":33,"categories":37,"tags":44,"alternates":45,"locale_paths":47,"views":58},198,"scraping-serp-geocible-meilleures-pratiques","Scraping SERP géociblé : meilleures pratiques pour développeurs","Guide technique complet pour le scraping SERP géociblé : paramètres uule, gl, hl, proxies résidentiels par ville, parsing du local pack, et patterns de production en Python.","\u003Ch2>Pourquoi le scraping SERP géociblé exige une précision au niveau ville\u003C\u002Fh2>\n\u003Cp>Le scraping SERP géociblé consiste à collecter les résultats de recherche Google en simulant une requête émise depuis une localisation géographique précise. Pour les SEO engineers et les développeurs de rank trackers, c'est la seule façon d'obtenir des données de classement représentatives de ce que voient réellement les utilisateurs dans un marché donné.\u003C\u002Fp>\n\u003Cp>Le problème est bien connu : une même requête comme « plombier urgence » retourne des résultats radicalement différents selon que l'utilisateur se trouve à Paris, Lyon ou Marseille. Google croise l'adresse IP, les paramètres d'URL, l'historique de recherche et le signal GPS pour personnaliser les résultats. D'après les observations de l'industrie, près de \u003Cstrong>50% des requêtes locales\u003C\u002Fstrong> produisent des SERP significativement différentes entre deux villes d'un même pays. Une collecte sans géociblage précis produit un jeu de données bruité et inexploitable.\u003C\u002Fp>\n\u003Cp>Le ciblage au niveau pays est insuffisant. Un proxy US vous donne une IP américaine, mais Google peut la géolocaliser à New York, Chicago ou Austin — trois marchés locaux distincts avec des classements différents. Pour du \u003Cstrong>local SERP scraping by city\u003C\u002Fstrong>, il faut contrôler simultanément trois couches : l'IP (proxy résidentiel géociblé), les paramètres d'URL (gl, hl, uule), et le domaine Google (google_domain).\u003C\u002Fp>\n\n\u003Ch2>Paramètres de localisation Google : gl, hl, google_domain, lr, uule, pws\u003C\u002Fh2>\n\u003Cp>Google expose plusieurs paramètres d'URL qui contrôlent la localisation des résultats. Les comprendre et les combiner correctement est la base du \u003Cstrong>geo-targeted SERP scraping\u003C\u002Fstrong>.\u003C\u002Fp>\n\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Paramètre\u003C\u002Fth>\n\u003Cth>Rôle\u003C\u002Fth>\n\u003Cth>Exemple\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>gl\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Code pays (2 lettres) pour le marché cible\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>gl=us\u003C\u002Fcode>, \u003Ccode>gl=fr\u003C\u002Fcode>, \u003Ccode>gl=de\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>hl\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Langue de l'interface Google\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>hl=fr\u003C\u002Fcode>, \u003Ccode>hl=en\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>google_domain\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Domaine Google à interroger\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>google.com\u003C\u002Fcode>, \u003Ccode>google.fr\u003C\u002Fcode>, \u003Ccode>google.de\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>lr\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Filtre la langue des résultats (\u003Ccode>lang_\u003C\u002Fcode> + code)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>lr=lang_fr\u003C\u002Fcode>, \u003Ccode>lr=lang_en\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>uule\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Localisation précise (ville\u002Fdistrict) encodée en base64\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>uule=w+CAIQICIR...\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pws\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Désactive la personnalisation web (0 = off)\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>pws=0\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\n\u003Cp>La combinaison \u003Cstrong>serp gl hl google_domain\u003C\u002Fstrong> couvre le ciblage national et linguistique. Le paramètre \u003Cstrong>uule\u003C\u002Fstrong> ajoute la précision ville. Et pws=0 garantit un baseline non personnalisé — indispensable pour des mesures de classement reproductibles. Pour aller plus loin sur les paramètres de recherche Google, consultez la documentation officielle de Google sur la recherche locale.\u003C\u002Fp>\n\n\u003Ch3>Construction du paramètre uule\u003C\u002Fh3>\n\u003Cp>Le paramètre uule Google encode un nom de lieu canonique en base64, précédé d'un préfixe fixe et d'une clé de longueur. Le format est :\u003C\u002Fp>\n\u003Cpre>\u003Ccode>uule = w+CAIQICI + length_key + base64(canonical_name)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Le nom canonique suit le format « Ville,État,Région,Pays » ou « Ville,Pays » selon la granularity souhaitée. La clé de longueur est un caractère unique qui encode la longueur de la chaîne base64. Voici la fonction de construction :\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode>import base64\n\n# Table de correspondance longueur base64 -&gt; clé\n# A=2, B=4, C=6, ... Z=52, a=54, b=56, ... 9=62, -=64\nLENGTH_KEYS = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-&quot;\n\ndef build_uule(canonical_name: str) -&gt; str:\n    &quot;&quot;&quot;Construit le paramètre uule Google à partir d&apos;un nom canonique.\n    \n    Args:\n        canonical_name: ex. &quot;Brooklyn,New York,United States&quot;\n                       ou &quot;Paris,France&quot;\n    Returns:\n        Valeur uule complète, ex. &quot;w+CAIQICIRQnJvb2tseW5u...\n    &quot;&quot;&quot;\n    encoded = base64.b64encode(canonical_name.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n    length = len(encoded)\n    # Chaque clé représente une longueur paire à partir de 2\n    key_index = (length - 2) \u002F\u002F 2\n    if key_index &lt; 0 or key_index &gt;= len(LENGTH_KEYS):\n        raise ValueError(f&quot;Longueur base64 hors plage: {length}&quot;)\n    key = LENGTH_KEYS[key_index]\n    return f&quot;w+CAIQICI{key}{encoded}&quot;\n\n# Exemples\nprint(build_uule(&quot;Austin,Texas,United States&quot;))\n# -&gt; w+CAIQICIRQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM=\nprint(build_uule(&quot;Paris,France&quot;))\n# -&gt; w+CAIQICIRUGFyaXMsRnJhbmNl\nprint(build_uule(&quot;München,Bayern,Germany&quot;))\n# -&gt; w+CAIQICITTXD8bmNoZW4sQmF5ZXJuLEdlcm1hbnk=\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Pour plus d'informations sur l'encodage base64, consultez \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBase64\" rel=\"noopener noreferrer\">l'article Wikipedia sur Base64\u003C\u002Fa>.\u003C\u002Fp>\n\n\u003Ch2>Cohérence entre géographie IP et paramètres SERP\u003C\u002Fh2>\n\u003Cp>C'est l'erreur la plus fréquente : un développeur configure \u003Ccode>gl=us\u003C\u002Fcode> et \u003Ccode>uule=Austin,Texas,United States\u003C\u002Fcode> mais route la requête via une IP française. Google détecte l'incohérence et peut soit ignorer les paramètres de localisation, soit déclencher un CAPTCHA, soit retourner des résultats hybrides non représentatifs.\u003C\u002Fp>\n\u003Cp>La règle d'or : les paramètres d'URL doivent toujours correspondre à la géographie de l'IP. Si vous ciblez Austin, Texas, votre proxy doit sortir des États-Unis — idéalement de la région de Texas. C'est là que le géociblage par ville des proxies résidentiels devient indispensable.\u003C\u002Fp>\n\u003Cp>Avec \u003Ca href=\"\u002Ffr\u002Flocations\">ProxyHat\u003C\u002Fa>, vous contrôlez le pays et la ville de sortie directement dans le nom d'utilisateur :\u003C\u002Fp>\n\u003Cpre>\u003Ccode># Proxy résidentiel à New York, US\nhttp:\u002F\u002Fuser-country-US-region-new_york-city-new_york_city:pass@gate.proxyhat.com:8080\n\n# Proxy résidentiel à Austin, Texas, US  \nhttp:\u002F\u002Fuser-country-US-region-texas-city-austin:pass@gate.proxyhat.com:8080\n\n# Proxy résidentiel à Paris, FR\nhttp:\u002F\u002Fuser-country-FR-region-ile_de_france-city-paris:pass@gate.proxyhat.com:8080\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>Implémentation en Python : requêtes SERP géociblées\u003C\u002Fh2>\n\u003Cp>Voici deux approches côte à côte : l'utilisation brute du proxy avec \u003Ccode>curl_cffi\u003C\u002Fcode> (impersonation Chrome) et un client ProxyHat structuré qui encapsule la logique de géociblage.\u003C\u002Fp>\n\n\u003Ch3>Approche 1 : proxy brut avec curl_cffi\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>from curl_cffi import requests as cffi_requests\nimport urllib.parse\nimport base64\nimport logging\nimport time\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\nLENGTH_KEYS = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-&quot;\n\ndef build_uule(canonical_name: str) -&gt; str:\n    encoded = base64.b64encode(canonical_name.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n    key = LENGTH_KEYS[(len(encoded) - 2) \u002F\u002F 2]\n    return f&quot;w+CAIQICI{key}{encoded}&quot;\n\ndef fetch_serp_raw(\n    query: str,\n    country: str = &quot;us&quot;,\n    city: str = &quot;Austin,Texas,United States&quot;,\n    region: str = &quot;texas&quot;,\n    hl: str = &quot;en&quot;,\n    google_domain: str = &quot;google.com&quot;,\n    max_retries: int = 3,\n) -&gt; str:\n    &quot;&quot;&quot;Récupère le HTML SERP avec proxy résidentiel géociblé.\n    \n    Utilise curl_cffi pour l&apos;impersonation Chrome (TLS fingerprint).\n    &quot;&quot;&quot;\n    uule = build_uule(city)\n    params = {\n        &quot;q&quot;: query,\n        &quot;gl&quot;: country,\n        &quot;hl&quot;: hl,\n        &quot;uule&quot;: uule,\n        &quot;pws&quot;: &quot;0&quot;,\n        &quot;num&quot;: &quot;100&quot;,\n    }\n    url = f&quot;https:\u002F\u002Fwww.{google_domain}\u002Fsearch?&quot; + urllib.parse.urlencode(params)\n    \n    # Proxy résidentiel correspondant à la ville cible\n    city_slug = city.split(&quot;,&quot;)[0].lower().replace(&quot; &quot;, &quot;_&quot;)\n    proxy_url = f&quot;http:\u002F\u002Fuser-country-{country.upper()}-region-{region}-city-{city_slug}:pass@gate.proxyhat.com:8080&quot;\n    \n    for attempt in range(1, max_retries + 1):\n        try:\n            response = cffi_requests.get(\n                url,\n                proxies={&quot;https&quot;: proxy_url, &quot;http&quot;: proxy_url},\n                impersonate=&quot;chrome&quot;,\n                timeout=30,\n            )\n            if response.status_code == 200:\n                logger.info(f&quot;SERP récupéré pour &apos;{query}&apos; à {city} (attempt {attempt})&quot;)\n                return response.text\n            elif response.status_code == 429:\n                wait = 2 ** attempt\n                logger.warning(f&quot;429 Too Many Requests, retry dans {wait}s&quot;)\n                time.sleep(wait)\n            elif response.status_code == 302:\n                logger.warning(f&quot;302 redirect — probable CAPTCHA. Changement d&apos;IP requis.&quot;)\n                raise RuntimeError(&quot;CAPTCHA détecté, rotation d&apos;IP nécessaire&quot;)\n            else:\n                logger.error(f&quot;HTTP {response.status_code} inattendu&quot;)\n                response.raise_for_status()\n        except Exception as e:\n            logger.error(f&quot;Tentative {attempt} échouée: {e}&quot;)\n            if attempt &lt; max_retries:\n                time.sleep(2 ** attempt)\n            else:\n                raise\n    raise RuntimeError(f&quot;Échec après {max_retries} tentatives&quot;)\n\n# Utilisation\nhtml = fetch_serp_raw(\n    query=&quot;best coffee shops&quot;,\n    country=&quot;us&quot;,\n    city=&quot;Austin,Texas,United States&quot;,\n    region=&quot;texas&quot;,\n    hl=&quot;en&quot;,\n    google_domain=&quot;google.com&quot;,\n)\nprint(f&quot;Longueur HTML: {len(html)} caractères&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3>Approche 2 : client ProxyHat structuré\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>from curl_cffi import requests as cffi_requests\nimport urllib.parse\nimport base64\nimport logging\nimport time\nfrom dataclasses import dataclass\nfrom typing import Optional\n\nlogger = logging.getLogger(__name__)\n\nLENGTH_KEYS = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-&quot;\n\n@dataclass\nclass GeoTarget:\n    &quot;&quot;&quot;Définit une cible géographique pour le scraping SERP.&quot;&quot;&quot;\n    country: str          # Code pays: &quot;us&quot;, &quot;fr&quot;, &quot;de&quot;\n    city: str             # Nom canonique: &quot;Austin,Texas,United States&quot;\n    hl: str = &quot;en&quot;        # Langue interface\n    google_domain: str = &quot;google.com&quot;\n    lr: Optional[str] = None  # Filtre langue résultats: &quot;lang_en&quot;\n\n    @property\n    def city_slug(self) -&gt; str:\n        return self.city.split(&quot;,&quot;)[0].lower().replace(&quot; &quot;, &quot;&quot;)\n\n    @property\n    def uule(self) -&gt; str:\n        encoded = base64.b64encode(self.city.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n        key = LENGTH_KEYS[(len(encoded) - 2) \u002F\u002F 2]\n        return f&quot;w+CAIQICI{key}{encoded}&quot;\n\nclass ProxyHatClient:\n    &quot;&quot;&quot;Client structuré pour le scraping SERP géociblé via ProxyHat.\n    \n    Encapsule la construction du proxy, la gestion des sessions,\n    et la logique de retry\u002Fbackoff. Référence: https:\u002F\u002Fdocs.proxyhat.com\u002F\n    &quot;&quot;&quot;\n    GATEWAY = &quot;gate.proxyhat.com&quot;\n    HTTP_PORT = 8080\n    SOCKS5_PORT = 1080\n\n    def __init__(self, username: str, password: str, use_socks5: bool = False):\n        self.username = username\n        self.password = password\n        self.port = self.SOCKS5_PORT if use_socks5 else self.HTTP_PORT\n        self.scheme = &quot;socks5&quot; if use_socks5 else &quot;http&quot;\n\n    def proxy_url(self, target: GeoTarget, session_id: Optional[str] = None) -&gt; str:\n        &quot;&quot;&quot;Construit l&apos;URL proxy avec géociblage pays + ville.&quot;&quot;&quot;\n        user_parts = [f&quot;user-country-{target.country.upper()}&quot;,\n                       f&quot;city-{target.city_slug}&quot;]\n        if session_id:\n            user_parts.append(f&quot;sid-{session_id}&quot;)\n        user = &quot;:&quot;.join(user_parts)\n        return f&quot;{self.scheme}:\u002F\u002F{user}:{self.password}@{self.GATEWAY}:{self.port}&quot;\n\n    def fetch_serp(\n        self,\n        query: str,\n        target: GeoTarget,\n        num_results: int = 100,\n        max_retries: int = 3,\n        session_id: Optional[str] = None,\n    ) -&gt; str:\n        &quot;&quot;&quot;Récupère le HTML SERP avec géociblage complet.&quot;&quot;&quot;\n        params = {\n            &quot;q&quot;: query,\n            &quot;gl&quot;: target.country,\n            &quot;hl&quot;: target.hl,\n            &quot;uule&quot;: target.uule,\n            &quot;pws&quot;: &quot;0&quot;,\n            &quot;num&quot;: str(num_results),\n        }\n        if target.lr:\n            params[&quot;lr&quot;] = target.lr\n        url = f&quot;https:\u002F\u002Fwww.{target.google_domain}\u002Fsearch?&quot; + urllib.parse.urlencode(params)\n        proxy = self.proxy_url(target, session_id)\n\n        for attempt in range(1, max_retries + 1):\n            try:\n                resp = cffi_requests.get(\n                    url,\n                    proxies={&quot;https&quot;: proxy, &quot;http&quot;: proxy},\n                    impersonate=&quot;chrome&quot;,\n                    timeout=30,\n                )\n                if resp.status_code == 200:\n                    logger.info(f&quot;OK: &apos;{query}&apos; @ {target.city} (attempt {attempt})&quot;)\n                    return resp.text\n                elif resp.status_code == 429:\n                    wait = min(2 ** attempt, 60)\n                    logger.warning(f&quot;429 — backoff {wait}s&quot;)\n                    time.sleep(wait)\n                elif resp.status_code in (302, 503):\n                    logger.warning(f&quot;{resp.status_code} — possible CAPTCHA&quot;)\n                    # Forcer une nouvelle IP en changeant de session\n                    session_id = f&quot;retry-{int(time.time())}&quot;\n                    proxy = self.proxy_url(target, session_id)\n                    time.sleep(3)\n                else:\n                    resp.raise_for_status()\n            except Exception as e:\n                logger.error(f&quot;Tentative {attempt}\u002F{max_retries}: {e}&quot;)\n                if attempt &gt;= max_retries:\n                    raise\n                time.sleep(2 ** attempt)\n        raise RuntimeError(f&quot;Échec après {max_retries} tentatives&quot;)\n\n# --- Utilisation ---\nclient = ProxyHatClient(username=&quot;user&quot;, password=&quot;pass&quot;)\n\ntarget_us_austin = GeoTarget(\n    country=&quot;us&quot;,\n    city=&quot;Austin,Texas,United States&quot;,\n    hl=&quot;en&quot;,\n    google_domain=&quot;google.com&quot;,\n    lr=&quot;lang_en&quot;,\n)\n\nhtml = client.fetch_serp(&quot;best coffee shops&quot;, target_us_austin)\nprint(f&quot;Résultats récupérés: {len(html)} chars&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Le client structuré sépare les préoccupations : \u003Ccode>GeoTarget\u003C\u002Fcode> définit le marché, \u003Ccode>ProxyHatClient\u003C\u002Fcode> gère l'infrastructure proxy. Pour découvrir d'autres patterns d'usage, consultez nos cas d'usage en \u003Ca href=\"\u002Ffr\u002Fuse-cases\u002Fserp-tracking\">SERP tracking\u003C\u002Fa> et \u003Ca href=\"\u002Ffr\u002Fuse-cases\u002Fweb-scraping\">web scraping\u003C\u002Fa>.\u003C\u002Fp>\n\n\u003Ch2>Parsing des résultats organiques et du local pack\u003C\u002Fh2>\n\u003Cp>Une fois le HTML récupéré, il faut extraire les résultats organiques et le local pack (les 3 résultats locaux avec carte). \u003Ccode>selectolax\u003C\u002Fcode> offre un parsing rapide et tolérant aux variations de structure de Google.\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode>from selectolax.parser import HTMLParser\nfrom dataclasses import dataclass\nfrom typing import List, Optional\nimport json\n\n@dataclass\nclass OrganicResult:\n    position: int\n    title: str\n    url: str\n    snippet: str\n    is_local_pack: bool = False\n\ndef parse_serp(html: str) -&gt; List[OrganicResult]:\n    &quot;&quot;&quot;Parse le HTML SERP Google : résultats organiques + local pack.\n    \n    Retourne une liste ordonnée par position dans la SERP.\n    &quot;&quot;&quot;\n    tree = HTMLParser(html)\n    results: List[OrganicResult] = []\n    position = 0\n\n    # --- Local Pack (3 résultats avec carte) ---\n    local_pack = tree.css_first(&quot;div[data-attrid=&apos;local_map_links&apos;]&quot;)\n    if not local_pack:\n        # Fallback : chercher le conteneur du local pack\n        local_pack = tree.css_first(&quot;div[jsname=&apos;Nj6kVc&apos;]&quot;) or tree.css_first(&quot;div.eT3znd&quot;)\n    \n    if local_pack:\n        for item in local_pack.css(&quot;div[data-cid]&quot;):\n            position += 1\n            title_el = item.css_first(&quot;div.dbg0p&quot;) or item.css_first(&quot;span&quot;)\n            link_el = item.css_first(&quot;a[href]&quot;)\n            snippet_el = item.css_first(&quot;span[jsname=&apos;Ud7xff&apos;]&quot;)\n            \n            results.append(OrganicResult(\n                position=position,\n                title=title_el.text(strip=True) if title_el else &quot;&quot;,\n                url=link_el.attributes.get(&quot;href&quot;, &quot;&quot;) if link_el else &quot;&quot;,\n                snippet=snippet_el.text(strip=True) if snippet_el else &quot;&quot;,\n                is_local_pack=True,\n            ))\n\n    # --- Résultats organiques ---\n    for div in tree.css(&quot;div.g&quot;):\n        link = div.css_first(&quot;a[href]&quot;)\n        title = div.css_first(&quot;h3&quot;)\n        snippet = div.css_first(&quot;div[data-sncf]&quot;) or div.css_first(&quot;span.aCOpRe&quot;)\n        if not link or not title:\n            continue\n        position += 1\n        results.append(OrganicResult(\n            position=position,\n            title=title.text(strip=True),\n            url=link.attributes.get(&quot;href&quot;, &quot;&quot;),\n            snippet=snippet.text(strip=True) if snippet else &quot;&quot;,\n            is_local_pack=False,\n        ))\n\n    return results\n\n# --- Utilisation ---\n# html = client.fetch_serp(&quot;coffee shop near me&quot;, target_us_austin)\n# results = parse_serp(html)\n# for r in results:\n#     tag = &quot;[LOCAL]&quot; if r.is_local_pack else &quot;[ORG]&quot;\n#     print(f&quot;{tag} #{r.position} {r.title} — {r.url}&quot;)\n\n# Export JSON pour le stockage\n# data = [{&quot;position&quot;: r.position, &quot;title&quot;: r.title, &quot;url&quot;: r.url,\n#          &quot;snippet&quot;: r.snippet, &quot;is_local_pack&quot;: r.is_local_pack}\n#         for r in results]\n# with open(&quot;serp_austin_coffee.json&quot;, &quot;w&quot;) as f:\n#     json.dump(data, f, indent=2, ensure_ascii=False)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cblockquote>\u003Cp>Conseil production : Google change régulièrement ses sélecteurs CSS. Utilisez des sélecteurs multiples en fallback et surveillez le taux d'extraction. Si vous passez sous 90% de résultats extraits sur un échantillon de 100 SERP, c'est qu'un changement de structure a eu lieu.\u003C\u002Fp>\u003C\u002Fblockquote>\n\n\u003Ch2>Sessions sticky pour SERP multi-pages\u003C\u002Fh2>\n\u003Cp>Pour collecter les pages 2, 3, 4+ d'une même SERP, vous devez conserver la même IP sortante sur toute la pagination. Sans session sticky, chaque page vient d'une IP différente et Google peut retourner des résultats incohérents ou déclencher un CAPTCHA.\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode>import uuid\nimport time\nimport urllib.parse\nfrom curl_cffi import requests as cffi_requests\n\ndef fetch_paginated_serp(\n    client: ProxyHatClient,\n    query: str,\n    target: GeoTarget,\n    max_pages: int = 5,\n    delay_seconds: float = 2.0,\n) -&gt; list[dict]:\n    &quot;&quot;&quot;Collecte plusieurs pages de SERP avec session sticky.\n    \n    Garde la même IP (même session ProxyHat) pour toutes les pages.\n    &quot;&quot;&quot;\n    session_id = f&quot;serp-{uuid.uuid4().hex[:8]}&quot;\n    all_results = []\n\n    for page in range(0, max_pages):\n        start = page * 10  # Google utilise start=0,10,20,30...\n        params = {\n            &quot;q&quot;: query,\n            &quot;gl&quot;: target.country,\n            &quot;hl&quot;: target.hl,\n            &quot;uule&quot;: target.uule,\n            &quot;pws&quot;: &quot;0&quot;,\n            &quot;num&quot;: &quot;10&quot;,\n            &quot;start&quot;: str(start),\n        }\n        url = f&quot;https:\u002F\u002Fwww.{target.google_domain}\u002Fsearch?&quot; + urllib.parse.urlencode(params)\n        proxy = client.proxy_url(target, session_id=session_id)\n\n        try:\n            resp = cffi_requests.get(\n                url,\n                proxies={&quot;https&quot;: proxy, &quot;http&quot;: proxy},\n                impersonate=&quot;chrome&quot;,\n                timeout=30,\n            )\n            if resp.status_code == 200:\n                page_results = parse_serp(resp.text)\n                for r in page_results:\n                    r.position += start  # Position globale\n                all_results.extend(page_results)\n                print(f&quot;Page {page + 1}: {len(page_results)} résultats (session {session_id})&quot;)\n            elif resp.status_code == 429:\n                print(f&quot;429 sur page {page + 1} — pause 15 min recommandée&quot;)\n                break\n            else:\n                print(f&quot;HTTP {resp.status_code} sur page {page + 1}&quot;)\n                break\n        except Exception as e:\n            print(f&quot;Erreur page {page + 1}: {e}&quot;)\n            break\n\n        time.sleep(delay_seconds)  # Respectful delay entre pages\n\n    return all_results\n\n# --- Utilisation ---\n# client = ProxyHatClient(&quot;user&quot;, &quot;pass&quot;)\n# target = GeoTarget(country=&quot;us&quot;, city=&quot;Austin,Texas,United States&quot;)\n# results = fetch_paginated_serp(client, &quot;best coffee shops&quot;, target, max_pages=3)\n# print(f&quot;Total: {len(results)} résultats sur 3 pages&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>Gestion des CAPTCHA, erreurs 429 et retries\u003C\u002Fh2>\n\u003Cp>Google défend activement contre le scraping automatisé. Les signaux de détection incluent le volume de requêtes par IP, le pattern de navigation, et l'empreinte TLS. Voici une stratégie de backoff avec circuit breaker :\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode>import time\nimport logging\nfrom collections import defaultdict\nfrom datetime import datetime, timedelta\n\nlogger = logging.getLogger(__name__)\n\nclass SERPBackoffManager:\n    &quot;&quot;&quot;Gestionnaire de backoff avec circuit breaker par pays.\n    \n    Suit le taux d&apos;erreur par marché et désactive temporairement\n    un pays si le taux d&apos;échec dépasse le seuil.\n    &quot;&quot;&quot;\n    def __init__(\n        self,\n        max_retries: int = 3,\n        base_delay: float = 2.0,\n        max_delay: float = 60.0,\n        error_threshold: int = 5,\n        cooldown_minutes: int = 15,\n    ):\n        self.max_retries = max_retries\n        self.base_delay = base_delay\n        self.max_delay = max_delay\n        self.error_threshold = error_threshold\n        self.cooldown = timedelta(minutes=cooldown_minutes)\n        self.error_counts: dict[str, int] = defaultdict(int)\n        self.circuit_open_until: dict[str, datetime] = {}\n\n    def can_request(self, country: str) -&gt; bool:\n        &quot;&quot;&quot;Vérifie si le circuit breaker autorise les requêtes pour ce pays.&quot;&quot;&quot;\n        until = self.circuit_open_until.get(country)\n        if until and datetime.now() &lt; until:\n            return False\n        if until and datetime.now() &gt;= until:\n            del self.circuit_open_until[country]\n            self.error_counts[country] = 0\n        return True\n\n    def record_success(self, country: str):\n        self.error_counts[country] = 0\n\n    def record_failure(self, country: str, status_code: int):\n        self.error_counts[country] += 1\n        if status_code == 429:\n            delay = min(self.base_delay * 2 ** self.error_counts[country], self.max_delay)\n            logger.warning(f&quot;429 pour {country} — backoff {delay:.0f}s&quot;)\n            time.sleep(delay)\n        if self.error_counts[country] &gt;= self.error_threshold:\n            self.circuit_open_until[country] = datetime.now() + self.cooldown\n            logger.error(\n                f&quot;Circuit breaker OUVERT pour {country} — &quot;\n                f&quot;cooldown {self.cooldown.total_seconds()\u002F60:.0f} min&quot;\n            )\n\n# --- Utilisation dans une boucle de collecte ---\nbackoff = SERPBackoffManager(max_retries=3, cooldown_minutes=15)\n\ndef safe_fetch(client: ProxyHatClient, query: str, target: GeoTarget) -&gt; str | None:\n    if not backoff.can_request(target.country):\n        logger.warning(f&quot;Circuit ouvert pour {target.country}, skip&quot;)\n        return None\n    try:\n        html = client.fetch_serp(query, target, max_retries=3)\n        backoff.record_success(target.country)\n        return html\n    except Exception as e:\n        backoff.record_failure(target.country, 429)\n        logger.error(f&quot;Échec {target.country}: {e}&quot;)\n        return None\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>Pools de proxies par pays\u003C\u002Fh2>\n\u003Cp>Pour du US geo-targeted SERP scraping à grande échelle, vous devez maintenir des pools séparés par marché. Chaque pays a son propre rythme de collecte, son propre circuit breaker, et ses propres sessions sticky. Voici un pattern async avec \u003Ccode>asyncio\u003C\u002Fcode> :\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode>import asyncio\nimport uuid\nfrom dataclasses import dataclass\nfrom typing import List\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n@dataclass\nclass ScrapingTask:\n    query: str\n    target: GeoTarget\n    priority: int = 0\n\nclass CountryProxyPool:\n    &quot;&quot;&quot;Pool de collecte asynchrone par pays avec contrôle de concurrence.&quot;&quot;&quot;\n    def __init__(\n        self,\n        client: ProxyHatClient,\n        max_concurrent: int = 10,\n        requests_per_second: float = 2.0,\n    ):\n        self.client = client\n        self.semaphore = asyncio.Semaphore(max_concurrent)\n        self.min_interval = 1.0 \u002F requests_per_second\n        self._last_request = 0.0\n\n    async def fetch_one(self, task: ScrapingTask) -&gt; dict | None:\n        async with self.semaphore:\n            # Rate limiting simple\n            now = asyncio.get_event_loop().time()\n            elapsed = now - self._last_request\n            if elapsed &lt; self.min_interval:\n                await asyncio.sleep(self.min_interval - elapsed)\n            self._last_request = asyncio.get_event_loop().time()\n\n            session_id = f&quot;pool-{uuid.uuid4().hex[:8]}&quot;\n            try:\n                # Note: curl_cffi ne supporte pas asyncio nativement.\n                # En production, utilisez httpx avec proxy ou exécutez\n                # dans un ThreadPoolExecutor.\n                loop = asyncio.get_event_loop()\n                html = await loop.run_in_executor(\n                    None,\n                    lambda: self.client.fetch_serp(\n                        task.query, task.target, session_id=session_id\n                    )\n                )\n                results = parse_serp(html)\n                return {\n                    &quot;query&quot;: task.query,\n                    &quot;city&quot;: task.target.city,\n                    &quot;country&quot;: task.target.country,\n                    &quot;results_count&quot;: len(results),\n                    &quot;local_pack_count&quot;: sum(1 for r in results if r.is_local_pack),\n                }\n            except Exception as e:\n                logger.error(f&quot;Échec {task.query} @ {task.target.city}: {e}&quot;)\n                return None\n\n    async def fetch_batch(self, tasks: List[ScrapingTask]) -&gt; List[dict]:\n        coros = [self.fetch_one(t) for t in tasks]\n        results = await asyncio.gather(*coros, return_exceptions=True)\n        return [r for r in results if r is not None and not isinstance(r, Exception)]\n\n# --- Utilisation ---\n# client = ProxyHatClient(&quot;user&quot;, &quot;pass&quot;)\n# pool = CountryProxyPool(client, max_concurrent=10, requests_per_second=2.0)\n#\n# tasks = [\n#     ScrapingTask(&quot;coffee shop&quot;, GeoTarget(&quot;us&quot;, &quot;Austin,Texas,United States&quot;)),\n#     ScrapingTask(&quot;coffee shop&quot;, GeoTarget(&quot;us&quot;, &quot;Brooklyn,New York,United States&quot;)),\n#     ScrapingTask(&quot;coffee shop&quot;, GeoTarget(&quot;us&quot;, &quot;Chicago,Illinois,United States&quot;)),\n#     ScrapingTask(&quot;boulangerie&quot;, GeoTarget(&quot;fr&quot;, &quot;Paris,France&quot;, hl=&quot;fr&quot;, google_domain=&quot;google.fr&quot;)),\n#     ScrapingTask(&quot;bäckerei&quot;, GeoTarget(&quot;de&quot;, &quot;München,Bayern,Germany&quot;, hl=&quot;de&quot;, google_domain=&quot;google.de&quot;)),\n# ]\n# batch_results = asyncio.run(pool.fetch_batch(tasks))\n# for r in batch_results:\n#     print(f&quot;{r[&apos;city&apos;]}: {r[&apos;results_count&apos;]} résultats ({r[&apos;local_pack_count&apos;]} local pack)&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>Erreurs courantes et cas limites\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Paramètres incohérents : \u003Ccode>gl=fr\u003C\u002Fcode> avec un proxy US et \u003Ccode>google_domain=google.de\u003C\u002Fcode>. Google ignore ou mélange les signaux. Alignez toujours les trois.\u003C\u002Fli>\n\u003Cli>uule trop précis : un nom canonique comme « 5th Avenue,Austin,Texas,United States » peut ne pas être reconnu. Restez au niveau ville ou ville+état.\u003C\u002Fli>\n\u003Cli>Encodage base64 incorrect : utilisez UTF-8, pas ASCII. Les noms de villes avec accents (München, São Paulo) doivent être encodés correctement. Consultez \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBase64\" rel=\"noopener noreferrer\">la spécification Base64\u003C\u002Fa> pour les détails.\u003C\u002Fli>\n\u003Cli>Oubli de pws=0 : sans ce paramètre, Google personnalise les résultats selon l'historique de la session IP. Vos mesures deviennent non reproductibles.\u003C\u002Fli>\n\u003Cli>Latence excessive : un proxy résidentiel ajoute 200–800ms de latence. Si vous scrapez 50 villes, parallélisez avec des pools par pays plutôt que de tout séquentialiser.\u003C\u002Fli>\n\u003Cli>Rotation d'IP trop agressive : changer d'IP à chaque requête peut sembler plus suspect qu'une navigation normale. Pour la pagination, utilisez des sessions sticky.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch2>Considérations légales et conditions d'utilisation\u003C\u002Fh2>\n\u003Cp>Le scraping de Google SERP soulève des questions légales. Les conditions d'utilisation de Google interdisent l'accès automatisé sans autorisation. En pratique, de nombreuses entreprises collectent des données SERP pour le suivi de classement, mais cela doit se faire dans le respect des limites techniques, des \u003Ccode>robots.txt\u003C\u002Fcode>, et des lois applicables (RGPD en Europe, CCPA en Californie). Consultez toujours les conditions d'utilisation de Google et, si nécessaire, un conseiller juridique. Pour des données SERP à grande échelle, envisagez l'\u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fcustom-search\u002Fv1\u002Foverview\" rel=\"noopener noreferrer\">API de recherche programmable de Google\u003C\u002Fa> qui offre un accès officiel et documenté.\u003C\u002Fp>\n\n\u003Ch2>Points clés à retenir\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cul>\n\u003Cli>Alignez IP, gl, hl, google_domain et uule — Google croise ces signaux. Une incohérence déclenche CAPTCHA ou résultats non représentatifs.\u003C\u002Fli>\n\u003Cli>Le paramètre uule encode le nom canonique de la ville en base64 avec un préfixe fixe et une clé de longueur. C'est le seul moyen de cibler une ville précise.\u003C\u002Fli>\n\u003Cli>pws=0 est obligatoire pour des baselines non personnalisés et reproductibles.\u003C\u002Fli>\n\u003Cli>Utilisez des proxies résidentiels géociblés via \u003Ccode>gate.proxyhat.com:8080\u003C\u002Fcode> avec \u003Ccode>-country-US-region-new_york-city-new_york_city\u003C\u002Fcode> dans le nom d'utilisateur.\u003C\u002Fli>\n\u003Cli>Sessions sticky pour la pagination multi-pages : conservez la même IP sur toute la collecte d'une SERP.\u003C\u002Fli>\n\u003Cli>Circuit breaker par pays : si un marché dépasse 5 erreurs consécutives, mettez-le en cooldown 15 minutes plutôt que de forcer.\u003C\u002Fli>\n\u003Cli>Surveillez vos sélecteurs CSS : Google change sa structure HTML régulièrement. Un taux d'extraction sous 90% signale un changement.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fblockquote>\n\n\u003Cp>Pour configurer vos pools de proxies résidentiels géociblés, consultez notre \u003Ca href=\"\u002Ffr\u002Fpricing\">page de tarification\u003C\u002Fa> et la \u003Ca href=\"https:\u002F\u002Fdocs.proxyhat.com\u002F\" rel=\"noopener noreferrer\">documentation ProxyHat\u003C\u002Fa> pour les détails d'intégration.\u003C\u002Fp>","Scraping SERP géociblé : meilleures pratiques | ProxyHat","Meilleures pratiques de scraping SERP géociblé : paramètres uule, gl, hl, proxies résidentiels par ville. Exemples Python et patterns de production.","\u002Ffr\u002Fblog\u002Fscraping-serp-geocible-meilleures-pratiques",null,"https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraping-serp-geocible-meilleures-pratiques-og.webp",[17,20,23],{"question":18,"answer":19},"Quel type de proxy fonctionne le mieux pour le scraping SERP géociblé ?","Les proxies résidentiels sont le meilleur choix car leur IP provient de vrais FAI et passe les vérifications de Google. Les datacenter proxies sont facilement détectés. ProxyHat offre des proxies résidentiels avec géociblage par ville via gate.proxyhat.com:8080, en utilisant le format user-country-US-region-new_york-city-new_york_city dans le nom d'utilisateur pour cibler précisément la zone souhaitée.",{"question":21,"answer":22},"Comment éviter les blocages lors du scraping SERP géociblé ?","Alignez toujours la géographie IP avec les paramètres gl, hl et uule. Utilisez pws=0 pour un baseline non personnalisé. Implémentez un backoff exponentiel sur les erreurs 429, un circuit breaker par pays, des sessions sticky pour la pagination, et un délai respectueux entre requêtes. L'impersonation TLS via curl_cffi ou des clients similaires réduit également la détection.",{"question":24,"answer":25},"Comment construire le paramètre uule de Google ?","Le paramètre uule se construit en encodant le nom canonique de la ville (ex. « Austin,Texas,United States ») en base64, puis en ajoutant le préfixe fixe « w+CAIQICI » et une clé de longueur correspondant à la taille de la chaîne base64. La clé est un caractère issue d'une table de correspondance où A=2, B=4, C=6, etc. Le résultat final ressemble à w+CAIQICIRQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM=.",false,"2026-07-31T16:11:16+00:00","2026-10-03T23:52:59+00:00","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraping-serp-geocible-meilleures-pratiques-master.webp","Geo-Targeted SERP Scraping Best Practices for Local SEO Data","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraping-serp-geocible-meilleures-pratiques-card.webp",12,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},"ProxyHat Team","proxyhat-team","The ProxyHat editorial team covers proxies, web scraping, and data extraction.",[38],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},3,"serp-et-seo","SERP et SEO","Guides SERP et SEO | Blog ProxyHat","Comment suivre les classements et extraire les données SERP avec des proxies.",[],{"en":46,"x-default":46},"\u002Fblog\u002Fgeo-targeted-serp-scraping-best-practices",{"zh":48,"ja":49,"pl":50,"es":51,"it":52,"tr":53,"ko":54,"en":46,"ru":55,"de":56,"fr":13,"pt":57},"\u002Fcn\u002Fblog\u002Fserp-dili-dingwei-zhuaqu-zhinan","\u002Fja\u002Fblog\u002Fgeo-targeted-serp-scraping-guide","\u002Fpl\u002Fblog\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki","\u002Fes\u002Fblog\u002Fscraping-serp-geo-segmentado-mejores-practicas","\u002Fit\u002Fblog\u002Fscraping-serp-geo-targetato-best-practices","\u002Ftr\u002Fblog\u002Fcografi-hedefli-serp-kazima-rehberi","\u002Fko\u002Fblog\u002Fgeo-targeted-serp-scraping-guide","\u002Fru\u002Fblog\u002Fgeo-targeted-serp-scraping-best-practices","\u002Fde\u002Fblog\u002Fgeo-targeted-serp-scraping-leitfaden","\u002Fpt\u002Fblog\u002Fpraticas-serp-scraping-geo-targeted",59,[60,77,93,110],{"id":61,"slug":62,"title":63,"excerpt":64,"featured_image_url":65,"featured_image_alt":66,"card_image_url":67,"og_image_url":68,"published_at":69,"updated_at":69,"content_updated_at":70,"reading_time_minutes":71,"is_featured":26,"indexable":26,"views":72,"author":73,"categories":74,"tags":76},155,"suivi-classement-amazon-proxy-python","Suivi de classement de mots-clés Amazon avec proxy : guide développeur","Apprenez à suivre le classement organique de vos ASIN sur Amazon avec des proxies résidentiels, curl_cffi et Playwright. Code Python complet, géo-ciblage et bonnes pratiques de production.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivi-classement-amazon-proxy-python-master.webp","Amazon Keyword Rank Tracking with Proxies: A Developer's Guide","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivi-classement-amazon-proxy-python-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivi-classement-amazon-proxy-python-og.webp","2026-07-16T16:05:31+00:00","2026-10-03T08:57:48+00:00",13,98,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[75],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":78,"slug":79,"title":80,"excerpt":81,"featured_image_url":82,"featured_image_alt":83,"card_image_url":84,"og_image_url":85,"published_at":86,"updated_at":86,"content_updated_at":87,"reading_time_minutes":32,"is_featured":26,"indexable":26,"views":88,"author":89,"categories":90,"tags":92},153,"scraper-google-paa-autocomplete-python-1","Scraper Google People Also Ask et Autocomplete pour la recherche de mots-clés (Guide Python)","Apprenez à scraper Google People Also Ask, Autocomplete et Related Searches avec Python et des proxies résidentiels. Code complet, expansion de mots-clés, et stratégies anti-blocage incluses.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraper-google-paa-autocomplete-python-1-master.webp","Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraper-google-paa-autocomplete-python-1-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fscraper-google-paa-autocomplete-python-1-og.webp","2026-07-14T16:06:25+00:00","2026-10-03T23:01:45+00:00",124,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[91],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":94,"slug":95,"title":96,"excerpt":97,"featured_image_url":98,"featured_image_alt":99,"card_image_url":100,"og_image_url":101,"published_at":102,"updated_at":102,"content_updated_at":103,"reading_time_minutes":104,"is_featured":26,"indexable":26,"views":105,"author":106,"categories":107,"tags":109},154,"suivre-marque-google-ai-overviews","Comment suivre votre marque dans Google AI Overviews en 2026","Guide stratégique pour mesurer la visibilité de votre marque dans Google AI Overviews et answer engines : données à capturer, build vs buy, implémentation avec proxies résidentiels et gouvernance.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivre-marque-google-ai-overviews-master.webp","How to Track Your Brand in Google AI Overviews: A Strategic Guide for SEO & Data Teams","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivre-marque-google-ai-overviews-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fsuivre-marque-google-ai-overviews-og.webp","2026-07-14T10:05:37+00:00","2026-10-03T08:21:25+00:00",10,66,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[108],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":111,"slug":112,"title":113,"excerpt":114,"featured_image_url":115,"featured_image_alt":116,"card_image_url":117,"og_image_url":118,"published_at":119,"updated_at":119,"content_updated_at":120,"reading_time_minutes":32,"is_featured":26,"indexable":26,"views":121,"author":122,"categories":123,"tags":125},152,"creer-tracker-positions-google-python-proxies","Créer un Tracker de Positions Google en Python avec Proxies Résidentiels","Guide développeur pour construire un tracker de positions Google en Python : pagination SERP, parsing des résultats organiques, proxies résidentiels avec geo-targeting, stockage SQLite et durcissement production.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fcreer-tracker-positions-google-python-proxies-master.webp","Build a Google Rank Tracker in Python with Residential Proxies","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fcreer-tracker-positions-google-python-proxies-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Ffr\u002Fcreer-tracker-positions-google-python-proxies-og.webp","2026-07-02T16:04:58+00:00","2026-10-03T08:29:58+00:00",56,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[124],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],[]]