Aller au contenu
Développement medium

Extraire du contenu web pour vos pipelines RAG

20 min de lecture

Logo Trafilatura

Vous construisez un système RAG et avez besoin d’extraire du contenu web propre ? Trafilatura résout ce problème : il télécharge une page HTML, supprime automatiquement les menus, publicités, footers et autres éléments parasites, puis vous retourne uniquement le texte utile — avec les métadonnées (titre, auteur, date) prêtes pour l’indexation.

Ce guide vous montre comment utiliser Trafilatura 2.0.0 en Python, de l’extraction basique jusqu’à la constitution d’un corpus complet pour RAG.

Quand vous téléchargez une page web, vous récupérez du HTML brut contenant :

  • Le contenu utile (l’article que vous voulez)
  • Des menus de navigation
  • Des publicités et trackers
  • Des sidebars, footers, pop-ups
  • Du JavaScript et du CSS

Pour un pipeline RAG, ce bruit dégrade la qualité de votre index vectoriel. Vos recherches sémantiques retournent des fragments de menus ou de publicités au lieu du contenu pertinent.

Pourquoi Trafilatura plutôt qu’une autre bibliothèque ?

Section intitulée « Pourquoi Trafilatura plutôt qu’une autre bibliothèque ? »
CritèreTrafilaturaBeautifulSoupnewspaper3k
Extraction intelligente✓ Automatique✗ Manuelle (CSS selectors)✓ Limité
Métadonnées✓ Titre, auteur, date✓ Partiel
Export JSON✓ Natif
Langues non-anglaises✓ ExcellentN/A✗ Anglais-centré
Déduplication✓ Simhash intégré
Sitemaps/RSS✓ Intégré

BeautifulSoup est puissant mais nécessite d’écrire des sélecteurs CSS/XPath pour chaque site. newspaper3k est abandonné et fonctionne mal sur les sites non-anglophones. Trafilatura offre une extraction “plug and play” qui fonctionne sur la majorité des sites.

  1. Créez un environnement virtuel pour isoler les dépendances :

    Fenêtre de terminal
    python -m venv venv
    source venv/bin/activate # Linux/macOS
    # ou: venv\Scripts\activate # Windows
  2. Installez Trafilatura avec toutes les dépendances optionnelles :

    Fenêtre de terminal
    pip install "trafilatura[all]" lxml_html_clean

    Le package lxml_html_clean est requis depuis la version 2.0.

  3. Vérifiez l’installation :

    Fenêtre de terminal
    trafilatura --version

    Vous devriez voir : Trafilatura 2.0.0 - Python 3.12.x

Avant de coder, testez Trafilatura en ligne de commande pour comprendre ce qu’il fait.

Fenêtre de terminal
trafilatura -u "https://fr.wikipedia.org/wiki/Python_(langage)"

Résultat : Trafilatura affiche uniquement le contenu de l’article Wikipedia, sans les menus, sidebars ou footers. Vous obtenez plusieurs milliers de caractères de texte propre.

Pour un pipeline RAG, vous avez besoin du texte et des métadonnées :

Fenêtre de terminal
trafilatura -u "https://fr.wikipedia.org/wiki/Python_(langage)" --json

Résultat : Un objet JSON structuré :

{
"title": "Python (langage)",
"author": null,
"date": "2025-02-10",
"text": "Python est un langage de programmation...",
"source": "https://fr.wikipedia.org/wiki/Python_(langage)",
"hostname": "fr.wikipedia.org"
}

Vous avez maintenant le titre, la date, la source — tout ce qu’il faut pour enrichir vos chunks lors de l’indexation.

Si vous voulez garder les titres et les listes :

Fenêtre de terminal
trafilatura -u "https://fr.wikipedia.org/wiki/Python_(langage)" --markdown

Résultat : Le texte avec la hiérarchie ##, ###, les listes à puces, etc. Utile si votre chunker exploite la structure du document.

