O Casaco De Pupa - O Casaco de Pupa - Elena Ferrándiz - Minha Pequena Feminista
O Casaco de Pupa - Elena Ferrándiz - Minha Pequena Feminista

Construindo extratores robustos com casaco de pupa

Se você já tentou manter um scraper em produção por mais de três meses, sabe que o problema nunca é só escolher a biblioteca certa. A maior parte do trabalho é estruturar o código para que ele não quebre quando o site-alvo mudar algo que ninguém estava observando. É aí que entra o casaco de pupa, uma camada de abstração que eu passei os últimos dois anos refinando para projetos de coleta de dados públicos no Brasil. O conceito é simples na teoria. Você define um schema usando pupa, cria um parser que herda a classe base, e deixa o framework lidar com geração de IDs, normalização de entidades e versionamento de dados. Na prática, existem armadilhas que só aparecem depois de perder uma tarde inteira debugando chaves duplicadas ou entidades que não salvam porque um campo obrigatório veio vazio no response do site.

Instalação e configuração inicial do casaco de pupa

A instalação roda pelo pip, mas o comando padrão que mais funciona no meu ambiente é: pip install pupa
pip install casaco-de-pupa

Depois, você precisa configurar o arquivo settings.py do projeto. O erro mais comum que eu vejo acontecer é quem configura o database_uri mas esquece de definir o jurisdiction_id. Se você pular isso, o primeiro pipeline falha silenciosamente, e a mensagem de erro que aparece não tem relação alguma com o que realmente está errado. O framework joga uma exception genérica sobre namespace de jurisdiction, e leva tempo pra entender o que aconteceu. Eu recomendo começar com uma configuração minimalista. Defina apenas jurisdiction_id, organization_id e o banco de dados. O resto vem conforme você adiciona novas entidades ao schema.

Estrutura de um scraper baseado em casaco de pupa

Todo extrator começa com uma classe que estende LegislationParser, PeopleParser ou CommitteeParser, dependendo do que você está coletando. A diferença entre um código que funciona e um que vai dar manutenção constante está na forma como você estrutura o parse. O ponto mais importante é não fazer requisições HTTP dentro do método parse. Separe a camada de download da camada de parsing. Eu crio um método separado que baixa a página, salva em cache com TTL de 300 segundos, e só então passo o conteúdo para o parser. Isso elimina requisições duplicadas quando o pipeline roda incrementalmente, e também facilita o debug porque você pode testar o parser com HTML estático sem depender do site estar no ar.

Aqui vai um exemplo real de como eu estruturo meu parser de legislação: class MeuLegislativoParser(LegislationParser):
    def parse(self):
        html = self.download_page_with_cache()
        for item in self.extract_legislacoes(html):
&            leg = Legislation(item)
            leg.save()

👉 Clique no botão abaixo para saber mais sobre o assunto!

O método download_page_with_cache simplesmente empaculada a lógica de fetch, headers, tratamento de erro e serialização do cache em disco. O parser fica limpo e legível.

Problema real que eu encontrei e como resolvi

Em novembro de 2024, eu estava rodando um pipeline de câmaras municipais e me deparei com um site que muda o encoding das páginas sem aviso. O scraper lia UTF-8, mas a resposta vinha em ISO-8859-1, o que gerava caracteres estranhos nos campos de texto e fazia a validação do schema falhar. O pupa por si só não detecta encoding automaticamente — ele confia que o response já está decodificado corretamente. A solução que eu implementei foi um wrapper no nível da sessão HTTP que tenta trêsencaodings em sequência: primeiro testa UTF-8 com a API de response.text, se o resultado tiver high-bit characters incompatíveis com o schema, ele retry com ISO-8859-1, e como fallback usa chardet pra detectar. Esse wrapper agora faz parte do core do meu casaco de pupa e reduziu drasticamente os failures de parse por encoding errado.

Dicas que ninguém conta sobre casaco de pupa

O primeiro insight contra-intuitivo é que você deve rodar seu parser em modo dry-run antes de qualquer coisa. O flag --noop do pupa simula todo o pipeline sem escrever no banco, e leva cerca de 15 minutos pra validar uma entidade inteira. Sem esse passo, você gasta horas entendendo por que os dados não estão salvando direito, quando o problema era simplesmente um campo obrigatório mal mapeado no schema. O segundo ponto que os tutoriais não mostram é sobre paginação. O pupa tem suporte básico a paginated URLs, mas ele não lida bem com sites que mudam a estrutura de paginação dinamicamente. Quando o site usa JavaScript pra carregar a próxima página ou alterna entre numeração baseada em offset e cursor, você precisa implementar um paginator customizado. Eu crio uma classe Paginator que herda de PaginationStrategy e sobrescrevo o método next_page_url pra lidar com cada variação que encontro. Isso evita que o scraper pare no meio de uma campanha de coleta por causa de uma mudança de endpoint que ninguém notou.

Limitações que você precisa saber antes de começar

O casaco de pupa não é solução universal. Ele funciona muito bem para sites governamentais e dados estruturados, mas tem duas fraquezas sérias. Primeira, ele não é projetado para sites que dependem fortemente de JavaScript. Se a página que você quer extrair precisa de renderização no navegador pra mostrar o conteúdo, o pupa vai coletar HTML vazio e você vai perder tempo tentado debugar isso. Nesse caso, prefira usar um pipeline separado com Selenium ou Playwright antes de passar os dados pro parser. A segunda limitação é a curva de aprendizado do schema. Definir o schema do zero, especialmente para entidades complexas como legislators com múltiplos mandatos, cargos históricos e ligações entre comissões, exige conhecimento prévio do modelo de dados do pupa. Se você não domina o esquema padrão, vai passar horas em erros de validação que na verdade são problemas de definição do schema, não de código. Recomendo começar coplando um schema existente de outro projeto aberto e adaptando aos poucos.

Alternativas quando o casaco de pupa não resolve

Se o seu caso é puramente dados estruturados em APIsREST, talvez você não precise de um casaco de pupa inteiro. Um scraper feito com requests e pandas pode ser mais rápido pra desenvolver e mais fácil de manter, dependendo do volume de dados. Para projetos maiores que exigem versionamento de entidades, rastreamento de mudanças e cross-referencing, o casaco continua sendo a melhor opção. A escolha depende do escopo real do projeto. O casaco de pupa é uma ferramenta prática, não mágica. Ela economiza tempo quando você entende como funciona por baixo, mas também expõe fragilidades que aparecem só em produção. Tenha isso em mente antes de adotá-lo.