O que é o DOM
O DOM (Document Object Model) é a representação de um documento HTML ou XML em forma de árvore de objetos. Cada tag, atributo e trecho de texto vira um nó (node), e o aninhamento das tags define a hierarquia «pai → filho».
O parser do DOM é o componente que lê a marcação (uma string ou um fluxo de bytes) e constrói a partir dela essa árvore em memória. Uma vez construída a árvore, dá para trabalhar com ela de forma programática: buscar elementos, ler atributos, modificar, remover e adicionar nós, ou serializá-la de volta para texto.
Como funciona
O parsing transcorre em duas etapas principais:
- Tokenização — o fluxo de caracteres é dividido em tokens (tag de abertura, tag de fechamento, texto, comentário etc.).
- Construção da árvore — os tokens são montados em uma estrutura hierárquica segundo as regras de aninhamento. Para HTML, valem as regras da especificação WHATWG, que, entre outras coisas, «consertam» a marcação incorreta (tags sem fechar, aninhamento errado).
Parsing DOM versus SAX
Existem duas abordagens radicalmente diferentes para analisar um documento.
| Abordagem | Memória | Acesso | Quando usar |
|---|---|---|---|
| DOM | Documento inteiro em memória | Acesso aleatório a qualquer nó | Web scraping, processamento de configurações, a maioria das tarefas |
| SAX / em fluxo | Consumo quase nulo | Apenas sequencial, por eventos | XML muito grandes (de centenas de MB a gigabytes) |
DOM carrega o documento completo e oferece um confortável acesso aleatório, mas exige memória proporcional ao tamanho do documento.
SAX não constrói árvore nenhuma: gera eventos («abriu uma tag», «fechou uma tag», «apareceu texto») à medida que lê. Quase não precisa de memória, mas o código é mais difícil de escrever e não há acesso aleatório.
A maioria das bibliotecas populares listadas abaixo são parsers DOM.
Formas de buscar na árvore
Uma vez construída a árvore, os elementos costumam ser buscados de duas maneiras.
Seletores CSS — os mesmos usados na estilização de páginas: div.post > a, #main .title, ul li:first-child. São familiares aos desenvolvedores front-end e legíveis.
XPath — uma linguagem de consultas sobre a árvore, mais poderosa que CSS: //div[@class="post"]/a/@href. Admite condições, o salto para o pai (..) e a busca por texto (//a[contains(text(),"Comprar")]). É mais usado com XML, embora muitas bibliotecas HTML também o suportem.
Regra de escolha: se CSS resolve, use CSS: é mais legível. XPath é necessário quando é preciso navegar para o pai ou um ancestral, selecionar por texto ou expressar condições complexas.
Exemplos em várias linguagens
Todos os exemplos resolvem a mesma tarefa: encontrar os links dentro de div.post e exibir seu texto e seu href.
Python
As bibliotecas mais populares são a BeautifulSoup (simples, tolerante) e a lxml (rápida, baseada na biblioteca C libxml2, com suporte a XPath).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Título</h2>
<a href="https://example.com">Link</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # o parser pode ser trocado por "html.parser"
# Busca por tag
print(soup.find("h2").text)
# Seletores CSS
for a in soup.select("div.post a"):
print(a.text, a.get("href"))lxml com 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']Na biblioteca padrão do Python também estão os módulos integrados html.parser e xml.dom.minidom, que não exigem instalação.
JavaScript / Node.js
No navegador, o parser do DOM já vem integrado — 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);
});No Node.js, usam-se o Cheerio (leve, com API no estilo jQuery) ou o jsdom (emulação completa do 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
O padrão de facto é o jsoup, muito prático para HTML, com seletores 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 o XML estrito, o JDK inclui o JAXP (javax.xml.parsers.DocumentBuilder), um parser DOM nativo, além de SAX/StAX para a análise em fluxo.
PHP
A classe integrada DOMDocument, com suporte a XPath
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ silencia os avisos do HTML «sujo»
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}A alternativa com seletores CSS é o Symfony DomCrawler
C# / .NET
HtmlAgilityPack — o clássico, trabalha com 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 — mais moderno, com um parsing fiel à especificação e seletores 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
O goquery oferece uma API no 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 baixo, o goquery utiliza o pacote oficial de baixo nível golang.org/x/net/html, que também pode ser usado diretamente.
Ruby
Nokogiri — suporta tanto CSS quanto XPath
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
endTabela comparativa de bibliotecas
| Linguagem | Biblioteca | Seletores | Particularidades |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Simples, tolera erros de marcação |
| Python | lxml | CSS + XPath | Rápida (backend C libxml2) |
| JS (navegador) | DOMParser | CSS | Integrada ao navegador |
| Node.js | Cheerio | CSS | Leve, no estilo jQuery |
| Node.js | jsdom | CSS | Emulação completa do DOM |
| Java | jsoup | CSS | Padrão de facto para HTML |
| PHP | DOMDocument | XPath | Integrada ao PHP |
| C# | HtmlAgilityPack | XPath | O clássico do ecossistema .NET |
| C# | AngleSharp | CSS | Moderno, parsing correto |
| Go | goquery | CSS | No estilo jQuery |
| Ruby | Nokogiri | CSS + XPath | Rápida (backend em C) |
Observações práticas
O HTML é «sujo» por natureza. A marcação real costuma conter tags sem fechar e erros de aninhamento. Os parsers voltados para HTML (BeautifulSoup, jsoup, AngleSharp, Nokogiri em modo HTML) os toleram e corrigem. Os parsers XML estritos, diante de uma entrada dessas, falham: para XML, é preciso uma marcação válida.
Parser ≠ navegador. As bibliotecas mencionadas analisam apenas o HTML original que chegou do servidor. Se o conteúdo é carregado por JavaScript já no navegador, um parser do DOM comum não vai vê-lo. Para esses sites, são necessários navegadores headless:
Primeiro eles executam os scripts e depois entregam o DOM já pronto.
Desempenho. Os parsers em C (lxml, o backend libxml2 do Nokogiri) são uma ordem de grandeza mais rápidos que as implementações puras em Python ou Ruby. Para XML enormes, escolha SAX/StAX em fluxo em vez de DOM, para não esbarrar na memória.
Escolha de seletores. Se CSS resolve, use CSS: é mais legível. XPath é necessário para navegar até o pai ou um ancestral, selecionar por texto ou expressar condições complexas.