[英]Parsing Large XML with Nokogiri
因此,我尝试使用Nokogiri解析一个超过40万行的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
根据此博客
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.