Cómo hacer que su programa de rastreo realice solicitudes simultáneas a gran velocidad
Lo anterior es una publicación que vi ayer cuando estaba actualizando V2. Un resumen simple del contenido del código del autor es que se ejecuta de forma completamente secuencial. Cada vez que descargas una imagen, debes esperar. La imagen actual se descargará antes de continuar descargando la siguiente Zhang, ¡por supuesto que esto será muy lento! Este artículo utilizará su código como muestra y realizará algunos ajustes basados en el original, de modo que el rastreador que escribió pueda pasar de ser una tortuga arrastrándose a tan rápido como un cohete.
En primer lugar, necesitamos saber qué es la concurrencia. Aquí la concurrencia se refiere a "enviar solicitudes en paralelo", es decir, enviar varias solicitudes a la vez, ¡ahorrando así tiempo! Entonces, ¿cuál es la diferencia entre concurrente y no concurrente?
En resumen, es así:
Tome el rastreador como ejemplo. Sin concurrencia, el trabajador solo puede hacer una cosa a la vez, por lo que debe esperar hasta la descarga. Sólo después de una imagen se puede continuar descargando la siguiente imagen.
En el caso de ejecución secuencial y concurrencia, hay muchos trabajadores trabajando juntos y a cada trabajador se le asigna una cosa, por lo que pueden descargar varias imágenes al mismo tiempo y la velocidad, naturalmente, será mucho más rápido.
Por supuesto, el ejemplo anterior solo analiza la concurrencia desde una perspectiva macro. En la práctica real, su rastreador debe poder realizar solicitudes concurrentes. Hay tres métodos: subprocesos múltiples, procesos múltiples. coprocesamiento No es El efecto de cada método en tiempo de ejecución es el anterior. No lo discutiremos en profundidad aquí porque este no es el tema central de este artículo. Ahora solo necesitamos saber que siempre que podamos hacer que el rastreador realice solicitudes simultáneas, podemos descargar varias imágenes al mismo tiempo, por lo que la velocidad será muy rápida y eso es suficiente.
Entonces, ¿cuál de estos tres métodos usaremos para implementar solicitudes concurrentes? ¿Necesitas siquiera preguntar? ¡Utilizaremos el programa concurrente más simple, menos modificado y más legible posible! Después de Python 3.4, Python introdujo una biblioteca llamada asyncio, que admite de forma nativa IO asincrónica. Después de 3.5, Python admite la sintaxis asincrónica y de espera, lo que hace que escribir código asincrónico sea tan fácil de leer como escribir código sincrónico.
También mencionamos dos palabras: sincronización y asincrónico. Estas dos palabras en realidad tienen significados similares a concurrencia. El código sincrónico se ejecuta en secuencia, pero no entraremos en detalles aquí. siempre y cuando sepas que existe tal cosa.
Algunas personas definitivamente comenzarán a tener preguntas después de ver esto. Aunque mencionamos anteriormente que necesitamos usar solicitudes concurrentes para lograr la concurrencia, luego hablamos sobre qué admite Python asíncrono nativo y cuál es la relación. ¿Entre asincrónico y concurrente?
De hecho, es muy simple. La concurrencia le permite escribir código asincrónico tan fácilmente como escribir código sincrónico. Aquí hay un ejemplo simple:
p>
1def?func():
2print(1)
3time.sleep(10)
4print( 2)
Esta es una función común que pertenece al código sincrónico, y time.sleep inside también es una función común que pertenece al código sincrónico.
1async?def?func():?#?La llamada a una función concurrente también debe ser una función concurrente
2print(1)
3await ?asyncio.sleep(10)?#?Llamar a una función concurrente precedida por await
4print(2)
Esta es una función concurrente, un código asincrónico, y asyncio.sleep es Internamente, las funciones concurrentes también son código asincrónico.
La diferencia entre ellos es obvia. El uso de corrutinas para escribir código asincrónico solo agrega un async y await. Además, debe cambiar a una biblioteca asincrónica.
Entonces, ahora que entendemos cómo escribir código lento, ¿podemos empezar a optimizarlo? La respuesta es no. Este código utiliza la biblioteca de solicitudes para manejar solicitudes de red, y las solicitudes son una biblioteca sincrónica y no se pueden usar en un entorno asincrónico. De manera similar, las operaciones de archivos que usan open y file.write también son operaciones sincrónicas y no se pueden usar en un entorno asincrónico.
Por lo tanto, antes de comenzar, debemos comprender las dos bibliotecas, aiohttp y aiofiles, respectivamente. aiohttp es una biblioteca de solicitudes de red asincrónica y aiofiles es una biblioteca de operaciones de archivos asincrónicas. (Aiofiles se implementa en función del grupo de subprocesos y no es verdaderamente asincrónico nativo, pero el problema no es grande y no afecta el uso)
Recuerde que el código asincrónico no se puede mezclar con el código sincrónico; código sincrónico Si tarda demasiado, el código asincrónico se bloqueará y se perderá el efecto asincrónico. Y las solicitudes de red y las operaciones de archivos son las partes que consumen más tiempo de todo el proceso, por lo que debemos usar una biblioteca asincrónica para manejarlas. ¡De lo contrario causará desperdicio!
Bien, echemos un vistazo al uso de aiohttp. El ejemplo en la documentación oficial es aproximadamente el siguiente:
1async?with?aiohttp.ClientSession()?as?session. :
p>
2async?with?session.get(url)? as?resp:
3resultado?=?await?resp.text()
¿No parece muy problemático? ¿Por qué no solicitar la biblioteca? Además, ¿crees que dos capas de async o no es feo? ¿Hay alguna manera de hacerlo tan conveniente como la biblioteca de solicitudes?
La respuesta es sí, existe una biblioteca llamada aiohttp-requests que puede hacer que el código anterior se vea así:
1resp?=?await?requests.get(url)
2result?=?await?response.text()
¿No es esto mucho más refrescante? ¡Lo usaremos más tarde! Recuerde, solo puede instalar esta biblioteca si aiohttp se instala primero.
Luego veamos el uso de aiofiles. El ejemplo en la documentación oficial es el siguiente:
1async?with?aiofiles.open('filename',?mode=' r')? as?f:
2contents?=?await?f.read()
3print(contents)
Vale, esto es lo mismo como usar código síncrono para manipular archivos El efecto es casi el mismo, no hay nada de qué ser exigente, solo úselo.
Consejo: aiohttp-requests crea y utiliza sesiones de forma predeterminada. Para algunos escenarios de aplicaciones que requieren solicitudes sin conservar cookies, debe crear una instancia de una clase Requests usted mismo y especificar cookie_jar como aiohttp .DummyCookieJar.
Además, también necesitamos usar la biblioteca aiohttp-requests para crear y usar sesiones. Después de comprender las bibliotecas que se utilizarán, podemos comenzar a publicar el código en Mobian. Si no está utilizando Python3.5 o superior, entonces necesita preparar el entorno. Además de la versión 3.5 de Python, también necesita instalar las siguientes bibliotecas:
aiohttp (biblioteca de solicitudes de red asíncronas)
aiohttp-requests (hace que la biblioteca aiohttp sea más fácil de usar) p>
aiofiles (biblioteca de operaciones de archivos asincrónicas)
almohada (en realidad, una biblioteca PIL, código, muy útil para operaciones de imágenes)
Ejecutar pip install aiohttp aiohttp-requests aiofiles almohada Es una instalación única. Si hay varias versiones diferentes de entornos Python, recuerde distinguirlas.
Luego abrimos el editor y comenzamos a modificar el código. Primero ajustamos la parte de la guía del paquete y reemplazamos las solicitudes con aiohttp-requests, así:
Luego busca solicitudes y echa un vistazo. en él donde se utiliza.
Luego cambie todas las partes buscadas a solicitudes asincrónicas.
No olvides cambiar todas las llamadas a solicitudes. Pasa a la coprogramación.
Luego, también cambiamos la parte de operación de archivos a operación asincrónica, usando aiofiles.open en lugar de open.
Después de cambiar la parte principal, movimos el código bajo if __name__ == '__main__': a una nueva función concurrente llamada run y agregamos await a todas las llamadas a la función concurrente anterior.
Importe la biblioteca asyncio y escriba este código en if __name__ == '__main__':
Lo anterior se aplica a Python 3.7 y versiones anteriores:
Ahora podemos ejecute el código y vea si funciona.
Aquí hay un error. Como se puede ver en la pila de errores, el problema radica en respuesta = await request.get(url=url, headers=headers) porque el método self.session._request lo hace. No tiene clave. Son los parámetros de la URL. Este problema es fácil de resolver, simplemente cambie url = url a url (no es necesario escribir tantos parámetros específicos). Todo el código que utiliza request.get y contiene url=url debe ajustarse:
Ejecútelo nuevamente después del ajuste, funciona muy bien y tiene el mismo efecto que el código original.
¡Atención! ¡Simplemente hacer esto no supone una gran diferencia en la velocidad! En última instancia, tuvimos que colocar las partes secuenciales y que consumen más tiempo de este conjunto de código en una función de coprocesador separada y usar asyncio.gather para llamadas simultáneas (excepto para solicitudes simultáneas debido a la naturaleza desordenada de la lógica original). realizó algunos otros ajustes, principalmente en términos de recuentos y rutas de archivos, que son irrelevantes).
Ejecútelo para ver el efecto. Solo toma un momento actualizar una fila de descargas completadas. En comparación con antes de la modificación, es un mundo de diferencia.
¡Éste es el poder de las solicitudes simultáneas! Simplemente ajustamos su código original, de modo que la parte más lenta de descargar imágenes se ejecutara de manera simple y tosca al mismo tiempo usando asyncio.gather, ¡y la velocidad cambió de una tortuga a tan rápida como un cohete! (De hecho, hay muchos puntos de optimización en el código, por lo que no los enumeraré todos).
Finalmente, quiero recordarles a todos:
Aunque las solicitudes simultáneas son muy buenos, pueden hacer que tu rastreador sea más rápido, ¡pero no está exento de problemas!
Si tiene demasiadas solicitudes simultáneas (también conocido como número de concurrencia demasiado alto), entonces su rastreador equivale a realizar un ataque DoS (ataque de denegación de servicio) en los servidores de otras personas.
Por ejemplo, si está rastreando un sitio web pequeño para acelerar su rastreo, no hay límite en la cantidad de solicitudes simultáneas, lo que hará que su rastreador realice cientos o miles de solicitudes al mismo tiempo. Pero, en general, los sitios web pequeños no pueden soportar una cantidad tan alta de concurrencia. ¡Casi instantáneamente, tu rastreador explotará! Imagínate, si fueras administrador de un sitio web, ¿qué pensarías al ver esta situación?
Si aún no comprende cómo se ve este ejemplo, puede crear un servicio web en el que simplemente coloque una página simple y luego abra cientos de solicitudes simultáneas para esta página, para que pueda experimentar cómo se sienten los demás.
Así que recuerde controlar razonablemente el número de solicitudes simultáneas y no ejercer demasiada presión sobre otros sitios web. Si les das a otros una manera de sobrevivir, ¡otros también te darán una manera de sobrevivir!
Por último, os dejo una pequeña tarea. ¿Cómo puedo agregar límites de concurrencia a este código modificado? Por favor dé su respuesta en la sección de comentarios. (Consejo: puede utilizar un motor de búsqueda para encontrar contenido relacionado con los límites de conexiones simultáneas de aiohttp y el corte de listas de Python)
Fin
Revisión de artículos anteriores
Cuándo escribes un programa rastreador ¿Qué debo hacer si encuentro una solicitud de aplicación con parámetros cifrados? Guía de introducción: segundo modo
Crawler Journey
¿Cuántas personas están leyendo este artículo?