2009-05-14 35 views
66

Tôi cần tải xuống một số tệp qua http bằng Python.Cách tải xuống tệp bằng cách sử dụng python theo cách 'thông minh hơn'?

Cách rõ ràng nhất để làm điều đó chỉ được sử dụng urllib2:

import urllib2 
u = urllib2.urlopen('http://server.com/file.html') 
localFile = open('file.html', 'w') 
localFile.write(u.read()) 
localFile.close() 

Nhưng tôi sẽ phải đối phó với các URL mà khó chịu một cách nào đó, nói như thế này: http://server.com/!Run.aspx/someoddtext/somemore?id=121&m=pdf. Khi tải xuống qua trình duyệt, tệp có tên có thể đọc được, ví dụ: accounts.pdf.

Có cách nào để xử lý điều đó trong python, vì vậy tôi không cần phải biết tên tệp và mã hóa chúng vào tập lệnh của tôi không?

+3

Tên tệp trên máy chủ có liên quan không? Có lẽ những tập tin này có một số ý nghĩa với bạn, vì vậy bạn nên có khả năng tự đặt tên cho chúng. Nếu các tên không có ý nghĩa, hãy đưa ra một tên duy nhất ngẫu nhiên cho mình (uuids có lẽ?) –

+0

Tôi muốn có tên tập tin có thể đọc được và có ý nghĩa. Vấn đề là, tập lệnh sẽ lấy URL để tải xuống từ tệp văn bản và URL sẽ được thêm và xóa bởi một người không phải là kỹ thuật. – kender

Trả lời

40

Tải về các kịch bản như vậy có xu hướng đẩy một tiêu đề cho người dùng-agent gì để đặt tên cho tập tin:

Content-Disposition: attachment; filename="the filename.ext" 

Nếu bạn có thể lấy tiêu đề đó, bạn có thể lấy tên tập tin thích hợp.

another thread có một chút mã để cung cấp cho Content-Disposition -grabbing.

remotefile = urllib2.urlopen('http://example.com/somefile.zip') 
remotefile.info()['Content-Disposition'] 
+0

Nhưng họ có thể không. –

+5

Không, họ có thể chuyển hướng đến một tệp đơn giản. Nhưng nếu nó giống như hầu hết các kịch bản tải xuống, họ đang đẩy nội dung. Bằng mọi cách kiểm tra. – Oli

+0

Nếu nó chuyển hướng tôi đến một tệp đơn giản thì cũng dễ dàng, tôi có thể truy cập url thực thông qua remotefile.url, phải không? – kender

35

Dựa trên ý kiến ​​và @ anwser Oli, tôi tạo ra một giải pháp như thế này:

from os.path import basename 
from urlparse import urlsplit 

def url2name(url): 
    return basename(urlsplit(url)[2]) 

def download(url, localFileName = None): 
    localName = url2name(url) 
    req = urllib2.Request(url) 
    r = urllib2.urlopen(req) 
    if r.info().has_key('Content-Disposition'): 
     # If the response has Content-Disposition, we take file name from it 
     localName = r.info()['Content-Disposition'].split('filename=')[1] 
     if localName[0] == '"' or localName[0] == "'": 
      localName = localName[1:-1] 
    elif r.url != url: 
     # if we were redirected, the real file name we take from the final URL 
     localName = url2name(r.url) 
    if localFileName: 
     # we can force to save the file as specified name 
     localName = localFileName 
    f = open(localName, 'wb') 
    f.write(r.read()) 
    f.close() 

Nó lấy tên tập tin từ Content-Disposition; nếu không có, sử dụng tên tệp từ URL (nếu chuyển hướng xảy ra, URL cuối cùng được tính đến).

+9

Tôi thấy điều này hữu ích. Nhưng để tải xuống các tệp lớn hơn, mà không lưu trữ toàn bộ nội dung trong bộ nhớ, tôi phải tìm hiểu điều này, sao chép 'r' sang 'f': nhập tắt shutil.copyfileobj (r, f) – u0b34a0f6ae

+0

@ kaizer.se tuyệt vời, cảm ơn vì đã chỉ ra điều này – kender

+4

Làm việc rất tốt, nhưng tôi sẽ bọc 'urlsplit (url) [2]' với một cuộc gọi đến 'urllib.unquote', nếu không tên tệp sẽ được mã hóa theo phần trăm. Đây là cách tôi đang làm: 'return basename (urllib.unquote (urlsplit (url) [2]))' – fjsj

23

Kết hợp nhiều ở trên, đây là một giải pháp pythonic hơn:

import urllib2 
import shutil 
import urlparse 
import os 

def download(url, fileName=None): 
    def getFileName(url,openUrl): 
     if 'Content-Disposition' in openUrl.info(): 
      # If the response has Content-Disposition, try to get filename from it 
      cd = dict(map(
       lambda x: x.strip().split('=') if '=' in x else (x.strip(),''), 
       openUrl.info()['Content-Disposition'].split(';'))) 
      if 'filename' in cd: 
       filename = cd['filename'].strip("\"'") 
       if filename: return filename 
     # if no filename was found above, parse it out of the final URL. 
     return os.path.basename(urlparse.urlsplit(openUrl.url)[2]) 

    r = urllib2.urlopen(urllib2.Request(url)) 
    try: 
     fileName = fileName or getFileName(url,r) 
     with open(fileName, 'wb') as f: 
      shutil.copyfileobj(r,f) 
    finally: 
     r.close() 
1

2 Kender:

if localName[0] == '"' or localName[0] == "'": 
    localName = localName[1:-1] 

nó không phải là an toàn - máy chủ web có thể đi sai định dạng tên như ["file.ext] hoặc [file.ext '] hoặc thậm chí trống và localName [0] sẽ tăng ngoại lệ. Mã đúng có thể trông giống như sau:

localName = localName.replace('"', '').replace("'", "") 
if localName == '': 
    localName = SOME_DEFAULT_FILE_NAME 
+2

Thậm chí còn tốt hơn: 'local_name.strip ('\'" ') '- nó sẽ chỉ tách khỏi bắt đầu và kết thúc và cũng ngắn gọn hơn. – koniiiik

0

Sử dụng wget:

custom_file_name = "/custom/path/custom_name.ext" 
wget.download(url, custom_file_name) 

Sử dụng urlretrieve:

urllib.urlretrieve(url, custom_file_name) 

urlretrieve cũng tạo ra cấu trúc thư mục nếu không tồn tại.

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