Mechanize und BeautifulSoup

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.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: vielleicht lernst Du dann auch, wann man Listen benutzt, warum man hier Listen benutzt, und warum hier die Liste leer ist.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3:Listen benutzt man um Daten zu strukturieren. Ich erspare mir jetzt eine weite Ausführung. In meinem Beispiel sehe ich allerdings nur eine Liste, und keine Mehrzahl (Listen). Und warum hier die Liste leer ist?

Code: Alles auswählen

response = br.submit()
   
soup = BeautifulSoup(response.read())
print soup.select('html body table tbody tr td div table tbody tr td table tbody tr td div p').text
Nun, ich sehe hier die Variable response, in der der ganze HTML-Code nach dem Absenden des Formulars gespeichert ist - darunter auch die Textausgaben. Durch BeautfifulSoup wird der Inhalt von response gelesen, und hier in die Variable soup gespeichert. Anschließend folgt eine Print-Anweisung. Hier wird die select()-Methode verwendet, damit Elemente aus der BeautifulSoup-Instanz ausgewählt werden können.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: die Frage ist generisch gemeint: Warum liefert select eine Liste als Ergebnis zurück? Und warum postest Du schon wieder diesen fehlerhaften Code?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Damit ich mich an den Quelltext entlang angeln kann, wenn ich etwas schreibe, damit man mich nachvollziehen kann, wo ich einen gedanklichen Knoten habe? Warum select die Liste als Ergebnis zurückliefert? Weil bein der text()-Methode nicht mit angegeben wurde, welcher Index zurück gegeben werden soll. In der fremden Version steht ja der Index 0. Python beginnt mit der Zählung auch mit 0.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: sowohl der Index als auch die text-Methode arbeiten auf dem Ergebnis, bzw. einem Element des Ergebnisses der select-Methode. Also hat das mit meiner Frage gar nichts zu tun, weil es im Programmablauf erst später kommt. Daher nochmal: Warum liefert select eine Liste und warum ist die Liste bei Dir leer?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Die Liste könnte möglicherweise leer sein, weil auf dem HTML-Baum nichts gefunden wird?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: sehr gut :D

Es gibt verschiedene Arten, wie man den HTML-Baum aufbaut. Das wurde erst mit HTML5 in feste Regeln gegossen, die BS wahrscheinlich noch nicht so umsetzt. Daher ist Dein 17 Elemente langer Pfad aus FireFox nicht das, was BS sieht.
Zuletzt geändert von Sirius3 am Samstag 2. Mai 2015, 23:02, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich habe jedoch diesen Abschnitt, also diesen HTML-Baum von Firebug entnommen. Gehe ich beim Firebug über diese Stelle wird auf die Text-Ausgab mit markiert. Es heißt also, es muss da irgendwas sein? Oder lügt Firebug?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3:

Ich habe das Ganze etwas anders gelöst. Womöglich alles falsch, jedoch hat es den Anschein dass es klappt.

Code: Alles auswählen

soup = BeautifulSoup(response)

for item in soup.find_all(text=lambda x: 'Ihr Benutzername muss mindestens 3 Zeichen enthalten!' in x):
    print "Lambda ", str(item)
In Zeile 1 habe ich bei der Variable response die read()-Methode entfernt. In Zeile 3 habe ich mit zunächst eine einfache For-Schleife gebastelt, mit der find_all()-Methode. Und innerhalb dieser Methode habe ich eine anonyme Funktion lambda gepackt, die sozusagen eine implizite Schleife ausführt. Hier wird also gesagt, wenn das Argument Ihr Benutzername muss mindestens 3 Zeichen enthalten! in der find_all()-Methode vorhanden ist, dann soll die Print-Anweisung angesprochen werden.

Der Nachteil wäre: Es gibt ja mehrere Meldungen, wie "E-Mail-Adresse ungültig", "Passwort zu kurz", "Benutzername existiert bereits" und eine ganze Menge mehr. Das heißt dann, ich müsste dann mehrere solcher lambdas innerhalb dieser find_all()-Methode schreiben? Würde sich das nicht aufblähen?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: jetzt hast Du Dir selbst bewiesen, dass es nicht sinnvoll sein kann, so einen komplizierten Selector zu benutzen und verwendest statt dessen ein noch viel spezielleres, das exakt den Text der Fehlermeldung kennen muß? Wo da eine implizite Schleife im lambda ist, wirst auch nur Du sehen können.

Weil Dein HTML-Salat absolute keine Struktur hat, ist es auch nicht einfach möglich, irgendetwas daraus zu lesen. Wenn Du also nach einem Alleinstellungsmerkmal suchst, dann merkst Du, dass die Fehlermeldungen vor dem <form>-Tag mit Namen "register" stehen.
Damit kannst Du sicher die Fehlermeldungen finden:

Code: Alles auswählen

