Что такое DOM
DOM (Document Object Model) — это представление HTML- или XML-документа в виде дерева объектов. Каждый тег, атрибут и текстовый фрагмент становится узлом (node), а вложенность тегов задаёт иерархию «родитель → потомок».
Парсер DOM — это компонент, который читает разметку (строку или поток байтов) и строит из неё это дерево в памяти. После построения с деревом можно работать программно: искать элементы, читать атрибуты, изменять, удалять и добавлять узлы, сериализовать обратно в текст.
Как он работает
Парсинг проходит в два основных этапа:
- Токенизация — поток символов разбивается на токены (открывающий тег, закрывающий тег, текст, комментарий и т. д.).
- Построение дерева — токены собираются в иерархическую структуру с учётом правил вложенности. Для HTML это правила из спецификации WHATWG, которые в том числе «чинят» некорректную разметку (незакрытые теги, неправильную вложенность).
DOM-парсинг против SAX
Существует два принципиально разных подхода к разбору документа.
| Подход | Память | Доступ | Когда использовать |
|---|---|---|---|
| DOM | Весь документ в памяти | Случайный доступ к любому узлу | Скрапинг, обработка конфигов, большинство задач |
| SAX / потоковый | Почти не расходуется | Только последовательный, по событиям | Очень большие XML (сотни МБ — гигабайты) |
DOM загружает документ целиком и даёт удобный случайный доступ, но требует памяти, пропорциональной размеру документа.
SAX не строит дерево, а генерирует события («начался тег», «закончился тег», «встретился текст») по мере чтения. Памяти почти не нужно, но писать код сложнее и нет случайного доступа.
Большинство популярных библиотек ниже — это DOM-парсеры.
Способы поиска по дереву
После построения дерева элементы обычно ищут двумя способами.
CSS-селекторы — те же, что в вёрстке: div.post > a, #main .title, ul li:first-child. Привычны фронтендерам, читаемы.
XPath — язык запросов к дереву, мощнее CSS: //div[@class="post"]/a/@href. Поддерживает условия, переход к родителю (..), поиск по тексту (//a[contains(text(),"Купить")]). Чаще используется в XML, но многие HTML-библиотеки тоже его поддерживают.
Правило выбора: если хватает CSS — берите CSS, он читаемее. XPath нужен, когда требуется навигация к родителю/предку, выбор по тексту или сложные условия.
Примеры на разных языках
Во всех примерах решается одна задача: найти ссылки внутри div.post и вывести их текст и href.
Python
Самые популярные библиотеки — BeautifulSoup (простой, прощающий) и lxml (быстрый, на базе C-библиотеки libxml2, с поддержкой XPath).
from bs4 import BeautifulSoup
html = """
<html><body>
<div class="post">
<h2>Заголовок</h2>
<a href="https://example.com">Ссылка</a>
</div>
</body></html>
"""
soup = BeautifulSoup(html, "lxml") # парсер можно заменить на "html.parser"
# Поиск по тегу
print(soup.find("h2").text)
# CSS-селекторы
for a in soup.select("div.post a"):
print(a.text, a.get("href"))
lxml с 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 также есть встроенные html.parser и xml.dom.minidom, не требующие установки.
JavaScript / Node.js
В браузере DOM-парсер встроен — DOMParser и 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);
});
В Node.js используют Cheerio (лёгкий, jQuery-подобный API) или jsdom (полная эмуляция 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
Стандарт де-факто — jsoup, очень удобный для HTML с 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"));
}
Для строгого XML в JDK есть встроенный JAXP (javax.xml.parsers.DocumentBuilder) — DOM-парсер из коробки, а также SAX/StAX для потокового разбора.
PHP
Встроенный класс DOMDocument с поддержкой XPath
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ подавляет предупреждения о «грязном» HTML
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//div[@class="post"]/a') as $node) {
echo $node->textContent . ' ' . $node->getAttribute('href') . PHP_EOL;
}
Альтернатива с CSS-селекторами — Symfony DomCrawler
C# / .NET
HtmlAgilityPack — классика, работает через 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 — более современный, со спецификационно-корректным парсингом и 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 даёт jQuery-подобный 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)
})
Под капотом goquery использует низкоуровневый официальный пакет golang.org/x/net/html, которым можно пользоваться и напрямую.
Ruby
Nokogiri — поддерживает и CSS, и XPath
require "nokogiri"
doc = Nokogiri::HTML(html)
doc.css("div.post a").each do |link|
puts "#{link.text} #{link['href']}"
end
Сводная таблица библиотек
| Язык | Библиотека | Селекторы | Особенности |
|---|---|---|---|
| Python | BeautifulSoup | CSS | Простая, прощает ошибки разметки |
| Python | lxml | CSS + XPath | Быстрая (C-бэкенд libxml2) |
| JS (браузер) | DOMParser | CSS | Встроена в браузер |
| Node.js | Cheerio | CSS | Лёгкая, jQuery-подобная |
| Node.js | jsdom | CSS | Полная эмуляция DOM |
| Java | jsoup | CSS | Стандарт де-факто для HTML |
| PHP | DOMDocument | XPath | Встроена в PHP |
| C# | HtmlAgilityPack | XPath | Классика экосистемы .NET |
| C# | AngleSharp | CSS | Современный, корректный парсинг |
| Go | goquery | CSS | jQuery-подобная |
| Ruby | Nokogiri | CSS + XPath | Быстрая (C-бэкенд) |
Практические замечания
HTML «грязный» по своей природе. Реальная разметка часто содержит незакрытые теги и ошибки вложенности. HTML-ориентированные парсеры (BeautifulSoup, jsoup, AngleSharp, Nokogiri в режиме HTML) их терпят и исправляют. Строгие XML-парсеры на таком вводе упадут — для XML нужна валидная разметка.
Парсер ≠ браузер. Перечисленные библиотеки разбирают только исходный HTML, пришедший с сервера. Если контент подгружается JavaScript-ом уже в браузере, простой DOM-парсер его не увидит. Для таких сайтов нужны headless-браузеры:
Они сначала исполняют скрипты, а потом отдают готовый DOM.
Производительность. Парсеры на C (lxml, libxml2-бэкенд Nokogiri) на порядок быстрее чисто-питоновских/руби-реализаций. Для огромных XML выбирайте потоковый SAX/StAX вместо DOM, чтобы не упереться в память.
Выбор селекторов. Если хватает CSS — берите CSS, он читаемее. XPath нужен для навигации к родителю/предку, выбора по тексту или сложных условий.