Use Cases

CAPTCHA İşleme ile Tedarik Zinciri İzleme

Tedarik zinciri izleme akışınız çoğu zaman veri ayrıştırma yüzünden değil, doğrulama ekranı yüzünden durur: gece 03.00'te çalışan cron işi sabaha boş bir rapor bırakır. Pratik çözüm, CAPTCHA'yı ayrı bir kriz olarak değil, HTTP isteğinin normal bir adımı olarak ele almaktır. Aşağıda tedarikçi portalı, kargo firması ve gümrük sitelerinden gelen üç farklı CAPTCHA türünü tek bir Python izleyicide çözen çalışan bir yapı var.

CaptchaAI tarafında ihtiyacınız olan tek şey bir API anahtarı ve yeterli thread. Faturalandırma thread başına olduğu için — çözüm başına değil — gecelik 400 istek de 40.000 istek de aynı plan içinde kalabilir; sınır yalnızca eşzamanlılıktır.


Tedarik zinciri verisini hangi CAPTCHA türleri durduruyor?

Kaynakları tek tek elden geçirmeden önce hangi türün nerede çıktığını bilmek, kodun tamamını yeniden yazmanızı önler. İthalat ağırlıklı ekiplerde tipik dağılım şöyle:

Kaynak türü CAPTCHA türü Veri Sıklık
Tedarikçi portalları reCAPTCHA v2 Stok, fiyat, tedarik süresi Günlük
Kargo ve nakliye firmaları Cloudflare Turnstile Takip, navlun, tahmini teslim Saatlik
Üretici katalogları Resim CAPTCHA'sı Ürün özellikleri, MOQ Haftalık
Gümrük portalları reCAPTCHA v2 Vergi oranları, GTİP kodları Günlük
Liman otoriteleri Resim CAPTCHA'sı Gemi programı, liman yoğunluğu 6 saatte bir
Emtia borsaları reCAPTCHA v3 Spot fiyat, vadeli işlem Gerçek zamanlı

Üç aile — reCAPTCHA, Turnstile ve resim/OCR — kaynakların neredeyse tamamını kapsar. Üçü de CaptchaAI'nin genel kullanıma açık yöntemleri arasındadır; hCaptcha ve FunCaptcha ise henüz desteklenmiyor, dolayısıyla bu iki türü kullanan bir portal için farklı bir erişim yolu (resmî API, EDI, doğrudan anlaşma) planlamanız gerekir.

Yerel bir senaryo: Mersin–Hamburg navlun takibi

İzmir'de çalışan bir tedarik ekibi düşünün: 60 tedarikçiden günlük stok, 4 nakliye firmasından saatlik navlun ve gümrük portalından GTİP bazlı vergi oranı çekiyor. Kur oynaklığı nedeniyle satın alma kararı, fiyat listesinin kaç saat eski olduğuna bağlı. Burada CAPTCHA doğrudan bir maliyet kalemi: her başarısız kontrol alım kararını bir gün geciktiriyor. Aynı ekibin CaptchaAI tarafındaki maliyeti ise USD cinsinden sabit — ADVANCE ($90/ay, 50 thread) gibi bir planla 60 tedarikçinin tamamı tek turda paralel taranabilir. TL bazlı bütçede bu öngörülebilirlik, çözüm başına faturalandırmaya göre bu pazarda gerçek bir avantaj.

Toplanan veri kişisel veri içeriyorsa (tedarikçi tarafındaki iletişim kişileri gibi) KVKK kapsamına girer; izleme akışını yalnızca yetkili olduğunuz portallar ve sözleşmeli veri alanlarıyla sınırlayın.


Adım 1: Tedarikçi portalı CAPTCHA'sını tek izleyicide çözün

Aşağıdaki SupplyChainMonitor sınıfı her tedarikçi için türü yapılandırmadan okur, sayfada doğrulama olup olmadığını kontrol eder ve yalnızca gerekiyorsa CaptchaAI'ye görev gönderir. Token alındıktan sonra ilgili alan adıyla forma yazılır: reCAPTCHA için g-recaptcha-response, Turnstile için cf-turnstile-response, resim CAPTCHA'sı için portalın kendi alanı.

Dikkat edilecek nokta sorgulama döngüsüdür: görev gönderildikten sonra sonucu periyodik olarak res.php üzerinden sorgularsınız ve CAPCHA_NOT_READY yanıtı geldiği sürece beklersiniz. Sabit bekleme yerine üstel geri çekilme (exponential backoff) kullanmak, yoğun saatlerde gereksiz istek üretmenizi engeller.

import requests
import time
import re
import json
import base64
from datetime import datetime

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


