Geotargetowane scrapowanie SERP: Najlepsze praktyki dla programistów SEO

Kompleksowy przewodnik dla programistów i inżynierów SEO: jak budować lokalizowane zbiory danych SERP z precyzją na poziomie miasta, konstruować parametr uule, dobierać proxy rezydencjalne i unikać blokad.

Geo-Targeted SERP Scraping Best Practices for Local SEO Data
W tym artykule

Dlaczego geotargetowane scrapowanie SERP wymaga precyzji na poziomie miasta

Kiedy wpisujesz to samo zapytanie w Google z Warszawy i z Nowego Jorku, otrzymujesz zupełnie różne wyniki. Geotargetowane scrapowanie SERP to nie tylko zmiana języka interfejsu — to sztuka wymuszenia na Google zwrócenia wyników tak, jakby zapytanie przyszło z konkretnego miasta, a nawet dzielnicy. Dla inżynierów SEO budujących rank trackery i zbiory danych lokalnych wyników, precyzja geograficzna jest fundamentem wiarygodności danych.

Problem polega na tym, że Google lokalizuje wyniki na podstawie wielu sygnałów: adresu IP, parametrów URL (gl, hl, google_domain, uule), ciasteczek, historii konta oraz sygnałów z przeglądarki. Jeśli którykolwiek z tych sygnałów jest niespójny — np. wysyłasz zapytanie z IP z Frankfurtu, ale ustawiasz gl=us i uule dla Chicago — Google wykrywa anomalię i może zwrócić wyniki dla Frankfurtu, pokazać CAPTCHA lub zablokować IP.

Dlatego us geo-targeted serp scraping best practices wymagają trzech elementów działających razem: poprawnych parametrów URL, proxy rezydencjalnego z lokalizacji zgodnej z parametrami oraz konfiguracji przeglądarki, która nie zdradza automatyzacji. W tym przewodniku pokazujemy, jak to osiągnąć z ProxyHat i Pythonem.

Lokalizacja na poziomie kraju to za mało — dlaczego potrzebujesz precyzji na poziomie miasta

Wyniki Google dla zapytania „pizza restaurant” w Miami różnią się od tych w Seattle, mimo że oba miasta leżą w USA. Pakiety lokalne (Local Pack), wyniki Map, a nawet rankingi organicne są silnie zależne od odległości użytkownika od firmy. Jeśli Twój rank tracker zbiera dane tylko z parametrem gl=us, tracisz sygnały lokalne — Google domyślnie użyje lokalizacji IP serwera proxy, co może być Los Angeles lub Ashburn, a nie miasto, które Cię interesuje.

Dla lokalnego SEO precyzja na poziomie miasta lub dzielnicy jest krytyczna. Według analiz Moz Local Search Ranking Factors, odległość od użytkownika jest jednym z najsilniejszych sygnałów rankingowych w wynikach lokalnych. Dlatego local serp scraping by city wymaga parametru uule, który precyzyjnie przypina wyniki do konkretnej lokalizacji.

Kluczowe parametry lokalizacji: gl, hl, google_domain, lr, uule

Google oferuje kilka parametrów URL do kontrolowania lokalizacji i języka wyników. Oto pełna rozkładka:

ParametrOpisPrzykład
glKod kraju (ISO 3166-1 alpha-2)gl=us
hlJęzyk interfejsu (ISO 639-1)hl=en
google_domainDomena Googlegoogle_domain=google.com
lrOgraniczenie języka wynikówlr=lang_en
uuleZakodowana lokalizacja (miasto/dzielnica)uule=w+CAIQICIE...New+York
pws=0Wyłączenie personalizacjipws=0

Konstrukcja parametru uule

Parametr uule (User-Defined Location) to najważniejszy parametr dla local serp scraping by city. Format to stały prefiks + klucz długości + base64 kanonicznej nazwy miejsca. Według dokumentacji społeczności i analiz takich jak SEO crawler community docs, struktura wygląda następująco:

uule = "w+CAIQICI" + length_key + base64(canonical_place_name)

Klucz długości zależy od długości nazwy miejsca po kodowaniu base64. Oto funkcja w Pythonie, która konstruuje uule poprawnie:

import base64

