Best Practices für Geo-Targeted SERP Scraping: Der Developer-Leitfaden

Praktischer Leitfaden für Geo-Targeted SERP Scraping: uule, gl, hl und google_domain korrekt kombinieren, City-Level-Targeting mit Residential Proxies umsetzen und Google-Blöcke vermeiden.

Geo-Targeted SERP Scraping Best Practices for Local SEO Data
In diesem Artikel

Best Practices für Geo-Targeted SERP Scraping: Was Sie wissen müssen

Wenn Sie lokalisierte SERP-Datasets für Rank-Tracking, Wettbewerbsanalyse oder lokales SEO aufbauen, reicht ein einfacher HTTP-Request an google.com nicht aus. Google personalisiert Suchergebnisse nach Geolocation, Sprache, Suchverlauf und Geräte-Typ — und genau hier setzen die Best Practices für Geo-Targeted SERP Scraping an: Sie müssen URL-Parameter (gl, hl, uule), IP-Geographie und Browser-Fingerprinting koordinieren, damit die zurückgegebenen SERPs tatsächlich dem Zielmarkt entsprechen. Dieser Leitfaden zeigt Ihnen, wie Sie das mit Python, curl_cffi, selectolax und Residential Proxies über gate.proxyhat.com produktionsreif umsetzen.

Warum dieselbe Suchanfrage unterschiedliche Ergebnisse liefert

Google verwendet mindestens drei Signale, um die Geolocation eines Nutzers zu bestimmen:

  • IP-Adresse — wird über GeoIP-Datenbanken wie MaxMind GeoIP2 auf Länder- und Stadtebene aufgelöst.
  • URL-Parameter — gl (Google-Ländercode), hl (Interface-Sprache), google_domain (z.B. google.de vs. google.co.uk) und uule (codierter Ortsname).
  • Kontextsignale — Browser-Sprache (Accept-Language), GPS/HTML5-Geolocation (nur im Browser) und Suchverlauf (personalisierte Ergebnisse).

Ein Request von einer deutschen IP ohne gl-Parameter liefert Ergebnisse für den deutschen Markt. Dieselbe Suchanfrage mit gl=us und einer US-IP liefert deutlich andere organische Ergebnisse, ein anderes Local Pack und oft andere Featured Snippets. Länderebene allein reicht jedoch nicht: Ein Nutzer in New York sieht andere Local-Pack-Einträge als jemand in Austin, Texas — selbst wenn beide gl=us setzen. Für lokales SEO benötigen Sie City- oder District-Level-Präzision über den uule-Parameter, kombiniert mit einer passenden IP aus derselben Region.

Die Kern-Parameter: gl, hl, google_domain, lr und uule

Google unterstützt mehrere Parameter zur Steuerung der Lokalisierung. Laut der Google Custom Search XML-Dokumentation steuern gl und hl die grundlegende Länder- und Sprachzuordnung. Für präzises Geo-Targeting benötigen Sie jedoch zusätzlich uule und pws=0.

Parameter Beschreibung Beispiel Pflicht für City-Targeting?
gl Ländercode (ISO 3166-1 alpha-2) gl=us Ja — Basis-Lokalisierung
hl Interface-Sprache hl=en Empfohlen — sollte mit gl übereinstimmen
google_domain Top-Level-Domain google.de Optional — gl ist verlässlicher
lr Sprache der Dokumente einschränken lr=lang_en Optional — für sprachspezifische Filter
uule Codierter kanonischer Ortsname w+CAIQICIdQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM= Ja — City-Level-Präzision
pws=0 Personalisierung deaktivieren pws=0 Empfohlen — reproduzierbare Baseline

uule konstruieren: City-Level-Präzision

Der uule-Parameter ist ein Base64-codierter kanonischer Ortsname mit einem festen Präfix und einem Längen-Key. Der Aufbau ist: w+CAIQICI (fester Präfix) + Längen-Key (ein Zeichen, das die Länge des Base64-Strings repräsentiert) + Base64(Ortsname). Der kanonische Ortsname folgt dem Format Stadt,Bundesland,Land, z.B. Austin,Texas,United States.

