""" 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 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 ou primeira linha de 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