Usa Python para descargar múltiples archivos (o URLs) en paralelo

Usa Python para descargar archivos en paralelo

Obtén más datos en menos tiempo

Foto de Wesley Tingey en Unsplash

Vivimos en un mundo de big data. A menudo, el big data se organiza como una gran colección de pequeños conjuntos de datos (es decir, un gran conjunto de datos compuesto por varios archivos). Obtener estos datos a menudo es frustrante debido a la descarga (o adquisición). Afortunadamente, con un poco de código, existen formas de automatizar y acelerar la descarga y adquisición de archivos.

Automatizar la descarga de archivos puede ahorrar mucho tiempo. Hay varias formas de automatizar la descarga de archivos con Python. La forma más fácil de descargar archivos es utilizando un simple bucle de Python para iterar a través de una lista de URL para descargar. Este enfoque serial puede funcionar bien con unos pocos archivos pequeños, pero si estás descargando muchos archivos o archivos grandes, querrás utilizar un enfoque paralelo para maximizar tus recursos computacionales.

Con una rutina de descarga de archivos en paralelo, puedes utilizar mejor los recursos de tu computadora para descargar múltiples archivos simultáneamente, ahorrándote tiempo. Este tutorial demuestra cómo desarrollar una función genérica de descarga de archivos en Python y aplicarla para descargar múltiples archivos con enfoques serial y paralelo. El código en este tutorial utiliza solo módulos disponibles en la biblioteca estándar de Python, por lo que no se requiere ninguna instalación.

Importar módulos

Para este ejemplo, solo necesitamos los módulos de Python requests y multiprocessing para descargar archivos en paralelo. Los módulos requests y multiprocessing están disponibles en la biblioteca estándar de Python, por lo que no necesitarás realizar ninguna instalación.

También importaremos el módulo time para hacer un seguimiento de cuánto tiempo tarda en descargar archivos individuales y comparar el rendimiento entre las rutinas de descarga serial y paralela. El módulo time también forma parte de la biblioteca estándar de Python.

import requests import time from multiprocessing import cpu_count from multiprocessing.pool import ThreadPool

Definir URL y nombres de archivo

Mostraré descargas de archivos en paralelo en Python utilizando archivos NetCDF de gridMET que contienen datos de precipitación diaria para Estados Unidos.

Aquí, especifico las URL de cuatro archivos en una lista. En otras aplicaciones, es posible que generes programáticamente una lista de archivos para descargar.

urls = ['https://www.northwestknowledge.net/metdata/data/pr_1979.nc', 'https://www.northwestknowledge.net/metdata/data/pr_1980.nc', 'https://www.northwestknowledge.net/metdata/data/pr_1981.nc', 'https://www.northwestknowledge.net/metdata/data/pr_1982.nc']

Cada URL debe estar asociada con su ubicación de descarga. Aquí, estoy descargando los archivos en el directorio ‘Descargas’ de Windows. He codificado los nombres de archivo en una lista para mayor simplicidad y transparencia. Según tu aplicación, es posible que desees escribir código que analice la URL de entrada y la descargue en un directorio específico.

fns = [r'C:\Users\konrad\Downloads\pr_1979.nc', r'C:\Users\konrad\Downloads\pr_1980.nc', r'C:\Users\konrad\Downloads\pr_1981.nc', r'C:\Users\konrad\Downloads\pr_1982.nc']

Multiprocessing requiere que las funciones paralelas tengan solo un argumento (hay algunas soluciones alternativas, pero no las abordaremos aquí). Para descargar un archivo, necesitaremos pasar dos argumentos, una URL y un nombre de archivo. Por lo tanto, combinaremos las listas urls y fns para obtener una lista de tuplas. Cada tupla en la lista contendrá dos elementos: una URL y el nombre de archivo de descarga para la URL. De esta manera, podemos pasar un solo argumento (la tupla) que contiene dos piezas de información.

inputs = zip(urls, fns)

Función para descargar una URL

Ahora que hemos especificado las URL para descargar y sus nombres de archivo asociados, necesitamos una función para descargar las URL (download_url).

Pasaremos un argumento ( arg) a download_url. Este argumento será un iterable (lista o tupla) donde el primer elemento es la URL a descargar ( url) y el segundo elemento es el nombre de archivo ( fn). Los elementos se asignan a variables ( url y fn) para mayor legibilidad.

Ahora crea una declaración try en la cual se recupere la URL y se escriba en el archivo después de que se crea. Cuando se escribe el archivo, se devuelven la URL y el tiempo de descarga. Si ocurre una excepción, se imprime un mensaje.

