Solucionado (ver solução)
Solucionado
(ver solução)
1
resposta

[Sugestão] Desafio: lendo uma tabela de uma página web

import pandas as pd
import requests
from pathlib import Path
from datetime import datetime
from io import StringIO, BytesIO
from tenacity import retry, wait_fixed, stop_after_attempt, retry_if_exception_type
import os

# --- CONFIGURAÇÕES ---
PASTA_SAIDA = Path('dados_salvos')
NOME_BASE = 'dados'
VERSOES_MANTIDAS = 3

# --- GARANTE QUE A PASTA DE SAÍDA EXISTE E É ACESSÍVEL ---
def criar_pasta_saida():
    PASTA_SAIDA.mkdir(exist_ok=True)
    if not os.access(PASTA_SAIDA, os.W_OK):
        raise PermissionError(f'Sem permissão de escrita em {PASTA_SAIDA}')

# --- DETECTA O TIPO DO CONTEÚDO COM BASE NO HEADER E EXTENSÃO DA URL ---
def identificar_tipo_conteudo(url):
    try:
        with requests.get(url, stream=True, timeout=15) as r:
            r.raise_for_status()
            ct = r.headers.get('Content-Type', '').lower()

            if 'html' in ct: return 'html'
            if 'xml' in ct: return 'xml'
            if 'json' in ct: return 'json'
            if 'csv' in ct: return 'csv'
            if 'excel' in ct or url.lower().endswith(('.xls', '.xlsx')): return 'excel'
            if url.lower().endswith('.xml'): return 'xml'
            if url.lower().endswith('.json'): return 'json'
            if url.lower().endswith('.csv'): return 'csv'
            if url.lower().endswith(('.html', '.htm')): return 'html'
            return 'desconhecido'
    except Exception as e:
        print(f'Erro ao identificar tipo: {e}')
        return 'erro'

# --- FAZ O DOWNLOAD E CARREGAMENTO DOS DADOS, COM TENTATIVAS AUTOMÁTICAS EM CASO DE ERRO ---
@retry(wait=wait_fixed(3), stop=stop_after_attempt(3), retry=retry_if_exception_type(requests.RequestException))
def baixar_e_carregar_dados(url, tipo):
    with requests.get(url, timeout=20) as r:
        r.raise_for_status()

        # Tratamento especial para HTML com múltiplas tabelas
        if tipo == 'html':
            tabelas = pd.read_html(StringIO(r.text))
            print(f'Foram encontradas {len(tabelas)} tabelas na página.\n')

            for i, tabela in enumerate(tabelas):
                print(f'Tabela {i}: {tabela.shape[0]} linhas x {tabela.shape[1]} colunas')
                print(tabela.head(3), '\n')

            while True:
                try:
                    escolha = int(input(f'Digite o número da tabela que deseja salvar (0 a {len(tabelas)-1}): '))
                    if 0 <= escolha < len(tabelas):
                        return tabelas[escolha]
                    else:
                        print('Número inválido.')
                except ValueError:
                    print('Entrada inválida. Digite um número inteiro.')

        buffer = BytesIO(r.content)

        if tipo == 'excel':
            return pd.read_excel(buffer)
        if tipo == 'xml':
            return pd.read_xml(buffer)
        if tipo == 'csv':
            return pd.read_csv(buffer)
        if tipo == 'json':
            return pd.read_json(buffer)

        print(f"Tipo '{tipo}' não suportado.")
        return None

# --- VERIFICA SE OS DADOS CARREGADOS SÃO VÁLIDOS ---
def validar_dados(df):
    if df is None or df.empty or df.shape[1] == 0:
        return False
    print(f'Colunas detectadas: {list(df.columns)}')
    return True

# --- SALVA A TABELA ESCOLHIDA EM CSV E HTML ---
def salvar_versao(df):
    ts = datetime.now().strftime('%Y%m%d_%H%M%S')
    df.to_csv(PASTA_SAIDA / f'{NOME_BASE}_{ts}.csv', index=False)
    df.to_html(PASTA_SAIDA / f'{NOME_BASE}_{ts}.html', index=False)
    print(f'Dados salvos: {NOME_BASE}_{ts}.csv/html')
    limpar_versoes_antigas()

# --- REMOVE VERSÕES ANTIGAS, MANTENDO SOMENTE AS MAIS RECENTES ---
def limpar_versoes_antigas():
    arquivos = sorted(PASTA_SAIDA.glob(f'{NOME_BASE}_*.csv'), reverse=True)
    for arq in arquivos[VERSOES_MANTIDAS:]:
        arq.unlink(missing_ok=True)
        html = arq.with_suffix('.html')
        if html.exists(): html.unlink()
        print(f'Removido: {arq.name}, {html.name}')

# --- EXECUTA O PROCESSO COMPLETO PARA UMA URL ---
def executar(url):
    print('Inicio da execução')
    try:
        criar_pasta_saida()
        tipo = identificar_tipo_conteudo(url)
        print(f'Tipo detectado: {tipo}')
        df = baixar_e_carregar_dados(url, tipo)

        if validar_dados(df):
            print('\nResumo dos dados:')
            df.info(verbose=False)
            salvar_versao(df)
            print('\nDados salvos com sucesso. Primeiras linhas:')
            print(df.head())
        else:
            print('Dados inválidos.')
    except Exception as e:
        print(f'Erro durante execução: {e}')

# --- EXEMPLO DE EXECUÇÃO ---
if __name__ == '__main__':
    url_exemplo = 'https://pt.wikipedia.org/wiki/Lista_de_pa%C3%ADses_por_popula%C3%A7%C3%A3o'
    executar(url_exemplo)
1 resposta
solução!

Olá, Marinaldo! Como vai?

Muito bem! Continue resolvendo os desafios e compartilhando com a comunidade Alura.

Observei que você explorou o uso de decoradores com @retry da biblioteca Tenacity para tornar o código resiliente e utilizou muito bem o pandas para carregar múltiplos formatos de dados.

Uma dica interessante para o futuro é usar pandas.read_table() com delimitadores personalizados para importar arquivos .txt ou .csv. Dessa forma:

df = pd.read_table('arquivo.txt', delimiter=';', encoding='utf-8')

Resultado:

DataFrame carregado com separador ";", pronto para análise.

Isso facilita a ingestão de dados que não seguem o padrão de separação por vírgulas, tornando o processo mais flexível.

Fico à disposição! E se precisar, conte sempre com o apoio do fórum.

Abraço e bons estudos!

AluraConte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!