Qu'est-ce que le DOM
Le DOM (Document Object Model) est la représentation d'un document HTML ou XML sous forme d'arbre d'objets. Chaque balise, attribut et fragment de texte devient un nœud (node), et l'imbrication des balises définit la hiérarchie « parent → enfant ».
Un parser DOM est un composant qui lit le balisage (une chaîne de caractères ou un flux d'octets) et en construit cet arbre en mémoire. Une fois l'arbre construit, on peut le manipuler par programmation : rechercher des éléments, lire des attributs, modifier, supprimer et ajouter des nœuds, ou le sérialiser à nouveau en texte.
Comment il fonctionne
Le parsing se déroule en deux étapes principales :
- Tokenisation — le flux de caractères est découpé en tokens (balise ouvrante, balise fermante, texte, commentaire, etc.).
- Construction de l'arbre — les tokens sont assemblés en une structure hiérarchique selon les règles d'imbrication. Pour le HTML, il s'agit des règles de la spécification WHATWG, qui « réparent » notamment le balisage incorrect (balises non fermées, imbrication invalide).
Parsing du DOM et parsing SAX
Il existe deux approches fondamentalement différentes pour analyser un document.
| Approche | Mémoire | Accès | Quand l'utiliser |
|---|---|---|---|
| DOM | Tout le document en mémoire | Accès aléatoire à n'importe quel nœud | Web scraping, traitement de fichiers de configuration, la plupart des tâches |
| SAX / en flux | Consommation quasi nulle | Séquentiel uniquement, par événements | Très gros XML (centaines de Mo à plusieurs Go) |
DOM charge le document en entier et offre un accès aléatoire pratique, mais nécessite une quantité de mémoire proportionnelle à la taille du document.
SAX ne construit pas d'arbre : il génère des événements (« balise ouverte », « balise fermée », « texte rencontré ») au fil de la lecture. La mémoire consommée est minime, mais le code est plus complexe à écrire et il n'y a pas d'accès aléatoire.
La plupart des bibliothèques populaires présentées ci-dessous sont des parseurs DOM.
Méthodes de recherche dans l'arbre
Une fois l'arbre construit, on recherche généralement les éléments de deux manières.
Les sélecteurs CSS — les mêmes qu'en intégration web : div.post > a, #main .title, ul li:first-child. Familiers aux développeurs front-end, très lisibles.
XPath — un langage de requête sur l'arbre, plus puissant que CSS : //div[@class="post"]/a/@href. Il prend en charge les conditions, la remontée vers le parent (..), la recherche par texte (//a[contains(text(),"Acheter")]). Plus courant en XML, mais de nombreuses bibliothèques HTML le supportent également.
Règle de choix : si CSS suffit, utilisez CSS, c'est plus lisible. XPath devient nécessaire quand il faut naviguer vers un parent/ancêtre, sélectionner par texte ou exprimer des conditions complexes.
Exemples dans différents langages
Tous les exemples résolvent la même tâche : trouver les liens à l'intérieur de div.post et afficher leur texte et leur href.
Python
Les bibliothèques les plus populaires sont BeautifulSoup (simple, tolérante) et lxml (rapide, basée sur la bibliothèque C libxml2, avec prise en charge de XPath).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Titre</h2>
<a href="https://example.com">Lien</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # le parser peut être remplacé par "html.parser"
# Recherche par balise
print(soup.find("h2").text)
# Sélecteurs CSS
for a in soup.select("div.post a"):
print(a.text, a.get("href"))
lxml avec XPath
from lxml import html as lxml_html
tree = lxml_html.fromstring(html)
hrefs = tree.xpath('//div[@class="post"]/a/@href')
print(hrefs) # ['https://example.com']
La bibliothèque standard de Python inclut également html.parser et xml.dom.minidom, qui ne nécessitent aucune installation.
JavaScript / Node.js
Dans le navigateur, le parser DOM est intégré — DOMParser et querySelector :
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
doc.querySelectorAll("div.post a").forEach(a => {
console.log(a.textContent, a.href);
});
Sous Node.js, on utilise Cheerio (léger, API de type jQuery) ou jsdom (émulation complète du DOM).
import * as cheerio from "cheerio";
const $ = cheerio.load(html);
$("div.post a").each((i, el) => {
console.log($(el).text(), $(el).attr("href"));
});
import { JSDOM } from "jsdom";
const dom = new JSDOM(html);
dom.window.document.querySelectorAll("div.post a")
.forEach(a => console.log(a.textContent, a.href));
Java
Le standard de facto est jsoup, très pratique pour le HTML avec des sélecteurs CSS.
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
Document doc = Jsoup.parse(html);
for (Element link : doc.select("div.post a")) {
System.out.println(link.text() + " " + link.attr("href"));
}
Pour du XML strict, le JDK fournit JAXP en standard (javax.xml.parsers.DocumentBuilder) — un parser DOM prêt à l'emploi, ainsi que SAX/StAX pour l'analyse en flux.
PHP
La classe intégrée DOMDocument avec prise en charge de XPath
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ supprime les avertissements liés au HTML « sale »
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}
Une alternative avec sélecteurs CSS — Symfony DomCrawler
C# / .NET
HtmlAgilityPack — un classique, fonctionne via XPath
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
foreach (var link in doc.DocumentNode.SelectNodes("//div[@class='post']/a"))
{
Console.WriteLine(link.InnerText + " " + link.GetAttributeValue("href", ""));
}
AngleSharp — plus moderne, avec un parsing conforme à la spécification et des sélecteurs CSS
var context = AngleSharp.BrowsingContext.New(AngleSharp.Configuration.Default);
var doc = await context.OpenAsync(req => req.Content(html));
foreach (var a in doc.QuerySelectorAll("div.post a"))
Console.WriteLine(a.TextContent + " " + a.GetAttribute("href"));
Go
goquery offre une API de type jQuery
import (
"fmt"
"strings"
"github.com/PuerkitoBio/goquery"
)
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(html))
doc.Find("div.post a").Each(func(i int, s *goquery.Selection) {
href, _ := s.Attr("href")
fmt.Println(s.Text(), href)
})
Sous le capot, goquery s'appuie sur le paquet officiel de bas niveau golang.org/x/net/html, que l'on peut aussi utiliser directement.
Ruby
Nokogiri — prend en charge à la fois CSS et XPath
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
end
Tableau récapitulatif des bibliothèques
| Langage | Bibliothèque | Sélecteurs | Particularités |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Simple, tolère les erreurs de balisage |
| Python | lxml | CSS + XPath | Rapide (backend C libxml2) |
| JS (navigateur) | DOMParser | CSS | Intégré au navigateur |
| Node.js | Cheerio | CSS | Légère, de type jQuery |
| Node.js | jsdom | CSS | Émulation complète du DOM |
| Java | jsoup | CSS | Standard de facto pour le HTML |
| PHP | DOMDocument | XPath | Intégrée à PHP |
| C# | HtmlAgilityPack | XPath | Classique de l'écosystème .NET |
| C# | AngleSharp | CSS | Moderne, parsing conforme à la spécification |
| Go | goquery | CSS | De type jQuery |
| Ruby | Nokogiri | CSS + XPath | Rapide (backend C) |
Remarques pratiques
Le HTML est « sale » par nature. Le balisage réel contient souvent des balises non fermées et des erreurs d'imbrication. Les parseurs orientés HTML (BeautifulSoup, jsoup, AngleSharp, Nokogiri en mode HTML) les tolèrent et les corrigent. Les parseurs XML stricts échoueront sur une telle entrée — le XML exige un balisage valide.
Un parser n'est pas un navigateur. Les bibliothèques listées analysent uniquement le HTML source renvoyé par le serveur. Si le contenu est chargé par JavaScript dans le navigateur, un simple parser DOM ne le verra pas. Pour ces sites, il faut des navigateurs headless :
Ils exécutent d'abord les scripts, puis renvoient le DOM final.
Performance. Les parseurs écrits en C (lxml, backend libxml2 de Nokogiri) sont d'un ordre de grandeur plus rapides que les implémentations en pur Python/Ruby. Pour les XML volumineux, préférez le SAX/StAX en flux au DOM afin de ne pas saturer la mémoire.
Choix des sélecteurs. Si CSS suffit, utilisez CSS, c'est plus lisible. XPath est nécessaire pour naviguer vers un parent/ancêtre, sélectionner par texte ou exprimer des conditions complexes.