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
- Como funciona a rotação de proxies residenciais
- Sessões fixas ou rotativas: como decidir
- Monitoramento de cadeia de suprimentos com CAPTCHA
Acompanhe estoque e preço de varejo em tempo real — obtenha sua chave da CaptchaAI e resolva o primeiro desafio ainda hoje.