Regex Anfängerproblem,Group gibt nur das erste element zurüc

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Sico
User
Beiträge: 6
Registriert: Samstag 4. Februar 2012, 08:59

Hallo,

ich benutze folgenden Code um per Regex <description> auszulesen:

Code: Alles auswählen

string = re.search("<description>(.*)</description>",html)
print string.groups(1)
Jetzt zeigt er mir natürlich nur das erste description an...wie kann ich mir auch noch alle anderen ausgeben lassen welche in html vorhanden sind.
deets

Gar nicht. Stattdessen solltest du einen HTML/XML-Parser verwenden, wie zB lxml oder zumindest ElementTree aus der stdlib. Dann kannst du so schoene Sachen wie "findall" machen.
problembär

Sico hat geschrieben:

Code: Alles auswählen

string = re.search("<description>(.*)</description>",html)
Nicht gut wegen Gier. Besser:

Code: Alles auswählen

string = re.search("<description>([^>]*)<\/description>", html)
Wenn das ganze html in einem String ist, mußt Du den natürlich splitten:

Code: Alles auswählen

html = html.split("\n")
for i in html:
    i = i.rstrip("\n")
    matchobj = re.search("<description>([^>]*)<\/description>", i)
    if matchobj:
        print matchobj.groups()
deets

@problembaer

Ein schoenes Beispiel, warum regulaere ausdruecke zur Verarbeitung von HTML ungeeignet sind, und dein Kreuzzug gegen richtige Ratschlaege bestenfalls Verwirrung, wenn nich gar Schaden anrichtet.

Code: Alles auswählen


html = """
<html>
  <body>
    <description>foo</description>
    <description>
    bar
    </description>
    <description><b>baz</b></description>
  </body>
</html>
"""
import re


for i in html.split("\n"):
    i = i.rstrip("\n")
    matchobj = re.search("<description>([^>]*)<\/description>", i)
    if matchobj:
        print matchobj.groups()

from xml.etree.ElementTree import fromstring

doc = fromstring(html)

for d in doc.findall(".//description"):
    print d.text
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

In specifications, Murphy's Law supersedes Ohm's.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Also man kann schon XML mit REs parsen.

Die Antwort auf das eigentlich Problem ist IMHO, "findall" bzw. "finditer" statt "search". Mit "search" findet man nur das erste Vorkommen eines regulären Ausdrucks in einem String, nicht alle. Außerdem sollte man bedenken, dass REs normalerweise greedy sind, d.h. den längsten möglichen String finden. Will man das nicht, sollte man hinter dem * (oder +) ein ? einfügen.

Dieser RE-basierte Parser kann übrigens XML korrekt zerlegen. Ich habe mir Attribute geschenkt, die könnte ich prinzipiell ebenfalls verarbeiten, nur dann hätte ich den Code nicht in 5min zusammenschreiben können:

Code: Alles auswählen

def sax_parse(s):
    def unquote(t): return t.replace("<", "<").replace("&", "&")
    for m in re.finditer(r"</\s*(.+?)\s*>|<\s*(.+?)\s*/>|<\s*(.+?)\s*>|([^<]+|<)", s.strip()):
        end, empty, start, text = m.groups()
        if start:
            yield sax_parse.START, start
        elif end:
            yield sax_parse.END, end
        elif empty:
            yield sax_parse.START, empty
            yield sax_parse.END, empty
        else:
            yield sax_parse.TEXT, unquote(text)
Damit könnte ich jetzt nach "description"-Tags suchen. Man könnte noch prüfen, ob das XML überhaupt wohlgeformt ist. Doch das mache ich gleich. Man müsste wohl auch unquote noch ausbauen. Das überlasse ich dem Leser genau wie das Behandeln von XML-Dokumenten, die nicht als UTF-8 vorliegen.

Dieses Beispiel kann ich jetzt korrekt verarbeiten:

Code: Alles auswählen

html = """
    < html >
      <body>
        <description>foo</description>
        <description>
        bar
        baz
        </description>
        <description><b>baz</b></description>
      </body>
    </html>"""
    
for e, t in sax_parse(html):
    print e, `t`
Es darf aber gerne noch etwas komfortabler sein. Ich baue mir schnell ein Objektmodell, damit ich XML-Elemente als Exemplare der Klasse `Elem` repräsentieren kann, die jeweils einen Namen und Kinder haben und zwei Attribute, in denen ich die Textknoten sammle.

Code: Alles auswählen

class Elem:
    def __init__(self, name):
        self.name, self.children, self.text, self.tail = name, [], "", ""
    
    def __str__(self):
        return "<%s>%s%s</%s>%s" % (self.name, self.text, "".join(str(c) for c in self.children), self.name, self.tail)
