Qué es el DOM
El DOM (Document Object Model) es la representación de un documento HTML o XML en forma de árbol de objetos. Cada etiqueta, atributo y fragmento de texto se convierte en un nodo (node), y el anidamiento de las etiquetas define la jerarquía «padre → hijo».
El parser del DOM es el componente que lee el marcado (una cadena o un flujo de bytes) y construye a partir de él ese árbol en memoria. Una vez construido, se puede trabajar con el árbol de forma programática: buscar elementos, leer atributos, modificar, eliminar y añadir nodos, o serializarlo de vuelta a texto.
Cómo funciona
El parseo transcurre en dos etapas principales:
- Tokenización — el flujo de caracteres se divide en tokens (etiqueta de apertura, etiqueta de cierre, texto, comentario, etc.).
- Construcción del árbol — los tokens se ensamblan en una estructura jerárquica según las reglas de anidamiento. Para HTML son las reglas de la especificación WHATWG, que, entre otras cosas, «reparan» el marcado incorrecto (etiquetas sin cerrar, anidamiento erróneo).
Parseo DOM frente a SAX
Existen dos enfoques radicalmente distintos para analizar un documento.
| Enfoque | Memoria | Acceso | Cuándo usarlo |
|---|---|---|---|
| DOM | Todo el documento en memoria | Acceso aleatorio a cualquier nodo | Web scraping, procesado de configuraciones, la mayoría de tareas |
| SAX / en flujo | Consumo casi nulo | Solo secuencial, por eventos | XML muy grandes (de cientos de MB a gigabytes) |
DOM carga el documento completo y ofrece un cómodo acceso aleatorio, pero exige una memoria proporcional al tamaño del documento.
SAX no construye ningún árbol: genera eventos («empezó una etiqueta», «terminó una etiqueta», «apareció texto») a medida que lee. Apenas necesita memoria, pero el código es más difícil de escribir y no hay acceso aleatorio.
La mayoría de las bibliotecas populares de más abajo son parsers DOM.
Formas de buscar en el árbol
Una vez construido el árbol, los elementos se suelen buscar de dos maneras.
Selectores CSS — los mismos que en la maquetación: div.post > a, #main .title, ul li:first-child. Resultan familiares a los desarrolladores front-end y son legibles.
XPath — un lenguaje de consultas sobre el árbol, más potente que CSS: //div[@class="post"]/a/@href. Admite condiciones, el salto al padre (..) y la búsqueda por texto (//a[contains(text(),"Comprar")]). Se usa más a menudo con XML, aunque muchas bibliotecas HTML también lo soportan.
Regla de elección: si le basta con CSS, use CSS: es más legible. XPath hace falta cuando se necesita navegar al padre o a un ancestro, seleccionar por texto o expresar condiciones complejas.
Ejemplos en varios lenguajes
En todos los ejemplos se resuelve la misma tarea: encontrar los enlaces dentro de div.post y mostrar su texto y su href.
Python
Las bibliotecas más populares son BeautifulSoup (simple, tolerante) y lxml (rápida, basada en la biblioteca C libxml2, con soporte de XPath).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Título</h2>
<a href="https://example.com">Enlace</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # el parser puede cambiarse por "html.parser"
# Búsqueda por etiqueta
print(soup.find("h2").text)
# Selectores 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']En la biblioteca estándar de Python también están los módulos integrados html.parser y xml.dom.minidom, que no requieren instalación.
JavaScript / Node.js
En el navegador, el parser del DOM viene integrado — DOMParser y 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);
});En Node.js se usan Cheerio (ligera, con API al estilo jQuery) o jsdom (emulación 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
El estándar de facto es jsoup, muy cómodo para HTML, con selectores 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"));
}Para el XML estricto, el JDK incluye JAXP (javax.xml.parsers.DocumentBuilder), un parser DOM de serie, además de SAX/StAX para el análisis en flujo.
PHP
La clase integrada DOMDocument, con soporte de XPath
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ silencia los avisos por el HTML «sucio»
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}La alternativa con selectores CSS es Symfony DomCrawler
C# / .NET
HtmlAgilityPack — el clásico, trabaja con 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 — más moderno, con un parseo fiel a la especificación y selectores 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 ofrece una API al estilo 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)
})Por debajo, goquery utiliza el paquete oficial de bajo nivel golang.org/x/net/html, que también puede usarse directamente.
Ruby
Nokogiri — soporta tanto CSS como XPath
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
endTabla comparativa de bibliotecas
| Lenguaje | Biblioteca | Selectores | Particularidades |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Simple, tolera errores de marcado |
| Python | lxml | CSS + XPath | Rápida (backend C libxml2) |
| JS (navegador) | DOMParser | CSS | Integrada en el navegador |
| Node.js | Cheerio | CSS | Ligera, al estilo jQuery |
| Node.js | jsdom | CSS | Emulación completa del DOM |
| Java | jsoup | CSS | Estándar de facto para HTML |
| PHP | DOMDocument | XPath | Integrada en PHP |
| C# | HtmlAgilityPack | XPath | El clásico del ecosistema .NET |
| C# | AngleSharp | CSS | Moderno, parseo correcto |
| Go | goquery | CSS | Al estilo jQuery |
| Ruby | Nokogiri | CSS + XPath | Rápida (backend en C) |
Observaciones prácticas
El HTML es «sucio» por naturaleza. El marcado real suele contener etiquetas sin cerrar y errores de anidamiento. Los parsers orientados a HTML (BeautifulSoup, jsoup, AngleSharp, Nokogiri en modo HTML) los toleran y los corrigen. Los parsers XML estrictos, ante una entrada así, fallan: para XML se necesita un marcado válido.
Parser ≠ navegador. Las bibliotecas mencionadas analizan únicamente el HTML original que llegó del servidor. Si el contenido lo carga JavaScript ya en el navegador, un parser del DOM corriente no lo verá. Para esos sitios hacen falta navegadores headless:
Primero ejecutan los scripts y después entregan el DOM ya listo.
Rendimiento. Los parsers en C (lxml, el backend libxml2 de Nokogiri) son un orden de magnitud más rápidos que las implementaciones puras en Python o Ruby. Para XML enormes, elija SAX/StAX en flujo en lugar de DOM, para no chocar contra la memoria.
Elección de selectores. Si le basta con CSS, use CSS: es más legible. XPath hace falta para navegar al padre o a un ancestro, seleccionar por texto o expresar condiciones complejas.