Coğrafi Hedefli SERP Kazıma En İyi Uygulamaları: Geliştirici Rehberi

Google uule, gl, hl parametreleri ile şehir seviyesinde SERP verisi toplama. Residential proxy rotation, sticky session, retry stratejileri ve üretim ortamına hazır Python örnekleri.

Geo-Targeted SERP Scraping Best Practices for Local SEO Data
Bu makalede

Aynı Google sorgusu İstanbul'da, Berlin'de ve New York'ta tamamen farklı sonuçlar döndürür. Coğrafi hedefli SERP kazıma en iyi uygulamaları konusunda çalışan her SEO mühendisinin çözmesi gereken ilk problem budur. Bu rehberde, uule parametresinin yapımından sticky session'lara kadar tüm teknik katmanı adım adım ele alacağız.

Eğer rank-tracking pipeline'ınız ülke düzeyinde (gl=US) kalıyorsa, yerel SEO verileriniz eksik kalır. Google sonuçları şehir ve hatta ilçe seviyesinde farklılaşır. Bu yazıda, şehir bazlı lokal SERP kazıma için gereken tüm parametreleri, IP coğrafyası eşleştirme stratejilerini ve üretim ortamına hazır Python kod örneklerini bulacaksınız.

Coğrafi Hedefli SERP Kazıma En İyi Uygulamaları: Neden Lokasyon Önemli?

Google, kullanıcının IP adresi, önceki aramalar, dil ayarları ve Google hesabı verilerini birleştirerek kişiselleştirilmiş sonuçlar üretir. Bir kullanıcı "kahve" aradığında, Google fiziksel konumuna en yakın kafeleri sıralar. Aynı sorgu farklı şehirlerde farklı işletmeleri gösterir. Google Search Central belgeleri, lokal arama sonuçlarının kullanıcının konumuna göre optimize edildiğini doğrular.

Ülke düzeyinde hedefleme (gl=US) çoğu durumda yetersizdir. "dentist" sorgusu New York'ta Chicago'dan tamamen farklı diş hekimlerini listeler. ABD coğrafi hedefli SERP kazıma en iyi uygulamaları, şehir ve hatta ilçe seviyesinde hassasiyet gerektirir. Bu nedenle sadece gl parametresiyle kalmayıp uule parametresi ile tam şehir konumu sabitlemeniz gerekir.

Temel Lokalizasyon Parametreleri: gl, hl, google_domain, lr ve uule

Google SERP'ini lokasyona göre özelleştirmek için kullanılan temel parametreler şunlardır:

  • gl: Ülke kodu (örn. us, de, tr). Sonuçların hangi ülke için optimize edileceğini belirler.
  • hl: Arayüz dili (örn. en, de, tr). Google arama sayfasının dilini ayarlar.
  • google_domain: Hangi Google alan adının kullanılacağı (örn. google.com, google.de, google.com.tr).
  • lr: Dil kısıtlaması (örn. lang_en, lang_tr). Sonuçların belirli bir dilde olmasını sağlar.
  • uule: Canonical place name tabanlı şehir/lokalasyon sabitleme. En güçlü lokalizasyon parametresidir.
  • pws=0: Kişiselleştirmeyi devre dışı bırakır. Tutarlı, tekrarlanabilir sonuçlar için her istekte eklenmelidir.

SERP gl hl google_domain parametreleri birlikte kullanıldığında ülke ve dil seviyesinde kontrol sağlar. Ancak şehir seviyesinde hassasiyet için uule parametresi şarttır.

Google uule Parametresi: Şehir Seviyesinde Hassasiyet

uule parametresi, Google'ın kullanıcının tam konumunu simüle etmesini sağlar. Yapısı üç bölümden oluşur:

  1. Sabit önek: w+CAEQ
  2. Uzunluk anahtarı: Base64 stringinin uzunluğunu tek karakterle kodlar (8→a, 9→b, ..., 33→z, 34→0, ..., 43→9, 44→+, 45→/)
  3. Base64 kodlu canonical place name: Örn. New York,New York,United States

Örnek: New York,New York,United States → base64 → 44 karakter → anahtar + → uule değeri: w+CAEQ+TmV3IFlvcmssTmV3IFlvcmssVW5pdGVkIFN0YXRlcw==

Wikipedia'nın Google Search makalesi, arama motorunun konum bazlı sonuç sunma mekanizmasını detaylandırır. uule parametresi, bu mekanizmayı programatik olarak kontrol etmenin en doğrudan yoludur.

