Mart başında, kira piyasasındaki saçmalığı anlamak için kendi VPS’ime küçük bir scraper script’i attım. Amacım hesapciyiz.com için daha gerçekçi bir kira çarpanı motoru kurmaktı ama ilk 48 saatin sonunda elimdeki verinin aslında koca bir yalan olduğunu fark ettim.
Ekranda gördüğüm 45.000 TL’lik Beşiktaş ilanı ile o evin kontratına atılan imza arasında bazen uçurumlar oluyor. Veri toplarken sadece selector yazıp json basmak yetmiyor; sahadaki “pazarlık marjını” ve “fake ilan” gürültüsünü temizlemek asıl işin kendisiymiş.
Veri Setindeki Gürültü: “Sahibinden” Değil “İsteyen” Fiyatı
Topladığım ilk 10 bin satırlık veriyi SQLite üzerine bastığımda garip bir şey fark ettim: Bazı ilanlar 3 aydır yayında ve fiyatı sürekli %5 artıyor. Kimse tutmuyor ama fiyat artıyor. Bu, aslında piyasanın fiyatı değil, mülk sahibinin “keşke bu fiyata gitse” dediği hayal dünyası.
Kendi VPS’imde çalışan 13 container’dan biri olan bu scraper, başlangıçta sshd’yi bile cevap veremez hale getirdi. Çünkü 28 Nisan’da yaşadığım o meşhur disk dolması vakasındaki gibi, logları tmpfs yerine doğrudan /var/log altına kontrolsüzce basmışım. Veri büyüdükçe, temizlenmemiş veri setinin sistem kaynaklarını nasıl sömürdüğünü bir kez daha gördüm.
İlan fiyatlarını “gerçek” kabul edip bir model kurmaya kalktığımda, sistem bana İstanbul’da kiraların son 2 ayda %15 düştüğünü söyledi. Ama sokağa çıktığımda durum tam tersiydi. Meğer emlakçılar, arama sonuçlarında üstte kalmak için eski ilanları silip 2.000 TL daha ucuza “yeni” ilan giriyor, sonra telefonda “o tutuldu ama şuna bak” diyorlarmış.
Pipeline’da Veri Temizleme: SQLite ve Heuristic Filtreler
Bu kirliliği çözmek için Node.js tarafında basit bir preflight resource guard mekanizması kurdum. Her gelen veri satırı, daha önce gördüğüm “spam pattern”lerine uyuyor mu diye kontrol ediliyor. Eğer bir ilan 30 günden uzun süredir yayındaysa ve fiyatı 3 kereden fazla değişmişse, onu “piyasa belirleyici” değil “gürültü” kategorisine alıyorum.
SQLite üzerinde bu sorguları koştururken index’lerin canına okuduğum bir an oldu. VACUUM komutunu çalıştırmayı unuttuğum için 2 GB’lık veritabanı diskte 5 GB yer kaplıyordu.
-- Gürültülü verileri temizlemek için kullandığım basit bir mantık
DELETE FROM listings
WHERE updated_at < date('now', '-30 days')
AND price_change_count > 3;
-- Gerçekçi fiyat tahmini için %15 'pazarlık/fake' payı düşüyorum
UPDATE listings
SET estimated_real_price = price * 0.85
WHERE source = 'web_scraper';
Bu temizleme işlemi sonrası elimde kalan veri, “ilan fiyatı” değil, “olası işlem fiyatı” haline geldi. Kendi VPS’imdeki kaynakları korumak için bu işlemleri gece 03:00’te, GitHub Actions runner’larını meşgul etmeden, systemd timer’ları ile yapıyorum.
VPS Üzerinde Veri Operasyonu Yapmanın Acı Gerçekleri
Eğer benim gibi her şeyi bir kutuya (box) sığdırmaya çalışıyorsanız, disk yangını kaçınılmazdır. Bir ara Docker build cache’i 33 GB’a ulaşmış, yanına bir de 23 GB’lık unused image eklenmişti. Sunucu %100 disk doluluğuna ulaştığında, scraper’ın çektiği veriler null olarak kaydedilmeye başlandı.
O gün öğrendim ki; veri toplamak sadece kod yazmak değil, o verinin yaşayacağı disk alanını ve swap alanını da yönetmektir. 7.6 GB sistem belleğinde Astro build’i ayağa kalkmaya çalışırken, bir yandan SQLite’a toplu INSERT yapmak tam bir intihar girişimi.
| Sorun | Semptom | Çözüm |
|---|---|---|
| Disk Doluluğu | Verilerin null gelmesi | docker system prune -af |
| OOM-Killed | Container’ların rastgele çökmesi | Swap file artırımı + polling wait |
| SSH Timeout | sshd paket kabul edememesi | CPU limit (cgroups) |
Kendi projelerimde (örneğin spamkalkani.com veya islistesi.com) bu tarz veri anomali durumlarını artık auto-fix pattern’leri ile çözüyorum. Eğer bir işlem 10 saniyeden uzun sürerse, pipeline otomatik olarak kaynak kullanımını kısıyor ve bana bir dedup-alert fırlatıyor.
Pipeline Reliability: Neden Otomasyon Şart?
Veri toplama sürecinde en büyük düşmanınız “değişen web yapıları” değil, “dolup taşan diskler ve cache’ler”dir. GitHub Actions üzerinde kendi runner’ımı (self-hosted) koştururken, _work/_temp altındaki dizinlerin silinmemesi yüzünden kaç kere pipeline’ın patladığını hatırlamıyorum bile.
Cloudflare tarafında Astro’nun max-age=0 döndürmesi yüzünden tüm trafiğin doğrudan VPS’e gelmesi de ayrı bir dertti. Nginx tarafında bu header’ları override ederek sunucunun üzerindeki yükü biraz olsun hafifletebildim. Aksi halde, veri çekerken aynı zamanda siteye gelen ziyaretçilere “502 Bad Gateway” izletmek zorunda kalıyordum.
Sonuç olarak, internetten çektiğiniz her satır veri “saf gerçek” değildir. O veri, bir insanın (genelde bir emlakçının veya satıcının) manipülasyonuna uğramış bir dijital izdir. Gerçek veriye ulaşmak için o izi kazımak, temizlemek ve en önemlisi kendi altyapınızı bu yük altında ezilmeyecek şekilde optimize etmek zorundasınız.
Şu sıralar bu temizlenmiş verilerle hesapciyiz.com’un backend’ini besliyorum. Bir sonraki yazıda, bu kadar veriyi Cloudflare R2 üzerinde nasıl daha ucuza depolarım ondan bahsedeceğim. Senin de benzer bir scraper tecrüben veya “disk doldu, site çöktü” anın varsa yorumlarda (ya da mailde) buluşalım.