- 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)
189 lines
6.8 KiB
Markdown
189 lines
6.8 KiB
Markdown
# 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
|
|
|
|
```python
|
|
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('&', '&')
|
|
text = text.replace('<', '<')
|
|
text = text.replace('>', '>')
|
|
text = text.replace('"', '"')
|
|
text = text.replace(''', "'")
|
|
text = text.replace(' ', ' ')
|
|
|
|
# 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('&', '&').replace('<', '<').replace('>', '>')
|
|
content = content.replace('"', '"').replace(''', "'")
|
|
|
|
# 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*
|