Обработка большого XML-файла с помощью фрагмента libxml-ruby
Я хотел бы прочитать большой XML- файл, который содержит более миллиона небольших библиографических записей (например, <article>...</article>
) используя libxml в Ruby. Я пробовал класс Reader в сочетании с expand
метод чтения записи по записи, но я не уверен, что это правильный подход, так как мой код потребляет память. Следовательно, я ищу рецепт, как удобно обрабатывать записи по записи с постоянным использованием памяти. Ниже мой основной цикл:
File.open('dblp.xml') do |io|
dblp = XML::Reader.io(io, :options => XML::Reader::SUBST_ENTITIES)
pubFactory = PubFactory.new
i = 0
while dblp.read do
case dblp.name
when 'article', 'inproceedings', 'book':
pub = pubFactory.create(dblp.expand)
i += 1
puts pub
pub = nil
$stderr.puts i if i % 10000 == 0
dblp.next
when 'proceedings','incollection', 'phdthesis', 'mastersthesis':
# ignore for now
dblp.next
else
# nothing
end
end
end
Ключевым моментом здесь является то, что dblp.expand
читает целое поддерево (как <article>
запись) и передает его в качестве аргумента фабрике для дальнейшей обработки. Это правильный подход?
Внутри фабричного метода я затем использую высокоуровневое XPath-подобное выражение для извлечения содержимого элементов, как показано ниже. Опять же, это жизнеспособно?
def first(root, node)
x = root.find(node).first
x ? x.content : nil
end
pub.pages = first(node,'pages') # node contains expanded node from dblp.expand
3 ответа
При обработке больших XML-файлов вы должны использовать потоковый парсер, чтобы избежать загрузки всего в память. Есть два общих подхода:
- Нажмите парсеры, такие как SAX, где вы реагируете на обнаруженные теги по мере их получения (см. Ответ tadman).
- Вытащите парсеры, где вы управляете "курсором" в XML-файле, который вы можете перемещать с помощью простых примитивов, таких как идти вверх / вниз и т. Д.
Я думаю, что push-парсеры удобно использовать, если вы хотите получить только некоторые поля, но они обычно беспорядочные для сложного извлечения данных и часто реализуются с использованием case... when...
конструкции
Парсер Pull, по моему мнению, является хорошей альтернативой между моделью на основе дерева и парсером. Вы можете найти хорошую статью в журнале доктора Добба о парсерах с REXML .
При обработке XML два общих варианта основаны на дереве и на основе событий. Древовидный подход обычно читает весь XML-документ и может занимать большой объем памяти. Подход, основанный на событиях, не использует никакой дополнительной памяти, но ничего не делает, если вы не напишите свою собственную логику обработчика.
Модель, основанная на событиях, используется синтаксическим анализатором в стиле SAX и производными реализациями.
Пример с REXML: http://www.iro.umontreal.ca/~lapalme/ForestInsteadOfTheTrees/HTML/ch08s01.html
REXML: http://ruby-doc.org/stdlib/libdoc/rexml/rdoc/index.html
У меня была такая же проблема, но я думаю, что решил ее, вызвав Node#remove! на расширенном узле. В вашем случае, я думаю, вы должны сделать что-то вроде
my_node = dblp.expand [делай то, что ты должен делать с my_node] dblp.next my_node.remove!
Не совсем уверен, почему это работает, но если вы посмотрите на источник для LibXML::XML::Reader#expand, есть комментарий об освобождении узла. Я предполагаю, что Reader # expand связывает узел с Reader, и вы должны вызвать Node#remove! чтобы освободить это.
Использование памяти не было большим, даже с этим хаком, но по крайней мере оно не продолжало расти.