Use Cases

CAPTCHA İşleme ile Perakende Envanter Takibi

Envanter takip akışınızın gerçek darboğazı HTML ayrıştırma değil, üçüncü haftada aniden gelen doğrulama sayfasıdır. Fiyat ve stok verisi çeken bir istemci, kontrol frekansı arttıkça er ya da geç reCAPTCHA v2 ya da Cloudflare Turnstile ile karşılaşır; o noktadan sonra topladığınız şey ürün verisi değil, doğrulama sayfasının HTML'idir. Çözüm üç adımlıdır: doğrulamayı tespit edin, sitekey ve sayfa adresini CaptchaAI'ye gönderin, dönen token'ı aynı oturumla geri gönderip veriyi alın.

CaptchaAI tarafında ilgilendiğiniz tipler bellidir: reCAPTCHA v2 (görünmez varyantı dahil), reCAPTCHA v3, Cloudflare Turnstile ve Cloudflare doğrulama akışı, GeeTest v3, görsel/OCR ve grid CAPTCHA'lar genel kullanımda; CaptchaFox (beta), Friendly Captcha (beta) ve Lemin (beta) beta aşamasında. hCaptcha ve FunCaptcha (Arkose Labs) desteklenmiyor, GeeTest v4 ise yalnızca "çok yakında" başlığı altında. Takip etmek istediğiniz sitede bu son üçünden biri varsa akışınızı ona göre planlayın.


Türkiye'den bakınca: USD ve thread bazlı fiyatlandırma neden önemli

Türkiye'deki e-ticaret ve otomasyon ekiplerinde envanter takibi çoğu zaman müşteri işi olarak yürür: bir markanın kendi ürünlerinin bayi sayfalarındaki fiyatını izlemek ya da bir pazaryeri satıcısının kendi listelerini doğrulamak. Bu işlerde maliyeti önden bilmek, aylık faturanın tahmin edilebilir olması kadar önemlidir.

CaptchaAI'nin faturalama modeli çözüm başına değil, eşzamanlı thread başınadır ve plan içinde thread başına çözüm sınırsızdır. Yani 200 ürünü saatte bir kontrol eden bir akışla aynı ürünleri 15 dakikada bir kontrol eden bir akış arasındaki fark, CAPTCHA maliyeti değil eşzamanlılık ihtiyacıdır. Küçük bir izleme işi için BASIC ($15/ay, 5 thread) genelde yeterlidir; birkaç yüz ürünü paralel tarayan bir üretim akışı ADVANCE ($90/ay, 50 thread) bandına oturur; çok mağazalı, sürekli çalışan kurulumlar için PREMIUM ($170/ay, 100 thread) daha rahat bir tavan verir. Fiyatlar USD'dir; kur oynaklığı nedeniyle TL karşılığını sabit bir sayı olarak planlamayın.

Bir başka yerel not: topladığınız veri kişisel veri içeriyorsa (satıcı adı, yorum yazarı, iletişim bilgisi) KVKK kapsamına girersiniz. Envanter takibinde doğru yaklaşım, ürün ve fiyat alanlarıyla sınırlı kalmak ve kişisel alanları hiç toplamamaktır.


Hangi perakende sayfası hangi doğrulamayı gösterir

Aşağıdaki tablo, izleme akışlarında en sık karşılaşılan kalıpları özetler. Siteler koruma yapılandırmasını zamanla değiştirir; tabloyu başlangıç haritası olarak okuyun.

Platform CAPTCHA türü Tetikleyen durum Çekilen veri
Amazon reCAPTCHA v2 Yüksek hacimli erişim Fiyat, stok, yorum sayısı
Walmart reCAPTCHA v3 + Cloudflare Bot tespiti Envanter, fiyat
Best Buy reCAPTCHA v2 Sepete ekleme kontrolü Stok, fiyat
Target Cloudflare Turnstile Otomatik erişim Bulunabilirlik
Shopify mağazaları Cloudflare Turnstile İstek sınırlama Ürün verisi
eBay reCAPTCHA v2 Arama + ilan erişimi İlanlar, fiyatlar

Pratik sonuç: tek bir tipe göre kod yazmayın. Aynı akış içinde hem g-recaptcha-response hem cf-turnstile-response alanını doldurabilmelisiniz — aşağıdaki sınıf bunu HTML'e bakarak kendisi seçer.


Ürün izleyicinin çekirdeği

RetailMonitor sınıfı üç işi yapar: sayfayı çeker, yanıtta doğrulama izi görürse sitekey'i yakalayıp CaptchaAI'ye gönderir, dönen token'ı formla birlikte geri gönderir. solve_captcha fonksiyonu görevi in.php ile gönderir ve res.php üzerinden periyodik sorgulama yapar; CAPCHA_NOT_READY yanıtı geldiği sürece beklemeye devam eder. YOUR_API_KEY yer tutucusunu panelinizden aldığınız API anahtarınızla değiştirin.

import requests
import time
import re
import json
from datetime import datetime
from bs4 import BeautifulSoup

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY, "method": method,
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Timeout")


