Este projeto apresenta a construção de uma pipeline de ETL (Extração, Transformação e Carregamento) para integrar e processar dados financeiros, com o objetivo de automatizar a geração de visualizações interativas em HTML. A proposta central é coletar mensalmente dados de ações da PETR4 a partir de uma API financeira (brapi), processá-los e transformá-los em um elemento HTML pronto para ser utilizado em um site, blog ou página de resumo.
O principal objetivo deste projeto é demonstrar o uso de ferramentas de orquestração e armazenamento de dados no gerenciamento de pipelines ETL (Extração, Transformação e Carregamento). Mais do que apenas implementar uma solução técnica, este projeto busca explorar a essência do processo ETL, destacando que ele não se limita a dados estruturados ou cálculos matemáticos. A essência do ETL está no ciclo completo que os dados percorrem: desde sua entrada como informação bruta, passando por uma transformação que os contextualiza e agrega valor, até sua saída como um novo objeto com uma função específica. Este processo reflete o propósito principal de pipelines de dados — a geração de valor a partir de informações dispersas e não refinadas.
O pipeline foi implementada em quatro etapas principais: Extração, Carregamento, Transformação e novamente Carregamento.
A extração de dados foi realizada a partir de uma API financeira (brapi) onde é fornecido o histórico de mercado de Ações Brasileiras.
def get_json():
# token mascarado com *
api_token = '***********************'
url = f'https://brapi.dev/api/quote/PETR4?range=1mo&interval=1d&fundamental=true&token={api_token}'
try:
api_response = requests.get(url)
if api_response.status_code != 200:
raise ValueError(f'(API) Erro {api_response.status_code}: {api_response.text}')
else:
return api_response.text
except Exception as e:
raise ValueError(f'(Requests) Erro: {e}')
O processo inclui:
def create_chart(ti):
json_data = ti.xcom_pull(task_ids='get_json')
json_data = json.loads(json_data)
dates = []
values = []
for x in json_data['results'][0]['historicalDataPrice']:
dates.append(datetime.date.fromtimestamp(x['date']))
values.append(float(x['close']))
fig = go.Figure()
fig.add_trace(go.Scatter(
x=dates,
y=values,
mode='lines+markers',
name='Valores',
line=dict(color='steelblue', width=2),
marker=dict(size=8)
))
fig.update_layout(
margin=dict(l=10, r=10, t=10, b=10),
xaxis=dict(fixedrange=True, showgrid=True),
yaxis=dict(fixedrange=True, tickprefix='R$ ',showgrid=True),
template="plotly_white",
autosize=True,
width=None,
height=400
)
html_element = to_html(fig, full_html=False, config={'displayModeBar': False})
return BeautifulSoup(html_element, 'html.parser')
def assemble_html(ti):
html_chart = ti.xcom_pull(task_ids='create_chart')
json_data = ti.xcom_pull(task_ids='get_json')
json_data = json.loads(json_data)
try:
hdfs_client = InsecureClient('http://127.0.0.1:50070', user='hdfs')
hdfs_path = f'/stock_charts/template/chart_template.html'
with hdfs_client.read(hdfs_path, encoding='utf-8') as file:
html_template = file.read()
soup = BeautifulSoup(html_template, "html.parser")
# getting html objects
price = soup.find(id="priceField")
change_price = soup.find(id="changePriceField")
change_percentage = soup.find(id="changePercentageField")
last_update = soup.find(id="updateInfo")
opening = soup.find(id="openingField")
min = soup.find(id="minField")
max = soup.find(id="maxField")
chart = soup.find(id="plotlyChart")
# setting html objects values
price.string = f'R$ {json_data['results'][0]['regularMarketPrice']}'
change_price.string = f'R$ {json_data['results'][0]['regularMarketChange']}'
change_percentage.string = f'{json_data['results'][0]['regularMarketChangePercent']}%'
date_last_update = datetime.datetime.strptime(str(json_data['results'][0]['regularMarketTime']),
"%Y-%m-%dT%H:%M:%S.%fZ")
date_last_update = date_last_update.strftime("%d/%m/%Y %H:%M")
last_update.string = f'Última atualização: {date_last_update}'
opening.string = f'R$ {json_data['results'][0]['regularMarketOpen']}'
min.string = f'R$ {json_data['results'][0]['regularMarketDayLow']}'
max.string = f'R$ {json_data['results'][0]['regularMarketDayHigh']}'
chart.append(html_chart)
# setting html objects styles
if json_data['results'][0]['regularMarketChangePercent'] < 0:
if 'style' in change_percentage.attrs:
change_percentage['style'] += 'background-color: #c31b1b !important;'
else:
change_percentage['style'] = 'background-color: #c31b1b !important;'
return str(soup)
except Exception as e:
raise ValueError(f'Erro: {e}')
def upload_json(ti):
date = datetime.now()
json_data = ti.xcom_pull(task_ids='get_json')
file_name = f'PETR4_{date.strftime("%b").lower()}{date.year}.json'
try:
hdfs_client = InsecureClient('http://127.0.0.1:50070', user='hdfs')
hdfs_path = f'/stock_charts/raw/{file_name}'
with hdfs_client.write(hdfs_path, encoding='utf-8') as writer:
writer.write(json_data)
logging.info(f"Arquivo gravado com sucesso em: {hdfs_path}")
except Exception as e:
raise ValueError(f'Erro: {e}')
def upload_html(ti):
date = datetime.now()
html_data = ti.xcom_pull(task_ids='assemble_html')
file_name = f'PETR4_{date.strftime("%b").lower()}{date.year}.html'
try:
hdfs_client = InsecureClient('http://127.0.0.1:50070', user='hdfs')
hdfs_path = f'/stock_charts/processed/{file_name}'
with hdfs_client.write(hdfs_path, encoding='utf-8') as writer:
writer.write(html_data)
logging.info(f"Arquivo gravado com sucesso em: {hdfs_path}")
except Exception as e:
raise ValueError(f'Erro: {e}')
from airflow import DAG
from airflow.operators.empty import EmptyOperator
from datetime import datetime
import utils
default_args = {
'owner': 'airflow',
'depends_on_past': False,
'email_on_failure': False,
'email_on_retry': False,
}
with DAG(
'monthly_dag_example',
default_args=default_args,
description='Uma DAG exemplo que roda todo dia 1 de cada mês',
schedule_interval='0 0 1 * *', # Cron para o primeiro dia de cada mês às 00:00
start_date=datetime(2023, 1, 1),
catchup=False, # Não executa execuções retroativas
tags=['example', 'monthly']
) as dag:
start = EmptyOperator(task_id='start')
get_json = PythonOperator(
task_id='get_json',
python_callable=utils.get_json,
dag=dag,
)
upload_json = PythonOperator(
task_id='upload_json',
python_callable=utils.upload_json,
dag=dag,
)
create_chart = PythonOperator(
task_id='create_chart',
python_callable=utils.create_chart,
dag=dag,
)
assemble_html = PythonOperator(
task_id='assemble_html',
python_callable=utils.assemble_html,
dag=dag,
)
upload_html = PythonOperator(
task_id='upload_html',
python_callable=utils.upload_html,
dag=dag,
)
end = EmptyOperator(task_id='end')
(
start >> get_json >> upload_json >>
create_chart >> assemble_html >>
upload_html >> end
)
Ao final do processo de ETL, os dados são organizados e apresentados em um gráfico interativo gerado pela biblioteca Plotly, que é incorporado em um elemento HTML customizado para poder ser incluso, por exemplo, em uma página de cotação de ações. O gráfico exemplificado acima é um dos resultados finais gerados pela pipeline.
A pipeline desenvolvida automatiza o processo de criação de elementos HTML personalizados, economizando tempo e garantindo consistência no design e apresentação das informações. Essa abordagem é ideal para sites que demandam atualizações frequentes, como portfólios financeiros, painéis de controle ou páginas de resumo de investimentos.
Com este projeto, é possivel demonstrar habilidades com pipeline de dados (ETL), integração com APIs, ferramentas como Hadoop e Apache Airflow, manipulação de dados e uso de ferramentas de visualização como o Plotly, além de competências na geração de códigos HTML prontos para integração em sistemas web.
Obrigado pela sua atenção! Caso queira enviar sugestões ou apenas dizer olá, fique à vontade para me contatar!
© [Gabriel Dultra] - Todos os direitos reservados.