Обработка XML в при помощи библиотеки lxml в Python

Опубликовано пользователем Бессонов Л.В. 10.03.2015г.

Библиотека lxml быстро достаточно быстрая и способна без проблем обрабатывать большие XML-файлы. Эта библиотека имеет высокую производительность и содержит встроенную поддержку XPath 1.0, XSLT 1.0, пользовательские элементарные классы и даже типичный для Python интерфейс привязки данных.

Библиотека lxml построена на базе двух библиотек языка С: libxml2 и libxslt. Они обеспечивают большую часть возможностей для основных задач анализа, сериализации и преобразования. Какие именно компоненты lxml использовать в коде – зависит от потребностей задачи.

XML-библиотеки обычно разрабатываются для небольших образцов файлов и тестируются на них же. В самом деле, многие настоящие проекты начинаются без полноценных данных. Программисты трудятся неделями и месяцами, используя образцы данных и создавая код, подобный следующему.

  1. from lxml import etree
  2. doc = etree.parse("data.xml")

Метод parse из lxml считывает файл целиком и строит в памяти дерево. Дерево lxml содержит много информации о контексте каждого узла, включая ссылки на родителя. В случаях, когда нежелательно или непрактично строить дерево в памяти (например, если анализируется большой файл), следует использовать технологию итерационного анализа, в которой не требуется считывать исходный файл целиком. lxml предлагает два подхода:

  1. Поддержка класса целевого анализатора
  2. Использование метода iterparse

Метод целевого анализатора

Целевой анализатор – это класс, который обеспечивает выполнение следующих методов:

  • start() – срабатывает при открытии элемента. Данные потомка и элемента пока недоступны.
  • end() – срабатывает при закрытии элемента. Теперь доступны все узлы-потомки элемента, в том числе и текстовые узлы.
  • data() – срабатывает при обращении к текстовым потомкам и имеет доступ к текстовому содержимому.
  • close() – срабатывает при завершении анализа.

Ниже показано создание класса целевого анализатора (TitleTarget) , который реализует необходимые методы. Этот анализатор собирает текстовые потомки элемента Title во внешний список (self.text) и при вызове метода close() возвращает его.

  1. class TitleTarget(object):
  2. def __init__(self):
  3. self.text = []
  4. def start(self, tag, attrib):
  5. self.is_title = True if tag == "Title" else False
  6. def end(self, tag):
  7. pass
  8. def data(self, data):
  9. if self.is_title:
  10. self.text.append(data.encode("utf-8"))
  11. def close(self):
  12. return self.text
  13.  
  14. parser = etree.XMLParser(target = TitleTarget())
  15.  
  16. infile = "data.xml"
  17.  
  18. results = etree.parse(infile, parser)
  19.  
  20. # В конце "results" будет содержать результаты работы
  21. # метода close() целевого анализатора
  22.  
  23. out = open("titles.txt", "w")
  24. out.write("\n".join(results))
  25. out.close()

Подход на основе целевого анализатора может быть достаточно быстрым и не генерирует дерево анализа, занимающее много памяти, но все события срабатывают для всех элементов, встречающихся в данных. Для очень больших документов, если нас интересует лишь небольшое число элементов, как в этом примере, такой подход может быть нежелателен. Возможно ли ограничить обработку до выбранного тега и улучшить производительность?

Использование метода iterparse

Метод iterparse из библиотеки lxml является расширением ElementTree API. Метод iterparse возвращает Python-итератор для контекста выбранного элемента. Он принимает два используемых аргумента: кортеж отслеживаемых событий и имя тега. В данном случае интерес представляет лишь текстовое содержимое элемента <Title> (которое становится доступно при достижении события end). Результат работы кода из листинга ниже будет идентичен результату метода целевого анализатора из предыдущего листинга, но этот код должен бы работать значительно быстрее, так как lxml может оптимизировать обработку событий изнутри. Объем кода также значительно меньше.

  1. context = etree.iterparse(infile, events=("end",), tag="Title")
  2.  
  3. for event, elem in context:
  4. out.write("%s\n" % elem.text.encode("utf-8"))

 

Баннер SGU.RU