Используя lxml и iterparse() для синтаксического анализа большого (+ - 1Gb) XML файла

Мне нужно разобрать XML файл 1Gb со структурой, такой как ниже, и извлечь текст в тегах "Автор" и "Контент":

<Database>
    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>

    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>

    [...]

    <BlogPost>
        <Date>MM/DD/YY</Date>
        <Author>Last Name, Name</Author>
        <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content>
    </BlogPost>
</Database>

До сих пор я пробовал две вещи: i) чтение всего файла и переход через него с помощью .find(xmltag) и ii) анализ XML файла с помощью lxml и iterparse(). Первый вариант у меня есть, чтобы работать, но он очень медленный. Второй вариант, который мне не удалось получить с земли.

Вот часть того, что у меня есть:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    if element.tag == "BlogPost":
        print element.text
    else:
        print 'Finished'

Результатом этого является только пустое пространство без текста.

Я должен делать что-то неправильно, но я не могу этого понять. Кроме того, если это было недостаточно очевидно, я довольно новичок в python, и это первый раз, когда я использую lxml. Пожалуйста, помогите!

Ответ 1

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    for child in element:
        print child.tag, child.text
    element.clear()

окончательная очистка не позволит вам использовать слишком много памяти.

[update:], чтобы получить "все между... как строку", я думаю, вы хотите один из:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    print etree.tostring(element)
    element.close()

или

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    print ''.join([etree.tostring(child) for child in element])
    element.close()

или, возможно, даже:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    print ''.join([child.text for child in element])
    element.close()

Ответ 2

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

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    for child in element:
        print child.tag, child.text
    element.clear()

^ Это не масштабируемое решение, особенно если ваш исходный файл становится все больше и больше. Лучшее решение - получить корневой элемент и очистить его каждый раз, когда вы загружаете полную запись. Это позволит сохранить память довольно стабильной (суб-20MB я бы сказал).

Вот решение, которое не требует поиска определенного тега. Эта функция возвращает генератор, который дает все первые дочерние узлы (например, <BlogPost>) под корнем node (например, <Database>). Это делается путем записи начала первого тега после корня node, затем ожидания соответствующего конечного тега, получения всего элемента, а затем очистки корня node.

from lxml import etree

xmlfile = '/path/to/xml/file.xml'

def iterate_xml(xmlfile):
    doc = etree.iterparse(xmlfile, events=('start', 'end'))
    _, root = next(doc)
    start_tag = None
    for event, element in doc:
        if event == 'start' and start_tag is None:
            start_tag = element.tag
        if event == 'end' and element.tag == start_tag:
            yield element
            start_tag = None
            root.clear()

Ответ 3

Я предпочитаю XPath для таких вещей:

In [1]: from lxml.etree import parse

In [2]: tree = parse('/tmp/database.xml')

In [3]: for post in tree.xpath('/Database/BlogPost'):
   ...:     print 'Author:', post.xpath('Author')[0].text
   ...:     print 'Content:', post.xpath('Content')[0].text
   ...: 
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.
Author: Last Name, Name
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.

Я не уверен, что он отличается от обработки больших файлов. Комментарии об этом будут оценены.

Выполняя свой путь,

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
     for info in element.iter():
         if info.tag in ('Author', 'Content'):
             print info.tag, ':', info.text