print soup.find("form", {"name":"register"}).fetchParents('p')[0].text
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich habe eben deine Variante Probiert:
Traceback (most recent call last):
File "D:\Dan\Python\Übung\login_mechanize.py", line 31, in <module>
print soup.find("form", {"name":"register"}).fetchParents('p')[0].text
IndexError: list index out of range
Du hast Recht, meine Variante kennt den ganzen Text der Fehlermeldung. Wenn man erweitern will, kann man sagen: "wenn dieser oder jener Text auf dieser Webseite nach dem submit()" zu finden ist, dann mache dies und jenes". Es wäre ja nur eine Alternative?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: dann hast Du wieder eine andere Seite als ich zum Testen. Denn wenn es die Form gibt, gibt es auch ein übergeordnetes p-Tag. Das war aber auch nur ein Beispiel, wie Du bestimmte Elemente in einem HTML-Dokument finden kannst, Du mußt es halt auf Deinen Fall entsprechend anpassen. Ich sag Dir ja nur die ganz Zeit, welche Probleme Du mit Deinen Ansätzen haben wirst, und prompt tauchen die auch auf. Es gibt nicht die perfekte Lösung, sondern nur mehr oder weniger robuste. Irgend ein p-Tag enthält halt in 99% der Fälle keine Fehlermeldung, "p.error" dagegen wird wohl in 99.9% der Fälle eine Fehlermeldung enthalten.

Die Seite hat mehrere Forms und damit mehrere submit. Die richtige Form erkennt man am Namen. Solche Gedanken solltest eigentlich DU von selbst machen. Alles was zu kompliziert ist, wird bei kleinen Änderungen nicht mehr funktionieren, alles was zu einfach ist, wird Dir möglicherweise falsche Ergebnisse liefern, wenn etwas zu einfach und gleichzeitig zu kompliziert ist, wie Deine Ideen bisher, ...
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich teste nach wie vor ein und die selbe Seite. Und das die Fehlermeldungen im 'p' zu finden sind, liegt ja an der Webseite. Meine Idee mag einfach sein, aber kompliziert zugleich?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: einen HTML-Baum über 17 Ebenen anzugeben halte ich für kompliziert. Trotzdem ist es zu einfach gedacht, weil auf diese Weise auch viele <p>-Elemente gefunden werden können, die keine Fehlermeldungen enthalten.

Hier das Ergebnis, wenn ich meine Suche durchführe:

Code: Alles auswählen

>>> soup.find("form", {"name":"register"}).fetchParents('p')[0].text
u'Sie muessen einen Benutzernamen angeben!Sie muessen ein Passwort angeben!Sie muessen eine E-Mail Adresse angeben!Sie muessen das Feld PrivateKey erst ausfuellen!Sie muessen das Feld ProgramName erst ausfuellen! \n\nName :\n\n\n\nEmail :\xa0\n \n\n\nPasswort :\xa0\n \n\n\nPasswort (Wdh.)  :\xa0\n\n\n\nICQ :\xa0\n\n\n\n\n\n\n\n\n\xa0\xa0\xa0        \n\xa0\xa0\xa0\n\n\n\n\n'
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Hier nochmal mein Quelltext mit deiner Print-Anweisung.

Code: Alles auswählen

from mechanize import Browser
from bs4 import BeautifulSoup

url ="http://xarphus.de/example.php"

br = Browser()
br.set_handle_robots(False)
br.open(url)


name = raw_input("Name: ")
e_mail = raw_input("EMail: ")
password = raw_input("Password: ")
password_confirm = raw_input("Password Confirm: ")
icq_id = raw_input("ICQ: ")

br.select_form(nr=1)
br.form["uname"] = name
br.form["email"] = e_mail
br.form["pass"] = password
br.form["pass2"] = password_confirm
br.form["icq"] = icq_id
response = br.submit()
   
soup = BeautifulSoup(response.read())
print soup.find("form", {"name":"register"}).fetchParents('p')[0].text
Fehlermeldung:
Traceback (most recent call last):
File "D:\Dan\Python\Übung\login_mechanize.py", line 26, in <module>
print soup.find("form", {"name":"register"}).fetchParents('p')[0].text
IndexError: list index out of range
Du siehst, ich benutze ein und die selbe Seite.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: was bekommst Du denn bei response.read() zurück?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich bekomme das zurück.
BlackJack

@Sirius3: Nur so als Anmerkung nebenbei: BeautifulSoup 4 hält sich mittlerweile an PEP8-Namensgebung, hat aber die alten Methoden noch aus Kompatibilitätsgründen. Bei neuem Code würde ich deshalb statt `fetchParents()` eher `find_parents()` vewenden. Es wurden also auch Methoden nicht nur in der Schreibweise angepasst, sondern auch umbenannt damit die API einheitlicher ist. Und wenn man sowieso nur das erste Element braucht, ginge auch `find_parent()`.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: Bei Dir wird aus irgendwelchen Gründen das <p>-Tag vor <form> geschlossen, also ist es ja logisch, dass Du statt dem Parent-Element das Vorgänger-Element möchtest:

Code: Alles auswählen

soup.find("form", {"name":"register"}).find_previous('p').text
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ich benutze Python 2.7.6, ist das der Grund, weshalb es sich bei mir anders verhält als bei dir?
Antworten