代码之家  ›  专栏  ›  技术社区  ›  SIM

脚本即使异步运行也会执行得非常慢

  •  1
  • SIM  · 技术社区  · 7 年前

    asyncio 联合 aiohttp 库异步解析网站内容。我已经尝试在下面的脚本中应用逻辑,它通常在 scrapy .

    然而,当我执行我的脚本时,它的行为就像同步库一样 requests urllib.request 做因此,这是非常缓慢的,不符合目的。

    link 变量但是,我是否已经用我现有的脚本以正确的方式完成了任务?

    在脚本中什么 processing_docs() 该函数的作用是收集不同帖子的所有链接,并将优化后的链接传递给 fetch_again() 函数从其目标页获取标题。这里面有一个逻辑 处理文件() 收集下一页链接并将其提供给 fetch() This next_page call is making the script slower whereas we usually do the same in 发痒的 and get expected performance.

    import aiohttp
    import asyncio
    from lxml.html import fromstring
    from urllib.parse import urljoin
    
    link = "https://stackoverflow.com/questions/tagged/web-scraping"
    
    async def fetch(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                text = await response.text()
                result = await processing_docs(session, text)
            return result
    
    async def processing_docs(session, html):
            tree = fromstring(html)
            titles = [urljoin(link,title.attrib['href']) for title in tree.cssselect(".summary .question-hyperlink")]
            for title in titles:
                await fetch_again(session,title)
    
            next_page = tree.cssselect("div.pager a[rel='next']")
            if next_page:
                page_link = urljoin(link,next_page[0].attrib['href'])
                await fetch(page_link)
    
    async def fetch_again(session,url):
        async with session.get(url) as response:
            text = await response.text()
            tree = fromstring(text)
            title = tree.cssselect("h1[itemprop='name'] a")[0].text
            print(title)
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.gather(*(fetch(url) for url in [link])))
        loop.close()
    
    1 回复  |  直到 7 年前
        1
  •  4
  •   Mikhail Gerasimov    7 年前

    使用asyncio的关键在于,您可以同时运行多个回迁(彼此并行)。让我们看看您的代码:

    for title in titles:
        await fetch_again(session,title)
    

    这一部分意味着每一个新的 fetch_again 将仅在等待(完成)上一个之后启动。如果您这样做,是的,使用同步方法没有区别。

    要调用asyncio的所有功能,请使用 asyncio.gather

    await asyncio.gather(*[
        fetch_again(session,title)
        for title 
        in titles
    ])
    

    您将看到显著的加速。


    你可以走得更远,然后开始 fetch 标题:

    async def processing_docs(session, html):
            coros = []
    
            tree = fromstring(html)
    
            # titles:
            titles = [
                urljoin(link,title.attrib['href']) 
                for title 
                in tree.cssselect(".summary .question-hyperlink")
            ]
    
            for title in titles:
                coros.append(
                    fetch_again(session,title)
                )
    
            # next_page:
            next_page = tree.cssselect("div.pager a[rel='next']")
            if next_page:
                page_link = urljoin(link,next_page[0].attrib['href'])
    
                coros.append(
                    fetch(page_link)
                )
    
            # await:
            await asyncio.gather(*coros)
    

    重要提示

    虽然这种方法可以让您更快地完成任务,但您可能希望限制并发请求的数量,以避免在您的机器和服务器上大量使用资源。

    你可以用 asyncio.Semaphore 为此目的:

    semaphore = asyncio.Semaphore(10)
    
    async def fetch(url):
        async with semaphore:
            async with aiohttp.ClientSession() as session:
                async with session.get(url) as response:
                    text = await response.text()
                    result = await processing_docs(session, text)
                return result