Lesen ohne den Arbeitsspeicher vollzustopfen?

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
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

im nachfolgenden (lauffähigen) Quelltext möchte ich auf einen geschützten Bereich zugreifen. Nach einem erfolgreichen Login möchte ich eine Textdatei auslesen. Das funktioniert auch alles wunderbar. Nur möchte ich an dieser Stelle Präventionsmaßnahme ergreifen. Es kann ja mal passieren, dass eine Datei "wächst". Ich möchte also nicht den Arbeitsspeicher unnätig vollstopfen. Mein derzeitiger Quelltext liest den gesamten Inhalt der Textdatei aus, und speichert den kompletten Inhalt in den Speicher. Irgendwie bin ich damit nicht ganz zufrieden.

Eine andere Möglichkeit wäre ja, die Datei herunterzuladen, sie irgendwo auf der Festplatte zu speichern, und dann zum Beispiel mit einem Generator auslesen. Allerdings erscheint mit dieser Weg wie ein Umweg.

Habt ihr eine bessere Idee?

Code: Alles auswählen

from requests.auth import HTTPBasicAuth

import requests 

def login_digest(url, login_name, login_pwd):
    res = requests.get(url, auth=HTTPDigestAuth(login_name, login_pwd))
    return res

if __name__ == "__main__":

    # my example link: http://www.sophus.bplaced.net/test/xar.txt
    # For testing:  username: xar
    #               password 456123
    while True:
        url = unicode(raw_input("Enter URL: "))
        login_name = unicode(raw_input("Enter your nickname: "))
        login_pwd = unicode(raw_input("Enter your password: "))
        
        result = log_in(url, login_name, login_pwd)

        if result.ok:
            res = result.content
            print res
        
            break

        else:
            print "Your login is not validated"
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wäre es nicht toll wenn sowas dokumentiert wäre?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ich muss mich gleich mal entschuldigen. Mein Quelltext im ersten Post war alles andere als lauffähig.

Nachdem ich mir den Ratschlag von DAsIch herangezoigen habe, sieht meine vorläufige Lösung wie folgt aus:

Code: Alles auswählen

from requests.auth import HTTPBasicAuth
from contextlib import closing

import requests 

def log_in(url, login_name, login_pwd):
    res = requests.get(url, auth=HTTPBasicAuth(login_name, login_pwd), stream=True)
    return res

if __name__ == "__main__":

    # my example link: http://www.sophus.bplaced.net/test/xar.txt
    # For testing:  username: xar
    #               password 456123
    while True:
        url = unicode(raw_input("Enter URL: "))
        login_name = unicode(raw_input("Enter your nickname: "))
        login_pwd = unicode(raw_input("Enter your password: "))
        
        result = log_in(url, login_name, login_pwd)

        if result.ok: 
            with closing(result) as online_file:
                print online_file.content
        
            break

        else:
            print "Your login is not validated"

Was hat sich geändert? Ich habe in Zeile 2 das Modul contextlib importiert. In Zeile 7 habe ich "stream=True" hinzugefügt. In Zeile 23 sorge ich einfach dafür, dass die Verbindung automatisch geschlossen wird.

Wen ihr noch Kritik oder Anmerkungen habt, immer her damit.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und inwiefern "schützt" das jetzt deinen Arbeitsspeicher vor zu großen Dateien?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@snafu: Ich ging davon aus, dass die Datei durch diesen Flag stream=True nicht sofort und komplett in einem Rutsch geladen wird. Allerdings möchte ich die Datei nicht herunterladen, sie irgendwo auf dem Rechner speichern. Was ich eher möchte ist, dass man die Datei liest, und die entsprechenden Informationen entnimmt. Daher habe ich den Aspekt des Herunterladens - im herkömlmlichen Sinne - ausgeblendet. Daher habe ich unteranderem die Methode iter_content beiseite gelassen. Denn sobald über den Inhalt der Server-Rückantwort iteriert wird, muss es ja irgendwo hinterlegt werden, richtig? Und an dieser Stelle habe ich gehofft, könnte ich das umgehen.

