Use Cases

Manipulação de CAPTCHA para comparação de preços de farmácias

Comparar o preço de um medicamento em cinco farmácias diferentes parece simples até a terceira busca travar em um reCAPTCHA. Cada portal trata consulta automatizada como suspeita e responde com um desafio — geralmente reCAPTCHA v2 na busca, às vezes Cloudflare Turnstile no resultado. Este guia mostra como resolver esses desafios com a API da CaptchaAI, em Python e em Node.js.

Onde a busca de preço de remédio esbarra em CAPTCHA

Onde CAPTCHA mais comum Quando aparece
Busca de medicamento reCAPTCHA v2 A cada consulta
Resultado de preço Cloudflare Turnstile Padrão de acesso automatizado
Localizador de farmácia reCAPTCHA v2 Busca por CEP
Cupom de desconto CAPTCHA de imagem Antes de exibir o código
Convênio/seguro reCAPTCHA v2 Login ou busca protegida

Por que os portais de farmácia protegem tanto os preços

No Brasil, o preço de referência de medicamentos segue o teto fixado pela CMED — o PMC, Preço Máximo ao Consumidor. Isso não elimina a variação real entre farmácias: descontos e promoções oscilam bastante, e é essa variação que um comparador existe para capturar. Os portais monitoram de perto o volume de buscas por medicamento e CEP, porque concorrentes usam o mesmo padrão de consulta — o CAPTCHA é a resposta a isso. Se o seu comparador guarda CEP e histórico de busca vinculados a um usuário, trate esses dados considerando a LGPD.

Fluxo básico: resolver o reCAPTCHA v2 e reenviar a busca

O padrão se repete em quase todo portal: carregar a página de busca, extrair a sitekey do reCAPTCHA, resolver com a API da CaptchaAI e reenviar o formulário com o token no campo g-recaptcha-response. O exemplo abaixo faz isso para uma única farmácia.

import requests
import time

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})

def solve_recaptcha(site_key, page_url):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": "YOUR_API_KEY",
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1
    })
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": "YOUR_API_KEY",
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]
    raise TimeoutError("Solve timed out")

def search_drug_prices(drug_name, zipcode):
    search_url = "https://pharmacy.example.com/search"

    # Load the search page to get the site key
    page = session.get(search_url)

    # Extract reCAPTCHA site key
    import re
    match = re.search(r'data-sitekey="([^"]+)"', page.text)
    site_key = match.group(1)

    # Solve the CAPTCHA
    token = solve_recaptcha(site_key, search_url)

    # Submit the search with token
    results = session.post(search_url, data={
        "drug": drug_name,
        "zip": zipcode,
        "g-recaptcha-response": token
    })

    return parse_prices(results.text)

Repare no polling: a função consulta res.php a cada 3 segundos, até 60 tentativas — o mesmo teto de menos de 60 segundos que a CaptchaAI usa para reCAPTCHA v2 em geral.

Comparando preços em várias farmácias ao mesmo tempo

Para montar o comparador de verdade, repita o fluxo acima para cada fonte e junte os resultados. A lista PHARMACY_SOURCES guarda a URL e o tipo de CAPTCHA de cada portal; compare_drug_price resolve os desafios em sequência e devolve a lista ordenada pelo menor preço encontrado.

PHARMACY_SOURCES = [
    {
        "name": "PharmacyA",
        "url": "https://pharmacya.example.com/pricing",
        "captcha_type": "recaptcha_v2"
    },
    {
        "name": "PharmacyB",
        "url": "https://pharmacyb.example.com/drugs",
        "captcha_type": "turnstile"
    },
    {
        "name": "PharmacyC",
        "url": "https://pharmacyc.example.com/search",
        "captcha_type": "image"
    }
]

def compare_drug_price(drug_name, zipcode):
    results = []

    for source in PHARMACY_SOURCES:
        try:
            prices = fetch_prices(
                source["url"],
                source["captcha_type"],
                drug_name,
                zipcode
            )
            results.append({
                "source": source["name"],
                "prices": prices,
                "status": "success"
            })
        except Exception as e:
            results.append({
                "source": source["name"],
                "error": str(e),
                "status": "failed"
            })

    return sorted(results, key=lambda r: r.get("prices", {}).get("lowest", float("inf")))

Isso também dá para paralelizar com concurrent.futures ou asyncio — cada farmácia ocupa uma thread própria na CaptchaAI, sem fila com as outras.

Um portal, um tipo de CAPTCHA diferente

Nem toda farmácia usa reCAPTCHA v2: alguns portais de resultado migraram para Turnstile, e cupons de desconto costumam ficar atrás de um CAPTCHA de imagem simples. A função abaixo escolhe o solver pelo captcha_type declarado e devolve o campo esperado pelo formulário — g-recaptcha-response ou cf-turnstile-response.

def fetch_prices(url, captcha_type, drug_name, zipcode):
    page = session.get(url)

    if captcha_type == "recaptcha_v2":
        import re
        match = re.search(r'data-sitekey="([^"]+)"', page.text)
        token = solve_recaptcha(match.group(1), url)
        field_name = "g-recaptcha-response"

    elif captcha_type == "turnstile":
        import re
        match = re.search(r'data-sitekey="(0x[^"]+)"', page.text)
        token = solve_turnstile(match.group(1), url)
        field_name = "cf-turnstile-response"

    elif captcha_type == "image":
        img_data = extract_captcha_image(page.text)
        token = solve_image_captcha(img_data)
        field_name = "captcha"

    return session.post(url, data={
        "drug": drug_name,
        "zip": zipcode,
        field_name: token
    })

O Turnstile resolve bem mais rápido que o reCAPTCHA v2 — menos de 10 segundos contra até 60 — o que explica por que cada vez mais páginas de resultado migraram para ele.

