Обработка большого XML-файла с помощью фрагмента libxml-ruby - PullRequest
4 голосов
/ 04 января 2010

Я бы хотел прочитать большой 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 ]

5 голосов
/ 04 января 2010

При обработке больших XML-файлов вы должны использовать потоковый парсер, чтобы избежать загрузки всего в память. Есть два общих подхода:

  • Push-парсеры как SAX, где вы реагируете на обнаруженные теги по мере их получения (см. tadman ответ).
  • Извлечение парсеров , где вы управляете «курсором» в файле XML, который вы можете перемещать с помощью простых примитивов, таких как идти вверх / вниз и т. Д.

Я думаю, что push-парсеры удобно использовать, если вы хотите получить только некоторые поля, но они обычно бесполезны для сложного извлечения данных и часто реализуются с использованием case... when... конструкций

Парсер Pull, по моему мнению, является хорошей альтернативой между моделью на основе дерева и парсером Push. Вы можете найти хорошую статью в журнале доктора Добба о парсерах с REXML.

1 голос
/ 04 января 2010

При обработке 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

0 голосов
/ 11 февраля 2010

У меня была такая же проблема, но я думаю, что решил ее, вызвав Node # remove! на расширенном узле. В вашем случае, я думаю, вы должны сделать что-то вроде

my_node = dblp.expand
[do what you have to do with my_node]
dblp.next
my_node.remove!

Не совсем уверен, почему это работает, но если вы посмотрите на источник для LibXML :: XML :: Reader # expand, есть комментарий об освобождении узла. Я предполагаю, что Reader # expand связывает узел с Reader, и вы должны вызвать Node # remove! освободить его.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...