Die Base64-Codierung ist in RFC 4648 spezifiziert. Der Längen-Key wird als chr(len(base64_string) + 0x40) berechnet — bei einer Länge von 36 Zeichen ergibt das chr(100) = 'd'.

# uule-Konstruktion in reinem Python
import base64

def build_uule(canonical_name: str) -> str:
    """
    Erzeugt einen Google uule-Parameter aus einem kanonischen Ortsnamen.
    Format: 'w+CAIQICI' + length_key + base64(canonical_name)
    
    Beispiele für canonical_name:
      'Austin,Texas,United States'
      'New York,New York,United States'
      'Berlin,Berlin,Germany'
      'München,Bayern,Germany'
    """
    b64 = base64.b64encode(canonical_name.encode("utf-8")).decode("utf-8")
    length_key = chr(len(b64) + 0x40)  # '@'=0x40, len=1 -> 'A'
    return f"w+CAIQICI{length_key}{b64}"

# Beispiel
uule = build_uule("Austin,Texas,United States")
print(uule)
# -> w+CAIQICIdQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM=

IP-Geographie und Proxy-Koordination

Google gleicht die URL-Parameter mit der IP-Geographie ab. Wenn Sie gl=us&uule=...Austin... senden, die Anfrage aber von einer IP in Frankfurt kommt, kann Google die Parameter ignorieren oder ein CAPTCHA auslösen. Die Lösung: Residential Proxies aus demselben Markt. ProxyHat bietet Geo-Targeting auf Länder- und Stadtebene direkt im Usernamen:

  • user-country-US-region-new_york-city-new_york_city:pass — US-IP aus New York
  • user-country-DE-region-state_of_berlin-city-berlin:pass — deutsche IP aus Berlin
  • user-country-US-region-texas-city-austin:pass — US-IP aus Austin, Texas

Die Kombination aus uule und passender IP aus derselben Stadt liefert die verlässlichsten lokalen SERP-Daten. Datacenter-IPs funktionieren für Tests, werden von Google aber häufiger herausgefiltert — Residential Proxies haben typischerweise eine 3-5× höhere Erfolgsquote bei SERP-Scraping.

Implementierung: Code-Beispiele

1. Basis-Request mit curl (Raw Proxy Usage)

Der einfachste Weg, einen geo-targeted SERP-Request abzusetzen — mit curl und ProxyHat als HTTP-Proxy:

# SERP-Request für "coffee shop" in Austin, TX
# mit gl, hl, uule und pws=0 über ProxyHat Residential Proxy

curl -s \
  --proxy "http://user-country-US-region-texas-city-austin:PASS@gate.proxyhat.com:8080" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" \
  -H "Accept-Language: en-US,en;q=0.9" \
  "https://www.google.com/search?q=coffee+shop&gl=us&hl=en&uule=w+CAIQICIdQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM=&pws=0&num=100"

2. ProxyHat Helper-Klasse (SDK-Stil) und curl_cffi mit Chrome-Impersonation

Für produktive Systeme sollten Sie den Proxy-URL-Bau kapseln. Hier eine Lightweight-Helper-Klasse, die als Mini-SDK fungiert, kombiniert mit curl_cffi für TLS-Fingerprint-Impersonation:

# pip install curl_cffi selectolax

from curl_cffi import requests as cffi_requests
from dataclasses import dataclass
import base64
import logging
import random
import time

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("serp_scraper")

# --- ProxyHat Mini-SDK ---

