使用Elementtree编写Python大型XML代码

问题描述:

我刚开始着眼于使用python从csv生成XML。我正在尝试使用ElementTree来做到这一点。但是,我无法得到我想要的格式。使用Elementtree编写Python大型XML代码

Here是我使用的示例csv数据。原始数据大约有1200万行,由此产生的全部xml大约有3800万行。以下是我的代码。

import csv 
import sys 
from xml.etree.ElementTree import Element, SubElement, Comment, ElementTree, tostring 
from xml.etree import ElementTree 
from xml.dom import minidom 

def prettify(elem): 
    rough_string = tostring(elem, 'utf-8', method="xml") 
    reparsed = minidom.parseString(rough_string) 
    return reparsed.toprettyxml(indent=" ") 

root = Element('plans') 
sys.stdout = open('C:/Users/s/Desktop/xml6.xml', 'w') 
print(prettify(root)) 

with open('C:/Users/s/Desktop/trip2.csv', 'rt') as f: 
    current_group = None 
    reader = csv.reader(f) 
    for row in reader: 
      personid, hno, pno, OXutmmtr, OYutmmtr, DXutmmtr, DYutmmtr, opcl, dpcl, depday, deptm, arrtm, newendacttma, dept, arr, newendacttmh, mode2, opurp2, dpurp2, dorp2 = row 
      if current_group is None or personid != old1 : 
      # Start a new group 
      current_group = SubElement(root, 'person', {'id':personid}) 
      old1 = personid 
      pln = SubElement(current_group, 'plan') 
      activ = SubElement(pln, 'act', {'type':opurp2, 'x':OXutmmtr, 'y':OYutmmtr,}) 
      trvl = SubElement(pln, 'leg', {'mode': mode2,}) 
      activ = SubElement(pln, 'act',{'type': dpurp2, 'x': DXutmmtr, 'y': DYutmmtr,}) 

      elif personid == old1: 
      trvl = SubElement(pln, 'leg', {'mode': mode2,}) 
      activ = SubElement(pln, 'act', {'type': dpurp2, 'x': DXutmmtr,'y': DYutmmtr,}) 
       if newendacttmh == "02:59:00": 
       sys.stdout = open('C:/Users/s/Desktop/xml6.xml', 'a') 
       print(prettify(current_group)) 
       root.clear() 

我需要一个像

<?xml version="1.0" ?> 
<plans> 


<person id="101"> 
    <plan> 
    <act type="home" x="338471.624256" y="3114225.84531"/> 
    <leg mode="sov"/> 
    <act type="work" x="353108.46905" y="3086263.42028"/> 
    <leg mode="sov"/> 
    <act type="home" x="338471.624256" y="3114225.84531"/> 
    </plan> 
</person> 


<person id="201"> 
    <plan> 
    <act type="home" x="338535.623855" y="3114558.14898"/> 
    <leg mode="hov3+"/> 
    <act type="meal" x="338520.432083" y="3105225.60283"/> 
    <leg mode="hov3+"/> 
    <act type="shop" x="333193.19769" y="3103842.61842"/> 
    <leg mode="hov3+"/> 
    <act type="pers.bus" x="338148.26292" y="3083556.85073"/> 
    <leg mode="hov3+"/> 
    <act type="home" x="338535.623855" y="3114558.14898"/> 
    </plan> 
</person> 

</plans> 

的格式,但我得到的格式一样

<?xml version="1.0" ?> 
<plans/> 

<?xml version="1.0" ?> 
<person id="101"> 
    <plan> 
    <act type="home" x="338471.624256" y="3114225.84531"/> 
    <leg mode="sov"/> 
    <act type="work" x="353108.46905" y="3086263.42028"/> 
    <leg mode="sov"/> 
    <act type="home" x="338471.624256" y="3114225.84531"/> 
    </plan> 
</person> 

<?xml version="1.0" ?> 
<person id="201"> 
    <plan> 
    <act type="home" x="338535.623855" y="3114558.14898"/> 
    <leg mode="hov3+"/> 
    <act type="meal" x="338520.432083" y="3105225.60283"/> 
    <leg mode="hov3+"/> 
    <act type="shop" x="333193.19769" y="3103842.61842"/> 
    <leg mode="hov3+"/> 
    <act type="pers.bus" x="338148.26292" y="3083556.85073"/> 
    <leg mode="hov3+"/> 
    <act type="home" x="338535.623855" y="3114558.14898"/> 
    </plan> 
