Use Cases

Monitoramento de estoque de varejo com manipulação de CAPTCHA

Um monitor de estoque que funciona sem interrupção por duas horas e de repente passa a receber CAPTCHA em toda página de produto não está com bug: está sendo identificado como bot. Amazon, Walmart e outras redes de varejo ativam reCAPTCHA v2, reCAPTCHA v3 e Cloudflare Turnstile assim que o volume de acesso foge do padrão humano — bem antes de bloquear o IP de origem. A saída não é reduzir a frequência de verificação até o monitoramento virar inútil: é resolver o desafio via API e manter o pipeline rodando. Este guia mostra como estruturar esse monitoramento com a CaptchaAI, da primeira requisição ao alerta de mudança de preço.


Onde o CAPTCHA aparece em cada plataforma de varejo

O tipo de desafio muda de varejista para varejista:

Plataforma Tipo de CAPTCHA Gatilho Dados coletados
Amazon reCAPTCHA v2 Acesso de alto volume Preço, estoque, avaliações
Walmart reCAPTCHA v3 + Cloudflare Detecção de bots Estoque, preços
Best Buy reCAPTCHA v2 Verificação ao adicionar ao carrinho Estoque, preços
Target Cloudflare Turnstile Acesso automatizado Disponibilidade
Lojas Shopify Cloudflare Turnstile Limite de requisições Dados do produto
eBay reCAPTCHA v2 Busca + acesso a anúncios Anúncios, preços

Esse mapa muda de intensidade em datas como a Black Friday e o Dia do Consumidor: o mesmo produto que raramente pede CAPTCHA em um dia comum pode passar a exigir resolução em quase toda requisição durante o pico de tráfego. Dimensione as threads da API pensando nesses picos, não na média do ano.

Frequência de verificação e estratégia de IP por categoria de produto

Defina dois parâmetros por categoria: a frequência de verificação e o tipo de origem de rede. Categorias voláteis pedem ciclos curtos e rotação de IPs; commodities toleram ciclos longos e origem fixa.

Tipo de produto Frequência recomendada Estratégia de IP
Eletrônicos A cada 30 minutos Rotação de IPs residenciais
Mercearia A cada 4 horas Rotação de IPs residenciais
Moda A cada 2 horas IP residencial fixo por ciclo
Lançamentos limitados A cada 1 minuto Rede móvel autorizada
Bens de casa A cada 6 horas IP residencial fixo por ciclo
Mercadorias em geral A cada 12 horas Datacenter (geralmente suficiente)

O intervalo de verificação também define quantas threads da API você precisa:

  • BASIC (US$ 15/mês, 5 threads) — catálogo de até ~150 produtos, verificação sequencial.
  • STANDARD (US$ 30/mês, 15 threads) — várias categorias em paralelo sem fila lenta.
  • ADVANCE (US$ 90/mês, 50 threads) — monitoramento contínuo de catálogo grande, mesmo código abaixo.

Monitor de produto único em Python

A classe abaixo cobre o caso mais comum: abrir a página do produto, checar se veio CAPTCHA e resolver via API antes de extrair preço e disponibilidade. Quando a página traz cf-turnstile, o helper resolve com o método turnstile e envia o token no campo cf-turnstile-response; nos demais casos, usa userrecaptcha e o campo g-recaptcha-response — a mesma convenção da API da CaptchaAI em in.php/res.php.

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:pass@residential.proxy.com: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)

track_prices faz uma única passada por todos os produtos e grava o resultado em JSON — útil para rodar via cron. monitor_products mantém o processo ativo e dispara _alert sempre que preço ou estoque mudam entre dois ciclos; troque o print por um webhook para Slack ou e-mail em produção.


Escaneamento de categoria inteira

Para acompanhar uma categoria completa em vez de uma lista fixa de URLs, pagine os resultados de busca e reaproveite o mesmo RetailMonitor — inclusive o mesmo tratamento de CAPTCHA por página:

def scan_category(base_url, category, max_pages=20):
    """Scan an entire product category for stock status."""
    monitor = RetailMonitor(
        proxy="http://user:pass@residential.proxy.com: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

Pare a paginação assim que uma página não trouxer itens (items vazio) — é o sinal mais confiável de que você chegou ao fim da categoria, mais estável do que confiar em um número fixo de páginas.


Comparação de preço entre concorrentes

Para comparar o mesmo produto em várias lojas, repita a busca por loja e normalize o resultado antes de ordenar por preço:

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

LGPD: se você armazena esse histórico de preços por período, considere as obrigações de proteção de dados. Preço e estoque público em geral não são dados pessoais, mas registre a origem e a finalidade da coleta mesmo assim.


Erros comuns no monitoramento e como corrigir

Problema Causa provável Correção
CAPTCHA em toda página de produto IP sinalizado ou limite de requisições excedido Aumente o intervalo entre requisições e faça rotação de IPs
Preço errado coletado Preço renderizado dinamicamente via JS Use Selenium ou Puppeteer para renderizar a página antes de coletar
Página de "verificação de robô" Detecção de bot no estilo Amazon Use cabeçalhos realistas e egress de rede autorizado
Estoque aparece como "indisponível" Disponibilidade restrita por região Combine a origem da requisição com a região do produto
Dados do produto ausentes Estrutura da página mudou Atualize os seletores CSS

Perguntas frequentes

Preciso de proxies para monitorar sem ser bloqueado?

Sim, na prática. Um IP fixo com muitas requisições por hora é o principal gatilho de CAPTCHA em grandes varejistas. Combine a rotação de IPs com resolução automática via API: o proxy reduz a frequência de desafios, e a CaptchaAI resolve os que ainda aparecerem.

Qual plano da CaptchaAI atende ao monitorar centenas de produtos?

Depende do paralelismo, não do número de produtos. Um catálogo de até ~150 SKUs verificado em sequência roda bem no BASIC (US$ 15/mês, 5 threads). Para monitorar várias categorias ao mesmo tempo, o STANDARD (US$ 30/mês, 15 threads) ou o ADVANCE (US$ 90/mês, 50 threads) dão mais threads simultâneas.

O CaptchaAI resolve o CAPTCHA do Cloudflare Turnstile automaticamente?

Sim. Cloudflare Turnstile é um dos tipos com suporte total pela API: envie a sitekey e a pageurl e receba o token pronto para o campo cf-turnstile-response, como no _solve_and_retry acima.

Dá para acompanhar milhares de produtos ao mesmo tempo?

Dá. Distribua as requisições entre vários IPs e escalone os horários de verificação por categoria. Com 1.000 produtos e um intervalo de 3 segundos entre requisições, um ciclo completo leva cerca de 50 minutos.

Sessão rotativa ou fixa: qual usar no monitoramento de estoque?

Rotativa, na maioria dos casos — cada página de produto é uma requisição independente e não depende de estado de sessão. Sessão fixa só entra em cena se checar detalhes do produto exigir navegação em várias etapas do mesmo fluxo.


Guias relacionados

Acompanhe estoque e preço de varejo em tempo real — obtenha sua chave da CaptchaAI e resolva o primeiro desafio ainda hoje.

Os comentários estão desativados para este artigo.