Dateien herunterladen und in einem Verzeichnis ablegen

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.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Hallo ich bin immer noch nicht weiter mit dem error handling. Nach stundenlangem Googeln und ausprobieren habe ich kein brauchbares Ergebnis. Ich brauche dringend einen Tip. Welche Funktion(en) muß ich anfassen? Danke für die Hilfe
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Auch wenn ihr den Speicherfehler beim Herunterladen der Dateien nicht bestätigt habt, so würde ich gern das Shutil Modul verwenden.

Ich habe in der folgenden Funktion die letzte Zeile verändert. von output.write(data) nach shutil.copyfileobj(data, output). Leider ohne Erfolg. Habe die Dokumentation und Google schon mehrfach danach befragt. Ich kann die Fehlermeldung nicht richtig deuten. Schaut euch bitte das Traceback einmal an.

Code: Alles auswählen

def save_file(symbol, year, week, data):
    filename = generate_filename(symbol, year, week)
    print(filename)
    if not os.path.exists(os.path.dirname(filename)):
        os.makedirs(os.path.dirname(filename))
    with open(filename, "wb") as output:
        shutil.copyfileobj(data, output)
Hier die Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv27.py", line 82, in <module>
    main()
  File "TickDataCsv27.py", line 79, in main
    fetch_single_week("EURUSD", 2017, 11)
  File "TickDataCsv27.py", line 44, in fetch_single_week
    save_file(symbol, year, week, data)
  File "TickDataCsv27.py", line 37, in save_file
    shutil.copyfileobj(data, output)
  File "/usr/lib64/python2.7/shutil.py", line 49, in copyfileobj
    buf = fsrc.read(length)
AttributeError: 'str' object has no attribute 'read'
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@aaron: copyfileobj kopiert von einem File-Objekt in ein anderes, also z.B. von dem Objekt das urlopen zurückgibt, zu Deinem Dateiobjekt.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

copy, copy2, copytree, copyfileobj, move ausprobiert. Leider ohne Erfolg. Vielleicht verwende ich shutil auch an der falschen Stelle?
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Hier habe ich einen neuen Ansatz. Leider funktioniert auch dieser nicht richtig. shutil.copy(filename, "data") ist auch falsch. shutil.copy(filename, "filenme") funktioniert auch nicht.

Code: Alles auswählen

def save_file(symbol, year, week, data):
    filename = generate_filename(symbol, year, week)
    print(filename)
    if not os.path.exists(os.path.dirname(filename)):
        os.makedirs(os.path.dirname(filename))
    with open(filename, "wb") as output:
        shutil.copy(filename, "data")
        output.write(data)
BlackJack

@aaron: Solange Du die Datei vor dem Speichern auf Festplatte zweimal lesen musst/willst wird Dir das eh nicht helfen, denn solange musst Du die Datei auf jeden Fall einmal komplett im Speicher halten damit die Daten ein zweites mal gelesen werden können, weil ein Dateiobjekt das die Daten aus dem Netz überträgt nicht ”seekable” ist. Das könntest Du nur umgehen wenn Du kopieren von Netz auf Platte und ermitteln der unkomprimierten Grösse in einem Schritt machst, was aber eigenen Code erfordert der ein kleines bisschen komplizierter wird als das was Du bis jetzt da machst.

Ich sehe bei den Dateigrössen auch keinen Grund warum das zu einem `MemoryError` führen sollte. Selbst bei einem 32-Bit-Python dürfte das nicht passieren, bei einem 64-Bit-Python und dem RAM schon gar nicht.

Wo Du bei der Verarbeitung Speicher sparen kannst ist beim ermitteln der unkomprimierten Grösse. Das musst Du aber selber ausprogrammieren, denn da wird ja nicht einfach nur kopiert, da musst Du die Längen der Blöcke aufsummieren.

Edit: Am einfachsten vom Code her wäre es wohl `shutil` zu verwenden um direkt von dem `Response`-Dateiobjekt in ein Dateiobjekt auf Platte kopieren zu lassen und zur Bestimmung der unkomprimierten Grösse die Datei erneut, blockweise, von Platte zu lesen. Bei dem RAM den Du hast dürfte das lesen ”von Platte” auf ein lesen aus dem Cache hinauslaufen, also auch keinen grossen Geschwindigkeitsverlust bedeuten.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Danke für die schnelle Antwort. Wie fasse ich das Problem am besten an? Könntest du mir ein wenig unter die Arme greifen? In welcher Funktion soll ich shutil verwenden? Das ist mit Sicherheit absolut logisch und richtig was du vorschlägst, nur mit der Umsetzung hapert es hier.
Edit: Am einfachsten vom Code her wäre es wohl `shutil` zu verwenden um direkt von dem `Response`-Dateiobjekt in ein Dateiobjekt auf Platte kopieren zu lassen und zur Bestimmung der unkomprimierten Grösse die Datei erneut, blockweise, von Platte zu lesen. Bei dem RAM den Du hast dürfte das lesen ”von Platte” auf ein lesen aus dem Cache hinauslaufen, also auch keinen grossen Geschwindigkeitsverlust bedeuten.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Bitte schaut Euch noch einmal meinen Versuch shutil zum Laufen zu bringen an. Es funktioniert nicht. Ich bin für jede Hilfe dankbar. Baue ich das Modul in der richtigen Funktion ein? Ich habe hier ein Verständnisproblem, trotz lesen der Dokumentation.

