Python ile Residential Proxy Kullanarak Google Sıralama Takipçisi Oluşturun

Google'ın num=100 parametresini kaldırması ve TLS parmak izi (JA3/JA4) uygulaması sonrası Python'da üretim düzeyinde bir SERP sıralama takipçisi nasıl kurulur? curl_cffi, ProxyHat residential proxy, SQLite depolama ve tekrar deneme stratejileri ile tam rehber.

Build a Google Rank Tracker in Python with Residential Proxies

Python ile residential proxy kullanarak Google sıralama takipçisi oluşturun — bu rehber, Google'ın Eylül 2025'te num=100 parametresini kaldırmasından ve TLS/JA3-JA4 parmak izi doğrulamasını sıkılaştırmasından sonra, SERP'leri güvenilir şekilde çekmek, organik pozisyonları ayrıştırmak ve sıralama geçmişini SQLite'ta saklamak için gereken tüm teknik adımları kod örnekleriyle açıklar.

Neden Python ile Residential Proxy Kullanarak Google Sıralama Takipçisi Oluşturun?

Sıralama takibi, SEO mühendislerinin en temel tekrarlayan işlerinden biridir. Tek seferlik bir kontrol, bir anahtar kelimenin belirli bir andaki durumunu gösterir; ancak Google sonuçları coğrafi konuma, cihaza, oturum geçmişine ve hatta günün saatine göre değişir. Günlük SERP anlık görüntüleri almak, sıralama oynaklığını, algoritma güncellemelerinin etkisini ve içerik optimizasyonlarının sonuçlarını zaman içinde izlemenizi sağlar.

Google, Eylül 2025'te num=100 parametresini tamamen kaldırdı. Artık her sayfada en fazla 10 organik sonuç görüntüleniyor ve ilk 100 sonucu görmek için start=0,10,20... ile sayfalama yapmak gerekiyor. Aynı dönemde TLS ClientHello paketinin JA3 ve JA4 hash'lerini kontrol eden anti-bot katmanı sıkılaştırıldı; bu da standart requests veya urllib ile yapılan isteklerin büyük ölçüde bloklandığı anlamına geliyor. IETF TLS parmak izi taslağı ve Wikipedia TLS fingerprinting makalesi, bu tekniklerin arka planını detaylıca açıklar.

Bu rehberde şunları yapacağız:

  • Sıralama takibi için bir veri modeli tasarlamak
  • Sayfalı SERP çekmek ve organik sonuçları ayrıştırmak
  • curl_cffi ile Chrome parmak izi taklit etmek ve ProxyHat residential proxy ile IP rotasyonu yapmak
  • Sonuçları SQLite ve CSV'de saklamak
  • Üretim için tekrar deneme, CAPTCHA tespiti ve eşzamanlılık sınırları eklemek

Veri Modeli: Anahtar Kelime, Hedef Alan, Ülke, Cihaz, Pozisyon, Yakalama Zamanı

Sıralama takibinin gücü tutarlılıkta yatar. Her ölçümün en az altı alanı olmalıdır:

AlanTürAçıklama
keywordstrTakip edilen arama sorgusu
target_domainstrİzlenen alan adı (ör. example.com)
countrystrISO 3166-1 alpha-2 ülke kodu
devicestrdesktop veya mobile
positionintOrganik sonuç sırası (1–100)
captured_atdatetimeSERP'nin çekildiği UTC zaman damgası

Günlük anlık görüntüler, tek seferlik kontrollerden üç nedenden dolayı üstündür:

  1. Oynaklık tespiti: Haftalık ortalama pozisyon, günlük dalgalanmaları gizler. 30 günlük bir seri, algoritma güncellemelerinin etkisini ortaya çıkarır.
  2. Korelasyon analizi: Trafik düşüşlerini sıralama değişimleriyle eşleştirmek için zaman damgalı veri gerekir.
  3. A/B doğrulaması: İçerik değişikliğinden önce ve sonra alınan anlık görüntüler, nedensel ilişki kurmanın temelidir.

SQLite Şeması

import sqlite3
from datetime import datetime, timezone

