2009-12-28 3 views
10

Как мне искать конкретную позицию в удаленном (HTTP) файле, чтобы я мог загрузить только эту часть?Python ищет удаленный файл, используя HTTP

Допустим, байты на удаленном файле были: 1234567890

Я хочу обратиться к 4 и скачать 3 байта оттуда, так что я бы: 456

, а также, как я могу проверить, если удаленный Файл существует? Я пробовал, os.path.isfile(), но он возвращает False, когда я передаю удаленный файл url.

+2

что вы подразумеваете под «дистанционным»? –

+0

Какой протокол вы используете? HTTP? FTP? NFS? SFTP? –

+0

удаленно я имею в виду http – Marconi

ответ

15

Если вы загружаете удаленный файл через HTTP, вам необходимо установить заголовок Range.

Проверить in this example как это сделать. Похоже, это:

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

EDIT: I just found a better implementation. Этот класс очень прост в использовании, как это видно в 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') 

Update: "лучше реализация" переместилась в github: excid3/urlgrabber в файле byterange.py.

+0

+1 для обновления с улучшенной реализацией. –

+0

только то, что мне нужно. Благодарю. – Marconi

1

Я думаю, что ключ к вашему вопросу заключается в том, что вы сказали «удаленный файл url». Это означает, что вы используете HTTP-URL для загрузки файла с помощью операции HTTP get.

Так что я просто сделал поиск Google для «HTTP GET», и я нашел, что это для вас:

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

Похоже, вы можете указать диапазон байтов в HTTP GET.

Итак, вам нужно использовать библиотеку HTTP, которая позволяет указать диапазон байтов. И когда я печатал это, jbochi разместил ссылку на пример.

4

AFAIK, это невозможно с помощью fseek() или аналогичного. Для этого вам нужно использовать заголовок HTTP Range. Этот заголовок может поддерживаться или не поддерживаться сервером, поэтому ваш пробег может отличаться.

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: Это, конечно, при условии, что с помощью удаленного файла вы имеете в виду файл, хранящийся на сервере HTTP ...

Если файл, который вы хотите, на FTP-сервер, FTP позволяет только в укажите начало смещение, а не диапазон. Если это то, что вы хотите, то следующий код должен это сделать (не тестировался!)

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

Вы также должны обрабатывать коды ответов 206, потому что они могут быть приемлемыми, если вы используете заголовок диапазона HTTP. – jbochi

+0

Достаточно честный. Ваш ответ делает это, хотя :) –

5

Я настоятельно рекомендую использовать requests библиотеку. Это просто лучшая библиотека HTTP, которую я когда-либо использовал. В частности, добиться того, что вы описали, вы могли бы сделать что-то вроде:

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

Тогда не было библиотеки запросов, но да, теперь это упрощает. – Marconi

0

я не нашел какие-либо существующие реализации файлового-подобный интерфейс с Seek() для HTTP URL, так что я помотал собственные простой версия: https://github.com/valgur/pyhttpio.Это зависит от urllib.request, но с этим, возможно, будет легко скорректировано использование requests, при необходимости.

Полный код:

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) 

Небольшой пример использования:

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: Существует на самом деле в основном идентичны, если чуть больше минимальной, реализация в этом ответе: https://stackoverflow.com/a/7852229/2997179

Смежные вопросы