Tüm yazılar
Pazarlama 6 dk okuma

Search Console'u elle indirmeyi bırakmak: API ile haftalık sorgu raporu

Search Console verisini API ile çekip haftalık sorgu raporuna dönüştürmek: erişim kurulumu, Python betiği, sayfalama, hafta karşılaştırması ve sık hatalar.

#seo #search-console #python #otomasyon

Search Console, bir sitenin hangi sorgularda göründüğünü öğrenmek için hâlâ en iyi ücretsiz kaynak; ama veriyi arayüzden elle indirip Excel'de karşılaştırmaya başladığınız anda iş bir raporlama alışkanlığına değil, her pazartesi tekrarlanan bir angaryaya dönüşüyor. Bu angaryanın asıl maliyeti harcanan yarım saat değil; dışa aktarma sınırına takılıp görünmeyen satırlar, geçen haftanın dosyasını bulamamak ve "bu düşüş gerçek mi" sorusuna ancak hafızayla cevap verebilmek.

Bu yazıda Search Console API'sine erişimi nasıl kurduğumu, veriyi Python ile nasıl çekip sayfaladığımı ve iki haftayı yan yana koyunca hangi üç listeye baktığımı anlatacağım. Betikler doğrudan kopyalanabilir; anahtar dosyası ve mülk adı koda gömülü değil, ortam değişkeninden geliyor.

Amaç gösterişli bir pano değil. Her hafta aynı saatte çalışan, bir CSV ve bir Markdown dosyası üreten, tarihle saklanan bir rutin. Bu rutin oturunca veri odaklı içerik stratejisi yazısında anlattığım "neyi, neden ölçüyoruz" sorusunun cevabı da her hafta elde hazır oluyor.

Arayüzün sınırları: 1000 satır ve 16 ay

Arayüzdeki performans raporu tabloda en fazla 1000 satır gösteriyor ve dışa aktarma da bu sınıra bağlı. Küçük bir blogda bu yeterli gibi görünüyor; birkaç yüz sayfalık bir e-ticaret ya da kurumsal sitede uzun kuyruk sorguların büyük kısmı o 1000 satırın dışında kalıyor. Veri geçmişi de yaklaşık 16 ayla sınırlı; daha eskisine bakmak istiyorsanız o günlerde bir yere kaydetmiş olmanız gerekiyor.

API bu sınırların ilkini ciddi ölçüde gevşetiyor: tek istekte 25.000 satıra kadar veri alabiliyor, startRow ile kaldığınız yerden devam edebiliyorsunuz. Sorgu ve sayfa kırılımını aynı tabloda, tek çağrıda istemek de mümkün; arayüzde bunun için önce sayfaya tıklayıp sonra sorgulara geçmek gerekiyor. 16 aylık pencere API'de de geçerli; bu yüzden asıl kazanç haftalık çekimi dosyaya yazıp kendi arşivinizi oluşturmak.

API erişimi: Cloud projesi, hizmet hesabı, mülk izni

Kurulum beş adım; en çok atlanan, sonuncusundan bir önceki.

  1. Cloud projesi: Google Cloud Console'da bir proje açın ya da var olanı kullanın. "APIs & Services" altındaki kütüphaneden Google Search Console API'yi etkinleştirin.
  2. Hizmet hesabı: IAM bölümünden bir hizmet hesabı oluşturun. Cloud tarafında rol vermenize gerek yok; yetki Search Console tarafında verilecek. Hesabın [email protected] biçimindeki e-posta adresini kopyalayın.
  3. JSON anahtar: hesabın anahtarlar sekmesinden JSON türünde bir anahtar oluşturup indirin. Bu dosya bir paroladır: depoya koymayın, .gitignore'a ekleyin, betiğe yolunu ortam değişkeniyle verin.
  4. Mülke kullanıcı olarak ekleme: Search Console'da ilgili mülkün Ayarlar > Kullanıcılar ve izinler bölümüne girip hizmet hesabının e-postasını ekleyin. Salt okuma için "Kısıtlı" izin yeterli. Bu adım atlanınca API çalışır gibi görünür ve 403 döner.
  5. Paketler: pip install google-api-python-client google-auth. Karşılaştırma betiği için ayrıca pandas ve tabulate.

Hizmet hesabı yerine OAuth ile kendi Google hesabınızla da bağlanabilirsiniz; ama zamanlanmış bir görevde tarayıcı açıp onay vermek istemezsiniz. Hizmet hesabı bir kez ekleyip unutulan tür. Müşteri sitelerinde de aynı yolu izliyorum: müşteri, benim kişisel hesabıma değil, projeye ait hizmet hesabına izin veriyor; iş bitince o tek satırı silmesi yeterli.

Python ile veriyi çekmek ve sayfalamak