IP Coğrafyası ve Parametre Uyumu

Google, URL parametreleri ile istek yapan IP'nin coğrafi konumunu çapraz kontrol eder. Eğer gl=us ve uule=New York parametreleriyle birlikte Almanya merkezli bir datacenter IP'si kullanırsanız, Google tutarsızlık tespit edebilir ve CAPTCHA veya 429 yanıtı döndürebilir.

Bu nedenle istekleri hedef pazarla eşleşen residential proxy üzerinden yönlendirmeniz gerekir. ProxyHat'ın gate.proxyhat.com:8080 gateway'i, kullanıcı adında ülke ve şehir parametreleriyle coğrafi hedefleme sağlar:

curl -x "http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080" \
  "https://www.google.com/search?q=coffee+shop&gl=us&hl=en&uule=w+CAEQ+TmV3IFlvcmssTmV3IFlvcmssVW5pdGVkIFN0YXRlcw==&pws=0&num=100"

Bu örnekte IP, ABD/New York residential havuzundan seçilir ve uule parametresiyle aynı şehri hedefler. Parametreler ve IP coğrafyası uyumlu olduğu için Google tutarlı lokal sonuçlar döndürür.

Python ile Uygulamalı Örnekler

Örnek 1: uule Üretimi ve requests ile Raw Proxy Kullanımı

import base64
import requests
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def make_uule(place_name: str) -> str:
    """Google uule parametresini canonical place name'den üretir."""
    b64 = base64.b64encode(place_name.encode("utf-8")).decode("utf-8")
    length = len(b64)
    if 8 <= length <= 33:
        key = chr(ord("a") + length - 8)
    elif 34 <= length <= 43:
        key = chr(ord("0") + length - 34)
    elif length == 44:
        key = "+"
    elif length == 45:
        key = "/"
    else:
        raise ValueError(f"Unexpected base64 length: {length}")
    return f"w+CAEQ{key}{b64}"

# New York için uule değeri
uule = make_uule("New York,New York,United States")
# Sonuç: w+CAEQ+TmV3IFlvcmssTmV3IFlvcmssVW5pdGVkIFN0YXRlcw==

params = {
    "q": "coffee shop",
    "gl": "us",
    "hl": "en",
    "uule": uule,
    "pws": "0",
    "num": "100",
}

# Raw proxy URL yapımı
proxy = "http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}

resp = requests.get(
    "https://www.google.com/search",
    params=params,
    proxies=proxies,
    timeout=30,
)
logger.info(f"Status: {resp.status_code}, Length: {len(resp.text)}")

Örnek 2: curl_cffi ile Chrome Taklidi

Google, TLS parmak iziyle bot tespiti yapar. Standart requests kütüphanesinin TLS imzası kolayca tanınır. curl_cffi ile Chrome TLS parmak izini taklit edebilirsiniz:

from curl_cffi import requests as cffi_requests

def scrape_serp_cffi(query: str, country: str, city: str, lang: str = "en"):
    uule = make_uule(f"{city.title()},{city.title()},United States")
    params = {
        "q": query,
        "gl": country,
        "hl": lang,
        "uule": uule,
        "pws": "0",
        "num": "100",
    }
    proxy = (
        f"http://user-country-{country.upper()}-city-{city.lower()}"
        f":pass@gate.proxyhat.com:8080"
    )

    resp = cffi_requests.get(
        "https://www.google.com/search",
        params=params,
        proxies={"https": proxy},
        impersonate="chrome",
        timeout=30,
    )
    return resp

resp = scrape_serp_cffi("best pizza", "us", "newyork")
print(f"Status: {resp.status_code}, Bytes: {len(resp.text)}")

Örnek 3: selectolax ile Organik Sonuçları ve Local Pack'i Parse Etme

from selectolax.parser import HTMLParser

def parse_serp(html: str) -> dict:
    tree = HTMLParser(html)
    results = {"organic": [], "local_pack": []}

    # Organik sonuçlar
    for div in tree.css("div.g"):
        title_el = div.css_first("h3")
        link_el = div.css_first("a[href]")
        if title_el and link_el:
            results["organic"].append({
                "title": title_el.text(),
                "url": link_el.attributes.get("href", ""),
            })

    # Local pack (Google Business sonuçları)
    for div in tree.css("div[role='heading']"):
        parent = div.parent
        if parent:
            cls = parent.attributes.get("class", "")
            if "local" in cls.lower() or "business" in cls.lower():
                results["local_pack"].append({
                    "name": div.text(),
                })

    return results

