如何使用 asyncio 在 Python 3 中异步运行 requests.get?

Posted

技术标签:

【中文标题】如何使用 asyncio 在 Python 3 中异步运行 requests.get?【英文标题】:How to run requests.get asynchronously in Python 3 using asyncio? 【发布时间】:2018-01-10 21:13:41 【问题描述】:

我正在尝试创建简单的网络监控脚本,它定期和异步地向列表中的 url 发送 GET 请求。这是我的请求函数:

def request(url,timeout=10):
    try:
        response = requests.get(url,timeout=timeout)
        response_time = response.elapsed.total_seconds()
        if response.status_code in (404,500):
            response.raise_for_status()
        html_response = response.text
        soup = BeautifulSoup(html_response,'lxml')
        # process page here
        logger.info("OK . Response time:  seconds".format(url,response_time))
    except requests.exceptions.ConnectionError:
        logger.error('Connection error.  is down. Response time:  seconds'.format(url,response_time))
    except requests.exceptions.Timeout:
        logger.error('Timeout.  not responding. Response time:  seconds'.format(url,response_time))
    except requests.exceptions.HTTPError:
        logger.error('HTTP Error.  returned status code . Response time:  seconds'.format(url,response.status_code, response_time))
    except requests.exceptions.TooManyRedirects:
        logger.error('Too many redirects for . Response time:  seconds'.format(url,response_time))
    except:
        logger.error('Content requirement not found for . Response time:  seconds'.format(url,response_time))

在这里我为所有网址调用此函数:

def async_requests(delay,urls):
    for url in urls:
        async_task = make_async(request,delay,url,10)
        loop.call_soon(delay,async_task)
    try:
        loop.run_forever()
    finally:
        loop.close()

delay 参数是循环间隔,它描述了函数需要执行的频率。为了循环request,我创建了这样的东西:

def make_async(func,delay,*args,**kwargs):

    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        loop.call_soon(delay, wrapper)

    return wrapper

每次执行async_requests 时,每个网址都会出现此错误:

Exception in callback 1.0(<function mak...x7f1d48dd1730>)
handle: <Handle 1.0(<function mak...x7f1d48dd1730>)>
Traceback (most recent call last):
  File "/usr/lib/python3.5/asyncio/events.py", line 125, in _run
    self._callback(*self._args)
TypeError: 'float' object is not callable

此外,每个 url 的 request 函数也没有按预期定期执行。我在async_requests 之后的打印功能也没有执行:

async_requests(args.delay,urls)
print("Starting...")

我知道我在代码中做错了,但我不知道如何解决这个问题。我是 python 的初学者,对 asyncio 不是很有经验。 总结我想要达到的目标:

在不阻塞主线程的情况下,针对特定 url 以异步和周期性的方式运行 request。 异步运行async_requests,这样我就可以启动一个简单的http服务器 例如在同一个线程中。

【问题讨论】:

call_soon 与您使用它的方式不同。它接受一个回调作为它的第一个参数。接下来的所有内容都是传递给回调的参数。一旦控制返回事件循环,回调就会被调用。你要找的是call_later 【参考方案1】:
except:

它还会捕获服务异常行 KeyboardInterruptStopIteration。永远不要做这样的事情。而是写:

except Exception:

如何在 Python 3 中使用 asyncio 异步运行 requests.get?

requests.get 本质上是阻塞的。

您应该为 aiohttp 模块之类的请求找到异步替代方案:

async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

或在单独的线程中运行 requests.get 并使用 loop.run_in_executor 等待该线程异步:

executor = ThreadPoolExecutor(2)

async def get(url):
    response = await loop.run_in_executor(executor, requests.get, url)
    return response.text

【讨论】:

以上是关于如何使用 asyncio 在 Python 3 中异步运行 requests.get?的主要内容,如果未能解决你的问题,请参考以下文章

使用 asyncio ProactorEventLoop 时如何分配线程池

如何在asyncio python中使用子进程模块限制并发进程数

异常事件循环在 python 3.8 中使用 aiohttp 和 asyncio 关闭

在Python中使用Asyncio系统(3-4)​Task 和 Future

所有任务完成后如何终止python asyncio event_loop

如何在 python asyncio 中等待 select.select 调用