Wie kann ich mit ElementTree in Python den gesamten Text aus einem Knoten extrahieren, alle Tags in diesem Element entfernen und nur den Text behalten?

Angenommen, ich habe Folgendes:

<tag>
  Some <a>example</a> text
</tag>

Ich möchte Some example text zurückgeben. Wie mache ich das? Bisher hatten meine Ansätze ziemlich katastrophale Folgen.

9
Trent Bing 15 Okt. 2013 im 01:53

3 Antworten

Beste Antwort

Wenn Sie unter Python 3.2+ arbeiten, können Sie itertext verwenden.

itertext erstellt einen Textiterator, der dieses Element und alle Unterelemente in Dokumentreihenfolge durchläuft und den gesamten inneren Text zurückgibt:

import xml.etree.ElementTree as ET
xml = '<tag>Some <a>example</a> text</tag>'
tree = ET.fromstring(xml)
print(''.join(tree.itertext()))

# -> 'Some example text'

Wenn Sie in einer niedrigeren Version von Python ausgeführt werden, können Sie die Implementierung von itertext() durch Anhängen an die Klasse Element. Danach können Sie sie genau wie oben aufrufen:

# original implementation of .itertext() for Python 2.7
def itertext(self):
    tag = self.tag
    if not isinstance(tag, basestring) and tag is not None:
        return
    if self.text:
        yield self.text
    for e in self:
        for s in e.itertext():
            yield s
        if e.tail:
            yield e.tail

# if necessary, monkey-patch the Element class
if 'itertext' not in ET.Element.__dict__:
    ET.Element.itertext = itertext

xml = '<tag>Some <a>example</a> text</tag>'
tree = ET.fromstring(xml)
print(''.join(tree.itertext()))

# -> 'Some example text'
20
Tomalak 13 Nov. 2018 im 17:33

Es gibt auch eine sehr einfache Lösung für den Fall, dass XPath verwendet werden kann. Es heißt XPath Axes: mehr dazu finden Sie hier.

Wenn Sie einen Knoten (wie ein Tag div) haben, der selbst Text und andere Knoten enthält (wie Tags a oder center oder einen anderen div), in denen sich Text befindet enthält nur Text und wir möchten den gesamten Text in diesem div Knoten auswählen. Dies ist mit folgendem XPath möglich: current_element.xpath("descendant-or-self::*/text()").extract(). Was wir bekommen, ist eine Liste aller Texte innerhalb eines aktuellen Elements, wobei Tags entfernt werden, falls vorhanden.

Das Schöne daran ist, dass keine rekursive Funktion benötigt wird. XPath kümmert sich um all dies (mit Recusion selbst, aber für uns ist es so sauber, wie es nur sein kann).

Hier ist die StackOverflow-Frage zu dieser vorgeschlagenen Lösung.

0
Michal 22 Sept. 2018 im 11:49

Wie in der Dokumentation angegeben, müssen Sie alle Attribute text und tail in der richtigen Reihenfolge rekursiv verketten, wenn Sie nur den Text ohne Zwischen-Tags lesen möchten.

Neuere Versionen (einschließlich der in der stdlib in 2.7 und 3.2, jedoch nicht in 2.6 oder 3.1 und den aktuell veröffentlichten Versionen von ElementTree und lxml auf PyPI) können dies jedoch für Sie tun automatisch in der tostring Methode:

>>> s = '''<tag>
...   Some <a>example</a> text
... </tag>'''
>>> t = ElementTree.fromstring(s)
>>> ElementTree.tostring(s, method='text')
'\n  Some example text\n'

Wenn Sie auch Leerzeichen aus dem Text entfernen möchten, müssen Sie dies manuell tun. In Ihrem einfachen Fall ist das einfach:

>>> ElementTree.tostring(s, method='text').strip()
'Some example text'

In komplizierteren Fällen, in denen Sie Leerzeichen innerhalb von Zwischen-Tags entfernen möchten, müssen Sie wahrscheinlich auf die rekursive Verarbeitung der text und tail zurückgreifen. Das ist nicht zu schwer; Sie müssen nur daran denken, sich mit der Möglichkeit zu befassen, dass die Attribute None sein können. Hier ist zum Beispiel ein Skelett, an das Sie Ihren eigenen Code anschließen können:

def textify(t):
    s = []
    if t.text:
        s.append(t.text)
    for child in t.getchildren():
        s.extend(textify(child))
    if t.tail:
        s.append(t.tail)
    return ''.join(s)

Diese Version funktioniert nur, wenn text und tail garantiert str oder None sind. Bei Bäumen, die Sie manuell aufbauen, ist dies nicht garantiert.

5
abarnert 14 Okt. 2013 im 22:19