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.
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: 17749
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: 17749
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: 17749
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.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Also, ich hab die bisherige Diskussion ja nicht verfolgt, aber ich würde genau diese Fehlermeldung erwarten:

Code: Alles auswählen

def print_data_length(symbol, year, week, data):
    ...
...
                print_data_length(data)                        <- hier taucht der nächste Fehler auf
Fehlermeldung:[codebox=bash file=Unbenannt.bsh]...
TypeError: print_data_length() takes exactly 4 arguments (1 given)[/code]
Zum Vergleich:[codebox=bash file=Unbenannt.bsh]>>> def foo(x, y):
... return x + y
...
>>> foo(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 2 arguments (1 given)[/code]
In specifications, Murphy's Law supersedes Ohm's.
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Es scheint so, daß ich eine Lösung gefunden habe. Ich habe auf dem Server unseres Hosters, welcher nur 2GB RAM zur Verfügung stellt getestet. 2 Symbole ohne Fehler heruntergeladen. Das 3 Symbol wurde nur bis 36.csv.gzip heruntergeladen und mit einem Fehler abgebrochen. Die Datenmenge für die 140 Wochen beträgt richtigerweise 1,190 GB. Ich habe nur diese Zeile verändert. data = f.read(1024). Kann ich bitte eine Erklärung bekommen, warum es jetzt anscheinend funktioniert.

Code: Alles auswählen

def print_data_length(data):
    f = gzip.GzipFile(fileobj=StringIO(data))
    data = f.read(1024)
    print(len(data))
Die Dateien für das Symbol welches die Fehlermeldung auswirft, sind die Dateien auf dem Server entweder nicht vorhanden, oder defekt.
Hier das Traceback der neuen Fehlermeldung.

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 80, in <module>
    main()
  File "TickDataCsv.py", line 75, in main
    fetch_whole_year(symbol, 2016)
  File "TickDataCsv.py", line 51, in fetch_whole_year
    data = pull_file(symbol, year, week)
  File "TickDataCsv.py", line 19, in pull_file
    response = urllib2.urlopen(url)
  File "/usr/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 437, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 550, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 475, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 409, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 558, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 404: Not Found
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@aaron: NEIN, Du hast keine Lösung gefunden, weil die Funktion ›print_data_length‹ jetzt nicht mehr das tut, was sie ursprünglich gemacht hat. Wenn Du meine Anmerkungen nicht verstehst, dann schreib das doch, dann kann ich ausführlicher Antworten. Ich schreibe extra knapp, damit niemand denkt "jetzt komm mal auf den Punkt". Nachfragen ist immer erlaubt.

Du schreibst selbst, dass die gepackten Daten 8MB, die ungepackten 100MB groß sind. Für einzelne Dateien eigentlich kein Problem mit dem Speicher. Jetzt kann es mehre Szenarien geben. Eine Datei ist gepackt nicht 8MB sondern 80MB. Und schon wirds beim Entpacken knapp mit dem Arbeitsspeicher von 2GB. Oder die Datei hat einen Fehler und aus 100MB sinnvollen Daten werden 1000MB Schrott. Das erkennt gzip an der Checksumme, aber erst, wenn die ganze Datei entpackt wurde, diese passt aber nicht in den Arbeitsspeicher, daher die Fehlermeldung.

Lösung: Blockweise Lesen:

Code: Alles auswählen

def print_data_length(data):
    f = gzip.GzipFile(fileobj=StringIO(data))
    length = 0
    while True:
        block = f.read(1024*1024)
        if not block:
            break
        length += len(block)
    print(length)
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Ich bin gerade dabei mich mit den möglichen Fehlern beim Herunterladen der
Dateien zu Beschäftigen. Wie fasse ich das Problem richtig an?

Ich habe das Script noch einmal durchlaufen lassen. Dabei sind folgende Fehler
aufgetreten.

1. Die Datei wird heruntergeladen und es wird keine Fehlermeldung ausgegeben.
Die Datei hat nur die Überschrift der Tabelle zum Inhalt. Die Dateien haben eine Größe von 52 Bytes.

2. Abbruch bei Symbol AUDJPY Kalenderwoche 36 bit folgender Fehlermeldung ab:
Hier scheint die Datei auf dem Server zu liegen, aber defekt zu sein.

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 89, in <module>
    main()
  File "TickDataCsv.py", line 86, in main
    fetch_single_week("AUDJPY", 2016, 36)
  File "TickDataCsv.py", line 46, in fetch_single_week
    print_data_length(data)
  File "TickDataCsv.py", line 29, in print_data_length
    block = f.read(1024 * 1024)
  File "/usr/lib/python2.7/gzip.py", line 268, in read
    self._read(readsize)
  File "/usr/lib/python2.7/gzip.py", line 315, in _read
    self._read_eof()
  File "/usr/lib/python2.7/gzip.py", line 354, in _read_eof
    hex(self.crc)))
IOError: CRC check failed 0x64e41a56 != 0x966a8aabL
3. Abbruch bei Symbol AUDJPY Kalenderwoche 37, 38, 39. Die Dateien scheinen nicht auf dem Server zu liegen. Ab Kalenderwoche 40 sind
die Dateien wieder da. Die Fehlermeldung taucht erneut auf, wenn die
Kalenderwoche in der Zukunft liegt.

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 89, in <module>
    main()
  File "TickDataCsv.py", line 86, in main
    fetch_single_week("AUDJPY", 2016, 37)
  File "TickDataCsv.py", line 45, in fetch_single_week
    data = pull_file(symbol, year, week)
  File "TickDataCsv.py", line 19, in pull_file
    response = urllib2.urlopen(url)
  File "/usr/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 437, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 550, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 475, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 409, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 558, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 404: Not Found
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Ich bin gerade dabei das Logging zum Laufen zu bringen. Ich habe 2 Funktionen geschrieben. Leider sind diese Fehlerhaft. Vielleicht ist die Idee falsch?
Hier erst einmal meine Fragen:
1. Ist die Idee richtig, das Logging in eine Funktion zu schreiben?
2. Ich möchte, daß einmal täglich ein Logfile erstellt wird, deshalb die Funktion generate_logfile.

