Você quer descobrir em quais países um concorrente B2B está anunciando neste trimestre, há quanto tempo suas campanhas estão no ar, qual é aproximadamente o volume de impressões e como ele segmenta o público. Esses dados estão na biblioteca pública de anúncios da plataforma, que muitas empresas precisam publicar para cumprir regras de transparência publicitária. Só que, ao abrir o DevTools e investigar a página, não aparece nenhuma API JSON limpa: há apenas uma página inteira de HTML renderizado no servidor. Você cria um parser, ele funciona e os dados chegam. Três semanas depois, a plataforma lança um redesign. O parser deixa de extrair todos os campos, não lança erro algum e devolve silenciosamente uma coleção de valores vazios. Por pouco, você não toma uma decisão com base em dados inexistentes.
Este artigo trata de como manter esse tipo de parser funcionando depois de um redesign — e de onde um modelo realmente ajuda na manutenção. Antes, vale delimitar os dados: tudo aqui vem das bibliotecas públicas de anúncios e creative centers de cada plataforma, acessados normalmente com sua própria conta. Sem assinaturas, sem contornar barreiras e sem endpoints não públicos. Como veremos, esse limite está explicitamente embutido no design do parser.
Por que inteligência de anúncios B2B acaba sendo extraída de HTML
Mesmo entre bibliotecas de anúncios, existem três formas de expor dados. Algumas bibliotecas voltadas ao consumidor — como a do Meta, por exemplo — oferecem busca estruturada e JSON. Outras exigem uma sessão até para pesquisar. Já a maior parte das bibliotecas de anúncios de plataformas B2B entrega apenas HTML renderizado no servidor, sem endpoint JSON algum. A diferença existe porque essas bibliotecas são um recurso de compliance, não uma API de produto.
Elas existem para atender exigências de transparência publicitária, não para serem consumidas por desenvolvedores. Não há número de versão, changelog nem promessa de compatibilidade retroativa. A página foi feita para pessoas: o servidor a renderiza e entrega HTML. Na prática, a única “API” disponível é a própria página web.
Isso torna tudo intrinsecamente frágil. Você depende de detalhes de implementação da interface de outra empresa; ela pode alterá-los quando quiser e não precisa avisar ninguém. Quando uma API JSON muda um campo, ao menos ocorreu uma mudança explícita. Já um redesign de HTML, para quem mantém a plataforma, é apenas mais uma iteração normal de frontend. Não há como evitar esse cenário. O caminho é projetar o parser para falhar de forma segura e ser simples de corrigir após um redesign, em vez de retornar campos vazios sem levantar qualquer sinal.
Um parser em streaming reduz mais a complexidade mental do que o código
O impulso inicial é usar lxml ou BeautifulSoup, montar a árvore DOM inteira da página e encadear .find() até chegar aos dados. Funciona, mas não é a melhor abordagem para esse alvo. A árvore DOM é um produto intermediário de como o navegador renderiza a página — a MDN define o DOM como a análise do documento em uma árvore de nós acessada por scripts segundo sua estrutura. Aqui, porém, você só precisa extrair alguns campos. Não precisa construir essa árvore nem se prender à estrutura dela.
Minha solução foi criar uma subclasse de HTMLParser, da biblioteca padrão do Python, com pouco mais de oitocentas linhas — 881, para ser exato. É um parser puramente em streaming: alguns callbacks de starttag, data e endtag alimentam uma máquina de estados. Ela acumula dados durante a leitura e, ao encontrar o limite de um card, emite um registro, limpa o estado e continua. Em nenhum momento constrói uma árvore DOM completa.
O ganho do streaming é bem concreto. Primeiro, memória: o HTML de uma página de detalhes pode facilmente ter dezenas ou centenas de KB. Uma árvore DOM mantém a estrutura inteira da página na memória; o streaming guarda apenas o estado de leitura, como “onde estou” e “em que ponto deste card cheguei”. O benefício mais importante, porém, é a redução de carga mental. No instante em que você escreve .find('div').find('div')[2], amarra a extração à posição hierárquica no DOM. E a hierarquia é exatamente o que um redesign mais altera: basta envolver um elemento em mais um container ou separar um wrapper para todas as posições mudarem. Uma máquina de estados obriga você a perguntar apenas: “o que estou lendo, semanticamente, é o começo de um card, um número de impressões ou uma tag de segmentação?”. A posição muda; a semântica, não.
Três escolhas de design para sobreviver a um redesign
Na prática, são três decisões — todas aprendidas depois de redesigns.
Primeira: use âncoras semânticas, não posições. A máquina de estados avança a partir de sinais semânticos: o texto do rótulo de um campo, palavras legíveis como “total impressions” e “run dates”, marcadores com papel definido ou o limite semântico de um bloco. Nunca porque algo é “o terceiro nó a partir do topo”. O teste cabe em uma linha: se moverem esse elemento ou inserirem mais uma camada de wrapper, a extração continua funcionando? Se sim, a âncora é válida. Âncoras posicionais quebram no primeiro redesign; âncoras semânticas sobrevivem à maioria das mudanças puramente visuais.
Segunda: quando um campo faltar, degrade em vez de lançar uma exceção. Antes de acumular dados de cada card, inicialize-o com um template no qual todos os campos tenham valores vazios por padrão: string vazia para texto, None para números e array vazio para listas. Preencha o que for possível e deixe em branco o que não for. A falha na extração de um único campo não pode inutilizar o card inteiro, muito menos a página toda. Se um anúncio não tiver o texto do CTA, você ainda quer suas impressões e os países segmentados. Permitir que um campo secundário ausente destrua a inteligência que a página ainda poderia fornecer é um péssimo design.
Terceira: sinalize a completude dos resultados para que “vazio” e “quebrado” não pareçam iguais. Essa é a decisão mais fácil de ignorar — e a mais cara. “Foram extraídos 0 anúncios” pode significar duas coisas completamente distintas: realmente não há anúncios, porque aquele anunciante não veiculou nada neste trimestre; ou a estrutura da página mudou e nenhuma âncora encontrou correspondência, ou seja, o parser quebrou. Esses casos precisam ser distinguíveis no valor retornado. A forma de fazer isso é incluir evidências de confirmação: a contagem de cards capturada, o total declarado pela própria página e o estado da paginação, todos retornados juntos. Assim, uma combinação como “contagem de cards 0, mas os metadados da página indicam que deveria haver um lote e não há marcador de próxima página” pode ser identificada como uma mudança estrutural, não como ausência real de anúncios. Em vez de devolver uma lista vazia impassivelmente, o parser lança um erro claro.
O limite de dados citado antes também aparece nessa camada. O parser verifica se foi redirecionado para uma página de login e, assim que identifica que o título pertence a uma tela de login ou cadastro, interrompe a execução com erro em vez de continuar processando. Ele lida apenas com páginas públicas que você consegue visualizar normalmente com sua própria conta, para diante de uma barreira de login e nunca tenta atravessá-la.
O valor está nas dimensões de filtro, não nos campos isolados
Até aqui, pode parecer que o objetivo é extrair perfeitamente todos os campos de cada anúncio. Não é. Os campos de um anúncio isolado têm pouco valor. O que realmente importa são as dimensões pelas quais você consegue segmentar o conjunto de anúncios. Os próprios filtros de busca da biblioteca de anúncios já formam uma lista pronta de dimensões de inteligência. Ao transformá-los em parâmetros de consulta programáveis, você deixa de obter “um anúncio” e passa a obter “o recorte de lançamento de um concorrente”:
País: em quais mercados a empresa anuncia — e em quais não anuncia. Quando uma companhia B2B começa repentinamente a anunciar em determinado país, isso muitas vezes revela uma movimentação de expansão antes de qualquer sinal no próprio site dela.
Janela de veiculação (datas de início e fim): por quanto tempo uma peça ficou ativa. Um anúncio de longa duração é um sinal forte, porque ninguém continua pagando por uma peça que não converte. A duração da campanha é, por si só, o resultado de um teste A/B validado com dinheiro real pelo outro lado.
Faixa de impressões (mínimo/máximo): uma aproximação grosseira do investimento. O número absoluto não é preciso, mas basta para ordenar quais são as compras de mídia prioritárias.
Facetas de segmentação: quais públicos estão incluídos ou excluídos. É a inteligência de audiência mais direta: quem o concorrente acredita que comprará seu produto.
Extrair campos é o meio; essas dimensões são o fim. Ao escrever o parser, pense de trás para frente: para consultar e ordenar por essas dimensões, qual é o conjunto mínimo de campos que preciso extrair com confiabilidade? Os demais campos sofisticados podem ficar de fora sem comprometer a inteligência.
Depois do redesign, use o modelo para comparar o HTML antigo com o novo
Um parser desse tipo certamente vai quebrar em algum redesign. É nesse momento que um modelo realmente tem lugar — e não dentro do processo de parsing. Parsing é trabalho determinístico, executado por uma máquina de estados codificada, e não deveria receber chamadas de modelo no meio do fluxo (não entregue trabalho determinístico a um modelo; é o mesmo princípio aplicado à engenharia reversa). O papel do modelo é manutenção.
O fluxo é simples: abra a biblioteca de anúncios com sua própria conta, guarde uma cópia do HTML salvo antes do redesign e outra do HTML novo. Entregue ambos ao modelo, junto da lista de campos que o parser atual extrai, e peça que ele aponte, a partir do diff entre as versões, quais âncoras semânticas mudaram, qual deveria ser a nova âncora e quais são as poucas linhas necessárias para o ajuste mínimo. Esse é um caso clássico para uma camada de raciocínio: o modelo precisa encontrar se a semântica correspondente continua existindo na nova estrutura e propor um plano corrigível, não apenas repetir que “a estrutura foi ajustada”. Cada etapa exige algo diferente do modelo; usar um único nível para tudo cobra preço em custo ou precisão:
Etapa | Capacidade necessária | Escolha | model id |
|---|---|---|---|
Ler uma página inteira de HTML SSR e alinhar as estruturas antiga e nova | Contexto longo, capaz de absorver de uma vez uma página de detalhes com dezenas ou centenas de KB | Kimi K3 |
|
Após um redesign, ler o diff antigo-novo, avaliar onde a âncora mudou e indicar o ajuste mínimo | Raciocínio forte, que explica com base na estrutura em vez de apenas descrever o fenômeno | Claude Opus 5 |
|
Normalizar e classificar em massa os cards de centenas de anunciantes como inteligência | Baixo custo, com centenas ou milhares de chamadas em alta concorrência | Claude Sonnet 5 |
|
Atribuir a causa de diferenças quando a comparação de fixtures falha | Raciocínio intermediário, explicando a divergência entre “campo esperado e extração real” | GPT-5.6 Sol |
|
A segunda camada é o núcleo do processo e a única etapa em que trocar de modelo altera visivelmente o resultado. Para saber se a camada de raciocínio vale a pena, não aceite minha palavra: teste. O protocolo é curto:
Salve o HTML de uma página antes do redesign e outro depois dele. Abra a biblioteca de anúncios com sua própria conta e salve as páginas.
Envie os dois HTMLs e a lista de campos do parser atual para
claude-opus-5egpt-5.6-sol.Observe uma única coisa: o ajuste aponta uma mudança concreta de âncora semântica — “antes, a âncora era o rótulo ‘total impressions’; o papel do container desse rótulo mudou na nova versão, então use X” — ou responde vagamente que “a estrutura foi ajustada e recomenda-se readaptar”?
Você consegue aplicar diretamente a primeira resposta; a segunda não diz nada de útil. Essa diferença é o seu critério de escolha e determina quantas rodadas de tentativa às cegas serão necessárias no dia do redesign.
Uma única rodada já deixa a diferença clara, de forma mais direta do que qualquer benchmark.
O maior problema é o custo de trocar de modelo
As quatro camadas vêm de três fornecedores, três SDKs, três esquemas de autenticação e três formatos de erro. Para conectar três clientes em etapas diferentes, a maioria das pessoas faz as contas, decide que não vale o esforço por “uma correção ocasional no parser mais limpeza de dados em massa” e acaba usando o mesmo modelo para tudo. Com isso, emprega no conserto do redesign uma camada que só responde “recomendo readaptar”, perde tempo e nem sabe por quê.
AIReiter simplifica essa camada: uma chave, uma interface compatível com OpenAI e todas as quatro categorias por trás dela. Para trocar de modelo, basta alterar o campo model no corpo da requisição.
# Correção de redesign: camada de raciocínio
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<diff de HTML antigo-novo + lista atual de campos; pergunte onde a âncora mudou>"}]
}'
# Normalização de inteligência em massa: troque o campo model e mantenha o resto
# "model": "claude-sonnet-5"
# Contexto longo para ler a página inteira: "model": "kimi-k3"
# Atribuição de diferenças: "model": "gpt-5.6-sol"
Se você já usa o SDK da OpenAI, aponte base_url para https://aireiter.com/api/v1 e não altere mais nada. No SDK da Anthropic, use POST /api/v1/messages com a mesma chave.
Em preço, os modelos Claude têm 30% de desconto sobre a tabela, os modelos GPT custam metade, e o Kimi K3 pode ser chamado com a mesma chave. Nesse fluxo, o desconto incide justamente sobre o maior custo. O conserto de redesign não é o maior gasto: trata-se de uma chamada esporádica, de baixa frequência e alto valor. O custo principal está na normalização de inteligência em massa: você acompanha vinte anunciantes concorrentes, com dezenas a centenas de cards cada, e envia todos ao modelo para identificar promessa, audiência e janela de veiculação. É a parte com mais chamadas e roda no Claude Sonnet, com 30% de desconto. A entrada de contexto longo para ler a página inteira e alinhar estruturas vem logo depois em densidade. As duas partes mais caras estão diretamente cobertas pelo desconto.
Teste sem se cadastrar: entregue manualmente um par de HTML antes e depois para os dois modelos e compare qual deles realmente consegue indicar onde a âncora mudou. Depois, decida se vale integrar.
Depois da correção e da normalização dos cards brutos em massa, o próximo passo dessa inteligência é alimentar o pipeline criativo para tomada de decisão e geração de ativos. Esse é o trabalho de todo o fluxo de palavra-chave até anúncio finalizado; este artigo cobre sua etapa mais inicial: coletar dados públicos de forma confiável.
Quem decide se os campos estão certos é a fixture
A correção sugerida pelo modelo continua sendo apenas uma sugestão até ser validada. Ele pode dizer que “a âncora deve mudar para X”, e isso pode parecer razoável, mas não há garantia de que X se sustente em todos os cards. Anúncios B2B podem trazer imagem e texto, apenas texto, carrossel, landing page ou nenhuma landing page. As duas amostras vistas pelo modelo talvez não cubram todas essas variações.
O que impede esse problema é o mesmo mecanismo que bloqueia alucinações de modelo em engenharia reversa: comparação com vetores fixos. Armazene um conjunto de entradas conhecidas — alguns HTMLs reais de páginas salvas — e suas saídas corretas conhecidas — resultados de campos que você conferiu manualmente uma vez — como uma fixture versionada no repositório. Sempre que alterar o parser, seja por iniciativa própria ou seguindo uma sugestão do modelo, execute novamente esse lote de fixtures e compare campo a campo. É a única forma de descobrir rapidamente, depois de um redesign upstream, se “apliquei a sugestão de maneira errada” ou “a página mudou outra vez”. Se tudo passar, a alteração está correta. Se poucos casos falharem, os campos desses casos mostram diretamente em qual camada está o problema. O modelo gera sugestões de reparo; a fixture determina se elas estão certas. As duas coisas não devem ser confundidas. O método completo dessa comparação diferencial está detalhado no artigo sobre testes diferenciais, e o parser da biblioteca de anúncios usa a mesma barreira. Sem essa camada, você trata a confiança do modelo como se fosse correção. É fácil “aplicar a sugestão, não ver erro, publicar e descobrir três dias depois que os dados de um país ficaram vazios o tempo todo”.
Conclusão
A inteligência de anúncios B2B acaba sendo extraída de HTML porque a biblioteca de anúncios é um artefato de compliance, não uma API de produto. Não existe contrato de API, e redesigns podem acontecer a qualquer momento. A resistência a mudanças depende de três escolhas: ancorar na semântica, não na posição; degradar diante de campos ausentes, em vez de lançar erro; e sinalizar a completude dos resultados para diferenciar “vazio” de “quebrado”. O que realmente importa não são os campos de um anúncio isolado, mas as dimensões de país, janela de veiculação, faixa de impressões e facetas de segmentação que permitem recortar o lançamento de um concorrente. O lugar do modelo é específico: não no parsing, que deve ser uma máquina de estados determinística, mas na manutenção. Comparar HTML antigo e novo após um redesign para sugerir reparos é onde a camada de raciocínio ajuda; transformar cards em inteligência em massa é trabalho da camada barata e de alta concorrência. Mas a decisão final sobre a correção dos campos sempre é da fixture. Conecte essas camadas a uma interface unificada e a única fricção restante será trocar o campo model — algo resolvido ao escolher seu modelo.