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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

MemoryError bei Beautiful Soup (dringend)

Beitragvon Michael Schneider » Donnerstag 13. September 2007, 12:51

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:

Beitragvon BlackVivi » Donnerstag 13. September 2007, 12:58

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Beitragvon Michael Schneider » Donnerstag 13. September 2007, 13:16

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

Beitragvon mq » Donnerstag 13. September 2007, 13:29

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

Beitragvon BlackJack » Donnerstag 13. September 2007, 13:58

`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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Beitragvon Michael Schneider » Donnerstag 13. September 2007, 14:15

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Beitragvon Michael Schneider » Donnerstag 13. September 2007, 19:24

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 ...
Benutzeravatar
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Beitragvon mitsuhiko » Donnerstag 13. September 2007, 20:54

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

Beitragvon birkenfeld » Donnerstag 13. September 2007, 21:05

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Beitragvon Michael Schneider » Donnerstag 13. September 2007, 22:08

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

Beitragvon keppla » Freitag 14. September 2007, 11:14

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

[gelöst] MemoryError bei Beautiful Soup (dringend)

Beitragvon Michael Schneider » Freitag 14. September 2007, 11:26

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Beitragvon Michael Schneider » Freitag 14. September 2007, 11:44

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: 567
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Bremen
Kontaktdaten:

Re: [gelöst] MemoryError bei Beautiful Soup (dringend)

Beitragvon Michael Schneider » Freitag 14. September 2007, 12:19

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

Beitragvon BlackJack » Freitag 14. September 2007, 12:29

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.

Wer ist online?

Mitglieder in diesem Forum: Astorek