Code: Alles auswählen

def generate_logfile():
    return os.path.join("log", str(datetime.date.today())+'.log')

def initialize_logger():
logger = logging.getLogger('Data warehouse')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(generate_logfile)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
Hier die Fehlermeldung dazu: Ich verstehe, was mir die Fehlermeldung sagt, aber wie und wo baue ich den indented block ein?

Code: Alles auswählen

Traceback (most recent call last):
  File "TickDataCsv.py", line 108, in <module>
    main()
  File "TickDataCsv.py", line 101, in main
    initialize_logger(log)
NameError: global name 'initialize_logger' is not defined
wp10750459@vwp7837:~/www/dwh$ vi TickDataCsv.py
wp10750459@vwp7837:~/www/dwh$ python TickDataCsv.py
  File "TickDataCsv.py", line 16
    logger = logging.getLogger('Data warehouse')
         ^
IndentationError: expected an indented block
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Meine Lösung sieht nun so aus. Es scheint zu funktionieren. Bitte schaut einmal drüber. Wie bringe ich das Logging und das exception handling zusammen? Wie baue ich die Meldung logger.error('error message') in die entsprechenden Funktionen ein? Ist die Idee richtig?

Code: Alles auswählen

def initialize_logger():
    logger = logging.getLogger('Data warehouse')
    logger.setLevel(logging.DEBUG)
    newpath = 'log'                                  <=== ist die Zeile eine gute Idee
    if not os.path.exists(newpath):
        os.makedirs(newpath)
        handler = logging.FileHandler(os.path.join("log", str(datetime.date.today())+'.log'))
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        
         # 'application' code
        logger.debug('debug message')
        logger.info('info message')
        logger.warn('warn message')
        logger.error('error message')
        logger.critical('critical message')
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

ich möchte

Code: Alles auswählen

handler = logging.FileHandler('log/2017-4-4.log') durch 
handler = logging.Filehandler("log", str(datetime.date.today()), '{}.log') ersetzen
Was mache ich falsch?
BlackJack

@aaron: 1. Schreibst Du `FileHandler` mit einem kleinen 'h'.

2.: Übergibst Du drei Argumente die so von `FileHandler` nicht erwartet werden:

Code: Alles auswählen

In [6]: logging.FileHandler("log", str(datetime.date.today()), '{}.log')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-e702480fc016> in <module>()
----> 1 logging.FileHandler("log", str(datetime.date.today()), '{}.log')

/usr/lib/python2.7/logging/__init__.pyc in __init__(self, filename, mode, encoding, delay)                                                                      
    895             self.stream = None
    896         else:
--> 897             StreamHandler.__init__(self, self._open())
    898 
    899     def close(self):

/usr/lib/python2.7/logging/__init__.pyc in _open(self)
    916             stream = open(self.baseFilename, self.mode)
    917         else:
--> 918             stream = codecs.open(self.baseFilename, self.mode, self.encoding)
    919         return stream
    920 

/usr/lib/python2.7/codecs.pyc in open(filename, mode, encoding, errors, buffering)
    879             # Force opening of the file in binary mode
    880             mode = mode + 'b'
--> 881     file = __builtin__.open(filename, mode, buffering)
    882     if encoding is None:
    883         return file

ValueError: mode string must begin with one of 'r', 'w', 'a' or 'U', not '2017-04-04b'
Das sieht extrem nach herum raten aus in dem Du wahllos Teile aus Code zusammenkopierst der etwas ähnliches macht(e) in der Hoffnung das da schon irgendwie etwas sinnvolles heraus kommt. So funktioniert programmieren aber nicht. Hier fehlen absolute Grundlagen über Zeichenketten und Funktionsaufrufe. Was hat Dich denn dazu gebracht diese drei Argumente zu übergeben? Welche Argumente erwartet `FileHandler` denn? Und welche übergibst Du und was denkst Du bedeutet das dann?
aaron
User
Beiträge: 92
Registriert: Donnerstag 1. Dezember 2016, 23:10

Vielen Dank für die Antwort.
1. Ich möchte erstens überprüfen, ob ein Verzeichnis log schon existiert und wenn nein, dann soll ein Verzeichnis erstellt werden.
2. Ich möchte das jede Datei das Datum des aktuellen Tages trägt.

Aus der Python Dokumentation ist folgendes zu lesen.
class logging.FileHandler(filename, mode='a', encoding=None, delay=False)
Das heißt, daß ich als erstes Argument den Filenamen übergeben muß. Wenn ich das richtig verstehe, dann muß ich vorher den Filenamen als Strin zusammen bauen und dann an FileHandler übergeben.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

aaron hat geschrieben:Das heißt, daß ich als erstes Argument den Filenamen übergeben muß. Wenn ich das richtig verstehe, dann muß ich vorher den Filenamen als Strin zusammen bauen und dann an FileHandler übergeben.
Das ist korrekt, es wird genau ein String mit dem Dateinamen erwartet und nicht drei. Vielleicht hast du mal bei print gesehen, dass man einfach mehrere Strings mit Kommas getrennt angeben kann und dann werden sie quasi zu einem zusammengefügt. Das funktioniert aber nicht überall.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten