Читать XML-файл во время его записи (на Python)

Я должен следить за тем, чтобы файл XML записывался инструментом, работающим весь день. Но файл XML правильно заполнен и закрыт только в конце дня.

Те же ограничения, что и при обработке потока XML:

  1. Анализируйте неполный XML-файл на лету и запускайте действия
  2. Отслеживайте последнюю позицию в файле, чтобы избежать повторной обработки с самого начала

В ответ на вопрос о необходимости чтения XML-файлов в виде потока с использованием BeautifulSoup в Python, slezica предлагает xml.sax, xml.etree.ElementTree а также cElementTree, Но безуспешно с моими попытками использовать xml.etree.ElementTree а также cElementTree, Это также xml.dom, xml.parsers.expat а также lxml но я не вижу поддержки для анализа "на лету".

Мне нужны более очевидные примеры...

В настоящее время я использую Python 2.7 в Linux, но я перейду на Python 3.x =>, пожалуйста, также предоставьте советы по новым возможностям Python 3.x. Я также использую watchdog для обнаружения изменений в файле XML => При необходимости повторно watchdog механизм. Опционально поддерживается также Windows.

Пожалуйста, предоставьте простые для понимания / поддержки решения. Если это слишком сложно, я могу просто использовать tell() / seek() чтобы переместиться в файл, используйте глупый текстовый поиск в необработанном XML и, наконец, извлеките значения, используя основное регулярное выражение.


Пример XML:

<dfxml xmloutputversion='1.0'>
   <creator version='1.0'>
     <program>TCPFLOW</program>
     <version>1.4.6</version>
   </creator>
   <configuration>
     <fileobject>
       <filename>file1</filename>
       <filesize>288</filesize>
       <tcpflow packets='12' srcport='1111' dstport='2222' family='2' />
     </fileobject>
     <fileobject>
       <filename>file2</filename>
       <filesize>352</filesize>
       <tcpflow packets='12' srcport='3333' dstport='4444' family='2' />
     </fileobject>
     <fileobject>
       <filename>file3</filename>
       <filesize>456</filesize>
       ...
       ...

Первый тест с использованием SAX не удался:

import xml.sax

class StreamHandler(xml.sax.handler.ContentHandler):
    def startElement(self, name, attrs):
        print 'start: name=', name
    def endElement(self, name):
        print 'end:   name=', name
        if name == 'root':
            raise StopIteration

if __name__ == '__main__':
    parser = xml.sax.make_parser()
    parser.setContentHandler(StreamHandler())
    with open('f.xml') as f:
        parser.parse(f)

Ракушка:

$ while read line; do echo $line; sleep 1; done <i.xml >f.xml &
...
$ ./test-using-sax.py
start: name= dfxml
start: name= creator
start: name= program
end:   name= program
start: name= version
end:   name= version
Traceback (most recent call last):
  File "./test-using-sax.py", line 17, in <module>
    parser.parse(f)
  File "/usr/lib64/python2.7/xml/sax/expatreader.py", line 107, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib64/python2.7/xml/sax/xmlreader.py", line 125, in parse
    self.close()
  File "/usr/lib64/python2.7/xml/sax/expatreader.py", line 220, in close
    self.feed("", isFinal = 1)
  File "/usr/lib64/python2.7/xml/sax/expatreader.py", line 214, in feed
    self._err_handler.fatalError(exc)
  File "/usr/lib64/python2.7/xml/sax/handler.py", line 38, in fatalError
    raise exception
xml.sax._exceptions.SAXParseException: report.xml:15:0: no element found

2 ответа

Через три часа после публикации моего вопроса ответа не последовало. Но я наконец-то реализовал простой пример, который искал.

Мое вдохновение исходит из ответа Саадж и основывается на xml.sax а также watchdog,

from __future__ import print_function, division
import time
import watchdog.events
import watchdog.observers
import xml.sax

class XmlStreamHandler(xml.sax.handler.ContentHandler):
  def startElement(self, tag, attributes):
    print(tag, 'attributes=', attributes.items())
    self.tag = tag
  def characters(self, content):
    print(self.tag, 'content=', content)

class XmlFileEventHandler(watchdog.events.PatternMatchingEventHandler):
  def __init__(self):
    watchdog.events.PatternMatchingEventHandler.__init__(self, patterns=['*.xml'])
    self.file = None
    self.parser = xml.sax.make_parser()
    self.parser.setContentHandler(XmlStreamHandler())
  def on_modified(self, event):
    if not self.file:
      self.file = open(event.src_path)
    self.parser.feed(self.file.read())

if __name__ == '__main__':
  observer = watchdog.observers.Observer()
  event_handler = XmlFileEventHandler()
  observer.schedule(event_handler, path='.')
  try:
    observer.start()
    while True:
      time.sleep(10)
  finally:
    observer.stop()
    observer.join()

Пока работает скрипт, не забудьте touch один файл XML или смоделируйте запись на лету с помощью следующей команды:

while read line; do echo $line; sleep 1; done <in.xml >out.xml &

Со вчерашнего дня я нашел ответ Питера Гибсона о недокументированных xml.etree.ElementTree.XMLTreeBuilder._parser.EndElementHandler,

Этот пример похож на другой, но использует xml.etree.ElementTree (а также watchdog).

Не работает когда ElementTree заменяется cElementTree:-/

import time
import watchdog.events
import watchdog.observers
import xml.etree.ElementTree

class XmlFileEventHandler(watchdog.events.PatternMatchingEventHandler):
    def __init__(self):
        watchdog.events.PatternMatchingEventHandler.__init__(self, patterns=['*.xml'])
        self.xml_file = None
        self.parser = xml.etree.ElementTree.XMLTreeBuilder()
        def end_tag_event(tag):
            node = self.parser._end(tag)
            print 'tag=', tag, 'node=', node
        self.parser._parser.EndElementHandler = end_tag_event

    def on_modified(self, event):
        if not self.xml_file:
            self.xml_file = open(event.src_path)
        buffer = self.xml_file.read()
        if buffer:
            self.parser.feed(buffer)

if __name__ == '__main__':
    observer = watchdog.observers.Observer()
    event_handler = XmlFileEventHandler()
    observer.schedule(event_handler, path='.')
    try:
        observer.start()
        while True:
            time.sleep(10)
    finally:
        observer.stop()
        observer.join()

Пока работает скрипт, не забудьте touch один XML-файл или смоделируйте запись на лету с помощью этого однострочного сценария:

while read line; do echo $line; sleep 1; done <in.xml >out.xml &

Для информации, xml.etree.ElementTree.iterparse не поддерживает запись файла. Мой тестовый код:

from __future__ import print_function, division
import xml.etree.ElementTree

if __name__ == '__main__':
    context = xml.etree.ElementTree.iterparse('f.xml', events=('end',))
    for action, elem in context:
        print(action, elem.tag)

Мой вывод:

end program
end version
end creator
end filename
end filesize
end tcpflow
end fileobject
end filename
end filesize
end tcpflow
end fileobject
end filename
end filesize
Traceback (most recent call last):
  File "./iter.py", line 9, in <module>
    for action, elem in context:
  File "/usr/lib64/python2.7/xml/etree/ElementTree.py", line 1281, in next
    self._root = self._parser.close()
  File "/usr/lib64/python2.7/xml/etree/ElementTree.py", line 1654, in close
    self._raiseerror(v)
  File "/usr/lib64/python2.7/xml/etree/ElementTree.py", line 1506, in _raiseerror
    raise err
xml.etree.ElementTree.ParseError: no element found: line 20, column 0
Другие вопросы по тегам