Каков самый быстрый способ анализа больших XML-документов в Python?
В настоящее время я использую следующий код на основе главы 12.5 Python Cookbook:
from xml.parsers import expat
class Element(object):
def __init__(self, name, attributes):
self.name = name
self.attributes = attributes
self.cdata = ''
self.children = []
def addChild(self, element):
self.children.append(element)
def getAttribute(self,key):
return self.attributes.get(key)
def getData(self):
return self.cdata
def getElements(self, name=''):
if name:
return [c for c in self.children if c.name == name]
else:
return list(self.children)
class Xml2Obj(object):
def __init__(self):
self.root = None
self.nodeStack = []
def StartElement(self, name, attributes):
element = Element(name.encode(), attributes)
if self.nodeStack:
parent = self.nodeStack[-1]
parent.addChild(element)
else:
self.root = element
self.nodeStack.append(element)
def EndElement(self, name):
self.nodeStack.pop()
def CharacterData(self,data):
if data.strip():
data = data.encode()
element = self.nodeStack[-1]
element.cdata += data
def Parse(self, filename):
Parser = expat.ParserCreate()
Parser.StartElementHandler = self.StartElement
Parser.EndElementHandler = self.EndElement
Parser.CharacterDataHandler = self.CharacterData
ParserStatus = Parser.Parse(open(filename).read(),1)
return self.root
Я работаю с XML-документами размером около 1 ГБ. Кто-нибудь знает более быстрый способ разобрать их?
Ответы
Ответ 1
Я смотрю на меня так, как будто вам не нужны какие-либо возможности DOM из вашей программы. Я бы поддержал использование библиотеки (c) ElementTree. Если вы используете функцию iterparse модуля cElementTree, вы можете пробираться через xml и обрабатывать события по мере их возникновения.
Обратите внимание, однако, совет Fredriks по использованию функции iterparse cElementTree:
для разбора больших файлов вы можете избавиться от элементов, как только вы их обработали:
for event, elem in iterparse(source):
if elem.tag == "record":
... process record elements ...
elem.clear()
Вышеуказанный шаблон имеет один недостаток; он не очищает корневой элемент, поэтому вы получите один элемент с множеством пустых дочерних элементов. Если ваши файлы огромные, а не просто большие, это может быть проблемой. Чтобы обойти это, вам нужно заполучить корневой элемент. Самый простой способ сделать это - включить стартовые события и сохранить ссылку на первый элемент в переменной:
# get an iterable
context = iterparse(source, events=("start", "end"))
# turn it into an iterator
context = iter(context)
# get the root element
event, root = context.next()
for event, elem in context:
if event == "end" and elem.tag == "record":
... process record elements ...
root.clear()
Lxml.iterparse() не позволяет этого.
Предыдущий не работает на Python 3.7, рассмотрите следующий способ получить первый элемент.
# get an iterable
context = iterparse(source, events=("start", "end"))
is_first = True
for event, elem in context:
# get the root element
if is_first:
root = elm
is_first = False
if event == "end" and elem.tag == "record":
... process record elements ...
root.clear()
Ответ 2
Вы пробовали модуль cElementTree?
cElementTree входит в состав Python 2.5 и более поздних версий, как xml.etree.cElementTree. См. тесты.
удалена мертвая ссылка ImageShack
Ответ 3
Я рекомендую вам использовать lxml, это связка python для библиотеки libxml2, которая очень быстрая.
По моему опыту, libxml2 и expat имеют очень схожую производительность. Но я предпочитаю libxml2 (и lxml для python), потому что он более активно развивается и тестируется. Также libxml2 имеет больше возможностей.
lxml в основном совместим с API xml.etree.ElementTree. На веб-сайте есть хорошая документация.
Ответ 4
Регистрация обратных вызовов сильно замедляет синтаксический анализ. [EDIT] Это потому, что (быстрый) код C должен вызывать интерпретатор python, который не так быстр, как C. В основном, вы используете код C для чтения файла (быстро), а затем создаете DOM в Python (медленно). [/EDIT]
Попробуйте использовать xml.etree.ElementTree, который реализован на 100% в C и который может анализировать XML без каких-либо обратных вызовов для кода python.
После анализа документа вы можете отфильтровать его, чтобы получить то, что вы хотите.
Если это все еще слишком медленно, и вам не нужен DOM, еще один вариант - прочитать файл в строке и использовать простые строковые операции для его обработки.
Ответ 5
Если ваше приложение чувствительно к производительности и может столкнуться с большими файлами (например, вы сказали, > 1 ГБ), я бы сильно советовал не использовать код, который вы показываете в своем вопросе, для просто потому, что он загружает весь документ в ОЗУ. Я бы посоветовал вам переосмыслить свой дизайн (если вообще возможно), чтобы избежать одновременного хранения всего дерева документов в ОЗУ. Не зная, каковы ваши требования к приложениям, я не могу правильно предложить какой-либо конкретный подход, кроме общих советов, чтобы попытаться использовать дизайн, основанный на событиях.
Ответ 6
expat ParseFile работает хорошо, если вам не нужно хранить все дерево в памяти, что рано или поздно приведет к удалению вашей RAM для больших файлов:
import xml.parsers.expat
parser = xml.parsers.expat.ParserCreate()
parser.ParseFile(open('path.xml', 'r'))
Он считывает файлы в куски и передает их в синтаксический анализатор без разрыва ОЗУ.
Doc: https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile
Ответ 7
По-видимому PyRXP действительно быстро.
Они утверждают, что это самый быстрый парсер, но cElementTree не входит в свой список статистики.
Ответ 8
Я потратил довольно много времени, чтобы попробовать это, и кажется, что самый быстрый и наименее ресурсоемкий подход - это использование lxml и iterparse, но при этом необходимо освободить ненужную память. В моем примере парсинг дампа arXiv:
from lxml import etree
context = etree.iterparse('path/to/file', events=('end',), tag='Record')
for event, element in context:
record_id = element.findtext('.//{http://arxiv.org/OAI/arXiv/}id')
created = element.findtext('.//{http://arxiv.org/OAI/arXiv/}created')
print(record_id, created)
# Free memory.
element.clear()
while element.getprevious() is not None:
del element.getparent()[0]
Так что element.clear
не достаточно, но также удаление любых ссылок на предыдущие элементы.