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:
- Sabit önek:
w+CAEQ - Uzunluk anahtarı: Base64 stringinin uzunluğunu tek karakterle kodlar (8→a, 9→b, ..., 33→z, 34→0, ..., 43→9, 44→+, 45→/)
- 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 Tipi | Başarı Oranı | Latans | Fiyat/1GB | SERP İçin Uygunluk |
|---|---|---|---|---|
| Residential | ~95% | 200-800ms | Orta | En iyi — gerçek ISP IP'leri |
| Datacenter | ~40% | 50-200ms | Düşük | Zayıf — Google datacenter IP'leri tespit eder |
| Mobile | ~98% | 500-2000ms | Yü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.






