如何在asyncio中使用请求?


126

我想在其中执行并行http请求任务asyncio,但是我发现这python-requests会阻止的事件循环asyncio。我找到了aiohttp,但它无法使用http代理提供http请求的服务。

所以我想知道是否有一种方法可以借助进行异步http请求asyncio


1
如果您只是发送请求,则可以subprocess用来并行处理您的代码。
WeaselFox 2014年

这种方法似乎不太优雅……
2014年

现在有一个异步请求端口。github.com/rdbhost/yieldfromRequests
Rdbhost 2015年

Answers:


181

要将请求(或任何其他阻塞库)与asyncio一起使用,可以使用BaseEventLoop.run_in_executor在另一个线程中运行一个函数,并从该线程中屈服以获得结果。例如:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

这将同时获得两个响应。

使用python 3.5可以使用new await/ async语法:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

有关更多信息,请参见PEP0492


5
您能解释一下这是如何工作的吗?我不明白这不会阻止。
Scott Coates

32
@christian,但是如果它同时在另一个线程中运行,那不是打破asyncio的观点吗?
Scott Coates

21
@scoarescoare这就是“如果做对了”部分的地方-在执行程序中运行的方法应该是独立的((与上面示例中的request.get相似))。这样,您就不必处理共享内存,锁定等问题,并且由于异步,您程序的复杂部分仍然是单线程的。
基督教徒

5
@scoarescoare主要用例是与不支持asyncio的IO库集成。例如,我正在使用真正古老的SOAP接口进行一些工作,并且使用suds-jurko库作为“最差”的解决方案。我正在尝试将其与asyncio服务器集成,因此我正在使用run_in_executor以看起来异步的方式进行阻塞的suds调用。
Lucretiel 2015年

10
真的很酷,它可以工作,因此对于旧的东西是如此容易,但是应该强调的是,它使用OS线程池,因此不会像aiohttp那样像真正面向asyncio的lib那样扩展
jsalter

78

aiohttp已经可以与HTTP代理一起使用:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())

连接器在这里做什么?
马库斯·梅斯卡宁

它提供了通过代理服务器的连接
mindmaster

16
与在单独的线程中使用请求相比,这是一个更好的解决方案。由于它是真正异步的,因此具有较低的开销和较低的内存使用率。
汤姆(Thom)2016年

14
对于python> = 3.5,将@ asyncio.coroutine替换为“ async”,将“ yield from”替换为“ await”
James

40

上面的答案仍在使用旧的Python 3.4样式协程。如果您使用的是Python 3.5以上版本,则应编写以下内容。

aiohttp 现在支持 http代理

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

1
您能否详细说明更多网址?当问题与并行http请求有关时,仅拥有一个URL是没有意义的。
匿名

传说。谢谢!效果出色
Adam

@ospider如何修改此代码以使用100个并行请求传递10k个URL?我们的想法是同时使用全部100个插槽,不等待100,以便开始下一个100交付
Antoan Milkov

@AntoanMilkov这是一个不同的问题,在评论区域无法回答。
ospider

@ospider你说得对,这里是问题:stackoverflow.com/questions/56523043/...
Antoan Milkov

11

请求目前不支持asyncio,也没有计划提供此类支持。这可能是因为你可以实现一个自定义的“传输适配器”(如讨论这里),它知道如何使用asyncio

如果我有一段时间的话,我可能会真正去研究,但是我什么也不能保证。


链接指向一个404
CodeBiker

8

Pimin Konstantin Kefaloukos 在Python和asyncio上进行的简单并行HTTP请求中有一篇很好的案例,介绍了异步/等待循环和线程 :

为了最大程度地减少总完成时间,我们可以增加线程池的大小以匹配我们必须发出的请求数量。幸运的是,这很容易做到,接下来我们将看到。下面的代码示例是如何使用二十个工作线程的线程池发出二十个异步HTTP请求的示例:

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

2
这样的问题是,如果我需要使用20个执行程序的块来运行10000个请求,则我必须等待所有20个执行程序完成才能从下一个20个执行程序开始,对吗?我不能这样做,for i in range(10000)因为一个请求可能失败或超时,对吗?
Sanandrea

1
您能解释一下为什么仅使用ThreadPoolExecutor就可以做到异步吗?
Asaf Pinhassi,

@lya Rusin基于什么,我们要设置max_workers的数量吗?它与CPU和线程数有关吗?
alt-f4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.