Tutorials

CaptchaAI ile Rakip Analizi Kontrol Paneli Oluşturun

Rakip fiyatlarını haftada bir elle kontrol etmek ölçeklenmez; bir hafta sonra elinizdeki tablo çoktan eskimiştir. Bu rehberde rakiplerin fiyat ve ürün sayfalarını düzenli olarak kazıyan, verileri SQLite'ta biriktiren ve zaman içindeki değişimi gösteren karşılaştırma raporları üreten çalışan bir panel kuracaksınız. Tek ciddi engel — fiyatlandırma sayfalarını koruyan reCAPTCHA v2 — CaptchaAI API'si ile çözülür; böylece kazıyıcınız her çalıştırmada takılıp kalmadan ilerler.

Parçalar sade: bir CAPTCHA çözücü, birkaç veri çıkarıcı, kalıcı depolama ve bir rapor üreteci. Hepsini Python ile birbirine bağlayıp günlük çalışacak hâle getireceğiz.


Neden otomatik rakip izleme?

Fiyat rekabetinin yoğun olduğu pazarlarda karar hızı doğrudan gelire dönüşür. Bir rakip gece yarısı kampanya başlatıp liste fiyatını düşürdüğünde, bunu ertesi öğlen elle fark etmek geç kalmak demektir. Otomatik bir panel farkı dakikalar içinde yakalar ve size iki şey verir: anlık pozisyon (bugün kimin fiyatı nerede) ve trend (son iki haftada kim ne yönde hareket etti).

İkinci kazanç tutarlılıktır. Elle toplanan verilerde format, para birimi ve zamanlama her seferinde biraz kayar; kod her metriği aynı şemayla kaydettiğinde karşılaştırmalar güvenilir olur. Bu panelin çıktısı bir fiyatlandırma toplantısında doğrudan gösterilebilecek kadar temizdir.


Mimari yapı

Akış tek yönlü ve izlenmesi kolay: kazıyıcı bir rakip sayfasını çeker, sayfada reCAPTCHA v2 varsa çözücü devreye girer, çıkarılan metrikler SQLite'a yazılır ve rapor üreteci en güncel değerleri karşılaştırır.

Competitor Sites ──> CAPTCHA Solver ──> Data Extractors
                                             │
                                        SQLite Store
                                             │
                                      Dashboard Report

Veri modeli ve depolama

Her ölçümü tek bir tabloda saklıyoruz: rakip adı, metrik türü, ham değer, sayısal karşılığı, kaynak URL ve zaman damgası. Zaman damgasını her kayıtta tuttuğumuz için trend sorguları ve "en son değer" karşılaştırmaları aynı tablodan gelir; ayrı bir geçmiş tablosuna gerek kalmaz.

# models.py
import sqlite3
from datetime import datetime
from dataclasses import dataclass
from typing import Optional


@dataclass
class CompetitorData:
    competitor: str
    metric: str
    value: str
    numeric_value: Optional[float] = None
    url: str = ""
    scraped_at: str = ""

    def __post_init__(self):
        if not self.scraped_at:
            self.scraped_at = datetime.now().isoformat()


