What the DOM is
The DOM (Document Object Model) is a representation of an HTML or XML document as a tree of objects. Every tag, attribute, and text fragment becomes a node, and the nesting of tags defines the parent → child hierarchy.
A DOM parser is the component that reads markup (a string or a byte stream) and builds that tree in memory. Once it's built, you can work with the tree programmatically: search for elements, read attributes, modify, delete, and add nodes, and serialize it back to text.
How it works
Parsing happens in two main stages:
- Tokenization — the stream of characters is broken into tokens (opening tag, closing tag, text, comment, and so on).
- Tree construction — tokens are assembled into a hierarchical structure according to nesting rules. For HTML those are the WHATWG spec rules, which also "repair" malformed markup (unclosed tags, incorrect nesting).
DOM parsing vs. SAX
There are two fundamentally different approaches to parsing a document.
| Approach | Memory | Access | When to use |
|---|---|---|---|
| DOM | Whole document in memory | Random access to any node | Scraping, config processing, most tasks |
| SAX / streaming | Almost none | Sequential only, event-driven | Very large XML (hundreds of MB to gigabytes) |
DOM loads the whole document and gives you convenient random access, but it needs memory proportional to the document's size.
SAX builds no tree; instead it emits events ("tag started," "tag ended," "text encountered") as it reads. It needs almost no memory, but the code is harder to write and there's no random access.
Most of the popular libraries below are DOM parsers.
Ways to search the tree
Once the tree is built, elements are usually found in one of two ways.
CSS selectors — the same ones you use in stylesheets: div.post > a, #main .title, ul li:first-child. Familiar to front-end developers and easy to read.
XPath — a query language for the tree, more powerful than CSS: //div[@class="post"]/a/@href. It supports conditions, navigating to a parent (..), and searching by text (//a[contains(text(),"Buy")]). More common in XML, but many HTML libraries support it too.
Rule of thumb: if CSS is enough, use CSS — it's more readable. Reach for XPath when you need to navigate to a parent/ancestor, select by text, or express complex conditions.
Examples in different languages
Every example solves the same task: find the links inside div.post and print their text and href.
Python
The most popular libraries are BeautifulSoup (simple, forgiving) and lxml (fast, built on the C library libxml2, with XPath support).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Heading</h2>
<a href="https://example.com">Link</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # the parser can be swapped for "html.parser"
# Search by tag
print(soup.find("h2").text)
# CSS selectors
for a in soup.select("div.post a"):
print(a.text, a.get("href"))
lxml with 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']
Python's standard library also includes the built-in html.parser and xml.dom.minidom, which need no installation.
JavaScript / Node.js
In the browser, a DOM parser is built in — DOMParser and 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 people use Cheerio (lightweight, jQuery-like API) or jsdom (full DOM emulation).
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
The de facto standard is jsoup, very convenient for HTML with CSS selectors.
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"));
}
For strict XML, the JDK ships JAXP (javax.xml.parsers.DocumentBuilder) — a DOM parser out of the box — plus SAX/StAX for streaming.
PHP
The built-in DOMDocument class with XPath support:
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ suppresses warnings about "dirty" HTML
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}
A CSS-selector alternative is Symfony DomCrawler.
C# / .NET
HtmlAgilityPack — the classic, works through 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 — more modern, with spec-correct parsing and CSS selectors:
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 gives you a jQuery-like API:
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)
})
Under the hood, goquery uses the low-level official package golang.org/x/net/html, which you can also use directly.
Ruby
Nokogiri — supports both CSS and XPath:
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
end
Library reference table
| Language | Library | Selectors | Notes |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Simple, forgives markup errors |
| Python | lxml | CSS + XPath | Fast (libxml2 C backend) |
| JS (browser) | DOMParser | CSS | Built into the browser |
| Node.js | Cheerio | CSS | Lightweight, jQuery-like |
| Node.js | jsdom | CSS | Full DOM emulation |
| Java | jsoup | CSS | De facto standard for HTML |
| PHP | DOMDocument | XPath | Built into PHP |
| C# | HtmlAgilityPack | XPath | .NET ecosystem classic |
| C# | AngleSharp | CSS | Modern, spec-correct parsing |
| Go | goquery | CSS | jQuery-like |
| Ruby | Nokogiri | CSS + XPath | Fast (C backend) |
Practical notes
HTML is dirty by nature. Real markup often contains unclosed tags and nesting errors. HTML-oriented parsers (BeautifulSoup, jsoup, AngleSharp, Nokogiri in HTML mode) tolerate and repair them. Strict XML parsers will fail on such input — XML requires valid markup.
A parser is not a browser. The libraries listed here only parse the source HTML that came from the server. If the content is loaded by JavaScript in the browser, a plain DOM parser won't see it. For those sites you need a headless browser:
They execute the scripts first and then hand you the finished DOM. If you're deciding between the two approaches, our guide to scraping dynamic websites walks through when a DOM parser is enough and when you truly need a browser.
Performance. C-based parsers (lxml, Nokogiri's libxml2 backend) are an order of magnitude faster than pure-Python/Ruby implementations. For huge XML, choose streaming SAX/StAX over DOM so you don't hit a memory wall.
Selector choice. If CSS is enough, use CSS — it's more readable. XPath is for navigating to a parent/ancestor, selecting by text, or complex conditions.
Choosing a parser and writing selectors is the easy 20% of a scraping project; keeping those selectors alive as sites change layout is the other 80%. If you'd rather receive structured data than maintain parsers, scraping.pro runs extraction as a done-for-you data service — you specify the fields, we handle the DOM.