Portfólio Blog Sobre

Catalog Scraper

Lê catálogos em PDF e em páginas web, extrai os produtos e converte os preços para uma unidade comum, deixando a comparação simples. Reconhece a estrutura do documento por regras fixas e só recorre a um modelo de IA quando o formato é irregular.

React 19 TypeScript Hono Drizzle ORM Postgres Zod Playwright Cloudflare Workers Neon Groq
Catalog Scraper

O Catalog Scraper resolve um problema simples de enunciar e trabalhoso de fazer na mão: comparar preços de produtos que chegam em catálogos de formatos diferentes.

Um fornecedor manda um PDF, outro tem uma página web, um terceiro envia uma lista de preços. Cada um descreve os mesmos produtos do seu jeito, então abrir tudo e conferir item a item é lento e fácil de errar.

O sistema automatiza esse trabalho. Você registra cada fonte, ele lê o documento, extrai os produtos e converte todos os preços para uma mesma unidade de medida (R$/kg, R$/L, R$/un). Com tudo na mesma medida, o melhor preço aparece no topo.

Para ler os documentos, o sistema aplica primeiro regras fixas que reconhecem a estrutura. Só recorre a um modelo de IA quando o formato é irregular.

Conceitos fundamentais

ConceitoO que é
ScrapingExtração automática de dados de documentos e páginas web. Aqui, lê catálogos em PDF e em HTML para coletar produtos e preços.
Pipeline de mineraçãoSequência de etapas que transforma um catálogo bruto em produtos estruturados: captura → extração → normalização → ranqueamento.
Extração determinísticaLeitura por regras fixas que reconhecem tabelas e blocos de dados (JSON, lista de preços) sem adivinhação. Roda primeiro porque é previsível e gratuita.
Fallback de IARecurso secundário acionado só quando as regras fixas não acham estrutura. Usa o nível gratuito da Groq (provedor de inferência de modelos de linguagem); sem chave, ele apenas não opera e nada é cobrado.
Unidade baseUnidade comum para comparar preços de embalagens diferentes. Uma caixa de 25 kg e uma de 5 kg viram ambas R$/kg, então o melhor valor fica visível.
Faixa de descontoTabela de preço por degraus de quantidade. Informada uma quantidade alvo, o ranking recalcula o valor contra a faixa aplicável.
Edge Deploy (implantação em borda)Código executado em servidores próximos do usuário. Aqui, dois Cloudflare Workers (um para a interface, um para a API).
build (construção)Etapa que monta os arquivos finais a partir do código-fonte, antes de publicar.
token (credencial)Texto assinado que o usuário recebe ao entrar e envia de volta a cada chamada, provando quem é.

Stack

Backend
Tecnologia Papel
Hono Framework web minimalista; roteamento da API e middleware de autenticação
Node.js 24 & TypeScript Runtime local e tipagem de ponta a ponta
Drizzle ORM + Postgres 16 Camada de dados tipada; ORM (mapeamento objeto-relacional) sobre Postgres
unpdf + Playwright Ingestão: leitura de texto do PDF e captura de páginas web
Groq SDK Fallback de extração no nível gratuito, acionado só quando falta estrutura
jose (JWT) Sessão por token assinado; credenciais de fonte cifradas em repouso
Frontend
Tecnologia Papel
React 19 + Vite SPA (aplicação de página única) da interface de compras
TanStack Router & Table Roteamento tipado e a tabela de produtos com filtro e ordenação
Tailwind CSS v4 + shadcn/ui Estilização utilitária e componentes base de interface
Zustand Estado local da sessão e dos filtros
exceljs + jspdf Exportação da comparação para Excel e PDF

Arquitetura e engenharia

O centro do projeto é o pipeline, a sequência de etapas que vai do documento bruto ao produto pronto para comparar.

O status de cada fonte registrada avança por três marcos (pendente → capturado → extraído), e os produtos só entram na comparação depois de convertidos para a unidade base.

Padrão Descrição Detalhes
Pipeline em etapas Captura → extração → normalização → ranqueamento Cada fonte passa pelas mesmas etapas; o status avança pendente → capturado → extraído
Leitura por regras antes da IA Heurísticas de tabela e JSON tentam primeiro; IA é o último recurso deterministic-extractor lê estrutura conhecida; groq-extractor só atua no fallback, evitando custo e variação
Preço por unidade comum Embalagens diferentes reduzidas a R$/kg, R$/L ou R$/un base-unit + normalize-price convertem cada item para a mesma medida antes de comparar
Ranking por melhor valor Produtos ordenados pelo menor preço na unidade base rank-products ordena com a quantidade informada aplicada às faixas de desconto
Contratos compartilhados Mesmos schemas Zod validam API e interface packages/shared é importado pelos dois apps; o formato de produto, fonte e usuário tem uma fonte única
Dois Workers no mesmo domínio Interface e API como Cloudflare Workers separados + Postgres na Neon Mesmo domínio raiz mantém o cookie de sessão sem configuração extra; a API abre um pool de conexão por requisição

Regras primeiro, IA por último

A ordem dessas duas estratégias importa, tanto pelo custo quanto pela confiança no resultado.

Regras fixas que reconhecem uma tabela de preços ou um bloco de dados são previsíveis, gratuitas e repetíveis. O mesmo PDF gera sempre a mesma saída, o que facilita testar e confiar no que sai.

O modelo de IA só entra quando o formato é irregular demais para as regras, e mesmo assim na camada gratuita da Groq.

Sem uma chave de acesso configurada, esse recurso apenas avisa que não conseguiu processar. O sistema continua funcionando, e sem chamada ao provedor não há cobrança.

Mesma unidade para poder comparar

Os dados brutos chegam em medidas que não se comparam diretamente: um item por caixa de 25 kg, outro por pacote de 5 kg, outro por litro.

Comparar o preço de etiqueta não ajuda, porque o tamanho da embalagem muda. Uma caixa mais cara pode sair mais barata por quilo.

A etapa de normalização (base-unit + normalize-price) converte cada produto para a unidade comum e, quando existe desconto por quantidade, aplica a faixa da quantidade que você pretende levar.

No fim, sobra uma única coluna de preço por unidade, e ordenar por ela já responde qual produto sai mais barato. A tabela na interface filtra por categoria e exporta a comparação para Excel ou PDF.

Um só contrato de dados para os dois lados

A interface e a API são programas separados dentro do mesmo repositório, mas precisam concordar sobre o formato dos dados que trocam.

Esse formato fica num pacote compartilhado (packages/shared), um conjunto de schemas Zod (regras de validação) importado pelos dois lados.

Produto, fonte e usuário têm uma definição única. A API valida o que entra e a interface valida o que recebe usando exatamente a mesma regra, então um campo só muda num lugar.

Isso evita o bug clássico em que o backend muda um campo e o frontend continua esperando o formato antigo, sem ninguém perceber.

Deploy na borda, segredo só no backend

A interface e a API rodam como dois Cloudflare Workers (programas distribuídos em servidores próximos do usuário), com o banco Postgres na Neon (serviço serverless, sem servidor para administrar).

A interface é estática e não carrega segredo nenhum. A conexão com o banco e as chaves de assinatura ficam só na API.

Como os dois Workers ficam sob o mesmo domínio, o cookie de sessão é enviado para os dois sem configuração extra de CORS (a regra que controla quais origens podem se comunicar).

Um git push na branch principal dispara o build e a publicação automática dos dois. A atualização do banco roda antes de publicar e é idempotente: rodar de novo não causa dano, porque o processo pula as migrações já aplicadas.