Performance Probleme bei vielen requests

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.
Antworten
Octavia
User
Beiträge: 5
Registriert: Donnerstag 21. Mai 2015, 22:41

Hallo Allerseits,

ich programmiere momentan an einem Python Script, das die Auflistung einer Website durchgeht und von jeder Seite dieser Auflistung die enthaltenen Links ausliest. Es sind circa 4000 Seiten mit 25 Links pro Seite also etwa 100.000 Links die ich speichern muss. Allerdings habe ich das Problem, dass nach circa 200 Seiten bereits die Performance so weit in die Brüche geht, das andere Programme einfrieren. Ich vermute einmal, ich habe irgendwo ein Detail übersehen, aber bin nicht in der Lage es auszumachen. In der Suche konnte leider ich nichts passendes finden. Ich hoffe jemand findet die Muse mich bei der Lösung zu unterstützen. Einen Beispiel Code wie das Programm aussieht, habe ich beigefügt.

Liebe Grüße,

Octavia

Code: Alles auswählen

from pprint import pprint
from lxml import etree
import itertools
import requests

def function parseUrls(tree):
    return tree.xpath('//span[@class="tip"]/a/@href')

def function isLastPage(tree):
    if not tree.xpath('//a[@rel="next"]'):
        return True

urls = []
for i in itertools.count(1):
    content = requests.get('http://www.site.de/index.php?page=' + str(i), allow_redirects=False)
    tree = etree.HTML(content.text)

    urls.extend(parseUrls(tree))

    if isLastPage(tree):
        break
    
pprint urls
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Octavia: Du speicherst ja auch nicht nur die URLs, sondern hast immer den kompletten Dokumentenbaum im Speicher. Je nach Seitengröße sind das etliche Megabytes * 200.
Octavia
User
Beiträge: 5
Registriert: Donnerstag 21. Mai 2015, 22:41

@Sirius3: Danke für den Hinweis :), ich habe dann dies betreffend eine Frage die du mir vielleicht auch noch beantworten magst.
Ich ging davon aus, dass wenn ich die Variable die den Dokumentenbaum hält (tree) überschreibe, die Referenz und ihr Inhalt von der Garbage Collection gelöscht wird oder habe ich da einen Denkfehler?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Octavia hat geschrieben: Ich ging davon aus, dass wenn ich die Variable die den Dokumentenbaum hält (tree) überschreibe, die Referenz und ihr Inhalt von der Garbage Collection gelöscht wird oder habe ich da einen Denkfehler?
Dem ist auch so - allerdings weiß man halt nicht, *wann* der GC das löscht.

Ich sehe bei Sirius3s Einwand hier aber auch noch keinen Bezug zum gezeigten Pseudo-Code. Laut Doku erzeugt die ``etree.HTML``-Factory-Methode einen Baum und gibt den Wurzelknoten zurück. Wenn die Referenz darauf verloren geht (also beim Parsen einer neuen Seite), sollte die Referenz doch dann als löschbar vom GC erkannt werden‽

Bist Du denn sicher, dass das nicht an den Seiten liegen kann, die bei 200 anfangen? Evtl. blockt Dich der Webserver ja auch, weil die nicht wollen, dass Du da fröhlich scrapst? ;-)

Du zeigst hier natürlich auch kein echtes Python - vielleicht weicht Dein Produktivcode doch an einer entscheidenen Stelle ab, so dass die Ursache hier nicht ersichtlich ist?

Statt

Code: Alles auswählen

if not tree.xpath('//a[@rel="next"]'):
    return True
kannst Du auch nur

Code: Alles auswählen

return not tree.xpath('//a[@rel="next"]')
schreiben. Du prüfst ja im ``if`` *immer* auf eine wahre Bedingung. Wenn Du das Ergebnis dessen zurückliefern willst, dann kannst Du das auch *direkt* tun ;-)

Ach ja, laut PEP8 solltest Du *vier Leerzeichen* als Einrückung pro Ebene benutzen. Nimm dafür keine Tabs :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Octavia
User
Beiträge: 5
Registriert: Donnerstag 21. Mai 2015, 22:41

Ich bin sicher das es nicht an der Seitenanzahl liegt. Das Programm nimmt ja quasi das ganze System mit. Wenn es nur vom Server her ausgebremst würde, sollte es doch eigentlich die anderen Programme nicht beeinträchtigen, so das diese einfrieren?

Mein Produktivcode den ich verwenden hat nur den Unterschied, dass nach dem gezeigten Code noch ein Teil kommt, der mit den Daten arbeiten soll. Ich dachte der Einfachheit halber und um es kompakter zu machen, schneide ich diese Teil weg, weil dieser Teil sowieso nicht erreicht wird.

Danke für den Hinweis, du hast natürlich recht, das if an der Stelle war überflüssig.
Das mit den 4 Leerzeichen tue ich, das macht Pycharm zum Glück automatisch. :)

Ich habe jetzt objgraph via pip installiert und probeweise mal die object growth pro Seite ausgegeben. Dort scheint es so, als würde der Speicher wirklich nicht gelöscht.

Das Endergebnis ist das _ElementUnicodeResult und _Elemente pro Seite um 25 wachsen und die Anzahl der _Document Objekte um 1.

Das geschieht sobald ich das Ergebnis von parseUrls in meine Liste speichere. Wenn ich die Funktion so aufrufe ohne mit den Resultwerten zu arbeiten nicht.

Kann es sein das durch extend() die Referenzen auf die Listenelemente des Returnwerts auf die neuen Listenlemente übertragen werden, so das die Referenzen auf die Returnwerte bestehen bleiben und somit abhängige Objekte nicht gelöscht werden können?
Zuletzt geändert von Octavia am Freitag 22. Mai 2015, 14:25, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Octavia: wenn Du nur die URLs als String haben willst, würde ich halt die Ergebnisse von tree.xpath in Strings umwandeln.
Octavia
User
Beiträge: 5
Registriert: Donnerstag 21. Mai 2015, 22:41

Sirius3: Warum sollte ich das nochmal in Strings umwandeln, das Ergebnis sind doch sowieso Listen aus Strings mit den URLs?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

*patsch* Wer lesen kann ist klar im Vorteil: http://lxml.de/api/lxml.etree._ElementT ... html#xpath

Da steht es ja bei ``return``: "Returns a list (nodeset), or bool, float or string." Wenn man da also Knoten-Objekte zurück bekommt, so werden durch diese ja Referenzen auf den ursprünglichen Baum gehalten :!: (Der Knoten muss ja ja sein API behalten, also auch ``getroot`` usw.)

Also ist es klar, dass man in der Liste *nur* die Strings der Links speichern sollte. Dann klappt 's auch mit dem GC :mrgreen:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Octavia: Du speicherst in Deiner Liste _ElementUnicodeResult-Objekte. Diese haben eine Referenz auf das Dokument. Wenn Du diese Objekte in Strings umwandelst, ist die Referenz auch weg und der Speicher kann freigegeben werden.
Octavia
User
Beiträge: 5
Registriert: Donnerstag 21. Mai 2015, 22:41

Jetzt verstehe ich das alles. Ich bin noch relativ neu was Python angeht und mir war nicht bewusst, dass es recht üblich ist in Python Klassen mit Basistypen-verhalten zu haben. Ich dachte die ganze Zeit das wäre eine Liste von Strings und ich verliere meinen Verstand.

Vielen Dank, Sirius3 und Hyperion, ihr habt mir nicht nur mit meinem Problem geholfen, sondern mir noch etwas neues über Python beigebracht.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Octavia: üblich ist das nicht, aber es ist durchaus möglich.
Antworten