Python中asyncio批量获取时await对象为空,如何解决?

起因是我要用到协程去批量验证多个 http 地址情况:当前 url 地址 ,http 状态 ,title

import asyncio,aiohttp
from bs4 import BeautifulSoup
res=[]
async def fetch_async(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            url=resp.url
            status=resp.status
            text=await resp.read()
            soup = BeautifulSoup(text, 'lxml')
            title=soup.title.string
            print(url,status,title)
            #res.append(url,status,title)

tasks = [fetch_async(‘http://www.baidu.com/’), fetch_async(‘https://www.v2ex.com’)]

event_loop = asyncio.get_event_loop() results = event_loop.run_until_complete(asyncio.gather(*tasks)) event_loop.close()

http://www.baidu.com/ 200 百度一下,你就知道 https://www.v2ex.com 200 V2EX

遵从一个函数只干一件事的原则,于是我将整个 response 对象返回到列表里了,一切 ok .

就当我要把它们取出的时候 。

for i in res: i.url —>https;//xx.com i.status —>200 i.read() ->null 由于 response 的 read() 要使用 await 不然是获取不到的。

我尝试 :

res.append(await response) print(len(res)) --> 0

现在是没有办法了,还望表哥们给点思路


Python中asyncio批量获取时await对象为空,如何解决?

7 回复
  1. asyncio.create_task(fetch_async('<a target="_blank" href="http://www.baidu.com/'" rel="nofollow noopener">http://www.baidu.com/'</a>))
    2. 用 queue 而不是 list,并发执行是这里有竞争

这个问题我遇到过,通常是因为在异步循环中批量创建任务时,没有正确等待所有任务完成。

核心问题是:当你用asyncio.create_task()创建多个任务后,如果没有用asyncio.gather()asyncio.wait()来收集结果,这些任务可能还在后台运行,你直接去访问结果就会得到空值。

看个具体例子:

import asyncio

async def fetch_data(item_id):
    await asyncio.sleep(1)  # 模拟网络请求
    return f"data_{item_id}"

async def wrong_way():
    tasks = []
    for i in range(5):
        task = asyncio.create_task(fetch_data(i))
        tasks.append(task)
    
    # 错误:直接访问task对象,此时任务可能还没完成
    results = []
    for task in tasks:
        results.append(task)  # 这里得到的是task对象,不是结果
    
    return results

async def right_way():
    tasks = []
    for i in range(5):
        task = asyncio.create_task(fetch_data(i))
        tasks.append(task)
    
    # 正确:等待所有任务完成并收集结果
    results = await asyncio.gather(*tasks)
    return results

# 测试错误方式
print("错误方式的结果:")
wrong_results = asyncio.run(wrong_way())
for r in wrong_results:
    print(type(r), r)  # 输出的是Task对象

print("\n正确方式的结果:")
right_results = asyncio.run(right_way())
for r in right_results:
    print(r)  # 输出实际的数据

关键点:

  1. asyncio.create_task()只是把协程包装成Task并开始执行,不等待完成
  2. 必须用await asyncio.gather(*tasks)等待所有任务完成并获取结果
  3. 或者用await asyncio.wait(tasks),然后从完成的task中提取结果

如果你用的是列表推导式创建任务,也要记得等待:

# 这样不行
tasks = [asyncio.create_task(fetch_data(i)) for i in range(5)]
# 任务还没完成就去访问

# 这样才行
tasks = [asyncio.create_task(fetch_data(i)) for i in range(5)]
results = await asyncio.gather(*tasks)

检查你的代码,确保在批量创建任务后正确等待了所有任务完成。

总结:用asyncio.gather等待所有任务完成再取结果。

遵从一个函数只干一件事的原则,于是我将整个 response 对象返回到列表里了

-----

你为何不将 response 读出来放到 res 里呢? 好好想想如何界定"一件事"

你必须在 async with session.get(url) as resp:这个上下文里面 await resp.read()或者 await resp.text() 你离开这个上下文这个链接已经关闭了,你就再也不能从这个 response 里面获取数据.

将整个 response 对象存起来,只能存到当时的状态,需要 await 的属性 就再也拿不到了嘛。
那我也有尝试过 append(await response ) 还是不可以

aiohttp 的 response 是惰性读取的,也就是只有你在 await resp.read()的时候才会真正的从 TCP 缓冲区读取,当你关闭这个 resp 的时候,http 连接已经关闭,就不可能读到数据了
当然你 await response 有什么用,这样既不会真正的读取,aiohttp 的 response 对象也不是可重用的,你只能自己 new 一个 dict 来把 resp 中你想要的数据保存出来,再 append 进 res

是的。response,只是带有一个从缓冲区取数据的方法,但你离开缓冲区了(不在 response 上下文),要去调用这个方法取数据也取不到数据了,你需要在没离开前取出数据 例如一个不是很好的实例 response.text = await response.text() 之后你就能从 response.text 拿到数据了

回到顶部