web-dev-qa-db-ja.com

asyncio webスクレイピング101:aiohttpで複数のURLを取得する

以前の質問で、aiohttpの作成者の1人は、_async with_の新しい_Python 3.5_構文を使用して aiohttpで複数のURLをフェッチする の方法を親切に提案しました。

_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)要求の1つが中断すると(上記のように_http://SDFKHSKHGKLHSKLJHGSDFKSJH.com_のため)、エラーは処理されず、すべてが中断します。

_try ... except ..._、または_if response.status != 200:_の場所を探すなど、session.get(url)の結果に関するテストを挿入する方法を探しましたが、操作方法がわかりません_async with_、awaitおよびさまざまなオブジェクト。

_async with_はまだ非常に新しいため、多くの例はありません。 asyncioウィザードがこれを行う方法を示すことができれば、多くの人にとって非常に役立ちます。結局のところ、ほとんどの人が最初にasyncioでテストしたいのは、複数のリソースを同時に取得することです。

目標

目標は、_the_results_を検査して、次のいずれかをすばやく確認できることです。

  • このURLは失敗しました(および理由:ステータスコード、おそらく例外名)、または
  • このURLは機能しました、そしてここに便利な応答オブジェクトがあります
21
Hans Schindler

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
19
kwarunek

私は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を返す呼び出しが成功していることがわかります。

Socket.errorは OSErrorの非推奨のエイリアス であるため、OSErrorを実際にキャッチする必要があります。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)

応答も200であることを確認したい場合は、ifもtryに入れ、reason属性を使用して詳細を取得できます。

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