import requests
import pandas as pd
import time
from datetime import date, timedelta

# ==========================================
# CONFIGURAÇÕES
# ==========================================

URL = "https://[Log in to view URL]"

# Período de publicação dos editais
hoje = date.today()
inicio = hoje - timedelta(days=1)

# UF consultada (filtro aplicado direto na API)
UF = "ES"

# Modalidades que serão consultadas
modalidades = {
    6: "Pregão Eletrônico",
    7: "Pregão Presencial",
    8: "Dispensa",
}

# Quantidade de registros por página
TAMANHO_PAGINA = 40

# Lista que armazenará os editais encontrados
resultados = []

# ==========================================
# FUNÇÃO PARA CONSULTAR A API
# ==========================================

def consultar_pagina(params):
    max_tentativas = 5

    for tentativa in range(max_tentativas):
        espera = 15 * (tentativa + 1)

        # Melhoria 3: erros de rede não derrubam mais o script
        try:
            resposta = requests.get(
                URL,
                params=params,
                timeout=60
            )
        except (requests.Timeout, requests.ConnectionError) as erro:
            print(f"Erro de rede: {erro}")
            print(f"Nova tentativa em {espera} segundos...")
            time.sleep(espera)
            continue

        print("URL consultada:", resposta.url)
        print("Status HTTP:", resposta.status_code)

        # Melhoria 2: 204 = consulta sem resultados (corpo vazio)
        if resposta.status_code == 204:
            time.sleep(2)
            return {"data": [], "totalPaginas": 0}

        # Se o PNCP limitar as requisições
        if resposta.status_code == 429:
            print(
                f"Limite de requisições atingido. "
                f"Aguardando {espera} segundos..."
            )
            time.sleep(espera)
            continue

        # Se ocorrer outro erro HTTP
        if resposta.status_code != 200:
            print("Mensagem de erro do PNCP:")
            print(resposta.text)
            resposta.raise_for_status()

        # Consulta realizada com sucesso
        dados = resposta.json()

        # Pausa para reduzir a frequência das consultas
        time.sleep(2)

        return dados

    raise RuntimeError(
        f"Não foi possível concluir a consulta "
        f"após {max_tentativas} tentativas."
    )

# ==========================================
# COLETA DOS EDITAIS
# ==========================================

for codigo, nome_modalidade in modalidades.items():

    print("\n" + "=" * 50)
    print(f"Consultando: {nome_modalidade}")
    print("=" * 50)

    pagina = 1

    while True:

        params = {
            "dataInicial": inicio.strftime("%Y%m%d"),
            "dataFinal": hoje.strftime("%Y%m%d"),
            "codigoModalidadeContratacao": codigo,
            "uf": UF,  # Melhoria 1: filtro direto na API
            "pagina": pagina,
            "tamanhoPagina": TAMANHO_PAGINA,
        }

        # Consulta a página atual
        dados = consultar_pagina(params)

        registros = dados.get("data", [])

        # Interrompe quando não houver mais registros
        if not registros:
            print("Nenhum registro nesta página.")
            break

        total_paginas = dados.get("totalPaginas", 1)

        print(
            f"Página {pagina} de {total_paginas} "
            f"| Registros recebidos: {len(registros)}"
        )

        # Processa cada edital
        for item in registros:

            unidade = item.get("unidadeOrgao") or {}
            orgao = item.get("orgaoEntidade") or {}

            # Segurança: mantém somente órgãos da UF desejada
            if unidade.get("ufSigla") != UF:
                continue

            resultados.append({
                "Controle PNCP": item.get(
                    "numeroControlePNCP"
                ),
                "Órgão": orgao.get("razaoSocial"),
                "Município": unidade.get("municipioNome"),
                "UF": unidade.get("ufSigla"),
                "Modalidade": nome_modalidade,
                "Número da compra": item.get("numeroCompra"),
                "Processo": item.get("processo"),
                "Objeto": item.get("objetoCompra"),
                "Publicação": item.get("dataPublicacaoPncp"),
                "Abertura": item.get("dataAberturaProposta"),
                "Encerramento": item.get(
                    "dataEncerramentoProposta"
                ),
                "Valor estimado": item.get(
                    "valorTotalEstimado"
                ),
                "Situação": item.get("situacaoCompraNome"),
                "Link de origem": item.get("linkSistemaOrigem"),
            })

        # Vai para a próxima página, se existir
        if pagina >= total_paginas:
            break

        pagina += 1

# ==========================================
# TRATAMENTO DOS RESULTADOS
# ==========================================

print("\nFinalizando a coleta...")

df = pd.DataFrame(resultados)

if not df.empty:

    # Remove editais duplicados
    if "Controle PNCP" in df.columns:
        df = df.drop_duplicates(
            subset=["Controle PNCP"]
        )

    # Ordena pela data de publicação, quando disponível
    if "Publicação" in df.columns:
        df = df.sort_values(
            by="Publicação",
            ascending=False,
            na_position="last"
        )

    # Exporta para Excel
    nome_arquivo = "editais_es.xlsx"

    df.to_excel(
        nome_arquivo,
        index=False
    )

    print("\n" + "=" * 50)
    print("COLETA CONCLUÍDA!")
    print("=" * 50)
    print(f"Total de editais únicos encontrados: {len(df)}")
    print(f"Arquivo gerado: {nome_arquivo}")

else:
    print(
        f"Nenhum edital de {UF} foi encontrado "
        "no período consultado."
    )

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: