在Python 3中从网上下载文件

453
我正在创建一个程序,通过读取同一游戏/应用程序的.jad文件中指定的URL,从Web服务器下载一个.jar(Java)文件。我正在使用Python 3.2.1。我已经成功从JAD文件中提取了JAR文件的URL(每个JAD文件都包含指向JAR文件的URL),但是如您所想,提取的值是字符串类型。以下是相关的函数:
def downloadFile(URL=None):
    import httplib2
    h = httplib2.Http(".cache")
    resp, content = h.request(URL, "GET")
    return content

downloadFile(URL_from_file)

但是,我总是收到一个错误消息,说上面的函数类型必须是字节而不是字符串。我已经尝试使用URL.encode('utf-8')和bytes(URL,encoding='utf-8'),但总是会收到相同或类似的错误。

所以,我的问题基本上是如何在URL存储为字符串类型时从服务器下载文件?


4
@alvas,这个问题需要悬赏吗?回答者仍然在Stack Overflow上非常活跃,为什么不加一条评论并询问呢? - Bhargav Rao
9
因为经得起时间考验的好答案值得奖励。此外,我们应该开始对许多其他问题进行这样的检查,以确定答案今天是否仍然相关。特别是当SO的排序有点混乱时,有时过时甚至更差的答案能够排在前面。 - alvas
9个回答

819

如果您想将网页内容获取到一个变量中,只需读取urllib.request.urlopen的响应即可:

import urllib.request
...
url = 'http://example.com/'
response = urllib.request.urlopen(url)
data = response.read()      # a `bytes` object
text = data.decode('utf-8') # a `str`; this step can't be used if data is binary

下载和保存文件最简单的方法是使用urllib.request.urlretrieve函数:

import urllib.request
...
# Download the file from `url` and save it locally under `file_name`:
urllib.request.urlretrieve(url, file_name)

import urllib.request
...
# Download the file from `url`, save it in a temporary directory and get the
# path to it (e.g. '/tmp/tmpb48zma.txt') in the `file_name` variable:
file_name, headers = urllib.request.urlretrieve(url)

但请记住,urlretrieve被认为是遗留接口,可能会被弃用(虽然不确定为什么)。
因此,最正确的方法是使用urllib.request.urlopen函数返回表示HTTP响应的文件对象,并使用shutil.copyfileobj将其复制到实际文件中。
import urllib.request
import shutil
...
# Download the file from `url` and save it locally under `file_name`:
with urllib.request.urlopen(url) as response, open(file_name, 'wb') as out_file:
    shutil.copyfileobj(response, out_file)

如果这看起来太复杂,你可能想要更简单的方法,将整个下载存储在一个 bytes 对象中,然后将其写入文件。但这仅适用于小文件。
import urllib.request
...
# Download the file from `url` and save it locally under `file_name`:
with urllib.request.urlopen(url) as response, open(file_name, 'wb') as out_file:
    data = response.read() # a `bytes` object
    out_file.write(data)

可以实时提取.gz(以及可能的其他格式)压缩数据,但这种操作可能需要HTTP服务器支持对文件的随机访问。

import urllib.request
import gzip
...
# Read the first 64 bytes of the file inside the .gz archive located at `url`
url = 'http://example.com/something.gz'
with urllib.request.urlopen(url) as response:
    with gzip.GzipFile(fileobj=response) as uncompressed:
        file_header = uncompressed.read(64) # a `bytes` object
        # Or do anything shown above using `uncompressed` instead of `response`.

7
您可以使用response.info().get_param('charset', 'utf-8')代替硬编码的utf-8,以从Content-Type头中获取字符编码。 - jfs
3
为什么 outfile.write(data) 只对小文件有效? - Startec
"urlretrieve 被视为遗留问题,可能会被弃用" 你是从哪里得到这个想法的? - Corey Goldberg
20
文档中可以看到,以下函数和类是从Python 2模块urllib(而不是urllib2)移植过来的。它们可能在未来某个时候变为弃用接口。我同意Oleh的“不确定为什么”。 - cfi
如果我使用以下代码:with urllib.request.urlopen(url) as response, open(file_name, 'wb') as out_file: shutil.copyfileobj(response, out_file),那么在catch语句中如何查找HTTP状态码以了解文件是否未找到? - Robert Achmann
显示剩余5条评论

201

每当我需要进行HTTP请求相关的操作时,我会使用requests包,因为其API非常易于上手:

首先,安装requests

$ pip install requests

然后就是这段代码:

from requests import get  # to make GET request


def download(url, file_name):
    # open in binary mode
    with open(file_name, "wb") as file:
        # get request
        response = get(url)
        # write to file
        file.write(response.content)

26

我希望我正确理解了问题,即:当URL以字符串形式存储时,如何从服务器上下载文件?

我使用以下代码下载文件并将其保存在本地:

import requests

url = 'https://www.python.org/static/img/python-logo.png'
fileName = 'D:\Python\dwnldPythonLogo.png'
req = requests.get(url)
file = open(fileName, 'wb')
for chunk in req.iter_content(100000):
    file.write(chunk)
file.close()

