Tecnologie e protezioni 6 min di lettura

Parser del DOM: analisi dettagliata

Che cos’è un parser del DOM e come funziona: costruzione dell’albero del documento, navigazione tra i nodi e differenze rispetto agli approcci in streaming e con le regex.

TW
Team Web-Scraping.it
Raccolta dati per le esigenze del business
Pubblicato il 4 febbraio 2025

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:

  1. Tokenizzazione — il flusso di caratteri viene suddiviso in token (tag di apertura, tag di chiusura, testo, commento e così via).
  2. 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).

BeautifulSoup

python
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

python
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:

javascript
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).

javascript
import * as cheerio from "cheerio";

const $ = cheerio.load(html);
$("div.post a").each((i, el) => {
  console.log($(el).text(), $(el).attr("href"));
});

jsdom

javascript
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.

java
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

php
$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

c#
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

c#
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

go
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

ruby
require "nokogiri"

doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
  puts "#{link.text} #{link['href']}"
end

Tabella 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.