Blog
Descargar archivos con Python y urllib
Importar la librería urllib.requests
Hacer solicitudes GET y descargar archivos
Modificar las cabeceras de las solicitudes GET
Enviar un formulario con una solicitud POST
El módulo urllib
en Python es una colección de módulos que podemos usar para trabajar con URLs. Este paquete cuenta con 4 módulos, que facilitan el manejo de URLs, definen errores para ser capturados en una sentencia try/except
y contienen funciones que permiten descargar archivos con Python.
En este artículo nos concentraremos en el módulo urllib.request
que define funciones y clases que ayudan a operar con URLs (HTTP en su mayoría) controlando autenticaciones, redirecciones, cookies y más.
urllib.requests: Descargar archivos con Python
El módulo urllib.request
es usado principalmente para buscar y abrir URLs. Una de sus funciones principales es urllib.request.urlopen()
. La función recibe una URL, ya sea como cadena de texto o como un objeto Request y su objeto de retorno depende del tipo de URL que se le pase como argumento. Por ejemplo:
- Ingresando como argumento una URL HTTP, obtenemos un objeto HTTPResponse ligeramente modificado ya que cuenta con un par de métodos extra:
In [1]: from urllib.request import urlopen In [2]: httpresponse = urlopen('http://www.google.com') In [3]: httpresponse Out[3]: <http.client.HTTPResponse at 0x7f4772af8390>
- Ingresando como argumento una URL FTP y URLs de archivos y datos esta función retorna un objeto urllib.response.addinfourl.
Veamos un ejemplo de varias funciones útiles que poseen los objetos HTTPResponse devueltos por la función urllib.request.urlopen()
:
In [1]: from urllib.request import urlopen In [2]: response = urlopen('http://www.google.com') In [3]: response.geturl() Out[3]: 'http://www.google.com' In [4]: response.info() Out[4]: <http.client.HTTPMessage at 0x7f74eaf939e8> In [5]: header = response.info() In [6]: header.as_string() Out[6]: 'Date: Fri, 19 Oct 2018 19:00:37 GMT\nExpires: -1\nCache-Control:ent-Type: text/html; charset=ISO-8859-1\nP3P: CP="This is not a P3P policore info."\nServer: gws\nX-XSS-Protection: 1; mode=block\nX-Frame-Options 1P_JAR=2018-10-19-19; expires=Sun, 18-Nov-2018 19:00:37 GMT; path=/; domie: NID=141=o5rFyT2XxBoSP3Kf-AeRubZ5TnbGJAAm9iMQ3tSN8adb6E6-yNOQs-ErHtl5mmsNQudS1qpKMfb6X_GC9hYURYzA6DSo-QA4mmMhZRE; expires=Sat, 20-Apr-2019 19:0.google.com; HttpOnly\nAccept-Ranges: none\nVary: Accept-Encoding\nConnec... In [7]: response.getcode() Out[7]: 200
Como podemos ver en el ejemplo creamos un objeto HTTPResponse al pasarle una URL tipo HTTP como argumento a la función urlopen()
. Un objeto Response contiene información sobre la respuesta del servidor a nuestra solicitud. Algunas de las funciones que podemos observar en el ejemplo son:
response.geturl()
: retorna la URL que fue solicitada.response.info()
: al ejecutar solo este código nos muestra el tipo de objeto que retorna, un objeto HTTPMessage. Este contiene toda la información guardada en el objeto HTTPResponse, para leerla debemos llamar al métodoHTTPMessage.as_string()
.response.getcode()
: retorna el código de la solicitud HTTP, en nuestro caso, 200 quiere decir que la solicitud fue exitosa, sin errores.
Descargando un archivo
Un caso de uso común del módulo urllib
es la descarga de un archivo. Veamos un par de maneras en las que podemos lograr esta tarea:
In [1]: import urllib.request In [2]: url = 'https://www.aprenderpython.net/wp-content/uploads/2018/10/SAMPLE_EXAM2.pdf' In [3]: response = urllib.request.urlopen(url) In [4]: data = response.read() In [5]: with open('/home/odars/Desktop/prueba.pdf', 'wb') as archivo: ...: archivo.write(data) ...: In [6]: with open('/home/odars/Desktop/prueba.pdf', 'r') as archivo: ...: print(archivo) ...: <_io.TextIOWrapper name='/home/odars/Desktop/prueba.pdf' mode='r' encoding='UTF-8'>
Aquí solo abrimos una URL que nos lleva a un archivo PDF guardado en esta página web. Luego leemos los datos y los escribimos en el disco. Al imprimir, vemos que hemos guardado un archivo PDF. Un enfoque alternativo a este, sería usar la función urlretrieve
:
In [1]: import urllib.request In [2]: url = 'https://www.aprenderpython.net/wp-content/uploads/2018/10/SAMPLE_EXAM2.pdf' In [3]: archivo_tmp, header = urllib.request.urlretrieve(url) In [4]: with open('/home/odars/Desktop/prueba.pdf', 'wb') as archivo: ...: with open(archivo_tmp, 'rb') as tmp: ...: archivo.write(tmp.read()) ...: In [5]:
El método urlretrieve
copia un objeto de red a un archivo local. El archivo que se copia, obtiene un nombre aleatorio y va a un directorio temporal a menos que usemos el segundo parámetro de el método urlretrieve
donde podemos especificar la ubicación donde queremos que el archivo sea guardado.
Especificando el User-Agent
Cuando visitamos un sitio web con nuestro navegador, este le dice al sitio web que tipo de navegador es, a traves de los headers. Esta información reside en la string user-agent. La librería urllib
de Python se identifica a sí misma como Python-urllib/x.y donde x y y son las versiones mayor y menor del intérprete que estemos utilizando. Algunos sitios web no reconocerán este user-agent y se comportarán de una manera no esperada o no funcionarán en absoluto. Para descargar archivos con Python exitosamente debemos tener en cuenta esta situación y modificar nuestros headers. Afortunadamente es fácil para nosotros configurar nuestra string user-agent utilizando los headers:
In [1]: import urllib.request In [2]: user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0' In [3]: url = 'http://www.whatismybrowser.com/' In [4]: headers = {'User-Agent':user_agent} In [5]: request = urllib.request.Request(url, headers=headers) In [6]: with urllib.request.urlopen(request) as response: ...: with open('/home/odars/Desktop/user_agent.html', 'wb') as out: ...: out.write(response.read()) ...: In [7]: with open('/home/odars/Desktop/user_agent.html', 'r') as file: ...: print(file) ...: <!DOCTYPE html> <html lang="en" class="no-js"> <head> <script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script> <script>window.bugsnagClient = bugsnag('76d0e9115916605f5efc48aadc83d9ea')</script> <script> var third_party_domain = "webbrowsertests.com"; // which server do third-party checks use? </script...
En este código, guardadmos el valor de nuestro user-agent en una variable para agregarlo a nuestros headers: creamos un objeto Request vinculando la URL que queremos solicitar y los headers, luego usamos urlopen()
para hacer nuestra solicitud, pasando como argumento el objeto Request. Para saber nuestro user-agent podemos visitar whatismybrowser.com para así agregarlo a nuestro header.
Urllib.parse
Esta librería es nuestra herramienta principal para dividir strings de URLs y combinarlas. Podemos usarla para convertir una URL relativa a una URL absoluta, por ejemplo.
In [1]: from urllib.parse import urlparse In [2]: result = urlparse('https://duckduckgo.com/?q=python&t=ffab&ia=web') In [3]: result Out[3]: ParseResult(scheme='https', netloc='duckduckgo.com', path='/', params='', query='q=python&t=ffab&ia=web', fragment='') In [4]: result.netloc Out[4]: 'duckduckgo.com' In [5]: result.geturl() Out[5]: 'https://duckduckgo.com/?q=python&t=ffab&ia=web' In [6]: result.port None
Aquí importamos la función urlparse
y pasamos como argumento una consulta de búsqueda en el buscador DuckDuckGo. La consulta que hicimos fue simplemente la palabra “python”. Como podemos ver, la funciónurlparse()
devolvió un objeto ParseResult el cual podemos usar para aprender más sobre la URL. Por ejemplo podemos obtener la ubicación de la red, su ruta, la información relacionada al puerto (ninguna en este caso) y mucho más.
Enviando un formulario
Este módulo también posee el método urlencode
, el cual es de gran ayuda para enviar datos a una URL. Un caso de uso típico para la librería urllib.parse
es rellenar y enviar un formulario web. Veamos como podemos hacer esto haciendo que el buscador DuckDuckGo haga una búsqueda de “python”.
In [1]: import urllib.request In [2]: import urllib.parse In [3]: data = urllib.parse.urlencode({'q':'python', 'ia':'web'}) In [4]: data Out[4]: 'q=python&ia=web' In [5]: url = 'http://www.duckduckgo.com/?{}'.format(data) In [6]: response = urllib.request.urlopen(url) In [7]: with open('/home/odars/Desktop/results.html', 'wb') as file: ...: file.write(response.read()) In [8]: with open('/home/odars/Desktop/results.html', 'r') as file: ...: print(file.read()) <!DOCTYPE html><!--[if IEMobile 7 ]> <html lang="en_US" class="no-js iem7"> <![endif]--><!--[if lt IE 7]> <html lang="en_US" class="no-js ie6 lt-ie10 lt-ie9 lt-ie8 lt-ie7"><![endif]--><!--[if IE 7]> <html lang="en_US" class="no-js ie7 lt-ie10 lt-ie9 lt-ie8"> <![endif]--><!--[if IE 8]> <html lang="en_US" class="no-js...
Si abrimos el archivo que crea este código, nuestro navegador nos mostrará una página de búsqueda parecida a esta. Lo que hacemos es crear una variable data que contendrá los datos de la consulta al buscador web. En este caso el query es python y la forma es web.
Como anteriormente dijimos, esta librería nos permite manejar algunos errores.
Estos errores pueden ser capturados mediante una sentencia try/except para luego ser manipulados según sea el caso o lo que necesitemos, esto ya es dependiente de lo que estemos haciendo dentro de un proyecto.
Entre estos errores destacan los siguientes:
- urlib.error.HTTPError: Cuando se generan errores del tipo HTTP request, como las solicitudes GET y POST. El caso más común el login en un formulario.
- urlib.error.URLError: Cuando la URL presenta errores o está mal escrita
- urlib.error.ContentTooShortError: Se produce cuando la función urlretrieve() detecta que la cantidad de datos descargados es menor que la cantidad de datos esperados.
Como podemos ver, la librería urllib
nos sirve para muchas cosas además de descargar archivos con Python y utilizar sus excepciones para manejar los distintos errores.
➡ ¡Bien hecho al llegar hasta aquí! Aprende mucho mas de redes en nuestro Curso de redes en Python:
[…] Curso de Redes en Python, 3º clase […]
Hola, sirve para descargar contenido multimedia por ejemplo animaciones flash, gadgets, banners y todo tipo de contenido interactivo de una web?
Hola Jorge, claro que si, gracias por comentar.
Hola Rafael, Tengo una aplicación en el hosting gratis PythonAnyWhere, dicha aplicación genera perfectamente un archivo PDF, pero lo aloja internamente en el hosting (esta demás decirte que a modo local funciona todo al 100%). He probado con WGET y un par de librerías mas, pero es imposible que el PDF lo aloje en local. Por último contacté a soporte de PythonAnyWhere y me dicen que existe una restriccion para la bajada de archivos: ” the program that is trying to do the download needs to be logged in to PythonAnywhere. We don’t expose our users files directly to the internet.”.
Por lo tanto mi pregunta concreta es: con URLLIB yo podria solucionar este problema o definitivamente tendría que cambiarme de hosting ?
Desde ya agradezco tu ayuda.
Rafael:
La verdad el mejor método frente a una duda, es probar la cuestión si funciona o no. Definitivamente PythonAnywhere NO PERMITE bajar un PDF que ha sido generado desde una aplicación alojada en dicho hosting. A no ser que me puedas ayudar con alguna función, método o librería creo que me veré obligado a buscar un hosting donde alojar mi aplicación y que permita algo tan simple como bajar un PDF.
Desde ya agradezco tu ayuda.