14

Are there any XML parsers for Python that can parse file streams? My XML files are too big to fit in memory, so I need to parse the stream.

Ideally I wouldn't have to have root access to install things, so lxml is not a very good option.

I have been using xml.etree.ElementTree but I am convinced it is broken.

Community
  • 1
  • 1
Aillyn
  • 23,354
  • 24
  • 59
  • 84

3 Answers3

21

Here's good answer about xml.etree.ElementTree.iterparse practice on huge XML files. lxml has the method as well. The key to stream parsing with iterparse is manual clearing and removing already processed nodes, because otherwise you will end up running out of memory.

Another option is using xml.sax. The official manual is too formal to me, and lacks examples so it needs clarification along with the question. Default parser module, xml.sax.expatreader, implement incremental parsing interface xml.sax.xmlreader.IncrementalParser. That is to say xml.sax.make_parser() provides suitable stream parser.

For instance, given a XML stream like:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <entry><a>value 0</a><b foo='bar' /></entry>
  <entry><a>value 1</a><b foo='baz' /></entry>
  <entry><a>value 2</a><b foo='quz' /></entry>
  ...
</root>

Can be handled in the following way.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xml.sax


class StreamHandler(xml.sax.handler.ContentHandler):

  lastEntry = None
  lastName  = None


  def startElement(self, name, attrs):
    self.lastName = name
    if name == 'entry':
      self.lastEntry = {}
    elif name != 'root':
      self.lastEntry[name] = {'attrs': attrs, 'content': ''}

  def endElement(self, name):
    if name == 'entry':
      print({
        'a' : self.lastEntry['a']['content'],
        'b' : self.lastEntry['b']['attrs'].getValue('foo')
      })
      self.lastEntry = None
    elif name == 'root':
      raise StopIteration

  def characters(self, content):
    if self.lastEntry:
      self.lastEntry[self.lastName]['content'] += content


if __name__ == '__main__':
  # use default ``xml.sax.expatreader``
  parser = xml.sax.make_parser()
  parser.setContentHandler(StreamHandler())
  # feed the parser with small chunks to simulate
  with open('data.xml') as f:
    while True:
      buffer = f.read(16)
      if buffer:
        try:
          parser.feed(buffer)
        except StopIteration:
          break
  # if you can provide a file-like object it's as simple as
  with open('data.xml') as f:
    parser.parse(f)
klactose
  • 1,190
  • 2
  • 9
  • 26
saaj
  • 23,253
  • 3
  • 104
  • 105
  • Thank you Saaj. I have finally found an answer to my own question thanks to you answer. See my more elaborate answer: https://stackoverflow.com/a/44398623/938111 – oHo Jun 07 '17 at 13:36
  • Dumb question but what is the `time.sleep(2)` for? – gman Oct 05 '19 at 02:56
  • @gman It's a good question in fact. It's hard to remember what was the intention behind `else` branch. Probably related to experimenting with simulating slow input. But I copy-pasted the snippet and ran it with `raise RuntimeError` in place of `time.sleep` call. It ran successfully, so it's a dead branch. Removed it. – saaj Oct 07 '19 at 08:50
12

Are you looking for xml.sax? It's right in the standard library.

Petr Viktorin
  • 65,510
  • 9
  • 81
  • 81
1

Use xml.etree.cElementTree. It's much faster than xml.etree.ElementTree. Neither of them are broken. Your files are broken (see my answer to your other question).

John Machin
  • 81,303
  • 11
  • 141
  • 189
  • 5
    Indeed, it is much faster. And yes, my files were broken. – Aillyn Oct 08 '11 at 06:37
  • 1
    Guy was asking about streaming parser. – mcepl Feb 14 '12 at 14:46
  • @mcepl: Guy wanted to parse huge files; guy can do that with `iterparse()`. What/where is *your* answer? – John Machin Feb 14 '12 at 19:13
  • 6
    Isn't iterparse() building the tree as well (“Note that iterparse still builds a tree, just like parser.” http://effbot.org/zone/element-iterparse.htm). And my answer was bumping the one by Peter Viktorin. – mcepl Feb 14 '12 at 23:54
  • 2
    Just FYI: in 2019, `cElementTree` is simply an alias for `ElementTree`. – Marco Dec 11 '19 at 14:52
  • I second @Marco. [The xml.etree.cElementTree module is deprecated.](https://docs.python.org/3/library/xml.etree.elementtree.html#module-xml.etree.ElementTree) – mths Mar 06 '20 at 11:41