Denn wie gesagt. Mir geht es darum, dass ich nicht die Datei herunterladen möchte, sondern ich möchte aus der Textdatei, die auf dem Webserver im geschützten Bereich liegt, bestimmte Informationen entnehmen. Um welche Informationen es sich hierbei handelt, ist erst einmal nebensächlich. Mein Kernproblem oder eher meine Befürchtung ist, dass in dieser Textdatei mit der Zeit reichlich viele Informationen ansammeln könnte. Ich kann es nicht vorherbestimmen. Wenn dem also so ist, dass die Textdatei "wächst", möchte ich nicht erst den ganzen Inhalt der Textdatei in den Speicher laden, nur um hinterher an einer bestimmten Inormation zugelangen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

snafu hat mich zum Nachdenken angeregt. Wie immer habe ich womöglich falsch gedacht. Ich war doch so mutig, und habe mich mal an die iter_content()-Methode herangetastet. Und raus kam dabei das:

Code: Alles auswählen


from requests.auth import HTTPBasicAuth
from contextlib import closing

import requests 

def log_in(url, login_name, login_pwd):
    res = requests.get(url, auth=HTTPBasicAuth(login_name, login_pwd), stream=True)
    return res

if __name__ == "__main__":

    # my example link: http://www.sophus.bplaced.net/test/xar.txt
    # For testing:  username: xar
    #               password 456123
    while True:
        url = unicode(raw_input("Enter URL: "))
        login_name = unicode(raw_input("Enter your nickname: "))
        login_pwd = unicode(raw_input("Enter your password: "))
        
        result = log_in(url, login_name, login_pwd)

        if result.ok: 
            with closing(result) as my_file:

                for chunk in result.iter_content(chunk_size=1024, decode_unicode=True):
                    print "text", chunk
        
            break

        else:
            print "Your login is not validated"

Nun, in Zeile 25 iteriere ich mit dem Iterator (iter_content) für die sogenannte Rückantwort des Servers. Der iter_content()-Methode übergebe ich gleich mal zwei nützliche Argumente. Das erste Argument (chunk_size) sagt nur, wie groß dfe einzelnen Daten-Stückchen sein sollen, und das zweite Argument (decode_unicode) ist dazu da, dass ich den Inhalt auch in unicode-Type bekomme, ansonsten bekäme ich Bytestrings sobald es False wäre.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Der Sinn von stream=True ist dass requests nicht sofort alles herunterlädt. Wenn du allerdings ein Attribut oder eine Methode auf dem Response Objekt aufrufst, dass den gesamten Body der Response benötigt, wie z.B. .content, wird natürlich alles heruntergeladen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Sophus:
Schön, dass du es doch noch selbst erkannt hast. Der Sinn von Iteratoren ist ja gerade der, dass man nicht alles gleichzeitig im Speicher hat. Daher sind die iter_*-Methoden hier eine sinnvolle Wahl. Ich würde allerdings iter_lines() nehmen und aufhören, sobald ich die nötigen Informationen habe.

Und vielleicht mal zur Erklärung: Durch stream=True sagst du explizit, dass eine Verbindung zwar hergestellt, aber der Dateiinhalt nicht gelesen werden soll. Mit iter_content() bekommt man einen Iterator, der häppchenweise den Dateiinhalt ausliest. Das ist im Grunde wie ein f.read() auf einem Dateiobjekt. Da kämst du ja wahrscheinlich auch nicht auf die Idee, dass vorab bereits der komplette Dateiinhalt in den Speicher gelesen wird. iter_lines() ist dann wieder so ähnlich wie das zeilenweise Iterieren über ein Dateiobjekt mittels (also analog zu: for line in f). Diese Methode basiert intern auf iter_content().
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Der grobe Ablauf für die Verwendung sähe bei mir in etwa so aus:

Code: Alles auswählen

from contextlib import closing
import requests

URL = 'http://www.beispiel.de/'

def do_something(lines):
    done = False
    for line in lines:
        # ...
        if done:
            break

def main():
    with closing(requests.get(URL, stream=True)) as response:
        do_something(response.iter_lines(decode_unicode=True))
Antworten