def solve_recaptcha(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    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,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_turnstile(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    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,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_image(image_bytes):
    img_b64 = base64.b64encode(image_bytes).decode()
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "base64",
        "body": img_b64, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(20):
        time.sleep(3)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


class SupplyChainMonitor:
    def __init__(self, suppliers, proxy=None):
        self.suppliers = suppliers
        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",
        })

    def check_all(self):
        """Check inventory and pricing across all suppliers."""
        report = {
            "timestamp": datetime.now().isoformat(),
            "suppliers": {},
        }

        for supplier in self.suppliers:
            try:
                data = self._check_supplier(supplier)
                report["suppliers"][supplier["name"]] = {
                    "status": "success",
                    "data": data,
                }
            except Exception as e:
                report["suppliers"][supplier["name"]] = {
                    "status": "error",
                    "error": str(e),
                }
            time.sleep(3)

        return report

    def _check_supplier(self, supplier):
        url = supplier["url"]
        resp = self.session.get(url, timeout=30)

        # Handle CAPTCHA based on type
        captcha_type = supplier.get("captcha_type")
        if captcha_type and self._has_captcha(resp.text):
            resp = self._solve_captcha(resp, url, supplier)

        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")

        return {
            "products": self._extract_inventory(soup),
            "last_updated": self._extract_date(soup),
        }

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

    def _solve_captcha(self, resp, url, supplier):
        captcha_type = supplier.get("captcha_type", "recaptcha")
        sitekey = supplier.get("sitekey", "")

        if not sitekey:
            match = re.search(r'data-sitekey="([^"]+)"', resp.text)
            sitekey = match.group(1) if match else ""

        if captcha_type == "turnstile":
            token = solve_turnstile(sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        elif captcha_type == "image":
            match = re.search(r'src="(/captcha[^"]+)"', resp.text)
            if match:
                img_resp = self.session.get(url.rstrip("/") + match.group(1))
                answer = solve_image(img_resp.content)
                return self.session.post(url, data={"captcha": answer})
        else:
            token = solve_recaptcha(sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

        return resp

    def _extract_inventory(self, soup):
        items = []
        for row in soup.select("table.inventory tr, .product-row"):
            cols = row.select("td, .col")
            if len(cols) >= 3:
                items.append({
                    "sku": cols[0].get_text(strip=True),
                    "stock": cols[1].get_text(strip=True),
                    "price": cols[2].get_text(strip=True),
                })
        return items

    def _extract_date(self, soup):
        date_el = soup.select_one(".last-updated, .update-time")
        return date_el.get_text(strip=True) if date_el else ""


# Configure suppliers
suppliers = [
    {
        "name": "Supplier A",
        "url": "https://supplier-a.example.com/inventory",
        "captcha_type": "recaptcha",
        "sitekey": "6Lc_xxxxxxx",
    },
    {
        "name": "Carrier B",
        "url": "https://carrier-b.example.com/rates",
        "captcha_type": "turnstile",
        "sitekey": "0x4AAAAAAA_xxx",
    },
    {
        "name": "Manufacturer C",
        "url": "https://manufacturer-c.example.com/catalog",
        "captcha_type": "image",
    },
]

monitor = SupplyChainMonitor(
    suppliers=suppliers,
    proxy="http://user:pass@residential.proxy.com:5000",
)
report = monitor.check_all()
print(json.dumps(report, indent=2))

Tek tedarikçiye takılıp kalmamak için check_all her kaynağı ayrı try/except içinde çalıştırır ve hatayı rapora yazar. Böylece 60 tedarikçiden üçü düşse bile raporun tamamı boşa gitmez.


Adım 2: Navlun oranlarını Turnstile arkasından alın

Kargo ve nakliye firmalarının fiyat sorgulama formları ağırlıklı olarak Cloudflare Turnstile kullanır. Akış reCAPTCHA'ya göre daha basittir: sayfadan sitekey çıkarılır, token alınır ve form gönderimine cf-turnstile-response alanı eklenir. Turnstile tarafındaki çözüm süresi kısa olduğu için saatlik navlun taraması gerçekçi bir hedeftir.

class ShippingRateTracker:
    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",
        })

    def get_rates(self, carrier_url, origin, destination, weight):
        """Fetch shipping rates, handling Turnstile CAPTCHA."""
        resp = self.session.get(carrier_url, timeout=30)

        sitekey_match = re.search(r'data-sitekey="([^"]+)"', resp.text)
        if sitekey_match:
            token = solve_turnstile(sitekey_match.group(1), carrier_url)
            resp = self.session.post(carrier_url, data={
                "origin": origin,
                "destination": destination,
                "weight": weight,
                "cf-turnstile-response": token,
            })

        if resp.status_code == 200:
            return resp.json().get("rates", [])
        return []

Aynı sorgulama yapısı GeeTest v3 kullanan Uzak Doğu tedarikçi portallarında da geçerlidir: yalnızca gönderim yöntemi ve token alanı değişir, döngü aynı kalır. Navlun yanıtı JSON dönmüyorsa formun POST gövdesindeki alan adlarını tarayıcı geliştirici araçlarından doğrulayın; kargo firmalarının çoğu alan adlarını sürüm geçişlerinde sessizce değiştirir.


Adım 3: Stok düşüşünde uyarı üretin

İzleme yalnızca veri toplarsa raporu kimse okumaz. Değerli olan kısım eşik geçişidir: bir SKU önceki turda eşiğin üstündeyken bu turda altına düştüyse uyarı üretilir. Aşağıdaki döngü bunu durum karşılaştırmasıyla yapar; yalnızca "stok düşük" değil, "bu turda düştü" olayını yakalar.

