Files
pagodera/iconScript/tibiawiki/spiders/items_spider.py
T
Evandro Fonseca Junior 29ba5aece0 feat: add TibiaWiki scraper project and update .gitignore
Integrates a Scrapy-based TibiaWiki scraper (spider, pipelines, SQLite/Images settings, README). Updates .gitignore to exclude generated data directories (/iconScript/data, /data/images) to keep the repository clean.
2025-10-30 00:55:25 -03:00

218 lines
7.8 KiB
Python

"""
Spider principal para extrair dados de itens do TibiaWiki.br
"""
import scrapy
from scrapy.http import Request
from tibiawiki.items import TibiaItem
from tibiawiki.utils import normalize_name, parse_int, parse_float, clean_text
class ItemsSpider(scrapy.Spider):
"""
Spider que extrai informações de todas as categorias de itens do TibiaWiki
"""
name = 'tibiawiki_items'
start_urls = ['https://www.tibiawiki.com.br/wiki/Itens']
custom_settings = {
'CONCURRENT_REQUESTS': 2,
'DOWNLOAD_DELAY': 2,
'RANDOMIZE_DOWNLOAD_DELAY': True,
'AUTOTHROTTLE_ENABLED': True,
'AUTOTHROTTLE_START_DELAY': 1,
'AUTOTHROTTLE_MAX_DELAY': 3,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.categories_found = set()
def parse(self, response):
"""
Parseia a página principal de itens e extrai links para categorias
"""
self.logger.info(f"Parseando página principal: {response.url}")
# Procura por links de categorias
# Os links geralmente estão em <a> dentro de listas ou tabelas
category_links = []
# Método 1: Procurar links com imagens de itens (ícones das categorias)
images_with_links = response.css('a:has(img)')
for link in images_with_links:
href = link.attrib.get('href', '')
text = link.css('::text').get('').strip()
# Verifica se é um link para categoria de itens
if '/wiki/' in href and href not in category_links:
category_links.append(response.urljoin(href))
# Método 2: Procurar por palavras-chave comuns de categorias
all_links = response.css('a::attr(href)').getall()
category_keywords = [
'Capacetes', 'Armaduras', 'Escudos', 'Calças', 'Spellbooks',
'Botas', 'Aljavas', 'Machados', 'Clavas', 'Espadas', 'Rods',
'Wands', 'Distância', 'Munição', 'Punhos', 'Livros', 'Decorações',
'Troféus', 'Comidas', 'Líquidos', 'Plantas', 'Amuletos', 'Colares',
'Anéis', 'Chaves', 'Ferramentas', 'Recipientes', 'Runas', 'Pergaminhos'
]
for link in all_links:
for keyword in category_keywords:
if keyword in link and link not in category_links:
category_links.append(response.urljoin(link))
break
self.logger.info(f"Encontradas {len(category_links)} categorias")
# Faz request para cada categoria
for link in category_links:
yield Request(
url=link,
callback=self.parse_category,
dont_filter=False,
meta={'category_url': link}
)
def parse_category(self, response):
"""
Parseia uma página de categoria específica e extrai dados dos itens
"""
# Extrai nome da categoria da URL
category_name = response.url.split('/')[-1].replace('_', ' ')
self.logger.info(f"Parseando categoria: {category_name}")
# Verifica se já foi processada
if category_name in self.categories_found:
return
self.categories_found.add(category_name)
# Procura por tabelas com itens
# Tables geralmente têm class 'wikitable' no MediaWiki
tables = response.css('table.wikitable')
if not tables:
# Se não encontrar wikitable, tenta qualquer table
tables = response.css('table')
for table in tables:
# Extrai cabeçalhos da tabela
headers = []
header_row = table.css('tr').get(0)
if header_row:
# Tenta pegar <th> ou primeira linha de <td>
ths = table.css('tr:first-child th, tr:first-child td')
for th in ths:
header_text = clean_text(''.join(th.css('::text').getall()))
headers.append(header_text)
self.logger.debug(f"Colunas encontradas: {headers}")
# Processa cada linha da tabela (pula o cabeçalho)
rows = table.css('tr')[1:] # Pula primeira linha (cabeçalho)
for row in rows:
item = TibiaItem()
item['categoria'] = category_name
# Extrai células
cells = row.css('td')
if not cells:
continue
# Mapeia cada célula para seu cabeçalho correspondente
for idx, cell in enumerate(cells):
if idx >= len(headers):
break
header = headers[idx]
cell_text = clean_text(''.join(cell.css('::text').getall()))
# Extrai imagem se houver
img = cell.css('img::attr(src)').get()
if img:
image_url = response.urljoin(img)
item['image_url'] = image_url
item['image_urls'] = [image_url]
# Mapeia colunas para campos do item
self._map_column_to_field(item, header, cell_text)
# Só yield item se tiver nome
if item.get('nome'):
yield item
def _map_column_to_field(self, item, column_name, value):
"""
Mapeia nomes de colunas para campos do item
Args:
item: Item scrapy a ser preenchido
column_name: Nome da coluna da tabela
value: Valor da célula
"""
# Normaliza nome da coluna para comparação
column_lower = column_name.lower()
# Mapeamento de campos comuns
if 'nome' in column_lower or 'item' in column_lower:
item['nome'] = value
elif 'nível' in column_lower or 'lvl' in column_lower:
item['nivel'] = parse_int(value)
elif 'peso' in column_lower:
item['peso'] = parse_float(value)
elif 'defesa' in column_lower or 'def' in column_lower:
item['defesa'] = parse_int(value)
elif 'ataque' in column_lower or 'atk' in column_lower:
item['ataque'] = parse_int(value)
elif 'alcance' in column_lower:
item['alcance'] = parse_int(value)
elif 'slots' in column_lower:
item['slots'] = parse_int(value)
elif 'skill' in column_lower:
item['skill_para_usar'] = value
elif 'valor' in column_lower or 'price' in column_lower:
item['valor_npc'] = parse_int(value)
elif 'descrição' in column_lower or 'descricao' in column_lower:
item['descricao'] = value
elif 'velocidade' in column_lower:
item['atk_velocidade'] = parse_int(value)
elif 'durabilidade' in column_lower:
item['durabilidade'] = parse_int(value)
elif 'dano' in column_lower:
item['dano'] = value
elif 'cura' in column_lower:
item['cura'] = parse_int(value)
elif 'mana' in column_lower:
item['mana'] = parse_int(value)
elif 'vocação' in column_lower or 'voc' in column_lower:
item['vocacao'] = value
elif 'capacidade' in column_lower:
item['capacidade'] = parse_int(value)
elif 'tempo' in column_lower:
item['tempo'] = value