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