data = parse_serp(resp.text)
print(f"Organik sonuç: {len(data['organic'])}")
print(f"Local pack: {len(data['local_pack'])}")
for item in data["organic"][:5]:
    print(f"  - {item['title']}")

Örnek 4: Sticky Session ile Çok Sayfalı SERP

Çok sayfalı SERP kazımada, sayfalar arası tutarlılık için aynı IP'yi kullanmanız gerekir. ProxyHat'ın session parametresi ile sticky session oluşturabilirsiniz:

import uuid

def scrape_multi_page(query: str, country: str, city: str, max_pages: int = 3):
    session_id = str(uuid.uuid4())[:8]
    # Sticky session: aynı IP tüm sayfalarda korunur
    proxy = (
        f"http://user-country-{country.upper()}-city-{city.lower()}"
        f"-session-{session_id}:pass@gate.proxyhat.com:8080"
    )
    proxies = {"http": proxy, "https": proxy}

    all_results = []
    for page in range(max_pages):
        params = {
            "q": query,
            "gl": country,
            "hl": "en",
            "uule": make_uule(f"{city.title()},{city.title()},United States"),
            "pws": "0",
            "start": str(page * 10),
        }
        resp = requests.get(
            "https://www.google.com/search",
            params=params,
            proxies=proxies,
            timeout=30,
        )
        if resp.status_code == 200:
            page_data = parse_serp(resp.text)
            all_results.extend(page_data["organic"])
            logger.info(f"Sayfa {page}: {len(page_data['organic'])} sonuç")
        else:
            logger.warning(f"Sayfa {page}: HTTP {resp.status_code} - durduruluyor")
            break
    return all_results

results = scrape_multi_page("dentist", "us", "newyork", max_pages=3)
print(f"Toplam sonuç: {len(results)}")

Örnek 5: ProxyHat SDK Wrapper ile Basitleştirilmiş Kullanım

import uuid
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ProxyHatSERP:
    """ProxyHat residential proxy ile geo-targeted SERP kazıma SDK wrapper."""

    GATEWAY = "gate.proxyhat.com"
    HTTP_PORT = 8080
    SOCKS5_PORT = 1080

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

    def get_proxy(self, country: str, city: str = None,
                  session: str = None, socks5: bool = False) -> dict:
        user = f"{self.username}-country-{country.upper()}"
        if city:
            user += f"-city-{city.lower()}"
        if session:
            user += f"-session-{session}"

        port = self.SOCKS5_PORT if socks5 else self.HTTP_PORT
        scheme = "socks5" if socks5 else "http"
        proxy_url = f"{scheme}://{user}:{self.password}@{self.GATEWAY}:{port}"
        return {"http": proxy_url, "https": proxy_url}

    def scrape(self, query: str, country: str, city: str,
               lang: str = "en", pages: int = 1) -> list:
        session_id = str(uuid.uuid4())[:8] if pages > 1 else None
        proxies = self.get_proxy(country, city, session_id)
        uule = make_uule(f"{city.title()},{city.title()},United States")

        all_results = []
        for page in range(pages):
            params = {
                "q": query, "gl": country, "hl": lang,
                "uule": uule, "pws": "0", "num": "100",
                "start": str(page * 10),
            }
            resp = requests.get(
                "https://www.google.com/search",
                params=params, proxies=proxies, timeout=30,
            )
            if resp.status_code == 200:
                data = parse_serp(resp.text)
                all_results.append(data)
                logger.info(f"Sayfa {page}: {len(data['organic'])} organik sonuç")
            else:
                logger.warning(f"Sayfa {page}: HTTP {resp.status_code}")
        return all_results

# Kullanım - raw proxy yerine temiz SDK arayüzü
scraper = ProxyHatSERP("user", "pass")
data = scraper.scrape("plumber", "US", "chicago", pages=2)
for page_data in data:
    for item in page_data["organic"][:3]:
        print(f"  {item['title']}")

Örnek 6: CAPTCHA/429 Backoff ve Retry Stratejisi

import time