Und habe dafür einen Parser, der mir mit Hilfe meines ersten Parsers nun eine Hierarchie von Elem-Objekten erzeugt:

Code: Alles auswählen

def elem_parse(s):
    stack = []
    for t, e in sax_parse(s):
        if t == sax_parse.START:
            stack.append(Elem(e))
        if t == sax_parse.END:
            if stack and stack[-1].name == e:
                elem = stack.pop()
                if stack: stack[-1].children.append(elem)
                else: return elem
            else: raise ValueError("wrong </%s>" % e)
        if t == sax_parse.TEXT:
            if stack:
                if stack[-1].children: stack[-1].children[-1].tail += e
                else: stack[-1].text += e
            else: raise ValueError("no root")
    raise ValueError("stuff after end")
Dieser Parser prüft dann nun auch, ob das XML-Dokument wohlgeformt ist.

Fügen wir noch folgende Methode zu `Elem` hinzu:

Code: Alles auswählen

    def find_all(self, name):
        if name == self.name:
            yield self
        else:
            for c in self.children:
                for e in c.find_all(name):
                    yield e
Nun kann ich bequem nach allen description-`Elem`s suchen, nur mit regulären Ausdrücken:

Code: Alles auswählen

for e in elem_parse(html).find_all("description"):
    print e
Stefan
deets

@sma

Schön zu sehen, dass reguläre Ausdrücke einen kleinen Anteil in der korrekten Verarbeitung von *ML spielen können.

Geht aber ein bisschen am Punkt hier vorbei, oder nicht? Natürlich ist es jedem freigestellt, seine Standardimplementation eines Parsers selbst zu bauen. Oder seine eigene Sprache. Aber ich für meinen Teil nehme vorlieb mit Python & seiner standart Bibliothek ;)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

deets hat geschrieben:Schön zu sehen, dass reguläre Ausdrücke einen kleinen Anteil in der korrekten Verarbeitung von *ML spielen können.
Hätte ich die Ironie durch einen :) verdeutlichen müssen?

Stefan
deets

Ja :) zumindest wenn ich sowas auf meinem iPhone lese - da ist die Schrift sonst zu
Klein ;) und ich behaupte, unser problembär nimmt das als beispiel , wie man ohne "modulgefrickel" weiter kommt...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

deets hat geschrieben:Ja :) zumindest wenn ich sowas auf meinem iPhone lese - da ist die Schrift sonst zu
Klein ;) und ich behaupte, unser problembär nimmt das als beispiel , wie man ohne "modulgefrickel" weiter kommt...
Also ersteres ist ja wohl ganz klar ein Problem des Forums und nicht meines Beitrags :) Ich würde mir auch eine mobile Form wünschen, wo ich - sei es per App oder angepasster Website - bequem vom Handy aus lesen und antworten kann. Dann würden meine Postings auch nicht so lang werden. Außer, ich könnte sie Siri diktieren.

Das den anderen Punkt angeht: Ich sehe einen großen Wert darin, nicht nur Module anwenden zu können, sondern auch zu wissen, wie man den Code - wenigstens prinzipiell - selbst schreiben würde. Das ist nicht nur eine gute Übung, sondern man lernt den Aufwand der Erstellung eines guten Moduls erst schätzen - oder erkennt schlechte Module. Außerdem hilft es, die Algorithmen zu kennen, wenn man mehr als nur Python anwenden können will. Weil mir z.B. Element-Tree als API gut gefiel, habe ich mir das unter iOS basierend auf dem mitgelieferten NSXMLParser selbst gebaut. Da da wollte ich nicht mit Modulen (bzw. fremden statischen Bibliotheken) frickeln. Und nein, bevor jemand fragt, libxml2 einbinden ist auch nicht wirklich einfacher, weil das ein C-API und umständlich aus Objective-C heraus zu benutzen ist.

Stefan
problembär

deets hat geschrieben:@problembaer

Ein schoenes Beispiel, warum regulaere ausdruecke zur Verarbeitung von HTML ungeeignet sind, und dein Kreuzzug gegen richtige Ratschlaege bestenfalls Verwirrung, wenn nich gar Schaden anrichtet.
Kreuzzug ist, wenn man eine bestimmte Auffassung verlangt und alle anderen bekämpft, weil man glaubt, schon im Besitz der letzten Wahrheit zu sein.
Ich dagegen trete für Code-Toleranz ein und für die Freiheit, das zu schreiben, was man möchte, egal ob es der Obrigkeit genehm ist oder nicht. Das ist das Gegenteil von Kreuzzug. Du verwechselst die Seiten, auf denen wir stehen.

Wenn also jemand HTML mit RegEx parsen möchte, sag' ich, "laß' ihn doch" und helfe ihm so gut ich kann dabei, daß es technisch auch klappt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