def build_uule(city: str) -> str:
    """Buduje parametr uule dla danej nazwy miasta."""
    # Stały prefiks używany przez Google
    prefix = "w+CAIQICI"
    
    # Kanoniczna nazwa miejsca (np. "New York,New York,United States")
    canonical = city
    
    # Kodowanie base64
    encoded = base64.b64encode(canonical.encode('utf-8')).decode('utf-8')
    
    # Klucz długości — liczba znaków zakodowanej nazwy
    length = len(encoded)
    
    # Mapowanie długości na klucz (uproszczone)
    length_key = chr(ord('A') + (length - 8)) if length > 8 else 'E'
    
    return f"{prefix}{length_key}{encoded}"

# Przykłady
print(build_uule("New York,New York,United States"))
print(build_uule("Chicago,Illinois,United States"))
print(build_uule("Berlin,Berlin,Germany"))

Wskazówka: Zawsze dodawaj pws=0 do URL, aby wyłączyć personalizację wyników. Bez tego Google może modyfikować wyniki na podstawie historii konta lub ciasteczek, co zaburza powtarzalność danych.

Dlaczego parametry muszą zgadzać się z geografią IP

Google cross-checkuje parametry URL z lokalizacją IP. Jeśli wyślesz zapytanie z gl=us i uule dla Miami, ale z IP z Londynu, Google wykryje niespójność. W najlepszym przypadku zignoruje Twoje parametry i zwróci wyniki dla Londynu. W najgorszym — pokaże CAPTCHA lub zwróci HTTP 429.

Dlatego musisz kierować zapytania przez proxy rezydencjalne z lokalizacji zgodnej z rynkiem docelowym. ProxyHat umożliwia geotargetowanie na poziomie kraju i miasta w nazwie użytkownika:

# Proxy dla Nowego Jorku (USA)
http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080

# Proxy dla Berlina (Niemcy)
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080

# Proxy dla Londynu (Wielka Brytania)
http://user-country-GB-city-london:pass@gate.proxyhat.com:8080

Pełną listę dostępnych lokalizacji znajdziesz na stronie lokalizacji ProxyHat.

Implementacja w Pythonie: requests i curl_cffi z rotacją IP

Poniżej pokazujemy dwa podejścia side-by-side: klasyczne requests oraz curl_cffi z impersonacją Chrome, która omija podstawowe fingerprinting botów.

Przykład 1: curl z uule, gl, hl i pws=0

# curl — podstawowe zapytanie z pełną lokalizacją
curl -x http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080 \
  "https://www.google.com/search?q=pizza+restaurant&gl=us&hl=en&google_domain=google.com&lr=lang_en&pws=0&uule=w+CAIQICIE...New+York" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -H "Accept-Language: en-US,en;q=0.9" \
  --compressed

Przykład 2: Python requests z konfiguracją proxy i uule

import requests
import base64
from urllib.parse import urlencode

PROXYHAT_USER = "user-country-US-city-newyork"
PROXYHAT_PASS = "pass"
PROXY_URL = f"http://{PROXYHAT_USER}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"

proxies = {"http": PROXY_URL, "https": PROXY_URL}

def build_uule(city: str) -> str:
    prefix = "w+CAIQICI"
    encoded = base64.b64encode(city.encode('utf-8')).decode('utf-8')
    length = len(encoded)
    length_key = chr(ord('A') + (length - 8)) if length > 8 else 'E'
    return f"{prefix}{length_key}{encoded}"

def scrape_serp(query: str, country: str, city: str, hl: str = "en") -> dict:
    uule = build_uule(city)
    params = {
        "q": query,
        "gl": country,
        "hl": hl,
        "google_domain": "google.com" if country == "us" else f"google.{country}",
        "lr": f"lang_{hl}",
        "pws": "0",
        "uule": uule,
        "num": "100",
    }
    url = f"https://www.google.com/search?{urlencode(params)}"
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Accept-Language": f"{hl}-{country.upper()},{hl};q=0.9",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    }
    
    try:
        resp = requests.get(url, headers=headers, proxies=proxies, timeout=30)
        resp.raise_for_status()
        return {"status": resp.status_code, "html": resp.text}
    except requests.exceptions.ProxyError as e:
        print(f"Proxy error: {e}")
        return {"status": 0, "error": str(e)}
    except requests.exceptions.RequestException as e:
        print(f"Request error: {e}")
        return {"status": 0, "error": str(e)}

