Seite 1 von 2
MemoryError bei Beautiful Soup (dringend)
Verfasst: Donnerstag 13. September 2007, 12:51
von Michael Schneider
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
Verfasst: Donnerstag 13. September 2007, 12:58
von BlackVivi
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
Verfasst: Donnerstag 13. September 2007, 13:16
von Michael Schneider
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
Verfasst: Donnerstag 13. September 2007, 13:29
von mq
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).
Verfasst: Donnerstag 13. September 2007, 13:58
von 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!
Verfasst: Donnerstag 13. September 2007, 14:15
von Michael Schneider
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
Verfasst: Donnerstag 13. September 2007, 19:24
von Michael Schneider
Leider hat das Extrahieren aller Elemente nicht (viel) geholfen. Ich habe mit:
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
Verfasst: Donnerstag 13. September 2007, 20:54
von mitsuhiko
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.
Verfasst: Donnerstag 13. September 2007, 21:05
von birkenfeld
Und zwar nicht mit "unicode()", denn __unicode__ liefert einfach self zurück.
Verfasst: Donnerstag 13. September 2007, 22:08
von Michael Schneider
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
Verfasst: Freitag 14. September 2007, 11:14
von keppla
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
[gelöst] MemoryError bei Beautiful Soup (dringend)
Verfasst: Freitag 14. September 2007, 11:26
von Michael Schneider
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
Verfasst: Freitag 14. September 2007, 11:44
von Michael Schneider
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
Re: [gelöst] MemoryError bei Beautiful Soup (dringend)
Verfasst: Freitag 14. September 2007, 12:19
von Michael Schneider
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
Verfasst: Freitag 14. September 2007, 12:29
von 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.
Verfasst: Freitag 14. September 2007, 13:33
von Michael Schneider
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
Verfasst: Freitag 14. September 2007, 14:46
von mitsuhiko
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.

Verfasst: Freitag 14. September 2007, 15:42
von BlackJack
Slicing hat bei einem kurzen Test auch funktioniert.

Verfasst: Freitag 14. September 2007, 21:13
von Michael Schneider
Könnt ihr mir nochmal erklären, was es mit dem "child +", dem Unicode und dem Slicing auf sich hat?
Michael
Verfasst: Freitag 14. September 2007, 22:07
von 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'>