针对25GB数据的高效XML解析

时间:2012-04-05 19:35:01

标签: python xml xml-parsing beautifulsoup lxml

我的目标是解析25 GB的XML数据。下面给出了这种数据的一个例子:

<Document>
<Data Id='12' category='1'  Body="abc"/>
<Data Id='13' category='1'  Body="zwq"/>
.
.
<Data Id='82018030' category='2' CorrespondingCategory1Id='13' Body="pqr"/>

然而......考虑到我所拥有的“25 GB”数据......我的方法效率很低。请提出一些改进我的代码或替代方法的方法。还要包括一个小例子代码,以使事情更清晰。

6 个答案:

答案 0 :(得分:4)

您可能会发现SAX解析器更适合此任务。 SAX解析器不是构建DOM,而是将XML文件转换为元素流,并调用您提供的函数以便处理每个元素。

好处是,与DOM解析器相比,SAX解析器可以非常快速且内存效率高,有些甚至不需要同时给出所有XML,这对于25 GB的解析是理想的

不幸的是,如果您需要任何上下文信息,例如“我想要标记<B>,但只有它位于标记<A>内”,您必须自己维护它,因为所有解析器都是“开始”标记<A>,开始标记<B>,结束标记<B>,结束标记<A>。“它从未明确告诉您标记<B>位于标记<A>内,您必须从您所看到的内容中找出答案。一旦你看到一个元素,它就会消失,除非你自己记住它。

这对于复杂的解析作业非常繁琐,但是你的作业可能是可管理的。

Python的标准库在xml.sax中有一个SAX解析器。您可能想要xml.sax.xmlreader.IncrementalParser

答案 1 :(得分:0)

我在查看您的问题时的第一个建议是使用MySQL或sqlite等关系数据库。将XML数据放入此表单并不难,然后查询该数据会更直接,更快。

答案 2 :(得分:0)

您的初始算法在O(n ^ 2)中运行,对于25GB的数据来说这将非常慢。理想情况下,您将其归结为O(n)或O(n log n)。如果没有关于数据的任何其他信息(比如类别1或类别2是否更小等),您可以执行类似的操作(即O(n)):

from lxml import objectify
f=open('myfile25GB', 'r')
text=f.read()
root=objectify.fromstring(text)

cat_one_bodies = {}
for e in root.attrib['Document'].row:
    category = int(e.attrib['category'])
    body = e.attrib['Body']
    if category == 1:
        e_id = int(e.attrib['Id'])
        cat_one_bodies[e_id] = body
    else: #Assuming there are only 2 categories
        cat_one_id = int(e.attrib['CorrespondingCategory1Id'])
        print "Cat1 Body: '%s' Cat2 Body: '%s'" % (body, cat_one_bodies[cat_one_id])

虽然这不会解析您的文件,但希望它会向您展示这个想法。它可能会占用相当多的内存(因为它维护了字典中的所有category1主体),所以这可能是一个考虑因素。

答案 3 :(得分:0)

在目前在Saxon-EE中实现的XSLT 3.0(草案)中,您可以编写流式转换来解决此问题,如下所示:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map">
<xsl:mode streamable="yes"/>
<xsl:template match="/">
  <xsl:iterate select="Document/Data">
    <xsl:param name="map" select="map{}"/>
    <xsl:choose>
      <xsl:when test="@category='1'">
        <xsl:next-iteration>
          <xsl:with-param name="map" select="map:put($map, string(@Id), string(@Body))"/>
        </xsl:next-iteration>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="'Cat1 Body: ', 
                              $map(@CorrespondingCategoryId), 'Cat2 Body', @Body"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:iterate>
</xsl:template>

我没有测试过这个问题(这是在为期四天的假期前夕深夜......)但是如果你有兴趣采用这种方法,我会很乐意提供帮助。 XSLT 3.0仍然是草案规范,相当流畅。它的重点是解决像这样的问题,使用流媒体方法处理使用有界内存的非常大的文档。 Saxon-EE 9.4实现了规范的快照。

答案 4 :(得分:0)

如果ID按升序排列,那么您可以推出自己的函数,该函数读取文件中任何位置的元素。然后你可以只扫描整个文件,对于每个元素,你可以使用二进制搜索算法找到相应的元素。该东西将在O(n log n)中运行,同时仍然使用可忽略的内存量。

答案 5 :(得分:0)

尝试使用lxml中的iterparse。我认为它适合您希望处理的问题。