import hashlib from datetime import datetime import logging import newspaper from newspaper import Config from concurrent.futures import ThreadPoolExecutor, as_completed def _process_individual_article(article_url, config): """ Función auxiliar que descarga y procesa un solo artículo. Está diseñada para ser ejecutada en un hilo separado. """ try: # Es crucial crear un nuevo objeto Article dentro de cada hilo. article = newspaper.Article(article_url, config=config) article.download() # Un artículo necesita ser parseado para tener título, texto, etc. article.parse() # Si no se pudo obtener título o texto, no es un artículo válido. if not article.title or not article.text: return None # El método nlp() es necesario para el resumen. article.nlp() return article except Exception: # Ignoramos errores en artículos individuales (p.ej., enlaces rotos, etc.) return None def process_newspaper_url(source_name, url, categoria_id, pais_id, idioma='es'): """ Explora la URL de un periódico, extrae los artículos que encuentra en paralelo y devuelve una lista de noticias listas para la base de datos. """ logging.info(f"Iniciando el scrapeo en paralelo de la fuente: {url} (idioma: {idioma})") todas_las_noticias = [] try: config = Config() config.browser_user_agent = 'RssApp/1.0 (Scraper)' config.request_timeout = 15 # Timeout más corto para artículos individuales. config.memoize_articles = False # No guardar en caché para obtener siempre lo último. # Usamos el idioma proporcionado para mejorar la extracción source = newspaper.build(url, config=config, language=idioma) # Limitar el número de artículos para no sobrecargar el servidor. articles_to_process = source.articles[:25] logging.info(f"Fuente construida. Procesando {len(articles_to_process)} artículos en paralelo...") # Usamos un ThreadPoolExecutor para procesar los artículos concurrentemente. with ThreadPoolExecutor(max_workers=10) as executor: # Creamos un futuro para cada URL de artículo. future_to_article = {executor.submit(_process_individual_article, article.url, config): article for article in articles_to_process} for future in as_completed(future_to_article): processed_article = future.result() # Si el artículo se procesó correctamente, lo añadimos a la lista. if processed_article: noticia_id = hashlib.md5(processed_article.url.encode()).hexdigest() if processed_article.summary: resumen = processed_article.summary else: # Fallback a un extracto del texto si no hay resumen. resumen = (processed_article.text[:400] + '...') if len(processed_article.text) > 400 else processed_article.text fecha = processed_article.publish_date if processed_article.publish_date else datetime.now() # --- LÍNEA CLAVE --- # Añadimos 'source_name' a la tupla de datos todas_las_noticias.append(( noticia_id, processed_article.title, resumen, processed_article.url, fecha, processed_article.top_image or '', source_name, categoria_id, pais_id )) if not todas_las_noticias: return [], "No se encontraron artículos válidos en la URL proporcionada." return todas_las_noticias, f"Se procesaron {len(todas_las_noticias)} noticias con éxito." except Exception as e: logging.error(f"Excepción al construir la fuente desde '{url}': {e}", exc_info=True) return [], f"Error al explorar la URL principal: {e}"