File size: 11,058 Bytes
698ce3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
"""
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']}")