2012-08-23 17 views
7

Jestem pobierania plików za pośrednictwem protokołu HTTP i wyświetlania postępy za pomocą urllib i następujący kod - który działa dobrze:Anuluj powolne pobieranie w python

import sys 
from urllib import urlretrieve 

urlretrieve('http://example.com/file.zip', '/tmp/localfile', reporthook=dlProgress) 

def dlProgress(count, blockSize, totalSize): 
    percent = int(count*blockSize*100/totalSize) 
    sys.stdout.write("\r" + "progress" + "...%d%%" % percent) 
    sys.stdout.flush() 

Teraz chciałbym również, aby ponownie uruchomić pobieranie, jeśli to będzie zbyt wolno (powiedz mniej niż 1 MB w 15 sekund). Jak mogę to osiągnąć?

+1

Możesz zgłosić wyjątek w swoim raporcie. – Tobold

+1

Tak, podniesienie wyjątku wydaje się być popularnym sposobem na przerwanie pobierania, od szybkiego spojrzenia na Google. Nie jest to jednak wspomniane w dokumentacji, co niepokoi mnie, że może mieć nieoczekiwane zachowanie. Na przykład, być może dane są pobierane przez dedykowany wątek, a wyrzucenie wyjątku sprawi, że stanie się sierotą, a właściwie nie zatrzyma pobierania. – Kevin

Odpowiedz

4

To powinno zadziałać. Oblicza rzeczywistą szybkość pobierania i przerywa, jeśli jest zbyt niska.

import sys 
from urllib import urlretrieve 
import time 

url = "http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz" # 14.135.620 Byte 
startTime = time.time() 

class TooSlowException(Exception): 
    pass 

def convertBToMb(bytes): 
    """converts Bytes to Megabytes""" 
    bytes = float(bytes) 
    megabytes = bytes/1048576 
    return megabytes 


def dlProgress(count, blockSize, totalSize): 
    global startTime 

    alreadyLoaded = count*blockSize 
    timePassed = time.time() - startTime 
    transferRate = convertBToMb(alreadyLoaded)/timePassed # mbytes per second 
    transferRate *= 60 # mbytes per minute 

    percent = int(alreadyLoaded*100/totalSize) 
    sys.stdout.write("\r" + "progress" + "...%d%%" % percent) 
    sys.stdout.flush() 

    if transferRate < 4 and timePassed > 2: # download will be slow at the beginning, hence wait 2 seconds 
     print "\ndownload too slow! retrying..." 
     time.sleep(1) # let's not hammer the server 
     raise TooSlowException 

def main(): 
    try: 
     urlretrieve(url, '/tmp/localfile', reporthook=dlProgress) 

    except TooSlowException: 
     global startTime 
     startTime = time.time() 
     main() 

if __name__ == "__main__": 
    main() 
+0

Niezła, właśnie to, czego potrzebowałem, dziękuję. –

+0

Pamiętaj, że działa to tylko w przypadku zwalnianego połączenia. Bardziej typowe porzucone połączenie nie zadziała, dopóki nie dodasz limitu czasu do gniazda. W przeciwnym razie - OK! +1 –

3

coś takiego:

class Timeout(Exception): 
    pass 

def try_one(func,t=3): 
    def timeout_handler(signum, frame): 
     raise Timeout() 

    old_handler = signal.signal(signal.SIGALRM, timeout_handler) 
    signal.alarm(t) # triger alarm in 3 seconds 

    try: 
     t1=time.clock() 
     func() 
     t2=time.clock() 

    except Timeout: 
     print('{} timed out after {} seconds'.format(func.__name__,t)) 
     return None 
    finally: 
     signal.signal(signal.SIGALRM, old_handler) 

    signal.alarm(0) 
    return t2-t1 

Wezwanie 'try_one' z func chcesz limit czasu, a czas do timeout:

try_one(downloader,15) 

OR, można to zrobić:

import socket 
socket.setdefaulttimeout(15) 
+1

To dobre rozwiązanie, jeśli pobierasz małe pliki o znanym rozmiarze. Jeśli nie znasz rozmiaru z wyprzedzeniem, nie będziesz wiedział, ile sekund przejść na "try_one". A jeśli pobierasz plik 100 MB, 'try_one (downloader, 1500)' nie poddaje się, dopóki nie minie 1500 sekund. Najlepiej byłoby zakończyć pracę, gdy tylko uzyska pewność, że pobieranie nie zakończy się na czas. – Kevin

+0

Tak, zgodzili się. Dzięki za rozwiązanie, ale chciałbym anulować na podstawie minimalnego progu przepustowości, nie w tym, czy pobieranie zostało ukończone w określonym czasie. –

+0

@HolyMackerel: Po prostu zmodyfikuj hak raportu, aby uzyskać limit czasu w odstępach 10-sekundowych i sprawdź stawkę. Problem polega na zawieszeniu pobierania, gdzie 0 bajtów jest xfered i twój hak raportu nigdy nie jest wywoływany. –

0

HolyMackerel! Użyj narzędzi!

import urllib2, sys, socket, time, os 

def url_tester(url = "http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz"): 
    file_name = url.split('/')[-1] 
    u = urllib2.urlopen(url,None,1)  # Note the timeout to urllib2... 
    file_size = int(u.info().getheaders("Content-Length")[0]) 
    print ("\nDownloading: {} Bytes: {:,}".format(file_name, file_size)) 

    with open(file_name, 'wb') as f:  
     file_size_dl = 0 
     block_sz = 1024*4 
     time_outs=0 
     while True:  
      try: 
       buffer = u.read(block_sz) 
      except socket.timeout: 
       if time_outs > 3: # file has not had activity in max seconds... 
        print "\n\n\nsorry -- try back later" 
        os.unlink(file_name) 
        raise 
       else:    # start counting time outs... 
        print "\nHmmm... little issue... I'll wait a couple of seconds" 
        time.sleep(3) 
        time_outs+=1 
        continue 

      if not buffer: # end of the download    
       sys.stdout.write('\rDone!'+' '*len(status)+'\n\n') 
       sys.stdout.flush() 
       break 

      file_size_dl += len(buffer) 
      f.write(buffer) 
      status = '{:20,} Bytes [{:.2%}] received'.format(file_size_dl, 
              file_size_dl * 1.0/file_size) 
      sys.stdout.write('\r'+status) 
      sys.stdout.flush() 

    return file_name 

Umożliwia wydrukowanie statusu zgodnie z oczekiwaniami. Gdybym odłączyć mój kabel ethernet, otrzymuję:

Downloading: Python-2.7.3.tgz Bytes: 14,135,620 
      827,392 Bytes [5.85%] received 


sorry -- try back later 

gdybym odłączyć kabel, a następnie podłącz go z powrotem w czasie krótszym niż 12 sekund, otrzymuję:

Downloading: Python-2.7.3.tgz Bytes: 14,135,620 
      716,800 Bytes [5.07%] received 
Hmmm... little issue... I'll wait a couple of seconds 

Hmmm... little issue... I'll wait a couple of seconds 
Done! 

Plik został pomyślnie pobrane.

Możesz zobaczyć, że urllib2 obsługuje zarówno limity czasu, jak i ponowne połączenia. Jeśli rozłączysz się i pozostaniesz rozłączony przez 3 * 4 sekundy == 12 sekund, to na jakiś czas przestanie działać i wzniesie fatalny wyjątek. Można to również rozwiązać.

+0

Dzięki, jest to dobre rozwiązanie, ale pobiera raczej pliki zatrzymane niż powolne pobieranie. –