我怎么能在asyncio中使用请求?

fly*_*yer 112 python python-requests python-3.4 aiohttp

我想做并行的http请求任务asyncio,但我发现python-requests会阻塞事件循环asyncio.我发现了aiohttp,但它无法使用http代理提供http请求服务.

所以我想知道是否有办法在借助的帮助下进行异步http请求asyncio.

chr*_*ian 172

要使用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())
Run Code Online (Sandbox Code Playgroud)

这将同时获得两个响应.

使用python 3.5,您可以使用新的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())
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅PEP0492.

  • @christian但如果它在另一个线程中并发运行,那是不是打败了asyncio? (27认同)
  • @scoarescoare这就是'如果你做对了'部分的地方 - 你在执行程序中运行的方法应该是自包含的((大多数情况下)像上面例子中的requests.get).这样你就不必处理共享内存,锁定等等,而且程序的复杂部分仍然是单线程的,这要归功于asyncio. (18认同)
  • 非常酷,这是有效的,所以很容易遗留的东西,但应该强调这使用一个操作系统线程池,因此不会扩展为真正的asyncio定向lib像aiohttp一样 (9认同)
  • 你能解释一下它是如何工作的吗?我不明白这是怎么阻止的. (5认同)
  • @scoarescoare主要用例是与不支持asyncio的IO库集成.例如,我正在使用真正古老的SOAP接口做一些工作,而我正在使用suds-jurko库作为"最不好"的解决方案.我正在尝试将它与asyncio服务器集成,所以我使用run_in_executor以*看起来*异步的方式进行阻塞suds调用. (5认同)
  • `run_in_executor` 不允许为目标回调函数传递 kwargs(如 `loop.run_in_executor(None, requests.get, 'http://www.google.com', auth=blah) # failed`,这可以是使用 lambda 或 functools.partial 作为代理实现:`loop.run_in_executor(None, lambda: requests.get('http://www.google.com', auth=blah))`。参见 https://www。 python.org/dev/peps/pep-3156/#callback-style (4认同)
  • @scoarescoare根据文档,run_in_executor()将使用Executor(默认情况下为[ThreadPoolExecutor](http://docs.python.org/3.4/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor))在不同的线程(或指定的子进程)中运行方法并等待结果.run_in_executor()使用Executor的优点是它与asyncio很好地集成. (2认同)
  • @christian是的,关于它触发一个呼叫并恢复执行的部分是有道理的.但如果我理解正确,`requests.get`将在另一个线程中执行.我相信asyncio的一个重要优点是保持单线程的概念:不必处理共享内存,锁定等等.我认为我的困惑在于你的例子同时使用asyncio和concurrent.futures模块. (2认同)
  • @bluppfisk问题是“requests.get()”不是异步的,因此当“do_get()”调用“requests.get()”时,该函数将阻塞。仅当函数产生控制权(使用“await”)时,事件循环才会执行其他操作,而“requests.get()”则不会。由于它是阻塞的,所以同时运行其他东西的唯一方法是在另一个线程/进程中,这就是 run_in_executor() 所做的。 (2认同)

min*_*ter 73

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())
Run Code Online (Sandbox Code Playgroud)

  • 这是一个更好的解决方案,然后在单独的线程中使用请求.由于它是真正的异步,因此它具有更低的开销和更低的内存使用率. (14认同)
  • for python> = 3.5用"async"替换@ asyncio.coroutine,用"await"替换"yield from" (11认同)

osp*_*der 28

上面的答案仍然使用旧的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())
Run Code Online (Sandbox Code Playgroud)

  • 你能详细说明更多的网址吗?当问题是关于并行 http 请求时,只有一个 url 是没有意义的。 (2认同)

Luk*_*asa 9

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

如果我发现自己有一段时间,我可能会真正研究,但我不能保证任何事情.

  • 该链接会导致 404。 (3认同)

Art*_*kov 9

考虑到 aiohttp 是功能齐全的 Web 框架,我\xe2\x80\x99d 建议使用更轻量级的东西,例如支持异步请求的 httpx ( https://www.python-httpx.org/ )。它具有与请求几乎相同的 api:

\n
>>> async with httpx.AsyncClient() as client:\n...     r = await client.get(\'https://www.example.com/\')\n...\n>>> r\n<Response [200 OK]>\n
Run Code Online (Sandbox Code Playgroud)\n


Ily*_*sin 7

有异步的一个很好的例子/等待循环并且通过Pimin康斯坦丁Kefaloukos的一篇文章中线程 Python和ASYNCIO易于并行的HTTP请求:

为了最小化总完成时间,我们可以增加线程池的大小以匹配我们必须进行的请求的数量.幸运的是,这很容易做到,我们将在下面看到.下面的代码清单是如何使用20个工作线程的线程池生成20个异步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())
Run Code Online (Sandbox Code Playgroud)

  • 问题是,如果我需要使用20个执行程序的块来运行10000个请求,则我必须等待所有20个执行程序完成才能从下一个20个执行程序开始,对吗?我不能为for for in range(10000)做,因为一个请求可能失败或超时,对吗? (2认同)
  • 你能解释一下为什么你需要 asyncio 当你只使用 ThreadPoolExecutor 就可以做同样的事情吗? (2认同)
  • @alt-f4 实际上,你有多少个 CPU 并不重要。将这项工作委托给线程(以及 asyncio 的全部意义)的目的是为了 IO 绑定操作。线程将简单地空闲(“等待”)从套接字检索响应。asyncio 能够实际处理许多并发(不是并行!)请求,而根本没有线程(好吧,只有一个)。但是,“requests”不支持 asyncio,因此您需要创建线程来获得并发。 (2认同)