嗨,我也在使用相同类型的代码下载文件,但有时会遇到异常,例如 -“charmap”编解码器无法编码字符'\u010c'.....你能帮助我吗? - Joyson

20

您可以使用流行的下载shell工具wgethttps://pypi.python.org/pypi/wget 这将是最简单的方法,因为它不需要打开目标文件。以下是一个示例。

import wget
url = 'https://i1.wp.com/python3.codes/wp-content/uploads/2015/06/Python3-powered.png?fit=650%2C350'  
wget.download(url, '/Users/scott/Downloads/cat4.jpg') 

19

在Python3中,我们可以使用urllib的传统接口:

以下函数和类是从Python 2模块urllib(而不是urllib2)移植过来的。它们可能在将来某个时候被弃用。

示例(2行代码)

import urllib.request

url = 'https://www.python.org/static/img/python-logo.png'
urllib.request.urlretrieve(url, "logo.png")

2
这样的函数被弃用的原因是什么?除了手动将响应写入文件之外,还有其他替代方法吗? - Michael Chen

5

是的,Definitely Requests 是一个用于处理 HTTP 请求相关内容的优秀库。但我们需要注意传入数据的编码类型。下面是一个例子来解释其中的差异。


from requests import get

# case when the response is byte array
url = 'some_image_url'

response = get(url)
with open('output', 'wb') as file:
    file.write(response.content)


# case when the response is text
# Here unlikely if the reponse content is of type **iso-8859-1** we will have to override the response encoding
url = 'some_page_url'

response = get(url)
# override encoding by real educated guess as provided by chardet
r.encoding = r.apparent_encoding

with open('output', 'w', encoding='utf-8') as file:
    file.write(response.content)


到目前为止,我看到的最适合初学者的方法。 - till Kadabra

0

动机

有时候,我们想要获取图片,但不需要将其下载到实际文件中,

下载数据并将其保存在内存中。

例如,如果我使用机器学习方法,训练一个可以识别带有数字(条形码)的图像的模型。

当我爬取一些包含这些图像的网站时,我可以使用该模型进行识别,

而我不想将这些图片保存在我的磁盘驱动器上,

那么您可以尝试下面的方法来帮助您将下载的数据保存在内存中。

要点

import requests
from io import BytesIO
response = requests.get(url)
with BytesIO as io_obj:
    for chunk in response.iter_content(chunk_size=4096):
        io_obj.write(chunk)

基本上,就像是对@Ranvijay Kumar的提及

一个例子

import requests
from typing import NewType, TypeVar
from io import StringIO, BytesIO
import matplotlib.pyplot as plt
import imageio

URL = NewType('URL', str)
T_IO = TypeVar('T_IO', StringIO, BytesIO)


def download_and_keep_on_memory(url: URL, headers=None, timeout=None, **option) -> T_IO:
    chunk_size = option.get('chunk_size', 4096)  # default 4KB
    max_size = 1024 ** 2 * option.get('max_size', -1)  # MB, default will ignore.
    response = requests.get(url, headers=headers, timeout=timeout)
    if response.status_code != 200:
        raise requests.ConnectionError(f'{response.status_code}')

    instance_io = StringIO if isinstance(next(response.iter_content(chunk_size=1)), str) else BytesIO
    io_obj = instance_io()
    cur_size = 0
    for chunk in response.iter_content(chunk_size=chunk_size):
        cur_size += chunk_size
        if 0 < max_size < cur_size:
            break
        io_obj.write(chunk)
    io_obj.seek(0)
    """ save it to real file.
    with open('temp.png', mode='wb') as out_f:
        out_f.write(io_obj.read())
    """
    return io_obj


def main():
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'Cache-Control': 'max-age=0',
        'Connection': 'keep-alive',
        'Host': 'statics.591.com.tw',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36'
    }
    io_img = download_and_keep_on_memory(URL('http://statics.591.com.tw/tools/showPhone.php?info_data=rLsGZe4U%2FbphHOimi2PT%2FhxTPqI&type=rLEFMu4XrrpgEw'),
                                         headers,  # You may need this. Otherwise, some websites will send the 404 error to you.
                                         max_size=4)  # max loading < 4MB
    with io_img:
        plt.rc('axes.spines', top=False, bottom=False, left=False, right=False)
        plt.rc(('xtick', 'ytick'), color=(1, 1, 1, 0))  # same of plt.axis('off')
        plt.imshow(imageio.imread(io_img, as_gray=False, pilmode="RGB"))
        plt.show()


if __name__ == '__main__':
    main()


-2
from urllib import request

def get(url):
    with request.urlopen(url) as r:
        return r.read()


def download(url, file=None):
    if not file:
        file = url.split('/')[-1]
    with open(file, 'wb') as f:
        f.write(get(url))

-7
如果您正在使用Linux,可以通过Python shell使用Linux的wget模块。以下是一个示例代码片段。
import os
url = 'http://www.example.com/foo.zip'
os.system('wget %s'%url)

3
这是我见过的引入注入攻击到系统中最糟糕的例子之一。os.system与没有检查的字符串格式化结合使用。 - Stephen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接