DB_PATH = "rank_tracker.db"

def init_db(path: str = DB_PATH) -> None:
    conn = sqlite3.connect(path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS rankings (
            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,
            raw_html TEXT,
            UNIQUE(keyword, target_domain, country, device, captured_at)
        )
    """)
    conn.execute("""
        CREATE INDEX IF NOT EXISTS idx_rank_lookup
        ON rankings(keyword, target_domain, country, device, captured_at DESC)
    """)
    conn.commit()
    conn.close()

init_db()

Bu şema, aynı anahtar kelime + alan + ülke + cihaz kombinasyonu için günlük tek satır sağlar; UNIQUE kısıtı tekrarlanan çekimleri önler ve captured_at DESC indeksi en son kaydı hızlıca bulmanızı sağlar.

SERP Sayfalama: num=100 Kaldırıldıktan Sonra start Parametresi

Google, Eylül 2025'ten itibaren num=100 parametresini yok sayıyor. İlk 100 organik sonucu almak için start=0,10,20,30,40,50,60,70,80,90 değerleriyle 10 sayfalık istek göndermeniz gerekir. Her sayfa yaklaşık 10 organik sonuç döndürür; ancak SERP özellikleri ( bilgi grafikleri, "İnsanlar da soruyor" kutuları, alışveriş reklamları) sayfa başına organik sonuç sayısını düşürebilir.

Sayfalama Mantığı

from urllib.parse import urlencode, urlparse, parse_qs

def build_google_url(keyword: str, country: str, device: str, start: int = 0) -> str:
    base = "https://www.google.com/search"
    params = {
        "q": keyword,
        "gl": country,            # coğrafi konum
        "hl": "en",               # arayüz dili
        "num": "10",               # Google artık >10'u yok sayıyor
        "start": str(start),       # 0, 10, 20, 30...
        "nfpr": "1",               # yazım düzeltmelerini devre dışı bırak
    }
    if device == "mobile":
        params["udm"] = "1"        # mobil görünüm
    return f"{base}?{urlencode(params)}"

# İlk 100 için sayfa başlangıç değerleri
PAGE_STARTS = list(range(0, 100, 10))  # [0, 10, 20, ..., 90]

nfpr=1 parametresi, Google'ın otomatik yazım düzeltmelerini engeller; böylece "sıralama takibi" yerine "sıralama takip" aratırsanız bile orijinal sorgu korunur. udm=1 ise mobil cihaz formatını zorunlu kılar.

Neden Residential Proxy ve Şehir Düzeyinde Coğrafi Hedefleme Zorunlu?

Google'ın anti-bot katmanı iki temel sinyale bakar:

  1. TLS parmak izi (JA3/JA4): Python'ın standart HTTP kütüphaneleri, Chrome veya Firefox'tan farklı bir ClientHello paketi gönderir. Bu fark, JA3 hash'inde görünür ve Google bunu bir bot sinyali olarak kullanır.
  2. IP itibar skoru: Veri merkezi IP blokları (ASN bazında), yüksek istek hacmi nedeniyle düşük itibar skoruna sahiptir. Residential IP'ler ise gerçek ISP atamaları olduğundan çok daha yüksek geçiş oranları sunar.

Bu nedenle, üretim düzeyinde bir sıralama takipçisi için residential proxy ve tercihen şehir düzeyinde coğrafi hedefleme kullanmak pratik bir zorunluluktur. ProxyHat, gate.proxyhat.com:8080 HTTP ağ geçidinde ve gate.proxyhat.com:1080 SOCKS5 ağ geçidinde çalışır; kullanıcı adında bayraklar ile ülke, şehir ve oturum belirleyebilirsiniz.

Proxy TürüIP KaynağıGeçiş Oranı (tahmini)Coğrafi HedeflemeUygun Kullanım
DatacenterASN blokları%40–60ÜlkeDüşük riskli hedefler
ResidentialGerçek ISP atamaları%90+Ülke + ŞehirSERP scraping
Mobile4G/5G operatör IP'leri%95+ÜlkeMobil SERP

Şehir düzeyinde hedefleme, yerel arama sonuçlarını doğru şekilde yakalamak için kritiktir. "pizza" sorgusu Chicago'da New York'tan farklı sonuçlar döndürür. ProxyHat kullanıcı adında -country-US-city-chicago bayrağı ile bu hedeflemeyi yapabilirsiniz. Her anahtar kelime için -session-{keyword_id} ile yapışkan oturum kullanmak, aynı IP'den art arda sayfalama yapmanızı sağlar — bu, Google'ın "aynı kullanıcı sayfalar arasında geziniyor" modeline uyması için önemlidir.

curl_cffi ile Chrome Parmak İzi Taklidi ve ProxyHat Entegrasyonu

curl_cffi, libcurl'ün TLS parmak izi taklit yeteneğini Python'a taşır. impersonate='chrome' parametresi, ClientHello paketini gerçek Chrome tarayıcısıyla birebir aynı şekilde gönderir. Bu, JA3/JA4 kontrolünü aşmanın en pratik yoludur.

Ham Proxy Kullanımı

from curl_cffi import requests as cffi_requests
from urllib.parse import quote
import logging

logger = logging.getLogger("rank_tracker")

def fetch_serp_raw(
    keyword: str,
    country: str = "US",
    device: str = "desktop",
    start: int = 0,
    proxy_username: str = "user-country-US-city-chicago-session-abc123",
    proxy_password: str = "pass",
) -> str:
    """Tek bir SERP sayfasını ham HTML olarak döndürür."""
    url = build_google_url(keyword, country, device, start)
    proxy_url = f"http://{proxy_username}:{proxy_password}@gate.proxyhat.com:8080"

    try:
        resp = cffi_requests.get(
            url,
            impersonate="chrome",
            proxies={"http": proxy_url, "https": proxy_url},
            timeout=20,
            allow_redirects=True,
        )
        resp.raise_for_status()
        return resp.text
    except cffi_requests.errors.RequestsError as e:
        logger.error("SERP çekme hatası start=%d: %s", start, e)
        raise

ProxyHat SDK Tarafı (Sarmalayıcı)

from dataclasses import dataclass
from curl_cffi import requests as cffi_requests
import hashlib
import 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, config: ProxyHatConfig):
        self.cfg = config

    def build_proxy_url(self, country: str, city: str | None, session_id: str) -> str:
        flags = [f"country-{country}"]
        if city:
            flags.append(f"city-{city}")
        flags.append(f"session-{session_id}")
        uname = f"{self.cfg.username}-" + "-".join(flags)
        return f"http://{uname}:{self.cfg.password}@{self.cfg.host}:{self.cfg.http_port}"

    def fetch(self, url: str, country: str, city: str | None, session_id: str) -> str:
        proxy_url = self.build_proxy_url(country, city, session_id)
        resp = cffi_requests.get(
            url,
            impersonate="chrome",
            proxies={"http": proxy_url, "https": proxy_url},
            timeout=20,
        )
        resp.raise_for_status()
        return resp.text

# Kullanım
cfg = ProxyHatConfig(username="user", password="pass")
client = ProxyHatClient(cfg)
session_id = hashlib.md5(b"pizza-chicago").hexdigest()[:12]
html = client.fetch(
    build_google_url("pizza", "US", "desktop", 0),
    country="US",
    city="chicago",
    session_id=session_id,
)

Organik Pozisyonları Ayrıştırma

Google'ın HTML yapısı sık değişir; bu nedenle CSS seçiciler ve regex kombinasyonu kullanmak dayanıklılığı artırır. Organik sonuçlar genellikle div.g veya div[data-sokoban-container] içinde bulunur ve her birinin başlık etiketi h3 içerir.

from bs4 import BeautifulSoup
import re
from urllib.parse import urlparse

def parse_organic_results(html: str, target_domain: str) -> int | None:
    """
    Hedef alan adının ilk organik pozisyonunu döndürür.
    Bulunamazsa None döner.
    """
    soup = BeautifulSoup(html, "html.parser")
    position = 0

    # div.g seçicisi — klasik organik sonuç konteyneri
    for div in soup.select("div.g"):
        link = div.find("a", href=True)
        if not link:
            continue
        href = link["href"]
        # Reklam bağlantılarını atla
        if "/aclk?" in href or "googleads" in href:
            continue
        parsed = urlparse(href)
        host = parsed.netloc.lower()
        if host.startswith("www."):
            host = host[4:]
        position += 1
        if target_domain.lower() in host:
            return position

    # Yedek: regex ile cite etiketi
    for cite in soup.find_all("cite"):
        text = cite.get_text().strip()
        if target_domain.lower() in text.lower():
            # Pozisyonu yaklaşık olarak bul
            return position or None

    return None

İlk 100 Sonucu Çekme ve Saklama

import csv
from datetime import datetime, timezone

PAGE_STARTS = list(range(0, 100, 10))

def track_keyword(
    keyword: str,
    target_domain: str,
    country: str = "US",
    city: str | None = "chicago",
    device: str = "desktop",
    client: ProxyHatClient | None = None,
) -> dict:
    if client is None:
        client = ProxyHatClient(ProxyHatConfig())

    session_id = hashlib.md5(f"{keyword}-{country}-{city}".encode()).hexdigest()[:12]
    found_position = None
    all_html = []

    for start in PAGE_STARTS:
        url = build_google_url(keyword, country, device, start)
        html = client.fetch(url, country=country, city=city, session_id=session_id)
        all_html.append(html)

        pos = parse_organic_results(html, target_domain)
        if pos is not None:
            # Sayfa içi pozisyonu global pozisyona çevir
            found_position = start + pos
            break

        # Sayfada hiç organik sonuç yoksa dur
        if not BeautifulSoup(html, "html.parser").select("div.g"):
            break

    record = {
        "keyword": keyword,
        "target_domain": target_domain,
        "country": country,
        "device": device,
        "position": found_position,
        "captured_at": datetime.now(timezone.utc).isoformat(),
        "raw_html": "\n".join(all_html)[:5000],  # depolama için kısalt
    }

    save_to_sqlite(record)
    append_to_csv(record, "rankings.csv")
    return record

def save_to_sqlite(record: dict) -> None:
    conn = sqlite3.connect(DB_PATH)
    conn.execute("""
        INSERT OR REPLACE INTO rankings
        (keyword, target_domain, country, device, position, captured_at, raw_html)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    """, (
        record["keyword"],
        record["target_domain"],
        record["country"],
        record["device"],
        record["position"],
        record["captured_at"],
        record["raw_html"],
    ))
    conn.commit()
    conn.close()

def append_to_csv(record: dict, path: str) -> None:
    with open(path, "a", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=list(record.keys()))
        if f.tell() == 0:
            writer.writeheader()
        writer.writerow(record)

Üretim Dayanıklılığı: Tekrar Deneme, CAPTCHA Tespiti, Eşzamanlılık

Tek bir anahtar kelime için 10 sayfa çekmek, 10 HTTP isteği demektir. 100 anahtar kelime takip ediyorsanız, günde 1000 istek yaparsınız. Bu hacimde hatalar kaçınılmazdır; bu nedenle üretim kodu şu mekanizmaları içermelidir:

Üstel Geri Çekilme ile Tekrar Deneme

import time
import random
from curl_cffi import requests as cffi_requests

class RetryableFetcher:
    def __init__(self, max_retries: int = 3, base_delay: float = 2.0):
        self.max_retries = max_retries
        self.base_delay = base_delay

    def fetch_with_retry(self, url: str, proxy_url: str) -> str:
        last_exc = None
        for attempt in range(self.max_retries):
            try:
                resp = cffi_requests.get(
                    url,
                    impersonate="chrome",
                    proxies={"http": proxy_url, "https": proxy_url},
                    timeout=20,
                )
                if resp.status_code == 429:
                    raise RuntimeError("Rate limited (429)")
                if self._is_captcha(resp.text):
                    raise RuntimeError("CAPTCHA detected")
                resp.raise_for_status()
                return resp.text
            except Exception as e:
                last_exc = e
                delay = self.base_delay * (2 ** attempt) + random.uniform(0, 1)
                logger.warning("Deneme %d başarısız: %s — %.1fs bekleniyor", attempt + 1, e, delay)
                time.sleep(delay)
        raise last_exc

    @staticmethod
    def _is_captcha(html: str) -> bool:
        markers = [
            "recaptcha",
            "g-recaptcha",
            "unusual traffic",
            "our systems have detected",
            "sorry/image",
        ]
        html_lower = html.lower()
        return any(m in html_lower for m in markers)

Eşzamanlılık Sınırları

100 anahtar kelimeyi ardışık çekmek saatler sürebilir. Ancak çok yüksek eşzamanlılık, proxy sağlayıcının hız limitlerini aşar ve geçiş oranını düşürür. Pratik bir denge, ülke başına 5–10 eşzamanlı oturumdur.

import asyncio
from asyncio import Semaphore

async def track_keyword_async(
    keyword: str,
    target_domain: str,
    country: str,
    semaphore: Semaphore,
    client: ProxyHatClient,
) -> dict:
    async with semaphore:
        # curl_cffi'nin async API'si
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            None,
            lambda: track_keyword(keyword, target_domain, country, client=client),
        )

async def track_batch(keywords: list[str], target_domain: str, country: str, max_concurrent: int = 5):
    semaphore = Semaphore(max_concurrent)
    client = ProxyHatClient(ProxyHatConfig())
    tasks = [
        track_keyword_async(kw, target_domain, country, semaphore, client)
        for kw in keywords
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return results

# Çalıştırma
# asyncio.run(track_batch(["pizza", "burger", "sushi"], "example.com", "US"))

Sıralama Oynaklığı Yumuşatma

Günlük tek ölçümler gürültülüdür. 7 günlük hareketli ortalama, gerçek trendleri ortaya çıkarır:

def moving_average(positions: list[int | None], window: int = 7) -> list[float | None]:
    """None değerleri atlayarak hareketli ortalama hesaplar."""
    result = []
    for i in range(len(positions)):
        start = max(0, i - window + 1)
        chunk = [p for p in positions[start:i + 1] if p is not None]
        result.append(sum(chunk) / len(chunk) if chunk else None)
    return result

Etik ve Yasal Sınırlar

SERP scraping, Google'ın Hizmet Şartları'yla çelişebilir. Kendi alan adınızın sıralamasını izlemek düşük riskli olsa da, yüksek hacimli otomatik scraping yasal risk taşır. Şu ilkeleri gözetin:

  • Düşük hacim tercih edin: Günde 100 anahtar kelime, çoğu kullanım için yeterlidir. Daha fazlası için resmi bir SERP API'si (ör. Google Custom Search API veya ticari SERP API sağlayıcıları) kullanın.
  • Hız sınırlarına saygı gösterin: Saniyede 1–2 istek, proxy sağlayıcının ve hedefin limitlerini aşmamak için güvenli bir bölgedir.
  • robots.txt kontrolü: Google'ın robots.txt'si /search yolunu izliyor; bu, scraping'in teknik olarak kısıtlanmış olduğunu gösterir. Bu gerçeği bilerek hareket edin.
  • GDPR/CCPA: SERP'ler kişisel veri içermez; ancak IP rotasyonu yaparken kullanıcı gizliliğini gözetin. Wikipedia robots.txt makalesi, standartın nasıl çalıştığını açıklar.

ProxyHat, web scraping kullanım senaryosunda ve SERP takibi sayfasında bu konuları detaylıca ele alır. Fiyatlandırma için ProxyHat fiyatlandırma sayfasını, mevcut konumlar için lokasyon listesini inceleyebilirsiniz. Teknik dokümantasyon için ProxyHat dokümantasyonuna başvurun.

Karşılaştırma: SERP API vs. Kendi Takipçiniz

ÖzellikTicari SERP APIKendi Takipçiniz (ProxyHat)
Maliyet (10k istek/ay)$50–150$20–40 (proxy maliyeti)
EsneklikSınırlı (önceden tanımlı alanlar)Tam (özel sorgular, özel ayrıştırma)
Gecikme200–800ms (API ek yükü)100–400ms (doğrudan)
Veri saklamaSağlayıcıya bağlıTam kontrol (SQLite, CSV, S3)
Şehir düzeyi coğrafi hedefGenelde sınırlıEvet (ProxyHat -city bayrağı)

Önemli Çıkarımlar

Günlük SERP anlık görüntüleri, tek seferlik kontrollere göre üç nedenden üstündür: oynaklık tespiti, korelasyon analizi ve A/B doğrulaması. Tutarlı zaman damgalı veri, SEO kararlarının temelidir.

  • num=100 artık çalışmıyor; start=0,10,20... ile sayfalama yapın ve her sayfada organik sonuç sayısını kontrol edin.
  • TLS parmak izi (JA3/JA4) ve IP itibar skoru, datacenter proxy'leri SERP scraping için pratik olmaktan çıkarır; residential proxy + şehir hedefleme kullanın.
  • curl_cffi ile impersonate='chrome', TLS kontrolünü aşmanın en güvenilir yoludur.
  • ProxyHat -session-{id} bayrağı ile her anahtar kelime için yapışkan oturum kullanın; bu, sayfalar arası tutarlılık sağlar.
  • Üstel geri çekilme, CAPTCHA tespiti ve eşzamanlılık sınırları, üretimde %90+ başarı oranının anahtarıdır.
  • Etik sınırlara uyun: düşük hacim, hız kontrolü ve gerektiğinde resmi SERP API'si tercih edin.

SSS

Python ile residential proxy kullanarak Google sıralama takipçisi oluşturmak nedir?

Python ile residential proxy kullanarak Google sıralama takipçisi oluşturmak, Google arama sonuç sayfalarını (SERP) otomatik olarak çekip hedef alan adınızın organik pozisyonunu kaydeden bir sistem kurmaktır. Bu sistem, anahtar kelime, ülke, cihaz ve zaman damgası ile pozisyon verisini SQLite veya CSV'de saklar. Residential proxy, Google'ın TLS parmak izi ve IP itibar kontrollerini aşmak için gerçek ISP IP'leri kullanır.

Bu neden proxy kullanıcıları için önemlidir?

Google, Eylül 2025'ten itibaren num=100 parametresini kaldırdı ve TLS/JA3-JA4 parmak izi doğrulamasını sıkılaştırdı. Bu, standart Python HTTP kütüphaneleri ve datacenter proxy'leri ile SERP çekmeyi neredeyse imkansız kıldı. Residential proxy'ler, gerçek ISP atamaları olduğu için %90+ geçiş oranı sunar ve şehir düzeyinde coğrafi hedefleme ile yerel arama sonuçlarını doğru şekilde yakalamanızı sağlar.

Hangi proxy türü bu iş için en iyisidir?

SERP scraping için residential proxy en iyi seçimdir. Datacenter proxy'ler düşük IP itibarı nedeniyle %40–60 geçiş oranıyla sınırlıyken, residential proxy'ler %90+ geçiş oranı sunar. Mobil proxy'ler (%95+) daha yüksek geçiş oranı sağlasa da şehir düzeyinde coğrafi hedefleme sunmaz; bu da yerel arama sonuçlarını takip etmek için residential'ı daha uygun kılar.

Blokları nasıl önlersiniz?

Blokları önlemek için dört strateji uygulayın: (1) curl_cffi ile impersonate='chrome' kullanarak TLS parmak izini gerçek tarayıcıyla eşleştirin; (2) her anahtar kelime için yapışkan oturum (-session-{id}) kullanın, böylece sayfalar arası aynı IP'den istek yapın; (3) saniyede 1–2 istekle hız sınırlaması uygulayın; (4) üstel geri çekilme ile tekrar deneyin ve CAPTCHA tespiti yapın. Ayrıca ülke başına 5–10 eşzamanlı oturum sınırı, proxy sağlayıcının limitlerini aşmamak için önemlidir.

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

148+ ülkede 50M+ konut IP'sine AI destekli filtreleme ile erişin.

Fiyatlandırmayı GörüntüleKonut Proxy'leri
← Bloga Dön