La función download_url es la parte principal de nuestro código. Hace el trabajo real de descarga y creación de archivos. Ahora podemos usar esta función para descargar archivos en serie (usando un bucle) y en paralelo. Veamos esos ejemplos.

def download_url(args):   t0 = time.time()   url, fn = args[0], args[1]   try:     r = requests.get(url)     with open(fn, 'wb') as f:       f.write(r.content)       return(url, time.time() - t0)   except Exception as e:     print('Excepción en download_url():', e)

Descargar múltiples archivos con un bucle en Python

Para descargar la lista de URL a los archivos asociados, recorre el iterable (inputs) que creamos, pasando cada elemento a download_url. Después de que se completa cada descarga, imprimiremos la URL descargada y el tiempo que llevó descargarla.

El tiempo total para descargar todas las URL se imprimirá después de que se hayan completado todas las descargas.

t0 = time.time() for i in inputs:   result = download_url(i)   print('url:', result[0], 'tiempo:', result[1])   print('Tiempo total:', time.time() - t0)

Salida:

url: https://www.northwestknowledge.net/metdata/data/pr_1979.nc tiempo: 16.381176710128784 url: https://www.northwestknowledge.net/metdata/data/pr_1980.nc tiempo: 11.475878953933716 url: https://www.northwestknowledge.net/metdata/data/pr_1981.nc tiempo: 13.059367179870605 url: https://www.northwestknowledge.net/metdata/data/pr_1982.nc tiempo: 12.232381582260132 Tiempo total: 53.15849542617798

Se tardó entre 11 y 16 segundos en descargar los archivos individuales. El tiempo total de descarga fue un poco menos de un minuto. Los tiempos de descarga variarán según tu conexión de red específica.

Comparemos este enfoque en serie (bucle) con el enfoque paralelo a continuación.

Descargar múltiples archivos en paralelo con Python

Para empezar, crea una función (download_parallel) para manejar la descarga en paralelo. La función (download_parallel) tomará un argumento, un iterable que contiene URLs y nombres de archivo asociados (la variable inputs que creamos anteriormente).

A continuación, obtén el número de CPUs disponibles para procesamiento. Esto determinará el número de hilos que se ejecutarán en paralelo.

Ahora usa el multiprocessing ThreadPool para asignar los inputs a la función download_url. Aquí usamos el método imap_unordered de ThreadPool y le pasamos la función download_url y los argumentos de entrada para download_url (la variable inputs). El método imap_unordered ejecutará download_url simultáneamente para el número de hilos especificados (es decir, descarga en paralelo).

Por lo tanto, si tenemos cuatro archivos y cuatro hilos, todos los archivos se pueden descargar al mismo tiempo en lugar de esperar a que una descarga termine antes de que comience la siguiente. Esto puede ahorrar una cantidad considerable de tiempo de procesamiento.

En la última parte de la función download_parallel, se imprimen las URL descargadas y el tiempo requerido para descargar cada URL.

def download_parallel(args):   cpus = cpu_count()   results = ThreadPool(cpus - 1).imap_unordered(download_url, args)   for result in results:     print('url:', result[0], 'tiempo (s):', result[1])

Una vez definidos los inputs y download_parallel, los archivos se pueden descargar en paralelo con una sola línea de código.

download_parallel(inputs)

Salida:

url: https://www.northwestknowledge.net/metdata/data/pr_1980.nc tiempo (s): 14.641696214675903 url: https://www.northwestknowledge.net/metdata/data/pr_1981.nc tiempo (s): 14.789752960205078 url: https://www.northwestknowledge.net/metdata/data/pr_1979.nc tiempo (s): 15.052601337432861 url: https://www.northwestknowledge.net/metdata/data/pr_1982.nc tiempo (s): 23.287317752838135 Tiempo total: 23.32273244857788

Observa que cada archivo individual tardó más tiempo en descargarse con este enfoque. Esto puede ser resultado de un cambio en la velocidad de la red o de la sobrecarga requerida para asignar las descargas a sus respectivos hilos. Aunque cada archivo individual tardó más tiempo en descargarse, el método paralelo resultó en una disminución del 50% en el tiempo total de descarga.

Puedes ver cómo el procesamiento paralelo puede reducir considerablemente el tiempo de procesamiento para múltiples archivos. A medida que aumenta el número de archivos, ahorrarás mucho más tiempo utilizando un enfoque de descarga paralela.

Conclusión

Automatizar las descargas de archivos en tus rutinas de desarrollo y análisis puede ahorrarte mucho tiempo. Como se demostró en este tutorial, implementar una rutina de descarga paralela puede disminuir considerablemente el tiempo de adquisición de archivos si necesitas muchos archivos o archivos grandes.

Publicado originalmente en https://opensourceoptions.com.