Ruby 使用Nokogiri解析大型XML
所以我尝试使用Nokogiri解析一个400k+行的XML文件 XML文件具有以下基本格式:Ruby 使用Nokogiri解析大型XML,ruby,xml,ruby-on-rails-4,nokogiri,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
<?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>
这在我使用的测试文件上非常有效,尽管它们小得多,大约10000行
当我尝试在大型XML文件上运行此操作时,它根本没有完成。我把它开了一夜,它好像就锁起来了。我写的代码会导致内存密集或效率低下,有什么根本原因吗?我意识到我将每一对都存储在一个列表中,但这个列表不应该太大,无法填满内存
谢谢您的帮助。您可能内存不足,因为症状列表的内存太大。为什么不在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
文件可能太大,缓冲区无法处理。在这种情况下,您可以将其拆分为较小的临时文件,并单独处理它们。您可能会耗尽内存,因为症状列表的内存太大。为什么不在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
文件可能太大,缓冲区无法处理。在这种情况下,您可以将其拆分为较小的临时文件,然后分别进行处理。我发现了一些可能的问题。首先,这是:
@doc = Nokogiri::XML(sympFile)
将整个XML文件作为某种libxml2数据结构读入内存,这可能比原始XML文件大
然后你会这样做:
@doc.xpath(...).each
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
这可能不够聪明,无法生成一个只维护指向XML内部形式的指针的枚举器,它可能在构建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解析器,如下所示:
@doc.xpath(...).each
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
评论
这种结构使得观察元素变得非常容易,这样你就可以跟踪你已经走了多远。这样,您就可以停止并重新启动导入,只需对脚本进行一些小的修改。我发现了一些可能的问题。首先,这是:
@doc = Nokogiri::XML(sympFile)
将整个XML文件作为某种libxml2数据结构读入内存,这可能比原始XML文件大
然后你会这样做:
@doc.xpath(...).each
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
这可能不够聪明,无法生成一个只维护指向XML内部形式的指针的枚举器,它可能在构建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解析器,如下所示:
@doc.xpath(...).each
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
评论
这种结构使得观察元素变得非常容易,这样你就可以跟踪你已经走了多远。这样,您就可以停止并重新启动导入,只需对脚本进行一些小的修改。SAX解析器正是您想要使用的。如果你和我一样,不能使用Nokogiri文档,那么有一个很棒的gem叫它,它让这个过程变得非常简单 一个你想做什么的例子-
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
SAX解析器正是您想要使用的。如果你和我一样,不能使用Nokogiri文档,那么有一个很棒的gem叫它,它让这个过程变得非常简单 一个你想做什么的例子-
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
您还可以使用Nokogiri::XML::Reader。Nokogiri::XML::SAX解析器更占用内存,但您可以保留XML结构,即e.x
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
基于此,您还可以使用Nokogiri::XML::Reader。Nokogiri::XML::SAX解析器更占用内存,但您可以保留XML结构,即e.x
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
基于此我不认为/////DisordSign做了你认为它做的事。如果你找到它锁在哪里,你可能会得到更多信息。是在DOM构建期间,即Nokogiri::XML行吗?您始终可以尝试SAX或读卡器接口。是在解析过程中吗?尝试将////DisorderSign改为//DisorderSign,或者更好的方法是使用DisorderSign的完整路径并将//全部删除。我还没有机会查看这些答案,但我一定会这样做
等我有机会的时候。至于你的问题,我知道它在DOM构建过程中没有被锁定,它会在几秒钟内通过。我将尝试使用完整路径,并报告从该路径以及使用下面的答案得到的任何结果。将/////disordsign更改为///disordsign完全解决了我的问题。我试图遵循nokogiri提供的示例中的用法,但我一定是误解了什么。你能试着澄清一下为什么这解决了我的问题吗?另外,你是否可以在另一个帖子中写下你的回复,这样我就可以接受你的回答?我不认为/////DisordSign做了你认为它做的事情。如果你找到它锁在哪里,你可能会得到更多的信息。是在DOM构建期间,即Nokogiri::XML行吗?您始终可以尝试SAX或读卡器接口。是在解析过程中吗?尝试将////disordesign改为//disordesign,或者更好的方法是使用完整的路径来修改//disordesign,然后将其全部删除。我还没有机会查看这些答案,但我会在有机会时确保这样做。至于你的问题,我知道它在DOM构建过程中没有被锁定,它会在几秒钟内通过。我将尝试使用完整路径,并报告从该路径以及使用下面的答案得到的任何结果。将/////disordsign更改为///disordsign完全解决了我的问题。我试图遵循nokogiri提供的示例中的用法,但我一定是误解了什么。你能试着澄清一下为什么这解决了我的问题吗?还有,你是否可以在另一篇文章中写下你的回答,这样我就可以接受你的回答?虽然这没有解决问题,但在xpath循环中执行SQL肯定更好,谢谢。虽然这没有解决问题,但在xpath循环中执行SQL肯定更好,谢谢。等我有时间,我将尝试此操作,并用结果编辑此评论。检查我对原始帖子的回复。虽然这解决了我的问题,但我确实看到SAX解析器对于更大的文件更有意义,并且可能最终会将其更改为该文件,谢谢您的帮助。啊,我错过了森林之树。您可以将该注释转换为答案,并在24小时延迟后接受自己的答案。对于来自internet的文件,您将如何调用SAX解析器?还有,如果用case语句替换掉那些if呢?@cyriduchon Doris:The可以使用IO句柄,这样你就可以直接把它连接到你正在下载的文件上。你可以使用任何适合你的控制结构。当我有时间的时候,我会尝试这个,并用结果编辑这个评论。检查我对我的原始帖子的回复。虽然这解决了我的问题,但我确实看到SAX解析器对于更大的文件更有意义,并且可能最终会将其更改为该文件,谢谢您的帮助。啊,我错过了森林之树。您可以将该注释转换为答案,并在24小时延迟后接受自己的答案。对于来自internet的文件,您将如何调用SAX解析器?还有,如果用case语句替换掉那些if呢?@cyriduchon Doris:The可以使用IO句柄,这样你就可以直接把它连接到你正在下载的文件上。您可以使用任何适合您的控制结构。