MemoryError bei Beautiful Soup (dringend)

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
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hallo,

ich benutze Beautiful Soup zum Parsen von HTML-Antwort Seiten auf individuelle Queries.
Das Problem ist, dass ich reproduzierbar nach etwa 2000 ausgewerteten Webseiten folgenden Fehler bekomme:
Traceback (most recent call last):
File "./getall.py", line 71, in ?
main()
File "./getall.py", line 54, in main
trupdate.update_trouble(sUid, iCheckedInOnly=False, MySQL=MySQL)
File "./trupdate.py", line 89, in update_trouble
dTrouble = get_props(sUrl=sLink)
File "./troubleinfo.py", line 217, in get_props
Soup = soup_page(sUrl)
File "/home/ts8azt/scripts/beautifulsoup/soupgimmics.py", line 59, in soup_page
soup = BeautifulSoup(sData)
File "/home/ts8azt/scripts/beautifulsoup/BeautifulSoup.py", line 1282, in __init__
BeautifulStoneSoup.__init__(self, *args, **kwargs)
File "/home/ts8azt/scripts/beautifulsoup/BeautifulSoup.py", line 946, in __init__
self._feed()
File "/home/ts8azt/scripts/beautifulsoup/BeautifulSoup.py", line 971, in _feed
SGMLParser.feed(self, markup)
File "/python22/lib/python2.2/sgmllib.py", line 95, in feed
self.goahead(0)
File "/python22/lib/python2.2/sgmllib.py", line 129, in goahead
k = self.parse_starttag(i)
File "/python22/lib/python2.2/sgmllib.py", line 290, in parse_starttag
self.finish_starttag(tag, attrs)
File "/python22/lib/python2.2/sgmllib.py", line 321, in finish_starttag
self.unknown_starttag(tag, attrs)
File "/home/ts8azt/scripts/beautifulsoup/BeautifulSoup.py", line 1134, in unknown_starttag
tag = Tag(self, name, attrs, self.currentTag, self.previous)
File "/home/ts8azt/scripts/beautifulsoup/BeautifulSoup.py", line 415, in __init__
self.hidden = False
MemoryError
Ich übergebe die gesamte Website auf einen Schlag (sData) an Beautiful Soup. Als ich die Seiten damals ohne Beautiful Soup parste, gab nie einen MemoryError.

Es liegt die Vermutung nahe, dass sich innerhalb des Moduls irgendwelche Daten ansammeln, aber ich weiß nicht, wo ich ansetzen soll.

Kennt jemand das Problem? Oder kann mir jemand einen Tipp geben, wie ich den Speicherfresser aufspüren kann?

Vielen Dank für eure Hilfe! Gruß,
Michael
Zuletzt geändert von Michael Schneider am Freitag 14. September 2007, 12:19, insgesamt 2-mal geändert.
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
BlackVivi
User
Beiträge: 762
Registriert: Samstag 9. Dezember 2006, 14:29
Kontaktdaten:

Vielleicht'ne dumme Idee, aber...

Warum müssen es über 2000 Seiten auf einmal sein oO? Mach'ne Funktion die das Ergebnis des parsens zurückgibt und fütter diese mit maximal 1800~ Seiten o_o
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi!

Nein, nein. Ich erzeuge für jede abgerufene Website eine eigene Instanz von BeautifulSoup, die ich aus Verzweiflung sogar extra mit "del Soup" vor Verlassen der Funktion wieder lösche.
Aus meiner Sicht kann ich keine Altlasten erkenne, die sich aufstauen könnten. Also keine nennenswerten Listen, Tupel, ..., die während dem Programmablauf gehalten werden. Die Ergebnisse werden über das Objekt MySQL in die Datenbank geschrieben und dann war es das.

Grüße,
Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
mq
User
Beiträge: 124
Registriert: Samstag 1. Januar 2005, 19:14

Michael Schneider hat geschrieben:Nein, nein. Ich erzeuge für jede abgerufene Website eine eigene Instanz von BeautifulSoup, die ich aus Verzweiflung sogar extra mit "del Soup" vor Verlassen der Funktion wieder lösche.
Zur Information (weil das oft falsch verstanden wird): "del irgendwas" loescht nicht das betreffende Objekt, sondern entfernt nur den Namen aus dem aktuellen Namespace. Das Objekt existiert immer noch, bis der Garbage Collector es wegraeumt (was er nicht sofort tun muss; uebrigens kann man mit gc.collect() einen Durchlauf des GC forcieren - keine Ahnung, ob das in deinem Fall was bringt, aber ich wuerde davon ausgehen, dass vor einem MemoryError nochmal automatisch der GC angeschmissen wird).
BlackJack

`BeautifulSoup` erzeugt aus der Webseite einen Graphen von Objekten. Nicht einfach nur einen Baum, sondern bei jedem Knoten auch einen Verweis auf's Elternelement. Und das gilt für *jedes* Objekt was man da heraus holt! Beispiel:

Code: Alles auswählen