Sessões que evitam CAPTCHA a cada busca

Repetir a busca do mesmo medicamento em CEPs diferentes gera dezenas de requisições, e cada uma pode cair em um novo CAPTCHA se a sessão não guardar os cookies do portal. A classe abaixo reaproveita uma sessão e só aciona o solver quando a página devolve um desafio.

class PharmacySession:
    def __init__(self, base_url):
        self.session = requests.Session()
        self.base_url = base_url
        self.searches_since_captcha = 0

    def search(self, drug_name, zipcode):
        result = self.session.post(f"{self.base_url}/search", data={
            "drug": drug_name,
            "zip": zipcode
        })

        if self.is_captcha_page(result.text):
            token = self.solve_page_captcha(result.text)
            result = self.session.post(f"{self.base_url}/search", data={
                "drug": drug_name,
                "zip": zipcode,
                "g-recaptcha-response": token
            })
            self.searches_since_captcha = 0
        else:
            self.searches_since_captcha += 1

        return result

    def is_captcha_page(self, html):
        return "g-recaptcha" in html or "cf-turnstile" in html

    def solve_page_captcha(self, html):
        import re
        match = re.search(r'data-sitekey="([^"]+)"', html)
        return solve_recaptcha(match.group(1), self.base_url)

Esse padrão sozinho já corta boa parte dos desafios repetidos, já que a maioria dos portais só cobra novo CAPTCHA depois de várias buscas seguidas.

Versão em Node.js para quem já roda JavaScript

Se o restante do seu pipeline de coleta já está em Node.js, a mesma lógica funciona sem reescrever nada em Python. Promise.all resolve os CAPTCHAs das farmácias em paralelo, e o resultado final sai ordenado do preço mais baixo para o mais alto.

async function comparePharmacyPrices(drugName, zipCode, sources) {
  const results = await Promise.all(
    sources.map(async (source) => {
      try {
        const price = await fetchDrugPrice(source, drugName, zipCode);
        return { source: source.name, price, status: 'success' };
      } catch (error) {
        return { source: source.name, error: error.message, status: 'failed' };
      }
    })
  );

  return results
    .filter(r => r.status === 'success')
    .sort((a, b) => a.price - b.price);
}

async function fetchDrugPrice(source, drugName, zipCode) {
  // Solve CAPTCHA for this source
  const token = await solveCaptcha(source.siteKey, source.url);

  const response = await fetch(source.url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      drug: drugName,
      zip: zipCode,
      'g-recaptcha-response': token
    })
  });

  return parsePrice(await response.text());
}

Qual plano da CaptchaAI cobre o volume de buscas diárias

Cada busca com CAPTCHA ocupa uma thread até o token voltar. Dimensione pelo número de farmácias consultadas ao mesmo tempo, não pelo total de buscas por dia — todo plano inclui solves ilimitados por thread, sem cobrança por CAPTCHA e sem limite diário.

Cenário Plano sugerido Threads
Poucos medicamentos, 3–5 farmácias, uma vez por dia BASIC ($15/mês, 5 threads) 5
Dezenas de medicamentos, vários CEPs, atualização horária ADVANCE ($90/mês, 50 threads) 50
Agregador para vários clientes, volume contínuo alto ENTERPRISE ($300/mês, 200 threads) 200

Limite de requisições: como não ser bloqueado

Portais de farmácia monitoram de perto o padrão de acesso:

Estratégia Como aplicar
Espaçar as requisições 5 a 10 segundos entre buscas
Trocar de sessão Nova sessão a cada 20–30 consultas
Variar os padrões de busca Não repita o mesmo medicamento várias vezes seguidas
Rotação de proxy IPs de datacenter acionam mais CAPTCHA

Erros comuns e como resolver

Problema Causa provável Correção
CAPTCHA aparece em toda busca A sessão não guarda cookies Use requests.Session() e mantenha os cookies entre requisições
Preço não carrega depois do CAPTCHA Falta o token CSRF do formulário Extraia e envie os campos ocultos junto com o token
Portal bloqueia depois de muitas buscas Rate limit do portal Aumente o intervalo entre requisições
Preço diferente do que aparece no navegador Faltam cookies ou cabeçalhos de sessão Replique os cabeçalhos do navegador exatamente

Perguntas frequentes

A LGPD afeta como eu armazeno os preços coletados?

Sim. CEP e histórico de busca vinculados a um usuário podem contar como dado pessoal. Colete só o necessário e defina um prazo de retenção.

Quanto tempo leva para resolver o CAPTCHA de um portal de farmácia?

Depende do tipo: Turnstile fica abaixo de 10 segundos, reCAPTCHA v2 tem teto de até 60 segundos, e imagem simples resolve em menos de 1 segundo.

Dá para comparar preços em várias farmácias ao mesmo tempo?

Sim — cada farmácia resolve o próprio CAPTCHA em uma thread separada na CaptchaAI, então as buscas rodam em paralelo sem fila, até o limite de threads do seu plano.

O que fazer se um portal trocar de reCAPTCHA para Turnstile de uma hora para outra?

Ajuste só o captcha_type e o field_name na função de busca — o resto do fluxo continua igual.

Preciso de um navegador (Selenium/Puppeteer) ou dá para fazer só com requisições HTTP?

Na maioria dos portais, só requests (Python) ou fetch (Node.js) resolvem — quem trata o CAPTCHA é a API, não o navegador. Um headless browser só entra em cena se a busca depender de JavaScript pesado.

Artigos relacionados

Próximos passos

Comece a montar seu comparador de preços de farmácia: pegue sua chave de API da CaptchaAI e resolva o primeiro reCAPTCHA em poucos minutos.

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