Python使用Asyncio协程以及多协程爬虫

Python使用Asyncio协程以及多协程爬虫的使用方法。

       我们都知道python有一个GIL(全局解释器锁),因此虽然有Threading库,但那只是用来模拟多线程,因此Python的多线程也不能带来真正的并行。

      所以要用python达到并发的效果,要么多进程(multiprocess),要么就是——多协程

    话说进程、线程、协程的区别应该也是面试重点了,值得一背~

        直接来说这篇文章的重点,协程。它涉及到Asyncio的基本使用方法,最后再附带一个多协程爬虫的小案例体会一下协程的妙用。

Python中定义协程,就像定义函数一样,只不过在函数之前加上了async来修饰。

async def func1(param1):

        pass

这里的func1就是一个协程函数,也可以当成一个协程。但协程函数还有一个最重要的元素:await。即一个正常的协程函数,应该是这样:

async def func1(param1):

        await asyncio.sleep(5)

        pass

  Await的作用,就是在遇到耗时(比如这里的模拟sleep5秒)操作时,将程序挂起,去执行其他的协程。等5秒过后,或者其他协程结束时,再回来,即充分利用了这5s 的时间。

  举个例子,你现在有两个协程函数,一个是洗衣服,一个是写作业。洗衣服的函数,你仅需要把衣服丢进洗衣机,然后等待20分钟即可。而此时你完全可以拖出神来去执行写作业的操作。等作业写完,再回来执行洗衣服的后续操作:挂起来,晾干等。

即:

使用await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程。

  那问题来了,现在我们只定义了一个协程函数,该怎么同时执行多个协程呢?我们把每一个协程函数都包装成一个任务,包装的方法也很简单,通过asyncio.ensure_future(coroutine),就可以把这个协程函数变成一个任务。那我们现在有两个任务,就可以写一个列表tasks=[asyncio.ensure_future(洗衣服),asyncio.ensure_future(写作业)]。

  此时任务列表已经创建好了,但这些协程函数还是不能直接调用运行,需要将协程注册到事件循环,我们通过启动事件循环来运行函数。

  首先定义

loop=asyncio.get_event_loop()

  然后通过

  loop.run_until_complete(asyncio.wait(tasks))

  就可以执行这些任务了。run_until_complete 是一个阻塞(blocking)调用,直到协程运行结束,它才返回。

  这里asyncio.wait(tasks)等同于asyncio.gathe(tasks),起聚合的作用,把多个 futures 包装成单个 future,因为 loop.run_until_complete 只接受单个 future。

    下面我用一个使用协程来写爬虫的小demo来复盘一下整个协程爬取的逻辑。不过这个是无法运行的哈,只是一个伪代码,我只是尽量保留最主要的框架。

import aiohttp

import asyncio

#负责拿到目标页面的html
async def get_html(url):

    #session上下文管理器,是aiohttp官方文档提供的写法

    async with aiohttp.ClientSession() as session:

        response = await session.get(url, headers=headers)

        result = await response.text(encoding="utf8")

        return result
#负责提取详细信息

async def get_detail(url):

    text = await get_html(url)

    #模拟提取和解析关键信息
    #text=text.strip() 

#模拟把爬到的数据保存

    with open('xxx.txt', 'a', encoding='utf8') as f:

        f.write(text)

if __name__="__main__":

    tasks = [asyncio.ensure_future(get_detail(url) for url in urls]

    # 协程函数不能直接调用运行,需要将协程注册到事件循环,并启动事件循环才能使用。

    loop = asyncio.get_event_loop()

    # 用run_until_complete方法将协程包装成为了一个任务(task)对象

    loop.run_until_complete(asyncio.wait(tasks))

  代码当中aiohttp是一个利用asyncio的库,可以暂时看成协程版的requests,如果要用协程来写爬虫,就用 session.get(url, headers=headers)替换requests.get(),具体使用方法直接模仿上面代码就好,要创建一个session,在session里写。

  从这段伪代码可以看到,把我们要爬的url放进列表中,对于每一个url,使用ensure_future(get_detail(url))创建future,然后放在loop里去执行。此时每个爬取url的不同协程就可以开始工作了。在需要一定耗时的步骤前,我们使用await把它挂起,去执行别的协程,这样就可以加快效率啦~

上一篇:线程、协程


下一篇:asynico异步编程高层级API