использование lxml и iterparse () для анализа большого (+ - 1Gb) XML-файла - PullRequest
14 голосов
/ 25 марта 2012

Мне нужно проанализировать XML-файл объемом 1 ГБ со структурой, подобной приведенной ниже, и извлечь текст в тегах «Автор» и «Содержимое»:

<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.Пожалуйста, помогите!

Ответы [ 3 ]

25 голосов
/ 25 марта 2012
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.clear()

или

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

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

for event, element in etree.iterparse(path_to_file, tag="BlogPost"):
    print ''.join([child.text for child in element])
    element.clear()
13 голосов
/ 13 февраля 2017

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

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

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

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

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()
5 голосов
/ 25 марта 2012

Я предпочитаю 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.

Хотя я не уверен, отличается ли это с точки зрения обработки больших файлов.Будем благодарны за комментарии по этому поводу.

Doing it your way,

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
...