Code: Alles auswählen

def save_file(symbol, year, week, data):
    filename = generate_filename(symbol, year, week)
    print(filename)
    url = URL_TEMPLATE.format(symbol, year, week)
    response = requests.get(url, stream = True)
    if not os.path.exists(os.path.dirname(filename)):
        os.makedirs(os.path.dirname(filename))
    with open(filename, "wb") as output:
        response.raw.decode_content = True
        shutil.copyfileobj(response.raw, output)
        output.write(data)
Zuletzt geändert von Anonymous am Donnerstag 30. März 2017, 12:48, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

Ich finde die Funktion überladen, warum rufst du dort Daten ab? Die Funktion save_file sollte meines Erachtens nur (!) das Speichern als Datei erledigen und den Rest als Parameter bekommen haben. Kannst Du mal die Fehlermeldung posten, das wäre m. E. sehr hilfreich.

Vollständiges Tool unter
https://github.com/joergklein/Datawareh ... DataCsv.py

Ich finde das Tool dort leichter zu lesen. Hast Du mal versucht ganz auf shutil zu verzichten? Ich nutze es nur zum Verschieben oder Kopieren von Dateien, nicht aber im Download von csv-Files...
BlackJack

@pixewakb: Ja hat er versucht, und es sind *grosse* Dateien, deswegen kann ja der Tipp von hier die nicht komplett in den Speicher herunter zu laden sondern beim herunterladen schon in eine Datei zu speichern. Das kann man nun selbst schreiben, oder man nimmt `shutil` dafür weil das Problem da ja schon mal gelöst wurde.
Benutzeravatar
pixewakb
User
Beiträge: 1412
Registriert: Sonntag 24. April 2011, 19:43

Für mich liest sich folgender Abschnitt im Code:

Code: Alles auswählen

with open(filename, "wb") as output:
        response.raw.decode_content = True
        shutil.copyfileobj(response.raw, output)
        output.write(data)
so, als würde er die Aufgabe einmal mit with open... und einmal mit shutil zu lösen versuchen. Ich habe beide Sachen selbst noch nie kombiniert? csv-files downloaden und abspeichern mache ich "häufig", aber die Dateien sind kleiner und es gibt eine fertige Funktion, die das für mich löst. Ich meine, dass da pandas verbaut wäre (habe ich nicht programmiert, habe ich mir noch nicht genauer angesehen).

Als ich das noch selbst gemacht habe, dann habe ich das mittels requests geladen und dann direkt als string-Daten auf die Festplatte gepackt. Das ist jetzt aber sicher mehr als ein Jahr her.
BlackJack

@pixewakb: Du musst die Ausgabedatei ja mit `open()` öffnen — wie willst Du sonst die Zieldatei erstellen? Also braucht man `open()` *und* `shutil`.

Da ist natürlich offensichtlich noch ein Quelltextfragment enthalten das keinen Sinn (mehr) macht, aber auch zu einer sehr eindeutigen Fehlermeldung führen wird, die eigentlich durch nachdenken sehr schnell dazu führen sollte, das man erkennt, das das keinen Sinn (mehr) macht. Vermutlich wird aber davor noch ein anderer Fehler auftreten. „Es funktioniert nicht.“ ist mir aber zu vage als Fehlerbeschreibung das ich da jetzt anfangen wollte zu raten.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Vielen Dank für die Rückmeldung. Wie gesagt, die ersten Dateien sind wieder ohne Probleme heruntergeladen worden. Ich habe jetzt noch einmal die Funktion umgeschrieben. Diese sieht jetzt so aus:

Code: Alles auswählen

def save_file(symbol, year, week, data):
    filename = generate_filename(symbol, year, week)
    print(filename)
    url = URL_TEMPLATE.format(symbol, year, week)
    response = requests.get(url, stream = True)
    if not os.path.exists(os.path.dirname(filename)):
        os.makedirs(os.path.dirname(filename))
        response.raw.decode_content = True
        shutil.copyfileobj(response.raw, data)
        output.write(data)
Das ist die Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 85, in <module>
    main()
  File "TickDataCsv.py", line 80, in main
    fetch_whole_year(symbol, 2016)
  File "TickDataCsv.py", line 57, in fetch_whole_year
    print_data_length(data)
  File "TickDataCsv.py", line 29, in print_data_length
    data = f.read()
  File "/usr/lib/python2.7/gzip.py", line 261, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 320, in _read
    self._add_read_data( uncompress )
  File "/usr/lib/python2.7/gzip.py", line 338, in _add_read_data
    self.extrabuf = self.extrabuf[offset:] + data