class CompetitorDB:
    def __init__(self, path="competitor_data.db"):
        self.conn = sqlite3.connect(path)
        self._init()

    def _init(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS metrics (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                competitor TEXT,
                metric TEXT,
                value TEXT,
                numeric_value REAL,
                url TEXT,
                scraped_at TEXT
            )
        """)
        self.conn.commit()

    def save(self, data: CompetitorData):
        self.conn.execute(
            """INSERT INTO metrics
               (competitor, metric, value, numeric_value, url, scraped_at)
               VALUES (?, ?, ?, ?, ?, ?)""",
            (data.competitor, data.metric, data.value,
             data.numeric_value, data.url, data.scraped_at),
        )
        self.conn.commit()

    def get_history(self, competitor, metric, limit=30):
        cursor = self.conn.execute(
            """SELECT value, numeric_value, scraped_at
               FROM metrics
               WHERE competitor = ? AND metric = ?
               ORDER BY scraped_at DESC LIMIT ?""",
            (competitor, metric, limit),
        )
        return cursor.fetchall()

    def latest_comparison(self, metric):
        cursor = self.conn.execute(
            """SELECT competitor, value, numeric_value, MAX(scraped_at) as latest
               FROM metrics WHERE metric = ?
               GROUP BY competitor ORDER BY numeric_value""",
            (metric,),
        )
        return cursor.fetchall()

latest_comparison her rakip için MAX(scraped_at) ile en güncel satırı seçer; böylece aynı sayfayı defalarca kazısanız da rapor tekrarlı eski kayıtlarla şişmez.


reCAPTCHA v2 çözücü

Kazıyıcı bir sayfa çektiğinde HTML içinde data-sitekey görürse, o sayfada reCAPTCHA v2 var demektir. Çözücü sitekey'i ayıklar, CaptchaAI'ye in.php üzerinden bir görev gönderir, sonucu res.php uç noktasından periyodik sorgulama ile bekler ve dönen g-recaptcha-response token'ını forma iletir. Sorgulama döngüsü hazır olmayan durumu (CAPCHA_NOT_READY) sessizce tekrar denerken gerçek hataları yukarı fırlatır.

# solver.py
import requests
import time
import re
import os


class CaptchaSolver:
    def __init__(self):
        self.api_key = os.environ["CAPTCHAAI_API_KEY"]

    def solve_if_needed(self, session, url, html):
        if "data-sitekey" not in html:
            return html

        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return html

        sitekey = match.group(1)
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": url,
            "json": 1,
        }, timeout=30)
        task_id = resp.json()["request"]

        time.sleep(15)
        for _ in range(24):
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            }, timeout=15)
            data = resp.json()
            if data.get("status") == 1:
                post_resp = session.post(url, data={
                    "g-recaptcha-response": data["request"],
                }, timeout=30)
                return post_resp.text
            if data["request"] != "CAPCHA_NOT_READY":
                raise RuntimeError(data["request"])
            time.sleep(5)

        raise TimeoutError("CAPTCHA solve timeout")

API anahtarınızı ortam değişkeninde tutun (CAPTCHAAI_API_KEY); kodun içine gömmeyin. Sorgulama döngüsü token'ı beklerken sabit aralıklarla res.php'yi yoklar, hazır olduğunda formu gönderir ve gerçek hata kodlarını (ERROR_*) olduğu gibi yukarı taşır; bu sayede sessiz başarısızlık yerine görünür bir istisna alırsınız.


Rakip kazıyıcı

Kazıyıcı üç tür metriği tek bir arayüzle toplar: plan bazında fiyatlar, özellik listeleri ve ürün sayısı. _fetch metodu her isteği çözücüden geçirir; yani CAPTCHA çıkarsa akış otomatik olarak çözülür ve kazıyıcı sonucu görmeyi sürdürür. CSS seçicileri rakibe göre parametre olarak verildiği için her siteye ayrı bir sınıf yazmanız gerekmez.

# scraper.py
import requests
import re
from bs4 import BeautifulSoup
from solver import CaptchaSolver
from models import CompetitorData


class CompetitorScraper:
    def __init__(self):
        self.solver = CaptchaSolver()
        self.session = requests.Session()
        self.session.headers["User-Agent"] = (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
        )

    def scrape_pricing(self, competitor_name, url, plan_selector, price_selector):
        html = self._fetch(url)
        soup = BeautifulSoup(html, "html.parser")
        plans = soup.select(plan_selector)
        data = []

        for plan in plans:
            name_el = plan.select_one("h3, h2, .plan-name")
            price_el = plan.select_one(price_selector)

            if not name_el or not price_el:
                continue

            price_text = price_el.get_text(strip=True)
            match = re.search(r'[\d,.]+', price_text)
            numeric = float(match.group().replace(",", "")) if match else None

            data.append(CompetitorData(
                competitor=competitor_name,
                metric=f"price_{name_el.get_text(strip=True).lower().replace(' ', '_')}",
                value=price_text,
                numeric_value=numeric,
                url=url,
            ))

        return data

    def scrape_features(self, competitor_name, url, feature_list_selector):
        html = self._fetch(url)
        soup = BeautifulSoup(html, "html.parser")
        features = soup.select(f"{feature_list_selector} li")

        return [
            CompetitorData(
                competitor=competitor_name,
                metric="feature",
                value=f.get_text(strip=True),
                url=url,
            )
            for f in features if f.get_text(strip=True)
        ]

    def scrape_product_count(self, competitor_name, url, count_selector):
        html = self._fetch(url)
        soup = BeautifulSoup(html, "html.parser")
        el = soup.select_one(count_selector)

        if el:
            text = el.get_text(strip=True)
            match = re.search(r'[\d,]+', text)
            if match:
                count = int(match.group().replace(",", ""))
                return CompetitorData(
                    competitor=competitor_name,
                    metric="product_count",
                    value=text,
                    numeric_value=count,
                    url=url,
                )
        return None

    def _fetch(self, url):
        resp = self.session.get(url, timeout=20)
        return self.solver.solve_if_needed(self.session, url, resp.text)

Yerel senaryo: Türkiye'de e-ticaret rekabeti hızlı; bir kategoride üç dört rakibin liste fiyatını günlük izlemek, kampanya dönemlerinde fiyat pozisyonunuzu ayarlamak için yeterli sinyali verir. Burada kritik bir sınır var: yalnızca herkese açık fiyat ve katalog verilerini toplayın. Kişisel veriye (kullanıcı yorumu, üye bilgisi) dokunduğunuz anda iş KVKK kapsamına girer; panelinizi yetkili, halka açık veri toplama iş akışıyla sınırlı tutun.


Rapor üreteci

Rapor üreteci iki çıktı verir: metrik başına en güncel karşılaştırma tablosu ve tek bir rakip-metrik çiftinin zaman içindeki seyri. Fiyat metriklerinde değeri dolar biçiminde, diğerlerinde düz sayı olarak işaretler.

# report.py
from models import CompetitorDB


def generate_report(db: CompetitorDB, metrics):
    lines = ["=" * 60, "Competitor Analysis Report", "=" * 60, ""]

    for metric in metrics:
        results = db.latest_comparison(metric)
        if not results:
            continue

        lines.append(f"--- {metric.replace('_', ' ').title()} ---")
        for comp, value, numeric, ts in results:
            marker = ""
            if numeric is not None:
                marker = f" (${numeric:,.2f})" if "price" in metric else f" ({numeric:,.0f})"
            lines.append(f"  {comp}: {value}{marker}")
        lines.append("")

    return "\n".join(lines)


def generate_trend(db: CompetitorDB, competitor, metric, periods=10):
    history = db.get_history(competitor, metric, limit=periods)
    if not history:
        return f"No data for {competitor} — {metric}"

    lines = [f"Trend: {competitor} — {metric}", "-" * 40]
    for value, numeric, ts in reversed(history):
        date = ts[:10]
        lines.append(f"  {date}: {value}")

    return "\n".join(lines)

generate_trend kayıtları tarihe göre eskiden yeniye sıralar; çıktıyı doğrudan terminalde okuyabilir ya da bir sonraki adımda grafik aracına besleyebilirsiniz.


Ana çalıştırıcı

Son parça, rakip listesini gezen ve her birinin fiyatlarını kazıyıp kaydeden çalıştırıcıdır. Rakipler arasına küçük bir gecikme koymak, isteklerin doğal bir ritimde gitmesini sağlar; bir rakipte hata çıkması diğerlerinin işlenmesini durdurmaz.

# main.py
import time
from models import CompetitorDB
from scraper import CompetitorScraper
from report import generate_report

COMPETITORS = [
    {
        "name": "Competitor A",
        "pricing_url": "https://competitor-a.example.com/pricing",
        "plan_selector": ".pricing-plan",
        "price_selector": ".price",
    },
    {
        "name": "Competitor B",
        "pricing_url": "https://competitor-b.example.com/pricing",
        "plan_selector": ".plan-card",
        "price_selector": ".plan-price",
    },
]


def main():
    db = CompetitorDB()
    scraper = CompetitorScraper()

    for comp in COMPETITORS:
        print(f"Scraping {comp['name']}...")

        try:
            pricing = scraper.scrape_pricing(
                comp["name"], comp["pricing_url"],
                comp["plan_selector"], comp["price_selector"],
            )
            for p in pricing:
                db.save(p)
                print(f"  {p.metric}: {p.value}")
        except Exception as e:
            print(f"  Error: {e}")

        time.sleep(5)

    # Generate report
    metrics = ["price_basic", "price_pro", "price_enterprise", "product_count"]
    report = generate_report(db, metrics)
    print(report)

    with open("competitor_report.txt", "w") as f:
        f.write(report)


if __name__ == "__main__":
    main()

Bu betiği bir zamanlayıcıya (cron veya Windows Görev Zamanlayıcı) günlük olarak bağladığınızda, panel kendi kendini besler ve trend verisi zamanla birikir.


Zamanlama ve ölçekleme

Tek bir rakip listesiyle başlayıp işleyen bir akış elde ettiğinizde, ölçeklendirme çoğunlukla iki eksende olur: daha çok rakip ve daha sık çalıştırma. Rakip sayısını artırırken COMPETITORS listesine yeni giriş eklemek yeterlidir; her yeni site kendi seçicileriyle gelir, kod aynı kalır. Sık çalıştırma tarafında, panelin gece bir kez tam tarama yapıp gündüz yalnızca fiyat sayfalarını daha sık kontrol etmesi pratik bir düzendir.

Eşzamanlılık burada CaptchaAI planınızla ilişkilidir. Ücretlendirme çözüm başına değil, eşzamanlı thread başınadır; yani aynı anda kaç sayfayı çözüme soktuğunuz plan seçiminizi belirler. Birkaç rakip için BASIC ($15/ay, 5 thread) yeterliyken, onlarca siteyi paralel izlemeye geçtiğinizde thread sayısı yüksek bir plana çıkmak akışı hızlandırır. Aylık USD fiyatın sabit olması, TL kur oynaklığından bağımsız bütçeleme yapmanızı sağlar.


Sorun giderme

Sorun Sebep Düzeltme
Fiyatlar çıkarılmadı Seçici uyumsuzluğu Sayfa HTML'sini inceleyip seçicileri her rakibe göre güncelleyin
Geçmiş veri boş İlk çalıştırma Veri zamanla birikir; trend görünürlüğü için günlük çalıştırın
Fiyat sayfasında CAPTCHA Bot tespiti İstekler arasına gecikme ekleyin; çözücü reCAPTCHA v2'yi otomatik çözer
Rapor eski değer gösteriyor Aynı kayıt yeniden eklendi MAX tarihine göre gruplayan latest_comparison'ı kullanın

Sık sorulan sorular

Rakip fiyat sayfasında CAPTCHA çıkarsa akış durur mu?

Hayır. _fetch her yanıtı çözücüden geçirir; sayfada reCAPTCHA v2 varsa CaptchaAI token'ı üretir ve kazıma kaldığı yerden sürer. Kod tarafında ek bir dallanma yazmanıza gerek yoktur.

Topladığım rakip verisini saklamak KVKK açısından sorun olur mu?

Herkese açık fiyat ve katalog verisi kişisel veri değildir. Kullanıcı yorumu, profil veya iletişim bilgisi topladığınız anda iş KVKK kapsamına girer; panelinizi halka açık, yetkili veri toplama iş akışıyla sınırlı tutun.

Rakip izleme için hangi CaptchaAI planı yeterli?

Günde birkaç rakip sayfası kazıyan bir panel için BASIC ($15/ay, 5 thread) rahatça yeter. Onlarca siteyi eşzamanlı izlemeye ölçeklendiğinizde thread sayısı daha yüksek bir plana geçebilirsiniz; ücretlendirme thread başınadır, çözüm başına değil.

Paneli ne sıklıkla çalıştırmalıyım?

Çoğu kategori için günde bir çalıştırma trend için yeterli çözünürlük verir. Kampanya dönemlerinde sıklığı artırabilirsiniz, ancak her çalıştırma arasına gecikme koymayı sürdürün.

Fiyat dışı metrikleri de takip edebilir miyim?

Evet. Özellik listeleri için scrape_features, katalog boyutu için scrape_product_count metotlarını kullanın; aynı CompetitorData modeliyle istediğiniz metrik için özel kazıyıcı ekleyebilirsiniz.


İlgili kılavuzlar


Rakipleri geniş ölçekte izleyin —CaptchaAI ile başlayın.

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