Files
Effi/docs/WEB-SCRAPER-TOOL.md
Folzo 93f31cdaeb Ajout Effi-OCR (Tesseract) + Web Scraper pour OpenWebUI
- Effi-OCR: Service OCR avec Tesseract (multilingue FR/EN)
  * API FastAPI sur port 8300
  * Prétraitement images (contraste, niveaux de gris)
  * Tool OpenWebUI intégré

- Web Scraper: Service Python (BeautifulSoup) sur port 11235
  * Alternative à Crawl4AI (incompatible ARM64/J5040)
  * Scraping HTML basique mais fonctionnel
  * Intégré au docker-compose d'effi-server

- Documentation:
  * ROADMAP-FONCTIONNALITES.md: Plan des 9 fonctionnalités
  * WEB-SCRAPER-CONFIG.md: Configuration OpenWebUI
  * WEB-SCRAPER-TOOL.md: Code du Tool

- Script start-all-services.sh mis à jour (6 services)
2026-04-06 16:34:27 +02:00

6.8 KiB

Tool Web Scraping pour OpenWebUI

Configuration

Dans OpenWebUI (http://192.168.1.27:3000) :

  1. Aller dans : Admin → Tools → Create Tool
  2. Nom : Web Scraper
  3. ID : web_scraper
  4. Description : Extrait le contenu texte de n'importe quelle URL web

Code du Tool

from typing import Optional
import requests
from urllib.parse import urlparse
import re

class Tools:
    def __init__(self):
        """Initialize Web Scraper Tool"""
        self.timeout = 15
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.0'
        }
    
    def scrape_url(self, url: str, max_chars: int = 5000) -> str:
        """
        Scrape le contenu texte d'une URL web
        
        Args:
            url: L'URL à scraper (doit commencer par http:// ou https://)
            max_chars: Nombre maximum de caractères à retourner (défaut: 5000)
        
        Returns:
            Le contenu texte extrait de la page
        """
        try:
            # Validation URL
            if not url.startswith(('http://', 'https://')):
                return "Erreur: L'URL doit commencer par http:// ou https://"
            
            parsed = urlparse(url)
            if not parsed.netloc:
                return "Erreur: URL invalide"
            
            # Télécharger la page
            response = requests.get(url, headers=self.headers, timeout=self.timeout)
            response.raise_for_status()
            
            # Détecter l'encodage
            if response.encoding == 'ISO-8859-1':
                response.encoding = response.apparent_encoding
            
            html = response.text
            
            # Extraction simple du texte (sans BeautifulSoup pour garder le Tool léger)
            # Supprimer les scripts et styles
            text = re.sub(r'<script[^>]*>[^<]*</script>', '', html, flags=re.IGNORECASE | re.DOTALL)
            text = re.sub(r'<style[^>]*>[^<]*</style>', '', text, flags=re.IGNORECASE | re.DOTALL)
            
            # Supprimer les balises HTML
            text = re.sub(r'<[^>]+>', ' ', text)
            
            # Nettoyer le texte
            text = re.sub(r'\s+', ' ', text)  # Espaces multiples
            text = text.strip()
            
            # Décoder les entités HTML
            text = text.replace('&amp;', '&')
            text = text.replace('&lt;', '<')
            text = text.replace('&gt;', '>')
            text = text.replace('&quot;', '"')
            text = text.replace('&#39;', "'")
            text = text.replace('&nbsp;', ' ')
            
            # Tronquer si nécessaire
            if len(text) > max_chars:
                text = text[:max_chars] + f"\n\n[...] {len(text) - max_chars} caractères supplémentaires"
            
            return f"Contenu extrait de {url}:\n\n{text}"
            
        except requests.exceptions.Timeout:
            return f"Erreur: Timeout après {self.timeout}s pour {url}"
        except requests.exceptions.ConnectionError:
            return f"Erreur: Impossible de se connecter à {url}"
        except requests.exceptions.HTTPError as e:
            return f"Erreur HTTP {e.response.status_code}: {url}"
        except Exception as e:
            return f"Erreur lors du scraping: {str(e)}"
    
    def scrape_article(self, url: str) -> str:
        """
        Scrape un article en essayant d'extraire le contenu principal uniquement
        
        Args:
            url: L'URL de l'article
        
        Returns:
            Le contenu principal de l'article (titre + texte)
        """
        try:
            if not url.startswith(('http://', 'https://')):
                return "Erreur: L'URL doit commencer par http:// ou https://"
            
            response = requests.get(url, headers=self.headers, timeout=self.timeout)
            response.raise_for_status()
            
            if response.encoding == 'ISO-8859-1':
                response.encoding = response.apparent_encoding
            
            html = response.text
            
            # Extraire le titre
            title_match = re.search(r'<title[^>]*>([^<]*)</title>', html, re.IGNORECASE)
            title = title_match.group(1).strip() if title_match else "Titre non trouvé"
            
            # Essayer d'extraire le contenu article (heuristique simple)
            # Chercher les balises article, main, ou div avec classe contenant "content"
            article_patterns = [
                r'<article[^>]*>(.*?)</article>',
                r'<main[^>]*>(.*?)</main>',
                r'<div[^>]*class=["\'][^"\']*(?:content|article|post)[^"\']*["\'][^>]*>(.*?)</div>',
            ]
            
            content = None
            for pattern in article_patterns:
                match = re.search(pattern, html, re.IGNORECASE | re.DOTALL)
                if match:
                    content = match.group(1)
                    break
            
            # Si pas trouvé, prendre tout le body
            if not content:
                body_match = re.search(r'<body[^>]*>(.*?)</body>', html, re.IGNORECASE | re.DOTALL)
                content = body_match.group(1) if body_match else html
            
            # Nettoyer le contenu
            content = re.sub(r'<script[^>]*>[^<]*</script>', '', content, flags=re.IGNORECASE | re.DOTALL)
            content = re.sub(r'<style[^>]*>[^<]*</style>', '', content, flags=re.IGNORECASE | re.DOTALL)
            content = re.sub(r'<[^>]+>', ' ', content)
            content = re.sub(r'\s+', ' ', content).strip()
            
            # Décoder les entités HTML
            content = content.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>')
            content = content.replace('&quot;', '"').replace('&#39;', "'")
            
            # Limiter la taille
            if len(content) > 8000:
                content = content[:8000] + "\n\n[...] Contenu tronqué"
            
            return f"Titre: {title}\nURL: {url}\n\n{content}"
            
        except Exception as e:
            return f"Erreur: {str(e)}"

Utilisation

Une fois activé, les agents peuvent utiliser :

  • scrape_url("https://example.com/article") - Extraire tout le texte d'une page
  • scrape_article("https://example.com/article") - Extraire le contenu article uniquement

Exemples de prompts

"Effi, résume-moi cet article : https://..."
→ L'agent utilise scrape_article automatiquement

"Qu'est-ce qui est écrit sur cette page ? https://..."
→ L'agent utilise scrape_url

"Cherche des infos sur X et lis les 3 premiers liens"
→ Web search + scrape_url pour chaque résultat

Limitations

  • Extraction basique (pas de JavaScript)
  • Timeout 15 secondes
  • Sites avec protection anti-bot peuvent bloquer
  • Ne fonctionne pas avec les paywalls

Tool créé le 6 Avril 2026