Concurrent.futures结合Requests HTML调用render()报线程无事件循环问题问询
问题成因
- requests-html的
render()方法底层依赖pyppeteer执行浏览器渲染,而pyppeteer是基于asyncio异步框架实现的 - asyncio的事件循环默认和线程绑定:只有主线程会默认创建事件循环,ThreadPoolExecutor启动的子线程没有默认的事件循环
- 你在子线程执行的
load_page_and_extract_items里调用render()时,内部执行asyncio.get_event_loop()找不到当前子线程的事件循环,就抛出了对应报错
关于把render移到as_completed循环的疑问
你把render()移到as_completed的遍历逻辑中时,代码是在主线程执行的,主线程存在默认事件循环所以不会报错,但此时所有页面的渲染操作是串行执行的,确实完全失去了并行处理的速度优势。
解决方案(两种可选,无需深入了解asyncio也能实现)
方案1:给子线程手动添加事件循环(改动最小,保留原有多线程逻辑)
只需要修改load_page_and_extract_items函数,在调用render()前给当前子线程创建并绑定事件循环即可:
import asyncio def load_page_and_extract_items(url): # 给当前子线程创建并设置事件循环 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) response = session.get(url, headers=get_headers()) # render javascript response.html.render(timeout=60, wait=3) source = BeautifulSoup(response.html.raw_html, 'lxml') # 用完关闭事件循环避免资源泄漏 loop.close()
这个方案改动不到5行代码,完全不需要你理解asyncio的运行逻辑,就能保留原有多线程并行渲染的能力。
方案2:使用requests-html自带的异步API(性能更优,资源占用更低)
requests-html本身提供了AsyncHTMLSession原生支持异步并发渲染,比多线程方案更适合IO密集的页面渲染场景,实现难度也很低:
import asyncio from requests_html import AsyncHTMLSession async def load_page_and_extract_items(url, session): response = await session.get(url, headers=get_headers()) await response.html.render(timeout=60, wait=3) source = BeautifulSoup(response.html.raw_html, 'lxml') # 这里补充你原有提取数据的逻辑 async def get_pages(remaining_urls): session = AsyncHTMLSession() tasks = [load_page_and_extract_items(url, session) for url in remaining_urls] # 批量并发执行所有页面的加载和渲染 await asyncio.gather(*tasks) await session.close() def main(): asyncio.run(get_pages(urls))
这个方案不需要依赖ThreadPoolExecutor,异步本身就能实现高并发,而且共用同一个浏览器实例打开多个页签渲染,比多线程每个线程单独开浏览器的资源占用低很多。
内容的提问来源于stack exchange,提问作者José Guedes
相关产品推荐
相关产品推荐

