• python【多线程、单线程、异步编程】三个版本--在爬虫中的应用


    并发编程在爬虫中的应用

    之前的课程,我们已经为大家介绍了 Python 中的多线程、多进程和异步编程,通过这三种手段,我们可以实现并发或并行编程,这一方面可以加速代码的执行,另一方面也可以带来更好的用户体验。爬虫程序是典型的 I/O 密集型任务,对于 I/O 密集型任务来说,多线程和异步 I/O 都是很好的选择,因为当程序的某个部分因 I/O 操作阻塞时,程序的其他部分仍然可以运转,这样我们不用在等待和阻塞中浪费大量的时间。

    下面我们以爬取“360图片”网站的图片并保存到本地为例,为大家分别展示使用单线程、多线程和异步 I/O 编程的爬虫程序有什么区别,同时也对它们的执行效率进行简单的对比。

    “360图片”网站的页面使用了 Ajax 技术,这是很多网站都会使用的一种异步加载数据和局部刷新页面的技术。简单的说,页面上的图片都是通过 JavaScript 代码异步获取 JSON 数据并动态渲染生成的,而且整个页面还使用了瀑布式加载(一边向下滚动,一边加载更多的图片)。我们在浏览器的“开发者工具”中可以找到提供动态内容的数据接口,如下图所示,我们需要的图片信息就在服务器返回的 JSON 数据中。

    在这里插入图片描述

    例如,要获取“美女”频道的图片,我们可以请求如下所示的URL,其中参数ch表示请求的频道,=后面的参数值beauty就代表了“美女”频道,参数sn相当于是页码,0表示第一页(共30张图片),30表示第二页,60表示第三页,以此类推。

    https://image.so.com/zjl?ch=beauty&sn=0
    
    • 1

    单线程版本

    通过上面的 URL 下载“美女”频道共90张图片。

    """
    example04.py - 单线程版本爬虫
    """
    import os
    
    import requests
    
    
    def download_picture(url):
        filename = url[url.rfind('/') + 1:]
        resp = requests.get(url)
        if resp.status_code == 200:
            with open(f'images/beauty/{filename}', 'wb') as file:
                file.write(resp.content)
    
    
    def main():
        if not os.path.exists('images/beauty'):
            os.makedirs('images/beauty')
        for page in range(3):
            resp = requests.get(f'https://image.so.com/zjl?ch=beauty&sn={page * 30}')
            if resp.status_code == 200:
                pic_dict_list = resp.json()['list']
                for pic_dict in pic_dict_list:
                    download_picture(pic_dict['qhimg_url'])
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在 macOS 或 Linux 系统上,我们可以使用time命令来了解上面代码的执行时间以及 CPU 的利用率,如下所示。

    time python3 example04.py 
    
    • 1

    下面是单线程爬虫代码在我的电脑上执行的结果。

    python3 example04.py  2.36s user 0.39s system 12% cpu 21.578 total
    
    • 1

    这里我们只需要关注代码的总耗时为21.578秒,CPU 利用率为12%

    多线程版本

    我们使用之前讲到过的线程池技术,将上面的代码修改为多线程版本。

    """
    example05.py - 多线程版本爬虫
    """
    import os
    from concurrent.futures import ThreadPoolExecutor
    
    import requests
    
    
    def download_picture(url):
        filename = url[url.rfind('/') + 1:]
        resp = requests.get(url)
        if resp.status_code == 200:
            with open(f'images/beauty/{filename}', 'wb') as file:
                file.write(resp.content)
    
    
    def main():
        if not os.path.exists('images/beauty'):
            os.makedirs('images/beauty')
        with ThreadPoolExecutor(max_workers=16) as pool:
            for page in range(3):
                resp = requests.get(f'https://image.so.com/zjl?ch=beauty&sn={page * 30}')
                if resp.status_code == 200:
                    pic_dict_list = resp.json()['list']
                    for pic_dict in pic_dict_list:
                        pool.submit(download_picture, pic_dict['qhimg_url'])
    
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    执行如下所示的命令。

    time python3 example05.py
    
    • 1

    代码的执行结果如下所示:

    python3 example05.py  2.65s user 0.40s system 95% cpu 3.193 total
    
    • 1

    异步I/O版本

    我们使用aiohttp将上面的代码修改为异步 I/O 的版本。为了以异步 I/O 的方式实现网络资源的获取和写文件操作,我们首先得安装三方库aiohttpaiofile,命令如下所示。

    pip install aiohttp aiofile
    
    • 1

    aiohttp 的用法在之前的课程中已经做过简要介绍,aiofile模块中的async_open函数跟 Python 内置函数open的用法大致相同,只不过它支持异步操作。下面是异步 I/O 版本的爬虫代码。

    """
    example06.py - 异步I/O版本爬虫
    """
    import asyncio
    import json
    import os
    
    import aiofile
    import aiohttp
    
    
    async def download_picture(session, url):
        filename = url[url.rfind('/') + 1:]
        async with session.get(url, ssl=False) as resp:
            if resp.status == 200:
                data = await resp.read()
                async with aiofile.async_open(f'images/beauty/{filename}', 'wb') as file:
                    await file.write(data)
    
    
    async def fetch_json():
        async with aiohttp.ClientSession() as session:
            for page in range(3):
                async with session.get(
                    url=f'https://image.so.com/zjl?ch=beauty&sn={page * 30}',
                    ssl=False
                ) as resp:
                    if resp.status == 200:
                        json_str = await resp.text()
                        result = json.loads(json_str)
                        for pic_dict in result['list']:
                            await download_picture(session, pic_dict['qhimg_url'])
    
    
    def main():
        if not os.path.exists('images/beauty'):
            os.makedirs('images/beauty')
        loop = asyncio.get_event_loop()
        loop.run_until_complete(fetch_json())
        loop.close()
    
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    执行如下所示的命令。

    time python3 example06.py
    
    • 1

    代码的执行结果如下所示:

    python3 example06.py  0.82s user 0.21s system 27% cpu 3.782 total
    
    • 1

    总结

    通过上面三段代码执行结果的比较,我们可以得出一个结论,使用多线程和异步 I/O 都可以改善爬虫程序的性能,因为我们不用将时间浪费在因 I/O 操作造成的等待和阻塞上,而time命令的执行结果也告诉我们,单线程的代码 CPU 利用率仅仅只有12%,而多线程版本的 CPU 利用率则高达95%;单线程版本的爬虫执行时间约21秒,而多线程和异步 I/O 的版本仅执行了3秒钟。另外,在运行时间差别不大的情况下,多线程的代码比异步 I/O 的代码耗费了更多的 CPU 资源,这是因为多线程的调度和切换也需要花费 CPU 时间。至此,三种方式在 I/O 密集型任务上的优劣已经一目了然,当然这只是在我的电脑上跑出来的结果。如果网络状况不是很理想或者目标网站响应很慢,那么使用多线程和异步 I/O 的优势将更为明显,有兴趣的读者可以自行试验。

  • 相关阅读:
    Java数据结构————队列
    小艺的英文名编程求助(帮忙检查)
    操作系统内存管理相关
    【flexbox弹性布局学习指南】CSS热门布局方案
    逻辑回归与决策边界解析
    神马搜索引擎
    c语言基础学习笔记(三):while循环
    MySQL 日志及数据备份
    jsp课程资源网站系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
    微信小程序开发学习笔记《17》uni-app框架-tabBar
  • 原文地址:https://blog.csdn.net/No_Name_Cao_Ni_Mei/article/details/133951174