Mert Özen Her Satırda Daha İleri
SEO ve GEO Analytics ve Ölçüm — #01

SEO'yu Ölçmek: Hangi Soru, Hangi Veri? Search Console, GA4, Server Log ve CrUX Neden Farklı Sayılar Verir

Mert Özen 15 Kas 2026 10 dk 4 görüntülenme
SEO'yu Ölçmek: Hangi Soru, Hangi Veri? Search Console, GA4, Server Log ve CrUX Neden Farklı Sayılar Verir

Search Console, GA4, sunucu log'u ve CrUX aynı ziyaretin farklı parçalarını saydığı için farklı sayılar verir; doğru sayı, sorduğun soruya göre seçilir. Soru-metric-kaynak eşlemesini, sayıların neden tutmadığını, Google'ın Referer kurallarıyla log'dan Google trafiğini ayıran C# kodunu ve mertozen.com için ölçüm seçeneklerini anlatıyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir. Bu yazıda resmî belgelerden yararlanılmıştır: Search Console, Google Analytics ve Chrome UX Report belgelerinin 15 Kasım 2026'daki hâli.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) blog'u için haftalık toplantıdasın. Pazarlamadan Selin üç ekran açmış: Search Console geçen hafta için bir click sayısı gösteriyor, GA4 (Google Analytics 4; Google'ın web analitik ürünü) "Organic Search" kanalında bundan belirgin biçimde az session gösteriyor, Emre'nin sunucu log'undan çıkardığı "Google'dan gelen istek" sayısı ise ikisinden de farklı. Selin soruyor: "Hangisi doğru? Biri bozuk mu?" Sence üçünden biri yanlış mı, yoksa üçü de doğru ama farklı şeyleri mi sayıyor? Ve hangi sayıyı yönetime "SEO (arama motoru optimizasyonu) bize ne kazandırdı?" sorusunun cevabı olarak götürmelisin?

Önce Soru, Sonra Metric, Sonra Kaynak

Ölçüm işi çoğu zaman tersten başlar: bir araç kurulur, dashboard'a ne çıkıyorsa ona bakılır. Doğru sıra şu: önce cevaplamak istediğin soruyu yaz, sonra o soruya cevap veren metric'i seç, en son o metric'i hangi kaynağın ölçebildiğine bak. SEO tarafında karşına çıkan soruların çoğu beş gruba giriyor. Tabloda geçen kısaltmalar seri boyunca sık kullanılacak: CTR (click-through rate; tıklamanın impression'a oranı), LCP (Largest Contentful Paint; sayfadaki en büyük içeriğin görünme süresi), INP (Interaction to Next Paint; bir etkileşime sayfanın tepki süresi), CLS (Cumulative Layout Shift; sayfa içeriğinin beklenmedik kaymalarının toplamı), CrUX (Chrome UX Report; uygun Chrome kullanıcılarından toplanan gerçek deneyim verisi) ve RUM (Real User Monitoring; kendi ziyaretçilerinden topladığın ölçüm). Bu metric'lerin SEO'yla ilişkisini serinin altıncı yazısında ayrıntılı konuşacağız.

SoruMetricKaynak
Google'da görünüyor muyum?impression, average positionSearch Console Performance raporu
Görününce tıklanıyor muyum?click, CTRSearch Console Performance raporu
Gelen okur sitede ne yapıyor?session, engagement rate, event, key eventGA4 veya başka bir site içi analitik aracı
Sayfa gerçek kullanıcı için hızlı mı?LCP, INP, CLS (75. persentil)CrUX, Search Console Core Web Vitals raporu, kendi RUM'un
Bot'lar ve gerçek istekler ne yapıyor?istek sayısı, status code, referersunucu log'u

Tablonun asıl mesajı şu: hiçbir kaynak bütün soruları cevaplamıyor ve bir kaynağın cevap veremediği soruyu ona sormak, yanlış sayıya güvenmenin en kısa yolu. "SEO işe yaradı mı?" sorusu aslında ilk iki satırın sorusu; "gelen okur memnun mu?" üçüncü satırın. Toplantıda bunları tek bir sayıya sıkıştırmaya çalışmak, Selin'in yaşadığı karışıklığın kaynağı.

