Files
pagodera/iconScript/COLUNAS_DINAMICAS.md
Evandro Fonseca Junior 2b790d8eb4 feat(library): add vocation filter, normalize categories, improve grid layout
Added vocation state and included it in query string builder, updated onFilter to accept a vocation parameter, improved grid layout with grid flow classes, normalized category names for consistent item icons, and refined stats mapping to handle type safety.
2025-10-30 18:44:52 -03:00

11 KiB

🎯 Sistema de Captura Dinâmica de Colunas

Visão Geral

O scraper agora captura automaticamente todas as colunas encontradas nas tabelas do TibiaWiki, sem necessidade de especificar quais colunas buscar.

🔄 Como Funciona

1. Normalização Automática de Nomes

Cada nome de coluna é normalizado para identificador SQL válido:

Coluna no Site Campo no Banco
Nome nome
Lvl lvl
Voc voc
Mãos maos
Atq atq
Dano Elemental dano_elemental
Def def
Mod. de Defesa mod_de_defesa
Bônus bonus
Slots slots
Tier tier
Peso peso
Dropa de dropa_de

Regras de normalização:

  • Remove acentos: Nível → nivel
  • Converte para lowercase: Atq → atq
  • Espaços viram underscores: Dano Elemental → dano_elemental
  • Remove caracteres especiais: Mod. de Defesa → mod_de_defesa
  • Garante nome válido SQL: não começa com número

2. Detecção Automática de Tipo

O sistema tenta converter o valor para o tipo mais apropriado:

Valor no site → Tipo detectado → Salvo como

"150" → INTEGER → 150
"10.5" → REAL → 10.5
"80 oz" → INTEGER → 80
"espada" → TEXT → "espada"
"Fire damage" → TEXT → "Fire damage"

Ordem de tentativa:

  1. Tenta converter para INTEGER
  2. Se falhar, tenta REAL (float)
  3. Se falhar, mantém como TEXT

3. Campos Especiais

Alguns campos têm tratamento especial:

Campo Tratamento
(sem nome) Imagem do item (image_url)
agrupável Tick/Cross → 1/0
nome Sempre TEXT (chave primária)
categoria Adicionado automaticamente
image_url Extraído de colunas sem nome
image_local_path Preenchido após download

4. Criação Dinâmica de Colunas

Se a tabela já existe mas não tem uma coluna:

  • ✅ A coluna é adicionada automaticamente com ALTER TABLE
  • ✅ O tipo SQL é detectado baseado no valor
  • ✅ Log informa: "Coluna 'X' adicionada à tabela 'Y'"

📊 Exemplo Prático

Tabela no Site: Espadas

+----------+-------+-----+-----+------+-----+----------------+-----+
| Nome     | (img) | Lvl | Voc | Mãos | Atq | Dano Elemental | Def |
+----------+-------+-----+-----+------+-----+----------------+-----+
| Broadsword | [🖼️] | 0   | -   | 1    | 30  | -              | 5   |
+----------+-------+-----+-----+------+-----+----------------+-----+

Banco de Dados Resultante

CREATE TABLE espadas (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    categoria TEXT,
    nome TEXT UNIQUE NOT NULL,
    image_url TEXT,
    image_local_path TEXT,
    lvl INTEGER,           -- detectado automaticamente
    voc TEXT,              -- detectado automaticamente
    maos INTEGER,          -- detectado automaticamente
    atq INTEGER,           -- detectado automaticamente
    dano_elemental TEXT,   -- detectado automaticamente
    def INTEGER            -- detectado automaticamente
);

Registro Inserido

INSERT INTO espadas VALUES (
    1,                     -- id (auto)
    'Espadas',            -- categoria
    'Broadsword',         -- nome
    'https://...gif',     -- image_url
    'data/images/...',    -- image_local_path
    0,                    -- lvl
    '-',                  -- voc
    1,                    -- maos
    30,                   -- atq
    '-',                  -- dano_elemental
    5                     -- def
);

✅ Vantagens