problembär hat geschrieben: Ich dagegen trete für Code-Toleranz ein und für die Freiheit, das zu schreiben, was man möchte, egal ob es der Obrigkeit genehm ist oder nicht.
Eigentlich schön formuliert - nur dass es hier keine "Obrigkeit" gibt; ich bin ihr zumindest noch nicht begegnet ;-) Ansonsten stimmen Dir hier sicherlich die meisten zu, dass man coden darf, was man will und wie man will, aber...
problembär hat geschrieben: Wenn also jemand HTML mit RegEx parsen möchte, sag' ich, "laß' ihn doch" und helfe ihm so gut ich kann dabei, daß es technisch auch klappt.
... es gibt eben gute und schlechte Hilfe. Wenn jemand über eine x spurige Straße gehen will und das Konzept des Druckknopfs für die Fußgängerampel nicht kennt, dann erkläre ich ihm dieses und sage nicht "Ich sag Bescheid, wenn links frei ist!" ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
deets

problembär hat geschrieben: Kreuzzug ist, wenn man eine bestimmte Auffassung verlangt und alle anderen bekämpft, weil man glaubt, schon im Besitz der letzten Wahrheit zu sein.
Ich dagegen trete für Code-Toleranz ein und für die Freiheit, das zu schreiben, was man möchte, egal ob es der Obrigkeit genehm ist oder nicht. Das ist das Gegenteil von Kreuzzug. Du verwechselst die Seiten, auf denen wir stehen.

Wenn also jemand HTML mit RegEx parsen möchte, sag' ich, "laß' ihn doch" und helfe ihm so gut ich kann dabei, daß es technisch auch klappt.
Wie ich ja schnell gezeigt habe, ist es mit dem "technischen klappen" deiner Hilfe nicht so weit her. Du hast also dem OP eine Baerendienst erwiesen (wie nicht anders zu erwarten...).

Und wenn jemand dabei ist, sich in den Fuss zu schiessen, aber der Abzug klemmt - dann bist du offensichtlich derjenige, der im die Waffe klariert, damit er das Blutbad anrichten kann. Und das mit dem religioesen Eifer der Kreuzfahrer....
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich denke, beide Seiten haben recht. Aus eigenen Fehlern zu lernen ist häufig der Weg, der besser funktioniert. Ein (gut gemeintes) "das macht man so nicht" ist dann weniger konstruktiv als der direkte Weg zur eigenen Erkenntnis. Oder um Bild zu bleiben, führe den Fragesteller ruhig auf den Mittelstreifen, warte bis er dort nicht mehr weiterkommt, und dann hilf ihm zurück und über die Brücke anstatt sofort zu sagen, "man geht da nicht rüber, aber in 3km Entfernung ist eine Brücke".

Ich bin da eher bei Problembär als das ich denke, dass man jeden Fragesteller zwangsbeglücken muss. Ich würde schon erwähnen, dass ich (bei meinem aktuellen Kenntnisstand des Problems und meinem Vorwissen) meine, dass es einen besseren Weg gibt, aber auch die möglichst direkte Antwort geben (findall statt search).

Und wäre es z.B. notwendig, aus großen Mengen XML, wo ich das Format sehr genau kenne, sehr schnell etwas zu extrahieren, dann würde ich schon zu REs greifen und nicht das mitgelieferte XML-API von Python benutzen. Das ist einfach pragmatisch. Will ich's bequem haben und sichergehen, dass alle Randfälle von XML abgedeckt sind, nehme ich den XML-Parser. Die Entscheidung ist aber in beiden Fällen nicht absolut.

Stefan
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

sma hat geschrieben:Oder um Bild zu bleiben, führe den Fragesteller ruhig auf den Mittelstreifen, warte bis er dort nicht mehr weiterkommt, und dann hilf ihm zurück und über die Brücke anstatt sofort zu sagen, "man geht da nicht rüber, aber in 3km Entfernung ist eine Brücke".
Hm... Du setzt dabei voraus, dass der OP aus Fehlern lernt; meine Erfahrungen als Nachhilfelehrer haben mir immer gezeigt, dass das bei den meisten dort nicht der Fall war, sondern man diese Leute direkt auf den "richtigen" Weg leiten musste. Sobald sie diesen einmal verstanden hatten, war das Problem damit gegessen. Ok, man kann natürlich einwerfen, dass Schüler / Studenten, die Nachhilfe benötigen, ggf. nicht repräsentativ sind ;-)
Ich denke vielleicht ist der Mittelweg der richtige: Man sagt dem Fragesteller, dass es eine Brücke gibt, das der sicherere Weg ist und ggf. wieso sein Weg nicht so gut ist. Sobald er das explizit erfahren hat, kann ihm ja jeder noch auf seinem eingeschlagegen Weg weiterhelfen.
Dabei sollte man auch nie google und die stillen Mitleser vergessen - die wollen ja auch sicher ans Ziel kommen ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man kann auch einfach antworten: "Die Lösung deines speziellen Problems geht so-und-so. Wenn du das intensiver betreibst, würde ich jedoch zu Modul xyz raten - das ist auf Dauer viel einfacher." Oder, wenn schon einiges an Code da ist: "Guck mal, wie komplex das ist. Da blickt ja kaum einer durch. Schau mal, mit Modul xyz bekomme ich das in 2 Zeilen hin..."

