""" Pipelines para processar os itens extraídos """ import sqlite3 import os import scrapy from itemadapter import ItemAdapter from scrapy.pipelines.images import ImagesPipeline from scrapy.exceptions import DropItem from tibiawiki.utils import normalize_name class SQLitePipeline: """ Pipeline que salva os itens em um banco SQLite Uma tabela por categoria de itens """ def __init__(self, db_path): self.db_path = db_path self.conn = None self.cursor = None self.created_tables = set() @classmethod def from_crawler(cls, crawler): db_path = crawler.settings.get('SQLITE_DB_PATH', 'data/tibiawiki.db') return cls(db_path) def open_spider(self, spider): """Abre conexão com o banco quando o spider iniciar""" # Cria diretório se não existir os.makedirs(os.path.dirname(self.db_path), exist_ok=True) self.conn = sqlite3.connect(self.db_path) self.conn.row_factory = sqlite3.Row self.cursor = self.conn.cursor() spider.logger.info(f"Conectado ao banco SQLite: {self.db_path}") def close_spider(self, spider): """Fecha conexão com o banco quando o spider terminar""" if self.conn: self.conn.commit() self.conn.close() spider.logger.info("Conexão com SQLite fechada") def process_item(self, item, spider): """Processa cada item e salva no banco""" adapter = ItemAdapter(item) category = adapter.get('categoria') if not category: raise DropItem("Item sem categoria") # Normaliza nome da tabela table_name = normalize_name(category) # Cria tabela se não existir self._create_table_if_not_exists(table_name, adapter) # Prepara dados para inserção data = dict(adapter) # Remove campo 'images' que é usado apenas pelo ImagesPipeline data.pop('images', None) data.pop('image_urls', None) # Prepara valores para SQL columns = list(data.keys()) values = list(data.values()) placeholders = ','.join(['?'] * len(values)) # Query de UPSERT (inserir ou atualizar) columns_str = ','.join(columns) update_str = ','.join([f"{col}=excluded.{col}" for col in columns]) sql = f""" INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders}) ON CONFLICT(nome) DO UPDATE SET {update_str} """ self.cursor.execute(sql, values) self.conn.commit() spider.logger.debug(f"Salvo item: {data.get('nome')} em tabela {table_name}") return item def _create_table_if_not_exists(self, table_name, adapter): """Cria tabela para categoria se não existir""" if table_name in self.created_tables: return # Prepara definições de colunas columns_def = [] # Adiciona todas as colunas possíveis baseadas nos campos do item for field_name, field_value in adapter.items(): sql_type = self._get_sql_type(field_value) constraint = "" # Nome é chave única if field_name == 'nome': constraint = " UNIQUE NOT NULL" columns_def.append(f"{field_name} {sql_type}{constraint}") # Cria tabela columns_sql = ','.join(columns_def) sql = f""" CREATE TABLE IF NOT EXISTS {table_name} ( id INTEGER PRIMARY KEY AUTOINCREMENT, {columns_sql} ) """ self.cursor.execute(sql) self.created_tables.add(table_name) def _get_sql_type(self, value): """Retorna o tipo SQL apropriado para um valor""" if value is None: return "TEXT" if isinstance(value, int): return "INTEGER" elif isinstance(value, float): return "REAL" else: return "TEXT" class CustomImagesPipeline(ImagesPipeline): """ Pipeline customizado para baixar e organizar imagens Organiza imagens por categoria em pastas separadas """ def file_path(self, request, response=None, info=None, *, item=None): """ Define o caminho onde a imagem será salva Formato: {categoria_normalizada}/nome_do_item.jpg """ # Obtém nome e categoria do item adapter = ItemAdapter(item) if item else {} categoria = adapter.get('categoria', 'uncategorized') nome = adapter.get('nome', 'unknown') # Normaliza nomes categoria_norm = normalize_name(categoria) nome_norm = normalize_name(nome) # Extrai extensão original original_url = request.url if '/' in original_url: original_filename = original_url.split('/')[-1] else: original_filename = original_url # Extrai extensão if '.' in original_filename: ext = original_filename.split('.')[-1].lower() # Normaliza extensão para jpg/jpeg if ext in ['jpeg', 'jpg']: ext = 'jpg' else: ext = 'jpg' # Cria caminho: categoria/nome.ext path = f"{categoria_norm}/{nome_norm}.{ext}" return path def get_media_requests(self, item, info): """ Faz download das imagens baseado no campo image_urls """ adapter = ItemAdapter(item) image_urls = adapter.get('image_urls', []) if not image_urls: return [] for image_url in image_urls: yield scrapy.Request(image_url) def item_completed(self, results, item, info): """ Atualiza campo image_local_path quando imagem é baixada """ if not results: return item # results é uma lista de tuplas (success, image_info_or_failure) for success, result in results: if success: adapter = ItemAdapter(item) adapter['image_local_path'] = result['path'] info.spider.logger.debug(f"Imagem salva: {result['path']}") return item