2026-03-12
Otel fiyatı scraping'inde karşılaştığım sorunlar
Otel fiyat karşılaştırma sistemi yazıyorum. Birden fazla operatörün sitesinden fiyat çekip analiz ediyorum. Selenium/Puppeteer yok — saf HTTP request + HTML parse. Hızlı ama zorlukları var.
HTML JavaScript'in içinde gizli
SAMO engine denen bir altyapı var, birçok tur operatörü kullanıyor. Normal HTML response bekliyorsun ama gelen JavaScript. HTML, JS içinde .ehtml("...") string'i olarak gömülü:
$('#divResults').ehtml("<table class=\"search-results\">...</table>");
İlk başta regex ile çekmeye çalıştım. String içinde escape'li tırnak, unicode karakter var — regex backtracking'e girip timeout oluyordu. Manuel string tarama yazdım: .ehtml( pozisyonunu bul, escape sequence'leri takip ederek string sonunu bul. Regex'ten çok daha güvenilir çıktı.
Cookie yoksa boş response
İlk request'i atıyorsun — boş. Site session cookie istiyor. Önce ana sayfaya GET atıp Set-Cookie header'ından cookie alman lazım.
1. GET site.com/ → Set-Cookie: SAMO=abc123
2. GET site.com/searchPrices?... Cookie: SAMO=abc123 → fiyatlar
Bazı siteler ek cookie de istiyor: dil, para birimi, bölge. Cookie alınamazsa 4 kez retry, 500ms aralıkla. Genelde ikincide geliyor.
Captcha gelince ne yapıyorum
Browser olmadığı için captcha çözemem. Ama tespit edebiliyorum: response'ta .ehtml() yoksa captcha var demek.
Captcha gelince: 2 saniye bekle, user-agent değiştir, cookie sıfırla, yeni session al, tekrar dene. Max 3 deneme. Çoğu zaman user-agent değiştirmek yetiyor. 3'te de olmadıysa o sayfayı atla.
Sayfalama döngüsü
200+ sayfa fiyat var. Ama site bazen aynı sayfayı tekrar döndürüyor — sonsuz döngüye girersin. Her sayfanın satırlarını SHA1 ile hash'liyorum. Aynı hash tekrar gelirse dur.
İstekler arası bekleme
Sabit bekleme koyarsan pattern olur, rate limit yersin. Random bekleme daha iyi:
long delay = ThreadLocalRandom.current().nextLong(800, 2501);
Thread.sleep(delay);
800ms-2500ms arası. İnsan gibi davranıyor.
Aynı engine, farklı HTML
Her site farklı template kullanıyor. Birinde fiyat span.price içinde, diğerinde td.td_price span. Birinde <tbody> var, diğerinde yok.
Cascading selector: ilk eşleşeni al.
Element price = firstNonNull(
tr.selectFirst("span.price"),
tr.selectFirst("td.td_price span.price"),
tr.selectFirst("td.td_price span"),
tr.selectFirst("td.price span")
);
Yeni site eklenince mevcut selector'lardan biri genelde tutturuyor. Tutmazsa listeye bir tane daha ekliyorsun.