2009-12-28 45 views
10

Làm cách nào để tìm kiếm một vị trí cụ thể trên một tệp từ xa (HTTP) để tôi chỉ có thể tải xuống phần đó?Python tìm kiếm tệp từ xa bằng cách sử dụng HTTP

phép nói rằng các byte trên một tập tin từ xa được: 1234567890

Tôi muốn tìm cách 4 và tải 3 byte từ đó vì vậy tôi sẽ có: 456

và cũng có, làm thế nào để kiểm tra xem một từ xa File tồn tại? Tôi đã thử, os.path.isfile() nhưng nó trả về Sai khi tôi chuyển url của tệp từ xa.

+2

những gì bạn có nghĩa là "từ xa"? –

+0

Bạn đang sử dụng giao thức nào? HTTP? FTP? NFS? SFTP? –

+0

bằng điều khiển từ xa, tôi có nghĩa là http – Marconi

Trả lời

15

Nếu bạn đang tải xuống tệp từ xa qua HTTP, bạn cần đặt tiêu đề Range.

Kiểm tra in this example cách thực hiện. Trông như thế này:

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

EDIT: I just found a better implementation. Lớp này rất đơn giản để sử dụng, vì nó có thể được nhìn thấy trong 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') 

Cập nhật: Các "thực hiện tốt hơn" đã chuyển sang github: excid3/urlgrabber trong file byterange.py.

+0

+1 để cập nhật w/triển khai tốt hơn. –

+0

chỉ là những gì tôi cần. cảm ơn. – Marconi

1

Tôi nghĩ chìa khóa cho câu hỏi của bạn là bạn đã nói "url tệp từ xa". Điều này ngụ ý rằng bạn đang sử dụng URL HTTP để tải xuống tệp có hoạt động "get" HTTP.

Vì vậy, tôi chỉ cần làm một tìm kiếm Google cho "HTTP get" và tôi thấy điều này cho bạn:

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

Dường như bạn có thể chỉ định một phạm vi byte trong một HTTP get.

Vì vậy, bạn cần sử dụng thư viện HTTP cho phép bạn chỉ định phạm vi byte. Và khi tôi đang gõ này, jbochi đăng một liên kết đến một ví dụ.

4

AFAIK, điều này là không thể sử dụng fseek() hoặc tương tự. Bạn cần sử dụng tiêu đề Phạm vi HTTP để đạt được điều này. Tiêu đề này có thể hoặc không được máy chủ hỗ trợ, vì vậy số dặm của bạn có thể thay đổi.

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: Đây là khóa học giả định rằng bởi tập tin từ xa bạn có nghĩa là một tập tin được lưu trữ trên một máy chủ HTTP ...

Nếu tập tin bạn muốn là trên một máy chủ FTP, FTP chỉ cho phép để chỉ định một số bắt đầu và không phải là phạm vi. Nếu đây là những gì bạn muốn, sau đó mã sau nên làm điều đó (không được kiểm tra!)

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

Bạn cũng nên xử lý 206 mã phản hồi, vì chúng có thể được chấp nhận nếu bạn đang sử dụng tiêu đề phạm vi HTTP. – jbochi

+0

Đủ công bằng. Câu trả lời của bạn làm điều đó mặc dù :) –

5

Tôi khuyên bạn nên sử dụng thư viện requests. Nó dễ dàng là thư viện HTTP tốt nhất mà tôi từng sử dụng. Đặc biệt, để thực hiện những gì bạn đã mô tả, bạn sẽ làm điều gì đó như:

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

Không có thư viện yêu cầu trở lại sau đó, nhưng có điều này làm cho mọi thứ đơn giản hơn bây giờ. – Marconi

0

tôi không tìm thấy bất kỳ hiện thực hiện một giao diện tập tin giống như với tìm kiếm() để HTTP URL, vì vậy tôi cuộn đơn giản của riêng tôi phiên bản: https://github.com/valgur/pyhttpio.Nó phụ thuộc vào urllib.request nhưng có thể dễ dàng được sửa đổi để sử dụng requests, nếu cần.

Mã đầy đủ:

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) 

Một ví dụ sử dụng nhỏ:

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: Có thực sự là một phần lớn là giống hệt nhau, nếu hơi tối thiểu hơn, thực hiện trong câu trả lời này: https://stackoverflow.com/a/7852229/2997179

Các vấn đề liên quan