</person> 

从本质上讲,我想,当我到了每个人的结束追加到文件记录(由时间字符串02:59:00表示),因为如果我等到整个根树被构造,那么我正在运行内存错误问题。有趣的是,内存使用永远不会超过2 GB,即使仍然存在12 GB的内存,程序仍会出现内存错误问题。我试图遵循关于使用ElementTree(top).write(sys.stdout)序列化XML流的建议here,但我无法操作它。我知道(某种)SAX解析器更适合大型XML创建。但是,我现在有点害怕。任何建议或建议将对我有用。

+0

我没有看到您的两个XML示例之间的任何区别。你只是在谈论空白区别? – BrenBarn

+0

在第一个(也是正确的)版本中,所有'person'元素都在'plans'元素中。在第二个版本中,'plans'元素在第二行开始和结束,'person'元素独立于'plans'。此外,在第二版xml序言中,'重复每个'person'元素。 – Gandalf

+0

你为什么用奇怪的方式做事,用'tostring'输出XML,然后用minidom再读一遍? – BrenBarn

你有正确的想法,但我认为你需要唠叨你的工具。像ElementTree这样的XML DOM文档不打算以迭代方式编写。当你做了print(prettify(root))时,你写下了当时存在的整棵树,这只是<plans/>。相反,您可以手动编写xml声明和一个开放标记,然后您可以使用DOM生成并将每个<plan>作为单独的文档编写。

minidom包含为您写入文档的每个树的xml声明,因此您需要切换到其他工具。 lxml有一个很好的打印效果。您想将xml写入二进制文件,因为DOM将处理任何编码。我发现sys.stdout不必要的问题,直接写文件。

你没有csv文件模式非常适合python3,所以我改变了这一点,而我在这里。

我也摆弄你如何创建每个我认为是更清晰。

import csv 
import sys 
from lxml.etree import Element, SubElement, Comment, ElementTree 

# note: I changed file paths for test 

with open('xml6.xml', 'wb') as outxml: 
    # write xml declaration and containing <plans> tag 
    outxml.write(b"""<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> 
<plans> 
""") 
    # process csv 
    with open('trip.csv', 'r', newline='') as f: 
     old1 = '' 
     current_group = None 
     reader = csv.reader(f) 
     for row in reader: 
      personid, hno, pno, OXutmmtr, OYutmmtr, DXutmmtr, DYutmmtr, opcl, dpcl, depday, deptm, arrtm, newendacttma, dept, arr, newendacttmh, mode2, opurp2, dpurp2, dorp2 = row 
      if personid != old1 : 
       # skip on first loop then write current group to file 
       if old1: 
        outxml.write(b"\n") 
        ElementTree(current_group).write(outxml, encoding='utf-8', method='xml', pretty_print=True) 
       # Start a new group 
       current_group = Element('person', {'id':personid}) 
       old1 = personid 
       pln = SubElement(current_group, 'plan') 
       activ = SubElement(pln, 'act', {'type':opurp2, 'x':OXutmmtr, 'y':OYutmmtr,}) 
      # write data 
      trvl = SubElement(pln, 'leg', {'mode': mode2,}) 
      activ = SubElement(pln, 'act',{'type': dpurp2, 'x': DXutmmtr, 'y': DYutmmtr,}) 

    # terminate outer tag and done 
    outxml.write(b""" 
</plans> 
""") 
+0

感谢您花时间解释这一点并帮助我解决问题。我需要通过更有条理的方式学习python来更深入地了解python。 – Gandalf

.toprettyxml打印您将它作为XML提供的内容文档,因此它包含XML序言。您正在为每个“计划”打印单独的文档。第一次,你打印空的plans节点,所以你得到<plans/>。在循环的每次迭代中,清除根目录,向其中添加一个元素,然后输出整个根目录。所以是的,你要重复输出整个元素plans。每次它只有一个​​在里面。当您使用诸如tostringtoprettyxml之类的东西时,您会输出整个元素,而不仅仅是开始标记。

正如你猜测的那样,你应该看看使用SAX方法。正如minidom库的名称所示,它是一个DOM库。正如ElementTree的the docs所说的那样,它被设计用来在内存中存储分层数据结构。如果您不想一次性将数据结构存储在内存中,它可能不是最佳选择。 (它有一些增量设施阅读,但不写。)你可以看看xml.sax库。但是,你应该探索这样的解决方案,并问一个关于如何使用SAX的更具体的问题,如果有的话。