Aşağıdaki betik son yedi günün sorgu ve sayfa kırılımını çekip CSV'ye yazıyor. Bitiş tarihini bugünden üç gün geriye alıyorum; Search Console'un son iki üç günü henüz kesinleşmemiş oluyor ve bunu hesaba katmayan her rapor cuma günkü "trafik düştü" telaşına dönüşüyor.

import csv, os
from datetime import date, timedelta
from google.oauth2 import service_account
from googleapiclient.discovery import build

KAPSAM = ["https://www.googleapis.com/auth/webmasters.readonly"]
MULK = os.environ["GSC_MULK"]                 # ornek: sc-domain:ornek.com
ANAHTAR = os.environ["GSC_ANAHTAR_DOSYASI"]   # hizmet hesabi JSON dosyasinin yolu

def servis():
    kimlik = service_account.Credentials.from_service_account_file(ANAHTAR, scopes=KAPSAM)
    return build("searchconsole", "v1", credentials=kimlik, cache_discovery=False)

def cek(svc, baslangic, bitis, parca_boyu=25000):
    satirlar, baslangic_satiri = [], 0
    while True:
        govde = {
            "startDate": baslangic.isoformat(),
            "endDate": bitis.isoformat(),
            "dimensions": ["query", "page"],
            "rowLimit": parca_boyu,        # API'nin istek basina ust siniri
            "startRow": baslangic_satiri,
        }
        yanit = svc.searchanalytics().query(siteUrl=MULK, body=govde).execute()
        parca = yanit.get("rows", [])
        satirlar.extend(parca)
        if len(parca) < parca_boyu:      # son sayfa geldi, dongu biter
            break
        baslangic_satiri += parca_boyu
    return satirlar

def yaz(satirlar, yol):
    with open(yol, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["sorgu", "sayfa", "tiklama", "gosterim", "ctr", "sira"])
        for r in satirlar:
            sorgu, sayfa = r["keys"]
            w.writerow([sorgu, sayfa, r["clicks"], r["impressions"],
                        round(r["ctr"], 4), round(r["position"], 1)])

if __name__ == "__main__":
    bitis = date.today() - timedelta(days=3)   # son 2-3 gun henuz kesinlesmemis olur
    baslangic = bitis - timedelta(days=6)      # 7 gunluk pencere
    veri = cek(servis(), baslangic, bitis)
    dosya = f"gsc/gsc_{baslangic}_{bitis}.csv"
    yaz(veri, dosya)
    print(f"{len(veri)} satir yazildi: {dosya}")

Birkaç ayrıntı. rowLimit için 25.000 API'nin istek başına üst sınırı; döngü, gelen parça bu sayıdan küçük olunca duruyor. Sayfalamayı yazmayı unutursanız betik hata vermez, sadece 25.000'inci satırdan sonrasını sessizce atar. keys listesi dimensions ile aynı sıradadır; sırayı değiştirirseniz açma satırını da değiştirin. Sonuçlarda arayüzün gizlediği anonim sorgular yer almadığı için CSV'deki toplam tıklama, arayüzdeki genel toplamdan biraz düşük çıkar; bu bir hata değil. Betiğin mantığı Python ile SEO otomasyonu yazısındaki denetim betikleriyle aynı: tek komut, tarihli dosya, insan yalnızca çıktıya bakar.

Haftalık karşılaştırma mantığı

Tek haftanın verisi bir liste; iki haftayı yan yana koyunca rapor oluyor. İki CSV'yi sorgu ve sayfa çifti üzerinden birleştirip üç listeye bakıyorum:

  • Tıklaması düşen sayfalar: sorgu ayrıntısını atıp sayfa bazında topluyorum, farkı en negatif olan 15 sayfa başa geliyor. Bir sayfa iki hafta üst üste bu listedeyse rakip içerik, kırılan iç bağlantı ya da güncelliğini yitirmiş bilgi arıyorum. Tek haftalık düşüşü tatil ve mevsimsellikle karıştırmamak için aynı listeyi dört hafta öncesiyle de üretiyorum.
  • Gösterimi artıp CTR'ı düşük kalan sorgular: Google sizi daha çok gösteriyor ama tıklanmıyorsunuz. Bu, başlık ve meta açıklamasının sorguyla konuşmadığının en net işareti; sıra 1-3 arasındaysa sorun neredeyse kesin snippet'tedir, 4-10 arasındaysa hem snippet hem içerik.
  • 4-10. sıradaki eşik sorgular: ilk sayfada ama görünürlüğü düşük. İçeriği güncellemek, ilgili yazılardan iç bağlantı vermek ve sorgunun sorduğu şeye doğrudan cevap veren bir bölüm eklemek bu sorguları en ucuz iten işler.
import sys
import pandas as pd

bu = pd.read_csv(sys.argv[1])        # bu haftanin CSV'si
gecen = pd.read_csv(sys.argv[2])     # gecen haftanin CSV'si
anahtar = ["sorgu", "sayfa"]
b = bu.merge(gecen, on=anahtar, how="outer", suffixes=("_bu", "_gecen")).fillna(0)

