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.
218 lines
7.8 KiB
Python
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
|
|
|