1. Flexibilidade Total

  • ✅ Funciona com qualquer categoria
  • ✅ Não precisa atualizar código para novas colunas
  • ✅ Adapta-se a mudanças no site

2. Manutenção Zero

  • ✅ Novas colunas aparecem automaticamente
  • ✅ Tabelas antigas recebem novas colunas
  • ✅ Tipos de dados corretos

3. Dados Completos

  • ✅ Nenhuma informação é perdida
  • ✅ Todas as colunas são capturadas
  • ✅ Valores vazios são tratados (NULL)

🔍 Exemplos de Normalização

Colunas Comuns

normalize_column_name("Nome")           # → "nome"
normalize_column_name("Lvl")            # → "lvl"
normalize_column_name("Nível")          # → "nivel"
normalize_column_name("Voc")            # → "voc"
normalize_column_name("Mãos")           # → "maos"
normalize_column_name("Atq")            # → "atq"
normalize_column_name("Dano Elemental") # → "dano_elemental"
normalize_column_name("Def")            # → "def"
normalize_column_name("Mod. de Defesa") # → "mod_de_defesa"
normalize_column_name("Bônus")          # → "bonus"
normalize_column_name("Slots")          # → "slots"
normalize_column_name("Tier")           # → "tier"
normalize_column_name("Peso")           # → "peso"
normalize_column_name("Dropa de")       # → "dropa_de"

Casos Especiais

normalize_column_name("")               # → "coluna_sem_nome"
normalize_column_name("123abc")         # → "col_123abc"
normalize_column_name("@#$%")           # → "coluna_sem_nome"
normalize_column_name("HP/MP")          # → "hp_mp"

🔄 Detecção de Tipo Detalhada

Exemplos de Conversão

_detect_and_convert_value("150")           # → 150 (int)
_detect_and_convert_value("10.5")          # → 10.5 (float)
_detect_and_convert_value("80 oz")         # → 80 (int)
_detect_and_convert_value("3.50 oz")       # → 3.5 (float)
_detect_and_convert_value("espada")        # → "espada" (text)
_detect_and_convert_value("Fire damage")   # → "Fire damage" (text)
_detect_and_convert_value("-")             # → "-" (text)
_detect_and_convert_value("")              # → None
_detect_and_convert_value("   ")           # → None

Lógica de Detecção

def _detect_and_convert_value(text):
    if not text or text.strip() == '':
        return None
    
    # 1. Tenta INT
    int_value = parse_int(text)  # "150" → 150, "80 oz" → 80
    if int_value is not None:
        return int_value
    
    # 2. Tenta FLOAT
    float_value = parse_float(text)  # "10.5" → 10.5, "3,50" → 3.5
    if float_value is not None:
        return float_value
    
    # 3. Retorna TEXT
    return text.strip()

⚠️ Casos Especiais

1. Coluna Sem Nome (Imagem)

<th></th>  <!-- Coluna sem nome -->

Comportamento:

  • ✅ Não cria campo no banco
  • ✅ Extrai imagem → image_url
  • ✅ Ignora Tick/Cross

2. Coluna "Agrupável"

<th>Agrup.</th>

Comportamento:

  • ✅ Tick.png → agrupavel = 1
  • ✅ Cross.png → agrupavel = 0
  • ✅ "Sim" → agrupavel = 1
  • ✅ "Não" → agrupavel = 0

3. Campos Reservados

Estes campos não são sobrescritos:

  • id (auto increment)
  • categoria (adicionado pelo script)
  • image_url (extraído da coluna sem nome)
  • image_local_path (após download)

📝 Logs e Monitoramento

Log de Nova Coluna

INFO: Coluna 'voc' adicionada à tabela 'espadas'
INFO: Coluna 'dano_elemental' adicionada à tabela 'espadas'

Verificar Colunas no Banco

-- Ver estrutura da tabela
PRAGMA table_info(espadas);