Ich glaube einfach, es ist didaktisch sinnvoller, jemandem erst seine Frage möglichst zu beantworten und ihm dann einen alternativen Weg anzubieten, anstatt direkt mit dem erhobenen Zeigefinger zu kommen, unter der strengen Annahme, dass ein Anfänger niemals-nie überhaupt falschen Code sehen darf und man ihn bloß vor diesem Unheil bewahren muss. problembär ist natürlich der krasse Gegenpol von dieser Ansicht. Dies halte ich ebenfalls für wenig zielführend. Und sma hat ja zum Glück bereits gesagt, dass man seine Antwort ruhig mit einem Augenzwinkern betrachten sollte - puh...! ^^

//edit: Und natürlich hat deets anschaulich dargelegt, warum es in "ernsthaften" Anwendungsfällen besser ist, etwas robustes fertiges zu verwenden. Prinzipiell könnte man aber trotzdem zunächst den regulären Ausdruck für einen simplen Fall zeigen, danach auf mögliche Fallstricke hinweisen und sozusagen als Fazit dann die Nutzung eines speziellen Moduls empfehlen. Das soll jetzt natürlich keine direkte Handlungsanweisung sein. Ich kann sma's Einwände aber halt schon irgendwo verstehen...
Zuletzt geändert von snafu am Sonntag 5. Februar 2012, 20:51, insgesamt 1-mal geändert.
BlackJack

@snafu: Wenn Du den Leuten die Frage beantwortest, wie sie dynamische Namen mit angehängten Nummern erzeugt oder wie man etwas mit ``global`` oder `eval()` „löst”, dann muss man aber auch damit leben, dass die das dann tatsächlich *machen* und sich nicht um die saubere Alternative scheren.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@BlackJack: Nun ja. Wenn man zusätzlich argumentiert, *warum* Listen besser als das Durchnummerieren von Variablen sind, kann man vielleicht auch einfach mit seiner Überzeugungskraft wirken. Wenn ein Anfänger unbedingt bei seinem Codestil bleiben will, dann kann man eh nicht viel machen. Wieso sollte man auch? Er würde sich jedenfalls die von ihm gewünschte Antwort schon von irgendwo her holen.

Es gibt ja IMHO 2 Sorten bzw Reaktionsarten von Anfängern: Die einen nehmen das dankbar an und ermuntern indirekt zu weiteren Ratschlägen und die anderen haben so oder so keinen Bock drauf - sie wollen einfach nur, dass es irgendwie compil...äh...funktioniert. ;)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Finden wir den einen Konsens, wenn man sagt, dass ein Anfänger vor allem aus Unkenntnis der sinnvollen Möglichkeiten einen eher ungünstigen Weg einschlägt, bei dem es dann hakt?

Wenn ja, ist es doch eher hilfreich ihm diesen Weg aufzuzeigen; ich nehme mich da nicht aus und habe durch solche Hinweise hier viel gelernt.

Ob man jemanden davon abhalten sollte, einen ungünstigen Weg zu beschreiten... da bin ich ein wenig unschlüssig; wer sich ins Knie schießen will, soll das ruhig tun. Aber ich denke man darf ihn dann ruhig drauf hinweisen und im Idealfall begründen, wieso das ein schlechter Weg ist. Zumal eben auch anderen Leute einen solchen Thread ggf. finden und die sollten dann zumindest die Chance haben, den richtigen Weg einzuschlagen oder, sofern dieser nicht eindeutig bestimmbar ist, zumindest einen Überblick über die Alternativen und deren Vor- bzw. Nachteile zu erhalten.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich spreche natürlich davon, mögliche Gefahren von "anfängerfreundlichen" Code (im Sinne von: mit den Mitteln einer begrenzten Kenntnis des Sprachumfangs) auch direkt im selben Post zu erwähnen, sodass jemand, der 1 Jahr später den Thread findet, dies nicht erst auf Seite 3 erfährt. Das sollte aber eigentlich auch klar sein, denke ich.
Antworten