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
- Oturum ve IP yapılandırmasının çözüm oranına etkisi
- Oturum sürekliliği mi, her istekte yeni oturum mu?
- Tedarik zinciri verisinde CAPTCHA yönetimi
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.