Ir al contenido principal

A simple python async spider (async programming with python 3.6 step 2)

As a second step learning async programming I developed a very simple spider with python 3.6 and asynchronous developing.

In this case the spider request a bunch of urls. The server that is serving waits on each request.

http://localhost:8000/1

The number '1' tells the number of seconds that the server should wait:

http://localhost:8000/2 -> makes server wait for 2 seconds
http://localhost:8000/5 -> makes server wait for 5 seconds

I'm testing with a server that makes to wait my consumer, feel free to use random waits or whatever you prefer.

The consumer accepts two queues, a queue for urls to retrieve, and a queue to store the results.

At the moment the consumer don't store nothing at the urls queue, only retrieve the urls configured on the hardcoded urls list.

More functionalities will be added in the future.

In this example aiohttp==2.3.10 is used.

(iospider) $ pip install "aiohttp==2.3.10"


import asyncio
from contextlib import closing
from time import perf_counter

import aiohttp

SPIDER_WORKERS = 16

async def consume(client: aiohttp.ClientSession, queue_results: asyncio.Queue, queue_urls: asyncio.Queue):
    while True:
        if queue_urls.empty():
            break
        url = await queue_urls.get()
        print(f'consumed {url}')
        with aiohttp.Timeout(10):
            async with client.get(url) as response:
                if response.status == 200:
                    page = await response.text()
                    await queue_results.put(page)


def run(queue_results: asyncio.Queue, queue_urls: asyncio.Queue, workers: int):
    with closing(asyncio.get_event_loop()) as loop:
        with aiohttp.ClientSession() as client:
            tasks = [consume(client, queue_results, queue_urls) for i in range(workers)]
            loop.run_until_complete(asyncio.gather(*tasks))

urls = ['http://localhost:8000/1', 'http://localhost:8000/2', 'http://localhost:8000/3', 'http://localhost:8000/4'] * 6
start = perf_counter()
queue_urls = asyncio.Queue()
queue_results = asyncio.Queue()
[queue_urls.put_nowait(url) for url in urls]
run(queue_results, queue_urls, SPIDER_WORKERS if queue_urls.qsize() > SPIDER_WORKERS else queue_urls.qsize())
print(f'Retrieved {queue_results.qsize()} pages in {perf_counter() - start}')

Example output, for 24 urls, with the spider configured with 16 default workers (same as scrapy).

# IOSpider output

(iospider) jesus@laptop:~/iospider$ time python iospider.py
Creating a client session outside of coroutine
client_session: <aiohttp.client.ClientSession object at 0x7f25c55ea208>
consumed http://localhost:8000/1
consumed http://localhost:8000/4
...
consumed http://localhost:8000/2
consumed http://localhost:8000/3
...
consumed http://localhost:8000/1

consumed http://localhost:8000/4
Retrieved 24 pages in 6.038133026999731

real    0m6,222s
user    0m0,217s
sys    0m0,024s

Using scrapy to retrieve the same number of urls with the same number of workers:

(scrapy) jesus@laptop:~/scrapy$ time scrapy runspider -s CONCURRENT_REQUESTS=16 -s CONCURRENT_REQUESTS_PER_DOMAIN=16 client_scrapy.py

# Scrapy output
...

real    0m6,870s
user    0m0,860s
sys    0m0,036s

The average times show that IOSpider is faster than Scrapy, but we need to consider that IOSpider is a very simplistic approach with only one main feature.

Comentarios

Entradas populares de este blog

Join o producto cartesiano de dos tablas en EXCEL 2007

Hace unos dias inicie mi ocupacion como becario de informatica en la facultad de humanidades y ciencias de la educacion de la UJAEN. Y como no, no han tardado en surgir los problemas. Supongamos que tenemos dos tablas, y queremos hacer una tabla que tenga datos de estas dos tablas, segun un criterio , y es que solo pueden aparecer ciertas filas, mas exactamente aquellas donde coincida cierto campo, en este ejemplo, el codigo de la asignatura. Si queremos realizar el join o producto cartesiano tal y como lo hariamos en una base de datos, parece ser que si no estamos trabajando con una bbdd sino con Excel, la cosa se complica un poco. Para "multiplicar tablas" en excel, primero vamos a hacer una cosa, cada tabla la vamos a guardar en hojas separadas, en nuestro caso, una tabla la guardamos en Hoja1 , y la otra en Hoja2 Ahora, nos situamos en la hoja donde queramos que aparezca el producto cartesiano de nuestras dos tablas, nos vamos a la ficha DATOS . Veremos que h

Descargar código fuente desde Google App Engine

Estaba desarrollando una aplicación en google app engine, cuando un día, al llegar al trabajo (hoy), me doy cuenta que no tengo acceso a mi versión de desarrollo. Como la ultima versión que estaba desarrollando, justo la noche de antes la había subido a google app engine, pues me dije: "Ya esta, me conecto y me descargo el código fuente" ERROR 404 // SOLUCIÓN A IDEA MÁGICA NO ENCONTRADA Tras buscar por google, observo que hay muchas voces que dicen que no te puedes descargar el código fuente, que google no deja disponible ninguna API para descargarte tu codigo, ... ¡Pero como va a ser así!, desde appengine, te dicen que se puede hacer, lo que no está tan claro es como hacerlo. Pues estos son los pasos para poder hacerlo: Crear un directorio vacío para poder descargar en el nuestra aplicación. Abrir la línea de comandos, y cambiarnos al directorio de google app engine: cd C:\Archivos de Programa\Google\google_appengine\ Ejecutar el siguiente comando para descar

Clases abstractas con python

¿Como se crean clases abstractas con python?. Voy a explicar cual es la forma correcta de definir una clase abstracta y heredar de ella. El procedimiento general es: Definir una clase abstracta utilizando una metaclase. Definir la subclase de la clase abstracta (sin herencia). Registrar esta última clase como subclase de la clase abstracta. Tomemos como ejemplo el siguiente código: from abc import ABCMeta, abstractmethod class AbstractFoo:     __metaclass__ = ABCMeta          @abstractmethod     def bar(self):         pass     @classmethod     def __subclasshook__(cls, C):         return NotImplemented class Foo(object):     def bar(self):         print "hola" AbstractFoo.register(Foo)  Lo primero que hacemos es importar del módulo abc la clase ABCMeta y el decorador abstractmethod . La clase ABCMeta es la metaclase que utilizamos para definir las clases abstractas, nos aporta una serie de funcionalidades. Una vez hemos asignada la metacl