class ProxyHat:
    """Lightweight ProxyHat SDK für Geo-Targeted SERP Scraping."""
    GATEWAY = "gate.proxyhat.com"
    HTTP_PORT = 8080
    SOCKS5_PORT = 1080

    def __init__(self, username: str, password: str):
        self.username = username
        self.password = password

    def proxy_url(
        self,
        country: str | None = None,
        city: str | None = None,
        session: str | None = None,
        protocol: str = "http",
    ) -> str:
        """Erzeugt eine Proxy-URL mit Geo- und Session-Flags."""
        parts = [self.username]
        if country:
            parts.append(f"country-{country.upper()}")
        if city:
            parts.append(f"city-{city.lower().replace(' ', '')}")
        if session:
            parts.append(f"sid-{session}")
        user = "-".join(parts)
        port = self.HTTP_PORT if protocol == "http" else self.SOCKS5_PORT
        scheme = "http" if protocol == "http" else "socks5"
        return f"{scheme}://{user}:{self.password}@{self.GATEWAY}:{port}"

    def proxies(self, **kwargs) -> dict:
        """Gibt ein proxies-dict für requests/curl_cffi zurück."""
        url = self.proxy_url(**kwargs)
        return {"http": url, "https": url}


# --- uule-Builder ---

def build_uule(canonical_name: str) -> str:
    b64 = base64.b64encode(canonical_name.encode("utf-8")).decode("utf-8")
    length_key = chr(len(b64) + 0x40)
    return f"w+CAIQICI{length_key}{b64}"


# --- SERP-Scraper ---

@dataclass
class LocaleConfig:
    country_code: str       # "us"
    language: str           # "en"
    canonical_name: str     # "Austin,Texas,United States"
    proxy_city: str         # "austin"
    google_domain: str = "www.google.com"


LOCALES = [
    LocaleConfig("us", "en", "Austin,Texas,United States", "austin"),
    LocaleConfig("us", "en", "New York,New York,United States", "newyork"),
    LocaleConfig("de", "de", "Berlin,Berlin,Germany", "berlin"),
    LocaleConfig("de", "de", "München,Bayern,Germany", "münchen"),
]


def scrape_serp(query: str, locale: LocaleConfig, ph: ProxyHat) -> str:
    """Sendet einen geo-targeted SERP-Request mit Chrome-Impersonation."""
    uule = build_uule(locale.canonical_name)
    params = {
        "q": query,
        "gl": locale.country_code,
        "hl": locale.language,
        "uule": uule,
        "pws": "0",
        "num": "100",
    }
    url = f"https://{locale.google_domain}/search"

    proxies = ph.proxies(
        country=locale.country_code,
        city=locale.proxy_city,
    )

    resp = cffi_requests.get(
        url,
        params=params,
        proxies=proxies,
        impersonate="chrome",
        timeout=30,
    )
    logger.info(
        f"[{locale.canonical_name}] status={resp.status_code} "
        f"bytes={len(resp.text)} ip_session=rotating"
    )
    resp.raise_for_status()
    return resp.text


# --- Main ---
if __name__ == "__main__":
    ph = ProxyHat("user", "your_password")
    for locale in LOCALES:
        try:
            html = scrape_serp("best coffee shop", locale, ph)
            with open(f"serp_{locale.proxy_city}.html", "w", encoding="utf-8") as f:
                f.write(html)
        except Exception as e:
            logger.error(f"Fehler für {locale.proxy_city}: {e}")
        time.sleep(random.uniform(1.5, 3.5))  # Rate-Limiting

Raw-Proxy-Alternative ohne Helper-Klasse: Statt der ProxyHat-Klasse können Sie die Proxy-URL auch direkt konstruieren:

# Direkte Proxy-URL (ohne SDK-Helper)
proxy = "http://user-country-US-region-texas-city-austin:PASS@gate.proxyhat.com:8080"
resp = cffi_requests.get(url, params=params, proxies={"http": proxy, "https": proxy}, impersonate="chrome")

3. Organic Results und Local Pack parsen mit selectolax

Nach dem Abruf müssen Sie die HTML-Struktur parsen. Google ändert Klassen häufig, aber die folgenden Selektoren sind über mehrere Updates stabil geblieben:

from selectolax.parser import HTMLParser
from dataclasses import dataclass, asdict
import json

@dataclass
class OrganicResult:
    position: int
    title: str
    url: str
    snippet: str

@dataclass
class LocalPackEntry:
    name: str
    rating: float | None
    reviews: int | None
    address: str | None
    phone: str | None


