Use Cases

Extração de dados imobiliários com tratamento CAPTCHA

Portais imobiliários bloqueiam scraping quase sempre da mesma forma: depois de algumas dezenas de requisições do mesmo IP, a página normal dá lugar a um desafio CAPTCHA — reCAPTCHA v3 nos grandes portais, Cloudflare Challenge nos agregadores de MLS, Turnstile em sites de leilão. A rota mais estável não é disfarçar o crawler, e sim resolver o desafio no momento em que ele aparece e seguir o fluxo normal da página. Este guia mostra os tipos de CAPTCHA mais comuns por categoria de site imobiliário, como montar um coletor em Python com a API da CaptchaAI, e quais campos priorizar numa análise de mercado — preço por m², dias no mercado, histórico de variação de preço.


CAPTCHAs mais comuns em portais e agregadores imobiliários

Cada categoria de site usa uma defesa diferente, então um coletor de dados imobiliários precisa reconhecer o tipo de desafio antes de decidir como resolvê-lo — é exatamente isso que o parâmetro method da API da CaptchaAI faz.

Tipo de plataforma Proteção Tipo de CAPTCHA
Agregadores de MLS Cloudflare Challenge Desafio completo + proxy
Portais no estilo Zillow reCAPTCHA v3 Invisível, comportamental
Diretórios de corretores reCAPTCHA v2 Caixa de seleção ou invisível
Registros de imposto sobre a propriedade CAPTCHA de imagem Reconhecimento de texto
Sites de leilão Cloudflare Turnstile Desafio de widget
Listagens comerciais reCAPTCHA v2 Enterprise Verificação reforçada

Repare que MLS e leilões usam as duas variantes de proteção da Cloudflare — Challenge e Turnstile —, então o coletor precisa tratar as duas, não só uma delas. É por isso que o código abaixo verifica reCAPTCHA e Turnstile na mesma função de busca, em vez de assumir um único tipo fixo.


Exemplo: monitorando preços de imóveis em uma região sem travar no CAPTCHA

Uma equipe de análise de mercado que acompanha o preço médio do m² em bairros de São Paulo normalmente roda o coletor todas as manhãs contra três ou quatro agregadores diferentes, em paralelo, para fechar o snapshot do dia antes das 9h. Cada agregador dispara um tipo de desafio diferente — Turnstile em um, reCAPTCHA v3 em outro — e é aí que a coleta manual trava: o script para, alguém precisa resolver o CAPTCHA na mão, e o snapshot do dia sai incompleto.

Com a API da CaptchaAI, o mesmo fluxo continua rodando sem intervenção: o parâmetro method já identifica o tipo de desafio, resolve e devolve o token para o fetch() continuar. Times que monitoram poucos portais fecham a conta com o plano BASIC (US$ 15/mês, 5 threads); quando o volume cresce para monitoramento diário de vários agregadores em paralelo, o STANDARD (US$ 30/mês, 15 threads) costuma ser o ponto de partida mais realista — o plano não cobra por CAPTCHA resolvido, só limita quantas coletas rodam ao mesmo tempo.


Como montar um coletor de dados imobiliários em Python com a API da CaptchaAI

O coletor abaixo resolve automaticamente reCAPTCHA e Cloudflare Turnstile conforme eles aparecem: detecta o sitekey na página, decide se é v2 ou v3 pela presença do script recaptcha/api.js?render=, resolve via in.php/res.php e reenvia o token no campo correto (g-recaptcha-response ou cf-turnstile-response) antes de seguir para a próxima página.

import requests
import time
import re
import json
import csv
import os
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]


def solve_captcha(params):
    params["key"] = API_KEY
    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit: {resp.text}")

    task_id = resp.text.split("|")[1]
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id,
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|", 1)[1]
        raise Exception(f"Solve: {result.text}")
    raise TimeoutError()


class PropertyCollector:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers["User-Agent"] = (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/120.0.0.0"
        )

    def fetch(self, url):
        """Fetch page with automatic CAPTCHA handling."""
        resp = self.session.get(url)

        # reCAPTCHA
        match = re.search(
            r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text
        )
        if match:
            # Detect v3
            is_v3 = "recaptcha/api.js?render=" in resp.text
            params = {
                "method": "userrecaptcha",
                "googlekey": match.group(1),
                "pageurl": url,
            }
            if is_v3:
                params["version"] = "v3"
                params["action"] = "search"

            token = solve_captcha(params)
            resp = self.session.post(url, data={
                "g-recaptcha-response": token,
            })

        # Turnstile
        if "cf-turnstile" in resp.text:
            match = re.search(r'data-sitekey=["\']([^"\']+)', resp.text)
            if match:
                token = solve_captcha({
                    "method": "turnstile",
                    "sitekey": match.group(1),
                    "pageurl": url,
                })
                resp = self.session.post(url, data={
                    "cf-turnstile-response": token,
                })

        return resp.text

    def collect_listings(self, urls):
        """Collect property listings from multiple pages."""
        listings = []
        for url in urls:
            try:
                html = self.fetch(url)
                page_listings = self._parse_listings(html)
                listings.extend(page_listings)
                print(f"  {len(page_listings)} listings from {url}")
                time.sleep(3)
            except Exception as e:
                print(f"  Error: {url} - {e}")
        return listings

    def _parse_listings(self, html):
        """Extract property data from HTML."""
        listings = []

        # Price extraction
        prices = re.findall(r'\$\s*([\d,]+)', html)
        # Address extraction
        addresses = re.findall(
            r'class="address"[^>]*>(.*?)</(?:div|span|p)', html
        )
        # Bed/Bath extraction
        beds = re.findall(r'(\d+)\s*(?:bed|br|bedroom)', html, re.I)
        baths = re.findall(r'(\d+)\s*(?:bath|ba|bathroom)', html, re.I)
        # Sqft extraction
        sqft = re.findall(r'([\d,]+)\s*(?:sq\s*ft|sqft)', html, re.I)

        # Combine available data
        count = max(len(prices), len(addresses), 1)
        for i in range(min(count, 50)):  # Cap at 50 per page
            listing = {
                "price": prices[i] if i < len(prices) else None,
                "address": (
                    addresses[i].strip() if i < len(addresses) else None
                ),
                "beds": beds[i] if i < len(beds) else None,
                "baths": baths[i] if i < len(baths) else None,
                "sqft": sqft[i] if i < len(sqft) else None,
                "collected_at": datetime.utcnow().isoformat(),
            }
            if listing["price"] or listing["address"]:
                listings.append(listing)

        return listings

    def export_csv(self, listings, filename):
        if not listings:
            print("No listings to export")
            return

        keys = ["price", "address", "beds", "baths", "sqft", "collected_at"]
        with open(filename, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=keys)
            writer.writeheader()
            writer.writerows(listings)
        print(f"Exported {len(listings)} listings to {filename}")


