ruby - 使用 Nokogiri 解析大型 XML

标签 ruby xml ruby-on-rails-4 nokogiri

所以我正在尝试使用 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 内部形式的指针的枚举器,它可能在构建 NodeSet 时生成所有内容的副本。那xpath返回。这将为您提供 XML 的大部分内存扩展版本的另一个副本。我不确定这里发生了多少复制和数组构造,但即使它不复制所有内容,也有相当多的内存和 CPU 开销空间。

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

symptomsList.push([signId, name])

最后遍历该数组:

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

我发现 SAX parsers使用大型数据集效果更好,但使用起来更麻烦。您可以尝试像这样创建自己的 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"> 变得非常容易。元素,以便您可以跟踪您走了多远。这样您就可以通过对脚本进行一些小的修改来停止和重新启动导入。

关于ruby - 使用 Nokogiri 解析大型 XML,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19866226/

相关文章:

javascript - 如何在 Prototype.js 中使用 Ajax 获取 XML 文件(而不是字符串)?

ruby-on-rails - Rails 4.0 Strong Parameters 嵌套属性带有指向散列的键

ruby - 从 XML 转换为 REST 客户端以进行 POST 请求

java - 我应该在哪里放置 XSD 文件以用于 JAXB 代码生成和 XML 验证

java - JDOMParseException : Error on line -1: Premature end of file

ruby-on-rails - 在 Heroku 的 paper_trail 中自动设置谁负责控制台的更改

ruby-on-rails - 使用单元格新建和创建操作

ruby - 如何在 heroku 中使用自己的 mysql 数据库服务器?

ruby-on-rails - Spring 停止 Rails 控制台运行

ruby - 使用包管理器时,在 Ubuntu 上哪个位置安装了 Ruby 1.9?