def parse_organic(html: str) -> list[OrganicResult]:
    """Parst organische Suchergebnisse."""
    tree = HTMLParser(html)
    results = []
    pos = 0
    for node in tree.css("div.g div.tF2Cxc"):
        pos += 1
        title_el = node.css_first("h3")
        link_el = node.css_first("a[href]")
        snippet_el = node.css_first("div[data-sncf], div.VwiC3b, span.aCOpRe")
        title = title_el.text(strip=True) if title_el else ""
        url = link_el.attributes.get("href", "") if link_el else ""
        snippet = snippet_el.text(strip=True) if snippet_el else ""
        if title and url:
            results.append(OrganicResult(pos, title, url, snippet))
    return results


def parse_local_pack(html: str) -> list[LocalPackEntry]:
    """Parst das Local Pack (Google Maps Results)."""
    tree = HTMLParser(html)
    entries = []
    # Local Pack Container — Selektor kann variieren
    for card in tree.css("div[data-cid] div.SW5HFd, div.rllt__details"):
        name_el = card.css_first("div.dbg0p, span.O3ErS")
        rating_el = card.css_first("span.MV3lbe, span[role='img']")
        name = name_el.text(strip=True) if name_el else ""
        rating = None
        if rating_el:
            r = rating_el.attributes.get("aria-label", "")
            if "star" in r.lower():
                try:
                    rating = float(r.split()[0].replace(",", "."))
                except (ValueError, IndexError):
                    pass
        entries.append(LocalPackEntry(name, rating, None, None, None))
    return entries


# --- Usage ---
if __name__ == "__main__":
    with open("serp_austin.html", "r", encoding="utf-8") as f:
        html = f.read()

    organic = parse_organic(html)
    local = parse_local_pack(html)

    print(f"=== Organic Results ({len(organic)}) ===")
    for r in organic[:5]:
        print(f"{r.position}. {r.title} — {r.url}")

    print(f"\n=== Local Pack ({len(local)}) ===")
    for e in local[:3]:
        print(f"{e.name} (Rating: {e.rating})")

    # Als JSON exportieren
    output = {
        "organic": [asdict(r) for r in organic],
        "local_pack": [asdict(e) for e in local],
    }
    with open("serp_austin_parsed.json", "w", encoding="utf-8") as f:
        json.dump(output, f, ensure_ascii=False, indent=2)

4. Sticky Sessions für Multi-Page-SERPs

Wenn Sie mehrere Seiten derselben Suchanfrage abrufen (z.B. Ergebnisse 1–100 über start=0, start=10, start=20), muss dieselbe IP verwendet werden — sonst kann Google unterschiedliche Personalisierungen pro Seite anwenden. ProxyHat unterstützt Sticky Sessions über das session-Flag im Usernamen:

import random
import string
from curl_cffi import requests as cffi_requests

def scrape_multi_page(query: str, locale: LocaleConfig, ph: ProxyHat, pages: int = 5):
    """
    Ruft mehrere SERP-Seiten ab mit derselben IP (Sticky Session).
    Die Session-ID hält die IP für die Dauer der Session stabil.
    """
    session_id = "serp_" + "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
    uule = build_uule(locale.canonical_name)
    all_results = []

    for page in range(pages):
        start = page * 10
        params = {
            "q": query,
            "gl": locale.country_code,
            "hl": locale.language,
            "uule": uule,
            "pws": "0",
            "num": "10",
            "start": str(start),
        }
        url = f"https://{locale.google_domain}/search"

        # Sticky Session: dieselbe session_id -> dieselbe IP
        proxies = ph.proxies(
            country=locale.country_code,
            city=locale.proxy_city,
            session=session_id,
        )

        try:
            resp = cffi_requests.get(
                url,
                params=params,
                proxies=proxies,
                impersonate="chrome",
                timeout=30,
            )
            logger.info(
                f"Page {page+1}/{pages} | start={start} | "
                f"status={resp.status_code} | session={session_id}"
            )
            resp.raise_for_status()
            results = parse_organic(resp.text)
            all_results.extend(results)
        except Exception as e:
            logger.error(f"Page {page+1} fehlgeschlagen: {e}")
            break

        time.sleep(random.uniform(2.0, 4.0))

    return all_results