class SERPScraper:
    """Exponential backoff ve circuit breaker ile güvenli SERP kazıma."""

    def __init__(self, max_retries: int = 3, base_delay: float = 5.0,
                 failure_threshold: int = 5):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.failure_threshold = failure_threshold
        self.circuit_open = False
        self.failure_count = 0

    def fetch_with_retry(self, url: str, params: dict, proxy: str) -> str:
        if self.circuit_open:
            raise RuntimeError("Circuit breaker açık - daha sonra tekrar deneyin")

        for attempt in range(self.max_retries):
            try:
                resp = requests.get(
                    url,
                    params=params,
                    proxies={"https": proxy},
                    timeout=30,
                )
                if resp.status_code == 200:
                    self.failure_count = 0
                    return resp.text
                elif resp.status_code == 429:
                    delay = self.base_delay * (2 ** attempt)
                    logger.warning(f"429 alındı, {delay}s bekleniyor (deneme {attempt+1}/{self.max_retries})")
                    time.sleep(delay)
                elif resp.status_code == 503:
                    logger.warning(f"CAPTCHA/503 - deneme {attempt+1}")
                    time.sleep(self.base_delay * (attempt + 1))
                else:
                    logger.error(f"HTTP {resp.status_code} - durduruluyor")
                    break
            except requests.RequestException as e:
                logger.error(f"İstek hatası: {e}")
                time.sleep(self.base_delay)

        self.failure_count += 1
        if self.failure_count >= self.failure_threshold:
            self.circuit_open = True
            logger.error(f"Circuit breaker açıldı! {self.failure_threshold} ardışık hata.")
        return ""

    def reset_circuit(self):
        self.circuit_open = False
        self.failure_count = 0
        logger.info("Circuit breaker sıfırlandı.")

# Ülke bazlı proxy havuzu
PROXY_POOLS = {
    "US": [
        "http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080",
        "http://user-country-US-city-chicago:pass@gate.proxyhat.com:8080",
        "http://user-country-US-city-losangeles:pass@gate.proxyhat.com:8080",
    ],
    "DE": [
        "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080",
        "http://user-country-DE-city-munich:pass@gate.proxyhat.com:8080",
    ],
}

scraper = SERPScraper(max_retries=3, base_delay=5.0)

# ABD/New York için kazıma
proxy = PROXY_POOLS["US"][0]
uule = make_uule("New York,New York,United States")
html = scraper.fetch_with_retry(
    "https://www.google.com/search",
    {"q": "restaurant", "gl": "us", "hl": "en", "uule": uule, "pws": "0"},
    proxy,
)
if html:
    results = parse_serp(html)
    print(f"Organik: {len(results['organic'])}, Local: {len(results['local_pack'])}")

Yaygın Hatalar ve Edge Case'ler

  • Parametre-IP uyumsuzluğu: gl=us parametresiyle Almanya IP'si kullanmak, Google'ın tutarsızlık tespitine yol açar. Her zaman hedef lokasyonla eşleşen residential proxy kullanın.
  • pws=0 eksikliği: Kişiselleştirme açıkken aynı sorgu farklı oturumlarda farklı sonuçlar döndürür. Tutarlı veri için her istekte pws=0 ekleyin.
  • uule formatı yanlış: Base64 uzunluğu 46'dan büyükse veya 8'den küçükse length key hesaplanamaz. Canonical place name formatını kontrol edin (örn. "City,State,Country").
  • Rate limiting: Aynı IP'den saniyede 5'den fazla istek göndermek 429 yanıtı tetikler. ProxyHat residential havuzunda IP rotation ile bu sınırı yönetin. 100 eşzamanlı oturum için farklı session ID'leri kullanın.
  • Eski CSS selector'lar: Google HTML yapısını sık değiştirir. selectolax selector'larınızı her 2-4 haftada bir test edin.

Proxy Tipleri Karşılaştırması

Proxy TipiBaşarı OranıLatansFiyat/1GBSERP İçin Uygunluk
Residential~95%200-800msOrtaEn iyi — gerçek ISP IP'leri
Datacenter~40%50-200msDüşükZayıf — Google datacenter IP'leri tespit eder
Mobile~98%500-2000msYüksekİyi — yüksek riskli hedefler için

SERP kazıma için residential proxy'ler en iyi dengeyi sunar. ProxyHat'ın pricing sayfasından residential proxy paketlerini inceleyebilir, locations sayfasından mevcut ülke ve şehir listesini görebilirsiniz. Daha fazla bilgi için ProxyHat dokümantasyonunu ziyaret edin.

Hukuki ve Etik Hususlar

Google'ın Hizmet Şartları otomatik veri toplamaya izin vermez. SERP kazıma yapmadan önce:

  • Google'ın Search Central belgelerini ve robots.txt kurallarını inceleyin.
  • Hedef sitenin ToS'unu kontrol edin.
  • GDPR ve CCPA kapsamında kişisel veri toplamaktan kaçının.
  • Rate limiting uygulayın ve hedef sunucuya aşırı yük bindirmeyin.
  • Topladığınız verinin kullanım amacını netleştirin — rekabet analizi ve SEO araştırması genellikle kabul edilebilir kullanımlardır.