# 1) Tiklamasi dusen sayfalar: sorgu detayini at, sayfa bazinda topla
sayfa = b.groupby("sayfa")[["tiklama_bu", "tiklama_gecen"]].sum()
sayfa["fark"] = sayfa["tiklama_bu"] - sayfa["tiklama_gecen"]
dusen = sayfa[sayfa["fark"] < 0].sort_values("fark").head(15)

# 2) Gosterimi artip CTR'i dusuk kalan sorgular: baslik / meta firsati
firsat = b[(b["gosterim_bu"] > b["gosterim_gecen"] * 1.2)
           & (b["gosterim_bu"] >= 50) & (b["ctr_bu"] < 0.02)]

# 3) Esik sorgular: ilk sayfada ama 4-10 arasinda, itmekle yukari cikabilir
esik = b[(b["sira_bu"] >= 4) & (b["sira_bu"] <= 10) & (b["gosterim_bu"] >= 30)]

kolon = anahtar + ["gosterim_bu", "ctr_bu", "sira_bu"]
with open(f"rapor_{sys.argv[3]}.md", "w", encoding="utf-8") as f:
    f.write("# Haftalik Search Console raporu\n\n## Tiklamasi dusen sayfalar\n\n")
    f.write(dusen.to_markdown() + "\n\n## Baslik ve meta firsati\n\n")
    f.write(firsat.sort_values("gosterim_bu", ascending=False).head(20)[kolon].to_markdown(index=False))
    f.write("\n\n## Esik sorgular (4-10. sira)\n\n")
    f.write(esik.sort_values("gosterim_bu", ascending=False).head(20)[kolon].to_markdown(index=False) + "\n")

Betikteki eşikler (yüzde 20 gösterim artışı, 50 gösterim tabanı, yüzde 2 CTR) benim sitem için makul; sizinki için başlangıç noktası. Gösterim tabanını kaldırırsanız liste gürültüye boğulur: haftada 5 gösterimin 8'e çıkması bir fırsat değil, rastlantı. Rapor tek bir Markdown dosyasına, adında haftanın bitiş tarihiyle yazılıyor; CSV'ler de aynı düzende bir klasörde birikiyor. Altı ay sonra elinizde hem ham veri hem o haftaki yorum oluyor.

Sık hatalar ve önerilen düzen

Kurulumda ve ilk haftalarda en çok karşılaştığım hatalar şunlar:

  • Mülk adı formatı: alan adı mülkü için sc-domain:ornek.com, URL öneki mülkü için https://www.ornek.com/ ve sondaki eğik çizgi dahil. İkisini karıştırınca ya 403 ya da mülk bulunamadı hatası alırsınız; ilk bakacağınız yer burası.
  • İzin yok hatası: "User does not have sufficient permission" mesajıyla 403. Sebep neredeyse her zaman hizmet hesabının mülke eklenmemiş ya da yanlış mülke eklenmiş olması. API etkinleştirilmediyse yine 403 gelir ama mesaj "API has not been used in project" der; ikisini ayırt edin.
  • Veri gecikmesi: son iki üç gün eksik ya da kesinleşmemiş gelir. Bitiş tarihini geriye çekmeden karşılaştırma yaparsanız her hafta "düştü" alarmı üretirsiniz.

Önerilen düzen

Her pazartesi sabahı zamanlayıcının iki betiği sırayla çalıştırması yeterli: önce çekim, sonra karşılaştırma. Ortam değişkenleri zamanlayıcının kendi yapılandırmasında; anahtar dosyası tek bir klasörde, yalnızca o kullanıcının okuyabildiği izinle. CSV'ler gsc/ klasöründe tarih adıyla birikiyor, Markdown raporlar da yanlarında. Raporu okumak kısa sürüyor; üç listeden çıkan iş, o hafta yapacağım iki üç içerik düzeltmesine dönüşüyor ve notu raporun altına yazıyorum. Rapor iki hafta üst üste okunmadan kalıyorsa üretmeyi durduruyorum; okunmayan raporun otomasyonu, kendi kendine dönen bir masraf.

Özetle: arayüz 1000 satır ve yaklaşık 16 ay ile sınırlı; API, hizmet hesabı ve tek bir mülk izniyle bu sınırın dışına çıkıyor; sayfalamalı bir Python betiği haftalık veriyi CSV'ye yazıyor, ikinci bir betik iki haftayı karşılaştırıp düşen sayfaları, CTR fırsatlarını ve eşik sorguları tek bir Markdown dosyasına döküyor. Kendi siteniz ya da müşterileriniz için böyle bir rapor düzeni kurmak isterseniz veri ve otomasyon danışmanlığı işimi anlattığım ana sayfadaki /#contact bölümünden bana yazabilirsiniz.