使用aiohttp的Python 3.6异步GET请求同步运行

时间:2018-03-05 19:58:12

标签: python python-3.x http python-asyncio aiohttp

我的功能正常如下,但由于某种原因,请求似乎是同步执行,而不是异步执行。

我现在的假设是,由于main函数中的for record in records for循环,这种情况正在发生,但我不确定如何更改此函数以便请求可以执行异步。如果不是这样,我还需要改变什么?

async def do_request(query_string):
        base_url = 'https://maps.googleapis.com/maps/api/place/textsearch/json?'
        params = {'key': google_api_key,
                  'query': query_string}
        async with aiohttp.ClientSession() as session:
            async with session.request('GET', base_url, params=params) as resp:
                return resp


async def main():
    create_database_and_tables()
    records = prep_sample_data()[:100]

    for record in records:
        r = Record(record)

        if not r.is_valid:
            continue

        query_string = r.generate_query_string()

        resp = await do_request(query_string)
        print("NOW WRITE TO DATABASE")

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

2 个答案:

答案 0 :(得分:6)

您正在等待单独的do_request()来电。而不是直接等待它们(在协程完成之前阻塞),使用asyncio.gather() function让事件循环同时运行它们:

async def main():
    create_database_and_tables()
    records = prep_sample_data()[:100]

    requests = []
    for record in records:
        r = Record(record)

        if not r.is_valid:
            continue

        query_string = r.generate_query_string()

        requests.append(do_request(query_string))

    for resp in asyncio.gather(*requests):
        print("NOW WRITE TO DATABASE")

asyncio.gather()返回值是协同程序返回的所有结果的列表,其顺序与您将它们传递给gather()函数的顺序相同。

如果您需要原始记录来处理回复,您可以通过几种不同的方式配对记录和查询字符串:

  • 将有效记录存储在单独的列表中,并在处理回复时使用zip()将其重新配对
  • 使用一个帮助程序,该程序获取有效记录,生成查询字符串,调用请求,并将记录和响应一起作为元组返回。

您还可以将响应处理混合到聚集的协程中;一个记录,生成查询字符串,等待do_request,然后在响应准备好时将结果存储在数据库中。

换句话说,将你需要连续发生的工作分开,在协程中并收集它们。

答案 1 :(得分:2)

建立Martijn的答案

如果请求的顺序对您来说太重要(当它被写入数据库时​​),您可以在获取命令时将响应写入数据库。

编辑(解释更多):我在这里使用2个信号量。 1是通过aiohttp限制连接数。这取决于您的系统。大多数Linux系统默认为1024.根据我自己的个人经验,将其设置为低于操作系统最大值是可取的。

max_coroutines是为了解决同时运行过多协程的问题。

我使用asyncio.ensure_future(),以便在构建列表时运行协同程序。这样,你就不会在执行任何协同程序之前创建完整的协程列表。

# Limit the total number of requests you make by 512 open connections.
max_request_semaphore = asyncio.BoundedSemaphore(512)
max_coroutines = asyncio.BoundedSemaphore(10000)


async def process_response(response):
    print('Process your response to your database')


async def do_request(query_string):
    base_url = 'https://maps.googleapis.com/maps/api/place/textsearch/json?'
    params = {'key': google_api_key,
              'query': query_string}
    async with max_request_semaphore:
        async with aiohttp.ClientSession() as session:
            async with session.request('GET', base_url, params=params) as resp:
                return resp


# Excuse me for the bad function naming
async do_full_request(query_string):
    resp = await do_request(query_string)
    await process_response(resp)
    max_coroutines.release()

async def main():
    create_database_and_tables()
    records = prep_sample_data()[:100]

    requests = []
    for record in records:
        r = Record(record)

        if not r.is_valid:
            continue

        query_string = r.generate_query_string()

        # Will prevent more than 10k coroutines created.
        await max_coroutines.acquire()
        requests.append(
            asyncio.ensure_future(
                do_full_request(query_string)))

    # Now gather all the coroutines
    await asyncio.gather(*requests)


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