import pandas as pd import json import requests import os import logging import numpy as np from requests.exceptions import RequestException # Configuração de logging logging.basicConfig( level=logging.INFO, format='[%(levelname)s] %(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # --- Configurações principais --- ARQUIVO_JSON = 'dados.json' URL = 'https://cdn3.gnarususercontent.com.br/2929-pandas/dados.json' def obter_dados_json(url, arquivo_local): """ Verifica a existência do arquivo JSON local. Se existir, pergunta ao usuário se deve atualizar. Se não existir ou se a atualização for solicitada, baixa e salva o arquivo. """ if os.path.exists(arquivo_local): logging.info(f'O arquivo "{arquivo_local}" já existe.') while True: resposta = input("Deseja atualizar o arquivo com os dados da URL? (s/n): ").strip().lower() if resposta == 's': logging.info('Atualização solicitada. Baixando novo arquivo...') break elif resposta == 'n': logging.info('Mantendo o arquivo local. Lendo dados existentes...') with open(arquivo_local, 'r', encoding='utf-8') as f: return json.load(f) else: print("Resposta inválida. Por favor, digite 's' para sim ou 'n' para não.") else: logging.info(f'O arquivo "{arquivo_local}" não existe. Baixando...') try: response = requests.get(url, timeout=10) response.raise_for_status() dados = response.json() with open(arquivo_local, 'w', encoding='utf-8') as f: json.dump(dados, f, ensure_ascii=False, indent=4) logging.info(f'Arquivo "{arquivo_local}" salvo com sucesso.') return dados except (RequestException, json.JSONDecodeError, IOError) as e: logging.critical(f'Falha ao obter ou salvar o arquivo JSON. Erro: {e}') return None def normalizar_e_processar_dados(dados_json): """ Normaliza o JSON, trata dados vazios, normaliza listas e otimiza o DataFrame. """ logging.info("Iniciando a normalização do JSON.") try: df = pd.json_normalize(dados_json) while True: list_columns = [col for col in df.columns if any(isinstance(x, list) for x in df[col].dropna())] if not list_columns: break for col in list_columns: logging.info(f"Normalizando a coluna '{col}' que contém listas.") df_exploded = df.explode(col).reset_index(drop=True) df_normalized = pd.json_normalize(df_exploded[col].dropna()).add_prefix(f'{col}_').reset_index(drop=True) df = df_exploded.drop(columns=[col]).join(df_normalized) df = df.replace(['', ' ', None], np.nan) df[df.select_dtypes(include=np.number).columns] = df.select_dtypes(include=np.number).fillna(-1) df[df.select_dtypes(include=['object']).columns] = df.select_dtypes(include=['object']).fillna('Desconhecido') logging.info("Otimizando tipos de dados para menor consumo de memória.") df_otimizado = df.copy() for col in df_otimizado.select_dtypes(include=['object']).columns: if len(df_otimizado[col].unique()) / len(df_otimizado[col]) < 0.5: df_otimizado[col] = df_otimizado[col].astype('category') for col in df_otimizado.select_dtypes(include=['int64', 'float64']).columns: df_otimizado[col] = pd.to_numeric(df_otimizado[col], downcast='integer') if df_otimizado[col].dtype == 'int64': df_otimizado[col] = pd.to_numeric(df_otimizado[col], downcast='signed') elif df_otimizado[col].dtype == 'float64': df_otimizado[col] = pd.to_numeric(df_otimizado[col], downcast='float') return df_otimizado except Exception as e: logging.critical(f'Erro durante o processamento do DataFrame. Erro: {e}') return None # --- Execução principal --- dados_json = obter_dados_json(URL, ARQUIVO_JSON) if dados_json: df_final = normalizar_e_processar_dados(dados_json) if df_final is not None: print('\n' + '-'*50) print("### Processamento Concluído ###") try: mem_original = pd.json_normalize(dados_json).memory_usage(deep=True).sum() / 1024 mem_otimizada = df_final.memory_usage(deep=True).sum() / 1024 print(f"Memória original: {mem_original:.2f} KB") print(f"Memória otimizada: {mem_otimizada:.2f} KB") print(f"Redução de memória: {((mem_original - mem_otimizada) / mem_original) * 100:.2f}%") except Exception: logging.warning("Não foi possível calcular a memória original para comparação.") print("\n" + '-'*50) print("\n### Verificação de Duplicatas ###") num_duplicatas = df_final.duplicated().sum() if num_duplicatas > 0: print(f"ATENÇÃO: Foram encontradas {num_duplicatas} linhas duplicadas.") print("Isso é esperado devido à normalização de listas.") remover = input("Deseja remover as duplicatas exatas? (s/n): ").strip().lower() if remover == 's': df_final = df_final.drop_duplicates().reset_index(drop=True) print(f"Duplicatas removidas. DataFrame agora tem {len(df_final)} linhas.") else: print("Duplicatas mantidas.") else: print("Não foram encontradas linhas duplicadas exatas no DataFrame.") print("\n" + '-'*50) print(f"DataFrame Final com {df_final.shape[0]} linhas e {df_final.shape[1]} colunas.") print("\n### Colunas e Tipos de Dados ###") df_final.info() print("\n" + '-'*50) print("\n### Amostra do DataFrame Final ###") df_display = df_final.copy() df_display.columns = df_display.columns.str.replace('^pessoas_endereco_', '', regex=True) df_display.columns = df_display.columns.str.replace('^pessoas_', '', regex=True) df_display.columns = df_display.columns.str.replace('^endereco.', '', regex=True) print(df_display.head()) print('\n' + '-'*50)