result = scrape_serp("pizza restaurant", "us", "New York,New York,United States")
print(f"Status: {result['status']}, HTML length: {len(result.get('html', ''))}")

Przykład 3: curl_cffi z impersonacją Chrome i rotacją IP per lokalizacja

from curl_cffi import requests as cffi_requests
import base64
from urllib.parse import urlencode
import logging

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

def build_uule(city: str) -> str:
    prefix = "w+CAIQICI"
    encoded = base64.b64encode(city.encode('utf-8')).decode('utf-8')
    length = len(encoded)
    length_key = chr(ord('A') + (length - 8)) if length > 8 else 'E'
    return f"{prefix}{length_key}{encoded}"

def make_proxy_url(country: str, city: str, session_id: str = None) -> str:
    user = f"user-country-{country.upper()}-city-{city.lower()}"
    if session_id:
        user += f"-session-{session_id}"
    return f"http://{user}:pass@gate.proxyhat.com:8080"

def scrape_with_impersonation(query: str, country: str, city: str, hl: str = "en") -> dict:
    proxy_url = make_proxy_url(country, city)
    uule = build_uule(city)
    
    params = {
        "q": query,
        "gl": country,
        "hl": hl,
        "google_domain": "google.com" if country == "us" else f"google.{country}",
        "lr": f"lang_{hl}",
        "pws": "0",
        "uule": uule,
        "num": "100",
    }
    url = f"https://www.google.com/search?{urlencode(params)}"
    
    try:
        resp = cffi_requests.get(
            url,
            impersonate="chrome",
            proxies={"http": proxy_url, "https": proxy_url},
            timeout=30,
        )
        logging.info(f"[{country}/{city}] Status: {resp.status_code}, Length: {len(resp.text)}")
        return {"status": resp.status_code, "html": resp.text}
    except Exception as e:
        logging.error(f"[{country}/{city}] Error: {e}")
        return {"status": 0, "error": str(e)}

# Równoległe zapytania dla wielu miast USA
targets = [
    ("us", "newyork", "New York,New York,United States", "en"),
    ("us", "chicago", "Chicago,Illinois,United States", "en"),
    ("us", "miami", "Miami,Florida,United States", "en"),
    ("us", "seattle", "Seattle,Washington,United States", "en"),
    ("us", "losangeles", "Los Angeles,California,United States", "en"),
]

for country, city_slug, city_full, hl in targets:
    result = scrape_with_impersonation("best coffee shops", country, city_full, hl)
    # Przetwarzaj result...

Parsowanie wyników organicznych i Local Pack z selectolax

Po pobraniu HTML musisz wyciągnąć wyniki organiczne oraz pakiety lokalne. selectolax jest szybszy niż BeautifulSoup i dobrze radzi sobie z zagnieżdżonym HTML Google.

Przykład 4: Parsowanie organic + local pack

from selectolax.parser import HTMLParser
import json
import re

def parse_serp(html: str) -> dict:
    tree = HTMLParser(html)
    results = {"organic": [], "local_pack": [], "related_searches": []}
    
    # Wyniki organiczne — div.g zawiera główne wyniki
    for node in tree.css("div.g"):
        title_node = node.css_first("h3")
        link_node = node.css_first("a[href]")
        snippet_node = node.css_first("div[data-sncf]") or node.css_first("span.aCOpRe")
        
        if title_node and link_node:
            results["organic"].append({
                "title": title_node.text(strip=True),
                "url": link_node.attributes.get("href", ""),
                "snippet": snippet_node.text(strip=True) if snippet_node else "",
            })
    
    # Local Pack — zwykle w div[role='heading'] lub div.eimjXd
    local_section = tree.css_first("div.eimjXd") or tree.css_first("div[jscontroller]")
    if local_section:
        for item in local_section.css("div.rllt__link"):
            name_node = item.css_first("div.dbg0pd")
            rating_node = item.css_first("span.BTtC6e")
            addr_node = item.css_first("span.rllt__featured")
            
            results["local_pack"].append({
                "name": name_node.text(strip=True) if name_node else "",
                "rating": rating_node.text(strip=True) if rating_node else "",
                "address": addr_node.text(strip=True) if addr_node else "",
            })
    
    # Related searches
    for node in tree.css("div.EbZp0e"):
        text = node.text(strip=True)
        if text:
            results["related_searches"].append(text)
    
    return results

