Crawl Budget, Log Analizi ve Googlebot'u Doğrulamak
Crawl budget milyonlarca sayfalı veya her gün binlerce sayfası değişen sitelerin derdi; küçük bir sitede önce log'daki Googlebot'un gerçek olup olmadığına bak. ASP.NET Core'da IIS ile aynı W3C formatında log üretiyor, bot trafiğini çıkaran bir C# aracı yazıyor ve Googlebot'u reverse ile forward DNS ve Google'ın IP aralığı JSON'larıyla doğruluyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (ASP.NET Core 10 W3C (World Wide Web Consortium) logging, System.Net.IPNetwork) ile derlenip denenmiştir; crawl budget ve Googlebot doğrulama bilgileri Google'ın crawling dokümanının Kasım 2026 hâlinden alınmıştır.
Önce Tahmin Et
Bookstore'un katalogunda birkaç bin kitap var. Bir geliştirici server log'larını açınca iki şey görüyor: "Googlebot" user-agent'lı isteklerin büyük çoğunluğu ?genre=...&sort=...&page=... kombinasyonlarına gidiyor ve bilinmeyen bir IP'den (Internet Protocol) gelen bir "Googlebot" saniyede onlarca istekle bütün kitap sayfalarını çekiyor. Ekip "crawl budget'ımız boşa gidiyor" diye bir sprint açmak istiyor.
İki soru. Birkaç bin sayfalık bir site için crawl budget gerçekten bir sorun mu? Ve log'daki o hızlı "Googlebot" gerçekten Google mı, nasıl anlarsın?
Serinin önceki yazılarında Google'a ne istediğimizi söylemenin yollarını gördük. Bu yazıda tersine bakıyoruz: Google'ın, ve kendini Google gibi gösteren başkalarının, sitede gerçekte ne yaptığını server log'larından okuyoruz.
Crawl Budget Kimin Derdi?
Google'ın crawl budget rehberi açılışta kimin için yazıldığını söylüyor: haftada bir değişen bir milyondan fazla tekil sayfası olan siteler, günlük değişen on binden fazla sayfası olan siteler ve Search Console'da URL'lerinin (Uniform Resource Locator) büyük kısmı "Discovered - currently not indexed" (keşfedildi, şu anda index'lenmedi) durumunda kalan siteler. Google bu sayıların kesin eşik değil, kaba tahmin olduğunu da ekliyor. Açılıştaki ilk sorunun cevabı: birkaç bin sayfalık Bookstore bu grupta değil. Google'ın ifadesiyle sayfaları yayımlandığı gün crawl edilen bir site için bu rehberi okumak gerekmiyor.
Kavramlar yine de işe yarıyor, çünkü küçük sitelerde de crawl'ı kötüleştiren şeyler aynı. Crawl budget iki şeyin kesişimi. Crawl capacity limit (hostload da deniyor), Googlebot'un sunucuyu yormadan aynı anda açık tutabileceği bağlantı miktarı: sunucu hızlı ve tutarlı yanıt verdikçe artıyor, yavaşladıkça veya 5xx ve 429 döndükçe azalıyor. Crawl demand ise Google'ın URL'lere ilgisi: sitenin büyüklüğü, güncellenme sıklığı, sayfa kalitesi ve popülerlik belirliyor. Kapasite olsa bile talep düşükse Google crawl etmiyor; talep yüksek olsa bile sunucu yavaşsa crawl kısılıyor.
Crawl'ı Ne Harcar, Ne Kazandırır?
Rehberin önerileri, serinin önceki yazılarının özeti gibi:
- Duplicate içeriği canonical ve redirect ile birleştir; crawl tekil sayfalara gitsin.
- Önemsiz URL'leri (sonsuz kaydırma kopyaları, aynı listenin farklı sıraları) robots.txt ile engelle.
- Kalıcı olarak kaldırılan sayfalara 404 veya 410 dön; engellenmiş URL'ler crawl kuyruğunda daha uzun kalıyor.
- Soft 404'leri temizle; tekrar tekrar crawl ediliyorlar.
- Sitemap'i güncel tut ve
lastmod'u doğru ver. - Uzun redirect zincirlerinden kaçın.
- Sayfaları hızlı servis et ve 304 (Not Modified) yanıtını destekle; değişmeyen sayfa tekrar indirilmesin.
İşe yaramayanlar da açıkça yazılı. noindex crawl'ı azaltmaz, Google sayfayı yine istemek zorunda. robots.txt ile bir bölümü geçici olarak kapatıp "budget'ı başka yere aktarmak" işe yaramaz; Google zaten kapasite sınırına dayanmıyorsa serbest kalan budget'ı başka sayfalara vermiyor. robots.txt'deki crawl-delay da Google'da desteklenmiyor.
Server Log'u: W3C Formatı ve ASP.NET Core'da Üretmek
Search Console Google'ın gözünden özet verir; server log'u ise her isteği gösterir. IIS (Internet Information Services) log'larını W3C extended log format ile yazar. ASP.NET Core'un yerleşik W3C logger'ı aynı formatı üretiyor; Kestrel'i doğrudan veya Nginx arkasında çalıştıran bir uygulamada da IIS log'larıyla aynı araçları kullanabiliyorsun:
using Microsoft.AspNetCore.HttpLogging;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// W3C extended log formatı: IIS'in yazdığı düzenin aynısı
builder.Services.AddW3CLogging(o =>
{
o.LoggingFields = W3CLoggingFields.Date | W3CLoggingFields.Time | W3CLoggingFields.ClientIpAddress
| W3CLoggingFields.Method | W3CLoggingFields.UriStem | W3CLoggingFields.UriQuery
| W3CLoggingFields.ProtocolStatus | W3CLoggingFields.TimeTaken | W3CLoggingFields.UserAgent;
o.LogDirectory = Path.Combine(builder.Environment.ContentRootPath, "logs");
o.FileName = "access";
o.FlushInterval = TimeSpan.FromSeconds(1);
});
WebApplication app = builder.Build();
app.UseW3CLogging();
Bookstore'un bu demo uygulamasına curl ile 70 istek attım: Googlebot, bingbot ve GPTBot'un yayımlanmış user-agent string'leri ve sıradan bir tarayıcı; kitap sayfaları, var olmayan slug'lar, eski URL'ler ve 36 farklı filtre kombinasyonu. Bu isteklerin hepsi benim makinemden geldi; yani log'daki her "bot" aslında sahte. Log dosyasının başı:
#Version: 1.0
#Start-Date: 2026-11-08 09:56:40
#Fields: date time c-ip cs-method cs-uri-stem cs-uri-query sc-status time-taken cs(User-Agent)
2026-11-08 09:56:39 ::1 GET /robots.txt - 200 2.4844 Mozilla/5.0+(Linux;+Android+6.0.1;+Nexus+5X+Build/MMB29P)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/156.0.0.0+Mobile+Safari/537.36+(compatible;+Googlebot/2.1;++http://www.google.com/bot.html)
2026-11-08 09:56:39 ::1 GET /robots.txt - 200 0.1036 Mozilla/5.0+(compatible;+bingbot/2.0;++http://www.bing.com/bingbot.htm)
Format basit: #Fields: satırı kolon sırasını veriyor, değerler boşlukla ayrılıyor, user-agent içindeki boşluklar + ile kodlanıyor. IIS'te hangi alanların yazılacağı site ayarından seçiliyor; sıra değişebileceği için parser'ın kolon adlarını #Fields:'tan okuması gerekiyor.
Bot Trafiğini Çıkaran Bir Console Aracı
public static class W3CLog
{
// IIS / ASP.NET Core W3C log'larını okur; "#Fields:" satırı kolon sırasını söyler
public static IEnumerable<LogEntry> Read(string file)
{
string[] fields = [];
foreach (string line in File.ReadLines(file))
{
if (line.StartsWith("#Fields:", StringComparison.Ordinal))
{
fields = line["#Fields:".Length..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
continue;
}
if (line.StartsWith('#') || line.Length == 0)
continue;
string[] v = line.Split(' ');
string Get(string name) => v[Array.IndexOf(fields, name)];
string query = Get("cs-uri-query");
yield return new LogEntry(
DateTime.Parse($"{Get("date")} {Get("time")}"),
Get("c-ip"),
Get("cs-method"),
Get("cs-uri-stem"),
query == "-" ? null : query,
int.Parse(Get("sc-status")),
Get("cs(User-Agent)").Replace('+', ' '));
}
}
public static string BotName(string userAgent) => userAgent switch
{
_ when userAgent.Contains("Googlebot", StringComparison.OrdinalIgnoreCase) => "Googlebot",
_ when userAgent.Contains("bingbot", StringComparison.OrdinalIgnoreCase) => "bingbot",
_ when userAgent.Contains("GPTBot", StringComparison.OrdinalIgnoreCase) => "GPTBot",
_ => "other",
};
}
File.ReadLines dosyayı satır satır okuyor; günlük birkaç GB'lık bir IIS log'u da belleğe yüklenmeden işlenebiliyor. Program bot başına istek sayısını ve status code dağılımını, Googlebot'un query string'li istek oranını ve en çok istediği path'leri yazdırıyor:
$ dotnet run -- ../Bookstore.CrawlSite/logs/access20261108.0000.txt
70 requests in access20261108.0000.txt
requests per bot (by user agent) and status codes
Googlebot 45 200x40 301x2 404x3
other 13 200x13
bingbot 6 200x4 301x2
GPTBot 6 200x6
Googlebot: 36 of 45 requests (80%) carry a query string
Googlebot top paths
36 /books
1 /robots.txt
1 /books/dune
1 /books/foundation
1 /books/neuromancer
Gerçek bir sitede bu tablo şunları söylerdi: Googlebot'un isteklerinin yüzde 80'i aynı liste sayfasının filtre ve sıralama varyasyonlarına gidiyor ve kitap sayfalarının kendisine yalnız üç istek düşüyor. Bu, faceted navigation'ın crawl'ı yuttuğunun tipik görüntüsü. Çözüm serinin önceki yazılarında: sıralama gibi içeriği değiştirmeyen parametreleri canonical'dan atmak, sonsuz kombinasyon üreten filtre link'lerini crawl'dan çıkarmak ve robots.txt'de bu parametreleri engellemek. 404'ler var olmayan slug'lar (link'leri düzeltilmeli veya eski slug'lar 301'e bağlanmalı), 301'ler eski URL'ler (iç link'ler hâlâ eski adresi mi gösteriyor?). Ama karar vermeden önce bir şeyi kontrol etmen gerek: bu Googlebot gerçekten Googlebot mu?
Googlebot'u Doğrulamak
User-agent bir istemcinin kendisi hakkında söylediği şey; curl ile, benim yukarıda yaptığım gibi, herkes "Googlebot" yazabilir. Scraper'lar ve güvenlik tarayıcıları bunu sık yapıyor, çünkü birçok site Googlebot'u engellemekten çekiniyor. Google iki doğrulama yöntemi veriyor. Elle doğrulama: IP'nin reverse DNS (Domain Name System) kaydına bak, alan adı googlebot.com, google.com veya googleusercontent.com ile bitmeli; sonra o host adını forward DNS ile çöz, aynı IP'ye dönmeli. Google'ın dokümanındaki örnek IP ile:
$ host 66.249.66.1
1.66.249.66.in-addr.arpa domain name pointer crawl-66-249-66-1.googlebot.com.
$ host crawl-66-249-66-1.googlebot.com
crawl-66-249-66-1.googlebot.com has address 66.249.66.1
Forward kontrolü şart: reverse DNS kaydını IP bloğunun sahibi istediği gibi yazabilir; birisi kendi IP'sinin PTR (pointer) kaydını crawl-1-2-3-4.googlebot.com yapabilir ama googlebot.com'un DNS'ini kontrol edemez. İkinci yöntem, Google'ın yayımladığı IP aralığı JSON (JavaScript Object Notation) dosyalarıyla karşılaştırmak: Googlebot gibi common crawler'lar için common-crawlers.json, AdsBot gibi özel crawler'lar için special-crawlers.json, kullanıcı tetiklemeli fetcher'lar için user-triggered-fetchers.json ve user-triggered-fetchers-google.json; hepsi developers.google.com/static/crawling/ipranges/ altında. Her iki yöntemin C# karşılığı:
public sealed class GooglebotVerifier(HttpClient http)
{
private static readonly string[] GoogleDomains = [".googlebot.com", ".google.com", ".googleusercontent.com"];
private const string RangesUrl = "https://developers.google.com/static/crawling/ipranges/common-crawlers.json";
// yöntem 1: reverse DNS -> domain kontrolü -> forward DNS aynı IP'yi dönmeli
public static async Task<(bool Ok, string Detail)> VerifyByDnsAsync(IPAddress ip)
{
string host;
try
{
host = (await Dns.GetHostEntryAsync(ip)).HostName;
}
catch (SocketException ex)
{
return (false, $"no reverse DNS ({ex.SocketErrorCode})");
}
if (!GoogleDomains.Any(d => host.EndsWith(d, StringComparison.OrdinalIgnoreCase)))
return (false, $"reverse DNS {host} is not a Google domain");
IPAddress[] forward = await Dns.GetHostAddressesAsync(host);
return forward.Contains(ip)
? (true, $"{host} -> {string.Join(", ", forward.Select(a => a.ToString()))}")
: (false, $"{host} does not resolve back to {ip}");
}
// yöntem 2: Google'ın yayımladığı IP aralıklarıyla karşılaştır (istek başına DNS yok)
public async Task<IReadOnlyList<IPNetwork>> LoadRangesAsync(CancellationToken ct)
{
RangeFile file = (await http.GetFromJsonAsync<RangeFile>(RangesUrl, ct))!;
return file.Prefixes
.Select(p => IPNetwork.Parse(p.Ipv4Prefix ?? p.Ipv6Prefix!))
.ToList();
}
public static bool InRanges(IPAddress ip, IReadOnlyList<IPNetwork> ranges) => ranges.Any(r => r.Contains(ip));
private sealed record RangeFile(DateTime CreationTime, List<Prefix> Prefixes);
private sealed record Prefix(string? Ipv4Prefix, string? Ipv6Prefix);
}
System.Net.IPNetwork, .NET 8 ile gelen CIDR (Classless Inter-Domain Routing) tipi; Contains bir IP'nin bloğun içinde olup olmadığını söylüyor, ek paket gerekmiyor. Log'daki "Googlebot"lar ve üç test IP'si (Google'ın örnek IP'si, loopback ve dokümantasyon için ayrılmış TEST-NET-3 bloğundan bir adres) için çıktı:
$ dotnet run -- ../Bookstore.CrawlSite/logs/access20261108.0000.txt 66.249.66.1,127.0.0.1,203.0.113.7
# yukarıdaki tablolar aynı; devamı:
who claims to be Googlebot?
::1 FAKE reverse DNS localhost is not a Google domain
317 prefixes in common-crawlers.json
66.249.66.1 dns=VERIFIED ranges=True crawl-66-249-66-1.googlebot.com -> 66.249.66.1
127.0.0.1 dns=FAKE ranges=False reverse DNS localhost is not a Google domain
203.0.113.7 dns=FAKE ranges=False no reverse DNS (HostNotFound)
Açılıştaki ikinci sorunun cevabı bu kontrolden geçiyor. Log'daki 45 "Googlebot" isteğinin hepsi tek bir sahte kaynaktan; gerçek bir sitede bu sonuç, filtre kombinasyonlarını tarayanın Google değil bir scraper olduğunu ve crawl budget sprint'inin yanlış problemi çözeceğini gösterirdi. İki yöntemin farklı kullanım yerleri var: DNS doğrulaması tek tük şüpheli IP için doğru ama her istekte iki DNS sorgusu demek; IP aralığı kontrolü toplu log analizinde ve gerçek zamanlı kararlarda (ör. rate limiter'ın Googlebot'a geniş sınır tanıması) daha uygun. JSON'ları günde bir indirip bellekte tutmak yeterli; dosyada bir creationTime alanı var.
Crawl Stats Raporu
Log'un Google tarafındaki karşılığı Search Console'un ayarlar bölümündeki Crawl Stats (tarama istatistikleri) raporu. Toplam crawl isteği, indirilen toplam boyut ve ortalama yanıt süresi zaman serisi olarak, altında yanıt koduna, dosya tipine, crawl amacına (yeni URL keşfi mi, mevcut URL'nin yenilenmesi mi) ve Googlebot tipine göre dağılımlar var. Bir de host durumu bölümü robots.txt'nin alınabilirliğini, DNS çözümlemesini ve sunucu bağlantısını gösteriyor. Raporu log analizinle yan yana okumak, yalnız doğrulanmış Googlebot isteklerini karşılaştırmak demek: log'daki "Googlebot" sayısı rapordan çok fazlaysa farkı sahte bot'lar oluşturuyor.
mertozen.com Açısından
mertozen.com, Google'ın crawl budget eşiklerinin çok altında bir site; Googlebot'un sayfaları zamanında crawl etmemesi beklenen bir sorun değil. Yine de iki nokta log'larda izlenmeye değer. Birincisi, blog listesindeki kategori, seri, tag, sıralama ve sayfa parametrelerinin kombinasyonları; sitenin robots.txt'sinde hiç Disallow olmadığı için bu kombinasyonların hepsi crawl edilebilir ve canonical'ları sayesinde index'lenmeleri zaten kontrol altında, ama crawl ediliyorlar. İkincisi, 7 Kasım'daki /en/ geçişi: eski ?lang= URL'leri artık 301 dönüyor ve Google bunları bir süre tekrar crawl edecek. Log'da lang= içeren isteklerin zamanla azalması, Google'ın yeni yapıyı işlediğinin en doğrudan göstergesi.
Site IIS üzerinde çalıştığı için log'lar zaten W3C formatında; yukarıdaki araç hiçbir değişiklik yapmadan çalışır. Plesk'in log dosyalarının yeri ve saklama süresi hosting'in ayarı; analiz için log'ları sunucudan düzenli olarak almak ve kişisel veri içerdikleri için (IP adresleri) saklama politikasına uymak gerekir.
Üretimde Dikkat
Log'ları reverse proxy'nin değil uygulamanın gördüğü IP ile yazdığından emin ol. Nginx veya bir CDN (Content Delivery Network) arkasında c-ip kolonunda proxy'nin IP'si görünür; doğrulama hiçbir isteği geçiremez. Forwarded header'ların doğru kurulumu Web Server'lar ve Hosting serisinin "Reverse Proxy Arkasında ASP.NET Core: Forwarded Headers, KnownIPNetworks ve Doğru Client IP" yazısında.
Doğrulamayı log analizinde yap, her istekte senkron yapma. İstek yolunda iki DNS sorgusu gecikme ve DNS sunucusuna yük demek; IP aralığı listesini bellekte tut.
Sahte Googlebot'u engellemek kararını dikkatli ver. User-agent'ı Googlebot olan ve doğrulamadan geçemeyen bir istemciye 403 dönmek mantıklı; ama doğrulama altyapısı (DNS, JSON indirme) çöktüğünde gerçek Googlebot'u engellememek için güvenli tarafta kal: doğrulama yapılamıyorsa engelleme.
Log'larda zaman damgalarının UTC (Coordinated Universal Time) olduğunu unutma; W3C logger ve IIS varsayılan olarak UTC yazar. Search Console'un raporlarıyla karşılaştırırken saat dilimi farkı günlük toplamları kaydırabilir.
Sık Düşülen Tuzaklar
Küçük bir sitede crawl budget optimizasyonuna zaman harcamak. Google'ın kendi rehberi büyük siteler için; birkaç bin sayfada asıl sorunlar içerik kalitesi ve index'lenebilirlik.
User-agent'a güvenmek. "Googlebot" yazan her istek Google değil; analiz ve kararlar doğrulanmış IP'lerle yapılmalı.
Yalnız reverse DNS'e bakmak. PTR kaydı sahte olabilir; forward DNS ile aynı IP'ye dönmesi kontrol edilmeli.
noindex ile crawl'ı azaltmaya çalışmak. Google sayfayı yine ister; crawl'ı azaltmanın yolu URL'yi hiç üretmemek veya robots.txt.
Kalıcı olarak kaldırılan sayfaları robots.txt ile engellemek. Engellenen URL'ler crawl kuyruğunda daha uzun kalır; 404 veya 410 dön.
Küçük Bir Deneme
★☆☆ Kendi sitenin bir günlük access log'unda "Googlebot" içeren istekleri say, IP'lerini tekilleştir ve host ile ilk beşini elle doğrula.
★★☆ Analiz aracına bir "yalnız doğrulanmış Googlebot" filtresi ekle: IP aralığı listesiyle doğrulanan istekler için status dağılımını ve query string oranını ayrı yazdırsın. Sahte ve gerçek Googlebot oranlarını karşılaştır.
★★★ GooglebotVerifier'ı bir ASP.NET Core service'ine çevir: IP aralıklarını BackgroundService ile günde bir yenilesin, doğrulanmış Googlebot için rate limiter'da ayrı bir partition açsın, user-agent'ı Googlebot olup doğrulanamayan istekleri ayrı bir sayaçla loglasın. Serinin altıncı yazısındaki rate limiter alıştırmasını bununla tamamla.
Crawl ve log analizi, Google'ın siteyi nasıl gördüğünü günlük olarak izlemenin aracı. En çok ihtiyaç duyulduğu an ise bütün URL'lerin değiştiği bir site taşıması. Serinin son yazısında URL mapping'i, 301 planını, Search Console'un Change of Address aracını ve taşıma sonrası bütün eski URL'leri doğrulayan bir script'i, mertozen.com'un /en/ geçişi üzerinden kuracağız.