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

411 lines
11 KiB
Markdown

# 🎯 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
```sql
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
```sql
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
```python
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
```python
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
```python
_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
```python
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)
```html
<th></th> <!-- Coluna sem nome -->
```
**Comportamento:**
- ✅ Não cria campo no banco
- ✅ Extrai imagem → `image_url`
- ✅ Ignora Tick/Cross
### 2. Coluna "Agrupável"
```html
<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
```sql
-- 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
```python
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
```python
# 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
- [x] Captura todas as colunas automaticamente
- [x] Normaliza nomes para SQL válido
- [x] Detecta tipos automaticamente (int/float/text)
- [x] Adiciona colunas dinamicamente a tabelas existentes
- [x] Preserva campos especiais (nome, categoria, image_url)
- [x] Ignora Tick/Cross como imagens
- [x] Trata valores vazios (NULL)
- [x] Logs informativos
- [x] Compatível com tabelas existentes
- [x] 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)