Konut Proxy'leri ile Python'da Google Sıralama Takipçisi Geliştirme: Giriş
SEO ekipleri ve Python geliştiricileri için konut proxy'leri ile Python'da Google sıralama takipçisi geliştirme, artık tek seferlik bir kontrol işinden çok daha fazlasını gerektiriyor. Google, Eylül 2025'te num=100 parametresini kaldırdı, TLS parmak izi (JA3/JA4) ve IP itibar skoru tabanlı anti-bot katmanlarını sıkılaştırdı. Bu rehber, build a rank tracker python niyetini karşılayan production-ready bir rank takipçisinin nasıl kurulacağını adım adım gösterir: veri modeli, SERP sayfalama, curl_cffi ile TLS taklidi, ProxyHat konut proxy rotasyonu, SQLite geçmiş ve CAPTCHA/tekrar deneme stratejileri.
Hedef kitle, serp scraping python 2026 araması yapan SEO mühendisleri ve otomasyon ekipleridir. Amacımız, SERP takibi için tekrarlanabilir, ölçeklenebilir ve bloklara dayanıklı bir pipeline kurmaktır.
Sorunun Teknik Bağlamı: Neden Basit Bir requests.Get() Artık Çalışmıyor
Google, yıllardır SERP'lerini botlardan korumak için birden fazla sinyali birlikte değerlendirir:
- TLS parmak izi (JA3/JA4): Python'un
urllib3/requestsstack'i, gerçek Chrome'dan farklı bir ClientHello üretir. Bu, ilk el sıkışmada bile bot sinyali verir. Mozilla'nın User-Agent dokümanında UA manipülasyonunun artık tek başına yeterli olmadığı belirtilir. - IP itibar skoru: Datacenter IP blokları yüksek risk olarak işaretlenir. TLS fingerprinting makalesinde de belirtildiği gibi, JA3 hash'i + IP sınıfı birlikte değerlendirilir.
- Hız ve pattern sinyalleri: Aynı IP'den saniyede 50+ sorgu, sabit aralıklar veya aynı User-Agent imzası anında CAPTCHA tetikler.
Sonuç: düz requests.get("https://www.google.com/search?q=seo") çağrısı ortalama 200-400ms içinde 429 veya 503 döndürür, ya da CAPTCHA sayfası gelir. Bu yüzden konut proxy + TLS taklidi kombinasyonu zorunludur.
Veri Modeli: Neden Günlük Snapshot'lar Tek Seferlik Kontrolden İyidir
Tek seferlik kontrol, bir anahtar kelimenin bugünkü pozisyonunu verir ama trendi vermez. Algoritmik güncellemeler, mevsimsel dalgalanmalar ve SERP feature kaymaları zaman serisi gerektirir. Şu veri modelini öneriyoruz:
| Alan | Tip | Açıklama |
|---|---|---|
| keyword | TEXT | Takip edilen sorgu |
| target_domain | TEXT | İzlenen domain (örn. example.com) |
| country | TEXT | ISO kodu (US, DE, TR) |
| device | TEXT | desktop veya mobile |
| position | INTEGER | Organik sonuçtaki sıra (1-100), yoksa NULL |
| captured_at | TIMESTAMP | Snapshot zaman damgası (UTC) |
Günlük snapshot'lar 7 günlük hareketli ortalama ile sıralama oynaklığını yumuşatır. Tek seferlik veri gürültülüdür; örneğin bir kelime bugün #4, yarın #12 olabilir ama 7-gün ortalaması #6 sabit kalır. Bu, gerçek algoritmik düşüşleri sahte dalgalanmalardan ayırır.
SQLite Şema Kurulumu
import sqlite3
from datetime import datetime
def init_db(path="ranks.db"):
conn = sqlite3.connect(path)
conn.execute("""
CREATE TABLE IF NOT EXISTS rank_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
target_domain TEXT NOT NULL,
country TEXT NOT NULL,
device TEXT NOT NULL,
position INTEGER,
captured_at TEXT NOT NULL,
UNIQUE(keyword, target_domain, country, device, captured_at)
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_kw_domain ON rank_snapshots(keyword, target_domain)")
conn.commit()
return conn
def insert_snapshot(conn, keyword, target_domain, country, device, position):
conn.execute(
"INSERT OR REPLACE INTO rank_snapshots VALUES (NULL, ?, ?, ?, ?, ?, ?)",
(keyword, target_domain, country, device, position, datetime.utcnow().isoformat()),
)
conn.commit()
SERP Sayfalama: num=100 Kaldırıldıktan Sonra İlk 100'ü Çekmek
Google, Eylül 2025'te num=100 parametresini sessizce devre dışı bıraktı. Artık her SERP sayfası ~10 organik sonuç döndürür. İlk 100 sıralamayı izlemek için start=0,10,20,...,90 ile 10 sayfayı ardışık çekmeniz gerekir. Her sayfa farklı bir proxy oturumuyla istenmeli; aynı oturum 10 sayfa üst üste isterse pattern sinyali tetiklenir.
def build_serp_urls(keyword, country="US", num_pages=10):
base = "https://www.google.com/search"
urls = []
for i in range(num_pages):
params = {
"q": keyword,
"gl": country.lower(),
"hl": "en",
"start": str(i * 10),
"num": "10",
}
query = "&".join(f"{k}={v}" for k, v in params.items())
urls.append(f"{base}?{query}")
return urls
Neden Konut Proxy'leri ve Şehir Seviyesinde Geo-Hedefleme Zorunlu
Google sonuçları coğrafi olarak kişiselleştirir. gl=US parametresi ülke seviyesinde çalışır ama şehir seviyesinde farklılık gösterir. New York'taki bir kullanıcıyla Chicago'daki bir kullanıcı aynı kelime için farklı yerel sonuçlar görebilir. Konut proxy'leri, gerçek ISP atamaları olduğu için IP itibar skoru yüksektir ve blok oranını datacenter'a göre %80+ düşürür.
ProxyHat gateway'inde şehir seviyesinde geo-hedefleme username içine gömülür:
# HTTP proxy, US - Chicago, sticky session
http_proxy = "http://user-country-US-city-chicago-session-kw123:pass@gate.proxyhat.com:8080"
# SOCKS5 proxy
socks5_proxy = "socks5://user-country-DE-city-berlin-session-kw456:pass@gate.proxyhat.com:1080"
Sticky session, bir anahtar kelime için aynı IP'yi tutar; bu, sayfalama sırasında tutarlı sonuç kümesi sağlar ve Google'ın "kullanıcı tutarlılığı" sinyalini taklit eder. ProxyHat lokasyonları sayfasından desteklenen ülkeleri görebilirsiniz.
Uygulama: curl_cffi ile TLS Taklidi ve ProxyHat Rotasyonu
curl_cffi, libcurl-impersonate üzerine inşa edilmiştir ve Chrome'un TLS ClientHello'sunu taklit eder. Bu, JA3/JA4 parmak izini gerçek tarayıcıya yaklaştırır. Aşağıdaki örnek, ham proxy kullanımı ile ProxyHat SDK tarzını yan yana gösterir.
1. Ham curl_cffi + ProxyHat Proxy
from curl_cffi import requests as cffi_requests
import random, time
def fetch_serp_raw(keyword, country="US", city="chicago"):
session_id = f"kw{abs(hash(keyword)) % 100000}"
proxy = f"http://user-country-{country}-city-{city}-session-{session_id}:pass@gate.proxyhat.com:8080"
proxies = {"http": proxy, "https": proxy}
headers = {
"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",
"Accept-Language": "en-US,en;q=0.9",
}
results = []
for page in range(10):
url = f"https://www.google.com/search?q={keyword}&gl={country.lower()}&hl=en&start={page*10}"
try:
r = cffi_requests.get(url, impersonate="chrome", proxies=proxies, headers=headers, timeout=20)
if r.status_code == 429 or r.status_code == 503:
print(f"CAPTCHA/limit at page {page}, status={r.status_code}")
break
results.append(r.text)
time.sleep(random.uniform(2.0, 5.0))
except Exception as e:
print(f"Error page {page}: {e}")
break
return results
2. ProxyHat SDK Tarzı (Proxy Sınıfı ile Soyutlama)
from dataclasses import dataclass
from curl_cffi import requests as cffi_requests
import random, time
@dataclass
class ProxyHatConfig:
username: str = "user"
password: str = "pass"
host: str = "gate.proxyhat.com"
http_port: int = 8080
socks5_port: int = 1080
class ProxyHatClient:
def __init__(self, cfg: ProxyHatConfig):
self.cfg = cfg
def http_proxy(self, country, city=None, session=None):
user = f"{self.cfg.username}-country-{country}"
if city:
user += f"-city-{city}"
if session:
user += f"-session-{session}"
return f"http://{user}:{self.cfg.password}@{self.cfg.host}:{self.cfg.http_port}"
def fetch(self, url, country, city=None, session=None, impersonate="chrome", timeout=20):
proxy = self.http_proxy(country, city, session)
proxies = {"http": proxy, "https": proxy}
return cffi_requests.get(url, impersonate=impersonate, proxies=proxies, timeout=timeout)
def track_keyword(client, keyword, country="US", city="chicago"):
session_id = f"kw{abs(hash(keyword)) % 100000}"
pages = []
for p in range(10):
url = f"https://www.google.com/search?q={keyword}&gl={country.lower()}&hl=en&start={p*10}"
r = client.fetch(url, country, city, session_id)
if r.status_code in (429, 503):
return None, r.status_code
pages.append(r.text)
time.sleep(random.uniform(2.0, 5.0))
return pages, 200
3. Organik Sonuçları Parse Etme (CSS + Regex)
from bs4 import BeautifulSoup
import re
def parse_organic(html, target_domain):
soup = BeautifulSoup(html, "html.parser")
positions = []
rank = 0
for div in soup.select("div.g, div.MjjYud"):
link = div.select_one("a[href^='/url?q='], a[href^='http']")
if not link:
continue
href = link.get("href", "")
if href.startswith("/url?q="):
href = re.search(r"url\?q=([^&]+)", href)
href = href.group(1) if href else ""
if target_domain in href:
rank += 1
positions.append(rank)
else:
rank += 1
return positions[0] if positions else None
def parse_all_pages(pages, target_domain):
rank = 0
for html in pages:
soup = BeautifulSoup(html, "html.parser")
for div in soup.select("div.g, div.MjjYud"):
link = div.select_one("a[href^='/url?q='], a[href^='http']")
if not link:
continue
href = link.get("href", "")
if href.startswith("/url?q="):
m = re.search(r"url\?q=([^&]+)", href)
href = m.group(1) if m else ""
rank += 1
if target_domain in href:
return rank
return None
4. Tekrarar Deneme ile Exponential Backoff
import time, random
def fetch_with_retry(client, url, country, city, session_id, max_retries=4):
for attempt in range(max_retries):
try:
r = client.fetch(url, country, city, session_id)
if r.status_code == 200:
return r.text, 200
if r.status_code in (429, 503):
wait = (2 ** attempt) + random.uniform(0.5, 2.0)
print(f"Retry {attempt+1}/{max_retries}, status={r.status_code}, wait={wait:.1f}s")
time.sleep(wait)
continue
return None, r.status_code
except Exception as e:
wait = (2 ** attempt) + random.uniform(0.5, 2.0)
print(f"Exception {attempt+1}: {e}, wait={wait:.1f}s")
time.sleep(wait)
return None, 0
5. Concurrency ile Ülke Bazlı Havuzlar
import concurrent.futures
def run_tracker(keywords, target_domain, countries):
cfg = ProxyHatConfig(username="user", password="pass")
client = ProxyHatClient(cfg)
conn = init_db()
tasks = []
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex:
for kw in keywords:
for country in countries:
tasks.append(ex.submit(track_and_store, client, conn, kw, target_domain, country))
for t in concurrent.futures.as_completed(tasks):
try:
t.result()
except Exception as e:
print(f"Task failed: {e}")
def track_and_store(client, conn, keyword, target_domain, country):
session_id = f"kw{abs(hash(keyword+country)) % 100000}"
pages = []
for p in range(10):
url = f"https://www.google.com/search?q={keyword}&gl={country.lower()}&hl=en&start={p*10}"
html, status = fetch_with_retry(client, url, country, "chicago", session_id)
if html is None:
break
pages.append(html)
time.sleep(random.uniform(2.0, 5.0))
if pages:
pos = parse_all_pages(pages, target_domain)
insert_snapshot(conn, keyword, target_domain, country, "desktop", pos)
print(f"{keyword} | {country} | pos={pos}")
6. CSV Export
import csv
def export_csv(conn, path="ranks.csv"):
rows = conn.execute("SELECT keyword, target_domain, country, device, position, captured_at FROM rank_snapshots ORDER BY captured_at").fetchall()
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["keyword", "target_domain", "country", "device", "position", "captured_at"])
w.writerows(rows)
Production Hardening: CAPTCHA Tespiti ve Oynaklık Yumuşatma
CAPTCHA tespiti için iki sinyali izleyin: HTTP 429/503 ve sayfa içi ipuçları (id="captcha-form", "unusual traffic" metni). Aşağıdaki yardımcı fonksiyon CAPTCHA sayfalarını filtreler:
def is_captcha_page(html):
markers = ["captcha-form", "unusual traffic", "detected unusual traffic", "/sorry/index"]
return any(m in html.lower() for m in markers)
def smooth_volatility(conn, keyword, target_domain, country, window=7):
rows = conn.execute(
"SELECT position, captured_at FROM rank_snapshots WHERE keyword=? AND target_domain=? AND country=? ORDER BY captured_at DESC LIMIT ?",
(keyword, target_domain, country, window)
).fetchall()
positions = [r[0] for r in rows if r[0] is not None]
if not positions:
return None
return round(sum(positions) / len(positions), 1)
Concurrency'yi ülke başına 5-8 thread ile sınırlayın. Google, tek IP'den saniyede 10+ istek gördüğünde anında bloklar. ProxyHat'in fiyatlandırması sayfasından uygun havuz boyutunu seçebilirsiniz. ProxyHat dokümantasyonunda rotasyon ve geo-hedefleme detayları mevcuttur.
Yaygın Hatalar ve Edge Case'ler
- Aynı session ile 10 sayfa çekmek: Pattern sinyali tetikler. Her sayfa için yeni session veya en azından 3-5 saniye bekleme ekleyin.
- Reklamları organik saymak:
div.uEierdve sponsor işaretlispan'leri atlayın. Organik sonuçlardiv.giçindekia[href]'dir. - SERP feature'ları saymak: Featured snippet, People Also Ask, knowledge panel organik sıralamaya girmez. Bunları atlamak için CSS selector'ları daraltın.
- Saat dilimi tutarsızlığı:
captured_ather zaman UTC olmalı; yoksa günlük snapshot'lar kayar. - Tek proxy ile tüm ülkeler: Her ülke için ayrı proxy havuzu kullanın; yoksa geo-kişiselleştirme bozulur.
Etik ve Sınırlar
Sadece kendi domaininizin veya kamuya açık sıralamaların takibini yapın. robots.txt ve Google'ın ToS'una saygı gösterin; yüksek hacimde resmi bir SERP API (örn. Google Search Console API veya üçüncü parti SERP API) tercih edilir. Düşük hacimde (günde <500 anahtar kelime) konut proxy + curl_cffi yaklaşığı uygundur; yüksek hacimde resmi API maliyet/uyumluluk açısından daha mantıklıdır. Web scraping etiği rehberimizi de inceleyin.
Pratik tavsiye: İlk 100 sıralamayı izlemek için 10 sayfa × 2-5 saniye bekleme = anahtar kelime başına ~30-50 saniye. 500 anahtar kelime × 5 ülke = ~20-40 dakika. 8 thread ile bu ~5-8 dakikaya iner.
Key Takeaways
num=100Eylül 2025'te kaldırıldı; ilk 100 içinstart=0,10,...,90ile 10 sayfa çekin.- curl_cffi
impersonate="chrome"JA3/JA4 parmak izini taklit eder; düz requests.get() artık çalışmaz. - Konut proxy + şehir geo-hedefleme + sticky session, blok oranını datacenter'a göre %80+ düşürür.
- Günlük snapshot + 7-gün hareketli ortalama, sıralama oynaklığını yumuşatır.
- CAPTCHA için HTTP 429/503 ve sayfa içi işaretleri izleyin; exponential backoff ile tekrar deneyin.
- Ülke başına ayrı proxy havuzu ve 5-8 thread concurrency sınırı, Google'ın hız sinyallerini tetiklemez.
- Yüksek hacimde resmi SERP API'yi tercih edin; düşük hacimde konut proxy yaklaşığı yeterlidir.
SSS
Konut proxy'leri ile Python'da Google sıralama takipçisi nedir?
Konut proxy'leri ile Python'da Google sıralama takipçisi, gerçek ISP IP'leri üzerinden Google SERP'lerini periyodik olarak çeken, anahtar kelime bazlı sıralama geçmişini SQLite/CSV'de saklayan ve organik sonuçları ayrıştırarak hedef domainin pozisyonunu izleyen bir otomasyon sistemidir. Proxy rotasyonu ve TLS parmak izi taklidi sayesinde engellenmeden günlük snapshot alır.
Google sıralama takibi için hangi proxy tipi en iyisidir?
Konut (residential) proxy'leri en iyi seçenektir. Google, datacenter IP'lerini yüksek itibar skorlu bot trafiği olarak işaretler ve JA3/JA4 TLS parmak iziyle birlikte değerlendirir. Konut proxy'leri gerçek ISP ataması olduğundan CAPTCHA ve blok oranını ciddi şekilde düşürür. Şehir seviyesinde geo-hedefleme ile ülke sonuçlarını doğru alırsınız.
Google SERP scraping sırasında blokları nasıl önlersiniz?
curl_cffi ile Chrome TLS parmak izini taklit edin, anahtar kelime başına sticky session kullanın, ülke/şehir geo-hedeflemesi yapın, istekler arasına 2-8 saniye rastgele bekleme ekleyin, exponential backoff ile tekrar deneyin ve CAPTCHA tespiti için HTTP 429/503 ve sayfa içi ipuçlarını izleyin. Concurrency'yi ülke bazlı havuzlarla sınırlayın.
Google num=100 parametresi 2025'te neden kaldırıldı?
Google, Eylül 2025'te num=100 sorgu parametresini devre dışı bıraktı; artık SERP her zaman 10 organik sonuç döndürür. Bu yüzden ilk 100 sıralamayı izlemek için start=0,10,20,... parametresi ile 10 sayfayı ardışık çekmeniz gerekir. Her sayfa ayrı bir proxy oturumuyla istenmeli ve sonuçlar birleştirilmelidir.
Python rank takipçisinde hangi veri modeli kullanılmalı?
keyword, target_domain, country, device, position, captured_at alanlarından oluşan bir model kullanın. Bu alanlar SQLite tablosunda saklanır ve günlük snapshot'lar zaman serisi analizi için tutulur. Tek seferlik kontrol yerine günlük snapshot, sıralama oynaklığını yumuşatmanızı ve algoritmik değişimleri tespit etmenizi sağlar.






