## GİRDİ — yalnız bu bloğu doldur ```text ScrapeUnblocker API anahtarım: BURAYA_YAPIŞTIR Bölge: BURAYA Sektör: BURAYA Sattığım hizmet: BURAYA Tarama: Hızlı ``` Girdin bu bloktur. Sohbette bana soru sorma, eksik alanı makul varsayımla doldurup devam et. Anahtar satırı boş ya da hala `BURAYA_YAPIŞTIR` ise canlı veri toplamaya HİÇ girme: ürünü kur, testleri koş, ağsız örnek çıktıyı üret ve sonunda tek satırla anahtarın eksik olduğunu söyle. Bölge ilçe, şehir ya da mahalle olabilir. Başlamadan önce hedefi tek satır geri oku. Bu klasörde çalışan bir mini ürün kurmanı istiyorum. Ürünün adı **Müşteri Fırsat Radarı** olsun. Bana teknik soru sorma. Gerekli dosyaları oluştur, testleri çalıştır ve ürünü hazırla. Eksik bilgi varsa güvenli varsayılanı seç. ## Ürünün sonucu Ürün yerel işletmeleri bulsun. Her işletmeyi ayrı ayrı incelesin. Sonuçta bana yalnız bir isim listesi vermesin. Şu iki soruyu cevaplasın: 1. Bugün önce hangi işletmeye ulaşmalıyım? 2. O işletmeye hangi doğrulanmış kanıtla yazabilirim? İlk ekranda en güçlü beş fırsat görünsün. Her satırda işletme adı, fırsat puanı, güven puanı, ana neden ve kısa mesaj bulunsun. Ardından ayrıntılı kartlar gelsin. ## Teknoloji ve kurulum - Python 3.10 veya üstünü kullan. - Resmi `scrapeunblocker==0.1.8` paketini kullan. - `httpx==0.28.1` sürümünü sabitle. - `requirements.txt` içinde sürümler tam yazılsın. - `baslat.py` ilk çalışmada `.venv` oluştursun. - Aynı dosya paketleri otomatik kursun ve uygulamayı başlatsın. - `.venv`, `.env`, `.cache` ve `cikti` git tarafından yok sayılsın. - ScrapeUnblocker dışındaki hiçbir servis veya veri API'si kullanılmasın. - Sayfa, arama ve veri getiren bütün çağrılar resmi ScrapeUnblocker istemcisinden geçsin. `requests`, `selenium`, `playwright`, `aiohttp`, `pycurl` ya da `curl` ile sayfa çekme. - Tek istisna kapak görselinin ham baytıdır: adresi ScrapeUnblocker ile bulunmuş bir görsel, tek bir yardımcı fonksiyonda, boyut sınırıyla doğrudan indirilebilir. O fonksiyon başarısız olursa ScrapeUnblocker'ın görsel ucuna düşsün. Ölçüldü: görsel ucundan gelen kapaklar ~527 KB PNG oluyor ve rapor 27 MB'a çıkıyor, aynı görsel kendi adresinden 47 KB JPEG geliyor. İkinci bir doğrudan indirme yazma; sayfa ya da JSON çekmek için asla kullanma. Tek komut satırı sözleşmesi kullan. Alternatif argüman adları üretme: ```text python3 baslat.py --bolge "Kadıköy" --sektor "diş kliniği" --satis "web sitesi" --mod hizli ``` Zorunlu adlar `--bolge` ve `--sektor`; diğer ürün argümanları `--satis`, `--mod {hizli,derin}`, `--sayi`, `--varyantlar`, `--cikti`, `--cache-saat`, `--cache-yok` ve `--rapor-yenile` olsun. `baslat.py` argümanları değiştirmeden uygulamaya iletsin. `--help`, kullanıcı girdisi, `getpass`, paket kurulumu, SDK/ağ çağrısı veya dosya yazımı yapmadan sıfır koduyla çıksın. Sıra önemli: `baslat.py` sanal ortamı kurmadan ÖNCE argümanlara baksın, `--help` ve eksik argüman yolları kurulumdan hiç geçmeden cevaplansın. Kuruluma koşulsuz başlayan bir başlatıcı bu şartı sessizce çiğner. Bölge ya da sektör eksikse anahtar istemeden yardım gösterip kod `2` ile çıksın. Anahtarı yukarıdaki girdi bloğundan al. İlk işin onu `.env` dosyasına tek seferde yazıp iznini `0600` yapmak olsun, sonra hep oradan oku. Sohbette ayrıca anahtar isteme, komut satırı argümanı ekleme. Ortam değişkeni `SCRAPEUNBLOCKER_KEY` ya da mevcut bir `.env` varsa blok yerine onu kullan; ikisi de yoksa ve blok da boşsa Python `getpass` ile gizli terminal girdisi iste. Anahtarı HTML, JSON, CSV, log, cache, shell history veya hata metnine asla yazma; sohbete geri yazma ve terminale bastırma. ## İki kullanım modu Varsayılan mod `hizli` olsun. - `hizli`: En fazla 10 işletme ve 30 API denemesi. - `derin`: En fazla 20 işletme ve 70 API denemesi. Başarısız çağrılar ve tekrarlar da deneme bütçesine dahil olsun. Bütçe dolunca yeni çağrı yapma. Toplanan veriyi kaybetme. `partial=true` ile kısmi rapor üret. Kullanıcı daha büyük bir sayı yazsa bile mod sınırını aşma. Başarılı cevapları 12 saat yerel cache'de tut. Cache anahtarı endpoint ve parametrelerden oluşsun. Cache'e API anahtarı girme. Manifest içinde cache isabeti sayısını ayrıca göster. ## ScrapeUnblocker yolu İlk ağ çağrısı bir Google Local sağlık kanaryası olsun: ```python su.google_local(keyword, hl="tr", gl="tr") ``` Hiçbir çağrıda `proxy_country` gönderme. Hesabın Türkiye vekil havuzu boş: `proxy_country="TR"` verildiğinde `get_page_source` `400 No proxies found for country 'TR'` döner, Google uçları da captcha duvarına düşer. Yerellik yalnız `hl` ve `gl` ile sağlanır. Bu üç yol için de geçerlidir: `google_local`, `serp`, `get_page_source`. Bu uç tüm koşuda TOPLAM bir kez çağrılsın. Sayfalama, ikinci sorgu ya da yeniden doğrulama için bile ikinci kez çağırma. Otomatik retry de uygulanmasın. Cevapta dolu ve geçerli bir `results` listesi varsa ana keşif kaynağı olarak kullan. Her kayıtta `name`, `rating`, `reviews`, `category`, `address`, `phone`, `website`, `hours`, `position` ve `sponsored` alanlarını güvenli biçimde oku. Eksik alan için değer uydurma. Google Local `403` verirse, bozuk JSON döndürürse veya sonuç vermezse fallback aç. Fallback iki resmi yoldan oluşsun: 1. Organik doğrulamalar için `su.serp(...)` kullan. 2. Yerel paket gerekirse `su.get_page_source(google_search_url)` kullan. SERP cevabında yalnız `organic` listesini işle. Sonuç yoksa bunu normal durum say. Bozuk cevap, `403` veya kalıcı hata olursa `get_page_source` yoluna geç. Gelen Google sayfasında robot kontrolü veya çerez duvarı varsa veri olarak kullanma. HTTP `200` olsa bile engel sayfasını reddet. Retry matrisi açık ve test edilebilir olsun: - `401` ve `402`: Anında dur. Tekrar yapma. - `400`, `403`, `404`, `415`, `422`: Aynı endpoint'i tekrar etme. Tekrar etmemek vazgeçmek değildir: `403` sonrası koşu yukarıdaki resmi fallback yoluyla DEVAM eder. Ölçüldü, bir kurulum Google Local `403` alınca hiç `serp` denemeden durdu ve boş rapor üretti; matris tek başına okununca "durmak" da kurallı görünüyor. - Tek istisna Google Local'in `403`üdür. Orada `403` kalıcı yasak değil, Google'ın olasılıksal robot kontrolüdür: ölçüldü, altı denemenin ikisi sonuç verdi. Aynı sorgu için en fazla sekiz deneme yap, denemeler tükenince koşuyu resmi fallback yoluyla sürdür. Sekizi aşma ve bu istisnayı başka hiçbir uca taşıma. Tek `403`te fallback'e düşen kurulum, yapılandırılmış yerel paketi elde edebilecekken HTML'den okumak zorunda kalıyor. - `408`, `429`, `500`, `502`, `503`, `504`: En fazla üç toplam deneme. - Ağ bağlantısı ve yerel timeout: En fazla üç toplam deneme. - Bekleme: Kısa üstel geri çekilme. En fazla dört saniye. Resmi istemciyi şu satırla aç, sınıf adını değiştirme: ```python from scrapeunblocker import Client su = Client(api_key=anahtar, timeout=180.0, max_retries=0) ``` Retry kararını bizim kodumuz versin. Böylece bütçe ve manifest gerçek deneme sayısını göstersin. Başarılı her `2xx` çağrıyı olası faturalanan çağrı olarak say. Kullanılmayan engel sayfasını ayrıca say. SDK'nın dönüş biçimini varsayma, ölçüldü: `google_local` ve `serp` doğrudan çözümlenmiş JSON döndürür (`dict`), `get_page_source` ise düz HTML metni döndürür (`str`). `.ok`, `.status_code`, `.json()` ya da `.text` alanı olan bir yanıt nesnesi YOKTUR; `cevap.ok` yazan kurulum `AttributeError` ile düştü. Hata da dönüş değeriyle gelmez, İSTİSNA ile gelir: `ScrapeUnblockerError` soyundan istisnalar `status_code` taşır. Yukarıdaki retry matrisini `if cevap.ok` ile değil `try/except` ile kur. ## İşletme kimliği Aynı işletmeyi yalnız adına bakarak birleştirme. Şu sinyalleri normalize et: - Alan adı: `www` ve portu kaldır. Rehber veya sosyal alanları şirket sitesi sayma. - Telefon: Yalnız gerçek telefon kalıbını kabul et. Türkiye ülke kodunu normalize et. - Adres: Noktalama ve genel adres kelimelerini temizle. Tam token benzerliği ölç. - İşletme adı: Sektörün genel kelimelerini çıkar. Anlamlı kelime benzerliğini ölç. Aynı alan adı veya aynı telefon güçlü kimlik kanıtıdır. Aynı adres tek başına yeterli değildir. Aynı plazada iki farklı şirket olabilir. Adres eşleşmesine ek olarak anlamlı ad benzerliği iste. Yıl, fiyat, yorum sayısı veya rastgele uzun rakamı telefon ya da kimlik kanıtı sayma. Her işletmede kimlik eşleştirme nedenlerini sakla. Fırsat puanı ve güven puanı ayrı hesaplansın. Fırsat puanı satış fırsatının büyüklüğünü göstersin. Güven puanı ise kanıt kapsamını göstersin. Düşük güvenli büyük fırsat, yüksek güvenli fırsat gibi sunulmasın. ## Denetim ve kanıt Her işletme için mümkünse şu ölçümleri yap: - Google kaydında kendi web sitesi var mı? - İşletme kendi adıyla arandığında doğrulanmış alan adı ilk sayfada mı? - Ana bölge ve sektör aramasında yerel veya organik sonuçta görünüyor mu? - Sitesine ScrapeUnblocker ile erişilebiliyor mu? - Alınan sayfada telefon, form, WhatsApp ve mobil viewport ayarı görünüyor mu? - Site adresi HTTPS kullanıyor mu? "Göremedim" ile "yok" aynı şey değildir. Site alınamadıysa eksik yazma. "Ölçemedim" de. Mobil viewport yoksa "mobil ayarı göremedim" de. "Mobil uyumsuz" deme. Bir sayfadaki en yeni yılı güncellik kanıtı sayma. Yorum veya fiyat metnini telefon sanma. Her kanıt şu alanlarla saklansın: ```json { "kod": "name_search", "iddia": "İşletmenin kendi adı araması tamamlandı", "kaynak_turu": "serpApi", "kaynak": "arama ifadesi veya URL", "gozlenen": {}, "guven": 90, "destekleniyor": true } ``` ## Mesaj doğruluk kapıları Mesaj yalnız desteklenen bir fırsat bulgusu varsa üretilecek. Genel satış mesajı üretme. Mesaj en fazla üç kısa cümle olsun. Bulunduğu gerçek arama ifadesiyle başlasın. Şu çelişkileri mekanik olarak engelle: - İşletme ana aramada görünmüyorsa "sizi o aramada gördüm" deme. - Site veya ada benzeyen doğrulanabilir alan bulunduysa "siteniz yok" deme. - Yalnız aynı adres görüldü diye "iki kaydınız var" deme. - Kendi adı araması başarısızsa görünürlük iddiası kurma. - Kaynak alınamadıysa site kalitesi hakkında kesin konuşma. Kapı geçilmezse mesaj alanını boş bırak. Sebebi `blocked_reasons` içinde yaz. Kullanıcıya uydurulmuş mesaj vermek, mesaj vermemekten daha kötüdür. ## Çıktı sözleşmesi Her koşu zaman damgalı bir klasör oluştursun. Şu beş dosya zorunlu: 1. `rapor.html`: Koyu temalı, tek dosya ve çevrimdışı rapor. 2. `leads.csv`: Sıra, ad, puanlar, ana neden, telefon, site, adres ve mesaj. 3. `evidence.json`: Her işletmenin kanıtları, bulguları ve mesaj kapısı. 4. `run_manifest.json`: Sürüm (`prompt_version=2026-08-17-v3`), mod, bütçe, sayaç, endpoint ve uyarılar. 5. `bulgular.json`: Raporu tekrar çizecek tam temiz veri. HTML hiçbir CDN, font, görsel, script veya dış otomatik istek kullanmasın. Sıkı CSP ekle. Dış URL yalnız kullanıcı tıklarsa açılabilsin. İlk görünümde beş fırsat, neden, kısa mesaj ve iki puan birlikte görünsün. Ayrıntı kartlarında kanıt cümlesi bulgunun hemen altında yer alsın. Mesaj kopyalama düğmesi çevrimdışı da çalışsın. Manifest şu sayaçları ayırsın: API denemesi, başarılı `2xx`, reddedilen `2xx`, cache isabeti ve işletme sayısı. Google Local kanaryasının sonucu ayrıca kaydedilsin. Kalan kredi miktarını tahmin etme. ## Kapak, gömülü video ve tarama düzeni Ekran okunmak için değil taranmak için kurulsun. Uzun düz paragraf yazma. Kanıt cümlesi kapalı bir açılır bölümün arkasında dursun, yalnız başlık her zaman görünsün. İlk ekranda gerçek bir görsel bulunsun, yalnız metin olmasın. Kapaklı Instagram gönderisi kutusu sayfa açılır açılmaz açık dursun. Açık ya da kapalı doğması indirme başarısına göre değil, gösterilecek en az bir kart olup olmadığına göre kararlaşsın. Yanlış tasarım tam tersini yapar: bütün kapaklar indiyse kutuyu kapatır, yalnız biri düşerse açar. Bu, ürünün en güçlü içeriğini gizler, yalnız başarısızlığı öne çıkarır. Böyle bir mantık kurma. Kapağı inmeyen gönderi için çıplak veya boş bir gömülü oynatıcı basma. Yerine kompakt, tek satırlık bir "Instagram'da aç" bağlantı rozeti koy. Birden fazla kapaksız gönderi varsa hepsini tek rozette topla, kaç tane olduğunu söyle; her biri için ayrı rozet basma. Kaç kapak denendiği, kaçının indiği ve düşenlerin nedeni `run_manifest.json`'a ve koşu notlarına yazılsın. Sessiz kapak başarısızlığı yasak. İndirilen her kapak gömülü veri adresi (data-URI) olarak saklansın, dış görsel bağlantısı olarak değil; rapor tamamen çevrimdışı açılabilsin. Bir işletmede en fazla birkaç gönderi göster: kendi adı aramasından çıkan bir gönderi, yoksa Instagram profilinden toplanan en fazla beş gönderi. Mesaj kopyalama düğmesi tıklanınca "Kopyalandı" yazsın; tarayıcı izin vermezse "Kopyalanamadı, elle seçin" yazıp kısa süre sonra eski haline dönsün. ### Raporun görünümü de ürünün parçasıdır Bu rapor ekranda gösterilir; çalışan ama düz bir sayfa ürünü satmaz. Şu iskelet ZORUNLU: - En üste tek cümlelik KARAR şeridi, altına dört kutuluk sayı şeridi (`display:grid`). - İlk ekrandaki beş fırsat yan yana KART olsun (`grid-template-columns` + `minmax`, dar ekranda alt alta); her kartta sıra numarası, işletme adı, `NEDEN`/`MESAJ`/`PUAN` etiketli kısa bloklar ve `
` içinde kanıt. - Paleti CSS değişkeniyle tanımla (`--zemin`, `--panel`, `--kenar`, `--metin`, `--soluk`, `--vurgu`); rengi hep değişkenden oku. - Tabloyu varsayılan görünümde bırakma: başlık satırı ayrı zeminde, ince satır ayracı, sayı hücreleri sağa yaslı. - Gövde en fazla 1100px, satır yüksekliği en az 1.5, bölüm aralığı en az 32px. - Gömülü CSS en az 3.000 karakter olsun. Bu süs değil taban: bu sınırın altında kalan her deneme ekranda varsayılan tarayıcı görünümüne düştü (ölçüldü). ## Test ve teslim kapısı `unittest` ile ağsız fixture testleri yaz. Testler gerçek API çağrısı yapmasın. Sahte resmi istemci kullan. En az şu durumları doğrula: - Sağlıklı Google Local kanaryası ve yalnız bir çağrı. - Google Local `403` denemelerinin aynı sorgu için sekizi aşmaması. - Aynı `403` koşusunun `serp` ya da `get_page_source` çağrısıyla devam etmesi: sahte istemcinin çağrı kaydında fallback en az bir kez görünsün, koşu boş raporla bitmesin. - `401` ve `402` kodlarının her birinin ayrı `subTest` içinde tek denemede duruşu. - `400`, `404`, `415` ve `422` kodlarının her birinin ayrı `subTest` içinde tekrar edilmemesi; `403` Google Local dışındaki uçlarda aynı şekilde sınansın. - `408`, `429`, `500`, `502`, `503` ve `504` kodlarının her birinin ayrı `subTest` içinde en fazla üç toplam deneme yapması; timeout ve ağ hatası da ayrıca sınansın. - 30 ve 70 deneme sınırlarının aşılamaması. - Aynı adresli farklı işletmelerin birleşmemesi. - Aynı telefon veya alan adının güçlü eşleşmesi. - Site bulunduğunda "site yok" bulgusu çıkmaması. - "Görünmüyor" ile "sizi gördüm" çelişkisinin engellenmesi. - Beş zorunlu çıktı ve çevrimdışı CSP. - Sayfa ve veri yolunda doğrudan ağ istemcisi bulunmaması: kapak indirici dışında ikinci bir doğrudan HTTP çağrısı olmaması ve fixture koşusunun gerçek ağa hiç çıkmaması. - `--help` çağrısının hiçbir input, key, SDK, ağ veya yazım yan etkisi olmadan sıfır dönmesi. - `.venv` bulunmayan temiz bir proje kopyasında, taze Python 3.10+ ile argümansız, yalnız `--bolge` ve yalnız `--sektor` çağrılarının yardım metnini göstermesi; `Traceback` üretmeden kod `2` ile çıkması ve hiçbir dosya yazmaması. - Beş çıktı testte geçici klasöre gerçekten yazıldıktan sonra her dosyanın var, boş olmayan ve parse edilebilir olması; HTML içinde görünür `DEMO/FIXTURE — canlı API çıktısı değildir` etiketi bulunması. Geçici fixture çıktısı canlı veri diye sunulmasın. Teslim kökünde boş olmayan `README.md` ve `KANIT.md` zorunludur. Ayrıca `ornek_cikti/` içinde beş zorunlu dosyanın kalıcı, parse edilebilir fixture kopyasını bırak. Yalnız testin geçici klasöründe üretilen dosyalar teslim sayılmaz. README'de hızlı ve derin modu anlat. KANIT.md içinde hangi davranışın fixture ile kanıtlandığını yaz. Canlı API çalıştırmadıysan açıkça söyle. Önce tüm testleri çalıştır. Sonra HTML'i tarayıcıda açıp ilk beş bölümünü ve kopyalama düğmesini doğrula. Anahtar kelimesi, `.env` değeri veya secret benzeri metin çıktı dosyalarında bulunmamalı. Son mesajı yazmadan hemen önce beyan edeceğin her çıktı yolunu yeniden kontrol et. Dosya yoksa, sıfır baytsa, HTML açılamıyorsa veya JSON parse edilemiyorsa “üretildi” deme. İş bitince bana yalnız şunları söyle: oluşturulan klasör, test sonucu, çıktı dosyaları ve canlı çağrı yapılıp yapılmadığı. API anahtarını cevabında tekrar etme.