Эффективный способ перебора элементов XML - PullRequest
16 голосов
/ 14 января 2011

У меня есть 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-элементов?

Ответы [ 4 ]

20 голосов
/ 15 января 2011

XPath должен быть быстрым.Вы можете уменьшить количество вызовов XPath до одного:

doc = etree.fromstring(xml)
btags = doc.xpath('//a/b')
for b in btags:
    print b.text

Если этого недостаточно, вы можете попробовать 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's 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's 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 s iterparse.(См. Таблицу 1).

10 голосов
/ 15 января 2011

Как насчет 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
5 голосов
/ 15 января 2011

Использовать 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 :

"" "Для приложений, которым требуется высокая пропускная способность синтаксического анализатора больших файлов и которые практически не выполняют сериализацию, cET является лучшим выбором. Также для приложений iterparse, которые извлекают небольшие объемы данных или агрегируют информацию из больших наборов данных XML которые не помещаются в память. Однако, если речь идет о производительности в обоих направлениях, lxml в целом будет в несколько раз быстрее. Поэтому, когда входные документы не намного больше выходных, lxml является явным победителем. "" «

0 голосов
/ 02 марта 2017

bs4 очень полезен для этого

from bs4 import BeautifulSoup
raw_xml = open(source_file, 'r')
soup = BeautifulSoup(raw_xml)
soup.find_all('tags')
...