MemoryError
Zuletzt geändert von Anonymous am Samstag 1. April 2017, 16:44, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@aaron: Der Traceback passt nicht nur nicht zur gezeigten `save_file()`-Funktion, denn die kommt dort überhaupt nicht vor — die `save_file()`-Funktion führt auch zu fehlern, aber anderen. Das heisst die wird gar nicht verwendet wenn diese Ausnahmen nicht kommen.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Hier noch einmal die ursprüngliche Funktion, ohne die Änderung. Gebt mir doch bitte einmal einen Tip. Vielleicht suche ich auch in der falschen Funktion.

Code: Alles auswählen

def save_file(symbol, year, week, data):
    filename = generate_filename(symbol, year, week)
    print(filename)
    if not os.path.exists(os.path.dirname(filename)):
        os.makedirs(os.path.dirname(filename))
    with open(filename, "wb") as output:
        output.write(data)
Hier das Traceback

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 82, in <module>
    main()
  File "TickDataCsv.py", line 77, in main
    fetch_whole_year(symbol, 2016)
  File "TickDataCsv.py", line 54, in fetch_whole_year
    print_data_length(data)
  File "TickDataCsv.py", line 29, in print_data_length
    data = f.read()
  File "/usr/lib/python2.7/gzip.py", line 261, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 320, in _read
    self._add_read_data( uncompress )
  File "/usr/lib/python2.7/gzip.py", line 338, in _add_read_data
    self.extrabuf = self.extrabuf[offset:] + data
MemoryError
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@aaron: einen Traceback liest man am besten von unten nach oben, so lange, bis man ein eine Datei kommt, die man selbst geschrieben hat. Dort ist dann mit großer Wahrscheinlichkeit der Fehler zu finden, mit Zeilennummer und Funktion:

Code: Alles auswählen

  File "TickDataCsv.py", line 29, in print_data_length
    data = f.read()
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Ich habe die Funktion jetzt umgeschrieben. Sollte diese jetzt richtig sein, dann taucht der nächste Fehler in Zeile 55 auf.

Code: Alles auswählen

def print_data_length(symbol, year, week, data):
    url = URL_TEMPLATE.format(symbol, year, week)
    response = requets.get(url, stream = True)
    response.raw.decode_content = True
    shutil.copyfileobj(response.raw, data)
    print(len(data))

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 83, in <module>
    main()
  File "TickDataCsv.py", line 78, in main
    fetch_whole_year(symbol, 2016)
  File "TickDataCsv.py", line 55, in fetch_whole_year
    print_data_length(data)
TypeError: print_data_length() takes exactly 4 arguments (1 given)

Code: Alles auswählen

def fetch_whole_year(symbol, year):
    for symbol in symbol:
        last_week = datetime.date(year, 12, 31).isocalendar()[1]
        for week in range(1, last_week + 1):
            if not exists_file(symbol, year, week):
                data = pull_file(symbol, year, week)
                print_data_length(data)                        <- hier taucht der nächste Fehler auf
                save_file(symbol, year, week, data)
            else:
                print("File for {}/{}/{} already fetched.".format(symbol, year,»

Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@aaron: statt Dein Problem genau zu analysieren und zu verstehen, wo der Fehler liegt, fängst Du an, wild herumzuraten und zu denken, eine Lösung (shutil.copyfileobj) würde für alles eine Lösung sein.

Schau doch nochmal, was die Funktion › print_data_length‹ eigentlich macht, und wo das wirkliche Problem liegt. Nicht das Laden verursacht einen MemoryError, sondern das Entpacken.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Ich verstehe es einfach nicht.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@aaron: das hatten wir doch schon einmal. Man schreibt Funktionen, die eine bestimmte Aufgabe erledigen. ›pull_file‹ lädt eine Datei herunter. Macht die Funktion das, was sie soll? Ja. ›save_file‹ speichert die Datei in einem Verzeichnis, das eventuell noch erstellt werden muß. Macht die Funktion das, was sie soll? Ja. ›print_data_length‹ soll die Daten entpacken und deren Länge ausgeben. Macht die Funktion das, was sie soll? Im Prinzip Ja, aber es kommt hin und wieder zu einem MemoryError. Und so hart das klingt, deshalb macht die Funktion nicht das was sie soll. Wenn nur ein Fall von einer Million nicht das tut, was er soll, dann ist die Funktion fehlerhaft.
Das heißt jetzt aber nicht, dass man wieder alles über den Haufen schmeißen muß. Sondern man muß sich nur diese eine Funktion anschauen und sich Gedanken darüber machen, was nicht passt. Konzentriere Dich auf das eine, was die Funktion macht. Also nicht auf das Herunterladen, nicht auf das Speichern, sondern nur auf das Entpacken. Hier führt also das Lesen an einem Stück zu einem MemoryError, weil die Entpackten Daten größer sind als erwartet. Das kann an einem Fehler in den gepackten Daten liegen, oder die Datei ist einfach so groß. Lösung: Blockweise Lesen.
Antworten