Эффективный способ итерации элементов 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')