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.
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:
- Tenta converter para INTEGER
- Se falhar, tenta REAL (float)
- 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
armadurascriada 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
raridadeadicionada 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)