Парсинг XML: библиотеки, XPath и примеры на разных языках

Работа с XML в разных языках: DOM против SAX, XPath, пространства имён и разбор больших выгрузок без переполнения памяти.

КP
Команда Parsing.agency
Сбор данных под задачи бизнеса
Опубликовано: 21 апреля 2025

XML (eXtensible Markup Language) — текстовый формат для хранения и передачи структурированных данных в виде дерева. На нём построены товарные выгрузки маркетплейсов и агрегаторов, банковские и обменные форматы, конфигурации, а также производные форматы — RSS-ленты и карты сайта. Эта статья — часть нашего общего обзора по парсингу документов, сфокусированная именно на XML.

Как устроен XML

Документ XML — это дерево элементов. У каждого элемента есть имя (тег), может быть набор атрибутов, текстовое содержимое и вложенные элементы.

xml
<catalog>
  <product id="101" available="true">
    <name>Кофемолка</name>
    <price currency="RUB">3490</price>
  </product>
  <product id="102" available="false">
    <name>Чайник</name>
    <price currency="RUB">2190</price>
  </product>
</catalog>

Ключевое отличие от JSON в том, что в XML данные хранятся в двух местах одновременно — в тексте элементов (<name>Кофемолка</name>) и в атрибутах (id="101"). Это нужно держать в голове: при извлечении вы обращаетесь то к содержимому тега, то к его атрибутам.

Отдельная тема — пространства имён (namespaces). В сложных форматах теги выглядят как <g:price> или <atom:link>, где префикс привязан к URI. Парсеры требуют учитывать namespace при поиске узлов, иначе выборка вернёт пустоту — это самая частая причина «парсер ничего не находит, хотя данные на месте».

Два подхода: дерево в памяти и потоковое чтение

Есть два принципиально разных способа разбора. DOM-подход строит всё дерево в памяти и даёт удобную навигацию, в том числе через XPath — это просто и подходит для файлов до десятков мегабайт. Потоковый подход (SAX / iterparse) читает файл по событиям, не загружая его целиком, и незаменим для выгрузок в сотни мегабайт и гигабайты. Для типовых задач почти всегда хватает DOM; к потоковому переходят, когда файл перестаёт помещаться в память.

Python

В стандартной библиотеке есть модуль xml.etree.ElementTree — его достаточно для простых случаев и он не требует установки.

python
import xml.etree.ElementTree as ET

tree = ET.parse("catalog.xml")
root = tree.getroot()

for product in root.findall("product"):
    name = product.find("name").text
    price = product.find("price").text
    available = product.get("available")  # чтение атрибута
    print(name, price, available)

Для серьёзной работы используют lxml — он быстрее, лучше переваривает «грязный» XML и полноценно поддерживает XPath, включая namespaces.

python
from lxml import etree

tree = etree.parse("catalog.xml")

# XPath: все товары в наличии дешевле 3000
for product in tree.xpath("//product[@available='true']"):
    name = product.xpath("string(name)")
    price = product.xpath("string(price)")
    print(name, price)

Если данные пришли из API в виде XML, но удобнее работать как со словарём, помогает библиотека xmltodict, которая превращает XML в обычные вложенные структуры Python — близко к тому, как вы обрабатывали бы JSON.

JavaScript / Node.js

В Node популярны два решения. fast-xml-parser разбирает XML в объект без внешних зависимостей и работает очень быстро.

javascript
const { XMLParser } = require("fast-xml-parser");
const fs = require("fs");

const xml = fs.readFileSync("catalog.xml", "utf-8");
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
const data = parser.parse(xml);

for (const product of data.catalog.product) {
  console.log(product.name, product.price["#text"], product["@_available"]);
}

Альтернатива — xml2js, которая давно используется в экосистеме и удобна для асинхронной обработки. В браузере доступен встроенный DOMParser, позволяющий обходить XML теми же методами, что и HTML-документ.

PHP

Для простых документов идеально подходит встроенный SimpleXML — он даёт доступ к элементам как к свойствам объекта.

php
<?php
$xml = simplexml_load_file("catalog.xml");

foreach ($xml->product as $product) {
    $name  = (string) $product->name;
    $price = (string) $product->price;
    $available = (string) $product["available"]; // атрибут
    echo "$name — $price — $available\n";
}

Когда нужны namespaces, сложный XPath или потоковая обработка больших файлов, переходят на DOMDocument и XMLReader из той же стандартной поставки PHP.

Go

В стандартной библиотеке Go есть пакет encoding/xml, который разбирает XML напрямую в структуры через теги полей.

go
package main

import (
    "encoding/xml"
    "fmt"
    "os"
)

type Catalog struct {
    Products []struct {
        ID        string `xml:"id,attr"`
        Available string `xml:"available,attr"`
        Name      string `xml:"name"`
        Price     string `xml:"price"`
    } `xml:"product"`
}

func main() {
    data, _ := os.ReadFile("catalog.xml")
    var c Catalog
    xml.Unmarshal(data, &c)
    for _, p := range c.Products {
        fmt.Println(p.Name, p.Price, p.Available)
    }
}

Типичные ошибки

Чаще всего проблемы возникают из-за кодировки: если в декларации указана windows-1251, а файл прочитан как UTF-8, символы превратятся в мусор — кодировку нужно либо доверить парсеру, либо явно перекодировать. Вторая по частоте причина — проигнорированные namespaces, из-за чего XPath-запросы молча возвращают пустой результат. Третья — попытка загрузить слишком большой файл целиком в DOM: гигабайтную выгрузку нужно читать потоково. И, наконец, не стоит разбирать XML регулярными выражениями: формат вложенный и рекурсивный, а регулярки на нём ломаются на первом же нестандартном случае — для текста без структуры это оправдано (см. парсинг TXT), но не для XML.

Если на вашей стороне нестандартная схема выгрузки, битые namespace или файлы в гигабайты — мы настроим парсинг XML под ваш формат. А если исходные данные приходят не в чистом XML, а в его производных, смотрите соседние материалы: RSS и sitemap.