""" Modulo per download e decompressione file IMDb Sostituisce ScaricaFile() e la decompressione PowerShell del VBA """ import gzip import shutil from pathlib import Path from typing import Dict import requests from tqdm import tqdm import logging logger = logging.getLogger(__name__) class ImdbDownloader: """Gestisce download e decompressione dei dataset IMDb""" def __init__(self, download_dir: Path): """ Args: download_dir: Directory dove salvare i file """ self.download_dir = Path(download_dir) self.download_dir.mkdir(parents=True, exist_ok=True) def download_file(self, url: str, filename: str) -> Path: """ Scarica un file da URL con progress bar Sostituisce: VBA ScaricaFile() con MSXML2.XMLHTTP Args: url: URL del file da scaricare filename: Nome del file di destinazione Returns: Path del file scaricato """ filepath = self.download_dir / filename logger.info(f"Download {filename} da {url}") try: response = requests.get(url, stream=True, timeout=300) response.raise_for_status() total_size = int(response.headers.get('content-length', 0)) with open(filepath, 'wb') as f: with tqdm( total=total_size, unit='B', unit_scale=True, unit_divisor=1024, desc=filename ) as pbar: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) pbar.update(len(chunk)) logger.info(f"Download completato: {filepath}") return filepath except requests.RequestException as e: logger.error(f"Errore download {filename}: {e}") raise def decompress_gzip(self, gz_file: Path, output_file: Path = None) -> Path: """ Decomprime un file .gz Sostituisce: PowerShell GZipStream del VBA Args: gz_file: File .gz da decomprimere output_file: File di output (opzionale, default rimuove .gz) Returns: Path del file decompresso """ if output_file is None: # Rimuove .gz dall'estensione output_file = gz_file.with_suffix('') logger.info(f"Decompressione {gz_file.name} -> {output_file.name}") try: with gzip.open(gz_file, 'rb') as f_in: with open(output_file, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) logger.info(f"Decompressione completata: {output_file}") return output_file except Exception as e: logger.error(f"Errore decompressione {gz_file.name}: {e}") raise def rename_to_txt(self, tsv_file: Path) -> Path: """ Rinomina .tsv in .txt (come nel VBA originale) Args: tsv_file: File .tsv da rinominare Returns: Path del file rinominato """ txt_file = tsv_file.with_suffix('.txt') if tsv_file.exists(): tsv_file.rename(txt_file) logger.info(f"Rinominato {tsv_file.name} -> {txt_file.name}") return txt_file else: logger.warning(f"File non trovato: {tsv_file}") return tsv_file def download_and_extract_all(self, urls: Dict[str, str]) -> Dict[str, Path]: """ Download e estrazione di tutti i file IMDb Sostituisce: Loop VBA per download + decompressione Args: urls: Dictionary {nome_dataset: url} Returns: Dictionary {nome_dataset: path_file_txt} """ extracted_files = {} for name, url in urls.items(): try: # 1. Download .gz gz_filename = f"{name}.tsv.gz" gz_file = self.download_file(url, gz_filename) # 2. Decomprimi .tsv tsv_file = self.decompress_gzip(gz_file) # 3. Rinomina .tsv -> .txt (come VBA) txt_file = self.rename_to_txt(tsv_file) extracted_files[name] = txt_file # Opzionale: elimina .gz per risparmiare spazio # gz_file.unlink() except Exception as e: logger.error(f"Errore elaborazione {name}: {e}") # Continua con i file successivi continue logger.info(f"Processati {len(extracted_files)}/{len(urls)} file") return extracted_files def main(): """Test del modulo""" import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from config import IMDB_URLS, TEMP_DIR logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) downloader = ImdbDownloader(TEMP_DIR) # Test con un singolo file (più piccolo) test_urls = {'title_ratings': IMDB_URLS['title_ratings']} files = downloader.download_and_extract_all(test_urls) print("\nFile processati:") for name, path in files.items(): print(f" {name}: {path}") if __name__ == '__main__': main()