# Beispielaufruf
ph = ProxyHat("user", "your_password")
locale = LocaleConfig("us", "en", "Austin,Texas,United States", "austin")
results = scrape_multi_page("plumber", locale, ph, pages=5)
print(f"Insgesamt {len(results)} organische Ergebnisse über 5 Seiten")

5. Retry/Backoff mit per-Country Proxy-Pools und CAPTCHA-Erkennung

Google antwortet auf verdächtiges Scraping-Verhalten mit HTTP 429 (Too Many Requests), CAPTCHAs oder 302-Redirects auf sorry.google.com. Ein robuster Scraper braucht Exponential Backoff, CAPTCHA-Erkennung und separate Proxy-Pools pro Land:

import time
import random
from curl_cffi import requests as cffi_requests
from dataclasses import dataclass

@dataclass
class ScrapeResult:
    success: bool
    html: str | None
    status_code: int
    error: str | None


class SERPScraper:
    """Robuster SERP-Scraper mit Retry, Backoff und CAPTCHA-Erkennung."""

    MAX_RETRIES = 4
    BASE_DELAY = 3.0  # Sekunden
    MAX_DELAY = 60.0

    def __init__(self, ph: ProxyHat):
        self.ph = ph

    def _is_captcha(self, html: str) -> bool:
        markers = ["sorry.google.com", "g-recaptcha", "unusual traffic", "CAPTCHA"]
        return any(m in html for m in markers)

    def _backoff(self, attempt: int) -> float:
        delay = min(self.BASE_DELAY * (2 ** attempt) + random.uniform(0, 1), self.MAX_DELAY)
        return delay

    def scrape(
        self,
        query: str,
        locale: LocaleConfig,
        session: str | None = None,
    ) -> ScrapeResult:
        """
        Sendet einen SERP-Request mit Retry-Logik.
        Rotiert bei 429/CAPTCHA automatisch auf eine neue IP
        (neue Session-ID = neue IP aus demselben Geo-Pool).
        """
        uule = build_uule(locale.canonical_name)
        url = f"https://{locale.google_domain}/search"
        params = {
            "q": query,
            "gl": locale.country_code,
            "hl": locale.language,
            "uule": uule,
            "pws": "0",
            "num": "100",
        }

        for attempt in range(self.MAX_RETRIES):
            # Bei Retry: neue Session-ID für neue IP aus demselben City-Pool
            current_session = session if session else f"retry_{attempt}_{random.randint(1000,9999)}"
            proxies = self.ph.proxies(
                country=locale.country_code,
                city=locale.proxy_city,
                session=current_session,
            )

            try:
                resp = cffi_requests.get(
                    url,
                    params=params,
                    proxies=proxies,
                    impersonate="chrome",
                    timeout=30,
                )

                # CAPTCHA-Erkennung
                if resp.status_code == 200 and self._is_captcha(resp.text):
                    delay = self._backoff(attempt)
                    logger.warning(
                        f"CAPTCHA erkannt für {locale.proxy_city} | "
                        f"attempt={attempt+1}/{self.MAX_RETRIES} | "
                        f"backoff={delay:.1f}s"
                    )
                    time.sleep(delay)
                    continue

                # Rate Limiting
                if resp.status_code == 429:
                    delay = self._backoff(attempt)
                    logger.warning(
                        f"HTTP 429 für {locale.proxy_city} | "
                        f"attempt={attempt+1}/{self.MAX_RETRIES} | "
                        f"backoff={delay:.1f}s"
                    )
                    time.sleep(delay)
                    continue

                resp.raise_for_status()
                logger.info(
                    f"Erfolg: {locale.proxy_city} | "
                    f"status={resp.status_code} | "
                    f"bytes={len(resp.text)} | "
                    f"attempt={attempt+1}"
                )
                return ScrapeResult(True, resp.text, resp.status_code, None)

            except cffi_requests.RequestsError as e:
                delay = self._backoff(attempt)
                logger.error(
                    f"Request-Fehler für {locale.proxy_city}: {e} | "
                    f"backoff={delay:.1f}s"
                )
                time.sleep(delay)

        return ScrapeResult(False, None, 0, "Max retries exceeded")


