Che cos’è il DOM
Il DOM (Document Object Model) è la rappresentazione di un documento HTML o XML sotto forma di albero di oggetti. Ogni tag, attributo e frammento di testo diventa un nodo (node), e l’annidamento dei tag definisce la gerarchia «padre → figlio».
Il parser del DOM è il componente che legge il markup (una stringa o un flusso di byte) e ne costruisce in memoria questo albero. Una volta costruito, con l’albero si può lavorare in modo programmatico: cercare elementi, leggere attributi, modificare, eliminare e aggiungere nodi, serializzarlo di nuovo in testo.
Come funziona
Il parsing avviene in due fasi principali:
- Tokenizzazione — il flusso di caratteri viene suddiviso in token (tag di apertura, tag di chiusura, testo, commento e così via).
- Costruzione dell’albero — i token vengono assemblati in una struttura gerarchica secondo le regole di annidamento. Per l’HTML sono le regole della specifica WHATWG, che tra l’altro «riparano» il markup scorretto (tag non chiusi, annidamenti sbagliati).
Parsing DOM contro SAX
Esistono due approcci radicalmente diversi all’analisi di un documento.
| Approccio | Memoria | Accesso | Quando usarlo |
|---|---|---|---|
| DOM | Tutto il documento in memoria | Accesso casuale a qualsiasi nodo | Web scraping, elaborazione di configurazioni, la maggior parte dei casi |
| SAX / streaming | Consumo quasi nullo | Solo sequenziale, a eventi | XML molto grandi (da centinaia di MB ai gigabyte) |
Il DOM carica il documento per intero e offre un comodo accesso casuale, ma richiede una quantità di memoria proporzionale alle dimensioni del documento.
SAX non costruisce alcun albero: genera eventi («è iniziato un tag», «è finito un tag», «è comparso del testo») man mano che legge. La memoria richiesta è minima, ma il codice è più difficile da scrivere e non c’è accesso casuale.
La maggior parte delle librerie più diffuse elencate qui sotto sono parser DOM.
Come cercare nell’albero
Una volta costruito l’albero, gli elementi si cercano di solito in due modi.
Selettori CSS — gli stessi dei fogli di stile: div.post > a, #main .title, ul li:first-child. Familiari a chi sviluppa front-end, e leggibili.
XPath — un linguaggio di interrogazione dell’albero, più potente del CSS: //div[@class="post"]/a/@href. Supporta condizioni, il passaggio al padre (..) e la ricerca per testo (//a[contains(text(),"Acquista")]). Si usa più spesso con l’XML, ma anche molte librerie HTML lo supportano.
Regola di scelta: se il CSS ti basta, usa il CSS: è più leggibile. XPath serve quando devi risalire al padre o a un antenato, selezionare in base al testo o esprimere condizioni complesse.
Esempi in vari linguaggi
In tutti gli esempi si risolve lo stesso compito: trovare i link dentro div.post e stamparne il testo e l’href.
Python
Le librerie più popolari sono BeautifulSoup (semplice, tollerante) e lxml (veloce, basata sulla libreria C libxml2, con supporto XPath).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Titolo</h2>
<a href="https://example.com">Link</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # il parser si può sostituire con "html.parser"
# Ricerca per tag
print(soup.find("h2").text)
# Selettori CSS
for a in soup.select("div.post a"):
print(a.text, a.get("href"))lxml con 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']Nella libreria standard di Python ci sono anche i moduli integrati html.parser e xml.dom.minidom, che non richiedono installazione.
JavaScript / Node.js
Nel browser il parser del DOM è integrato — DOMParser e 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);
});In Node.js si usano Cheerio (leggera, con API in stile jQuery) o jsdom (emulazione completa del 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
Lo standard de facto è jsoup, molto comodo per l’HTML, con selettori 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"));
}Per l’XML rigoroso il JDK include JAXP (javax.xml.parsers.DocumentBuilder), un parser DOM già pronto, oltre a SAX/StAX per l’analisi in streaming.
PHP
La classe integrata DOMDocument, con supporto XPath
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ silenzia gli avvisi sull'HTML «sporco»
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}L’alternativa con i selettori CSS è Symfony DomCrawler
C# / .NET
HtmlAgilityPack — un classico, lavora 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 — più moderna, con un parsing conforme alla specifica e selettori 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 un’API in stile 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)
})Sotto il cofano goquery usa il pacchetto ufficiale di basso livello golang.org/x/net/html, che si può utilizzare anche direttamente.
Ruby
Nokogiri — supporta sia CSS sia XPath
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
endTabella riassuntiva delle librerie
| Linguaggio | Libreria | Selettori | Particolarità |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Semplice, perdona gli errori di markup |
| Python | lxml | CSS + XPath | Veloce (backend C libxml2) |
| JS (browser) | DOMParser | CSS | Integrata nel browser |
| Node.js | Cheerio | CSS | Leggera, in stile jQuery |
| Node.js | jsdom | CSS | Emulazione completa del DOM |
| Java | jsoup | CSS | Standard de facto per l’HTML |
| PHP | DOMDocument | XPath | Integrata in PHP |
| C# | HtmlAgilityPack | XPath | Un classico dell’ecosistema .NET |
| C# | AngleSharp | CSS | Moderna, parsing corretto |
| Go | goquery | CSS | In stile jQuery |
| Ruby | Nokogiri | CSS + XPath | Veloce (backend in C) |
Note pratiche
L’HTML è «sporco» per natura. Il markup reale contiene spesso tag non chiusi ed errori di annidamento. I parser orientati all’HTML (BeautifulSoup, jsoup, AngleSharp, Nokogiri in modalità HTML) li tollerano e li correggono. I parser XML rigorosi, davanti a un input del genere, falliscono: per l’XML serve un markup valido.
Parser ≠ browser. Le librerie elencate analizzano solo l’HTML originale arrivato dal server. Se il contenuto viene caricato da JavaScript già nel browser, un normale parser del DOM non lo vedrà. Per questi siti servono i browser headless:
Prima eseguono gli script, poi restituiscono il DOM già pronto.
Prestazioni. I parser in C (lxml, il backend libxml2 di Nokogiri) sono un ordine di grandezza più veloci delle implementazioni in puro Python o Ruby. Per XML enormi scegli SAX/StAX in streaming al posto del DOM, per non sbattere contro i limiti di memoria.
Scelta dei selettori. Se il CSS ti basta, usa il CSS: è più leggibile. XPath serve per risalire al padre o a un antenato, selezionare in base al testo o esprimere condizioni complesse.