Her Kaynak Yolculuğun Başka Bir Parçasını Görür

01-data-sources-tr.png

Bir okurun yolculuğunu adım adım düşün. Google sonuç sayfasında Bookstore'un bir yazısı görünüyor; Search Console bunu impression olarak sayıyor. Okur tıklıyor; Search Console bunu click olarak sayıyor ve tıklama anında işi bitiyor. Tarayıcı sunucuya istek atıyor; bunu yalnız senin sunucun görüyor ve log'a yazıyor. Sayfa yükleniyor, JavaScript çalışıyor ve kullanıcı izin verdiyse GA4 tag'i bir page_view gönderiyor. Okur kod kopyalıyor ya da serinin sonraki yazısına geçiyor; bunlar GA4'te event olarak görünüyor. Aynı ziyaret sırasında tarayıcı LCP, INP ve CLS'yi ölçüyor; bu değerler, okur uygun bir Chrome kullanıcısıysa CrUX'e, senin kurduğun bir RUM varsa ona gidiyor.

Search Console'un verisi Google'ın kendi kayıtlarından geliyor; senin sitende hiçbir kod çalışmasına gerek yok. GA4 ise tamamen tarayıcıda çalışan koda bağlı. Sunucu log'u ikisinin arasında duruyor: JavaScript'ten ve consent'ten bağımsız, ama her isteği, bot'lar dahil, sayıyor. Kaynakları kurmayı ve doğrulamayı SEO Temelleri serisinin "Google Search Console ve Bing Webmaster Tools'a Başlangıç" yazısında, bot isteklerini doğrulamayı Teknik SEO serisinin "Crawl Budget, Log Analizi ve Googlebot'u Doğrulamak" yazısında anlattık; burada yalnız hangisinin neyi gördüğüne bakıyoruz.

Sayılar Neden Tutmaz?

Google'ın Şubat 2025'te yayımladığı Search Console ve Google Analytics'i birlikte kullanma rehberi, bu iki aracın en yakın karşılığının "Search Console click'i ile Analytics session'ı" olduğunu söylüyor ve hemen ekliyor: ikisi farklı hesaplandığı için birebir tutmaz, önemli olan eğilimlerin aynı yöne gitmesi. Rehberin ve Google'ın Kasım 2026'da eklediği Referer sayfasının saydığı nedenleri kendi gözlemlerinle birleştirince liste şöyle oluyor:

  • Click ile session farklı anlarda sayılıyor: Search Console sonuç seçildiği anda sayıyor, GA4 ise sayfa yüklenip tag çalıştığında. Arada sayfayı beklemeden kapatan okur yalnız birinde var.
  • Consent, ad blocker ve gizlilik ayarları JavaScript tag'ini durduruyor; Search Console bundan etkilenmiyor.
  • Search Console tarihleri Pasifik saatine göre ve bu değiştirilemiyor; GA4 property'nin saat dilimini kullanıyor. Türkiye'den bakınca günlük sayılar gece yarısı civarında kayıyor.
  • GA4 attribution modelleri bir session'ı farklı kanala yazabiliyor; Search Console her click'i sayıyor.
  • Search Console veriyi Google'ın seçtiği canonical URL'ye yazıyor; GA4 tag'in çalıştığı her URL'yi ayrı görüyor.
  • PDF gibi HTML olmayan sayfalar Search Console'da click alabiliyor ama içlerinde tag çalışmadığı için GA4'te yok.
  • GA4 bilinen bot'ları dışarıda bırakıyor; Search Console'un bot filtrelemesi aynı değil, sunucu log'u ise ayıklamazsan her şeyi sayıyor.
  • Search Console gizlilik için bazı sorguları anonimleştiriyor: toplamlara dahil, sorgu tablosunda yoklar. Satırları toplayınca grafiğin toplamını bulamazsın.

Bu liste, başlangıçtaki sorunun cevabı: üç sayı da büyük ihtimalle doğru, sadece başka şeyleri sayıyorlar. Yönetime götüreceğin "Google'dan gelen trafik" sayısı Search Console click'i; "bu okurlar ne yaptı" sorusunun cevabı GA4'te; sunucu log'u ise ikisini kontrol etmek için bağımsız bir referans.