La ligne de commande est utile pour tester, mais en production vous utiliserez l’API Python.

from trafilatura import fetch_url, extract
# 1. Télécharger le HTML
url = "https://fr.wikipedia.org/wiki/Python_(langage)"
html = fetch_url(url)
# 2. Extraire le texte
texte = extract(html)
# 3. Afficher un aperçu
print(f"Longueur: {len(texte)} caractères")
print(texte[:500])

Ce que fait ce code :

  1. fetch_url() télécharge la page et retourne le HTML brut
  2. extract() analyse le HTML et retourne uniquement le contenu textuel
  3. Vous obtenez une chaîne de caractères prête à être découpée en chunks

Pour un pipeline RAG, le texte seul ne suffit pas. Vous avez besoin de la source, de la date, du titre — ces métadonnées permettent de filtrer les résultats et d’afficher des citations.

import json
from trafilatura import fetch_url, extract
url = "https://fr.wikipedia.org/wiki/Python_(langage)"
html = fetch_url(url)
# Extraire en JSON avec les métadonnées
resultat = extract(
html,
output_format="json",
with_metadata=True
)
# Parser le JSON
data = json.loads(resultat)
print(f"Titre: {data['title']}")
print(f"Date: {data['date']}")
print(f"Source: {data['source']}")
print(f"Texte ({len(data['text'])} car.): {data['text'][:200]}...")

Sortie attendue :

Titre: Python (langage)
Date: 2025-02-10
Source: https://fr.wikipedia.org/wiki/Python_\(langage\)
Texte (45231 car.): Python est un langage de programmation...

Si votre stratégie de chunking exploite la structure du document (découper par sections), exportez en Markdown :

from trafilatura import fetch_url, extract
url = "https://fr.wikipedia.org/wiki/Python_(langage)"
html = fetch_url(url)
markdown = extract(html, output_format="markdown")
# Afficher les premières lignes
for ligne in markdown.split('\n')[:20]:
print(ligne)

Résultat : Les titres sont préfixés par ##, ###, les listes sont formatées — vous pouvez découper par section.

Selon votre cas d’usage, vous pouvez ajuster ce que Trafilatura extrait.

Par défaut, les liens sont supprimés. Pour les conserver (utile pour la navigation ou les citations) :

texte = extract(html, include_links=True)

Résultat : Les liens apparaissent au format [texte](url) dans la sortie.

Les tableaux sont inclus par défaut, mais vous pouvez les désactiver si vous ne les voulez pas :

texte = extract(html, include_tables=False)

Précision vs Rappel : le compromis qualité/quantité

Section intitulée « Précision vs Rappel : le compromis qualité/quantité »

Pour un pipeline RAG où la qualité prime :

# Mode précision : moins de bruit, idéal pour RAG
texte = extract(html, favor_precision=True)

Pour des pages complexes où vous voulez tout récupérer :

# Mode rappel : plus de contenu, risque de bruit
texte = extract(html, favor_recall=True)

Recommandation RAG : Utilisez favor_precision=True par défaut. Le bruit dans votre index vectoriel dégrade la pertinence des recherches.

Voici la configuration recommandée pour un pipeline RAG :

resultat = extract(
html,
output_format="json", # Format structuré
with_metadata=True, # Titre, date, source
include_links=True, # Conserver les références
include_tables=True, # Inclure les tableaux
favor_precision=True # Minimiser le bruit
)

Extraire une page, c’est bien. Extraire 500 pages pour constituer un corpus, c’est mieux. Trafilatura offre des outils pour le batch processing.

