Search Console Performance Raporu Derinlemesine: Position, CTR, Regex Filtreleri, Branded Query'ler ve Query Groups
Performance raporunda position her sorgudaki en üst sonucun ortalamasıdır ve property ile sayfa görünümü farklı topladığı için CTR iki sekmede farklı çıkar. Anonim sorguları, 1.000 satır ve 16 ay sınırını, RE2 regex'lerinde Türkçe karakter tuzağını, branded filtreyi, query groups'u, annotation'ları ve sitenin kendi CTR'ıyla düşük CTR'lı sorguları bulan C# kodunu anlatıyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ve Go 1.27 ile derlenip denenmiştir. Bu yazıda resmî belgelerden yararlanılmıştır: Search Console Help'in Performance raporu sayfaları ve Search Central blog duyuruları, 15 Kasım 2026'daki hâliyle.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) blog'unda EF (Entity Framework; .NET'in veritabanı erişim kütüphanesi) Core migration rehberinin başlığını SEO (arama motoru optimizasyonu) için yeniledin. Dört hafta sonra Search Console'da yazının average position'ı 8'den 5'e inmiş, yani iyileşmiş görünüyor; ama click sayısı neredeyse aynı. Aynı hafta ekipten Ayşe şunu fark ediyor: Performance raporunda Queries sekmesindeki CTR (click-through rate; tıklamanın impression'a oranı) ile Pages sekmesindeki CTR, aynı tarih aralığı için farklı. Sence position iyileşmesi neden trafiğe dönmemiş olabilir? İki sekmedeki CTR'lardan biri yanlış mı?
Metric'ler Nasıl Sayılıyor?
Raporu doğru okumak için önce dört metric'in Google'ın yardım sayfasındaki tanımını bilmek gerekiyor. Impression, bir link'in kullanıcının o anda baktığı sonuç sayfasında görünmesiyle sayılıyor; sayfayı aşağı kaydırmış olmak gerekmiyor. Carousel ve açılır bölümlerdeki sonuçlar ise ancak görünür hâle gelince sayılıyor. Click, kullanıcıyı Google'ın dışına çıkaran tıklama; aynı sonuca geri dönüp aynı link'e tekrar tıklamak tek click. Position ise her sorgu için sitenin en üstteki sonucunun sırası; raporun gösterdiği değer bu sıraların ortalaması. AI Overview tek bir position kaplıyor ve içindeki bütün link'ler aynı position'ı paylaşıyor. AI Mode'da bir takip sorusu yeni bir sorgu sayılıyor.
Google 16 Haziran 2025'te AI Mode verisinin Performance raporunun toplamlarına dahil olduğunu duyurdu; AI Overviews ve AI Mode verisi "Web" arama tipinin içinde, ayrı değil. Haziran 2026'dan beri yalnız impression gösteren ayrı bir generative AI raporu da var ve 31 Ağustos 2026'dan beri bütün sitelerde; bunun ölçüm tarafını GEO (AI tabanlı arama motorları için içerik optimizasyonu) serisinin "GEO'yu Ölçmek" yazısında konuştuk.
Başlangıçtaki ikinci sorunun cevabı aggregation'da. Grafik ve Queries, Countries, Devices, Dates sekmeleri veriyi property bazında topluyor: aynı sorguda sitenin iki sayfası çıkarsa bu tek impression ve position en üstteki sonuç. Pages ve Search appearance sekmeleri ise sayfa bazında topluyor: her URL ayrı sayılıyor. Bookstore'un iki yazısı "ef core migration" sorgusunda 3. ve 7. sırada çıktıysa, property görünümü bir impression ve position 3 görüyor; sayfa görünümü iki impression, biri position 3, biri position 7. Aynı click iki görünümde farklı paydalara bölündüğü için CTR farklı çıkıyor. İkisi de doğru.
Position ile trafik arasındaki ilişki de bu ortalamadan geliyor. Average position bir sorgu karışımının ortalaması: yazı yeni bir sorguda 40. sırada görünmeye başlarsa ortalama kötüleşir ama trafik artmış olabilir; tersine, düşük sıradaki sorgulardaki impression'lar kaybolursa ortalama iyileşir ama tek bir yeni click gelmez. Google'ın trafik düşüşü rehberi de position'a impression ve click'ten daha az ağırlık vermeyi öneriyor.
Anonim Sorgular, Satır Sınırı ve Saklama Süresi
Search Console bazı sorguları gizlilik için anonimleştiriyor. Bunlar grafiğin toplamına dahil, ama sorgu tablosunda yok; "queries containing" gibi bir sorgu filtresi uyguladığında ise toplamdan da çıkıyorlar. Bu yüzden bir sorgu filtresi eklediğin anda toplam düşebilir; bu bir hata değil. Tablo yalnız en önemli satırları gösteriyor ve 1.000 satırla sınırlı; daha fazlası için API ya da BigQuery'ye bulk export gerekiyor, ki bu serinin üçüncü yazısının konusu.
Diğer sınırlar: veri 16 ay saklanıyor; bütün tarihler Pasifik saatine göre (yalnız 24 saatlik görünüm tarayıcının saatini kullanıyor); en yeni günler ön veri olabiliyor ve grafikte noktalı çizgiyle gösteriliyor. Bir de yıllık karşılaştırmalar için önemli bir kayıt var: Google'ın Data anomalies sayfasına göre bir logging hatası 13 Mayıs 2025 ile 27 Nisan 2026 arasında impression'ların doğru raporlanmasını engelledi. Click etkilenmedi; impression, CTR ve average position etkilendi. 2026 sonbaharını 2025 sonbaharıyla karşılaştırırken impression ve CTR farkının bir kısmı bu düzeltmeden geliyor olabilir.
Regex Filtreleri: RE2 ve Türkçe Karakter Tuzağı
Queries ve Pages filtrelerinde "Custom (regex)" seçeneği RE2 (Google'ın geliştirdiği regex motoru) sözdizimini kullanıyor. Help sayfasındaki kurallar: eşleşme varsayılan olarak kısmi, yani ^ ve $ koymazsan ifade metnin herhangi bir yerinde arar; büyük-küçük harf ayrımı yok, ayrım istiyorsan ifadenin başına (?-i) koyuyorsun; birden fazla alternatifi | ile ayırıp parantez içine alıyorsun; dışarıda bırakmak için "Doesn't match regex" seçeneği var. RE2'nin kendi dokümanına göre lookahead, lookbehind ve backreference desteklenmiyor.
Türkçe sorgularda asıl tuzak \b. RE2'de \b yalnız ASCII (İngilizce alfabenin temel karakter kümesi) kelime sınırını tanıyor; ö, ı, ş gibi harfler kelime karakteri sayılmıyor. Bunu Go ile denedim, çünkü Go'nun regexp paketi RE2 sözdizimini kullanıyor. Küçük araç her sorgunun yanına eşleşirse x koyuyor:
// re2check, bir Search Console regex'ini örnek sorgularla dener.
// Go'nun regexp paketi Search Console gibi RE2 sözdizimini kullanır.
package main
import (
"bufio"
"fmt"
"os"
"regexp"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintln(os.Stderr, "usage: re2check <pattern> <queries.txt>")
os.Exit(2)
}
// Search Console varsayılan olarak büyük/küçük harf ayırmaz
re, err := regexp.Compile("(?i)" + os.Args[1])
if err != nil {
fmt.Println("invalid RE2 pattern:", err)
os.Exit(1)
}
f, err := os.Open(os.Args[2])
if err != nil {
panic(err)
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
mark := " "
if re.MatchString(s.Text()) {
mark = "x"
}
fmt.Printf("[%s] %s\n", mark, s.Text())
}
}
data/queries-sample.txt dosyasındaki sekiz örnek sorguyla çıktısı (sorgular sentetik):
$ cd tools/re2check
$ go run . '\b(örnek|örneği)\b' ../../data/queries-sample.txt
[ ] ef core migration nasıl geri alınır
[ ] dependency injection nedir
[ ] nasil yapilir ef core migration
[ ] ef core migration örneği
[ ] dapper örnek proje
[ ] what is dependency injection
[ ] how to rollback ef core migration
[ ] cs8618 non-nullable property
$ go run . '(^|\s)(örnek|örneği)(\s|$)' ../../data/queries-sample.txt
[ ] ef core migration nasıl geri alınır
[ ] dependency injection nedir
[ ] nasil yapilir ef core migration
[x] ef core migration örneği
[x] dapper örnek proje
[ ] what is dependency injection
[ ] how to rollback ef core migration
[ ] cs8618 non-nullable property
$ go run . '(?=nasıl)' ../../data/queries-sample.txt
invalid RE2 pattern: error parsing regexp: invalid or unsupported Perl syntax: `(?=`
exit status 1
\b(örnek|örneği)\b hiçbir satırı yakalamıyor, çünkü boşluk ile ö arasında RE2 için bir kelime sınırı yok. Boşluk ve satır başı/sonu ile yazılan ikinci ifade iki satırı da buluyor. Lookahead ise doğrudan reddediliyor. İfadeyi .NET'te denersen sonucun farklı çıktığını görürsün, çünkü .NET'in \b'si Unicode'u biliyor; bunu bir testle kayda geçirdik:
[Fact]
public void DotNet_regex_word_boundary_is_unicode_unlike_re2()
{
// .NET'in \b'si Unicode'u bilir; RE2 (Search Console) yalnız ASCII karakterleri kelime karakteri sayar.
Assert.Matches(new Regex(@"\b(örnek|örneği)\b"), "dapper örnek proje");
}
Pratik sonuç: Search Console regex'lerini PCRE (Perl uyumlu regex) ya da .NET gibi başka bir motorda değil, RE2 uyumlu bir motorda dene. Teknik bir blog için işe yarayan birkaç ifade: TR soru kalıpları için (^|\s)(nedir|nasıl|nasil|neden)(\s|$), EN için ^(how|what|why)\s, İngilizce sayfaları ayırmak için Pages filtresinde ^https://www\.bookstore\.example/en/. Türkçe karaktersiz yazılan aramaları (nasil) eklemeyi unutma.
Karşılaştırma Modu
Karşılaştırma, herhangi bir boyutta iki değeri yan yana koyuyor: iki tarih aralığı, iki ülke, iki cihaz, iki sayfa ya da iki arama tipi. Tabloya bir Difference sütunu geliyor ve en çok değişen sorguları ya da sayfaları bu sütuna göre sıralayarak buluyorsun. Aynı anda yalnız bir karşılaştırma açık olabiliyor. Help sayfasının tarih karşılaştırması için önerisi önemli: haftalık ya da aylık granularity seçmek haftanın günü etkisini ortadan kaldırıyor, yani bir pazartesiyi bir cumartesiyle karşılaştırmıyorsun. Bir değer bir grupta çok seyrekse o hücrede ~ görürsün; o değere filtre uygulayınca iki grubun verisi de geliyor. Önce/sonra karşılaştırmasını dürüstçe yapmayı serinin son yazısında ayrıntılı konuşacağız.
Branded Query Filtresi, Query Groups ve Annotation'lar
Google 20 Kasım 2025'te Performance raporuna branded ve non-branded sorgu filtresini ekledi; 11 Mart 2026 güncellemesine göre filtre bütün uygun sitelerde açık. Branded, markanı, onun varyasyonlarını, yazım hatalarını ve ürünlerini içeren sorgular. Sınıflandırmayı keyword eşleşmesi değil, Google'ın AI destekli bir sistemi yapıyor ve duyuru hatalı sınıflanan sorgular olabileceğini açıkça söylüyor. Filtre yalnız üst seviye property'lerde var, URL-path ya da subdomain property'lerinde yok; yeterli sorgu ve impression hacmi istiyor; verisi Mart 2025'ten başlıyor. "SEO bize yeni okur getiriyor mu?" sorusu için non-branded görünüm daha anlamlı: seni zaten tanıyan birinin araması yeni bir okur kazandığını göstermiyor.
Query groups 27 Ekim 2025'te Insights raporuna geldi. Benzer sorguları, yazım hatalarını, farklı ifadeleri ve dilleri AI ile gruplayıp en çok click alan, yükselen ve düşen grupları gösteriyor. Gruplar zamanla değişebiliyor, sıralamayı etkilemiyor ve yalnız sorgu hacmi büyük property'lerde görünüyor; az sorgusu olan sitede düz bir liste kalıyor. Bookstore blog'u gibi küçük bir sitede bu kartı görmemen normal.
17 Kasım 2025'ten beri grafiğe kendi annotation'larını ekleyebiliyorsun: grafiğe sağ tıklayıp bir tarih ve en fazla 120 karakterlik bir not. Property başına 200 annotation sınırı var, 500 günden eski olanlar siliniyor ve not bütün kullanıcılarla paylaşıldığı için kişisel bilgi yazılmamalı. Etki ölçümünde annotation'ı nasıl kullanacağımız serinin son yazısında.
Senaryo: Yüksek Impression, Düşük CTR
Raporun en pratik kullanımı, çok görünüp az tıklanan sorguları bulmak. Help sayfası da düşük CTR'ı başlık ve açıklamanın içerikle uyuşmamasının bir işareti olarak anıyor. Ama "düşük" neye göre? Sektör ortalaması gibi CTR eğrileri sitenin kendisini, sorgu tipini ve sonuç sayfasının yapısını bilmiyor. Daha dürüst bir referans, sitenin aynı position aralığındaki kendi CTR'ı. Aşağıdaki kod bunu yapıyor; az veri olan satırları baştan dışarıda bırakıyor:
public static class CtrOpportunities
{
public static string BucketOf(double position) => position switch
{
< 3.5 => "1-3",
< 10.5 => "4-10",
< 20.5 => "11-20",
_ => "21+",
};
// Her satırı, aynı position aralığındaki sitenin kendi CTR'ıyla karşılaştırır;
// sektör ortalaması bir eğriyle değil.
public static CtrOpportunityResult Find(IEnumerable<QueryPageRow> rows, int minImpressions = 100, double ratio = 0.5)
{
List<QueryPageRow> enough = [];
int tooLittle = 0;
foreach (QueryPageRow row in rows)
{
if (row.Impressions >= minImpressions)
{
enough.Add(row);
}
else
{
tooLittle++;
}
}
Dictionary<string, double> bucketCtr = enough
.GroupBy(r => BucketOf(r.Position))
.ToDictionary(g => g.Key, g => (double)g.Sum(r => r.Clicks) / g.Sum(r => r.Impressions));
List<CtrOpportunity> opportunities = enough
.Where(r => r.Position < 10.5)
.Select(r => (Row: r, Bucket: BucketOf(r.Position)))
.Where(x => x.Row.Ctr < bucketCtr[x.Bucket] * ratio)
.Select(x => new CtrOpportunity(x.Row, x.Bucket, bucketCtr[x.Bucket],
x.Row.Impressions * (bucketCtr[x.Bucket] - x.Row.Ctr)))
.OrderByDescending(o => o.MissedClicks)
.ToList();
return new CtrOpportunityResult(bucketCtr, opportunities, tooLittle);
}
}
Girdi, data/gsc-pages-queries.csv: sayfa ve sorgu bazında 22 satırlık sentetik bir export. Bookstore blog'u için uydurulmuş sayılar, gerçek bir sitenin verisi değil:
$ dotnet run --project src/Bookstore.Analytics.Cli -- opportunities data/gsc-pages-queries.csv
site CTR by position bucket (rows with >= 100 impressions):
1-3 8.0 %
4-10 3.1 %
11-20 0.5 %
rows skipped (< 100 impressions): 3
query impr ctr pos bucket ctr missed
ef core migration nasıl yapılır 2100 1.9 % 3.2 8.0 % 128
dependency injection 2600 0.3 % 8.9 3.1 % 72
c# async await 1300 0.8 % 9.6 3.1 % 30
dapper nedir 700 0.9 % 7.2 3.1 % 16
Listenin ilk satırı tipik bir bulgu: "ef core migration nasıl yapılır" sorgusunda yazı ortalama 3,2. sırada, ama CTR sitenin 1-3 aralığındaki ortalamasının dörtte biri. Bu, başlığın "nasıl yapılır" niyetine cevap vermediğini düşündürüyor. "dependency injection" ise farklı bir durum: kısa, genel bir sorgu ve sonuç sayfasında başka türde içerikler de olabilir; burada başlıktan önce search intent'e bakmak gerekiyor. Başlık ve açıklamayı Google'ın nasıl seçtiğini SEO Temelleri serisinin "Sonuç Sayfasında Görünmek" yazısında, niyeti okumayı Content SEO serisinde anlattık. missed sütunu bir tahmin değil, bir öncelik sırası: satırı sitenin aralık ortalamasına çekseydin ek click'in kaba büyüklüğü.
Üretimde Dikkat
Hangi görünümde olduğunu not et. Bir sorgunun CTR'ını Queries sekmesinden, bir sayfanınkini Pages sekmesinden alıp aynı tabloya koyarsan iki farklı aggregation'ı karıştırırsın.
Filtre değişince toplamın değiştiğini bil. Sorgu ya da sayfa filtresi anonim sorguları ve kesilen satırları dışarıda bırakıyor; "filtreli toplam" ile "grafiğin toplamı" aynı şey değil.
Regex'leri bir dosyada sakla. Sık kullandığın ifadeleri ve neyi yakaladıklarını bir README'ye yaz; aynı ifadeyi her ay elle yazarken yapılan küçük bir hata raporu sessizce değiştirir.
16 ayı aşan geçmiş istiyorsan bugün başla. Search Console eski veriyi silmeye devam ediyor; arşivi sonradan geriye doğru dolduramazsın.
Sık Düşülen Tuzaklar
Düşük hacimli sorguda CTR yorumlamak. 20 impression'lık bir sorguda 1 click yüzde 5, 0 click yüzde 0; aradaki fark tamamen şans olabilir. Eşik koymadan CTR listesi çıkarmak, rastgele dalgalanmayı bulgu sanmaktır.
Position iyileşmesini trafik artışı sanmak. Ortalama position sorgu karışımına bağlı; karar için click ve impression'a bak.
Yıllık karşılaştırmada veri düzeltmelerini unutmak. 2025-2026 arasındaki impression logging hatası gibi kayıtlar Data anomalies sayfasında; karşılaştırmadan önce oraya bak.
Branded filtreyi kesin bir ayrım sanmak. Sınıflandırma AI ile yapılıyor ve hatalı olabilir; önemli bir karar öncesi en büyük sorguların hangi gruba düştüğünü elle kontrol et.
Küçük Bir Deneme
★☆☆ Kendi sitende aynı tarih aralığı için Queries ve Pages sekmelerindeki toplam CTR'ı yaz. Fark ne kadar? Sitenin bir sorguda birden fazla sayfayla göründüğü bir örnek bul.
★★☆ re2check aracıyla Türkçe soru kalıplarını yakalayan bir ifade yaz; "nasıl", "nasil", "nedir" ve "ne demek" geçen sorguları yakalasın, "nedirler" gibi sorguları yakalamasın.
★★★ CtrOpportunities'e bir sayfa bazlı mod ekle: aynı sayfanın birden fazla sorgusu listedeyse sayfayı bir kez, toplam missed değeriyle göster. Testini yaz.
Arayüzün sınırlarını gördük: 1.000 satır, 16 ay, elle yapılan filtreler. Bir sonraki yazıda bu sınırları Search Console API ve BigQuery bulk export ile aşıyoruz ve C# ile haftalık bir SEO raporunu otomatikleştiriyoruz.