# Użycie
parsed = parse_serp(result["html"])
print(json.dumps(parsed, indent=2, ensure_ascii=False))

Sesje lepkie (sticky) dla wielostronicowych SERP

Google paginuje wyniki — domyślnie 10 wyników na stronę. Jeśli zmienisz IP między stronami wyników, Google może zwrócić inne wyniki na stronie 2, ponieważ lokalizacja IP jest inna. Dlatego używaj sticky sessions, aby utrzymać to samo IP przez całą sesję paginacji.

Przykład 5: Sticky session dla wielu stron SERP

import uuid
from curl_cffi import requests as cffi_requests
import base64
from urllib.parse import urlencode
import time

def build_uule(city: str) -> str:
    prefix = "w+CAIQICI"
    encoded = base64.b64encode(city.encode('utf-8')).decode('utf-8')
    length = len(encoded)
    length_key = chr(ord('A') + (length - 8)) if length > 8 else 'E'
    return f"{prefix}{length_key}{encoded}"

def scrape_multi_page(query: str, country: str, city: str, max_pages: int = 5) -> list:
    session_id = str(uuid.uuid4())[:12]
    user = f"user-country-{country.upper()}-city-{city.lower()}-session-{session_id}"
    proxy_url = f"http://{user}:pass@gate.proxyhat.com:8080"
    
    uule = build_uule(city)
    all_results = []
    
    for page in range(max_pages):
        start = page * 10
        params = {
            "q": query,
            "gl": country,
            "hl": "en",
            "google_domain": "google.com",
            "pws": "0",
            "uule": uule,
            "num": "10",
            "start": str(start),
        }
        url = f"https://www.google.com/search?{urlencode(params)}"
        
        try:
            resp = cffi_requests.get(
                url,
                impersonate="chrome",
                proxies={"http": proxy_url, "https": proxy_url},
                timeout=30,
            )
            if resp.status_code == 200:
                parsed = parse_serp(resp.text)
                parsed["page"] = page + 1
                all_results.append(parsed)
                print(f"Page {page + 1}: {len(parsed['organic'])} organic results")
                time.sleep(2)  # Uprzejme opóźnienie między stronami
            elif resp.status_code == 429:
                print(f"Rate limited on page {page + 1}, backing off...")
                time.sleep(30)
                continue
            else:
                print(f"Unexpected status {resp.status_code} on page {page + 1}")
                break
        except Exception as e:
            print(f"Error on page {page + 1}: {e}")
            break
    
    return all_results

pages = scrape_multi_page("plumber", "us", "Chicago,Illinois,United States", max_pages=5)
print(f"Total pages scraped: {len(pages)}")

CAPTCHA, HTTP 429 i strategie backoff

Nawet z najlepszymi proxy rezydencjalnymi Google nałoży limit na zbyt agresywne zapytania. Oto produkcyjna strategia retry z wykładniczym backoffem i circuit breakerem:

Przykład 6: Retry z backoffem i per-country proxy pool

import time
import random
import logging
from curl_cffi import requests as cffi_requests
from functools import wraps

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

MAX_RETRIES = 5
INITIAL_BACKOFF = 2  # sekundy
MAX_BACKOFF = 120  # 2 minuty max
CIRCUIT_BREAKER_THRESHOLD = 5  # po 5 nieudanych próbach zatrzymaj

class CircuitBreaker:
    def __init__(self, threshold: int = 5, reset_time: int = 300):
        self.failure_count = 0
        self.threshold = threshold
        self.reset_time = reset_time
        self.last_failure_time = 0
    
    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
    
    def record_success(self):
        self.failure_count = 0
    
    def is_open(self) -> bool:
        if self.failure_count >= self.threshold:
            if time.time() - self.last_failure_time > self.reset_time:
                self.failure_count = 0
                return False
            return True
        return False

breaker = CircuitBreaker(threshold=CIRCUIT_BREAKER_THRESHOLD)