-- Resultado:
-- cid | name             | type    | notnull | dflt_value | pk
-- 0   | id               | INTEGER | 0       | NULL       | 1
-- 1   | categoria        | TEXT    | 0       | NULL       | 0
-- 2   | nome             | TEXT    | 1       | NULL       | 0
-- 3   | lvl              | INTEGER | 0       | NULL       | 0
-- 4   | voc              | TEXT    | 0       | NULL       | 0
-- 5   | maos             | INTEGER | 0       | NULL       | 0
-- 6   | atq              | INTEGER | 0       | NULL       | 0
-- 7   | dano_elemental   | TEXT    | 0       | NULL       | 0
-- ...

🎯 Casos de Uso

Caso 1: Nova Categoria com Colunas Diferentes

Se você processar "Armaduras" que tem colunas diferentes de "Espadas":

Armaduras: Nome, Lvl, Voc, Defesa, Peso

Resultado:

  • ✅ Tabela armaduras criada automaticamente
  • ✅ Colunas: nome, lvl, voc, defesa, peso
  • ✅ Tipos detectados automaticamente

Caso 2: Site Adiciona Nova Coluna

TibiaWiki adiciona coluna "Raridade":

Próxima execução:

  • ✅ Coluna raridade adicionada automaticamente
  • ✅ Itens antigos: raridade = NULL
  • ✅ Itens novos: raridade = "Comum" (ou valor)

Caso 3: Reprocessar Categoria

Você roda novamente para "Espadas":

Comportamento:

  • ✅ Tabela já existe: não recria
  • ✅ Colunas novas: adicionadas
  • ✅ Dados existentes: atualizados (UPSERT por nome)

🔧 Código Técnico

Função de Normalização

def normalize_column_name(column_name):
    """
    Normaliza nome de coluna para identificador SQL válido
    """
    if not column_name:
        return ""
    
    # Remove acentos
    nfd = unicodedata.normalize('NFD', column_name)
    name_no_accent = ''.join(c for c in nfd if unicodedata.category(c) != 'Mn')
    
    # Converte para lowercase
    normalized = name_no_accent.lower()
    
    # Substitui caracteres não alfanuméricos por underscore
    normalized = re.sub(r'[^a-z0-9]+', '_', normalized)
    
    # Remove underscores do início e fim
    normalized = normalized.strip('_')
    
    # Garante que não começa com número
    if normalized and normalized[0].isdigit():
        normalized = 'col_' + normalized
    
    # Se ficou vazio, retorna um nome padrão
    if not normalized:
        return "coluna_sem_nome"
    
    return normalized

Lógica de Processamento

# Para cada célula da tabela
for cell_idx, cell in enumerate(cells):
    header = headers[cell_idx]
    header_clean = clean_text(header)
    cell_text = clean_text(cell.get_text(strip=True))
    
    # 1. Coluna sem nome → imagem
    if not header_clean:
        extract_image(cell)
        continue
    
    # 2. Coluna "agrupável" → Tick/Cross
    if 'agrup' in header_clean.lower():
        handle_tick_cross(cell)
        continue
    
    # 3. Demais colunas → captura automática
    field_name = normalize_column_name(header_clean)
    
    if field_name == 'nome':
        item_data['nome'] = cell_text.strip()
    else:
        value = _detect_and_convert_value(cell_text)
        if value is not None and value != '':
            item_data[field_name] = value

✅ Checklist de Funcionamento

  • Captura todas as colunas automaticamente
  • Normaliza nomes para SQL válido
  • Detecta tipos automaticamente (int/float/text)
  • Adiciona colunas dinamicamente a tabelas existentes
  • Preserva campos especiais (nome, categoria, image_url)
  • Ignora Tick/Cross como imagens
  • Trata valores vazios (NULL)
  • Logs informativos
  • Compatível com tabelas existentes
  • Funciona com qualquer categoria

🎉 Resultado Final

Agora o scraper é completamente dinâmico:

  • ✅ Nenhuma coluna é perdida
  • ✅ Nenhum código precisa ser alterado
  • ✅ Funciona com qualquer estrutura de tabela
  • ✅ Adapta-se automaticamente a mudanças

Antes: 8 colunas fixas (nome, nivel, peso, defesa, slots, ataque, alcance, descricao)
Agora: Todas as colunas da tabela (ilimitado, dinâmico)