import json
from trafilatura import extract
from trafilatura.downloads import (
add_to_compressed_dict,
buffered_downloads,
load_download_buffer
)
# Liste des URLs à traiter
urls = [
"https://fr.wikipedia.org/wiki/Python_(langage)",
"https://fr.wikipedia.org/wiki/JavaScript",
"https://fr.wikipedia.org/wiki/Rust_(langage)",
]
# Préparer le gestionnaire d'URLs
url_store = add_to_compressed_dict(urls)
bufferlist, url_store = load_download_buffer(url_store, sleep_time=1)
# Télécharger en parallèle (4 threads)
corpus = []
for url, html_content in buffered_downloads(bufferlist, download_threads=4):
if html_content:
resultat = extract(html_content, output_format="json", with_metadata=True)
if resultat:
corpus.append(json.loads(resultat))
print(f"✓ {url}")
print(f"\n{len(corpus)} documents extraits")

Ce que fait ce code :

  1. add_to_compressed_dict() crée un store optimisé pour les URLs
  2. load_download_buffer() prépare les téléchargements avec un délai de 1s
  3. buffered_downloads() télécharge en parallèle (4 threads ici)
  4. Chaque page est extraite et ajoutée au corpus

Plutôt que de lister manuellement les URLs, Trafilatura peut explorer un site.

Via le sitemap :

from trafilatura import sitemap_search
# Récupérer toutes les URLs du sitemap
urls = sitemap_search("https://blog.stephane-robert.info")
print(f"{len(urls)} URLs trouvées dans le sitemap")

Via les flux RSS :

from trafilatura import feeds_search
# Récupérer les URLs des flux RSS
urls = feeds_search("https://blog.stephane-robert.info")
print(f"{len(urls)} URLs trouvées dans les flux RSS")

Via le crawler :

from trafilatura.spider import focused_crawler
# Explorer le site (max 50 pages)
urls_trouvees = focused_crawler(
"https://blog.stephane-robert.info",
max_seen_urls=50,
max_known_urls=100
)
print(f"{len(urls_trouvees)} URLs découvertes par le crawler")

Quand vous crawlez un site, vous pouvez récupérer plusieurs fois le même contenu (pages dupliquées, versions multiples). Trafilatura intègre Simhash pour détecter les doublons.

Simhash calcule une empreinte du contenu. Deux textes similaires auront des empreintes proches. En comparant les empreintes, vous détectez les doublons sans comparer caractère par caractère.

from trafilatura.deduplication import Simhash
# Créer des empreintes
texte1 = "Python est un langage de programmation interprété"
texte2 = "Python est un langage de programmation interprété" # Identique
texte3 = "JavaScript est un langage de programmation" # Différent
hash1 = Simhash(texte1)
hash2 = Simhash(texte2)
hash3 = Simhash(texte3)
# Comparer les similarités
print(f"Similarité 1-2: {hash1.similarity(hash2)}") # 1.0 (identique)
print(f"Similarité 1-3: {hash1.similarity(hash3)}") # ~0.6 (différent)
from trafilatura import fetch_url, extract
from trafilatura.deduplication import Simhash
SEUIL_DOUBLON = 0.95 # 95% de similarité = doublon
urls = [
"https://example.com/article-v1",
"https://example.com/article-v2", # Peut être un doublon
"https://example.com/autre-article",
]
hashes_vus = []
corpus = []
for url in urls:
html = fetch_url(url)
texte = extract(html)
if texte:
# Calculer l'empreinte
hash_contenu = Simhash(texte)
# Vérifier si c'est un doublon
est_doublon = any(
hash_contenu.similarity(h) > SEUIL_DOUBLON
for h in hashes_vus
)
if not est_doublon:
hashes_vus.append(hash_contenu)
corpus.append({"url": url, "texte": texte})
print(f"✓ Ajouté: {url}")
else:
print(f"✗ Doublon ignoré: {url}")
print(f"\n{len(corpus)} documents uniques")

Voici un script complet qui combine tout ce que nous avons vu :