class RetailMonitor:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def check_product(self, url):
        """Check single product's price and availability."""
        resp = self.session.get(url, timeout=30)

        # Handle CAPTCHA
        if self._has_captcha(resp.text):
            resp = self._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        return {
            "url": url,
            "title": self._text(soup, "h1, .product-title, #productTitle"),
            "price": self._text(soup, ".price, .a-price .a-offscreen, .prod-price"),
            "availability": self._text(soup, "#availability, .stock-status, .fulfillment"),
            "in_stock": self._check_stock(soup),
            "timestamp": datetime.now().isoformat(),
        }

    def monitor_products(self, product_urls, interval_sec=1800):
        """Continuously monitor products for changes."""
        history = {}

        while True:
            for url in product_urls:
                try:
                    current = self.check_product(url)

                    # Check for changes
                    prev = history.get(url)
                    if prev:
                        changes = self._detect_changes(prev, current)
                        if changes:
                            self._alert(current["title"], changes)

                    history[url] = current
                    time.sleep(3)

                except Exception as e:
                    print(f"Error checking {url}: {e}")

            print(f"Cycle complete: {len(product_urls)} products checked")
            time.sleep(interval_sec)

    def track_prices(self, product_urls, output_file="prices.json"):
        """Single price check across all products."""
        results = []
        for url in product_urls:
            try:
                data = self.check_product(url)
                results.append(data)
                time.sleep(3)
            except Exception as e:
                results.append({"url": url, "error": str(e)})

        with open(output_file, "w") as f:
            json.dump(results, f, indent=2)
        print(f"Tracked {len(results)} products → {output_file}")
        return results

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile', 'captcha',
        ])

    def _solve_and_retry(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)

        sitekey = match.group(1)
        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""

    def _check_stock(self, soup):
        stock_el = soup.select_one("#availability, .stock-status")
        if stock_el:
            text = stock_el.get_text(strip=True).lower()
            return "in stock" in text or "available" in text
        return None

    def _detect_changes(self, prev, current):
        changes = []
        if prev["price"] != current["price"]:
            changes.append(f"Price: {prev['price']} → {current['price']}")
        if prev["in_stock"] != current["in_stock"]:
            status = "In Stock" if current["in_stock"] else "Out of Stock"
            changes.append(f"Stock: → {status}")
        return changes

    def _alert(self, title, changes):
        print(f"ALERT [{title}]: {', '.join(changes)}")


# Usage
monitor = RetailMonitor(
    proxy="http://user:[email protected]:5000"
)

products = [
    "https://store.example.com/product/abc123",
    "https://store.example.com/product/def456",
    "https://store.example.com/product/ghi789",
]

# One-time price check
results = monitor.track_prices(products)

# Or continuous monitoring (every 30 min)
# monitor.monitor_products(products, interval_sec=1800)

Kodun üzerinde durmaya değer iki ayrıntısı var. Birincisi, token'ı üreten oturumla gönderen oturum aynıdır — farklı bir requests.Session üzerinden gönderilen token tipik olarak reddedilir. İkincisi, _has_captcha kasıtlı olarak geniş tutulmuştur: data-sitekey, g-recaptcha ve cf-turnstile izlerini birlikte arar, çünkü hangi korumanın devreye gireceğini önceden bilemezsiniz. GeeTest v3 gösteren bir mağaza panelinde de aynı desen geçerlidir; değişen tek şey gönderilen method değeridir.


Kategori sayfalarını sayfa sayfa tarama

Tek ürün yerine bütün bir kategorinin stok durumunu çıkarmak istediğinizde mantık aynıdır, sadece sayfalama eklenir. Boş sonuç dönen ilk sayfada döngüyü kırın; perakende siteleri var olmayan sayfa numaralarında çoğu zaman 200 döner ve döngü boşuna sürer gider.

def scan_category(base_url, category, max_pages=20):
    """Scan an entire product category for stock status."""
    monitor = RetailMonitor(
        proxy="http://user:[email protected]:5000"
    )

    all_products = []
    for page in range(1, max_pages + 1):
        url = f"{base_url}/{category}?page={page}"
        resp = monitor.session.get(url, timeout=30)

        if monitor._has_captcha(resp.text):
            resp = monitor._solve_and_retry(resp.text, url)

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.select(".product-card, .s-result-item")

        if not items:
            break

        for item in items:
            all_products.append({
                "name": monitor._text(item, ".product-name, .a-text-normal"),
                "price": monitor._text(item, ".price, .a-price"),
                "stock": monitor._text(item, ".stock, .a-color-success"),
                "url": item.select_one("a")["href"] if item.select_one("a") else "",
            })

        time.sleep(3)

    return all_products

Aynı ürünü mağazalar arasında karşılaştırma

Fiyat karşılaştırması, izleme akışının en çok değer üreten parçasıdır: aynı ürün için birkaç satıcının arama sonucundaki ilk kaydını alıp yan yana koyar.