Yasal tavsiye için bir avukata danışın. Bu rehber teknik bilgi amaçlıdır ve hukuki tavsiye içermez.

Önemli Çıkarımlar

Key Takeaways:

  • Ülke düzeyinde gl parametresi yetersizdir — şehir hassasiyeti için uule kullanın.
  • uule = sabit önek (w+CAEQ) + length key + base64(canonical place name).
  • Her istekte pws=0 ekleyerek kişiselleştirmeyi devre dışı bırakın.
  • IP coğrafyası ile URL parametreleri her zaman uyumlu olmalıdır — residential proxy kullanın.
  • Çok sayfalı SERP için sticky session (session ID) kullanın.
  • 429 ve 503 yanıtları için exponential backoff + circuit breaker uygulayın.
  • Google HTML yapısını sık değiştirir — selector'larınızı düzenli test edin.

SERP kazıma pipeline'ınızı kurmaya hazır mısınız? SERP tracking use case sayfamızdan başlayın, web scraping use case'i ile genel kazıma stratejilerinizi genişletin.

Sık sorulan sorular

Coğrafi hedefli SERP kazıma en iyi uygulamaları nelerdir?

Coğrafi hedefli SERP kazıma en iyi uygulamaları şunları içerir: uule parametresi ile şehir seviyesinde konum sabitleme, gl ve hl parametreleri ile ülke ve dil ayarlama, pws=0 ile kişiselleştirmeyi devre dışı bırakma, hedef lokasyonla eşleşen residential proxy kullanma, sticky session ile çok sayfalı tutarlılık sağlama, ve 429/503 yanıtları için exponential backoff ile retry stratejisi uygulama.

Google uule parametresi nedir ve nasıl kullanılır?

Google uule parametresi, arama sonuçlarının belirli bir şehir veya lokalasyona sabitlenmesini sağlar. Yapısı üç bölümden oluşur: sabit w+CAEQ öneki, base64 string uzunluğunu kodlayan tek karakterlik length key, ve canonical place name'in base64 kodlu hali. Örneğin New York için uule değeri w+CAEQ+TmV3IFlvcmssTmV3IFlvcmssVW5pdGVkIFN0YXRlcw== şeklindedir. Bu parametre gl'den daha hassas lokal sonuçlar sağlar.

SERP kazıma için hangi proxy tipi en iyisidir?

SERP kazıma için residential proxy'ler en iyi seçenektir. Gerçek ISP IP'leri kullandıkları için Google tarafından datacenter IP'lerine kıyasla çok daha az tespit edilirler. Residential proxy'lerle ~95% başarı oranı elde edilirken, datacenter proxy'lerde bu oran ~40%'a düşer. Mobile proxy'ler ~98% başarı oranı sunsa da daha yüksek latans ve maliyet nedeniyle genellikle yüksek riskli hedefler için reservedir.

SERP kazımada engellenmeyi nasıl önlersiniz?

Engellenmeyi önlemek için: hedef lokasyonla eşleşen residential proxy kullanın, curl_cffi ile Chrome TLS parmak izini taklit edin, saniyede 5'den az istek gönderin, 429 yanıtlarında exponential backoff uygulayın, sticky session ile IP tutarlılığı sağlayın, ve her istekte pws=0 parametresini ekleyin. Ayrıca circuit breaker pattern ile ardışık hatalarda kazımayı durdurup sonra yeniden deneyin.

gl, hl ve google_domain parametreleri arasındaki fark nedir?

gl parametresi sonuçların hangi ülke için optimize edileceğini belirler (örn. us, de). hl parametresi Google arayüzünün dilini ayarlar (örn. en, de). google_domain ise isteğin hangi Google alan adına yapılacağını kontrol eder (örn. google.com, google.de). lr parametresi ise sonuçların belirli bir dilde olmasını zorunlu kılar (örn. lang_en). Bu parametreler birlikte kullanıldığında ülke ve dil seviyesinde kontrol sağlar, ancak şehir hassasiyeti için uule gereklidir.

Başlamaya hazır mısınız?

148'den fazla ülkede residential, ISP ve mobil proxy'ler. Ücretsiz hesap oluşturun.

Ücretsiz hesap oluştur
← Bloga Dön