Python содержит встроенные XML инструменты для парсинга, к которым вы можете получить доступ при помощи модуля xml. В данной статье мы рассмотрим два подмодуля xml:
- minidom
- ElementTree
Мы начнем с minidom по той причине, что де-факто он используется в качестве метода парсинга XML. После этого, мы взглянем на то, как использовать ElementTree для этих целей.
Работаем с minidom
Для начала, нам нужен XML для парсинга. Давайте взглянем на следующий небольшой пример XML:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" ?> <zAppointments reminder="15"> <appointment> <begin>1181251680</begin> <uid>040000008200E000</uid> <alarmTime>1181572063</alarmTime> <state></state> <location></location> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> </zAppointments> |
Это типичный XML и читается он вполне интуитивно. В будущем вы, скорее всего, столкнетесь с более сложным примером XML, так что в нашем случае мы работаем с очень удобным материалом. В любом случае, сохраните описанный выше код XML под следующим названием: appt.xml
Давайте уделим больше времени на то, чтобы поближе познакомиться с парсингом данного файла в Python при помощи модуля minidom. Это достаточно длинный кусок кода, так что подготовьтесь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# -*- coding: utf-8 -*- import xml.dom.minidom import urllib.request class ApptParser(object): def __init__(self, url, flag='url'): self.list = [] self.appt_list = [] self.flag = flag self.rem_value = 0 xml = self.getXml(url) self.handleXml(xml) def getXml(self, url): try: print(url) f = urllib.request.urlopen(url) except: f = url doc = xml.dom.minidom.parse(f) node = doc.documentElement if node.nodeType == xml.dom.Node.ELEMENT_NODE: print('Элемент: %s' % node.nodeName) for (name, value) in node.attributes.items(): print(' Attr -- имя: %s значение: %s' % (name, value)) return node def handleXml(self, xml): rem = xml.getElementsByTagName('zAppointments') appointments = xml.getElementsByTagName("appointment") self.handleAppts(appointments) def getElement(self, element): return self.getText(element.childNodes) def handleAppts(self, appts): for appt in appts: self.handleAppt(appt) self.list = [] def handleAppt(self, appt): begin = self.getElement(appt.getElementsByTagName("begin")[0]) duration = self.getElement(appt.getElementsByTagName("duration")[0]) subject = self.getElement(appt.getElementsByTagName("subject")[0]) location = self.getElement(appt.getElementsByTagName("location")[0]) uid = self.getElement(appt.getElementsByTagName("uid")[0]) self.list.append(begin) self.list.append(duration) self.list.append(subject) self.list.append(location) self.list.append(uid) if self.flag == 'file': try: state = self.getElement(appt.getElementsByTagName("state")[0]) self.list.append(state) alarm = self.getElement(appt.getElementsByTagName("alarmTime")[0]) self.list.append(alarm) except Exception as e: print(e) self.appt_list.append(self.list) def getText(self, nodelist): rc = "" for node in nodelist: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc if __name__ == "__main__": appt = ApptParser("appt.xml") print(appt.appt_list) |
Этот код основан на примере из документации Python и стоит отметить, что, на мой взгляд, он немного уродливый. Давайте его разберем по кусочкам. Параметр url, который мы видим в классе ApptParser, может быть как url так и файлом. В методе getXml, мы используем обработчик исключений для того, чтобы попробовать открыть url. Если это привело к ошибке, значит url – это путь к файлу. Далее мы используем метод парсинга для парсинга XML. Далее мы изымаем node из XML. Мы опустим все условия, так как в данной статье это не принципиально. Наконец, мы возвращаем объект node. Технически, node является объектом XML, и мы передаем его методу handleXml. Чтобы получить все назначения в XML, мы делаем следующее:
1 |
xml.getElementsByTagName("appointment") |
После этого, мы передаем эту информацию методу handleAppts. Это большой объем информации. Хорошей идеей будет небольшой рефакторинг этого кода для этой цели, вместо передачи всей этой информации целиком, таким образом мы просто настраиваем переменные класса, после чего вызываем следующий метод без каких-либо аргументов. Я оставлю это в качестве упражнения для читателей. В любом случае, метод handleAppts только создает цикл в каждом назначении и вызывает метод handleAppt, чтобы вытянуть кое-какую дополнительную информации, добавляет данные в список и добавляет этот список в другой список. Идея в том, чтобы закончить со списком списков, которые содержат все соответствующие данные о встречах.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Обратите внимание на то, что метод handleAppt вызывает метод getElement, который, в свою очередь вызывает метод getText. Технически, вы можете пропустить вызов getElement и вызвать getText напрямую. С другой стороны, вам может понадобиться добавить дополнительную обработку в getElement для конвертации текста, или чего-то другого перед возвратом. Например, вам может понадобиться конвертировать целые числа, числа с запятыми или объекты decimal.Decimal. Давайте попробуем еще один пример с minidom, перед тем как двигаться дальше. Мы используем пример XML с сайта MSDN Майкрософт: http://msdn.microsoft.com/en-us/library/ms762271%28VS.85%29.aspx . Сохраните следующий код под названием example.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0"?> <catalog> <book id="bk101"> <author>Gambardella, Matthew</author> <title>XML Developer's Guide</title> <genre>Computer</genre> <price>44.95</price> <publish_date>2000-10-01</publish_date> <description>An in-depth look at creating applications with XML.</description> </book> <book id="bk102"> <author>Ralls, Kim</author> <title>Midnight Rain</title> <genre>Fantasy</genre> <price>5.95</price> <publish_date>2000-12-16</publish_date> <description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description> </book> <book id="bk103"> <author>Corets, Eva</author> <title>Maeve Ascendant</title> <genre>Fantasy</genre> <price>5.95</price> <publish_date>2000-11-17</publish_date> <description>After the collapse of a nanotechnology society in England, the young survivors lay the foundation for a new society.</description> </book> </catalog> |
В этом примере мы выполнили парсинг XML, извлекли заголовки книги и вывели их в stdout. Давайте взглянем на код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# -*- coding: utf-8 -*- import xml.dom.minidom as minidom def getTitles(xml): """ Выводим все заголовки из xml. """ doc = minidom.parse(xml) node = doc.documentElement books = doc.getElementsByTagName("book") titles = [] for book in books: titleObj = book.getElementsByTagName("title")[0] titles.append(titleObj) for title in titles: nodes = title.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: print(node.data) if __name__ == "__main__": document = 'example.xml' getTitles(document) |
Данный код – это только одна короткая функция, которая принимает один аргумент, который является файлом XML. Мы импортируем модуль minidom и даем ему такое же название, чтобы упростить отсылки к нему. Далее мы выполняем парсинг XML. Первые две строки функции очень похожи на те, что были в предыдущем примере. Мы используем метод getElementsByTagName чтобы собрать нужные нам части XML, после чего выполнить итерацию над результатом и извлечь заголовки книги. Так, мы извлекаем объекты заголовков, так что нужно выполнить итерацию этих данных в том числе, а также вытащить обычный текст, по этой причине мы используем вложенные данные цикла. Давайте уделим немного времени на унаследованный модуль xml под названием ElementTree.
Парсинг с ElementTree
В данном разделе, мы научимся создавать XML файлы, редактировать и выполнять парсинг при помощи ElementTree. Для сравнения, мы используем тот же XML, который мы использовали в предыдущем разделе для того, чтобы продемонстрировать разницу в использовании minidom и ElementTree. Вот наш код:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" ?> <zAppointments reminder="15"> <appointment> <begin>1181251680</begin> <uid>040000008200E000</uid> <alarmTime>1181572063</alarmTime> <state></state> <location></location> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> </zAppointments> |
Давайте начнем с изучения того, как создавать такую XML структуру при помощи Python
Как создавать XML при помощи ElementTree
Создание XML при помощи ElementTree – это очень просто. В данном разделе, мы попытаемся создать написанный выше XML в Python. Давайте посмотрим на код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# -*- coding: utf-8 -*- import xml.etree.ElementTree as xml def createXML(filename): """ Создаем XML файл. """ root = xml.Element("zAppointments") appt = xml.Element("appointment") root.append(appt) # создаем дочерний суб-элемент. begin = xml.SubElement(appt, "begin") begin.text = "1181251680" uid = xml.SubElement(appt, "uid") uid.text = "040000008200E000" alarmTime = xml.SubElement(appt, "alarmTime") alarmTime.text = "1181572063" state = xml.SubElement(appt, "state") location = xml.SubElement(appt, "location") duration = xml.SubElement(appt, "duration") duration.text = "1800" subject = xml.SubElement(appt, "subject") tree = xml.ElementTree(root) with open(filename, "w") as fh: tree.write(fh) if __name__ == "__main__": createXML("appt.xml") |
Если вы запустите этот код, вы должны получить что-то вроде нижеизложенного (возможно в одной строке):
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" ?> <zAppointments reminder="15"> <appointment> <begin>1181251680</begin> <uid>040000008200E000</uid> <alarmTime>1181572063</alarmTime> <state /> <location /> <duration>1800</duration> <subject /> </appointment> </zAppointments> |
Это очень похоже на исходный код и это, безусловно, действенный XML. Само собой, наши коды отличаются, но весьма похожи. Давайте уделим время для разбора кода и убедиться в том, что мы его хорошо понимаем. Для начала мы создаем корневой элемент при помощи функции Element модуля ElementTree. Далее, мы создаем элемент назначения и добавляем его к root. Далее, мы создаем SubElements, выполнив парсинг назначения объекта Element (appt) в SubElement наряду с именем, например, begin. Далее, для каждого SubElement, мы назначаем их текстовые свойства, для передачи значения. В конце скрипта мы создаем ElementTree и используем его для написания XML в файле. Теперь мы готовы к тому, чтобы научиться редактировать файл!
Как редактировать XML при помощи ElementTree
Редактирование XML при помощи ElementTree это также очень просто. Чтобы все было немного интереснее, мы добавим другой блок назначения в XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?xml version="1.0" ?> <zAppointments reminder="15"> <appointment> <begin>1181251680</begin> <uid>040000008200E000</uid> <alarmTime>1181572063</alarmTime> <state></state> <location></location> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> <appointment> <begin>1181253977</begin> <uid>sdlkjlkadhdakhdfd</uid> <alarmTime>1181588888</alarmTime> <state>TX</state> <location>Dallas</location> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> </zAppointments> |
Теперь мы напишем код для того, чтобы изменить каждое значение тега begin от секунд, начиная с эпохи на что-нибудь более читабельное. Мы используем модуль time python, чтобы облегчить себе жизнь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# -*- coding: utf-8 -*- import time import xml.etree.cElementTree as ET def editXML(filename): """ Редактируем XML файл. """ tree = ET.ElementTree(file=filename) root = tree.getroot() for begin_time in root.iter("begin"): begin_time.text = time.ctime(int(begin_time.text)) tree = ET.ElementTree(root) with open("updated.xml", "w") as f: tree.write(f) if __name__ == "__main__": editXML("original_appt.xml") |
Здесь мы создаем объект ElementTree под названием tree и извлечем из него root. Далее мы используем метод iter() чтобы найти все теги, помеченные “begin”. Обратите внимание на то, что метод iter() был добавлен в Python 2.7. В наем цикле for, мы указываем текстовое содержимое каждого объекта, чтобы получить более читабельный временной формат при помощи метода time.ctime(). Вы также можете обратить внимание на то, что нам нужно конвертировать строку для целых чисел, при передаче их к ctime. Результат будет выглядеть примерно следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<zAppointments reminder="15"> <appointment> <begin>Thu Jun 07 16:28:00 2007</begin> <uid>040000008200E000</uid> <alarmTime>1181572063</alarmTime> <state /> <location /> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> <appointment> <begin>Thu Jun 07 17:06:17 2007</begin> <uid>sdlkjlkadhdakhdfd</uid> <alarmTime>1181588888</alarmTime> <state>TX</state> <location>Dallas</location> <duration>1800</duration> <subject>Bring pizza home</subject> </appointment> </zAppointments> |
Вы также можете использовать методы ElementTree, такие как find() или findall() для поиска конкретных тегов в вашем XML. Метод find() найдет только первый пример, в то время как findall() найдет каждый тег с указанной отметкой. Это очень полезно при решении задач, возникших при редактировании или при парсинге, что является темой нашего следующего раздела!
Парсинг и ElementTree
Сейчас мы научимся тому, как выполнять базовый парсинг при помощи ElementTree. Сначала, мы пройдемся по коду в целом, затем разберем его кирпичик за кирпичиком, чтобы понять, как это работает. Обратите внимание на то, что этот код основан на оригинальном примере, и должен работать также и на втором примере.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# -*- coding: utf-8 -*- import xml.etree.cElementTree as ET def parseXML(xml_file): """ Парсинг XML используя ElementTree """ tree = ET.ElementTree(file=xml_file) print(tree.getroot()) root = tree.getroot() print("tag=%s, attrib=%s" % (root.tag, root.attrib)) for child in root: print(child.tag, child.attrib) if child.tag == "appointment": for step_child in child: print(step_child.tag) # Парсинг всей XML структуры. print("-" * 40) print("Iterating using a tree iterator") print("-" * 40) iter_ = tree.getiterator() for elem in iter_: print(elem.tag) # получаем данные используя дочерние элементы. print("-" * 40) print("Обрабатываем дочерние жлменты getchildren()") print("-" * 40) appointments = root.getchildren() for appointment in appointments: appt_children = appointment.getchildren() for appt_child in appt_children: print("%s=%s" % (appt_child.tag, appt_child.text)) if __name__ == "__main__": parseXML("appt.xml") |
Вы уже должны понять, что и как работает, но в этом примере и предыдущем мы импортируем cElementTree вместо обычного ElementTree. Разница между этими двумя в том, что cElementTree основан на С, а не на Python, так что он намного быстрее. В любом случае, мы снова создаем объект ElementTree и извлекаем root из него. Обратите внимание на то, что мы выводим root, его тег и атрибуты. Далее мы покажем несколько способов итерации тегов. Первый цикл просто итерирует XML, дочку за дочкой. Таким образом выведется только дочерний код (назначение) с наивысшим уровнем , так что мы добавили оператор if, чтобы проверить дочерний код и выполнить его итерацию. Далее мы берем итератор из объекта tree и также итерируем его. В итоге мы получим ту же информацию, но без дополнительных ступеней, как в первом примере. Третий метод использует корневую функцию getchildren(). Так что нам снова понадобится применить внутренний цикл, чтобы получить все теги дочернего кода и назначения. В последнем примере используется корневой метод iter() для цикла всех тегов, соответствующих строке “begin”.
Как было отмечено в последнем разделе, вы также можете использовать методы find() или findall(), чтобы облегчить поиск конкретных тегов, или набор тегов соответственно. Также обратите внимание на то, что каждый объект Element имеет свой тег и текстовое значение, этим можно воспользоваться для получения необходимой точной информации.
Подведем Итоги
Теперь вы знаете, как использовать minidom для парсинга XML. Вы также освоили ElementTree для создания, редактирования и парсинга XML. Существуют и другие библиотеки вне Python, которые предлагают дополнительные методы для работы с XML. Убедитесь в том, что вы пользуетесь понятным вам инструментом, так как данный вопрос может быть очень сложным и непонятным, если пытаться решить его неправильным инструментом.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»