def compare_product_across_stores(product_name, stores):
    """Compare prices across retailers for the same product."""
    results = []

    for store in stores:
        monitor = RetailMonitor(proxy=store.get("proxy"))
        search_url = f"{store['base_url']}/search?q={product_name}"

        try:
            resp = monitor.session.get(search_url, timeout=30)
            if monitor._has_captcha(resp.text):
                resp = monitor._solve_and_retry(resp.text, search_url)

            soup = BeautifulSoup(resp.text, "html.parser")
            first_result = soup.select_one(".product-card, .s-result-item")

            if first_result:
                results.append({
                    "store": store["name"],
                    "price": monitor._text(first_result, ".price"),
                    "in_stock": "in stock" in first_result.get_text().lower(),
                })
        except Exception as e:
            results.append({"store": store["name"], "error": str(e)})

        time.sleep(5)

    results.sort(key=lambda x: x.get("price", "zzzz"))
    return results

Sıralamanın metin bazlı yapıldığına dikkat edin; ciddi bir karşılaştırma için fiyatı Decimal'e çevirip para birimini ayrıca saklayın.


Kontrol frekansını nasıl seçmelisiniz

Envanter takibinde en sık yapılan hata, "ne kadar sık o kadar iyi" varsayımıdır. Frekans arttıkça CAPTCHA sıklığı da artar; belirli bir noktadan sonra döngünüzün süresini veri tazeliği değil doğrulama beklemesi belirler. Aşağıdaki aralıklar, veri değişim hızına göre makul bir başlangıçtır.

Ürün tipi Kontrol aralığı Not
Elektronik 30 dakika Fiyat gün içinde birden çok kez değişebilir
Market ürünleri 4 saat Stok yavaş döner, sık kontrol gereksiz
Moda 2 saat Beden bazlı stok için ürün sayfası şart
Sınırlı stoklu ürünler 5–15 dakika Düşük ürün sayısıyla, kendi altyapınızda çalıştırın
Ev ürünleri 6 saat Günlük rapor için fazlasıyla yeterli
Standart tüketim ürünleri 12 saat Günde iki tam döngü yeterli

Kural olarak: önce aralığı gevşetin, sonra eşzamanlılığı artırın. 1.000 ürünü 3 saniyelik gecikmelerle tek akışta taramak yaklaşık 50 dakika sürer; aynı işi beş thread'e bölerseniz döngü on dakikanın altına iner ve site başına istek yoğunluğu değişmez.


Sorun giderme

Sorun Sebep Çözüm
Her ürün sayfasında CAPTCHA çıkıyor İstek yoğunluğu eşiği aşıldı Gecikmeyi artırın, döngüyü kademelendirin
Fiyat alanı boş ya da yanlış geliyor Fiyat JavaScript ile oluşturuluyor Selenium veya Playwright ile render edin
"Robot kontrolü" sayfası dönüyor Sayfa düzeyinde bot tespiti Gerçekçi HTTP başlıkları ekleyin, istek hızını düşürün
Stok "mevcut değil" görünüyor Bölgeye bağlı bulunabilirlik Proxy yapılandırmanızı hedef bölgeyle eşleştirin
Ürün verisi eksik Sayfa yapısı değişti CSS seçicilerini güncelleyin
ERROR_ZERO_BALANCE Bakiye bitti Panelden bakiyenizi yükleyin
Token hedef site tarafından reddedildi Token gönderilmeden önce süresi doldu Token'ı aldıktan hemen sonra gönderin

SSS

Envanter takibi için hangi plan yeterli olur?

Birkaç yüz ürünü saatlik kontrol eden bir akış için BASIC ($15/ay, 5 thread) genellikle yeterlidir. Eşzamanlı kontrol sayısı arttıkça ADVANCE ($90/ay, 50 thread) bandına geçilir; belirleyici olan toplam çözüm sayısı değil, aynı anda açık kalan thread sayısıdır.

CAPTCHA çözümü döngü süremi ne kadar uzatır?

Turnstile için tipik çözüm süresi 10 saniyenin altındadır, reCAPTCHA v2 için birkaç on saniye bandındadır. Asıl fark, doğrulamanın kaç sayfada çıktığıdır: 500 üründen onunda çıkıyorsa etkisi ihmal edilebilir, hepsinde çıkıyorsa frekansınız fazla yüksek demektir.

Aynı token'ı birden fazla istekte kullanabilir miyim?

Hayır. Token tek kullanımlıktır ve kısa ömürlüdür; her doğrulama için yeni bir görev gönderin ve token'ı hemen kullanın.

CaptchaAI hCaptcha korumalı sayfaları çözebilir mi?

Hayır. hCaptcha ve FunCaptcha (Arkose Labs) desteklenmiyor. İzlemek istediğiniz sitede bunlardan biri varsa resmî API veya veri ortaklığı gibi alternatifleri değerlendirin.

Topladığım verilerde KVKK riski var mı?

Ürün adı, fiyat ve stok durumu kişisel veri değildir. Satıcı adı, yorum yazarı veya iletişim bilgisi topluyorsanız KVKK kapsamına girersiniz — bu alanları akıştan tamamen çıkarmak en temiz yoldur.


İlgili rehberler


Stok ve fiyat verisi akmaya devam etsin: CaptchaAI API anahtarınızı alın ve doğrulama adımını akışınıza bağlayın.

Bu makale için yorumlar devre dışı bırakılmıştır.