python中在使用asyncio中使用requests

来源:互联网 发布:js笔试题及答案 编辑:程序博客网 时间:2024/05/29 18:35

起因

需要写个爬虫去爬一些数据,于是用python写了个,但由于众所周知的GIL锁问题,python的多线程其实效率并不高,于是准备采用协程的方法去实现,在写demo测试的时候就遇到问题了,使用await去等待requests的响应却是无效的

测试代码

    import asyncio    import requests    async def hello1(url):        print('55555555555555555555')        async with requests.get(url) as resp:        print(url, resp.status_code)        print('666666666666')    async def hello(n,url):        print("协程" + str(n) +"启动")        await hello1(url)        print("协程" + str(n) + "结束")    if __name__ == "__main__":        tasks = []        url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'        for i in range(0, 3):            tasks.append(hello(i,url))        loop = asyncio.get_event_loop()        loop.run_until_complete(asyncio.wait(tasks))        loop.close()

希望实现的效果是:

当hello1()执行requests.get请求时,服务器还没响应就先去执行其他的协程的代码,也就是输出55555555555部分,我给服务器上对应controller加了断点,希望得到的效果是协程启动-》输出5555,服务器未响应,切换协程-》协程启动-》输出55555-》协程启动-》输出55555,服务器点继续就输出响应码以及6666666

实际效果是:

第一条协程执行到请求部分就停下来了,等服务器响应,不会切到其他的协程去执行

第一次改进后:

import asyncioimport timeimport requests@asyncio.coroutinedef hello1(url):    yield print('55555555555555555555')    print('是否执行请求')    resp = requests.get(url)    print('666666666666')async def hello(n,url):    print("协程" + str(n) +"启动")    await hello1(url)    print("协程" + str(n) + "结束")if __name__ == "__main__":    tasks = []    url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'    for i in range(0, 3):        tasks.append(hello(i,url))    loop = asyncio.get_event_loop()    loop.run_until_complete(asyncio.wait(tasks))    loop.close()

输出结果为:

协程0启动
55555555555555555555
协程1启动
55555555555555555555
协程2启动
55555555555555555555
是否执行请求
显然每次执行到hello1()中的yield部分返回了,去切换到其他协程,但是下面的请求部分并未执行,这样的话不就等于三个协程都执行完前面的计算部分,然后再依次执行io部分,而不是第一个协程去执行io部分的同时,切到其他协程去执行计算部分

解决方案:

最后在一位前辈的指点下才知道问题,requests会阻塞asyncio循环,所以会出现收不到服务器响应的情况。可以使用aiohttp替代requests解决这个问题。

但是stack overflow给出了一个使用requests会阻塞asyncio的解决方法:

import asyncioimport requestsasync def hello1(url):    print('55555555555555555555')    loop = asyncio.get_event_loop()    await loop.run_in_executor(None, requests.get, url)    print('666666666666')async def hello(n,url):    print("协程" + str(n) +"启动")    await hello1(url)    print("协程" + str(n) + "结束")    # await hello2(n)if __name__ == "__main__":    tasks = []    url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'    for i in range(0, 100):        tasks.append(hello(i,url))    loop = asyncio.get_event_loop()    loop.run_until_complete(asyncio.wait(tasks))    loop.close()


测试了下,符合预期,把协程数量设置成100,虽然看上去都是先启动,全部启动完了再去运行结束,但是实际上,在其他协程的启动过程中,后台服务器是一直在接收请求的,也就是能实现挂起io操作转而执行其他线程的效果
参考链接:原问题
以及stackoverflow官网上关于这种问题的解释:
https://stackoverflow.com/questions/22190403/how-could-i-use-requests-in-asyncio

原创粉丝点击