我需要解析非常大的XML文件(3-5GB范围),该文件必须根据XML节点中包含的数据分成几个较小的XML文件。
每个输入文件都包含数十万个<measure>
元素,就像这个(非常)简化的片段中一样。
<items>
<measure code="0810">
<condition sequ="001" SID="-5041162"/>
<footnote Id="00550"/>
<footnote Id="00735"/>
</measure>
<measure code="6304">
<component Id="01" national="1"/>
<footnote Id="00001"/>
</measure>
<measure code="0811">
<condition sequ="002" SID="-5041356"/>
<footnote Id="00555"/>
</measure>
<measure code="2915">
<component Id="01" national="0"/>
<certif SID="-737740"/>
<certif SID="-737780"/>
</measure>
</items>
实际<measure>
元素的内容几乎可以是任何格式正确的XML。
我在解析这些文件时需要执行两个过程:
<measure>
元素的内容中提取信息,然后转储
它到MongoDB数据库(此部分已解决...)<measure>
的“ code”属性的前两位数字
节点。也就是说,新的100个XML文件(名为“ part_00.xml”
需要创建“ part_99.xml”),并且每个<measure>
元素都必须是
附加到相应的子文件。
即示例中的<measure>
块1和3应该复制到'part_08.xml',块2应该复制到'part_63.xml'... 我正在使用SAX解析原始文件,并且上面的进程1运行良好。 SAX流程的纯框架是:
import sys
from xml.sax import ContentHandler
from xml.sax import make_parser
class ParseMeasures(ContentHandler):
code = ''
def startElement(self, name, attrs):
if name == 'measure':
self.code = attrs.get('code')
def endElement(self, name):
if name == 'measure':
print('***Must append <measure> block to file part_{0}.xml'.format(self.code[:2]))
def main(args):
handler = ParseMeasures()
sax_parser = make_parser()
sax_parser.setContentHandler(handler)
sax_parser.parse('my_large_xml.file.xml')
print('Ended')
if __name__ == '__main__':
main(sys.argv[1:])
我需要的是能够访问'endElement()'中的整个<measure>
XML元素,以将其附加到相应的子文件中。
是否可以将SAX与其他XML解析功能结合使用,从而可以在'endElement()'中获取整个<measure>
XML元素? (我可以处理子文件的创建和管理……这不是问题!)
首先,也许在这种情况下SAX方法不是最合适的吗?
“唯一”的警告是该进程应处理3-5GB范围内的输入文件...
答案 0 :(得分:2)
以下是一种混合解决方案,该解决方案使用内置的SAX解析器生成解析事件,并使用lxml生成部分树(仅<measure>
元素,一次仅包含一个)。
一旦构建了元素,lxml的API for incremental XML generation就会根据@code
的值将其序列化为不同的文件。
此代码可处理<measure>
元素内的任何嵌套级别以及包括空格在内的文本值。它目前不处理注释,处理指令或名称空间,但可以添加对它们的支持。
即使输入文件很大,内存消耗也应保持较低水平。 lxml将增加一些开销,但是迭代编写支持非常方便。总体上来说,完全手动完成此操作会更快,但也会更加复杂。
from xml.sax import ContentHandler, make_parser
from lxml import etree
class ParseMeasures(ContentHandler):
def __init__(self):
self.stack = []
self.open = False
self.elem = None
self.writers = {}
self.text = []
def _get_writer(self, filename):
with etree.xmlfile(filename) as xf:
with xf.element('items'):
while True:
el = (yield)
xf.write(el)
xf.flush() # maybe don't flush *every* write
def _write(self):
grp = self.elem.attrib['code'][0:2]
if grp in self.writers:
writer = self.writers[grp]
else:
writer = self.writers[grp] = self._get_writer('part_%s.xml' % grp)
next(writer) # run up to `yield` and wait
writer.send(self.elem) # write out current `<measure>`
self.elem = None
def _add_text(self):
if self.elem is not None and self.text:
if self.open:
self.elem.text = ''.join(self.text)
else:
self.elem.tail = ''.join(self.text)
self.text = []
def startElement(self, name, attrib):
if self.stack or name == 'measure':
self._add_text()
self.open = True
self.elem = etree.Element(name, attrib)
self.stack.append(self.elem)
if len(self.stack) > 1:
self.stack[-2].append(self.elem)
def characters(self, content):
if self.elem is not None:
self.text.append(content)
def endElement(self, name):
if self.stack:
self._add_text()
self.open = False
self.elem = self.stack.pop()
if not self.stack:
self._write()
def endDocument(self):
# clean up
for writer in self.writers:
self.writers[writer].close()
def main():
sax_parser = make_parser()
sax_parser.setContentHandler(ParseMeasures())
sax_parser.parse(r'test.xml')
if __name__ == '__main__':
main()
这会生成part_08.xml
<items>
<measure code="0810">
<condition sequ="001" SID="-5041162"/>
<footnote Id="00550"/>
<footnote Id="00735"/>
</measure>
<measure code="0811">
<condition sequ="002" SID="-5041356"/>
<footnote Id="00555"/>
</measure>
</items>
和part_29.xml
<items>
<measure code="2915">
<component Id="01" national="0"/>
<certif SID="-737740"/>
<certif SID="-737780"/>
</measure>
</items>
和part_63.xml
<items>
<measure code="6304">
<component Id="01" national="1"/>
<footnote Id="00001"/>
</measure>
</items>
答案 1 :(得分:1)
下面(输出的是3个文件名为“ part_zz.xml”的文件)
请注意,下面的解决方案不使用任何外部库。
import sys
import xml.etree.ElementTree as ET
from collections import defaultdict
from xml.sax import ContentHandler
from xml.sax import make_parser
class ParseMeasures(ContentHandler):
def __init__(self):
self.data = defaultdict(list)
self.code = None
def startElement(self, name, attrs):
if name == 'measure':
self.code = attrs.get('code')[:2]
if self.code:
self.data[self.code].append((name, attrs._attrs))
def endDocument(self):
for k, v in self.data.items():
root = None
for entry in v:
if entry[0] == 'measure':
if not root:
root = ET.Element('items')
measure = ET.SubElement(root, 'measure')
temp = ET.SubElement(measure, entry[0])
temp.attrib = entry[1]
tree = ET.ElementTree(root)
tree.write('part_{}.xml'.format(k), method='xml')
def main(args):
handler = ParseMeasures()
sax_parser = make_parser()
sax_parser.setContentHandler(handler)
sax_parser.parse('my_large_xml.file.xml')
if __name__ == '__main__':
main(sys.argv[1:])
输出示例('part_08.xml')
<items>
<measure>
<measure code="0810"/>
<condition SID="-5041162" sequ="001"/>
<footnote Id="00550"/>
<footnote Id="00735"/>
</measure>
<measure>
<measure code="0811"/>
<condition SID="-5041356" sequ="002"/>
<footnote Id="00555"/>
</measure>
</items>