import json
from pathlib import Path
from trafilatura import fetch_url, extract, sitemap_search
from trafilatura.deduplication import Simhash
def creer_corpus_rag(
site_url: str,
output_dir: str = "corpus",
max_pages: int = 100
) -> list[dict]:
"""
Crée un corpus RAG à partir d'un site web.
1. Découvre les URLs via le sitemap
2. Extrait le contenu avec métadonnées
3. Déduplique automatiquement
4. Sauvegarde en JSON
"""
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
# 1. Découvrir les URLs
print(f"Découverte des URLs de {site_url}...")
urls = sitemap_search(site_url)
urls = list(urls)[:max_pages]
print(f" {len(urls)} URLs à traiter")
# 2. Extraire et dédupliquer
hashes_vus = []
documents = []
SEUIL = 0.95
for i, url in enumerate(urls):
print(f"[{i+1}/{len(urls)}] {url[:60]}...")
try:
html = fetch_url(url)
if not html:
print(" ✗ Échec téléchargement")
continue
resultat = extract(
html,
output_format="json",
with_metadata=True,
include_links=True,
favor_precision=True
)
if not resultat:
print(" ✗ Pas de contenu")
continue
data = json.loads(resultat)
texte = data.get("text", "")
# Déduplication
hash_contenu = Simhash(texte)
if any(hash_contenu.similarity(h) > SEUIL for h in hashes_vus):
print(" ✗ Doublon")
continue
hashes_vus.append(hash_contenu)
# Structurer le document
doc = {
"id": f"doc_{i:04d}",
"source": url,
"title": data.get("title", "Sans titre"),
"author": data.get("author"),
"date": data.get("date"),
"text": texte,
}
documents.append(doc)
# Sauvegarder
fichier = output_path / f"{doc['id']}.json"
fichier.write_text(json.dumps(doc, ensure_ascii=False, indent=2))
print(f" ✓ {doc['title'][:40]}...")
except Exception as e:
print(f" ✗ Erreur: {e}")
# 3. Créer l'index
index = {
"site": site_url,
"total": len(documents),
"documents": [
{"id": d["id"], "title": d["title"], "source": d["source"]}
for d in documents
]
}
(output_path / "index.json").write_text(
json.dumps(index, ensure_ascii=False, indent=2)
)
print(f"\n{'='*50}")
print(f"Corpus créé: {len(documents)} documents")
print(f"Sauvegardé dans: {output_path}")
return documents
# Utilisation
if __name__ == "__main__":
docs = creer_corpus_rag(
"https://blog.stephane-robert.info",
output_dir="corpus_devops",
max_pages=50
)

Pour réutiliser une configuration sur plusieurs extractions :

from trafilatura import fetch_url, extract
from trafilatura.settings import Extractor
# Configuration réutilisable
config = Extractor(
output_format="json",
links=True, # Équivaut à include_links
tables=True, # Équivaut à include_tables
precision=True, # Équivaut à favor_precision
with_metadata=True
)
# Utiliser sur plusieurs pages
for url in urls:
html = fetch_url(url)
resultat = extract(html, options=config)

Accès direct aux métadonnées avec bare_extraction

Section intitulée « Accès direct aux métadonnées avec bare_extraction »

Pour un accès programmatique sans passer par JSON :

from trafilatura import fetch_url
from trafilatura.core import bare_extraction
html = fetch_url("https://example.com/article")
doc = bare_extraction(html, with_metadata=True)
# Accès direct aux attributs
print(doc.title)
print(doc.author)
print(doc.date)
print(doc.text[:200])
Fenêtre de terminal
pip install lxml_html_clean

Cette dépendance est requise depuis Trafilatura 2.0.

Causes possibles :

  • La page nécessite JavaScript (Trafilatura ne l’exécute pas)
  • Le contenu est derrière un paywall
  • Le User-Agent est bloqué

Solutions :

  1. Essayez avec favor_recall=True pour être moins strict
  2. Pour JavaScript : téléchargez d’abord avec Playwright/Selenium, puis passez le HTML à extract()
  3. Configurez un User-Agent personnalisé dans un fichier settings.cfg
texte = extract(html, max_tree_size=5000000)

Augmente la limite de taille de l’arbre DOM.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn