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
- Como resolver o callback do reCAPTCHA v2 pela API
- reCAPTCHA v2 e Turnstile no mesmo site
- Como funciona o callback do reCAPTCHA v2
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.