Эффективный способ итерации элементов xml
У меня есть xml:
<a>
<b>hello</b>
<b>world</b>
</a>
<x>
<y></y>
</x>
<a>
<b>first</b>
<b>second</b>
<b>third</b>
</a>
Мне нужно повторить все теги <a>
и <b>
, но я не знаю, сколько из них находится в документе. Поэтому я использую xpath
для обработки этого:
from lxml import etree
doc = etree.fromstring(xml)
atags = doc.xpath('//a')
for a in atags:
btags = a.xpath('b')
for b in btags:
print b
Это работает, но у меня довольно большие файлы, а cProfile
показывает мне, что xpath
очень дорого используется.
Интересно, может быть, есть более эффективный способ перебора бесконечного числа xml-элементов?
Ответы
Ответ 1
XPath должен быть быстрым. Вы можете уменьшить количество вызовов XPath до одного:
doc = etree.fromstring(xml)
btags = doc.xpath('//a/b')
for b in btags:
print b.text
Если это не достаточно быстро, вы можете попробовать Liza Daly fast_iter. Это имеет то преимущество, что не требуется, чтобы весь XML обрабатывался с помощью etree.fromstring
, а родительские узлы выбрасывались после того, как дети были посещены. Обе эти вещи помогают снизить требования к памяти. Ниже измененная версия fast_iter
, которая более агрессивно относится к удалению других элементов, которые больше не нужны.
def fast_iter(context, func, *args, **kwargs):
"""
fast_iter is useful if you need to free memory while iterating through a
very large XML file.
http://lxml.de/parsing.html#modifying-the-tree
Based on Liza Daly fast_iter
http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
See also http://effbot.org/zone/element-iterparse.htm
"""
for event, elem in context:
func(elem, *args, **kwargs)
# It safe to call clear() here because no descendants will be
# accessed
elem.clear()
# Also eliminate now-empty references from the root node to elem
for ancestor in elem.xpath('ancestor-or-self::*'):
while ancestor.getprevious() is not None:
del ancestor.getparent()[0]
del context
def process_element(elt):
print(elt.text)
context=etree.iterparse(io.BytesIO(xml), events=('end',), tag='b')
fast_iter(context, process_element)
Лиза Дали статья о синтаксическом анализе больших файлов XML может оказаться полезным для вас. Согласно статье, lxml с fast_iter
может быть быстрее, чем cElementTree
iterparse
. (См. Таблицу 1).
Ответ 2
Как насчет iter?
>>> for tags in root.iter('b'): # root is the ElementTree object
... print tags.tag, tags.text
...
b hello
b world
b first
b second
b third
Ответ 3
Используйте iterparse:
import lxml.etree as ET
for event, elem in ET.iterparse(filelike_object):
if elem.tag == "a":
process_a(elem)
for child in elem:
process_child(child)
elem.clear() # destroy all child elements
elif elem.tag != "b":
elem.clear()
Обратите внимание, что это не сохраняет всю память, но я смог пробираться через потоки XML через Gb, используя эту технику.
Попробуйте import xml.etree.cElementTree as ET
... он поставляется с Python, а его iterparse
быстрее, чем lxml.etree
iterparse
, согласно lxml docs:
"" Для приложений, для которых требуется высокая производительность синтаксического анализа больших файлов и мало что делает для сериализации, cET - лучший выбор. Также для iterparse приложений, которые извлекают небольшие объемы данных или агрегированную информацию из больших наборов данных XML которые не вписываются в память.Если речь идет о производительности в оба конца, однако, lxml имеет тенденцию быть в несколько раз быстрее. Таким образом, всякий раз, когда входные документы не намного больше, чем выход, lxml является явным победителем. ""
Ответ 4
bs4 очень полезен для этого
from bs4 import BeautifulSoup
raw_xml = open(source_file, 'r')
soup = BeautifulSoup(raw_xml)
soup.find_all('tags')