将BeautifulSoup代码放在异步Web报废应用程序的何处



我需要抓取并获取许多(每天5-10k(新闻文章的正文段落的原始文本。我已经写了一些线程代码,但考虑到这个项目的高度I/O限制性质,我正在涉足asyncio。下面的代码片段并不比单线程版本快,而且比我的线程版本差得多。有人能告诉我我做错了什么吗?非常感谢。

async def fetch(session,url):
async with session.get(url) as response:
return await response.text()
async def scrape_urls(urls):
results = []
tasks = []
async with aiohttp.ClientSession() as session:
for url in urls:
html = await fetch(session,url)
soup = BeautifulSoup(html,'html.parser')
body = soup.find('div', attrs={'class':'entry-content'})
paras = [normalize('NFKD',para.get_text()) for para in body.find_all('p')]
results.append(paras)
return results

await的意思是";等待直到结果准备好";,因此,当您在每个循环迭代中等待获取时,您请求(并获得(顺序执行。为了并行获取,您需要使用asyncio.create_task(fetch(...))之类的东西将每个fetch派生到后台任务中,然后等待它们,类似于使用线程的方式。或者更简单地说,您可以让asyncio.gather便利功能为您完成此操作。例如(未经测试(:

async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
def parse(html):
soup = BeautifulSoup(html,'html.parser')
body = soup.find('div', attrs={'class':'entry-content'})
return [normalize('NFKD',para.get_text())
for para in body.find_all('p')]
async def fetch_and_parse(session, url):
html = await fetch(session, url)
paras = parse(html)
return paras
async def scrape_urls(urls):
async with aiohttp.ClientSession() as session:
return await asyncio.gather(
*(fetch_and_parse(session, url) for url in urls)
)

如果您发现这仍然比多线程版本运行得慢,那么HTML的解析可能会减慢与IO相关的工作。(默认情况下,Asyncio在一个线程中运行所有内容。(为了防止CPU绑定的代码干扰异步,可以使用run_in_executor:将解析转移到一个单独的线程

async def fetch_and_parse(session, url):
html = await fetch(session, url)
loop = asyncio.get_event_loop()
# run parse(html) in a separate thread, and
# resume this coroutine when it completes
paras = await loop.run_in_executor(None, parse, html)
return paras

注意CCD_ 7必须等待;唤醒";当后台线程完成给定的分配时。由于此版本使用异步进行IO,使用线程进行解析,因此它的运行速度应该与线程版本一样快,但可以扩展到更大数量的并行下载。

最后,如果您希望解析使用多个核心并行运行,则可以使用多处理:

_pool = concurrent.futures.ProcessPoolExecutor()
async def fetch_and_parse(session, url):
html = await fetch(session, url)
loop = asyncio.get_event_loop()
# run parse(html) in a separate process, and
# resume this coroutine when it completes
paras = await loop.run_in_executor(pool, parse, html)
return paras

最新更新