Bir B2B rakibinizin bu çeyrekte hangi ülkelerde reklam verdiğini, kampanyaların ne kadar sürdüğünü, kabaca ne ölçekte gösterim satın aldığını ve hangi hedefleme kırılımlarını kullandığını öğrenmek istiyorsunuz. Bu verilerin tamamı, reklam şeffaflığı yükümlülükleri nedeniyle birçok platformun yayınlamak zorunda olduğu herkese açık reklam kütüphanesinde duruyor. Ancak DevTools'u açıp biraz kurcaladığınızda temiz bir JSON endpoint'i bulamıyorsunuz; karşınızda yalnızca sunucuda oluşturulmuş, dolu dolu bir HTML sayfası var. Bir parser yazıyorsunuz, çalışıyor ve veriyi alıyorsunuz. Üç hafta sonra platform tasarımı yeniliyor; parser hata da vermeden tek bir alan bile çıkaramaz hale geliyor. Sessizce boş alanlarla dolu bir sonuç döndürüyor ve neredeyse boş veriye dayanarak karar veriyorsunuz.
Bu yazıda, böyle bir parser'ı yeniden tasarımlar sonrasında nasıl ayakta tutabileceğinizi ve modelin bakım sürecinde gerçekten nerede fayda sağladığını ele alacağım. Önce veri sınırını netleştirelim: Buradaki tüm veriler, platformların herkese açık reklam kütüphaneleri ve creative center'larından geliyor. Kendi hesabınızla normal şekilde giriş yapılıyor; imza kullanılmıyor, hiçbir kısıtlama aşılmıyor ve herkese açık olmayan endpoint'lere erişilmiyor. Göreceğiniz gibi bu sınır, parser tasarımının doğrudan bir parçası.
B2B reklam istihbaratında neden HTML'ye mahkûmuz?
Reklam kütüphaneleri bile veriyi aynı şekilde sunmuyor. Meta'nın reklam kütüphanesi gibi bazı tüketici odaklı kütüphaneler, yapılandırılmış arama ve JSON sağlıyor. Başka bir grup, arama yapabilmek için bile oturum gerektiriyor. B2B platformlarının çoğunda ise reklam kütüphanesi tamamen sunucuda oluşturulmuş HTML'den ibaret; ortada JSON endpoint'i yok. Bunun temel nedeni, bu kütüphanelerin bir ürün API'si değil, uyumluluk amacıyla ortaya çıkmış yapılar olması.
Varlık nedenleri geliştiriciler tarafından çağrılmak değil, reklam şeffaflığı düzenlemelerini karşılamak. Sürüm numarası yok, değişiklik kaydı yok, geriye dönük uyumluluk sözü yok. Sayfa insanlar için hazırlanıyor; sunucu HTML'yi üretip gönderiyor ve elinizdeki tek “API” web sayfasının kendisi oluyor.
Dolayısıyla kırılganlık kaçınılmaz: Başkasının arayüz uygulama ayrıntılarına bağımlısınız, onlar istedikleri anda değiştirebilir ve sizi bilgilendirmek gibi bir yükümlülükleri yoktur. JSON API'de bir alanın değişmesi en azından açıkça bir “değişiklik” sayılır. HTML yeniden tasarımı ise onların gözünde sıradan bir frontend iterasyonudur. Bundan kaçış yok; yapabileceğiniz şey parser'ı sessizce boş değer döndürecek şekilde değil, kontrollü biçimde bozulacak ve yeniden tasarım sonrası kolay onarılacak şekilde yazmaktır.
Streaming parser yazmak koddan çok zihinsel yük kazandırır
İlk refleks genellikle lxml ya da BeautifulSoup alıp tüm sayfanın DOM ağacını kurmak, ardından .find() zincirleriyle aşağı doğru inmektir. Çalışır, ama bu hedef türü için doğru yaklaşım değildir. DOM ağacı, tarayıcının sayfayı işlerken ürettiği ara yapıdır; MDN, DOM'u script'lerin yapısal olarak eriştiği düğümlerden oluşan bir belge ağacı olarak tanımlar. Sizin ihtiyacınız ise birkaç alanı çekmek. Bu ağaca ihtiyacınız yok; parser'ı onun yapısına bağlamamanız da gerekir.
Benim vardığım çözüm, Python standart kütüphanesindeki HTMLParser'ın 800 satırı biraz aşan, tam olarak 881 satırlık bir alt sınıfı oldu. Tamamen streaming çalışıyor: Birkaç starttag / data / endtag callback'i durum makinesini yönetiyor. Tarama ilerledikçe veri birikiyor; kart sınırına gelindiğinde kayıt üretiliyor, durum temizleniyor ve devam ediliyor. Hiçbir noktada tam DOM ağacı oluşturulmuyor.
Streaming yaklaşımının somut iki kazancı var. İlki bellek: Bir detay sayfasının HTML'si rahatlıkla onlarca ya da yüzlerce KB olabilir. DOM ağacı tüm sayfa yapısını bellekte tutar; streaming yaklaşım ise yalnızca “taramanın neresindeyim, bu kartta ne kadar ilerledim?” durumunu saklar. Daha önemlisi zihinsel yükü azaltmasıdır. .find('div').find('div')[2] yazdığınız anda parse işlemini DOM'daki hiyerarşik konuma bağlamış olursunuz. Yeniden tasarımların en sevdiği şey de tam olarak bu hiyerarşiyi değiştirmektir: Araya bir container eklenir, wrapper ayrılır ve tüm konumlar kayar. Durum makinesi sizi yalnızca şunu sormaya zorlar: “Taradığım şey anlamsal olarak kart başlangıcı mı, gösterim sayısı mı, hedefleme etiketi mi?” Konum değişir; anlam değişmez.
Yeniden tasarımlara karşı üç temel tasarım ilkesi
Bunların hepsi, yaşanmış bir yeniden tasarımın öğrettiği üç ilkeye dayanıyor.
İlki: Konuma değil, anlama tutunun. Durum makinesi anlamsal sinyallerle ilerlemeli: Alan etiketlerindeki okunabilir ifadeler, örneğin “total impressions” veya “run dates”; rol taşıyan işaretçiler; bir bloğun anlamsal sınırı. Asla “yukarıdan üçüncü düğüm” gibi konumsal tanımlarla hareket etmeyin. Testi basit: Bu öğe taşınsa ya da etrafına bir katman daha eklense parse işlemi hâlâ çalışır mı? Çalışıyorsa doğru anchor'ı bulmuşsunuzdur. Konumsal anchor'lar ilk yeniden tasarımda parçalanır; anlamsal anchor'lar ise salt stil değişikliklerinin çoğunu atlatır.
İkincisi: Alan eksikse hata fırlatmayın, kontrollü eksilin. Her kartı toplamadan önce, tüm alanların boş varsayılanlarla tanımlandığı bir şablon başlatın: Metinler için boş string, sayılar için None, listeler için boş dizi. Çıkarabildiğinizi doldurun, çıkaramadığınızı boş bırakın. Tek bir alanın extraction'ının başarısız olması ne tüm kartı ne de tüm sayfayı bozmalı. Bir reklamın CTA metni eksik olabilir; siz yine de gösterimlerini ve hedef ülkelerini almak istersiniz. Önemsiz bir eksik alanın, sayfanın sağlayabileceği istihbaratı yok etmesine izin vermek en kötü tasarımdır.
Üçüncüsü: Sonuçları tamlık bilgisiyle etiketleyin; “boş” ile “bozuk” aynı görünmesin. En kolay gözden kaçan, fakat maliyeti en yüksek ilke budur. “0 reklam parse edildi” ifadesinin iki bambaşka anlamı olabilir: Gerçekten reklam yoktur, yani bu reklamveren ilgili çeyrekte hiç reklam yayınlamamıştır; ya da sayfa yapısı değişmiştir ve hiçbir anchor eşleşmemiştir, yani parser bozulmuştur. Dönüş değerinde bu iki durum mutlaka ayırt edilebilmelidir. Bunun yolu destekleyici kanıtları birlikte döndürmektir: Yakalanan kart sayısı, sayfanın kendisinin bildirdiği toplam ve sayfalama durumu. Örneğin “kart sayısı 0, fakat sayfa metadata'sı bir grup olması gerektiğini söylüyor ve next-page işaretçisi yok” kombinasyonu, gerçek bir boşluktan ziyade yapı değişikliği olarak değerlendirilebilir. Böylece sessizce boş liste dönmek yerine açık bir hata verilir.
Başta tanımladığımız veri sınırı bu katmanda da uygulanır: Parser, giriş sayfasına yönlendirilip yönlendirilmediğini kontrol eder. Başlığın login/signup sayfasına ait olduğunu gördüğü anda parse etmeyi sürdürmek yerine hata verir. Yalnızca kendi hesabınızla normalde görebildiğiniz herkese açık sayfaları işler; login duvarında durur ve asla ötesine geçmeye çalışmaz.
Asıl değer, reklam alanlarında değil filtre boyutlarında
Buraya kadar gelince hedefin her reklamın tüm alanlarını kusursuz çıkarmak olduğunu düşünebilirsiniz. Değil. Tek bir reklamın alanları tek başına cansız veridir; esas değer, bu reklamları hangi boyutlarda dilimleyebildiğinizdedir. Reklam kütüphanesinin arama filtreleri zaten hazır bir istihbarat boyutları listesi sunar. Bunları programlanabilir sorgu parametreleri olarak düzenlediğinizde elinize “tek bir reklam” değil, “bir rakibin lansman dilimi” geçer:
Ülke: Hangi pazarlarda reklam verdiği ve hangilerinde vermediği. Bir B2B şirketinin aniden belirli bir ülkede reklam vermeye başlaması, genişleme hamlesini kendi web sitesinden daha erken ele verebilir.
Yayın dönemi (başlangıç ve bitiş tarihleri): Kreatifin ne kadar süre yayında kaldığı. Uzun süre çalışan reklam en güçlü sinyaldir; çünkü kimse dönüşüm getirmeyen bir kreatife para ödemeyi sürdürmez. Yayın süresi, karşı tarafın sizin için gerçek parayla yürüttüğü ve doğruladığı bir A/B testinin sonucudur.
Gösterim aralığı (min/max): Harcama için kaba bir vekil metrik. Mutlak değer doğru değildir, ancak hangi reklamların öncelikli satın alımlar olduğunu sıralamaya yeter.
Hedefleme kırılımları: Hangi hedeflemelerin dahil edildiği veya hariç tutulduğu. Karşı tarafın ürününü kimlerin satın alacağını düşündüğünü doğrudan gösterdiği için en net kitle istihbaratı budur.
Alanları çıkarmak araçtır; amaç bu boyutlardır. Parser'ı yazarken tersinden düşünün: Bu boyutlarda sorgulama ve sıralama yapabilmek için güvenilir biçimde çıkarmam gereken en az alan seti nedir? Geri kalan gösterişli alanlar, istihbarat kalitesine zarar vermeden çıkarılmadan kalabilir.
Yeniden tasarım sonrası onarım önerileri için modele eski ve yeni HTML'yi karşılaştırın
Böyle bir parser yeniden tasarımda mutlaka kırılır. Modelin gerçekten ait olduğu yer de tam olarak onarım sürecidir; parser'ın kendisi değil. Parse işlemi deterministik bir iştir, hardcoded bir durum makinesiyle yürür ve içine model çağrıları doldurulmamalıdır. Deterministik işi modele vermeyin; reverse-engineering tarafındaki ilke burada da aynıdır. Modelin görevi bakımdır.
İş akışı şöyle: Reklam kütüphanesini kendi hesabınızla açın; yeniden tasarım öncesinde kaydettiğiniz HTML kopyasını ve sonrasındaki yeni HTML'yi elinizde tutun. Bunları, mevcut parser'ın çıkardığı alan listesiyle birlikte modele verin. Eski-yeni farkına göre hangi alanların anlamsal anchor'larının değiştiğini, yeni anchor'ın nerede olması gerektiğini ve gerekli en küçük kod değişikliğinin hangi birkaç satırdan oluştuğunu söylemesini isteyin. Bu, reasoning tier için ders kitabı örneğidir: Modelin yalnızca “yapı değişti” demesi değil, yeni yapıda karşılık gelen anlamların hâlâ bulunup bulunmadığını tespit ederek uygulanabilir bir plan sunması gerekir. Bu adımlar modelden farklı yetkinlikler ister; tüm süreçte tek bir tier kullanmak ya para maliyetini ya da doğruluğu artırır:
Adım | Gereken yetkinlik | Tercih | model id |
|---|---|---|---|
SSR HTML ile oluşturulmuş tüm sayfayı okumak, eski ve yeni yapıyı eşleştirmek | Uzun context; onlarca ila yüzlerce KB'lık detay sayfasını tek seferde işleyebilme | Kimi K3 |
|
Yeniden tasarım sonrası eski-yeni diff'i okumak, anchor'ın nereye kaydığını değerlendirmek ve en küçük düzeltmeyi önermek | Güçlü reasoning; olguyu tekrar etmek yerine yapıya dayalı açıklama | Claude Opus 5 |
|
Yüzlerce reklamverenin kartlarını toplu olarak normalize edip istihbarat etiketleri eklemek | Düşük maliyet, yüksek eşzamanlılıkta yüzlerce ila binlerce çağrı | Claude Sonnet 5 |
|
Fixture karşılaştırması başarısız olduğunda farkın nedenini atfetmek | Orta seviye reasoning; “beklenen alan ile gerçek extraction” farkı üzerinden açıklama | GPT-5.6 Sol |
|
İkinci tier işin çekirdeğidir ve model değiştirmenin sonucu gözle görülür biçimde etkilediği tek adımdır. Reasoning tier'a değip değmediğini bana inanarak değil, kısa bir protokolle test ederek görün:
Bir sayfanın yeniden tasarım öncesi ve sonrası HTML'sini kaydedin. Reklam kütüphanesini kendi hesabınızla açın ve sayfayı kaydedin.
Her iki HTML'yi ve mevcut parser'ın alan listesini
claude-opus-5ilegpt-5.6-sol'a verin.Tek bir şeye bakın: Önerilen düzeltme belirli bir anlamsal anchor değişimini mi işaret ediyor? Örneğin, “önceden 'total impressions' etiketine tutunuyordu; yeni sürümde bu etiketin container role'ü değişti, X'e anchor olun.” Yoksa yalnızca “yapı değiştirildi, yeniden uyarlama önerilir” gibi belirsiz bir yanıt mı veriyor?
İlk yanıt doğrudan uygulanabilir; ikincisi ise bir şey söylemez. Seçim kriteriniz bu farktır ve yeniden tasarım gününde kaç tur kör deneme yapacağınızı doğrudan belirler.
Tek tur, farkı herhangi bir benchmark'tan daha doğrudan gösterir.
Asıl engel model değiştirme maliyeti
Dört tier; üç farklı sağlayıcı, üç SDK, üç kimlik doğrulama yöntemi ve üç hata formatı demek. Farklı adımlar için üç istemci bağlamanın maliyetini hesaplayan çoğu kişi, bunun “ara sıra parser düzeltmesi ve toplu veri temizliği” için değmeyeceğine karar veriyor. Sonunda tüm akışta tek bir model kullanılıyor; yeniden tasarım düzeltmesinde yalnızca “yeniden uyarlama önerilir” diyebilen bir tier ile çalışılıyor ve nedenini bilmeden zaman kaybediliyor.
AIReiter bu katmanı düzleştiriyor: Tek anahtar, tek OpenAI uyumlu arayüz ve arkasında dört tier. Geçiş için istek gövdesindeki model alanını değiştirmeniz yeterli.
# Redesign fix: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<old-new HTML diff + current field list, ask where the anchor drifted>"}]
}'
# Bulk intelligence normalization: change the model field, leave the rest
# "model": "claude-sonnet-5"
# Long context to read the whole page: "model": "kimi-k3"
# Difference attribution: "model": "gpt-5.6-sol"
OpenAI SDK kullanıyorsanız base_url değerini https://aireiter.com/api/v1 olarak ayarlayın; başka bir değişiklik yapmanız gerekmez. Anthropic SDK'da ise aynı anahtarla POST /api/v1/messages endpoint'ine istek atın.
Fiyat tarafında Claude modelleri liste fiyatına göre %30 indirimli, GPT modelleri yarı fiyatına; Kimi K3 de aynı anahtarla çağrılabiliyor. Bu akışta indirim doğrudan ana maliyete yansıyor. Ana maliyet yeniden tasarım düzeltmesi değil; o seyrek yapılan, düşük frekanslı ama yüksek değerli bir çağrı. Esas maliyet, toplu istihbarat normalizasyonu: Yirmi rakip reklamvereni izliyor, her biri için onlarca ila yüzlerce kart topluyor ve promise / audience / run window bilgilerini çıkarmak üzere bunların tamamını modele gönderiyorsunuz. En yoğun çağrı yapılan bölüm bu ve Claude Sonnet üzerinde %30 indirimle çalışıyor. Tüm sayfayı okuyup yapıyı eşleştirmek için gereken uzun context girdisi ise ikinci en yoğun maliyet kalemi. En pahalı iki bölüm doğrudan indirimin uygulandığı noktada.
Kayıt olmadan deneyin: Önce ve sonra alınmış bir HTML çiftini iki modele elle verin; anchor'ın nereye kaydığını gerçekten hangisinin gösterebildiğini karşılaştırın, ardından entegrasyon yapıp yapmayacağınıza karar verin.
Parser düzeltildikten ve ham kartlar toplu olarak normalize edildikten sonra, bu istihbaratın bir sonraki durağı karar alma ve varlık üretimi için creative pipeline'a beslenmesidir. Bu, anahtar kelimeden tamamlanmış reklama uzanan tam döngünün işidir; bu yazı ise o döngünün en başındaki, herkese açık veriyi güvenilir biçimde çekme aşamasını anlatıyor.
Alanların doğruluğunda son söz fixture'ındır
Modelin verdiği düzeltme, doğrulanana kadar yalnızca öneridir. “Anchor X olmalı” demesi makul gelebilir, ancak X'in tüm kart türlerinde çalışacağının garantisi yoktur. B2B reklamlar görsel-metin, yalnızca metin, carousel, landing page'li veya landing page'siz biçimlerde gelir; modelin incelediği iki örnek bunların tamamını kapsamıyor olabilir.
Bunu engelleyen şey, reverse engineering'de model halüsinasyonunu engelleyen yaklaşımın aynısıdır: Sabit vektör karşılaştırması. Bilinen girdilerden oluşan bir grubu, yani kaydedilmiş birkaç gerçek sayfa HTML'sini, ve bunların bilinen doğru çıktılarıyla, yani bir kez elle doğruladığınız alan sonuçlarıyla birlikte repo'ya commit edilen bir fixture olarak saklayın. Parser'da her değişiklik yaptığınızda, ister kendiniz düzenlemiş olun ister model önerisini uygulamış olun, bu fixture grubunu yeniden çalıştırın ve alan alan karşılaştırın. Upstream yeniden tasarım sonrasında size hızla şu ayrımı yaptıran tek şey budur: “Öneriyi yanlış mı uyguladım, yoksa sayfa yeniden mi değişti?” Hepsinin geçmesi değişikliğin doğru olduğunu gösterir; birkaçının başarısız olması ise ilgili örneklerin alanları üzerinden sorunun hangi katmanda olduğunu doğrudan söyler. Model onarım önerisi üretir, fixture önerinin doğru olup olmadığına karar verir; ikisini birbirine karıştırmamak gerekir. Bu fark temelli karşılaştırma yönteminin tamamı differential-testing yazısında anlatılıyor; reklam kütüphanesi parser'ı da aynı kapıyı kullanır. Bu katman olmadan modelin güvenini doğruluk yerine koymuş olursunuz. Böyle bir durumda “öneriyi uygula, hata görme, yayına al; sonra üç gün boyunca bir ülkenin verisinin tamamen boş kaldığını fark et” senaryosu çok kolay yaşanır.
Sonuç
B2B reklam istihbaratı HTML'den parse edilmek zorundadır; çünkü reklam kütüphanesi bir ürün API'si değil, uyumluluk yapısıdır. API sözleşmesi yoktur ve yeniden tasarımlar her an gelebilir. Değişime dayanıklılık üç tasarım ilkesine dayanır: Konuma değil anlama tutunmak, eksik alanlarda hata fırlatmak yerine kontrollü eksilmek ve “boş” ile “bozuk” sonuçların birbirinden ayrılması için tamlık bilgisi eklemek. Asıl değer tek bir reklamın alanlarında değil; rakibin lansmanlarını ülke / yayın dönemi / gösterim aralığı / hedefleme kırılımı boyunca dilimleyen boyutlardadır. Modelin yeri nettir: Parse işleminin içinde değil, deterministik durum makinesinin bakımında. Yeniden tasarım sonrası onarım önerileri için eski ve yeni yapıyı karşılaştırmak reasoning tier'ın güçlü olduğu alandır; kartları toplu biçimde istihbarata dönüştürmek ise düşük maliyetli, yüksek eşzamanlılık tier'ının işidir. Ancak alanların doğru olup olmadığına her zaman fixture karar verir. Bu tier'ları tek bir birleşik arayüze bağladığınızda geriye yalnızca model alanını değiştirmek kalır; bunu da modelinizi seçerek çözebilirsiniz.