# --- Multi-Locale Scraping mit Per-Country Pools ---
if __name__ == "__main__":
    ph = ProxyHat("user", "your_password")
    scraper = SERPScraper(ph)

    queries = ["dentist", "electrician", "roofing contractor"]
    locales = [
        LocaleConfig("us", "en", "Austin,Texas,United States", "austin"),
        LocaleConfig("us", "en", "New York,New York,United States", "newyork"),
        LocaleConfig("de", "de", "Berlin,Berlin,Germany", "berlin"),
    ]

    for locale in locales:
        for query in queries:
            result = scraper.scrape(query, locale)
            if result.success:
                organic = parse_organic(result.html)
                local_pack = parse_local_pack(result.html)
                logger.info(
                    f"{locale.proxy_city} | q='{query}' | "
                    f"organic={len(organic)} | local_pack={len(local_pack)}"
                )
            else:
                logger.error(f"{locale.proxy_city} | q='{query}' | FAILED: {result.error}")
            time.sleep(random.uniform(2.0, 5.0))

Häufige Fehler und Edge Cases

  • Parameter-Mismatch: gl=us mit hl=de und einer deutschen IP führt zu unvorhersehbaren Ergebnissen. gl, hl und Proxy-Land sollten konsistent sein.
  • uule ohne passende IP: uule für Austin mit einer IP aus Los Angeles liefert verzerrte Local-Pack-Ergebnisse. Die IP-Stadt muss nicht exakt übereinstimmen, sollte aber im selben Markt liegen.
  • Vergessenes pws=0: Ohne pws=0 personalisiert Google Ergebnisse basierend auf Cookies und Suchverlauf — selbst bei Inprivate-Requests können personalisierte SERPs zurückkommen, wenn Google den Fingerprint assoziiert.
  • Zu hohe Concurrency: Mehr als 10–15 Requests pro Sekunde aus demselben Proxy-Pool erhöhen das CAPTCHA-Risiko deutlich. Eine Rate von 2–5 Requests/Sekunde mit zufälligen Delays ist sicherer.
  • Veraltete CSS-Selektoren: Google ändert HTML-Klassen regelmäßig. Testen Sie Parser gegen aktuelle SERP-HTML-Snippets und implementieren Sie Fallback-Selektoren.
  • Local Pack in Ländern ohne Google Maps: In einigen Märkten (z.B. China) ist das Local Pack nicht verfügbar oder unterschiedlich strukturiert.

ProxyHat-spezifische Einrichtung

ProxyHat macht Geo-Targeting einfach: Alle Parameter werden im Usernamen codiert. Für US Geo-Targeted SERP Scraping Best Practices verwenden Sie das Format user-country-US-region-{region}-city-{city}:pass. Die vollständige Dokumentation aller Parameter finden Sie unter docs.proxyhat.com.

Die wichtigsten Konfigurationen für SERP-Scraping:

  • Rotierende IPs (Standard): Jeder Request erhält eine neue IP — ideal für breite SERP-Erfassung.
  • Sticky Sessions: sid-{id} hält die IP stabil — notwendig für Multi-Page-SERPs.
  • City-Level-Targeting: city-{name} — kombiniert mit uule für maximale Präzision beim Local SERP Scraping by City.
  • SOCKS5: Für Anwendungen, die SOCKS5 benötigen, verwenden Sie Port 1080 statt 8080.

Preise und verfügbare Standorte finden Sie auf der Preisseite und der Standortübersicht. Weitere Use Cases für Web-Scraping und SERP-Tracking finden Sie unter Web-Scraping und SERP-Tracking.

Rechtliche Hinweise: TOS und öffentliche Daten

