【问题标题】:Fetch multiple URLs with asyncio/aiohttp and retry for failures使用 asyncio/aiohttp 获取多个 URL 并重试失败
【发布时间】:2019-07-01 18:15:08
【问题描述】:

我正在尝试使用 aiohttp 包编写一些异步 GET 请求,并且已经弄清楚了大部分内容,但我想知道处理失败(作为异常返回)时的标准方法是什么。

到目前为止我的代码的总体思路(经过反复试验,我遵循here 的方法):

import asyncio
import aiofiles
import aiohttp
from pathlib import Path

with open('urls.txt', 'r') as f:
    urls = [s.rstrip() for s in f.readlines()]

async def fetch(session, url):
    async with session.get(url) as response:
        if response.status != 200:
            response.raise_for_status()
        data = await response.text()
    # (Omitted: some more URL processing goes on here)
    out_path = Path(f'out/')
    if not out_path.is_dir():
        out_path.mkdir()
    fname = url.split("/")[-1]
    async with aiofiles.open(out_path / f'{fname}.html', 'w+') as f:
        await f.write(data)

async def fetch_all(urls, loop):
    async with aiohttp.ClientSession(loop=loop) as session:
        results = await asyncio.gather(*[fetch(session, url) for url in urls],
                return_exceptions=True)
        return results

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(fetch_all(urls, loop))

现在运行正常:

  • 正如预期的那样,results 变量填充有 None 条目,其中对应的 URL [即在urls数组变量中的相同索引处,即在输入文件urls.txt]中的相同行号处被成功请求,并且相应的文件被写入磁盘。
  • 这意味着我可以使用 results 变量来确定哪些 URL 不成功(results 中的那些条目不等于 None

我查看了一些使用各种异步 Python 包(aiohttpaiofilesasyncio)的不同指南,但我还没有看到处理这最后一步的标准方法。

  • 是否应该在 await 语句“完成”/“完成”之后重试发送 GET 请求?
  • ...或者重试发送 GET 请求是否应该在失败时由某种回调启动
    • 错误看起来像这样:(ClientConnectorError(111, "Connect call failed ('000.XXX.XXX.XXX', 443)") 即在端口443 上对IP 地址000.XXX.XXX.XXX 的请求失败,可能是因为服务器有一些限制,我应该在重试之前等待超时。
  • 我是否可以考虑设置某种限制,以批量处理请求而不是全部尝试?
  • 尝试列表中的几百个(超过 500 个)URL 时,我收到了大约 40-60 个成功请求。

天真地,我期待 run_until_complete 以这样的方式处理这个问题,它会在成功请求所有 URL 后完成,但事实并非如此。

我以前没有使用过异步 Python 和会话/循环,所以如果能帮助我找到如何获取 results,我将不胜感激。如果我能提供更多信息来改进这个问题,请告诉我,谢谢!

【问题讨论】:

    标签: python python-asyncio aiohttp


    【解决方案1】:

    是否应该在等待语句“完成”/“完成”后重试发送 GET 请求? ...或者是否应该在失败时通过某种回调来重试发送 GET 请求

    你可以做前者。您不需要任何特殊的回调,因为您在协程中执行,所以一个简单的while 循环就足够了,并且不会干扰其他协程的执行。例如:

    async def fetch(session, url):
        data = None
        while data is None:
            try:
                async with session.get(url) as response:
                    response.raise_for_status()
                    data = await response.text()
            except aiohttp.ClientError:
                # sleep a little and try again
                await asyncio.sleep(1)
        # (Omitted: some more URL processing goes on here)
        out_path = Path(f'out/')
        if not out_path.is_dir():
            out_path.mkdir()
        fname = url.split("/")[-1]
        async with aiofiles.open(out_path / f'{fname}.html', 'w+') as f:
            await f.write(data)
    

    天真地,我期待 run_until_complete 以这样的方式处理这个问题,它会在成功请求所有 URL 后完成

    术语“完成”是指协程完成(运行它的过程)的技术意义,这是通过协程返回或引发异常来实现的。

    【讨论】:

      猜你喜欢
      • 2016-06-25
      • 1970-01-01
      • 2019-01-14
      • 2016-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-14
      相关资源
      最近更新 更多