def retry_with_backoff(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if breaker.is_open():
            raise Exception("Circuit breaker open — pausing requests")
        
        for attempt in range(MAX_RETRIES):
            try:
                result = func(*args, **kwargs)
                if result.get("status") == 200:
                    breaker.record_success()
                    return result
                elif result.get("status") == 429:
                    backoff = min(INITIAL_BACKOFF * (2 ** attempt) + random.uniform(0, 1), MAX_BACKOFF)
                    logging.warning(f"429 received — backing off {backoff:.1f}s (attempt {attempt + 1}/{MAX_RETRIES})")
                    time.sleep(backoff)
                    continue
                elif result.get("status") == 503:
                    logging.warning(f"503 — Google may be blocking. Backing off.")
                    time.sleep(60)
                    continue
                else:
                    breaker.record_failure()
                    return result
            except Exception as e:
                logging.error(f"Request failed: {e} (attempt {attempt + 1}/{MAX_RETRIES})")
                breaker.record_failure()
                backoff = min(INITIAL_BACKOFF * (2 ** attempt), MAX_BACKOFF)
                time.sleep(backoff)
        
        return {"status": 0, "error": "Max retries exceeded"}
    return wrapper

@retry_with_backoff
def scrape_with_retry(query: str, country: str, city: str) -> dict:
    proxy_url = f"http://user-country-{country.upper()}-city-{city.lower()}:pass@gate.proxyhat.com:8080"
    uule = build_uule(city)
    params = {
        "q": query, "gl": country, "hl": "en",
        "google_domain": "google.com", "pws": "0", "uule": uule,
    }
    url = f"https://www.google.com/search?{urlencode(params)}"
    
    resp = cffi_requests.get(
        url, impersonate="chrome",
        proxies={"http": proxy_url, "https": proxy_url},
        timeout=30,
    )
    return {"status": resp.status_code, "html": resp.text}

# Per-country proxy pools — różne miasta dla tego samego kraju
us_cities = ["newyork", "chicago", "miami", "seattle", "losangeles", "dallas", "atlanta"]
city_index = 0

for keyword in ["dentist", "lawyer", "restaurant", "gym", "plumber"]:
    city_slug = us_cities[city_index % len(us_cities)]
    city_full = f"{city_slug.title()},United States"
    result = scrape_with_retry(keyword, "us", city_full)
    print(f"[{city_slug}] {keyword}: status={result['status']}")
    city_index += 1
    time.sleep(3)  # Uprzejme opóźnienie między zapytaniami

Wskazówki produkcyjne:

  • Utrzymuj opóźnienie 2–5 sekund między zapytaniami z tej samej sesji.
  • Rotuj miasta w obrębie tego samego kraju, aby rozproszyć obciążenie.
  • Ustaw timeout na 30 sekund — proxy rezydencjalne są wolniejsze niż datacenter (zwykle 200–800ms vs 50–100ms).
  • Monitoruj success rate — jeśli spada poniżej 90%, zwiększ opóźnienie lub zmień pulę IP.
  • Używaj oddzielnych sesji (sticky sessions) dla każdego miasta, aby uniknąć zmiany IP w trakcie paginacji.

ProxyHat SDK — uproszczona integracja

ProxyHat oferuje SDK, które automatyzuje rotację IP i zarządzanie sesjami. Oto porównanie surowego proxy vs SDK:

Surowe proxy (requests)

import requests

# Ręczne zarządzanie proxy URL
proxy = "http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080"
resp = requests.get("https://www.google.com/search?q=test&gl=us&hl=en&pws=0",
                     proxies={"http": proxy, "https": proxy})
print(resp.status_code)

ProxyHat SDK

from proxyhat import ProxyHatClient

# Automatyczna rotacja, sesje, geotargeting
client = ProxyHatClient(
    username="user",
    password="pass",
    gateway="gate.proxyhat.com",
    port=8080,
)

# Sesja rezydencjalna dla Nowego Jorku
session = client.create_session(
    proxy_type="residential",
    country="US",
    city="newyork",
    sticky=True,
)

resp = session.get("https://www.google.com/search?q=test&gl=us&hl=en&pws=0")
print(f"Status: {resp.status_code}, IP: {session.current_ip}")

# Rotacja do Chicago
session.rotate(city="chicago")
resp = session.get("https://www.google.com/search?q=test&gl=us&hl=en&pws=0")
print(f"New IP: {session.current_ip}")

Sprawdź pełną dokumentację na docs.proxyhat.com. Cennik proxy rezydencjalnych znajdziesz na stronie cennika ProxyHat.

Aspekty prawne i etyczne scrapowania SERP

Scrapowanie publicznych wyników Google istnieje w szarej strefie prawnej. Google's Terms of Service zabraniają automatycznego pobierania wyników, ale dane publicznie dostępne mogą być chronione przez inne przepisy. W UE Database Directive (Directive 96/9/EC) i GDPR mogą mieć zastosowanie do zbieranych danych.

Key Takeaway: Scrapuj tylko dane publicznie dostępne, przestrzegaj robots.txt, nie przechowuj danych osobowych bez podstawy prawnej i rozważ użycie oficjalnego Google Search API dla zastosowań komercyjnych.

Najważniejsze wnioski (Key Takeaways)

  • Parametry URL + IP muszą być spójne — Google cross-checkuje geografię IP z gl, hl i uule. Używaj proxy rezydencjalnych z ProxyHat z lokalizacją zgodną z parametrami.
  • uule to klucz do precyzji na poziomie miastagl=us wystarczy tylko dla wyników krajowych. Dla lokalnego SEO potrzebujesz uule z kanoniczną nazwą miejsca.
  • Zawsze dodawaj pws=0 — wyłącza personalizację i zapewnia powtarzalność danych.
  • Sticky sessions dla paginacji — nie zmieniaj IP między stronami wyników, bo Google zwróci inne wyniki.
  • Backoff i circuit breaker — Google nałoży 429 przy zbyt agresywnym scrapowaniu. Wykładniczy backoff z jitterem to standard produkcyjny.
  • Impersonacja przeglądarkicurl_cffi z impersonate="chrome" omija podstawowe fingerprinting TLS, co zmniejsza wskaźnik blokad.

Gotowy na budowę lokalizowanych zbiorów danych SERP? Odwiedź przypadek użycia SERP tracking lub web scraping, aby zobaczyć więcej implementacji. Cennik i dostępne lokalizacje znajdziesz na stronie cennika i liście lokalizacji.

Często zadawane pytania

Czym jest geotargetowane scrapowanie SERP?

Geotargetowane scrapowanie SERP to proces pobierania wyników wyszukiwania Google z wymuszoną lokalizacją geograficzną — na poziomie kraju, miasta lub dzielnicy. Używa się parametrów URL takich jak gl, hl, google_domain i uule, w połączeniu z proxy rezydencjalnymi z lokalizacji zgodnej z parametrami, aby Google zwrócił wyniki tak, jakby zapytanie przyszło z konkretnego miejsca.

Dlaczego geotargetowane scrapowanie SERP jest ważne dla użytkowników proxy?

Google cross-checkuje parametry URL z lokalizacją IP. Jeśli parametry są niespójne z geografią IP, Google może zignorować parametry, pokazać CAPTCHA lub zablokować IP. Proxy rezydencjalne z lokalizacją zgodną z parametrami gl i uule są niezbędne do uzyskania wiarygodnych, powtarzalnych danych SERP dla konkretnych rynków lokalnych.

Który typ proxy działa najlepiej dla geotargetowanego scrapowania SERP?

Proxy rezydencjalne są najlepsze dla geotargetowanego scrapowania SERP, ponieważ pochodzą od prawdziwych dostawców ISP i są trudniejsze do wykrycia przez systemy anti-bot Google. Proxy datacenter są łatwo wykrywane i często blokowane. Proxy mobilne są bezpieczniejsze, ale droższe. Proxy rezydencjalne z geotargetowaniem na poziomie miasta oferują najlepszy stosunek ceny do niezawodności.

Jak unikać blokad podczas geotargetowanego scrapowania SERP?

Aby unikać blokad, używaj proxy rezydencjalnych z lokalizacją zgodną z parametrami URL, utrzymuj opóźnienia 2–5 sekund między zapytaniami, stosuj sticky sessions dla paginacji, używaj impersonacji przeglądarki (np. curl_cffi z impersonate=chrome), implementuj wykładniczy backoff przy HTTP 429 i utrzymuj success rate powyżej 90%. Rotuj miasta w obrębie kraju, aby rozproszyć obciążenie.

Śledzenie pozycji, którego nikt nie zablokuje

Dokładne dane SERP dzięki proxy residential. Załóż konto i rozpocznij monitorowanie.

Rozpocznij śledzenie
← Powrót do Bloga