爬虫的单线程+多任务异步协程:asyncio 3.6

单线程+多任务异步协程:asyncio 3.6

  • 事件循环

    • 无限循环的对象.事件循环中最终需要将一些 特殊的函数(被async关键字修饰的函数) 注册在该对象中.
  • 协程

    • 本质上是一个对象.可以把协程对象(特殊的函数)注册到事件循环中
  • 任务对象

    • 就是对协程对象进一步的封装.
  • 绑定回调: task.add_done_callback(func)

    • func(task):task参数表示的就是绑定的任务对象
    • task.result():返回就是任务对象对应的特殊函数内部的返回值
    • 回调多被用作于爬虫中的解析方法
  • await

    • 在任务对象对应的特殊函数内部的实现语句中,如果出现了阻塞的操作,则必须使用await进行修饰
  • 异步操作的体现

    • 当将多个协程对象(特殊的函数)注册到事件循环中后,事件循环开启后,则会循环执行其内部的协程对象们
    • 假如事件循环对象在执行某一个协程对象时,发生了阻塞,则事件循环对象不会等待阻塞结束,反而会执行下一个协程对象
  • aiohttp:支持异步的网络请求模块

  • 简单示例

    import asyncio
    #特殊的函數:该函数调用后,函数内部的程序语句不会被执行,但是该函数调用会返回一个协程对象
    async def test():
        print('i am test()')
        print('i am test()')
        print('i am test()')
    
    #调用该特殊函数,让其返回一个协程对象
    c = test()
    
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    
    #将协程对象注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(c)
    
    print(c)
    
  • 任务对象的使用

    import asyncio
    #特殊的函數:该函数调用后,函数内部的程序语句不会被执行,但是该函数调用会返回一个协程对象
    async def test():
        print('i am test()')
        
    #调用该特殊函数,让其返回一个协程对象
    c = test()
    
    #将协程对象封装到任务对象中
    task = asyncio.ensure_future(c)
    
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    
    #将任务对象注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(task)
    
  • 任务对象绑定回调函数

    import asyncio
    #特殊的函數:该函数调用后,函数内部的程序语句不会被执行,但是该函数调用会返回一个协程对象
    async def test():
        print('i am test()')
        return 'hello bobo'
    
    #任务对象的回调函数,参数task表示的就是任务对象
    def func(task):
        # print('i am task callback!')
        print(task.result()) #返回的是任务对象对应的特殊函数的返回值
        
    #调用该特殊函数,让其返回一个协程对象
    c = test()
    
    #将协程对象封装到任务对象中
    task = asyncio.ensure_future(c)
    
    #给任务对象绑定一个回调函数
    task.add_done_callback(func)
    
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    
    #将任务对象注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(task)
    
  • 多任务异步协程

    import asyncio
    import time
    #函数内部不可以出现不支持异步模块的代码
    #该函数内部的异步操作必须使用await进行修饰
    async def request(url):
        print('正在下载:',url)
        # time.sleep(2) #time模块是一个不支持异步的模块
        await asyncio.sleep(2) #asyncio模块中提供的一个支持异步的阻塞方法
        print(url,'下载完毕!')
        return url
        
    #创建一个回调函数
    def callback(task):
    	#返回的是任务对象对应的特殊函数的返回值
        print(task.result())
    
    urls = [
        'www.1.com',
        'www.2.com',
        'www.3.com',
        'www.4.com',
    ]
    #记录开始时间
    start = time.time()
    #任务列表
    tasks = []
    for url in urls:
    	#调用该特殊函数,让其返回一个协程对象
        c = request(url)
        #将协程对象封装到任务对象中
        task = asyncio.ensure_future(c)
        # 给任务对象绑定回调
        task.add_done_callback(callback)
    	#将任务对象添加到列表中
        tasks.append(task)
    
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    #将任务对象列表注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(asyncio.wait(tasks))
    ##记录结束时间
    print(time.time()-start)
    
  • 单线程+多任务异步协程的爬虫

    import asyncio
    import requests
    import time
    import aiohttp
    from lxml import etree
    urls = [
        'http://localhost:5000/bobo',
        'http://localhost:5000/jay',
        'http://localhost:5000/tom',
    
        'http://localhost:5000/bobo',
        'http://localhost:5000/jay',
        'http://localhost:5000/tom'
    ]
    
    # async def get_page(url):
    #     #requests模块是一个不支持异步的模块,解决方法就是使用一个支持异步的模块进行请求发送
    #     page_text =  requests.get(url=url).text
    #     return page_text
    
    async def get_page(url):
        #使用aiohttp进行请求发送
        #实例化了一个发送网络请求的对象
        async with aiohttp.ClientSession() as session:
        	#该函数内部的异步操作必须使用await进行修饰
            async with await session.get(url) as response:
                #获取响应数据(页面源码数据)
                page_text = await response.text()
                # print(page_text)
                return page_text
    #数据解析的操作需要在回调函数中实现
    def parse(task):
        page_text = task.result()
        tree = etree.HTML(page_text)
        parse_data = tree.xpath('//body/text()')[0]
        print(parse_data)
    
    
    start = time.time()
    tasks = []
    for url in urls:
    	#调用该特殊函数,让其返回一个协程对象
        c = get_page(url)
        #将协程对象封装到任务对象中
        task = asyncio.ensure_future(c)
        # 给任务对象绑定回调
        task.add_done_callback(parse)
        #将任务对象添加到列表中
        tasks.append(task)
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    #将任务对象列表注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(asyncio.wait(tasks))
    
    print(time.time()-start)
    
  • 单线程+多任务异步协程的应用

    #爬取喜马拉雅中的相声音频
    import requests
    import aiohttp
    import asyncio
    #通用的url模板
    url = 'https://www.ximalaya.com/revision/play/album?albumId=19366477&pageNum=%d&sort=1&pageSize=2'
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
    }
    #获取了所有即将被下载的音频连接
    urls = []
    for page in range(1,3):
        new_url = format(url%page)
        dic_obj = requests.get(url=new_url,headers=headers).json()
        for dic in dic_obj['data']['tracksAudioPlay']:
            audio_url = dic['src']
            urls.append(audio_url)
    
    #特殊的函數:该函数调用后,函数内部的程序语句不会被执行,但是该函数调用会返回一个协程对象
    async def get_audio_data(url):
    	#使用aiohttp进行请求发送
        #实例化了一个发送网络请求的对象
        async with aiohttp.ClientSession() as s:
        	#该函数内部的异步操作必须使用await进行修饰
            async with await s.get(url=url,headers=headers) as response:
                audio_data = await response.read()  #read()返回的是二进制形式的响应数据
                return {'data':audio_data,'url':url}
    
    #任务对象的回调函数,进行数据的持久化存储
    def saveData(task):
        dic_obj = task.result()
        name = dic_obj['url'].split('/')[-1]
        data = dic_obj['data']
        with open(name,'wb') as fp:
            fp.write(data)
        print(name+'下载完毕!')
    
    tasks = []
    for url in urls:
    	#调用该特殊函数,让其返回一个协程对象
        c = get_audio_data(url)
        #将协程对象封装到任务对象中
        task = asyncio.ensure_future(c)
        # 给任务对象绑定回调函数
        task.add_done_callback(saveData)
        #将任务对象添加到列表中
        tasks.append(task)
    #创建一个事件循环对象
    loop = asyncio.get_event_loop()
    #将任务对象列表注册到事件循环对象中,并且开启事件循环
    loop.run_until_complete(asyncio.wait(tasks))
    
posted @ 2019-06-14 19:47  Yeokrin  阅读(345)  评论(0编辑  收藏  举报