2009-12-28 5 views
10

Come si cerca una determinata posizione su un file remoto (HTTP) in modo che sia possibile scaricare solo quella parte?Ricerca Python su file remoto tramite HTTP

Diciamo che i byte su un file remoto sono stati: 1234567890

voglio cercare di 4 e scaricare 3 byte da lì così avrei: 456

e anche, come faccio a controllare se un telecomando il file esiste? Ho provato, os.path.isfile() ma restituisce False quando sto passando un url di file remoto.

+2

cosa intendi per "remoto"? –

+0

Che protocollo stai usando? HTTP? FTP? NFS? SFTP? –

+0

tramite remote, intendo http – Marconi

risposta

15

Se si sta scaricando il file remoto tramite HTTP, è necessario impostare l'intestazione Range.

Verificare in this example come può essere fatto. Sembra che questo:

myUrlclass.addheader("Range","bytes=%s-" % (existSize)) 

EDIT: I just found a better implementation. Questa classe è molto semplice da usare, come si può vedere nella docstring.

class HTTPRangeHandler(urllib2.BaseHandler): 
"""Handler that enables HTTP Range headers. 

This was extremely simple. The Range header is a HTTP feature to 
begin with so all this class does is tell urllib2 that the 
"206 Partial Content" reponse from the HTTP server is what we 
expected. 

Example: 
    import urllib2 
    import byterange 

    range_handler = range.HTTPRangeHandler() 
    opener = urllib2.build_opener(range_handler) 

    # install it 
    urllib2.install_opener(opener) 

    # create Request and set Range header 
    req = urllib2.Request('http://www.python.org/') 
    req.header['Range'] = 'bytes=30-50' 
    f = urllib2.urlopen(req) 
""" 

def http_error_206(self, req, fp, code, msg, hdrs): 
    # 206 Partial Content Response 
    r = urllib.addinfourl(fp, hdrs, req.get_full_url()) 
    r.code = code 
    r.msg = msg 
    return r 

def http_error_416(self, req, fp, code, msg, hdrs): 
    # HTTP's Range Not Satisfiable error 
    raise RangeError('Requested Range Not Satisfiable') 

Aggiornamento: Il "migliore attuazione" si è spostato github: excid3/urlgrabber nel file byterange.py.

+0

+1 per l'aggiornamento con una migliore implementazione. –

+0

proprio quello di cui avevo bisogno. Grazie. – Marconi

1

Penso che la chiave della tua domanda sia che hai detto "URL file remoto". Ciò implica che stai utilizzando un URL HTTP per scaricare un file con un'operazione HTTP "get".

Così ho appena fatto una ricerca su Google per "HTTP get" e ho trovato questo per voi:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35

Sembra che è possibile specificare un intervallo di byte in un HTTP GET.

Quindi, è necessario utilizzare una libreria HTTP che consente di specificare l'intervallo di byte. E mentre stavo scrivendo questo, jbochi ha pubblicato un link ad un esempio.

4

AFAIK, questo non è possibile utilizzando fseek() o simile. È necessario utilizzare l'intestazione Range HTTP per ottenere ciò. Questa intestazione può o non può essere supportata dal server, quindi il tuo chilometraggio può variare.

import urllib2 

myHeaders = {'Range':'bytes=0-9'} 

req = urllib2.Request('http://www.promotionalpromos.com/mirrors/gnu/gnu/bash/bash-1.14.3-1.14.4.diff.gz',headers=myHeaders) 

partialFile = urllib2.urlopen(req) 

s2 = (partialFile.read()) 

EDIT: Questo è naturalmente assumendo che da file remoto si intende un file memorizzato su un server HTTP ...

Se il file che si desidera si trova su un server FTP, FTP consente solo di a specificare un offsete non un intervallo. Se questo è ciò che si vuole, quindi il seguente codice dovrebbe farlo (non testato!)

import ftplib 
fileToRetrieve = 'somefile.zip' 
fromByte = 15 
ftp = ftplib.FTP('ftp.someplace.net') 
outFile = open('partialFile', 'wb') 
ftp.retrbinary('RETR '+ fileToRetrieve, outFile.write, rest=str(fromByte)) 
outFile.close() 
+0

È inoltre necessario trattare i 206 codici di risposta, in quanto potrebbero essere accettabili se si utilizza l'intestazione dell'intervallo HTTP. – jbochi

+0

Abbastanza giusto. La tua risposta è così :) –

5

Mi consiglia di utilizzare la libreria requests. È facilmente la migliore libreria HTTP che abbia mai usato. In particolare, per compiere quello che hai descritto, si potrebbe fare qualcosa di simile:

import requests 

url = "http://www.sffaudio.com/podcasts/ShellGameByPhilipK.Dick.pdf" 

