使用 Nokogiri 解析大型 XML



所以我正在尝试使用Nokogiri解析一个400k+行的XML文件。

XML 文件具有以下基本格式:

<?xml version="1.0" encoding="windows-1252"?>
<JDBOR date="2013-09-01 04:12:31" version="1.0.20 [2012-12-14]" copyright="Orphanet (c) 2013">
 <DisorderList count="6760">
  *** Repeated Many Times ***
  <Disorder id="17601">
  <OrphaNumber>166024</OrphaNumber>
  <Name lang="en">Multiple epiphyseal dysplasia, Al-Gazali type</Name>
  <DisorderSignList count="18">
    <DisorderSign>
      <ClinicalSign id="2040">
        <Name lang="en">Macrocephaly/macrocrania/megalocephaly/megacephaly</Name>
      </ClinicalSign>
      <SignFreq id="640">
        <Name lang="en">Very frequent</Name>
      </SignFreq>
    </DisorderSign>
  </Disorder>
  *** Repeated Many Times ***
 </DisorderList>
</JDBOR>

以下是我创建的代码,用于解析每个 DisorderSign ID 和名称并将其返回到数据库中:

require 'nokogiri'
sympFile = File.open("Temp.xml")
@doc = Nokogiri::XML(sympFile)
sympFile.close()
symptomsList = []
@doc.xpath("////DisorderSign").each do |x|
    signId = x.at('ClinicalSign').attribute('id').text()      
    name = x.at('ClinicalSign').element_children().text()
    symptomsList.push([signId, name])
end
symptomsList.each do |x|
    Symptom.where(:name => x[1], :signid => Integer(x[0])).first_or_create
end

这在我使用的测试文件上非常有效,尽管它们要小得多,大约 10000 行。

当我尝试在大型XML文件上运行它时,它根本没有完成。我把它放了一夜,它似乎只是锁定了。我编写的代码会占用大量内存或效率低下,这有什么根本原因吗?我意识到我将每个可能的对都存储在一个列表中,但这不应该大到足以填满内存。

感谢您的任何帮助。

我看到了一些可能的问题。首先,这个:

@doc = Nokogiri::XML(sympFile)
将整个XML文件

作为某种libxml2数据结构放入内存中,并且可能比原始XML文件大。

然后你做这样的事情:

@doc.xpath(...).each

这可能不够智能,无法生成仅维护指向 XML 内部形式的指针的枚举器,当它生成xpath返回的NodeSet时,它可能会生成所有内容的副本。这将为您提供大多数 XML 扩展内存版本的另一个副本。我不确定这里发生了多少复制和数组构建,但即使它不复制重复所有内容,也有相当大的内存和 CPU 开销的空间。

然后,您复制您感兴趣的内容:

symptomsList.push([signId, name])

最后遍历该数组:

symptomsList.each do |x|
    Symptom.where(:name => x[1], :signid => Integer(x[0])).first_or_create
end

我发现 SAX 解析器在处理大型数据集时效果更好,但它们使用起来更麻烦。您可以尝试创建自己的 SAX 解析器,如下所示:

class D < Nokogiri::XML::SAX::Document
  def start_element(name, attrs = [ ])
    if(name == 'DisorderSign')
      @data = { }
    elsif(name == 'ClinicalSign')
      @key        = :sign
      @data[@key] = ''
    elsif(name == 'SignFreq')
      @key        = :freq
      @data[@key] = ''
    elsif(name == 'Name')
      @in_name = true
    end
  end
  def characters(str)
    @data[@key] += str if(@key && @in_name)
  end
  def end_element(name, attrs = [ ])
    if(name == 'DisorderSign')
      # Dump @data into the database here.
      @data = nil
    elsif(name == 'ClinicalSign')
      @key = nil
    elsif(name == 'SignFreq')
      @key = nil
    elsif(name == 'Name')
      @in_name = false
    end
  end
end

结构应该非常清晰:你观察你感兴趣的元素的打开,并在做的时候做一些簿记设置,然后缓存字符串,如果你在一个你关心的元素内,最后清理和处理数据元素关闭。您的数据库工作将取代

# Dump @data into the database here.

评论。

这种结构使得观察<Disorder id="17601">元素变得非常容易,以便您可以跟踪自己走了多远。这样,您可以通过对脚本进行一些小的修改来停止并重新启动导入。

SAX 解析器绝对是您想要使用的。 如果你像我一样,不能使用Nokogiri文档,那么有一个很棒的宝石叫做Saxerator,它使这个过程变得非常简单。

举个例子来说明你正在尝试做的事情——

require 'saxerator'
parser = Saxerator.parser(Temp.xml)

parser.for_tag(:DisorderSign).each do |sign|
  signId = sign[:ClinicalSign][:id]
  name = sign[:ClinicalSign][:name]
  Symtom(:name => name, :id => signId).create!
end

您可能内存不足symptomsList因为内存大小太大。 为什么不在 xpath 循环中执行 SQL?

require 'nokogiri'
sympFile = File.open("Temp.xml")
@doc = Nokogiri::XML(sympFile)
sympFile.close()
@doc.xpath("////DisorderSign").each do |x|
  signId = x.at('ClinicalSign').attribute('id').text()      
  name = x.at('ClinicalSign').element_children().text()
  Symptom.where(:name => name, :signid => signId.to_i).first_or_create
end

文件太大,缓冲区也可能无法处理。 在这种情况下,您可以将其切成较小的临时文件并单独处理它们。

您也可以

使用 Nokogiri::XML::Reader .Nokogiri::XML::SAX解析器更占用内存,但您可以保持XML结构,例如

class NodeHandler < Struct.new(:node)
  def process
    # Node processing logic
    #e.x.
    signId = node.at('ClinicalSign').attribute('id').text()      
    name = node.at('ClinicalSign').element_children().text()
  end
end

Nokogiri::XML::Reader(File.open('./test/fixtures/example.xml')).each do |node|
  if node.name == 'DisorderSign' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
    NodeHandler.new(
        Nokogiri::XML(node.outer_xml).at('./DisorderSign')
    ).process
  end
end

基于此博客

相关内容

  • 没有找到相关文章

最新更新