2016-03-10 2 views
8

В предыдущий вопрос, один из авторов aiohttp любезно предложил способ fetch multiple urls with aiohttp используя новый async with синтаксис из Python 3.5:asyncio веб выскабливание 101: извлечение нескольких веб-адресов с 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 

Однако, когда один из session.get(url) запросов брейков (как указано выше из-за http://SDFKHSKHGKLHSKLJHGSDFKSJH.com) ошибка не обрабатывается, и вся вещь ломается.

Я искал способы вставки тестов о результате session.get(url), например, ищу места для try ... except ..., или для if response.status != 200:, но я просто не понять, как работать с async with, await и различными объектами.

С async with по-прежнему очень много новых примеров. Было бы очень полезно многим людям, если бы мастер asyncio мог показать, как это сделать. В конце концов, одна из первых вещей, которые большинство людей захотят протестировать, с asyncio получает несколько ресурсов одновременно.

Гол

Цель состоит в том, что мы можем осмотреть the_results и быстро увидеть, как:

  • этот URL не удалось (и почему: код статуса, может быть, имя исключения), или
  • это url работал, и вот полезный объект ответа

ответ

9

Я хотел бы использовать gather вместо wait, который может возвращать исключения, как объекты, не поднимая их. Затем вы можете проверить каждый результат, если это экземпляр некоторого исключения.

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)) 

Тесты:

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

Потрясающе, большое вам спасибо! Мне нужно переварить это, но, немного поиграв с ним, кажется, что он довольно гибкий. +1, принять. :) –

+1

Отличный ответ. Одна вещь, о которой мне интересно, поскольку вы сразу же перебираете результаты после выполнения 'asyncio.gather', было бы лучше сделать' asyncio.as_completed' в списке 'fetch'es? Таким образом, вы можете перебирать завершенные сразу и ждать их завершения? – dalanmiller

+0

@ dalanmiller: для этого требуется обработка исключений, как в ответе Padraic Cunningham. Но если вам нужен результат для каждого Будущего сразу, то это именно так. – kwarunek

4

Я далек от эксперта asyncio, но вы хотите поймать ошибку вы должны поймать ошибку сокета:

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) 

Запуск кода и печать 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()) 

Вы можете видеть, что мы получаем поймать ошибку и дальнейшие вызовы по-прежнему успешно возвращаются html.

Мы должны, вероятно, действительно будет ловить OSError в socket.error является A deprecated alias of OSError так питона 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) 

Если вы хотите также проверить ответ 200, положить, если в тоже попробовать и вы можете использовать атрибут причины, чтобы получить больше информации:

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

Спасибо так много! Два отличных ответа, я бы хотел выбрать оба. Выбор @kwarunek, потому что он работает из коробки, но +1, и я пойду, найду два ваших лучших ответа на вопрос.:) –