# Retrieve bytes between offsets 3 and 5 (inclusive). 
r = requests.get(url, headers={"range": "bytes=3-5"}) 

# If a 4XX client error or a 5XX server error is encountered, we raise it. 
r.raise_for_status() 
+0

Al momento non esisteva alcuna libreria di richieste, ma sì, questo rende le cose più semplici ora. – Marconi

0

non ho trovato alcun implementazioni esistenti di un'interfaccia simile a file con la ricerca() per URL HTTP, così ho arrotolato la mia semplice versione: https://github.com/valgur/pyhttpio.Dipende da urllib.request ma potrebbe essere facilmente modificato per utilizzare requests, se necessario.

il codice completo:

import cgi 
import time 
import urllib.request 
from io import IOBase 
from sys import stderr 


class SeekableHTTPFile(IOBase): 
    def __init__(self, url, name=None, repeat_time=-1, debug=False): 
     """Allow a file accessible via HTTP to be used like a local file by utilities 
     that use `seek()` to read arbitrary parts of the file, such as `ZipFile`. 
     Seeking is done via the 'range: bytes=xx-yy' HTTP header. 

     Parameters 
     ---------- 
     url : str 
      A HTTP or HTTPS URL 
     name : str, optional 
      The filename of the file. 
      Will be filled from the Content-Disposition header if not provided. 
     repeat_time : int, optional 
      In case of HTTP errors wait `repeat_time` seconds before trying again. 
      Negative value or `None` disables retrying and simply passes on the exception (the default). 
     """ 
     super().__init__() 
     self.url = url 
     self.name = name 
     self.repeat_time = repeat_time 
     self.debug = debug 
     self._pos = 0 
     self._seekable = True 
     with self._urlopen() as f: 
      if self.debug: 
       print(f.getheaders()) 
      self.content_length = int(f.getheader("Content-Length", -1)) 
      if self.content_length < 0: 
       self._seekable = False 
      if f.getheader("Accept-Ranges", "none").lower() != "bytes": 
       self._seekable = False 
      if name is None: 
       header = f.getheader("Content-Disposition") 
       if header: 
        value, params = cgi.parse_header(header) 
        self.name = params["filename"] 

    def seek(self, offset, whence=0): 
     if not self.seekable(): 
      raise OSError 
     if whence == 0: 
      self._pos = 0 
     elif whence == 1: 
      pass 
     elif whence == 2: 
      self._pos = self.content_length 
     self._pos += offset 
     return self._pos 

    def seekable(self, *args, **kwargs): 
     return self._seekable 

    def readable(self, *args, **kwargs): 
     return not self.closed 

    def writable(self, *args, **kwargs): 
     return False 

    def read(self, amt=-1): 
     if self._pos >= self.content_length: 
      return b"" 
     if amt < 0: 
      end = self.content_length - 1 
     else: 
      end = min(self._pos + amt - 1, self.content_length - 1) 
     byte_range = (self._pos, end) 
     self._pos = end + 1 
     with self._urlopen(byte_range) as f: 
      return f.read() 

    def readall(self): 
     return self.read(-1) 

    def tell(self): 
     return self._pos 

    def __getattribute__(self, item): 
     attr = object.__getattribute__(self, item) 
     if not object.__getattribute__(self, "debug"): 
      return attr 

     if hasattr(attr, '__call__'): 
      def trace(*args, **kwargs): 
       a = ", ".join(map(str, args)) 
       if kwargs: 
        a += ", ".join(["{}={}".format(k, v) for k, v in kwargs.items()]) 
       print("Calling: {}({})".format(item, a)) 
       return attr(*args, **kwargs) 

      return trace 
     else: 
      return attr 

    def _urlopen(self, byte_range=None): 
     header = {} 
     if byte_range: 
      header = {"range": "bytes={}-{}".format(*byte_range)} 
     while True: 
      try: 
       r = urllib.request.Request(self.url, headers=header) 
       return urllib.request.urlopen(r) 
      except urllib.error.HTTPError as e: 
       if self.repeat_time is None or self.repeat_time < 0: 
        raise 
       print("Server responded with " + str(e), file=stderr) 
       print("Sleeping for {} seconds before trying again".format(self.repeat_time), file=stderr) 
       time.sleep(self.repeat_time) 

Un piccolo esempio di utilizzo:

url = "https://www.python.org/ftp/python/3.5.0/python-3.5.0-embed-amd64.zip" 
f = SeekableHTTPFile(url, debug=True) 
zf = ZipFile(f) 
zf.printdir() 
zf.extract("python.exe") 

Edit: V'è in realtà un gran parte identico, se leggermente più minimale, attuazione in questa risposta: https://stackoverflow.com/a/7852229/2997179