Die ElementTree XML-API von Python bietet verschiedene Analysemethoden. Konzentrieren Sie sich auf Geschwindigkeit und vergleichen Sie.
Die für die Messung verwendete Umgebung ist wie folgt.
Python verfügt über eine ElementTree XML-API für die Arbeit mit XML.
Es werden vier Hauptmethoden angeboten.
fromstring
XMLParser
XMLPullParser
iterparse
Diese werden gemäß Bedingungen wie Speichereffizienz und Blockiervermeidung ordnungsgemäß verwendet. Dieses Mal interessieren mich solche Bedingungen nicht, ich konzentriere mich nur auf die Verarbeitungszeit von riesigem XML.
Wiktionary Targets Dump-Dateien der englischen Version. Die Dump-Daten werden komprimiert mit bzip2 bereitgestellt. Es wird verwendet, wenn es komprimiert wird, ohne es zu dekomprimieren. (Es wird ungefähr 6 GB sein, wenn es erweitert wird)
Die Dump-Datei hat die folgende Tag-Struktur.
<mediawiki>
<siteinfo> ⋯ </siteinfo>
<page> ⋯ </page>
<page> ⋯ </page>
⋮
<page> ⋯ </page>
</mediawiki>
Das Ganze ist so groß, dass es durch eine Methode namens Multi-Stream komprimiert wird, sodass nur ein Teil herausgenommen und verarbeitet werden kann.
siteinfo | page × 100 | page × 100 | ⋯ |
Dieses Mal werden wir das Ganze verarbeiten, während wir die Streams einzeln abrufen.
Der zweite und nachfolgende Stream enthält 100
<page> ⋯ </page>
<page> ⋯ </page>
⋮
<page> ⋯ </page>
Jedes <page>
-Tag hat die folgende Struktur.
<page>
<title>Titel</title>
<ns>Namensraum</ns>
<id>ID</id>
<revision>
<id>Revision ID</id>
<parentid>Revisions-ID vor dem Update</parentid>
<timestamp>Datum und Uhrzeit aktualisieren</timestamp>
<contributor>
<username>Editor</username>
<id>Editor-ID</id>
</contributor>
<comment>Kommentare zum Zeitpunkt der Bearbeitung</comment>
<model>wikitext</model>
<format>text/x-wiki</format>
<text bytes="Länge" xml:space="preserve">Artikelinhalt</text>
<sha1>Hashwert</sha1>
</revision>
</page>
Von diesen wird der Inhalt des Tags
Die in diesem Artikel verwendeten Skripte sind in den folgenden Repositorys enthalten:
Implementieren Sie "getpage", um "
Der Teil, der die Anzahl der Zeilen mit getpage zählt, wird gemeinsam genutzt.
allgemeiner Teil
lines = 0
with open(target, "rb") as f:
f.seek(slen[0])
for length in slen[1:-1]:
for id, text in getpages(f.read(length)):
for line in text():
lines += 1
print(f"lines: {lines:,}")
Slen
ist die Länge jedes im Voraus untersuchten Streams.fromstring
Erstellen Sie einen Baum, indem Sie XML als Zeichenfolge übergeben. Führen Sie Operationen am Baum aus (suchen Sie nach Tags, rufen Sie sie ab usw.).
Code | Verarbeitungszeit |
---|---|
python/research/countlines-text-xml.py | 5m50.826s |
def getpages(bz2data):
xml = bz2.decompress(bz2data).decode("utf-8")
pages = ET.fromstring(f"<pages>{xml}</pages>")
for page in pages:
if int(page.find("ns").text) == 0:
id = int(page.find("id").text)
with io.StringIO(page.find("revision/text").text) as t:
def text():
while (line := t.readline()):
yield line
yield id, text
Da XML ein Root-Tag erfordert, wird es vorübergehend in "
<ns>
repräsentiert den Seitentyp. Da 0
eine normale Seite ist, haben wir sie darauf eingegrenzt.
Es gibt verschiedene Arten von "page.find (" id ")
gilt nur für <id>
direkt unter <page>
. Da sich "
XMLParser
fromstring
ist der Parser, der intern zum Erstellen des Baums verwendet wird. Die direkte Verwendung ist schnell, da Sie das Erstellen des Baums überspringen und ihn einfach analysieren können.
Es ist ein Parser vom Typ ** Push-Typ **, der fast der Methode SAX entspricht. Bereiten Sie eine Klasse vor, die Methoden zur Behandlung von Ereignissen wie Tag-Start und Tag-Ende enthält.
Code | Verarbeitungszeit |
---|---|
python/research/countlines-text-xmlparser.py | 6m46.163s |
class XMLTarget:
def __init__(self):
self._ns = 0
self._id = 0
self._data = []
self.pages = []
def start(self, tag, attrib):
self._data = []
def data(self, data):
self._data.append(data)
def end(self, tag):
if tag == "ns":
self._ns = int(self._data[0])
self._id = 0
elif self._id == 0 and tag == "id":
self._id = int(self._data[0])
elif self._ns == 0 and tag == "text":
text = []
cur = ""
for d in self._data:
if d == "\n":
text.append(cur)
cur = ""
else:
cur += d
if cur: text.append(cur)
self.pages.append((self._id, text))
self._data = []
def getpages(bz2data):
target = XMLTarget()
parser = ET.XMLParser(target=target)
parser.feed("<pages>")
parser.feed(bz2.decompress(bz2data).decode("utf-8"))
return target.pages
data
ist der Inhalt des Tags. Da es getrennt durch lange Zeilen oder Zeilenumbrüche gesendet wird, wird es nach Zeilen gruppiert.
Die Dokumentation zeigt Beispielcode ähnlich dem folgenden.
>>> class MaxDepth: # The target object of the parser
... maxDepth = 0
... depth = 0
... def start(self, tag, attrib): # Called for each opening tag.
... self.depth += 1
... if self.depth > self.maxDepth:
... self.maxDepth = self.depth
... def end(self, tag): # Called for each closing tag.
... self.depth -= 1
... def data(self, data):
... pass # We do not need to do anything with data.
... def close(self): # Called when all data has been parsed.
... return self.maxDepth
Die direkt unter der Klasse definierten Variablen "maxDepth" und "depth" sind Klassenvariablen und allen Instanzen gemeinsam. In diesem Code werden Instanzvariablen erstellt und Klassenvariablen während Aktualisierungen wie "+ =" und "=" ausgeblendet. Daher wird der Wert der Klassenvariablen niemals mit "0" aktualisiert.
Die Geschichte ist jedoch anders, wenn Klassenvariablen Methoden wie "Anhängen" in Listen verwenden. Ich war süchtig danach und konnte den Code nicht schreiben, der wie erwartet funktionierte.
XMLPullParser
Es ist eine Art Parser namens ** Pull-Typ **. In Java heißt es StAX für SAX.
Im Gegensatz zum Push-Typ muss keine Klasse vorbereitet werden, und sie kann durch Schleifen und bedingte Verzweigungen verarbeitet werden, was den Code vereinfacht.
[Referenz] Vergleich zwischen XmlReader und SAX Reader | Microsoft Docs
Code | Verarbeitungszeit |
---|---|
python/research/countlines-text-xmlpull.py | 6m4.553s |
def getpages(bz2data):
xml = bz2.decompress(bz2data).decode("utf-8")
parser = ET.XMLPullParser()
parser.feed("<pages>")
parser.feed(xml)
ns, id = 0, 0
for ev, el in parser.read_events():
if el.tag == "ns":
ns = int(el.text)
id = 0
elif id == 0 and el.tag == "id":
id = int(el.text)
elif ns == 0 and el.tag == "text":
with io.StringIO(el.text) as t:
def text():
while (line := t.readline()):
yield line
yield id, text
Da es mehrere Arten von "
iterparse
Es ist ein Pull-Parser. Der Unterschied zu "XMLPullParser" wird wie folgt erklärt.
XMLPullParser
ist so flexibel, dass es für Benutzer, die es einfach verwenden möchten, unpraktisch sein kann. Wenn Ihre Anwendung beim Lesen von XML-Daten nicht blockieren muss, sondern die Möglichkeit zum schrittweisen Analysieren haben soll, lesen Sieiterparse ()
. Dies ist nützlich, wenn Sie ein großes XML-Dokument lesen und nicht möchten, dass sich alles im Speicher befindet.
iterparse ()
gibt einen Iterator zurück und erweitert die Analyse jedes Mal, wenn Sie mit next ()
fortfahren. Die Struktur des verwendeten Codes entspricht fast der von "XMLPullParser".
Code | Verarbeitungszeit |
---|---|
python/research/countlines-text-xmliter.py | 6m29.298s |
def getpages(bz2data):
xml = bz2.decompress(bz2data).decode("utf-8")
ns, id = 0, 0
for ev, el in ET.iterparse(io.StringIO(f"<pages>{xml}</pages>")):
if el.tag == "ns":
ns = int(el.text)
id = 0
elif id == 0 and el.tag == "id":
id = int(el.text)
elif ns == 0 and el.tag == "text":
with io.StringIO(el.text) as t:
def text():
while (line := t.readline()):
yield line
yield id, text
Von allen Versuchen, die ich versucht habe, war der Baumbau "fromstring" der schnellste. Der Unterschied ist jedoch nicht groß. Wenn Sie also nicht mit Daten dieser Größenordnung (6 GB) arbeiten, ist dies kein großes Problem.
Methode | Code | Verarbeitungszeit |
---|---|---|
fromstring | python/research/countlines-text-xml.py | 5m50.826s |
XMLParser | python/research/countlines-text-xmlparser.py | 6m46.163s |
XMLPullParser | python/research/countlines-text-xmlpull.py | 6m04.553s |
iterparse | python/research/countlines-text-xmliter.py | 6m29.298s |
Als Referenz verwendet .NET Framework auch den Pull-Typ "XmlReader", um den Baum zu erstellen. Es ist schneller, "XmlReader" direkt zu verwenden, ohne einen Baum zu erstellen.
Methode | Code | Verarbeitungszeit | Bemerkungen |
---|---|---|---|
XmlDocument | fsharp/research/countlines-text-split-doc.fsx | 6m21.588s | Da ist ein Baum |
XmlReader | fsharp/research/countlines-text-split-reader.fsx | 3m17.916s | Kein Baum |