Sunucu Log'undan Google Trafiğini Ayırmak

Google 12 Kasım 2026'da "Identify Google Search traffic" adlı bir sayfa yayımladı ve log tarafını netleştirdi. Google Search origin referrer policy'si kullanıyor, yani Referer header'ında yalnız şema ve host var, yol ve sorgu yok. Web'de değer https://www.google.com/ ya da https://www.google.com.tr/ gibi yerel bir domain. Android'deki Google uygulaması linki tarayıcı sekmesinde açınca android-app://com.google.android.googlequicksearchbox/https/www.google.com gönderiyor. Google Images gibi aynı origin'den çalışan servisler de aynı değeri gönderdiği için bu sinyal "Google'ın bir yüzeyinden geldi" demek, "web aramasından geldi" demek değil. Sayfa bir de uyarıyor: CSS, JavaScript ve görsel istekleri de Referer taşır, ama değerleri senin sayfanın URL'si; yalnız landing sayfalarını saymak gerekiyor.

Bookstore blog'unun nginx'i log'u "combined" formatında yazıyor. Bu formattaki satırı okuyan küçük bir parser:

using System.Globalization;
using System.Text.RegularExpressions;

namespace Bookstore.Analytics.Logs;

// nginx ve Apache'nin kullandığı "combined" access log formatını okur:
// ip - user [time] "METHOD path HTTP/x" status bytes "referer" "user-agent"
public static partial class CombinedLogParser
{
    [GeneratedRegex("""^\S+ \S+ \S+ \[(?<time>[^\]]+)\] "(?<method>[A-Z]+) (?<path>\S+) [^"]*" (?<status>\d{3}) \S+ "(?<referer>[^"]*)" "(?<ua>[^"]*)"$""")]
    private static partial Regex Line();

    public static AccessLogEntry? Parse(string line)
    {
        Match m = Line().Match(line);
        if (!m.Success)
        {
            return null;
        }

        DateTimeOffset time = DateTimeOffset.ParseExact(
            m.Groups["time"].Value, "dd/MMM/yyyy:HH:mm:ss zzz", CultureInfo.InvariantCulture);
        string referer = m.Groups["referer"].Value;
        string ua = m.Groups["ua"].Value;

        return new AccessLogEntry(
            time,
            m.Groups["method"].Value,
            m.Groups["path"].Value,
            int.Parse(m.Groups["status"].Value, CultureInfo.InvariantCulture),
            referer is "" or "-" ? null : referer,
            ua is "" or "-" ? null : ua);
    }
}

Sınıflandırıcı önce landing olmayan istekleri (asset, redirect, hata, GET dışı) ayırıyor, sonra user agent'ı bilinen bir bot'u iddia eden istekleri kenara koyuyor, en son Referer'a bakıyor:

public sealed partial class TrafficClassifier(string siteHost)
{
    private static readonly string[] AssetExtensions =
        [".css", ".js", ".png", ".jpg", ".jpeg", ".webp", ".avif", ".svg", ".ico", ".woff2", ".map", ".json", ".xml", ".txt"];

    private static readonly string[] BotTokens = ["Googlebot", "bingbot", "GPTBot", "ClaudeBot", "PerplexityBot"];

    // www.google.com, www.google.com.tr, www.google.co.uk ...
    [GeneratedRegex(@"^(www\.)?google(\.[a-z]{2,3}){1,2}$")]
    private static partial Regex GoogleHost();

    private const string GoogleAndroidApp = "android-app://com.google.android.googlequicksearchbox/";

    public TrafficSource Classify(AccessLogEntry entry)
    {
        if (entry.Method != "GET" || entry.Status != 200 || IsAsset(entry.Path))
        {
            return TrafficSource.NotALanding;
        }

        if (entry.UserAgent is { } ua && BotTokens.Any(t => ua.Contains(t, StringComparison.OrdinalIgnoreCase)))
        {
            // yalnız user agent'ın iddiası; doğrulama reverse DNS ile
            return TrafficSource.ClaimsToBeBot;
        }

        if (entry.Referer is null)
        {
            return TrafficSource.NoReferer;
        }

        if (entry.Referer.StartsWith(GoogleAndroidApp, StringComparison.Ordinal))
        {
            return TrafficSource.GoogleSearch;
        }

        if (!Uri.TryCreate(entry.Referer, UriKind.Absolute, out Uri? referer))
        {
            return TrafficSource.OtherReferral;
        }

        if (referer.Host.Equals(siteHost, StringComparison.OrdinalIgnoreCase))
        {
            return TrafficSource.InternalNavigation;
        }

        return GoogleHost().IsMatch(referer.Host) ? TrafficSource.GoogleSearch : TrafficSource.OtherReferral;
    }

