使用 asyncio/aiohttp 获取多个 URL 并重试失败
Fetch multiple URLs with asyncio/aiohttp and retry for failures
我正在尝试使用 aiohttp 包编写一些异步 GET 请求,并且已经弄清楚了大部分内容,但我想知道处理失败(作为异常返回)时的标准方法是什么。
到目前为止我的代码的总体思路(经过反复试验,我遵循的方法 ):
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
] 已成功请求,并将相应的文件写入磁盘。
- 这意味着我可以使用结果变量来确定哪些 URL 不成功(
results
中的条目不等于 None
)
我查看了一些使用各种异步 Python 包(aiohttp
、aiofiles
和 asyncio
)的不同指南,但我还没有看到处理这最后一步的标准方法。
- 是否应该在
await
语句有 'finished'/'completed' 之后重试发送 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 和 sessions/loops,所以如果能帮助我找到如何获得 results
,我将不胜感激。如果我能提供更多信息来改进这个问题,请告诉我,谢谢!
Should the retrying to send a GET request be done after the await statement has 'finished'/'completed'? ...or should the retrying to send a GET request be initiated by some sort of callback upon failure
你可以做前者。您不需要任何特殊的回调,因为您是在协程内部执行的,所以一个简单的 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)
Naively, I was expecting run_until_complete
to handle this in such a way that it would finish upon succeeding at requesting all URLs
术语"complete"在协程的技术意义上是指完成(运行它的过程),这是通过协程返回来实现的或引发异常。
我正在尝试使用 aiohttp 包编写一些异步 GET 请求,并且已经弄清楚了大部分内容,但我想知道处理失败(作为异常返回)时的标准方法是什么。
到目前为止我的代码的总体思路(经过反复试验,我遵循的方法
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
] 已成功请求,并将相应的文件写入磁盘。 - 这意味着我可以使用结果变量来确定哪些 URL 不成功(
results
中的条目不等于None
)
我查看了一些使用各种异步 Python 包(aiohttp
、aiofiles
和 asyncio
)的不同指南,但我还没有看到处理这最后一步的标准方法。
- 是否应该在
await
语句有 'finished'/'completed' 之后重试发送 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 和 sessions/loops,所以如果能帮助我找到如何获得 results
,我将不胜感激。如果我能提供更多信息来改进这个问题,请告诉我,谢谢!
Should the retrying to send a GET request be done after the await statement has 'finished'/'completed'? ...or should the retrying to send a GET request be initiated by some sort of callback upon failure
你可以做前者。您不需要任何特殊的回调,因为您是在协程内部执行的,所以一个简单的 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)
Naively, I was expecting
run_until_complete
to handle this in such a way that it would finish upon succeeding at requesting all URLs
术语"complete"在协程的技术意义上是指完成(运行它的过程),这是通过协程返回来实现的或引发异常。