web-dev-qa-db-ja.com

Pythonで大規模なXMLドキュメントを解析する最速の方法は何ですか?

現在、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

私は約1 GBのサイズのXMLドキュメントで作業しています。誰もこれらを解析するより速い方法を知っていますか?

56
Jeroen Dirks

私は、プログラムからDOM機能を必要としないように見えます。次に、(c)ElementTreeライブラリーを使用します。 cElementTreeモジュールのiterparse関数を使用する場合、xmlを処理して、発生したイベントを処理できます。

ただし、cElementTree iterparse function の使用に関するFredriksのアドバイスに注意してください。

大きなファイルを解析するには、要素を処理したらすぐに要素を削除できます。

for event, elem in iterparse(source):
    if elem.tag == "record":
        ... process record elements ...
        elem.clear()

上記のパターンには1つの欠点があります。ルート要素はクリアされないため、多くの空の子要素を持つ単一の要素になります。ファイルが単に大きいというよりも巨大な場合、これが問題になる可能性があります。これを回避するには、ルート要素に手を加える必要があります。これを行う最も簡単な方法は、開始イベントを有効にし、変数の最初の要素への参照を保存することです。

# 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()
63
Steen

CElementTreeモジュールを試しましたか?

cElementTreeは、xml.etree.cElementTreeとしてPython 2.5以降に含まれています。 ベンチマーク を参照してください。

削除された死んだImageShackリンク

15
bhadra

lxml を使用することをお勧めします。これは、非常に高速なlibxml2ライブラリのpythonバインディングです。

私の経験では、libxml2とexpatのパフォーマンスは非常に似ています。しかし、私はlibxml2(およびpythonのlxml)の方がより積極的に開発およびテストされているように見えるため、好んでいます。また、libxml2にはより多くの機能があります。

lxmlは主に xml.etree.ElementTree と互換性のあるAPIです。また、Webサイトには優れたドキュメントがあります。

8
Manuel Ceron

コールバックを登録すると、解析の速度が大幅に低下します。 [編集]これは、(高速)Cコードがpythonインタープリターを呼び出す必要があるためです。これはCほど高速ではありません。基本的に、Cコードを使用してファイルを読み取ります(速い)そして、DOMをPython(遅い)。[/ EDIT]でビルドします

Cで100%実装され、pythonコードへのコールバックなしでXMLを解析できるxml.etree.ElementTreeを使用してください。

ドキュメントが解析された後、それをフィルタリングして必要なものを取得できます。

それでも遅すぎてDOMが不要な場合は、ファイルを文字列に読み込んで、単純な文字列操作を使用して処理することもできます。

5
Aaron Digulla

あなたのアプリケーションがパフォーマンスに敏感で、大きなファイル(あなたが言ったように、1GB以上)に出会う可能性が高いなら、私はstrongly単純な理由で質問であなたが示しているコードを使うことに対して助言するドキュメント全体をRAMにロードします。ドキュメントツリー全体を一度にRAMに保持することを避けるために。可能な限り)デザインを再考することをお勧めします。アプリケーションの要件がわからないため、適切に提案することはできません「イベントベース」の設計を使用しようとする一般的なアドバイス以外の特定のアプローチ。

4
Matt Campbell

expat ParseFileは、ツリー全体をメモリに保存する必要がない場合にうまく機能し、遅かれ早かれあなたのRAM大きなファイルの場合:

import xml.parsers.expat
parser = xml.parsers.expat.ParserCreate()
parser.ParseFile(open('path.xml', 'r'))

ファイルをチャンクに読み取り、RAMを爆発させることなくパーサーにフィードします。

Doc: https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

どうやら PyRXP は本当に速いです。

彼らは、それが最速のパーサーであると主張しています-しかし、cElementTreeは統計リストにありません。

0

私はこれを試すのにかなりの時間を費やしました。それは、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は十分ではありませんが、以前の要素へのリンクを削除することもできます。

0
Mitar