def monitor_with_alerts(monitor, alert_thresholds, check_interval=3600):
    """Continuously monitor and alert on inventory changes."""
    previous_data = {}

    while True:
        report = monitor.check_all()

        for supplier, info in report["suppliers"].items():
            if info["status"] != "success":
                continue

            for product in info["data"].get("products", []):
                sku = product["sku"]
                stock = product.get("stock", "")

                # Parse stock level
                try:
                    stock_qty = int(re.sub(r'\D', '', stock))
                except ValueError:
                    continue

                key = f"{supplier}:{sku}"
                prev_qty = previous_data.get(key, stock_qty)

                threshold = alert_thresholds.get(sku, 10)
                if stock_qty < threshold and prev_qty >= threshold:
                    print(f"ALERT: {supplier} - {sku} dropped to {stock_qty}")

                previous_data[key] = stock_qty

        time.sleep(check_interval)

Uyarıları e-posta yerine Slack veya bir webhook'a bağlamak tedarik ekiplerinde çok daha yüksek okunma oranı verir. Zaman damgalarını Europe/Istanbul saat diliminde yazın — vardiya devri tartışmalarının yarısı UTC ile yerel saat karışıklığından çıkar.


Sağlayıcı kesintisi için geri dönüş planı

  • Kritik tedarikçileri isteğe bağlı kaynaklardan ayırın; tek bir portalın düşmesi tüm raporu geçersiz kılmasın.
  • Verinin ne kadar eski olabileceğini yazılı bir eşikle tanımlayın: 6 saatlik stok verisi kabul edilebilir, 48 saatlik navlun verisi değil.
  • Bir tedarikçide CAPTCHA baskısı kalıcı olarak artıyorsa bunu yalnızca mühendislik hatası değil, tedarik riski olarak raporlayın — çoğu zaman portal tarafındaki bir politika değişikliğinin habercisidir.
  • Thread bütçesini kritik kaynaklara ayırın; haftalık taranan katalogları gece penceresine kaydırın.

Sorun giderme

Sorun Sebep Düzeltme
Tedarikçi sayfa düzeni değişti Site yeniden tasarımı CSS seçicilerini güncelleyin
Her kontrolde CAPTCHA çıkıyor Kontrol aralığı çok kısa Kontroller arasındaki aralığı artırın
Oturum kontrol ortasında düşüyor Portal zaman aşımı Oturumu yeniden kurun ve kontrolü kısaltın
Navlun verisi boş dönüyor Giriş gerekiyor Akışa kimlik doğrulama adımı ekleyin
Fiyatlar beklenenden farklı Bölgesel fiyatlandırma İstek yapılandırmasını hedef pazara göre ayarlayın
Token reddedildi Token'ın süresi doldu Token'ı alındıktan sonra 60 saniye içinde gönderin

Sık sorulan sorular

Tedarikçi portallarını izlemek için hangi plan yeterli?

Eşzamanlılığa bakın, toplam isteğe değil. 60 tedarikçiyi sırayla tarayan bir akış için BASIC ($15/ay, 5 thread) yeterlidir; tamamını paralel tarayıp turu birkaç dakikaya indirmek isterseniz ADVANCE ($90/ay, 50 thread) daha uygundur. Her planda thread başına çözüm sınırsızdır.

CaptchaAI gümrük portallarındaki her doğrulama türünü çözebilir mi?

Hayır. reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3 ve resim/OCR CAPTCHA'ları desteklenir; hCaptcha ve FunCaptcha henüz desteklenmiyor, GeeTest v4 için "çok yakında" durumu geçerli. Portalın türünü entegrasyondan önce tespit edin.

Cron işini hangi sıklıkta çalıştırmalıyım?

Çoğu tedarikçi için günlük, tedarik daralması yaşanan kritik bileşenlerde saatlik. Aralığı gereğinden fazla kısaltmak CAPTCHA'ların daha sık çıkmasına yol açar; bu da hem gecikme hem thread tüketimi demektir.

İzleme akışında toplanan veriler KVKK kapsamına girer mi?

Veri yalnızca ürün, fiyat ve lojistik alanlarından oluşuyorsa genelde girmez. Tedarikçi tarafındaki kişi adları, e-posta veya telefon bilgileri toplanıyorsa girer — bu alanları kaydetmeden önce hukuk tarafıyla saklama süresini netleştirin.

İlk entegrasyonu ne kadar sürede kurabilirim?

Tek bir tedarikçi portalı için yarım gün gerçekçi bir hedeftir. API anahtarını alıp ilk token'ı üretmek birkaç dakika sürer; zamanın çoğu portal HTML'inden alan seçicilerini çıkarmaya gider. Sıfırdan başlıyorsanız önce tek bir tedarikçiyle uçtan uca çalışan bir akış kurun, ardından listeyi genişletin.


İlgili rehberler


Tedarik zincirinizi görünür tutun — CaptchaAI anahtarınızı alın ve tedarikçi portallarındaki veri toplamayı bugün otomatikleştirin.

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