├── src ├── README.md ├── requirements.txt ├── bitcoin_dados.db ├── pipeline_00.py ├── bitcoin_dados.json ├── database.py ├── pipeline_01.py ├── pipeline_02.py ├── pipeline_03.py ├── pipeline_04.py ├── pipeline_05.py ├── pipeline_06.py └── pipeline_07.py ├── .gitignore ├── app ├── requirements.txt ├── dashboard_01.py └── dashboard_00.py ├── exemplo_api_compare.py ├── exemplo_selenium_compare.py ├── requirements.txt └── README.md /src/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .env 3 | __pycache__ -------------------------------------------------------------------------------- /app/requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit 2 | psycopg2-binary 3 | python-dotenv 4 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | tinydb 3 | sqlalchemy 4 | psycopg2-binary 5 | python-dotenv 6 | -------------------------------------------------------------------------------- /src/bitcoin_dados.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgalvao/ETLProjectAPIExtract/HEAD/src/bitcoin_dados.db -------------------------------------------------------------------------------- /src/pipeline_00.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | def extrair_dados_bitcoin(): 4 | """Obtém o preço atual do Bitcoin na Coinbase.""" 5 | url = 'https://api.coinbase.com/v2/prices/spot' 6 | resposta = requests.get(url) 7 | dados = resposta.json() 8 | return dados 9 | 10 | if __name__ == "__main__": 11 | print(extrair_dados_bitcoin()) 12 | -------------------------------------------------------------------------------- /src/bitcoin_dados.json: -------------------------------------------------------------------------------- 1 | {"_default": {"1": {"valor": 107605.375, "criptomoeda": "BTC", "moeda": "USD"}, "2": {"valor": 107594.71, "criptomoeda": "BTC", "moeda": "USD"}, "3": {"valor": 107637.225, "criptomoeda": "BTC", "moeda": "USD"}, "4": {"valor": 107589.395, "criptomoeda": "BTC", "moeda": "USD", "timestamp": "2024-12-17 15:08:31"}, "5": {"valor": 107579.605, "criptomoeda": "BTC", "moeda": "USD"}, "6": {"valor": 107595.765, "criptomoeda": "BTC", "moeda": "USD", "timestamp": "2024-12-17 15:11:18"}}} -------------------------------------------------------------------------------- /src/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import declarative_base 2 | from sqlalchemy import Column, Float, String, Integer, DateTime 3 | from datetime import datetime 4 | 5 | # Cria a classe Base do SQLAlchemy (na versão 2.x) 6 | Base = declarative_base() 7 | 8 | class BitcoinPreco(Base): 9 | """Define a tabela no banco de dados.""" 10 | __tablename__ = "bitcoin_precos" 11 | 12 | id = Column(Integer, primary_key=True, autoincrement=True) 13 | valor = Column(Float, nullable=False) 14 | criptomoeda = Column(String(50), nullable=False) # até 50 caracteres 15 | moeda = Column(String(10), nullable=False) # até 10 caracteres 16 | timestamp = Column(DateTime, default=datetime.now) 17 | -------------------------------------------------------------------------------- /src/pipeline_01.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from datetime import datetime 3 | 4 | def extrair_dados_bitcoin(): 5 | """Extrai o JSON completo da API da Coinbase.""" 6 | url = 'https://api.coinbase.com/v2/prices/spot' 7 | resposta = requests.get(url) 8 | return resposta.json() 9 | 10 | def tratar_dados_bitcoin(dados_json): 11 | """Transforma os dados brutos da API, renomeia colunas e adiciona timestamp.""" 12 | valor = dados_json['data']['amount'] 13 | criptomoeda = dados_json['data']['base'] 14 | moeda = dados_json['data']['currency'] 15 | 16 | dados_tratados = [{ 17 | "valor": valor, 18 | "criptomoeda": criptomoeda, 19 | "moeda": moeda 20 | }] 21 | 22 | return dados_tratados 23 | 24 | 25 | if __name__ == "__main__": 26 | # Extração dos dados 27 | dados_json = extrair_dados_bitcoin() 28 | dados_tratados = tratar_dados_bitcoin(dados_json) 29 | print(dados_tratados) -------------------------------------------------------------------------------- /exemplo_api_compare.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import tracemalloc 3 | 4 | def get_bitcoin_price(): 5 | """Obtém o preço atual do Bitcoin na Coinbase.""" 6 | url = 'https://api.coinbase.com/v2/prices/spot' 7 | response = requests.get(url) # Requisição da API 8 | data = response.json() 9 | price = data['data']['amount'] 10 | print(f"Preço atual do Bitcoin: ${price} USD") 11 | return float(price) 12 | 13 | if __name__ == "__main__": 14 | # Inicia o monitoramento de memória 15 | tracemalloc.start() 16 | 17 | # Captura o momento inicial 18 | snapshot_inicio = tracemalloc.take_snapshot() 19 | 20 | # Executa a função para obter o preço do Bitcoin 21 | get_bitcoin_price() 22 | 23 | # Captura o momento final 24 | snapshot_fim = tracemalloc.take_snapshot() 25 | 26 | # Pega o pico de memória durante a execução 27 | memoria_pico = tracemalloc.get_traced_memory()[1] / (1024 * 1024) # Em MB 28 | 29 | # Exibe o consumo de memória 30 | print(f"🔍 Pico de memória durante a execução: {memoria_pico:.2f} MB") 31 | 32 | # Para o monitoramento 33 | tracemalloc.stop() 34 | -------------------------------------------------------------------------------- /src/pipeline_02.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from tinydb import TinyDB 3 | from datetime import datetime 4 | 5 | def extrair_dados_bitcoin(): 6 | """Extrai o JSON completo da API da Coinbase.""" 7 | url = 'https://api.coinbase.com/v2/prices/spot' 8 | resposta = requests.get(url) 9 | return resposta.json() 10 | 11 | def tratar_dados_bitcoin(dados_json): 12 | """Transforma os dados brutos da API e adiciona timestamp.""" 13 | valor = float(dados_json['data']['amount']) 14 | criptomoeda = dados_json['data']['base'] 15 | moeda = dados_json['data']['currency'] 16 | dados_tratados = { 17 | "valor": valor, 18 | "criptomoeda": criptomoeda, 19 | "moeda": moeda 20 | } 21 | return dados_tratados 22 | 23 | def salvar_dados_tinydb(dados, db_name="bitcoin_dados.json"): 24 | """Salva os dados em um banco NoSQL usando TinyDB.""" 25 | db = TinyDB(db_name) 26 | db.insert(dados) 27 | print("Dados salvos no TinyDB!") 28 | 29 | if __name__ == "__main__": 30 | # Extração e tratamento dos dados 31 | dados_json = extrair_dados_bitcoin() 32 | dados_tratados = tratar_dados_bitcoin(dados_json) 33 | 34 | # Mostrar os dados tratados 35 | print("Dados Tratados:") 36 | print(dados_tratados) 37 | 38 | # Salvar no TinyDB 39 | salvar_dados_tinydb(dados_tratados) 40 | -------------------------------------------------------------------------------- /src/pipeline_03.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from tinydb import TinyDB 3 | from datetime import datetime 4 | 5 | def extrair_dados_bitcoin(): 6 | """Extrai o JSON completo da API da Coinbase.""" 7 | url = 'https://api.coinbase.com/v2/prices/spot' 8 | resposta = requests.get(url) 9 | return resposta.json() 10 | 11 | def tratar_dados_bitcoin(dados_json): 12 | """Transforma os dados brutos da API e adiciona timestamp.""" 13 | valor = float(dados_json['data']['amount']) 14 | criptomoeda = dados_json['data']['base'] 15 | moeda = dados_json['data']['currency'] 16 | dados_tratados = { 17 | "valor": valor, 18 | "criptomoeda": criptomoeda, 19 | "moeda": moeda, 20 | "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") 21 | } 22 | return dados_tratados 23 | 24 | def salvar_dados_tinydb(dados, db_name="bitcoin_dados.json"): 25 | """Salva os dados em um banco NoSQL usando TinyDB.""" 26 | db = TinyDB(db_name) 27 | db.insert(dados) 28 | print("Dados salvos no TinyDB!") 29 | 30 | if __name__ == "__main__": 31 | # Extração e tratamento dos dados 32 | dados_json = extrair_dados_bitcoin() 33 | dados_tratados = tratar_dados_bitcoin(dados_json) 34 | 35 | # Mostrar os dados tratados 36 | print("Dados Tratados:") 37 | print(dados_tratados) 38 | 39 | # Salvar no TinyDB 40 | salvar_dados_tinydb(dados_tratados) 41 | -------------------------------------------------------------------------------- /src/pipeline_04.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from tinydb import TinyDB 3 | from datetime import datetime 4 | 5 | def extrair_dados_bitcoin(): 6 | """Extrai o JSON completo da API da Coinbase.""" 7 | url = 'https://api.coinbase.com/v2/prices/spot' 8 | resposta = requests.get(url) 9 | return resposta.json() 10 | 11 | def tratar_dados_bitcoin(dados_json): 12 | """Transforma os dados brutos da API e adiciona timestamp.""" 13 | valor = float(dados_json['data']['amount']) 14 | criptomoeda = dados_json['data']['base'] 15 | moeda = dados_json['data']['currency'] 16 | dados_tratados = { 17 | "valor": valor, 18 | "criptomoeda": criptomoeda, 19 | "moeda": moeda, 20 | "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") 21 | } 22 | return dados_tratados 23 | 24 | def salvar_dados_tinydb(dados, db_name="bitcoin_dados.json"): 25 | """Salva os dados em um banco NoSQL usando TinyDB.""" 26 | db = TinyDB(db_name) 27 | db.insert(dados) 28 | print("Dados salvos no TinyDB!") 29 | 30 | if __name__ == "__main__": 31 | # Extração e tratamento dos dados 32 | dados_json = extrair_dados_bitcoin() 33 | dados_tratados = tratar_dados_bitcoin(dados_json) 34 | 35 | # Mostrar os dados tratados 36 | print("Dados Tratados:") 37 | print(dados_tratados) 38 | 39 | # Salvar no TinyDB 40 | salvar_dados_tinydb(dados_tratados) 41 | -------------------------------------------------------------------------------- /app/dashboard_01.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import psycopg2 3 | import pandas as pd 4 | import time 5 | import os 6 | from datetime import datetime 7 | from dotenv import load_dotenv 8 | 9 | # Carrega variáveis de ambiente do arquivo .env 10 | load_dotenv() 11 | 12 | # Lê as variáveis separadas do arquivo .env (sem SSL) 13 | POSTGRES_USER = os.getenv("POSTGRES_USER") 14 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 15 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 16 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 17 | POSTGRES_DB = os.getenv("POSTGRES_DB") 18 | 19 | def ler_dados_postgres(): 20 | """Lê os dados do banco PostgreSQL e retorna como DataFrame.""" 21 | try: 22 | conn = psycopg2.connect( 23 | host=POSTGRES_HOST, 24 | database=POSTGRES_DB, 25 | user=POSTGRES_USER, 26 | password=POSTGRES_PASSWORD, 27 | port=POSTGRES_PORT 28 | ) 29 | query = "SELECT * FROM bitcoin_precos ORDER BY timestamp DESC" 30 | df = pd.read_sql(query, conn) 31 | conn.close() 32 | return df 33 | except Exception as e: 34 | st.error(f"Erro ao conectar no PostgreSQL: {e}") 35 | return pd.DataFrame() 36 | 37 | def main(): 38 | st.set_page_config(page_title="Dashboard de Preços do Bitcoin", layout="wide") 39 | st.title("📊 Dashboard de Preços do Bitcoin") 40 | st.write("Este dashboard exibe os dados do preço do Bitcoin coletados periodicamente em um banco PostgreSQL.") 41 | 42 | df = ler_dados_postgres() 43 | 44 | if not df.empty: 45 | st.subheader("📋 Dados Recentes") 46 | st.dataframe(df) 47 | 48 | df['timestamp'] = pd.to_datetime(df['timestamp']) 49 | df = df.sort_values(by='timestamp') 50 | 51 | st.subheader("📈 Evolução do Preço do Bitcoin") 52 | st.line_chart(data=df, x='timestamp', y='valor', use_container_width=True) 53 | 54 | st.subheader("🔢 Estatísticas Gerais") 55 | col1, col2, col3 = st.columns(3) 56 | col1.metric("Preço Atual", f"${df['valor'].iloc[-1]:,.2f}") 57 | col2.metric("Preço Máximo", f"${df['valor'].max():,.2f}") 58 | col3.metric("Preço Mínimo", f"${df['valor'].min():,.2f}") 59 | else: 60 | st.warning("Nenhum dado encontrado no banco de dados PostgreSQL.") 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /exemplo_selenium_compare.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import time 3 | from selenium import webdriver 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.chrome.service import Service 6 | from webdriver_manager.chrome import ChromeDriverManager 7 | 8 | def monitorar_memoria_completa(): 9 | """ 10 | Monitora o consumo de memória do processo atual e seus filhos. 11 | """ 12 | processo_principal = psutil.Process() 13 | memoria_total = processo_principal.memory_info().rss 14 | 15 | # Inclui memória de processos filhos (ex: ChromeDriver) 16 | for filho in processo_principal.children(recursive=True): 17 | memoria_total += filho.memory_info().rss 18 | 19 | return memoria_total / (1024 * 1024) # Converte para MB 20 | 21 | def obter_preco_bitcoin_infomoney(): 22 | """ 23 | Utiliza Selenium para pegar o preço atual do Bitcoin em reais no site InfoMoney usando XPath. 24 | Monitora o uso de memória durante todo o processo, incluindo processos filhos. 25 | """ 26 | memoria_inicial = monitorar_memoria_completa() 27 | print(f"🔍 Memória inicial: {memoria_inicial:.2f} MB") 28 | 29 | # Configuração do WebDriver (Chrome) 30 | service = Service(ChromeDriverManager().install()) 31 | driver = webdriver.Chrome(service=service) 32 | 33 | try: 34 | # Medir memória após abrir o navegador 35 | memoria_apos_webdriver = monitorar_memoria_completa() 36 | print(f"📈 Memória após abrir o navegador: {memoria_apos_webdriver:.2f} MB") 37 | 38 | # URL do InfoMoney para cotação do Bitcoin 39 | url = "https://www.infomoney.com.br/cotacoes/cripto/ativo/bitcoin-btc/" 40 | print("Abrindo o navegador...") 41 | driver.get(url) 42 | 43 | # Aguardar o carregamento da página 44 | time.sleep(5) 45 | 46 | # Medir memória após carregar a página 47 | memoria_apos_carregamento = monitorar_memoria_completa() 48 | print(f"📈 Memória após carregar a página: {memoria_apos_carregamento:.2f} MB") 49 | 50 | # Localiza o elemento usando o XPath fornecido 51 | xpath = "/html/body/div[4]/div/div[1]/div[1]/div/div[3]/div[1]/p" 52 | elemento_preco = driver.find_element(By.XPATH, xpath) 53 | 54 | # Extrai e imprime o preço 55 | preco_bitcoin = elemento_preco.text.strip() 56 | print(f"💰 Preço atual do Bitcoin (InfoMoney): {preco_bitcoin}") 57 | 58 | # Medir memória após captura do preço 59 | memoria_apos_captura = monitorar_memoria_completa() 60 | print(f"📈 Memória após captura do preço: {memoria_apos_captura:.2f} MB") 61 | 62 | except Exception as e: 63 | print(f"❌ Erro ao obter o preço: {e}") 64 | finally: 65 | # Fecha o navegador 66 | driver.quit() 67 | 68 | # Medir memória após fechar o navegador 69 | memoria_final = monitorar_memoria_completa() 70 | print(f"🔻 Memória final após fechar o navegador: {memoria_final:.2f} MB") 71 | 72 | if __name__ == "__main__": 73 | obter_preco_bitcoin_infomoney() 74 | -------------------------------------------------------------------------------- /src/pipeline_05.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import requests 4 | from datetime import datetime 5 | from dotenv import load_dotenv 6 | from sqlalchemy import create_engine 7 | from sqlalchemy.orm import sessionmaker 8 | 9 | # Importar Base e BitcoinPreco do database.py 10 | from database import Base, BitcoinPreco 11 | 12 | # Carrega variáveis de ambiente do arquivo .env 13 | load_dotenv() 14 | 15 | # Lê as variáveis separadas do arquivo .env (sem SSL) 16 | POSTGRES_USER = os.getenv("POSTGRES_USER") 17 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 18 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 19 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 20 | POSTGRES_DB = os.getenv("POSTGRES_DB") 21 | 22 | # Monta a URL de conexão ao banco PostgreSQL (sem ?sslmode=...) 23 | DATABASE_URL = ( 24 | f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}" 25 | f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" 26 | ) 27 | 28 | # Cria o engine e a sessão 29 | engine = create_engine(DATABASE_URL) 30 | Session = sessionmaker(bind=engine) 31 | 32 | def criar_tabela(): 33 | """Cria a tabela no banco de dados, se não existir.""" 34 | Base.metadata.create_all(engine) 35 | print("Tabela criada/verificada com sucesso!") 36 | 37 | def extrair_dados_bitcoin(): 38 | """Extrai o JSON completo da API da Coinbase.""" 39 | url = 'https://api.coinbase.com/v2/prices/spot' 40 | resposta = requests.get(url) 41 | if resposta.status_code == 200: 42 | return resposta.json() 43 | else: 44 | print(f"Erro na API: {resposta.status_code}") 45 | return None 46 | 47 | def tratar_dados_bitcoin(dados_json): 48 | """Transforma os dados brutos da API e adiciona timestamp.""" 49 | valor = float(dados_json['data']['amount']) 50 | criptomoeda = dados_json['data']['base'] 51 | moeda = dados_json['data']['currency'] 52 | timestamp = datetime.now() 53 | 54 | dados_tratados = { 55 | "valor": valor, 56 | "criptomoeda": criptomoeda, 57 | "moeda": moeda, 58 | "timestamp": timestamp 59 | } 60 | return dados_tratados 61 | 62 | def salvar_dados_postgres(dados): 63 | """Salva os dados no banco PostgreSQL.""" 64 | session = Session() 65 | novo_registro = BitcoinPreco(**dados) 66 | session.add(novo_registro) 67 | session.commit() 68 | session.close() 69 | print(f"[{dados['timestamp']}] Dados salvos no PostgreSQL!") 70 | 71 | if __name__ == "__main__": 72 | criar_tabela() 73 | print("Iniciando ETL com atualização a cada 15 segundos... (CTRL+C para interromper)") 74 | 75 | while True: 76 | try: 77 | dados_json = extrair_dados_bitcoin() 78 | if dados_json: 79 | dados_tratados = tratar_dados_bitcoin(dados_json) 80 | print("Dados Tratados:", dados_tratados) 81 | salvar_dados_postgres(dados_tratados) 82 | time.sleep(15) 83 | except KeyboardInterrupt: 84 | print("\nProcesso interrompido pelo usuário. Finalizando...") 85 | break 86 | except Exception as e: 87 | print(f"Erro durante a execução: {e}") 88 | time.sleep(15) 89 | -------------------------------------------------------------------------------- /src/pipeline_06.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import requests 4 | from datetime import datetime 5 | from dotenv import load_dotenv 6 | from sqlalchemy import create_engine 7 | from sqlalchemy.orm import sessionmaker 8 | 9 | # Integração com Logfire 10 | import logging 11 | import logfire 12 | from logging import basicConfig, getLogger 13 | # Configura o Logfire e adiciona o handler 14 | logfire.configure() 15 | basicConfig(handlers=[logfire.LogfireLoggingHandler()]) 16 | logger = getLogger(__name__) 17 | logger.setLevel(logging.INFO) 18 | 19 | # Importar Base e BitcoinPreco do database.py 20 | from database import Base, BitcoinPreco 21 | 22 | # Carrega variáveis de ambiente do arquivo .env 23 | load_dotenv() 24 | 25 | 26 | # Lê as variáveis separadas do arquivo .env 27 | POSTGRES_USER = os.getenv("POSTGRES_USER") 28 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 29 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 30 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 31 | POSTGRES_DB = os.getenv("POSTGRES_DB") 32 | 33 | # Monta a URL de conexão ao banco PostgreSQL (sem SSL) 34 | DATABASE_URL = ( 35 | f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}" 36 | f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" 37 | ) 38 | 39 | # Cria o engine e a sessão 40 | engine = create_engine(DATABASE_URL) 41 | Session = sessionmaker(bind=engine) 42 | 43 | def criar_tabela(): 44 | """Cria a tabela no banco de dados, se não existir.""" 45 | Base.metadata.create_all(engine) 46 | logger.info("Tabela criada/verificada com sucesso!") 47 | 48 | def extrair_dados_bitcoin(): 49 | """Extrai o JSON completo da API da Coinbase.""" 50 | url = 'https://api.coinbase.com/v2/prices/spot' 51 | resposta = requests.get(url) 52 | if resposta.status_code == 200: 53 | return resposta.json() 54 | else: 55 | logger.error(f"Erro na API: {resposta.status_code}") 56 | return None 57 | 58 | def tratar_dados_bitcoin(dados_json): 59 | """Transforma os dados brutos da API e adiciona timestamp.""" 60 | valor = float(dados_json['data']['amount']) 61 | criptomoeda = dados_json['data']['base'] 62 | moeda = dados_json['data']['currency'] 63 | timestamp = datetime.now() 64 | 65 | dados_tratados = { 66 | "valor": valor, 67 | "criptomoeda": criptomoeda, 68 | "moeda": moeda, 69 | "timestamp": timestamp 70 | } 71 | return dados_tratados 72 | 73 | def salvar_dados_postgres(dados): 74 | """Salva os dados no banco PostgreSQL.""" 75 | session = Session() 76 | try: 77 | novo_registro = BitcoinPreco(**dados) 78 | session.add(novo_registro) 79 | session.commit() 80 | logger.info(f"[{dados['timestamp']}] Dados salvos no PostgreSQL!") 81 | except Exception as ex: 82 | logger.error(f"Erro ao inserir dados no PostgreSQL: {ex}") 83 | session.rollback() 84 | finally: 85 | session.close() 86 | 87 | if __name__ == "__main__": 88 | criar_tabela() 89 | logger.info("Iniciando ETL com atualização a cada 15 segundos... (CTRL+C para interromper)") 90 | 91 | while True: 92 | try: 93 | dados_json = extrair_dados_bitcoin() 94 | if dados_json: 95 | dados_tratados = tratar_dados_bitcoin(dados_json) 96 | logger.info(f"Dados Tratados: {dados_tratados}") 97 | salvar_dados_postgres(dados_tratados) 98 | time.sleep(15) 99 | except KeyboardInterrupt: 100 | logger.info("Processo interrompido pelo usuário. Finalizando...") 101 | break 102 | except Exception as e: 103 | logger.error(f"Erro durante a execução: {e}") 104 | time.sleep(15) 105 | -------------------------------------------------------------------------------- /src/pipeline_07.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import requests 4 | import logging 5 | import logfire 6 | from datetime import datetime 7 | from dotenv import load_dotenv 8 | from sqlalchemy import create_engine 9 | from sqlalchemy.orm import sessionmaker 10 | from logging import basicConfig, getLogger 11 | 12 | # ------------------------------------------------------ 13 | # Configuração Logfire 14 | logfire.configure() 15 | basicConfig(handlers=[logfire.LogfireLoggingHandler()]) 16 | logger = getLogger(__name__) 17 | logger.setLevel(logging.INFO) 18 | logfire.instrument_requests() 19 | 20 | # ------------------------------------------------------ 21 | # Importar Base e BitcoinPreco do database.py 22 | from database import Base, BitcoinPreco 23 | 24 | # Carrega variáveis de ambiente do arquivo .env 25 | load_dotenv() 26 | 27 | # Lê as variáveis separadas do arquivo .env 28 | POSTGRES_USER = os.getenv("POSTGRES_USER") 29 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 30 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 31 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 32 | POSTGRES_DB = os.getenv("POSTGRES_DB") 33 | 34 | # Monta a URL de conexão ao banco PostgreSQL (sem SSL) 35 | DATABASE_URL = ( 36 | f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}" 37 | f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" 38 | ) 39 | 40 | # Cria o engine e a sessão 41 | engine = create_engine(DATABASE_URL) 42 | Session = sessionmaker(bind=engine) 43 | 44 | def criar_tabela(): 45 | """Cria a tabela no banco de dados, se não existir.""" 46 | Base.metadata.create_all(engine) 47 | logger.info("Tabela criada/verificada com sucesso!") 48 | 49 | def extrair_dados_bitcoin(): 50 | """Extrai o JSON completo da API da Coinbase.""" 51 | url = 'https://api.coinbase.com/v2/prices/spot' 52 | resposta = requests.get(url) 53 | if resposta.status_code == 200: 54 | return resposta.json() 55 | else: 56 | logger.error(f"Erro na API: {resposta.status_code}") 57 | return None 58 | 59 | def tratar_dados_bitcoin(dados_json): 60 | """Transforma os dados brutos da API e adiciona timestamp.""" 61 | valor = float(dados_json['data']['amount']) 62 | criptomoeda = dados_json['data']['base'] 63 | moeda = dados_json['data']['currency'] 64 | timestamp = datetime.now() 65 | 66 | dados_tratados = { 67 | "valor": valor, 68 | "criptomoeda": criptomoeda, 69 | "moeda": moeda, 70 | "timestamp": timestamp 71 | } 72 | return dados_tratados 73 | 74 | def salvar_dados_postgres(dados): 75 | """Salva os dados no banco PostgreSQL.""" 76 | session = Session() 77 | try: 78 | novo_registro = BitcoinPreco(**dados) 79 | session.add(novo_registro) 80 | session.commit() 81 | logger.info(f"[{dados['timestamp']}] Dados salvos no PostgreSQL!") 82 | except Exception as ex: 83 | logger.error(f"Erro ao inserir dados no PostgreSQL: {ex}") 84 | session.rollback() 85 | finally: 86 | session.close() 87 | 88 | def pipeline_bitcoin(): 89 | """Executa a pipeline de ETL do Bitcoin com spans do Logfire.""" 90 | with logfire.span("Executando pipeline ETL Bitcoin"): 91 | 92 | with logfire.span("Extrair Dados da API Coinbase"): 93 | dados_json = extrair_dados_bitcoin() 94 | 95 | if not dados_json: 96 | logger.error("Falha na extração dos dados. Abortando pipeline.") 97 | return 98 | 99 | with logfire.span("Tratar Dados do Bitcoin"): 100 | dados_tratados = tratar_dados_bitcoin(dados_json) 101 | 102 | with logfire.span("Salvar Dados no Postgres"): 103 | salvar_dados_postgres(dados_tratados) 104 | 105 | # Exemplo de log final com placeholders 106 | logger.info( 107 | f"Pipeline finalizada com sucesso!" 108 | ) 109 | 110 | if __name__ == "__main__": 111 | criar_tabela() 112 | logger.info("Iniciando pipeline ETL com atualização a cada 15 segundos... (CTRL+C para interromper)") 113 | 114 | while True: 115 | try: 116 | pipeline_bitcoin() 117 | time.sleep(15) 118 | except KeyboardInterrupt: 119 | logger.info("Processo interrompido pelo usuário. Finalizando...") 120 | break 121 | except Exception as e: 122 | logger.error(f"Erro inesperado durante a pipeline: {e}") 123 | time.sleep(15) 124 | -------------------------------------------------------------------------------- /app/dashboard_00.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | import numpy as np 4 | from datetime import datetime 5 | import time 6 | 7 | # 1) st.title - Título principal 8 | st.title("🚀 Quick Starter Streamlit - Jornada de dados") 9 | 10 | # 2) st.header - Seção 11 | st.header("1. Contexto de Data Engineering") 12 | 13 | # 3) st.write - Texto simples 14 | st.write( 15 | """ 16 | Esse dashboard simula alguns elementos que você usaria em um projeto de Engenharia de Dados: 17 | - Coleta/Extração de dados 18 | - Transformação e limpeza 19 | - Visualização de métricas 20 | - Monitoramento de pipelines 21 | """ 22 | ) 23 | 24 | # 4) st.markdown - Texto em formato Markdown 25 | st.markdown(""" 26 | ### Tópicos Abordados: 27 | - **Criação de widgets** para coleta de parâmetros 28 | - **Exibição de DataFrames** (parte de "Transform") 29 | - **Gráficos** para monitorar performance e throughput 30 | - **Métricas** representando KPIs de pipelines ETL 31 | """) 32 | 33 | st.header("2. Parâmetros para Pipelines") 34 | 35 | # 5) st.text_input - Parâmetro textual 36 | nome_pipeline = st.text_input("Nome da Pipeline de Dados", value="Pipeline_Ingestao_Bitcoin") 37 | 38 | # 6) st.number_input - Parâmetro numérico 39 | batch_size = st.number_input("Batch size (linhas por ingestão):", min_value=100, max_value=100000, value=1000, step=100) 40 | 41 | # 7) st.slider - Seletor contínuo 42 | latencia_maxima = st.slider("Latência Máxima Aceitável (segundos):", min_value=1, max_value=30, value=5) 43 | 44 | # 8) st.selectbox - Escolha de um pipeline 45 | tipo_pipeline = st.selectbox("Tipo de Pipeline:", ["Batch", "Streaming", "Lambda", "Kappa"]) 46 | 47 | # 9) st.multiselect - Escolha múltipla de camadas de processamento 48 | camadas = st.multiselect( 49 | "Quais camadas envolvidas no pipeline?", 50 | ["Raw", "Staging", "Trusted", "Analytics", "Sandbox", "Dimensão", "Fato"], 51 | default=["Raw", "Staging"] 52 | ) 53 | 54 | # 10) st.checkbox - Check para simular ativação de logs 55 | ativar_logs = st.checkbox("Ativar Logs de Execução") 56 | 57 | st.header("3. Exibição de Dados (Transform)") 58 | 59 | # 11) Criar um dataset fictício sobre "execuções" de pipeline 60 | dados_execucoes = { 61 | "data_execucao": pd.date_range(end=datetime.now(), periods=5, freq="H"), 62 | "status": ["Sucesso", "Sucesso", "Falha", "Sucesso", "Sucesso"], 63 | "linhas_processadas": [1000, 1200, 900, 1500, 1300], 64 | "tempo_execucao_seg": [4.2, 5.1, 7.8, 3.9, 4.5] 65 | } 66 | df_execucoes = pd.DataFrame(dados_execucoes) 67 | 68 | # 12) st.dataframe - Tabela interativa 69 | st.subheader("Executões Recentes da Pipeline") 70 | st.dataframe(df_execucoes) 71 | 72 | # 13) st.table - Tabela estática 73 | st.subheader("Tabela Estática - Últimas Execuções") 74 | st.table(df_execucoes) 75 | 76 | # 14) st.metric - Exibir métricas de KPIs 77 | st.subheader("Indicadores de Performance (KPIs)") 78 | col1, col2, col3 = st.columns(3) 79 | col1.metric("Total de Linhas Processadas", f"{df_execucoes['linhas_processadas'].sum():,}") 80 | col2.metric("Execuções com Sucesso", str(df_execucoes['status'].value_counts().get("Sucesso", 0))) 81 | col3.metric("Execuções com Falha", str(df_execucoes['status'].value_counts().get("Falha", 0))) 82 | 83 | st.header("4. Visualização de Gráficos (Monitor)") 84 | 85 | # 15) st.line_chart - Gráfico de linha com métricas 86 | st.subheader("Linhas Processadas por Execução (Line Chart)") 87 | df_execucoes_ordenado = df_execucoes.sort_values(by="data_execucao") 88 | st.line_chart(data=df_execucoes_ordenado, x="data_execucao", y="linhas_processadas") 89 | 90 | # 16) st.bar_chart - Gráfico de barras 91 | st.subheader("Tempo de Execução por Data (Bar Chart)") 92 | st.bar_chart(data=df_execucoes_ordenado, x="data_execucao", y="tempo_execucao_seg") 93 | 94 | st.header("5. Outros Recursos Úteis") 95 | 96 | # 17) st.date_input - Seletor de data 97 | data_planejada = st.date_input("Data de início para nova pipeline", datetime.now()) 98 | 99 | # 18) st.progress - Barra de progresso (simulação) 100 | st.write("Carregando dados de log...") 101 | progress_bar = st.progress(0) 102 | for i in range(101): 103 | time.sleep(0.01) 104 | progress_bar.progress(i) 105 | 106 | # 19) Mensagens de sucesso/erro 107 | if ativar_logs: 108 | st.success("Logs de execução estão ativos.") 109 | else: 110 | st.warning("Logs de execução estão desativados.") 111 | 112 | # 20) st.button - Botão para simular disparo de pipeline 113 | if st.button("Disparar Nova Execução"): 114 | st.info(f"Pipeline '{nome_pipeline}' disparada em modo {tipo_pipeline}.") 115 | st.write(f"Batch size configurado para {batch_size} linhas. Latência Máxima: {latencia_maxima}s") 116 | st.write(f"Camadas selecionadas: {', '.join(camadas)}") 117 | 118 | st.markdown("___") 119 | st.caption("Quick Starter de Streamlit aplicado à Engenharia de Dados. © 2024") 120 | 121 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altair==5.5.0 2 | attrs==24.3.0 3 | blinker==1.9.0 4 | cachetools==5.5.0 5 | certifi==2024.12.14 6 | charset-normalizer==3.4.0 7 | click==8.1.7 8 | colorama==0.4.6 9 | gitdb==4.0.11 10 | GitPython==3.1.43 11 | greenlet==3.1.1 12 | idna==3.10 13 | Jinja2==3.1.4 14 | jsonschema==4.23.0 15 | jsonschema-specifications==2024.10.1 16 | markdown-it-py==3.0.0 17 | MarkupSafe==3.0.2 18 | mdurl==0.1.2 19 | narwhals==1.18.4 20 | numpy==2.2.0 21 | packaging==24.2 22 | pandas==2.2.3 23 | pillow==11.0.0 24 | protobuf==5.29.1 25 | pyarrow==18.1.0 26 | pydeck==0.9.1 27 | Pygments==2.18.0 28 | python-dateutil==2.9.0.post0 29 | python-dotenv==1.0.1 30 | pytz==2024.2 31 | referencing==0.35.1 32 | requests==2.32.3 33 | rich==13.9.4 34 | rpds-py==0.22.3 35 | six==1.17.0 36 | smmap==5.0.1 37 | SQLAlchemy==2.0.36 38 | streamlit==1.41.1 39 | tenacity==9.0.0 40 | tinydb==4.8.2 41 | toml==0.10.2 42 | tornado==6.4.2 43 | typing_extensions==4.12.2 44 | tzdata==2024.2 45 | urllib3==2.2.3 46 | watchdog==6.0.0 47 | altair==5.5.0 48 | attrs==24.3.0 49 | blinker==1.9.0 50 | cachetools==5.5.0 51 | certifi==2024.12.14 52 | charset-normalizer==3.4.0 53 | click==8.1.7 54 | colorama==0.4.6 55 | gitdb==4.0.11 56 | GitPython==3.1.43 57 | greenlet==3.1.1 58 | idna==3.10 59 | Jinja2==3.1.4 60 | jsonschema==4.23.0 61 | jsonschema-specifications==2024.10.1 62 | markdown-it-py==3.0.0 63 | MarkupSafe==3.0.2 64 | mdurl==0.1.2 65 | narwhals==1.18.4 66 | numpy==2.2.0 67 | packaging==24.2 68 | pandas==2.2.3 69 | pillow==11.0.0 70 | protobuf==5.29.1 71 | psycopg2-binary==2.9.10 72 | pyarrow==18.1.0 73 | pydeck==0.9.1 74 | Pygments==2.18.0 75 | python-dateutil==2.9.0.post0 76 | python-dotenv==1.0.1 77 | pytz==2024.2 78 | referencing==0.35.1 79 | requests==2.32.3 80 | rich==13.9.4 81 | rpds-py==0.22.3 82 | six==1.17.0 83 | smmap==5.0.1 84 | SQLAlchemy==2.0.36 85 | streamlit==1.41.1 86 | tenacity==9.0.0 87 | tinydb==4.8.2 88 | toml==0.10.2 89 | tornado==6.4.2 90 | typing_extensions==4.12.2 91 | tzdata==2024.2 92 | urllib3==2.2.3 93 | watchdog==6.0.0 94 | altair==5.5.0 95 | attrs==24.3.0 96 | blinker==1.9.0 97 | cachetools==5.5.0 98 | certifi==2024.12.14 99 | charset-normalizer==3.4.0 100 | click==8.1.7 101 | colorama==0.4.6 102 | Deprecated==1.2.15 103 | executing==2.1.0 104 | gitdb==4.0.11 105 | GitPython==3.1.43 106 | googleapis-common-protos==1.66.0 107 | greenlet==3.1.1 108 | idna==3.10 109 | importlib_metadata==8.5.0 110 | Jinja2==3.1.4 111 | jsonschema==4.23.0 112 | jsonschema-specifications==2024.10.1 113 | logfire==2.7.1 114 | markdown-it-py==3.0.0 115 | MarkupSafe==3.0.2 116 | mdurl==0.1.2 117 | narwhals==1.18.4 118 | numpy==2.2.0 119 | opentelemetry-api==1.29.0 120 | opentelemetry-exporter-otlp-proto-common==1.29.0 121 | opentelemetry-exporter-otlp-proto-http==1.29.0 122 | opentelemetry-instrumentation==0.50b0 123 | opentelemetry-instrumentation-requests==0.50b0 124 | opentelemetry-instrumentation-sqlalchemy==0.50b0 125 | opentelemetry-proto==1.29.0 126 | opentelemetry-sdk==1.29.0 127 | opentelemetry-semantic-conventions==0.50b0 128 | opentelemetry-util-http==0.50b0 129 | packaging==24.2 130 | pandas==2.2.3 131 | pillow==11.0.0 132 | protobuf==5.29.1 133 | psycopg2-binary==2.9.10 134 | pyarrow==18.1.0 135 | pydeck==0.9.1 136 | Pygments==2.18.0 137 | python-dateutil==2.9.0.post0 138 | python-dotenv==1.0.1 139 | pytz==2024.2 140 | referencing==0.35.1 141 | requests==2.32.3 142 | rich==13.9.4 143 | rpds-py==0.22.3 144 | six==1.17.0 145 | smmap==5.0.1 146 | SQLAlchemy==2.0.36 147 | streamlit==1.41.1 148 | tenacity==9.0.0 149 | tinydb==4.8.2 150 | toml==0.10.2 151 | tornado==6.4.2 152 | typing_extensions==4.12.2 153 | tzdata==2024.2 154 | urllib3==2.2.3 155 | watchdog==6.0.0 156 | wrapt==1.17.0 157 | zipp==3.21.0 158 | altair==5.5.0 159 | attrs==24.3.0 160 | blinker==1.9.0 161 | cachetools==5.5.0 162 | certifi==2024.12.14 163 | cffi==1.17.1 164 | charset-normalizer==3.4.0 165 | click==8.1.7 166 | colorama==0.4.6 167 | Deprecated==1.2.15 168 | executing==2.1.0 169 | gitdb==4.0.11 170 | GitPython==3.1.43 171 | googleapis-common-protos==1.66.0 172 | greenlet==3.1.1 173 | h11==0.14.0 174 | idna==3.10 175 | importlib_metadata==8.5.0 176 | Jinja2==3.1.4 177 | jsonschema==4.23.0 178 | jsonschema-specifications==2024.10.1 179 | logfire==2.7.1 180 | markdown-it-py==3.0.0 181 | MarkupSafe==3.0.2 182 | mdurl==0.1.2 183 | narwhals==1.18.4 184 | numpy==2.2.0 185 | opentelemetry-api==1.29.0 186 | opentelemetry-exporter-otlp-proto-common==1.29.0 187 | opentelemetry-exporter-otlp-proto-http==1.29.0 188 | opentelemetry-instrumentation==0.50b0 189 | opentelemetry-instrumentation-requests==0.50b0 190 | opentelemetry-instrumentation-sqlalchemy==0.50b0 191 | opentelemetry-proto==1.29.0 192 | opentelemetry-sdk==1.29.0 193 | opentelemetry-semantic-conventions==0.50b0 194 | opentelemetry-util-http==0.50b0 195 | outcome==1.3.0.post0 196 | packaging==24.2 197 | pandas==2.2.3 198 | pillow==11.0.0 199 | protobuf==5.29.1 200 | psutil==6.1.0 201 | psycopg2-binary==2.9.10 202 | pyarrow==18.1.0 203 | pycparser==2.22 204 | pydeck==0.9.1 205 | Pygments==2.18.0 206 | PySocks==1.7.1 207 | python-dateutil==2.9.0.post0 208 | python-dotenv==1.0.1 209 | pytz==2024.2 210 | referencing==0.35.1 211 | requests==2.32.3 212 | rich==13.9.4 213 | rpds-py==0.22.3 214 | selenium==4.27.1 215 | six==1.17.0 216 | smmap==5.0.1 217 | sniffio==1.3.1 218 | sortedcontainers==2.4.0 219 | SQLAlchemy==2.0.36 220 | streamlit==1.41.1 221 | tenacity==9.0.0 222 | tinydb==4.8.2 223 | toml==0.10.2 224 | tornado==6.4.2 225 | trio==0.27.0 226 | trio-websocket==0.11.1 227 | typing_extensions==4.12.2 228 | tzdata==2024.2 229 | urllib3==2.2.3 230 | watchdog==6.0.0 231 | webdriver-manager==4.0.2 232 | websocket-client==1.8.0 233 | wrapt==1.17.0 234 | wsproto==1.2.0 235 | zipp==3.21.0 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Jornada de Dados 4 |

