Descargar archivo de la web en Python 3

333

Estoy creando un programa que descargará un archivo .jar (java) desde un servidor web, leyendo la URL que se especifica en el archivo .jad del mismo juego / aplicación. Estoy usando Python 3.2.1

Me las arreglé para extraer la URL del archivo JAR del archivo JAD (cada archivo JAD contiene la URL del archivo JAR), pero como puede imaginar, el valor extraído es la cadena type ().

Aquí está la función relevante:

def downloadFile(URL=None):
    import httplib2
    h = httplib2.Http(".cache")
    resp, content = h.request(URL, "GET")
    return content

downloadFile(URL_from_file)

Sin embargo, siempre recibo un error que dice que el tipo en la función anterior tiene que ser bytes y no una cadena. Intenté usar URL.encode ('utf-8') y también bytes (URL, encoding = 'utf-8'), pero siempre obtengo el mismo error o un error similar.

Entonces, básicamente, mi pregunta es cómo descargar un archivo de un servidor cuando la URL se almacena en un tipo de cadena.

Bo Milanovich
fuente
44
@alvas, ¿Una recompensa por esto? El respondedor sigue (y bastante) activo en SO. ¿Por qué no solo agregar un comentario y preguntar?
Bhargav Rao
8
Porque vale la pena otorgar una buena respuesta que dura la prueba del tiempo. Además, debemos comenzar a hacer esto para muchas otras preguntas para verificar si las respuestas son relevantes hoy en día. Especialmente cuando la clasificación de las respuestas SO es bastante loca, a veces la respuesta desactualizada o incluso la peor va al principio.
alvas

Respuestas:

647

Si desea obtener el contenido de una página web en una variable, solo readla respuesta de 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

La forma más fácil de descargar y guardar un archivo es usar la urllib.request.urlretrievefunción:

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)

Pero tenga en cuenta que urlretrievese considera heredado y podría quedar en desuso (aunque no estoy seguro de por qué).

Entonces, la forma más correcta de hacer esto sería usar la urllib.request.urlopenfunción para devolver un objeto similar a un archivo que represente una respuesta HTTP y copiarlo a un archivo real usando 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)

Si esto parece demasiado complicado, es posible que desee simplificar y almacenar toda la descarga en un bytesobjeto y luego escribirla en un archivo. Pero esto funciona bien solo para archivos pequeños.

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)

Es posible extraer .gz(y tal vez otros formatos) datos comprimidos sobre la marcha, pero tal operación probablemente requiere que el servidor HTTP admita acceso aleatorio al archivo.

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`.
Oleh Prypin
fuente
77
podría usar en response.info().get_param('charset', 'utf-8')lugar de codificación rígida utf-8, para obtener la codificación de caracteres del Content-Typeencabezado
jfs
2
@OlehPrypin ¿Por qué outfile.write(data)solo funciona bien para archivos pequeños?
Startec
"urlretrieve se considera heredado y podría quedar obsoleto" ¿de dónde sacaste esa idea?
Corey Goldberg
13
@Corey: directamente desde los documentos : "21.6.24. Interfaz heredada Las siguientes funciones y clases se transfieren del módulo urllib de Python 2 (a diferencia de urllib2). Pueden quedar obsoletas en algún momento en el futuro". ... y estoy de acuerdo en Oleh de "no sé por qué"
TPI
@Oleh Prypin si lo uso con urllib.request.urlopen (url) como respuesta, abrir (nombre_archivo, 'wb') como out_file: shutil.copyfileobj (respuesta, out_file), entonces ¿cómo puedo encontrar el código de estado HTTP en la declaración catch? saber que no se encontró el archivo?
Robert Achmann
146

Uso el requestspaquete cuando quiero algo relacionado con las solicitudes HTTP porque su API es muy fácil de comenzar:

primero, instalar requests

$ pip install requests

entonces el código:

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)
Ali Faki
fuente
16

Espero haber entendido bien la pregunta, que es: ¿cómo descargar un archivo de un servidor cuando la URL se almacena en un tipo de cadena?

Descargo archivos y los guardo localmente usando el siguiente código:

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()
Ranvijay Kumar
fuente
hola, también estoy usando el mismo tipo de código para descargar el archivo, pero en algún momento me encuentro con una excepción como: el códec 'charmap' no puede codificar el carácter '\ u010c' ..... ¿me pueden ayudar con eso?
Joyson
10

Aquí podemos usar la interfaz Legacy de urllib en Python3:

Las siguientes funciones y clases se transfieren desde el módulo Python 2 urllib (a diferencia de urllib2). Pueden quedar obsoletos en algún momento en el futuro.

Ejemplo (código de 2 líneas) :

import urllib.request

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

Puede usar wget, que es una herramienta de descarga popular para eso. https://pypi.python.org/pypi/wget Este será el método más simple ya que no necesita abrir el archivo de destino. Aquí hay un ejemplo.

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') 
Lasith Niroshan
fuente
0

Sí, definitivamente las solicitudes son un excelente paquete para usar en algo relacionado con las solicitudes HTTP. pero tenemos que tener cuidado con el tipo de codificación de los datos entrantes. A continuación, un ejemplo que explica la diferencia.


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)
Kaushal
fuente
0

Motivación

A veces, queremos obtener la imagen pero no es necesario descargarla en archivos reales,

es decir, descargue los datos y guárdelos en la memoria.

Por ejemplo, si uso el método de aprendizaje automático, entrene un modelo que pueda reconocer una imagen con el número (código de barras).

Cuando araño algunos sitios web y que tienen esas imágenes para poder usar el modelo para reconocerlo,

y no quiero guardar esas fotos en mi unidad de disco,

entonces puede probar el siguiente método para ayudarlo a mantener los datos descargados en la memoria.

Puntos

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)

básicamente, es como @Ranvijay Kumar

Un ejemplo

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()
Carson
fuente
-3
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))
usuario7726287
fuente