In [85]: b
Out[85]: <td align="center" width="90"><font size="2" face="Arial" class="titel"><b>DATUM</b></font></td>

In [86]: b(text=True)
Out[86]: [u'DATUM']

In [87]: b(text=True)[0]
Out[87]: u'DATUM'

In [88]: c = b(text=True)[0]

In [89]: c.parent
Out[89]: <b>DATUM</b>

In [90]: type(c)
Out[90]: <class 'BeautifulSoup.NavigableString'>
Das Bedeutet wenn dieses 'DATUM' das einzige ist, was man noch im Programm verwendet, hängt da trotzdem immer noch die gesamte Webseite dran!
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi!

@Lumax: vielen Dank. Die Funktion von del ist mir bekannt, war auch vielmehr eine Verzweifelungsaktion. ;-)
BlackJack hat geschrieben:`BeautifulSoup` erzeugt aus der Webseite einen Graphen von Objekten. Nicht einfach nur einen Baum, sondern bei jedem Knoten auch einen Verweis auf's Elternelement. Und das gilt für *jedes* Objekt was man da heraus holt!

Das Bedeutet wenn dieses 'DATUM' das einzige ist, was man noch im Programm verwendet, hängt da trotzdem immer noch die gesamte Webseite dran!
Hallo Blacky, ja, daran könnte das liegen. Ich dachte zwar, dass der GC inzwischen auch diese wechselseitigen Bezüge knackt, aber bei Py2.2 sicher noch nicht. :-)
Was kann ich tun, meinst Du ein ".extract()" auf jedes Element reicht aus, um den Grafen zu zersprengen?

[edit] Ich kann ja mal die Referenzen auf das Objekt zählen und wie sich ein .extract() auswirkt. In der Anleitung zu BS steht, dass über die Methode Speicherplatz freigegeben wird. Heißt das, dass das was man vor dem .extract()-Aufruf nicht referenziert hat, komplett mit der inneren Struktur aufgelöst wird?

Grüße,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Leider hat das Extrahieren aller Elemente nicht (viel) geholfen. Ich habe mit:

Code: Alles auswählen

for Elem in Soup():
    Elem.extract()
Kurz vor dem Löschen alle Elemente vereinzelt. Beim nächsten Versuch habe ich ca. 2600-2800 Seiten laden können (die Abweichung kann aber auch an geringerem Inhalt liegen). Da muss noch etwas anderes sein.

Kann man solche Speicherfresser in fremden Modulen irgendwie ermitteln?

Grüße,
Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Pass auf. Beautiful Soup hat keine normalen Strings. Das sind auch Nodes. Wenn du nur irgendwas davon behälst hast du den ganzen Baum noch dran. Wandle die UnicodeDammit objekte auf alle Fälle per hand in unicode() um.
TUFKAB – the user formerly known as blackbird
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Und zwar nicht mit "unicode()", denn __unicode__ liefert einfach self zurück.
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Oh, richtig, da war noch etwas. Mit einem der Suchbefehle bekomme ich nur die Tag-Objekte und vielleicht war Soup() einer davon...

Mit Elem.extract() werden (angeblich) alle Referenzen des Baumes auf dieses Element gelöst, außer natürlich die des Sub-Baums. Von daher muss ich nochmal überprüfen, ob Soup() wirklich über alle Elemente iteriert. Ich probiere das morgen mal.

Behalten sollte ich eigentlich nichts (jedenfalls beabsichtigt), da alles in der Funktion 'update_trouble' abgehandelt wird und nichts zurückgegeben wird.

Man, wenn ich gewusst hätte, dass das Auflösen des BS-Baums so kompliziert ist, dann hätte ich doch wieder einen eigenen Parser geschrieben. ;-)

Grüße,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

BlackJack hat geschrieben:`BeautifulSoup` erzeugt aus der Webseite einen Graphen von Objekten. Nicht einfach nur einen Baum, sondern bei jedem Knoten auch einen Verweis auf's Elternelement. Und das gilt für *jedes* Objekt was man da heraus holt!

Das Bedeutet wenn dieses 'DATUM' das einzige ist, was man noch im Programm verwendet, hängt da trotzdem immer noch die gesamte Webseite dran!
Hallo Blacky, ja, daran könnte das liegen. Ich dachte zwar, dass der GC inzwischen auch diese wechselseitigen Bezüge knackt, aber bei Py2.2 sicher noch nicht. :-)
Das hat nix mit der Version zu tun, auch einer, der Zirkelbezüge erkennt, wird in dem Beispiel nichts freigeben können, weil Datum ja noch nicht freigegeben werden kann (weils genutzt wird). Weil man Datum nutzen kann, muss man auch all die objekte, auf die Datum referenziert, noch nutzen können, etc.
Zirkelbezüge sind etwas wie dies hier:

Code: Alles auswählen

class X:
   pass

# Zwei objekte im Speicher
a = X()
b = X()

# Objekte verweisen aufeinander
a.x = b
b.x = a