    private static bool IsAsset(string path)
    {
        string clean = path.Split('?', 2)[0];
        return AssetExtensions.Any(ext => clean.EndsWith(ext, StringComparison.OrdinalIgnoreCase));
    }
}

Bot ayrımı bilerek "iddia" diye adlandırıldı: user agent taklit edilebilir, gerçek Googlebot'u reverse DNS ve IP listesiyle doğrulamak Teknik SEO serisinin log yazısının konusu. Aşağıdaki çıktı, data/access-sample.log dosyasındaki 26 satırlık sentetik bir log'dan; satırlar gerçek bir sitenin trafiği değil, sınıflandırıcının her dalını denemek için yazıldı:

$ dotnet run --project src/Bookstore.Analytics.Cli -- logs data/access-sample.log www.bookstore.example
source               requests
NotALanding                 7
ClaimsToBeBot               2
GoogleSearch               10
OtherReferral               2
InternalNavigation          1
NoReferer                   3
unparsed lines: 1

top landing pages from Google Search:
    4  /blog/cs8618-nullable-uyarisi
    2  /blog/dependency-injection-nedir
    2  /blog/ef-core-migration-rehberi
    1  /blog/yeni-yazi
    1  /en/blog/ef-core-migrations-guide

Okunacak üç yer var. Birincisi, 25 parse edilmiş satırın yalnız 10'u Google'dan gelen landing; asset'ler ve bot'lar sayılsaydı sayı iki katına çıkardı. İkincisi, Google'dan gelen bir 301 ve bir 404 landing sayılmadı: redirect sonrası asıl sayfa ayrı bir istek olarak zaten geliyor, 404 ise bir okur kaybı ve ayrı izlenmeli. Üçüncüsü, NoReferer: gizli pencere, gizlilik eklentileri ve bazı yeni sekme davranışları header'ı siliyor; bu ziyaretlerin bir kısmı da Google'dan geliyor olabilir ve log bunu bilemez.

Kodun testleri tests/Bookstore.Analytics.Tests/Logs altında; Android uygulama değeri, ülke domain'leri, mail.google.com gibi arama olmayan Google host'ları ve asset filtresi için birer test var. dotnet test 11 testin hepsini geçiyor.

mertozen.com Vakası: Ne Kurmalı?

Bu siteyi örnek alalım. 15 Kasım 2026 itibarıyla mertozen.com'un kaynak kodunda client-side analytics script'i yok: ne GA4, ne başka bir tag. Bugün elde olan veri Search Console ve sunucu tarafındaki log'lar. Bir sonraki adım için üç gerçekçi seçenek var ve her biri farklı bir şeyden vazgeçiyor:

SeçenekNeyi gösterirNeyi göstermezMaliyet ve uyum
Yalnız Search Console ve sunucu log'uGoogle görünürlüğü, click, landing sayfalarıSayfa içi davranış, seri içi gezinme, kod kopyalamaEk script yok; consent banner gerekmez; Core Web Vitals'a etkisi yok
GA4, consent ileSession, engagement, event, key event, Search Console bağlantısıyla landing raporuConsent vermeyen okurlar; ad blocker kullananlarÜçüncü taraf script ve çerez; açık rıza akışı ve aydınlatma gerekir; sayfaya JavaScript maliyeti
Çerezsiz bir araç (Plausible, Umami, Matomo çerezsiz modda, Cloudflare Web Analytics)Sayfa görüntüleme, referrer, kaba ziyaretçi sayısıGeri dönen ziyaretçiyi güvenilir tanıma, ayrıntılı event analiziKüçük script; self-hosted seçenekler veriyi sende tutar; consent gerekip gerekmediği hukuki bir değerlendirme

