Spaces:
Sleeping
Sleeping
| """ | |
| Module principal de l'application Web Scraper et Convertisseur Markdown. | |
| """ | |
| import os | |
| import logging | |
| import time | |
| from typing import Dict, Optional, Union, List | |
| from urllib.parse import urlparse | |
| import pathlib | |
| from dotenv import load_dotenv | |
| from src.web2llm.app.scraper.scraper import WebScraper | |
| from src.web2llm.app.converter.converter import MarkdownConverter | |
| # Configuration du logging | |
| logging.basicConfig(level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Chargement des variables d'environnement | |
| load_dotenv() | |
| # Configuration | |
| OUTPUT_DIR = os.getenv('OUTPUT_DIR', './output') | |
| DEFAULT_FILENAME = os.getenv('DEFAULT_FILENAME', 'scraped_content') | |
| class WebToMarkdown: | |
| """Classe principale combinant le scraping et la conversion en Markdown.""" | |
| def __init__(self, output_dir: str = OUTPUT_DIR): | |
| """ | |
| Initialise l'outil. | |
| Args: | |
| output_dir: Répertoire où sauvegarder les fichiers Markdown | |
| """ | |
| self.scraper = WebScraper() | |
| self.converter = MarkdownConverter() | |
| self.output_dir = output_dir | |
| # S'assurer que le répertoire de sortie existe | |
| os.makedirs(self.output_dir, exist_ok=True) | |
| def generate_filename(self, url: str, title: Optional[str] = None, extension: str = '.md') -> str: | |
| """ | |
| Génère un nom de fichier valide à partir de l'URL ou du titre. | |
| Args: | |
| url: L'URL de la page | |
| title: Le titre de la page (optionnel) | |
| extension: L'extension du fichier (.md par défaut) | |
| Returns: | |
| Un nom de fichier valide | |
| """ | |
| if title: | |
| # Nettoyer le titre pour en faire un nom de fichier valide | |
| safe_title = "".join([c if c.isalnum() or c in [' ', '-', '_'] else "_" for c in title]) | |
| safe_title = safe_title.strip() | |
| filename = safe_title[:100] # Limiter la longueur mais permettre des noms plus longs | |
| else: | |
| # Utiliser l'URL | |
| parsed_url = urlparse(url) | |
| hostname = parsed_url.netloc | |
| path = parsed_url.path.strip('/') | |
| filename = f"{hostname}_{path}".replace('/', '_') | |
| # Remplacer les espaces par des tirets | |
| filename = filename.replace(' ', '-') | |
| # S'assurer que le nom se termine par l'extension spécifiée | |
| if not filename.endswith(extension): | |
| filename += extension | |
| return filename | |
| def save_raw_html(self, html_content: str, filepath: str) -> bool: | |
| """ | |
| Sauvegarde le contenu HTML brut dans un fichier. | |
| Args: | |
| html_content: Le contenu HTML | |
| filepath: Chemin où sauvegarder le fichier | |
| Returns: | |
| True si la sauvegarde a réussi, False sinon | |
| """ | |
| try: | |
| # S'assurer que le répertoire existe | |
| os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) | |
| with open(filepath, 'w', encoding='utf-8') as f: | |
| f.write(html_content) | |
| logger.info(f"Contenu HTML sauvegardé avec succès dans {filepath}") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Erreur lors de la sauvegarde du fichier HTML: {str(e)}") | |
| return False | |
| def process_url(self, url: str, save: bool = False, | |
| filename: Optional[str] = None) -> Dict[str, Union[str, None, bool]]: | |
| """ | |
| Traite une URL: scraping, nettoyage et conversion en Markdown. | |
| Args: | |
| url: L'URL à traiter | |
| save: Si True, sauvegarde le résultat dans un fichier | |
| filename: Nom du fichier pour la sauvegarde | |
| Returns: | |
| Dictionnaire avec les résultats et le statut | |
| """ | |
| result = { | |
| "url": url, | |
| "title": None, | |
| "markdown": None, | |
| "saved": False, | |
| "saved_path": None, | |
| "success": False, | |
| "error": None, | |
| "html_saved": False, | |
| "html_saved_path": None | |
| } | |
| try: | |
| # Définir l'URL de base pour la conversion des liens relatifs | |
| self.converter.base_url = url | |
| # Scraper l'URL | |
| logger.info(f"Scraping de l'URL: {url}") | |
| scraped_data = self.scraper.scrape(url, clean=True, extract_text=True) | |
| # Stocker le titre | |
| result["title"] = scraped_data["title"] | |
| if not scraped_data["clean_html"]: | |
| result["error"] = "Impossible de récupérer ou nettoyer le contenu HTML" | |
| return result | |
| # Conversion en Markdown | |
| logger.info("Conversion du HTML en Markdown") | |
| markdown_content = self.converter.html_to_markdown( | |
| scraped_data["clean_html"], url) | |
| # Vérifier si la conversion a produit un résultat significatif | |
| if not markdown_content or len(markdown_content) < 100: | |
| logger.warning("Conversion en Markdown insuffisante, tentative avec le texte brut") | |
| # Si le texte brut est disponible, l'utiliser comme alternative | |
| if scraped_data["text_content"]: | |
| markdown_content = scraped_data["text_content"] | |
| else: | |
| # Dernière tentative: extraire le texte à partir du HTML nettoyé | |
| from bs4 import BeautifulSoup | |
| soup = BeautifulSoup(scraped_data["clean_html"], 'html.parser') | |
| markdown_content = soup.get_text(separator='\n\n', strip=True) | |
| # Mise à jour du résultat | |
| result["markdown"] = markdown_content | |
| result["success"] = True | |
| # Sauvegarde si demandée | |
| if save: | |
| # Générer un nom de fichier si non spécifié | |
| if not filename: | |
| filename = self.generate_filename(url, result["title"]) | |
| # S'assurer que l'extension est .md | |
| elif not filename.endswith('.md'): | |
| filename += '.md' | |
| filepath = os.path.join(self.output_dir, filename) | |
| # Enregistrer le fichier Markdown | |
| saved = self.converter.save_markdown(markdown_content, filepath) | |
| result["saved"] = saved | |
| result["saved_path"] = filepath if saved else None | |
| # Si la conversion en Markdown n'est pas optimale, sauvegarder aussi le HTML | |
| if len(markdown_content) < 500 or "<" in markdown_content: | |
| html_filename = filename.replace('.md', '.html') | |
| html_filepath = os.path.join(self.output_dir, html_filename) | |
| html_saved = self.save_raw_html(scraped_data["clean_html"], html_filepath) | |
| result["html_saved"] = html_saved | |
| result["html_saved_path"] = html_filepath if html_saved else None | |
| if html_saved: | |
| logger.info(f"Le HTML a été sauvegardé en complément dans {html_filepath}") | |
| return result | |
| except Exception as e: | |
| logger.error(f"Erreur lors du traitement de l'URL {url}: {str(e)}") | |
| result["error"] = str(e) | |
| # En cas d'erreur, tenter de sauvegarder le HTML brut si disponible | |
| if save and scraped_data and "raw_html" in scraped_data and scraped_data["raw_html"]: | |
| if not filename: | |
| filename = self.generate_filename(url, result["title"], '.html') | |
| else: | |
| filename = filename.replace('.md', '.html') | |
| html_filepath = os.path.join(self.output_dir, filename) | |
| html_saved = self.save_raw_html(scraped_data["raw_html"], html_filepath) | |
| result["html_saved"] = html_saved | |
| result["html_saved_path"] = html_filepath if html_saved else None | |
| if html_saved: | |
| logger.info(f"Sauvegarde de secours du HTML brut dans {html_filepath}") | |
| return result | |
| def process_multiple_urls(self, urls: List[str], save: bool = True) -> Dict[str, List[Dict]]: | |
| """ | |
| Traite plusieurs URLs en parallèle. | |
| Args: | |
| urls: Liste d'URLs à traiter | |
| save: Si True, sauvegarde les résultats | |
| Returns: | |
| Dictionnaire contenant les résultats pour chaque URL | |
| """ | |
| results = [] | |
| for url in urls: | |
| result = self.process_url(url, save=save) | |
| results.append(result) | |
| return { | |
| "total": len(urls), | |
| "success": sum(1 for r in results if r["success"]), | |
| "results": results | |
| } | |
| # Fonction pour une utilisation rapide en ligne de commande | |
| def process_url(url: str, save: bool = False, filename: Optional[str] = None) -> Dict: | |
| """ | |
| Fonction utilitaire pour traiter rapidement une URL. | |
| Args: | |
| url: L'URL à traiter | |
| save: Si True, sauvegarde le résultat | |
| filename: Nom du fichier pour la sauvegarde | |
| Returns: | |
| Dictionnaire avec les résultats | |
| """ | |
| processor = WebToMarkdown() | |
| return processor.process_url(url, save, filename) | |
| if __name__ == "__main__": | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Scraper et convertisseur Markdown") | |
| parser.add_argument("url", help="URL à scraper") | |
| parser.add_argument("--save", action="store_true", help="Sauvegarder en fichier Markdown") | |
| parser.add_argument("--output", help="Nom du fichier de sortie") | |
| parser.add_argument("--dir", help="Répertoire de sortie", default=OUTPUT_DIR) | |
| args = parser.parse_args() | |
| processor = WebToMarkdown(output_dir=args.dir) | |
| result = processor.process_url(args.url, save=args.save, filename=args.output) | |
| if result["success"]: | |
| print(f"Titre: {result['title']}") | |
| print("\nContenu Markdown:") | |
| print("-------------------") | |
| print(result["markdown"][:500] + "..." if len(result["markdown"]) > 500 else result["markdown"]) | |
| if result["saved"]: | |
| print(f"\nFichier sauvegardé: {result['saved_path']}") | |
| if result["html_saved"]: | |
| print(f"\nFichier HTML sauvegardé: {result['html_saved_path']}") | |
| else: | |
| print(f"Erreur: {result['error']}") | |
| if result["html_saved"]: | |
| print(f"\nFichier HTML de secours sauvegardé: {result['html_saved_path']}") |