# Kein Objekt kann mehr erreicht werden, aber sie verweisen immer noch aufeinander
del a
del b
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi!

Mit der neuesten Änderung scheint das Script jetzt zu laufen, inzwischen wurden ca. 8000 Seiten ohne MemoryError geparst. Soup() hatte nur die Tag-Knoten geliefert und selbst da ist der Iterator vielleicht durch das Extrahieren der Knoten durcheinander gekommen.

Hier meine Lösung zur Entschärfung der komplexen Soup-Graphen, sollte ich vielleicht bei Code-Snippets einstellen ;-):

Code: Alles auswählen

def disjoint_soup(Soup):
    """remove the internal links between the <Soup>-elements"""
    All = lambda s: True
    l = [I for I in Soup(All)]              # collect tags
    l.extend([I for I in Soup(text=All)])   # collect navigable strings
    for I in l: 
        I.extract()
Vielen Dank allen Beteiligten!!

Gruß,
Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hallo Keppla!
keppla hat geschrieben:
Michael Schneider hat geschrieben:
BlackJack hat geschrieben:Das Bedeutet wenn dieses 'DATUM' das einzige ist, was man noch im Programm verwendet, hängt da trotzdem immer noch die gesamte Webseite dran!
Hallo Blacky, ja, daran könnte das liegen. Ich dachte zwar, dass der GC inzwischen auch diese wechselseitigen Bezüge knackt, aber bei Py2.2 sicher noch nicht. :-)
Das hat nix mit der Version zu tun, auch einer, der Zirkelbezüge erkennt, wird in dem Beispiel nichts freigeben können, weil Datum ja noch nicht freigegeben werden kann (weils genutzt wird).

Weil man Datum nutzen kann, muss man auch all die objekte, auf die Datum referenziert, noch nutzen können, etc.
Zirkelbezüge sind etwas wie dies hier:
Mir sind Zirkelbezüge hinreichend bekannt und ich bezog mich nicht auf BlackJacks Beispiel, sondern allgemein auf Zirkelbezüge. Ich möchte gelesen haben, dass der GC dahingehend verbessert wurde, dass er in sich abgeschlossene Zirkelbezugkonstrukte aufbrechen kann.

Im übrigen schrieb ich:
Michael Schneider hat geschrieben:Behalten sollte ich eigentlich nichts (jedenfalls beabsichtigt), da alles in der Funktion 'update_trouble' abgehandelt wird und nichts zurückgegeben wird.
Gruß,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Michael Schneider hat geschrieben:Hi!

Mit der neuesten Änderung scheint das Script jetzt zu laufen, inzwischen wurden ca. 8000 Seiten ohne MemoryError geparst.
Nach 10000-12000 Seiten ist es dann doch passiert: MemoryError. Das gibts noch nicht!

Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
BlackJack

Vielleicht ist es einfacher das Programm so umzuschreiben, das pro Seite, oder für alle hundert Seiten, ein eigener Prozess gestartet wird. Dann könnte man auch gleich mehrere Prozesse parallel starten und damit Multicore-Prozessoren besser ausnutzen.
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

BlackJack hat geschrieben:Vielleicht ist es einfacher das Programm so umzuschreiben, das pro Seite, oder für alle hundert Seiten, ein eigener Prozess gestartet wird. Dann könnte man auch gleich mehrere Prozesse parallel starten und damit Multicore-Prozessoren besser ausnutzen.
Wow, die Idee ist gut! Ich dachte gerade darüber nach, wirklich ein paar hundert Seiten auf einmal über einen popen2-Aufruf in einer eigenen Programminstanz zu analysieren (wie kann man eigentlich über eine Pipe in ein Skript kommende Daten dort auslesen?).
Aber wenn es stimmt, dass im Zweifelsfall nur der eine Prozess gekillt wird und nicht der mit meinem Hauptscript, dann ist das zweifellos besser. Gerade weil der Import der MySQL Schnittstelle immer etwas dauert.

Danke für die Anregung, mal sehen wie ich das einbauen kann!

Grüße,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

birkenfeld hat geschrieben:Und zwar nicht mit "unicode()", denn __unicode__ liefert einfach self zurück.
Tatsache. Und jetzt ist mir auch wieder klar warum TextPress "child + ''" macht. :wink:
TUFKAB – the user formerly known as blackbird
BlackJack

Slicing hat bei einem kurzen Test auch funktioniert. :-)
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Könnt ihr mir nochmal erklären, was es mit dem "child +", dem Unicode und dem Slicing auf sich hat?

Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
BlackJack

Jo, man achte auf den Typ:

Code: Alles auswählen

In [137]: c
Out[137]: u'DATUM'

In [138]: type(c)
Out[138]: <class BeautifulSoup.NavigableString>

In [139]: c + ''
Out[139]: u'DATUM'

In [140]: type(c + '')
Out[140]: <type 'unicode'>

In [141]: type(c[:])
Out[141]: <type 'unicode'>
Antworten