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

@Sophus: da ich nur auf Deinen Quelltexten operiere scheint der Unterschied irgendwo bei Dir entstanden zu sein.

Erster Paste:

Code: Alles auswählen

<p>Sie muessen einen Benutzernamen angeben!<br />Sie muessen ein Passwort angeben!<br />Sie muessen eine E-Mail Adresse angeben!<br />Sie muessen das Feld PrivateKey erst ausfuellen!<br />Sie muessen das Feld ProgramName erst ausfuellen!<br /><form action="/example.php?adv_sent=registration" method="POST" name="register">   <table width="348" border="0" cellpadding="2">
Zweiter Paste:

Code: Alles auswählen

<p>Ihr Benutzername muss mindestens 3 Zeichen enthalten!<br/>Ihr Passwort muss mindestens 3 Zeichen enthalten!<br/>Sie haben eine ungueltige E-Mail angegeben!<br/>Sie muessen das Feld PrivateKey erst ausfuellen!<br/>Sie muessen das Feld ProgramName erst ausfuellen!<br/></p><form action="/example.php?adv_sent=registration" method="POST" name="register"> <table border="0" cellpadding="2" width="348">
Zwei völlig unterschiedliche Formatierungen bei anscheinend dem selben PHP-Skript? Zum Glück funktioniert find_previous komischerweise bei beiden Varianten. Das meinte ich mit "robust".
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Ja, die Methode find_previous () funktioniert tatsächlich. Und weshalb ich hier zwei unterschiedliche Ergebnisse bekomme, verstehe ich genauso wenig.

Hatte ich beinahe vergessen: Danke 8)
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: jetzt habe ich doch wieder die ganze Lösung präsentiert. Hast Du wenigstens kapiert, wie und warum die Lösung so ist? Fürs nächste mal. Nochmal drei Tage Diskussion halte ich nicht aus. :P
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Hier meine Verständnis-Hausaufgabe.

Code: Alles auswählen

soup.find("form", {"name":"register"}).find_previous('p').text
Durch die find()-Methode wird durch die Datenstruktur bzw. durch den HTML-Baum der Webseite navigiert. Man hätte hier auch find_all() nehmen können, und im gesamten HTML-Baum suchen können. Da man hier aber genau weißt, wonach man sucht, und zwar die Form mit dem Namen register, so wäre es sinnvoller find() zu benutzen. Die find()-Methode setzt hier bei form an, und sucht dann im nächsten Schritt nach dem Schlüssel name, der dann mit dem Wert register im Zusammenhang steht. Aus diesem Grund wird innerhalb dieser Methode eine Dictionaries benutzt. Mit find_previous() wird vor dem Ergebnis der Methode find() geschaut, und zwar ab dem <p>-Tag, anschließend wird dann später das Ganze als Text über die Print-Anweisung ausgegeben.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sophus hat geschrieben:(...) und zwar ab dem <p>-Tag, anschließend wird dann später das Ganze als Text über die Print-Anweisung ausgegeben.
Genauer gesagt: Mit dem `.text`-Attribut ist halt der nackte Text innerhalb der `<p>`-Tags gemeint.

Und der Unterschied zwischen `findall()` und `find()` ist halt, dass `findall()` alle Treffer als Sequenz (oder Generator, müsste ich jetzt nachsehen) liefert, während `find()` nur den ersten Treffer zurückgibt. `find()` ist verkürzt ausgedrückt sozusagen ein `findall()[0]` (bzw `next(findall())`.
Antworten