# Usage
collector = PropertyCollector()

search_urls = [
    "https://example-realty.com/search?city=austin&type=sale&page=1",
    "https://example-realty.com/search?city=austin&type=sale&page=2",
    "https://example-realty.com/search?city=austin&type=sale&page=3",
]

listings = collector.collect_listings(search_urls)
collector.export_csv(listings, "austin_listings.csv")

Quais dados de imóveis vale a pena coletar

Depois que o CAPTCHA para de interromper o fluxo, o próximo ponto de atenção é decidir o que vale a pena extrair de cada página — nem todo campo tem o mesmo peso para quem vai analisar os dados depois.

Campo Fonte Caso de uso
Preço do anúncio Página do imóvel Avaliação de mercado
Endereço Página do imóvel Geoanálise
Quartos/banheiros/m² Detalhes do imóvel Análise comparável
Dias no mercado Metadados do anúncio Velocidade de venda
Histórico de preço Registro de alterações Análise de tendência
Imposto sobre a propriedade Registros fiscais Análise de investimento
Taxa de condomínio Detalhes do anúncio Análise de custo

Como organizar a coleta diária, semanal e mensal

Depois que o coletor está estável, vale desenhar a rotina em três camadas — diária para captar o que mudou, semanal para enxergar tendência, mensal para consolidar o relatório que vai para o time de investimento:

Daily Collection
    → Property listings (500-1000 per market)
    → Price changes (delta from previous day)
    → New listings vs delisted

Weekly Analysis
    → Median price trends
    → Inventory levels
    → Days-on-market averages
    → Price-per-sqft by neighborhood

Monthly Report
    → Market heat map
    → Competitive pricing analysis
    → Investment opportunity scoring

Boas práticas e limites éticos na coleta de dados imobiliários

Dados de anúncio público — preço, endereço, características do imóvel — geralmente podem ser coletados sem problema. O limite fica claro quando o alvo passa a ser dado pessoal: telefone do corretor, e-mail do proprietário, CPF em documentos anexados. Nesses casos, a LGPD (no Brasil) e o RGPD (em Portugal) tratam esses campos como dados pessoais, e coletá-los sem base legal é o tipo de risco que nenhuma automação de CAPTCHA resolve por você.

Na prática, isso significa três coisas: extrair só o que está publicamente visível no anúncio, respeitar os termos de uso de cada portal antes de rodar o coletor em produção, e manter um intervalo entre requisições (o time.sleep(3) do exemplo acima já cumpre esse papel) para não sobrecarregar o servidor do site. Rodar a coleta com essas três regras é o que diferencia monitoramento de mercado de abuso de infraestrutura alheia.


Perguntas frequentes

Dados de anúncio público (preço, endereço, características do imóvel) geralmente podem ser coletados. Evite extrair dados pessoais de corretores ou proprietários e sempre respeite os termos de uso do site — a LGPD e o RGPD tratam dado pessoal como categoria separada, mesmo quando está dentro de um anúncio público.

Como lidar com portais que têm centenas de páginas de resultados?

Incremente o parâmetro de página na URL (?page=N ou &offset=N, dependendo do portal) e rode a coleta em lotes, com um pequeno intervalo entre requisições. Isso evita que o mesmo IP dispare o CAPTCHA com mais frequência do que o necessário.

Qual plano da CaptchaAI faz sentido para monitorar vários portais todos os dias?

Depende de quantos coletores rodam em paralelo, não de quantas páginas são coletadas por mês — o plano é por thread simultânea, com solves ilimitados em cada uma. Para dois ou três portais por vez, o BASIC (US$ 15/mês, 5 threads) costuma bastar; para monitoramento diário de vários agregadores ao mesmo tempo, o STANDARD (US$ 30/mês, 15 threads) dá mais folga.

O CaptchaAI resolve o Cloudflare Turnstile usado em sites de leilão de imóveis?

Sim. Cloudflare Turnstile é um dos tipos suportados nativamente pela API, junto com Cloudflare Challenge, reCAPTCHA v2/v3 e CAPTCHA de imagem — os mesmos tipos que aparecem na tabela no início deste guia.


Guias relacionados


Mantenha a coleta de dados imobiliários rodando sem travar em CAPTCHA — crie sua chave de API na CaptchaAI e automatize o monitoramento de mercado.

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