5 |

6 | Nossa missão é fornecer o melhor ensino em engenharia de dados 7 |

8 | 9 | Bem-vindo a **Jornada de Dados** 10 | 11 | # Extração de API do zero - Projeto completo de ETL com Python e Microsoft Azure, até o Dashboard. 12 | Aqui está o **README** atualizado, agora usando a **API da Bitcoin na Coinbase** como referência: 13 | 14 | --- 15 | 16 | Esquema do projeto: [app.excalidraw.com](https://app.excalidraw.com/s/8pvW6zbNUnD/9zZctm3OR9f) 17 | 18 | --- 19 | 20 | # 💰 **Data Pipeline: Extração de Dados Bitcoin com ETL em Python** 21 | 22 | ## **Introdução** 23 | 24 | Este projeto faz parte de um workshop gratuito de **Data Engineering para Iniciantes**, focado na criação de pipelines de dados ETL (Extract, Transform, Load). O objetivo é construir um programa que consome dados de uma **API** (Coinbase), organiza esses dados e armazena em uma base de dados. 25 | 26 | Você aprenderá conceitos fundamentais de Engenharia de Dados, como: 27 | 1. O que é uma API e como consumi-la usando Python. 28 | 2. O processo completo de ETL (extração, transformação e carga). 29 | 3. Como automatizar a execução do pipeline para coleta contínua. 30 | 31 | Ao final do projeto, você terá um programa funcional que: 32 | - Coleta o preço atual da Bitcoin em tempo real. 33 | - Salva esses dados em um formato tabular para futura análise. 34 | 35 | --- 36 | 37 | ## **Overview do Projeto** 38 | 39 | ### **Objetivo Principal** 40 | Desenvolver um pipeline ETL automatizado para consumir dados da **API da Coinbase** e armazenar informações sobre o preço da Bitcoin ao longo do tempo. 41 | 42 | ### **Etapas do Projeto** 43 | 44 | 1. **Extração (E)**: 45 | - Utilizar a **API da Coinbase** para obter o preço atual da Bitcoin. 46 | - Trabalhar com os endpoints públicos da API (sem necessidade de autenticação). 47 | 48 | 2. **Transformação (T)**: 49 | - Selecionar apenas as informações relevantes: preço da Bitcoin, horário da consulta e moeda de referência (USD). 50 | - Organizar os dados no formato tabular utilizando **Pandas**. 51 | 52 | 3. **Carga (L)**: 53 | - Armazenar os dados coletados em um arquivo **CSV** ou em um banco de dados SQLite. 54 | 55 | 4. **Automatização**: 56 | - Agendar o programa para executar periodicamente (por exemplo, a cada hora ou diariamente), garantindo a coleta contínua dos dados. 57 | 58 | --- 59 | 60 | ## **Tecnologias Utilizadas** 61 | - **Python 3.12** 62 | - **Bibliotecas**: 63 | - `requests`: Para consumir APIs. 64 | - `pandas`: Para manipulação e organização de dados. 65 | - `sqlite3`: Para armazenamento em banco de dados (opcional). 66 | - `tinydb`: Para armazenamento em banco de dados NoSQL. 67 | - `sqlalchemy`: SQLAlchemy é uma biblioteca de mapeamento objeto-relacional para Python. 68 | - `psycopg2-binary`: Psycopg é uma biblioteca de acesso a dados PostgreSQL para Python. 69 | - `streamlit`: Para criar dashboards interativos. 70 | - `time`: Para medir o tempo de execução do programa. 71 | - `datetime`: Para manipulação de datas e horas. 72 | - **Coinbase API**: Para obter o preço da Bitcoin em tempo real. 73 | 74 | --- 75 | 76 | ## **Exemplo de Dados Coletados** 77 | | Data/Hora | Preço (USD) | Moeda | 78 | |----------------------|------------|---------| 79 | | 2024-01-01 12:00:00 | 42,000.50 | Bitcoin | 80 | | 2024-01-01 13:00:00 | 42,150.75 | Bitcoin | 81 | 82 | --- 83 | 84 | ## **Resultados Esperados** 85 | Ao final deste projeto, você será capaz de: 86 | 1. Extrair dados em tempo real de APIs públicas. 87 | 2. Transformar e organizar os dados em formato estruturado. 88 | 3. Automatizar o pipeline ETL para coleta recorrente dos dados. 89 | 90 | **Exemplo de Análises Futuras**: 91 | - Monitorar o preço da Bitcoin ao longo do tempo. 92 | - Identificar padrões de variação diária, semanal ou mensal. 93 | - Criar alertas para valores mínimos/máximos. 94 | 95 | --- 96 | 97 | ## **Próximos Passos** 98 | Este projeto é apenas o começo. Nos próximos módulos, cobriremos: 99 | 1. **Transformação Avançada**: Limpeza e enriquecimento dos dados. 100 | 2. **Armazenamento Persistente**: Introdução a bancos de dados em nuvem. 101 | 3. **Visualização de Dados**: Construção de dashboards interativos. 102 | 103 | --- 104 | 105 | ## **Como Executar o Projeto** 106 | 107 | 1. **Clone o Repositório**: 108 | ```bash 109 | git clone https://github.com/seu-usuario/data-pipeline-bitcoin.git 110 | cd data-pipeline-bitcoin 111 | ``` 112 | 113 | 2. **Instale as Dependências**: 114 | ```bash 115 | pip install requests pandas 116 | ``` 117 | 118 | 3. **Execute o Script**: 119 | ```bash 120 | python main.py 121 | ``` 122 | 123 | 4. **Verifique os Dados**: 124 | - O arquivo `bitcoin_prices.csv` será gerado com os preços coletados. 125 | 126 | --- 127 | 128 | **Agora, mãos à obra! 🚀** 129 | 130 | --- 131 | 132 | Essa versão usa a **API pública da Coinbase** e adapta o fluxo do projeto para a captura do preço da Bitcoin. Ela é simples, funcional e ideal para iniciantes em Engenharia de Dados! 133 | 134 | ## Realmente precisamos de uma API? 135 | 136 | A grande diferença entre o **consumo de memória do Selenium** e o **consumo de memória do requests** é resultado da **complexidade** e **funcionamento interno** das duas abordagens. Vamos analisar ponto a ponto: 137 | 138 | --- 139 | 140 | ## **Diferença Entre Selenium e Requests** 141 | 142 | 1. **Selenium**: 143 | - **Selenium** abre um **navegador real**, como o Google Chrome. 144 | - O ChromeDriver **inicializa uma instância completa do navegador**, que carrega: 145 | - HTML completo, 146 | - CSS, JavaScript dinâmico, 147 | - Imagens e outros recursos visuais. 148 | - O navegador consome memória como qualquer navegador que você usaria manualmente. 149 | - O **processo principal Python** apenas controla o ChromeDriver, mas a maior parte da memória é consumida pelo **processo filho** (navegador). 150 | 151 | 2. **Requests**: 152 | - A biblioteca **`requests`** faz apenas uma **requisição HTTP simples** ao servidor. 153 | - Ele **não carrega imagens, CSS, JavaScript ou renderiza nada**. Apenas recebe o **HTML cru ou JSON** como texto e o processa. 154 | - Como resultado, o consumo de memória é extremamente baixo, pois não há dependências complexas ou renderização. 155 | 156 | --- 157 | 158 | ## **Resultados Explicados** 159 | 160 | ### **Selenium** 161 | ```plaintext 162 | 🔍 Memória inicial: 33.39 MB 163 | 📈 Memória após abrir o navegador: 587.02 MB 164 | 📈 Memória após carregar a página: 967.70 MB 165 | 💰 Preço atual do Bitcoin (InfoMoney): 655.084,00 166 | 📈 Memória após captura do preço: 963.44 MB 167 | 🔻 Memória final após fechar o navegador: 21.14 MB 168 | 🚀 Pico de memória do processo Python: 0.56 MB 169 | ``` 170 | 171 | **Explicação**: 172 | - **33 MB**: Memória inicial do Python, apenas carregando o interpretador e bibliotecas. 173 | - **587 MB**: O navegador foi aberto, e ele sozinho consome cerca de **500 MB** para funcionar. 174 | - **967 MB**: A página foi carregada com todos os recursos (scripts, imagens, etc.). 175 | - **Pico do Python (0.56 MB)**: O Selenium apenas **controla o navegador**, mas não aloca muito dentro do Python. O **navegador Chrome** (processo filho) consome a maior parte dos recursos. 176 | 177 | --- 178 | 179 | ### **Requests** 180 | ```plaintext 181 | Pico de memória durante a execução: 0.17 MB 182 | ``` 183 | 184 | **Explicação**: 185 | - A biblioteca `requests` apenas faz uma **requisição leve** e recebe uma resposta. 186 | - Como não há renderização ou recursos pesados sendo carregados, o consumo de memória é **mínimo**. 187 | - O pico de memória é extremamente baixo porque o Python armazena apenas o JSON (ou HTML) da resposta, que ocupa poucos KBs. 188 | 189 | --- 190 | 191 | ## **Por Que Essa Diferença Acontece?** 192 | 193 | | Aspecto | Selenium | Requests | 194 | |---------------------------|----------------------------------------|----------------------------| 195 | | **Execução** | Abre um navegador completo (Chrome). | Apenas faz uma requisição. | 196 | | **Carregamento** | Renderiza HTML, CSS, JavaScript, etc. | Processa apenas o texto. | 197 | | **Processo Controlado** | Processos filhos consomem memória. | Apenas processo Python. | 198 | | **Complexidade** | Mais pesado devido ao ChromeDriver. | Muito leve e direto. | 199 | | **Pico de Memória Python**| Baixo: Python controla o ChromeDriver. | Leve: Apenas resposta HTTP.| 200 | 201 | --- 202 | 203 | ## **Conclusão** 204 | 205 | 1. **Selenium**: 206 | - É **muito mais pesado** porque ele abre e controla um navegador completo. 207 | - O ChromeDriver e o navegador Chrome (processos filhos) consomem quase **1 GB** de RAM. 208 | 209 | 2. **Requests**: 210 | - É **muito mais leve** porque apenas faz uma requisição HTTP. 211 | - Não há renderização ou carregamento de recursos extras, então o consumo é quase insignificante. 212 | 213 | --- 214 | 215 | ### **Quando Usar Cada Um?** 216 | 217 | - **Use Requests**: 218 | - Quando o site oferece uma **API** ou os dados estão disponíveis diretamente no HTML sem necessidade de renderização dinâmica. 219 | 220 | - **Use Selenium**: 221 | - Quando o site carrega conteúdo com **JavaScript** ou é necessário interagir com o navegador (cliques, scroll, etc.). 222 | 223 | --- 224 | 225 | Se precisar otimizar ou escolher a melhor abordagem para um caso específico, é só avisar! 🚀 226 | 227 | Quando você entra em um **site** ou consome uma **API**, a comunicação entre o **cliente** (navegador ou aplicativo) e o **servidor** ocorre por meio de requisições e respostas. A **diferença principal** está no **conteúdo** enviado e recebido. 228 | 229 | --- 230 | 231 | ## **1. Diferença Entre Site e API** 232 | 233 | | **Aspecto** | **Site** | **API** | 234 | |------------------------|----------------------------------------|--------------------------------------| 235 | | **Requisição** | Feita pelo navegador (HTTP GET/POST). | Feita pelo cliente (aplicação/script) via HTTP. | 236 | | **Resposta do Servidor**| HTML, CSS, JavaScript e recursos visuais (imagens, vídeos). | Dados estruturados (geralmente JSON ou XML). | 237 | | **Renderização** | Navegador renderiza a página (front-end visual). | Cliente processa os dados e decide o uso. | 238 | | **Uso** | Visualização para um usuário humano. | Integração entre sistemas ou aplicações. | 239 | 240 | --- 241 | 242 | ## **2. Exemplos Práticos** 243 | 244 | 1. **Site**: 245 | - O cliente (navegador) envia uma **requisição HTTP GET** para acessar a URL de um site. 246 | - O servidor responde com um **HTML** que contém links para **CSS, JavaScript e imagens**. 247 | - O navegador **renderiza** todos esses arquivos para exibir a página. 248 | 249 | 2. **API**: 250 | - O cliente (aplicação) envia uma **requisição HTTP GET/POST** para a URL da API. 251 | - O servidor responde apenas com **dados estruturados** (exemplo: JSON ou XML). 252 | - Não há renderização visual; a resposta é processada diretamente pelo código. 253 | 254 | --- 255 | 256 | ## **3. Fluxo no Mermaid** 257 | 258 | Aqui está o fluxo dos dois casos no formato **Mermaid**. 259 | 260 | ### **Fluxo de um Site** 261 | ```mermaid 262 | sequenceDiagram 263 | participant Cliente as Navegador (Cliente) 264 | participant Servidor as Servidor Web 265 | 266 | Cliente->>Servidor: HTTP GET (Requisição para URL do site) 267 | Servidor-->>Cliente: HTML, CSS, JS, Imagens (Resposta) 268 | 269 | Cliente->>Cliente: Renderiza HTML e carrega recursos 270 | ``` 271 | 272 | --- 273 | 274 | ### **Fluxo de uma API** 275 | ```mermaid 276 | sequenceDiagram 277 | participant Cliente as Aplicação (Cliente) 278 | participant Servidor as Servidor de API 279 | 280 | Cliente->>Servidor: HTTP GET/POST (Requisição para endpoint) 281 | Servidor-->>Cliente: JSON ou XML (Resposta) 282 | 283 | Cliente->>Cliente: Processa os dados retornados 284 | ``` 285 | 286 | --- 287 | 288 | ## **Diferenças Claras nos Fluxos** 289 | 290 | 1. **Site**: 291 | - O servidor envia um **HTML** que o navegador processa e renderiza. 292 | - O HTML referencia **CSS**, **JavaScript**, e **imagens**, que são carregados separadamente. 293 | 294 | 2. **API**: 295 | - O servidor retorna apenas **dados estruturados** em **JSON/XML**. 296 | - A aplicação cliente consome e processa esses dados diretamente, sem renderização visual. 297 | 298 | --- 299 | 300 | Se quiser, posso ajustar o fluxo para adicionar mais detalhes ou exemplos práticos. 🚀 301 | 302 | ## **Banco de dados** 303 | 304 | Vamos usar o Postgres, que é um banco de dados open source, gratuito e muito popular. 305 | 306 | Para instalar o Postgres, você pode usar o Docker, que é uma ferramenta que facilita a execução de contêineres de software. 307 | 308 | Ou você pode usar o servidor de banco de dados da Render ou Azure que é um serviço de cloud computing que facilita a execução de contêineres de software. 309 | 310 | ### Criando um servidor de banco de dados na Render 311 | 312 | A Render é uma plataforma de cloud computing que facilita a execução de contêineres de software. 313 | 314 | Para criar um servidor de banco de dados na Render, você pode usar o link: [Render](https://render.com/docs/databases) 315 | 316 | Clique em **New** e **Postgres** 317 | 318 | Coloque o nome do seu banco de dados, o usuário e a senha. 319 | 320 | ### Criando a pipeline em Python 321 | 322 | --- 323 | 324 | #### 1. `database.py` 325 | 326 | Esse arquivo é responsável por criar a tabela no banco de dados. 327 | 328 | O ORM (Object-Relational Mapping) é uma técnica que permite mapear objetos de um programa para tabelas de um banco de dados. 329 | 330 | Dessa forma, você não precisa mais usar SQL puro para criar e manipular a tabela. 331 | 332 | ```python 333 | class BitcoinPreco(Base): 334 | """Define a tabela no banco de dados.""" 335 | __tablename__ = "bitcoin_precos" 336 | 337 | id = Column(Integer, primary_key=True, autoincrement=True) 338 | valor = Column(Float, nullable=False) 339 | criptomoeda = Column(String(50), nullable=False) # até 50 caracteres 340 | moeda = Column(String(10), nullable=False) # até 10 caracteres 341 | timestamp = Column(DateTime, default=datetime.now) 342 | ``` 343 | 344 | A classe `BitcoinPreco` define as colunas para uma tabela chamada `bitcoin_precos` no PostgreSQL. Aqui vai o **SQL bruto** que você pode usar para criar a tabela manualmente no PostgreSQL: 345 | 346 | ```sql 347 | CREATE TABLE IF NOT EXISTS bitcoin_precos ( 348 | id SERIAL PRIMARY KEY, 349 | valor DOUBLE PRECISION NOT NULL, 350 | criptomoeda VARCHAR(50) NOT NULL, 351 | moeda VARCHAR(10) NOT NULL, 352 | timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 353 | ); 354 | ``` 355 | 356 | Essa instrução **corresponde** à estrutura modelada em `BitcoinPreco` pelo SQLAlchemy. Se você quiser adequar o tamanho de cada `VARCHAR` ou usar outro tipo como `TEXT`, fique à vontade para ajustar conforme suas necessidades. 357 | 358 | --- 359 | 360 | ## 2. Arquivo principal (por exemplo, `pipeline_bitcoin_05.py`) 361 | 362 | ```python 363 | import os 364 | import time 365 | import requests 366 | from datetime import datetime 367 | from dotenv import load_dotenv 368 | from sqlalchemy import create_engine 369 | from sqlalchemy.orm import sessionmaker 370 | 371 | # Importar Base e BitcoinPreco do database.py 372 | from database import Base, BitcoinPreco 373 | 374 | # Carrega variáveis de ambiente do arquivo .env 375 | load_dotenv() 376 | 377 | # Lê as variáveis separadas do arquivo .env (sem SSL) 378 | POSTGRES_USER = os.getenv("POSTGRES_USER") 379 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 380 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 381 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 382 | POSTGRES_DB = os.getenv("POSTGRES_DB") 383 | 384 | # Monta a URL de conexão ao banco PostgreSQL (sem ?sslmode=...) 385 | DATABASE_URL = ( 386 | f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}" 387 | f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" 388 | ) 389 | 390 | # Cria o engine e a sessão 391 | engine = create_engine(DATABASE_URL) 392 | Session = sessionmaker(bind=engine) 393 | 394 | def criar_tabela(): 395 | """Cria a tabela no banco de dados, se não existir.""" 396 | Base.metadata.create_all(engine) 397 | print("Tabela criada/verificada com sucesso!") 398 | 399 | def extrair_dados_bitcoin(): 400 | """Extrai o JSON completo da API da Coinbase.""" 401 | url = 'https://api.coinbase.com/v2/prices/spot' 402 | resposta = requests.get(url) 403 | if resposta.status_code == 200: 404 | return resposta.json() 405 | else: 406 | print(f"Erro na API: {resposta.status_code}") 407 | return None 408 | 409 | def tratar_dados_bitcoin(dados_json): 410 | """Transforma os dados brutos da API e adiciona timestamp.""" 411 | valor = float(dados_json['data']['amount']) 412 | criptomoeda = dados_json['data']['base'] 413 | moeda = dados_json['data']['currency'] 414 | timestamp = datetime.now() 415 | 416 | dados_tratados = { 417 | "valor": valor, 418 | "criptomoeda": criptomoeda, 419 | "moeda": moeda, 420 | "timestamp": timestamp 421 | } 422 | return dados_tratados 423 | 424 | def salvar_dados_postgres(dados): 425 | """Salva os dados no banco PostgreSQL.""" 426 | session = Session() 427 | novo_registro = BitcoinPreco(**dados) 428 | session.add(novo_registro) 429 | session.commit() 430 | session.close() 431 | print(f"[{dados['timestamp']}] Dados salvos no PostgreSQL!") 432 | 433 | if __name__ == "__main__": 434 | criar_tabela() 435 | print("Iniciando ETL com atualização a cada 15 segundos... (CTRL+C para interromper)") 436 | 437 | while True: 438 | try: 439 | dados_json = extrair_dados_bitcoin() 440 | if dados_json: 441 | dados_tratados = tratar_dados_bitcoin(dados_json) 442 | print("Dados Tratados:", dados_tratados) 443 | salvar_dados_postgres(dados_tratados) 444 | time.sleep(15) 445 | except KeyboardInterrupt: 446 | print("\nProcesso interrompido pelo usuário. Finalizando...") 447 | break 448 | except Exception as e: 449 | print(f"Erro durante a execução: {e}") 450 | time.sleep(15) 451 | ``` 452 | 453 | --- 454 | 455 | ## 3. `.env` (Exemplo) 456 | 457 | Neste caso, seu arquivo `.env` também não conterá SSL: 458 | 459 | ```bash 460 | POSTGRES_USER=jornadadedados 461 | POSTGRES_PASSWORD=mudar123 462 | POSTGRES_HOST=bancodedadospostgres.postgres.database.azure.com 463 | POSTGRES_PORT=5432 464 | POSTGRES_DB=postgres 465 | ``` 466 | --- 467 | 468 | ### Como funciona agora 469 | 470 | 1. **`database.py`**: contém apenas a definição de `Base` e do modelo `BitcoinPreco`. 471 | 2. **`exemplo_05.py`** (ou outro nome principal): faz o ETL, cria a tabela usando `Base`, e salva os dados usando a instância da `Session`. 472 | 473 | Com isso, você removeu completamente a parte de SSL. --------------------------------------------------------------------------------