Обработка большого 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! чтобы освободить это.

Использование памяти не было большим, даже с этим хаком, но по крайней мере оно не продолжало расти.

Другие вопросы по тегам