Karar site sahibinin; bu seri hukuki tavsiye vermiyor ve bir seçeneği "doğru" ilan etmiyor. Teknik açıdan söyleyebileceğimiz şu: hangi seçenek seçilirse seçilsin, Search Console verisi aynı kalıyor ve SEO'nun etkisini ölçmenin ana kaynağı o. GA4 eklemek "SEO işe yarıyor mu?" sorusuna yeni bir cevap vermiyor; "gelen okur ne yapıyor?" sorusuna cevap veriyor. Consent ve çerezsiz araçların ayrıntısı serinin beşinci yazısında.

Üretimde Dikkat

Her raporun yanına tanım yaz. "Organik trafik" kelimesi bir slaytta Search Console click'i, diğerinde GA4 session'ı olabiliyor. Raporun başlığına kaynağı ve metric'i koymak, toplantıdaki yarım saatlik tartışmayı bitiriyor.

Saat dilimini sabitle. Günlük karşılaştırmalarda Search Console'un Pasifik saatini referans al ya da haftalık ve aylık toplamlarla çalış; gün sınırındaki kayma o zaman önemsizleşiyor.

Log'u saklama süresiyle birlikte düşün. Log'daki IP adresi ve user agent kişisel veri sayılabilir; analiz için gereken alanlar (zaman, yol, status, referer host'u, sınıflandırma sonucu) çoğu zaman yeterli. Log'da kişisel verinin ele alınışı .NET'te Logging serisinin "Log'larda Hassas Veri" yazısında.

Kaynak değiştiğinde not düş. Consent banner'ının metnini değiştirmek, bir ad blocker listesinin GA4'ü engellemeye başlaması ya da CDN'in (içeriği kullanıcıya yakın sunuculardan dağıtan ağ) log formatını değiştirmesi, trafikte bir değişiklik olmadan grafiği kırar. Bu kırılmaları ayırt etmeyi serinin son yazısında konuşacağız.

Sık Düşülen Tuzaklar

Vanity metric'e bakmak. Toplam sayfa görüntüleme büyük ve hoş bir sayı, ama "SEO işe yaradı mı?" sorusuna cevap vermiyor; bot'lar, yenilemeler ve iç gezinme de onu büyütüyor. Soruya bağlı olmayan bir sayı karar üretmez.

Tek kaynağı mutlak gerçek sanmak. GA4'teki düşüşü trafik kaybı, Search Console'daki artışı kesin başarı diye okumak; iki kaynağı birbirine karşı kontrol etmeden karar vermek.

Kaynakları birebir eşitlemeye çalışmak. Search Console click'i ile GA4 session'ının aynı çıkmasını beklemek, haftalarca boşuna "hata" aramak demek. Beklenen, ikisinin aynı yöne gitmesi.

Log'daki her Google referer'ını arama trafiği saymak. Asset istekleri, Google'ın başka servisleri ve redirect zincirleri sayıyı şişiriyor; landing sayfasını ve status code'u filtrelemek gerekiyor.

Küçük Bir Deneme

★☆☆ Kendi sitende son dört haftanın Search Console click toplamını ve GA4'teki "Organic Search" session toplamını yan yana yaz. Haftalık oranları hesapla: oran sabit mi, yoksa bir hafta kırılıyor mu? Kırıldığı haftada sitede ne değişmişti?

★★☆ TrafficClassifier'a Bing'i ekle: www.bing.com referer'ını ayrı bir BingSearch değeriyle say ve TrafficClassifierTests'e bir InlineData satırı ekle.

★★★ Komuta bir saat dilimi parametresi ekle ve Google landing'lerini Pasifik saatine göre günlere böl. Aynı log'u İstanbul saatiyle ve Pasifik saatiyle günlük grupladığında hangi günlerin sayısı değişiyor?

Kaynakların ne gördüğünü ayırdık. Bir sonraki yazıda SEO ölçümünün ana kaynağına, Search Console Performance raporuna giriyoruz: average position nasıl hesaplanıyor, regex filtreleri nasıl yazılıyor, branded query filtresi ve query groups neyi gösteriyor.