2016-03-10 33 views
8

In precedenza questione, uno degli autori di aiohttp modo gentilmente suggerito di fetch multiple urls with aiohttp utilizzando la nuova async with sintassi da Python 3.5:asyncio web scraping 101: recupero più URL con aiohttp

import aiohttp 
import asyncio 

async def fetch(session, url): 
    with aiohttp.Timeout(10): 
     async with session.get(url) as response: 
      return await response.text() 

async def fetch_all(session, urls, loop): 
    results = await asyncio.wait([loop.create_task(fetch(session, url)) 
            for url in urls]) 
    return results 

if __name__ == '__main__': 
    loop = asyncio.get_event_loop() 
    # breaks because of the first url 
    urls = ['http://SDFKHSKHGKLHSKLJHGSDFKSJH.com', 
      'http://google.com', 
      'http://twitter.com'] 
    with aiohttp.ClientSession(loop=loop) as session: 
     the_results = loop.run_until_complete(
      fetch_all(session, urls, loop)) 
     # do something with the the_results 

Tuttavia quando uno dei session.get(url) richieste rompe (come sopra a causa di http://SDFKHSKHGKLHSKLJHGSDFKSJH.com) l'errore non viene gestito e l'intera cosa si interrompe.

ho cercato modi per inserire le prove circa il risultato di session.get(url), per esempio, alla ricerca di luoghi per un try ... except ..., o per una if response.status != 200:, ma io non sono solo capire come lavorare con async with, await ei vari oggetti.

Dal async with è ancora molto nuovo non ci sono molti esempi. Sarebbe molto utile per molte persone se una procedura guidata asyncio mostrasse come eseguire questa operazione. Dopo tutto una delle prime cose che la maggior parte delle persone vorranno testare con asyncio sta ottenendo più risorse contemporaneamente.

Goal

L'obiettivo è che siamo in grado di ispezionare the_results e vedere rapidamente uno:

  • questo URL fallito (e perché: il codice di stato, forse il nome eccezione), o
  • questo url ha funzionato, ed ecco un utile oggetto di risposta

risposta

9

userei gather invece di wait, che può tornare come oggetti eccezioni, senza sollevare. Quindi puoi controllare ogni risultato, se è un'istanza di qualche eccezione.

import aiohttp 
import asyncio 

async def fetch(session, url): 
    with aiohttp.Timeout(10): 
     async with session.get(url) as response: 
      return await response.text() 

async def fetch_all(session, urls, loop): 
    results = await asyncio.gather(
     *[fetch(session, url) for url in urls], 
     return_exceptions=True # default is false, that would raise 
    ) 

    # for testing purposes only 
    # gather returns results in the order of coros 
    for idx, url in enumerate(urls): 
     print('{}: {}'.format(url, 'ERR' if isinstance(results[idx], Exception) else 'OK')) 
    return results 

if __name__ == '__main__': 
    loop = asyncio.get_event_loop() 
    # breaks because of the first url 
    urls = [ 
     'http://SDFKHSKHGKLHSKLJHGSDFKSJH.com', 
     'http://google.com', 
     'http://twitter.com'] 
    with aiohttp.ClientSession(loop=loop) as session: 
     the_results = loop.run_until_complete(
      fetch_all(session, urls, loop)) 

Test:

$python test.py 
http://SDFKHSKHGKLHSKLJHGSDFKSJH.com: ERR 
http://google.com: OK 
http://twitter.com: OK 
+0

Fantastico, grazie mille! Dovrò digerirlo, ma dopo averlo giocato un po 'sembra essere abbastanza flessibile. +1, accetta. :) –

+1

Ottima risposta. Una cosa di cui sono curioso, dato che immediatamente esegui un'iterazione sui risultati dopo aver eseguito 'asyncio.gather', non sarebbe meglio fare' asyncio.as_completed' sulla lista di 'fetch'es? In questo modo puoi scorrere immediatamente quelli completati contro l'attesa che tutti finiscano? – dalanmiller

+0

@dalanmiller: richiede la gestione delle eccezioni, come nella risposta di Padraic Cunningham. Ma se hai bisogno di risultati per ciascun Futuro immediatamente, allora questo è il modo. – kwarunek

4

Io sono lontano da un esperto asyncio ma tu vuoi rilevare l'errore è necessario prendere un errore di presa:

async def fetch(session, url): 
    with aiohttp.Timeout(10): 
     try: 
      async with session.get(url) as response: 
       print(response.status == 200) 
       return await response.text() 
     except socket.error as e: 
      print(e.strerror) 

L'esecuzione del codice e la stampa the_results:

Cannot connect to host sdfkhskhgklhskljhgsdfksjh.com:80 ssl:False [Can not connect to sdfkhskhgklhskljhgsdfksjh.com:80 [Name or service not known]] 
True 
True 
({<Task finished coro=<fetch() done, defined at <ipython-input-7-535a26aaaefe>:5> result='<!DOCTYPE ht...y>\n</html>\n'>, <Task finished coro=<fetch() done, defined at <ipython-input-7-535a26aaaefe>:5> result=None>, <Task finished coro=<fetch() done, defined at <ipython-input-7-535a26aaaefe>:5> result='<!doctype ht.../body></html>'>}, set()) 

Potete vedere otteniamo rilevare l'errore e le altre chiamate sono ancora riuscita ritorno l'html.

Probabilmente dovremmo davvero essere di prendere un OSError come socket.error è A deprecated alias of OSError dal python 3.3:

async def fetch(session, url): 
    with aiohttp.Timeout(10): 
     try: 
      async with session.get(url) as response: 
       return await response.text() 
     except OSError as e: 
      print(e) 

Se si desidera controllare anche la risposta è 200, mettere il vostro se nel cercare troppo e è possibile utilizzare l'attributo motivo per maggiori informazioni:

async def fetch(session, url): 
    with aiohttp.Timeout(10): 
     try: 
      async with session.get(url) as response: 
       if response.status != 200: 
        print(response.reason) 
       return await response.text() 
     except OSError as e: 
      print(e.strerror) 
+0

Grazie mille! Due grandi risposte, vorrei poter selezionare entrambe. Selezionando @kwarunek perché funziona fuori dalla scatola, ma +1 e andrò a trovare due delle tue migliori risposte per upvotare.:) –