Разбор большого файла с помощью SaxMachine, кажется, загружает весь файл в память - PullRequest
5 голосов
/ 08 февраля 2012

У меня есть xml-файл 1.6 ГБ, и когда я анализирую его с помощью Sax Machine, он, кажется, не передает и не загружает файл в виде фрагментов - скорее он загружает весь файл в память (или, возможно, утечка памяти где-нибудь?), потому что мой процесс ruby ​​поднимается выше 2,5 ГБ ОЗУ Я не знаю, где он перестает расти, потому что у меня закончилась память.

Для файла меньшего размера (50 МБ) он также загружает весь файл. Моя задача перебирает записи в файле xml и сохраняет каждую запись в базе данных. Требуется около 30 секунд «холостого хода», а затем внезапно начинают выполняться запросы к базе данных.

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

Есть что-то, что я пропускаю?

Большое спасибо

Обновление для добавления образца кода

class FeedImporter

  class FeedListing
    include ::SAXMachine

    element :id
    element :title
    element :description
    element :url

    def to_hash
      {}.tap do |hash|
        self.class.column_names.each do |key|
          hash[key] = send(key)
        end
      end
    end
  end

  class Feed
    include ::SAXMachine
    elements :listing, :as => :listings, :class => FeedListing
  end

  def perform
    open('~/feeds/large_feed.xml') do |file|

      # I think that SAXMachine is trying to load All of the listing elements into this one ruby object.
      puts 'Parsing'
      feed = Feed.parse(file)

      # We are now iterating over each of the listing elements, but they have been "parsed" from the feed already.
      puts 'Importing'
      feed.listings.each do |listing|
        Listing.import(listing.to_hash)
      end

    end
  end

end

Как видите, меня не волнует элемент <listings> в ленте. Я просто хочу атрибуты каждого элемента <listing>.

Вывод выглядит так:

Parsing
... wait forever
Importing (actually, I don't ever see this on the big file (1.6gb) because too much memory is used :(

Ответы [ 4 ]

3 голосов
/ 07 ноября 2012

К сожалению, сейчас есть три разные репо для саксофона.И что еще хуже, версия gemspec не столкнулась.

Несмотря на комментарий к блогу Грега Вебера , я не думаю, что этот код был интегрирован в форки pauldix или ezkl.Чтобы использовать ленивую версию кода на основе волоконно-оптических сетей, я думаю, вам нужно конкретно указать версию gregweb в вашем gemfile, например:

gem 'sax-machine', :git => 'https://github.com/gregwebs/sax-machine'
3 голосов
/ 10 февраля 2012

Вот Reader, который выдаст XML каждого листинга в блок, так что вы можете обрабатывать каждый листинг без загрузки всего документа в память

reader = Nokogiri::XML::Reader(file)
while reader.read
  if reader.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT and reader.name == 'listing'
    listing = FeedListing.parse(reader.outer_xml)
    Listing.import(listing.to_hash)
  end
end

Если элементы листинга могут быть вложенными, и вы хотите проанализироватьсамые отдаленные списки в виде отдельных документов, вы можете сделать это:

require 'rubygems'
require 'nokogiri'


# Monkey-patch Nokogiri to make this easier
class Nokogiri::XML::Reader
  def element?
    node_type == TYPE_ELEMENT
  end

  def end_element?
    node_type == TYPE_END_ELEMENT
  end

  def opens?(name)
    element? && self.name == name
  end

  def closes?(name)
    (end_element? && self.name == name) || 
      (self_closing? && opens?(name))
  end

  def skip_until_close
    raise "node must be TYPE_ELEMENT" unless element?
    name_to_close = self.name

    if self_closing?
      # DONE!
    else
      level = 1
      while read
        level += 1 if opens?(name_to_close)
        level -= 1 if closes?(name_to_close)

        return if level == 0
      end
    end
  end

  def each_outer_xml(name, &block)
    while read
      if opens?(name)
        yield(outer_xml)
        skip_until_close
      end
    end
  end

end

, как только вы исправите его, легко справиться с каждым списком в отдельности:

open('~/feeds/large_feed.xml') do |file|
  reader = Nokogiri::XML::Reader(file)
  reader.each_outer_xml('listing') do |outer_xml|

    listing = FeedListing.parse(outer_xml)
    Listing.import(listing.to_hash)

  end
end
2 голосов
/ 30 мая 2012

Я раздвоил саксофон, чтобы он использовал постоянную память: https://github.com/gregwebs/sax-machine

Хорошая новость: появился новый сопровождающий, который планирует объединить мои изменения.Я и новый сопровождающий уже год без проблем используем мою вилку.

0 голосов
/ 09 февраля 2012

Вы правы, SAXMachine с нетерпением читает весь документ.Посмотрите на источники его обработчиков: https://github.com/pauldix/sax-machine/blob/master/lib/sax-machine/sax_handler.rb

Чтобы решить вашу проблему, я бы использовал http://nokogiri.rubyforge.org/nokogiri/Nokogiri/XML/SAX/Parser.html напрямую и реализовал обработчик самостоятельно.

...