Portfólio GD GitHub Vamos conversarConversar
← Todos os projetos

Engenharia de dados

Coletores de preços

Robôs em Python que leem a API das lojas virtuais e trazem todos os preços, com o código de barras conferido.

PythonWeb scrapingAirflowPostgreSQLpytest
Em produção · projeto corporativoCriado e implementado por mim

O desafio

Onde tudo começou.

Acompanhar os preços do mercado significa ler dezenas de lojas virtuais, e cada rede publica os dados de um jeito. Um código de barras errado faz a comparação cair no produto errado.

A solução

Da ideia à implementação.

Criei um coletor por rede, apoiado em motores compartilhados para cada plataforma de e-commerce. Todos entregam o mesmo contrato: EAN válido, loja, origem, horário e cada tipo de preço em seu próprio campo.

Como funciona

Por dentro do fluxo.

O coletor consulta a API de catálogo da loja, categoria por categoria, valida o GTIN pelo dígito verificador e separa preço regular, promocional, de atacado, de clube e de cartão. Itens sem EAN válido passam por uma recuperação segura antes de serem descartados; nada é preenchido por aproximação. No Airflow, cada loja tem a sua DAG.

  1. 1Loja virtual
  2. 2Coletor da rede
  3. 3Validação de EAN
  4. 4Preços por tipo
  5. 5Lote completo ou nada
  6. 6Banco de dados

Por dentro do código

Como um coletor funciona, no código.

Trechos simplificados do coletor real. Nomes de rede e endereços foram trocados.

1Cada rede é uma classe de dez linhas.scrapers/rede_exemplo.py

O arquivo de uma rede só diz onde fica a loja. A coleta vem de um motor que já sabe conversar com aquela plataforma de e-commerce, então uma rede nova na mesma plataforma entra sem reescrever nada.

scrapers/rede_exemplo.py
from scrapers.shared.vtex_checkout import VtexCheckoutScraper


class RedeExemploScraper(VtexCheckoutScraper):
    MARKET_INFO = {
        "slug": "rede_exemplo",
        "platform": "VTEX",
        "default_domain": "https://www.loja-exemplo.com.br",
    }
    SETTING_PREFIX = "REDE_EXEMPLO"
2O catálogo inteiro, categoria por categoria.scrapers/shared/vtex_checkout.py

O motor pede a árvore de categorias à API da loja e percorre cada uma de 50 em 50 produtos. Se a loja responde com erro, ele espera, tenta de novo e divide a faixa ao meio até passar.

scrapers/shared/vtex_checkout.py
def _iter_category_pages(self, session, category_path, max_pages):
    for page in range(max_pages):
        # API de catálogo da própria loja: 50 produtos por chamada
        payload = self._fetch_catalog_range(
            session, category_path, page * 50, page * 50 + 49
        )
        if not payload:
            break
        yield payload
        if len(payload) < 50:  # última página da categoria
            break
3O preço que vale na região da loja.scrapers/shared/vtex_checkout.py

O preço do catálogo nem sempre é o da loja daquela cidade. Por isso o coletor simula o carrinho com o CEP da loja e em quantidades diferentes: quando a unidade fica mais barata levando 3, 6 ou 12, esse é o preço de atacado.

scrapers/shared/vtex_checkout.py
# O mesmo produto no carrinho, em várias quantidades,
# com o CEP e o vendedor da loja monitorada.
quantities = [1, 3, 6, 12]
snapshots = {
    quantity: self._checkout_snapshot(session, records, quantity)
    for quantity in quantities
}

prices = self._classify_prices(snapshots[1], snapshots)
# regular, promocional, atacado, clube e cartão
4Ou vem tudo, ou não grava.scrapers/shared/vtex_checkout.py

Categorias que falham ganham uma segunda passada, a partir da página em que pararam. Se ainda faltar alguma, a coleta inteira falha no Airflow: preço pela metade engana mais do que preço nenhum.

scrapers/shared/vtex_checkout.py
failed = self._retry_failed_categories(failed, max_pages)
if failed:
    # Faltou categoria: a execução falha no Airflow
    # e o lote parcial não vai para o banco.
    raise RuntimeError("Coleta incompleta")

self.finish()  # valida EAN, remove duplicados e entrega o lote
Simulação · loja fictícia

O robô lê a loja. O contrato confere.

loja-demonstrativa.exemplo/ofertas
Café Aurora 500 gEAN 2000000001012R$ 18,90
Arroz Horizonte 5 kgEAN 2000000001029R$ 24,90 R$ 22,90
Leite Campo 1 LEAN 2000000001036R$ 4,79
Azeite Serra 500 mlEAN 2000000001040R$ 32,50
Feijão Vale 1 kgEAN 2000000001050R$ 8,49 R$ 7,99
Açúcar Cristal 1 kgEAN 2000000001067R$ 4,29
lote demo-01 · saída
EANProdutoRegularPromoDígito

Pronto. O robô lê cada produto e confere o código de barras.

Em resumo

  • 59 DAGs de coleta, uma por loja
  • 36 coletores: supermercados, atacados, farmácias, pet shops e outros ramos
  • EAN conferido pelo dígito verificador
  • Cinco tipos de preço, cada um em seu campo
  • Coleta que falha não grava lote pela metade

Minha parte

Criei os coletores e as DAGs: motores por plataforma, regras de EAN e de tipos de preço, novas tentativas e a entrega dos lotes à central.

Resultado e situação

Hoje são 59 DAGs de coleta no ar, uma por loja, entre supermercados, atacados, farmácias e pet shops. Com as de ETL e publicação, são 106 DAGs, todas criadas por mim. Numa execução real de 10/10/2026, uma loja inteira foi lida em 16 min 37 s: 7.746 produtos com EAN válido, em 515 corredores e 1.422 páginas, com pico de 109 MB de memória.

Contexto do projeto

Projeto real, criado por mim para a empresa em que trabalho hoje. O código é privado: os trechos desta página são versões simplificadas, com nomes de redes e endereços trocados. O método serve para qualquer ramo que publique preço na internet.