Todo mundo constrói o scraper de agregador primeiro. Cars.com, CarGurus, AutoTrader: três sites, um parser para cada um e, até o final da semana, você tem um feed que se parece com o mercado de carros usados.
Aí alguém pergunta onde está o restante.
O Desafio
A NADA contabiliza 16.972 concessionárias de veículos leves franqueadas nos Estados Unidos em seu relatório de meados de 2025, e isso sem contar as lojas independentes, que ninguém quantifica de forma consistente. Relatórios secundários do mesmo dado da NADA variam entre 15.720 e 16.990, o que dá uma ideia de como esse mercado é medido.
Cada uma dessas concessionárias opera seu próprio site. O inventário listado nele é o estoque real da loja, com o preço de hoje, dias antes de qualquer item ser publicado em um agregador terceirizado. Se você calcula preços de veículos usados, projeta valores residuais ou vende ferramentas de inteligência competitiva para revendedores, o site da concessionária é a fonte de dados que você procura. Os agregadores são apenas uma cópia defasada e filtrada dessa informação.
Então as equipes vão atrás dos sites de concessionárias e descobrem três coisas em sequência.
Eles não são únicos. Quase todos rodam sobre um conjunto pequeno de plataformas de sites para revendedores: Dealer.com, DealerOn, Dealer Inspire, CDK, Reynolds, Sincro, Lotlinx (a lista exata varia de acordo com a fonte). Quem comercializa esses dados desenvolve um parser por plataforma, não um por concessionária. O dealer website inventory scraper da Apify deixa isso explícito: primeiro detecte a plataforma, depois faça a extração. Um punhado de templates cobre dezenas de milhares de lojas. Essa é a parte fácil do processo.
Geralmente o preço não está no HTML que você acabou de buscar. Essas plataformas renderizam o bloco de preços no lado do cliente, e estimativas de pagamento e incentivos costumam chegar em uma segunda chamada logo depois. Um simples fetch HTTP traz ano, marca, modelo, quilometragem e VIN. O campo de preço retorna vazio.
E depois vem a parte que arruina datasets silenciosamente: no site de uma concessionária, uma falha parece exatamente com um dado real. Um serviço de detecção de bots responde a uma requisição suspeita com HTTP 200 e uma página intersticial. Um bloco de preço que nunca renderizou deixa "Call for Price" no DOM, algo que as próprias lojas costumam escrever de propósito. Ambas as linhas chegam ao seu data warehouse parecendo igualmente válidas.
Já cobrimos esse padrão em outro segmento, onde um bloqueio se parece com um ponto de dado. O setor automotivo é o cenário mais complexo, porque "sem preço" é um estado de negócio legítimo e não uma anomalia evidente.
O Que Muda Quando Você Divide por Custo
Os pipelines que se mantêm estáveis não são organizados por site. Eles são organizados pelo custo de cada request.
O request caro é o primeiro feito contra a concessionária: aquele que precisa executar um navegador headless, contornar qualquer barreira imposta pela plataforma da loja e retornar com uma sessão. Tudo o que vem depois é uma chamada HTTP barata reutilizando o que a primeira requisição obteve.
import requests
FOURA = "https://api.foura.ai/api"
AUTH = {"Authorization": "Bearer pk_live_..."}
# First page of a rooftop: render it, clear the defense, keep the session.
first = requests.post(f"{FOURA}/browser", headers=AUTH, json={
"url": "https://example-motors.com/used-inventory/index.htm",
"unblocker": True,
"timeout_ms": 45000,
}).json()
listings = first["body"]
jar = "; ".join(f'{c["name"]}={c["value"]}' for c in first["cookies"])
agent = first["userAgent"]
exit_id = first["proxy"] # opaque proxy ID, send it back to stay on the same exit
Em seguida, percorra o restante do estoque dessa concessionária sem pagar por um navegador novamente:
page = requests.post(f"{FOURA}/single", headers=AUTH, json={
"method": "GET",
"url": "https://example-motors.com/used-inventory/index.htm?start=20",
"proxy": exit_id,
"headers": [["Cookie", jar], ["User-Agent", agent]],
"validate": {
"status": {"accept": [200]},
"data": {
"accept": ["vehicle-card"],
"fail": ["Just a moment", "Access Denied"]
}
}
}).json()
Dois detalhes aí têm mais peso do que aparentam ter.
A sessão viaja como uma unidade única. Um cookie de clearance fica vinculado à saída (exit) que o obteve e ao User-Agent sob o qual foi gerado. Se você reproduzir isso de outro lugar, ou sob um User-Agent diferente, o site reinicia você no challenge. É por isso que o jar, a string de agent e o ID do proxy se movem juntos. Esse é o detalhe que mais vemos as pessoas errarem: elas mantêm o cookie, descartam a saída e depois se perguntam por que o caminho mais barato deixou de ser barato.
O bloco validate é o que elimina o problema das falhas silenciosas. Trata-se da request definindo a aparência real de uma página: aceitar um marcador que só existe depois que a grade de listagem foi renderizada, falhar nas strings de páginas intermediárias. Uma response que não atende a essas regras não é apenas uma linha com preço nulo. É uma falha, classificada como tal, e não é contabilizada como sucesso. No setor automotivo, escreva tanto o marcador positivo quanto a lista negativa, porque "Ligue para o Preço" é genuinamente ambíguo, enquanto "o card do veículo nunca foi renderizado" nunca é.
Quando você ainda não sabe de qual caminho uma determinada plataforma precisa, o Auto descobre em uma única chamada e retorna a sessão que funcionou. Trate isso como reconhecimento, não como a rota de produção. Assim que você souber que uma plataforma precisa do navegador e as vizinhas não, fixe cada uma na engine direta e pare de pagar um orquestrador para redescobrir a mesma resposta todas as noites.
Resultados
Faça os cálculos para um trabalho de médio porte (cenário ilustrativo baseado em benchmarks do setor, não um cliente específico): 4.000 concessionárias, cerca de 180 veículos seminovos cada, atualizados todas as noites.
- 4.000 páginas renderizadas em vez de 720.000. Uma renderização por concessionária abre a sessão; as outras 716.000 páginas seguem pelo caminho barato na mesma sessão. Essa proporção, e não o parser, é o que define se a cobertura noturna é viável financeiramente.
- Dois tipos de preços ausentes, em duas tabelas diferentes. Com as regras de validate ativas, "a concessionária não publica o preço" e "nunca recebemos a página" deixam de compartilhar o mesmo formato de linha. Seu modelo passa a ver apenas o primeiro caso.
- Um parser por plataforma, não por concessionária. Detecte a plataforma a partir da response e envie o HTML para o parser responsável por ela. Uma nova concessionária em uma plataforma já mapeada tem custo zero de integração.
- Uma concessionária que muda de plataforma falha de forma visível. A detecção de plataforma falha, a linha nunca é gravada e alguém recebe um ticket em vez de passar seis semanas com preços incorretos silenciosamente.
Onde isso se torna menos simples: os grandes grupos de concessionárias utilizam cada vez mais sites personalizados fora de plataformas padrão, e esses ainda exigem parsers feitos sob medida, com toda a manutenção que isso implica. A atualização do estoque das concessionárias também não é uniforme. Algumas plataformas aplicam um cache agressivo em suas páginas de estoque, de modo que o "preço de hoje" pode ter um dia de atraso, independentemente da frequência da sua coleta. Se o seu modelo considerar o timestamp de cada concessionária como igualmente atualizado em tempo real, ele estará errado de uma forma que nenhuma infraestrutura de coleta conseguirá corrigir.
Principais conclusões
A parte difícil dos dados automotivos nunca foram os três agregadores que todos usam como referência. São os dezessete mil sites pequenos que nunca justificaram um scraper dedicado individualmente, mas que juntos têm muito valor.
Esse padrão aparece muito além dos carros. Farmácias, revendedores de equipamentos, supermercados regionais, qualquer tipo de franquia: a cauda longa só parece cara enquanto você trata cada membro dela como um site exclusivo. Geralmente não é. Acerte a primeira request, torne a sessão reutilizável, e o que sobra deixa de ser um problema de coleta e vira um problema de parsing, que é de longe o mais barato dos dois.