Das Scraping von Google-SERPs bewegt sich in einer rechtlichen Grauzone. Googles Nutzungsbedingungen verbieten automatisierte Abfragen. In der Praxis scrapen viele SEO-Tools SERP-Daten, aber Sie sollten:

  • Rate-Limits respektieren und menschenähnliche Request-Muster verwenden.
  • robots.txt prüfen — Google erlaubt bestimmte Pfade nicht.
  • DSGVO/GDPR beachten, wenn personenbezogene Daten (z.B. aus Local Packs) gespeichert werden.
  • Eine rechtliche Beratung einholen, bevor Sie kommerzielle SERP-Datensets verkaufen.

Wichtig: Dieser Artikel beschreibt technische Methoden für Entwickler. Die Einhaltung der Nutzungsbedingungen Dritter und geltender Gesetze liegt in Ihrer Verantwortung.

Key Takeaways

  • Stellen Sie gl, hl und uule konsistent ein und kombinieren Sie sie mit pws=0 für reproduzierbare, nicht-personalisierte SERPs.
  • City-Level-Targeting erfordert uule + passende IP: Google gleicht URL-Parameter mit der IP-Geographie ab. Verwenden Sie Residential Proxies aus derselben Stadt.
  • Sticky Sessions für Multi-Page-SERPs: Verwenden Sie sid-{id} im ProxyHat-Usernamen, um dieselbe IP über mehrere Seiten zu halten.
  • Implementieren Sie Exponential Backoff und CAPTCHA-Erkennung: HTTP 429 und CAPTCHA-Redirects sind normal — ein robuster Scraper rotiert die IP und wartet mit Backoff.
  • Chrome-Impersonation via curl_cffi reduziert TLS-Fingerprinting-Risiken erheblich gegenüber Standard requests.
  • Halten Sie Parser aktuell: Google ändert HTML-Klassen regelmäßig. Testen Sie gegen aktuelle SERP-HTML-Snippets.

Häufig gestellte Fragen

Welcher Proxy-Typ funktioniert am besten für Geo-Targeted SERP Scraping?

Residential Proxies sind die beste Wahl für Geo-Targeted SERP Scraping. Sie stammen von echten ISPs und werden von Google seltener als automatisierte Traffic-Quelle erkannt als Datacenter-IPs. ProxyHat bietet Residential Proxies mit Geo-Targeting auf Stadtebene über das Username-Format user-country-US-region-texas-city-austin:pass. Datacenter-Proxies können für Tests funktionieren, haben aber eine deutlich höhere Block-Wahrscheinlichkeit.

Wie vermeidet man Blöcke beim Geo-Targeted SERP Scraping?

Verwenden Sie Chrome-Impersonation (z.B. curl_cffi mit impersonate=chrome), um TLS-Fingerprints zu verschleiern. Setzen Sie realistische Accept-Language-Header, die mit hl übereinstimmen. Implementieren Sie Exponential Backoff bei HTTP 429 und CAPTCHA-Redirects, rotieren Sie IPs bei Fehlern, und halten Sie die Concurrency niedrig (2–5 Requests/Sekunde pro Proxy-Pool). Verwenden Sie Sticky Sessions für Multi-Page-Abfragen, um IP-Wechsel innerhalb einer Suchanfrage zu vermeiden.

Wie konstruiert man den uule-Parameter korrekt?

Der uule-Parameter besteht aus dem festen Präfix w+CAIQICI, gefolgt von einem Längen-Key-Zeichen und dem Base64-codierten kanonischen Ortsnamen im Format Stadt,Bundesland,Land. Der Längen-Key wird als chr(len(base64_string) + 0x40) berechnet. Beispiel: Für Austin,Texas,United States ergibt sich w+CAIQICIdQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM=. Setzen Sie immer zusätzlich pws=0, um Personalisierung zu deaktivieren.

Bereit loszulegen?

Residential-, ISP- und Mobile-Proxys in über 195 Ländern. Erstelle ein kostenloses Konto.

Kostenloses Konto erstellen
← Zurück zum Blog