以爬虫为例,单线程,协程,线程,进程之间性能的比较,原来协程可以这么快?

前言

因为刚刚学习到了协程,然后之前也对爬虫有一定的了解,所以打算结合之前学的线程和进程,和协程进行对比,看看它的性能到底有多高,在测试完成后,结果还是不错的!下面就直接上代码了,因为代码逻辑都比较简单,我就不一一解释了,重点是看测试结果,真的很让人兴奋!!!

案例

我这里以爬取一个网站上的所有国家的旗帜为例。(下图为爬取结果,共192张)分别利用了单线程,协程,多线程,多进程进行爬取测试。线程的效率真的已经很高了,但是协程居然比它还高!!!

定位旗帜的url

我这里使用的xpath对元素进行定位。

 

相关代码:

# 获取各国国旗的下载地址
def get_country_img_urls() -> List[Tuple]:
    url = 'http://www.51zzl.com/jiaoyu/guoqi.asp'
    response = get(url, headers=HEADERS)
    response.encoding = 'gb2312'  # 编码格式
    html = response.text
    e = etree.HTML(html)
    country_url_li, country_name_li = e.xpath('//ul[@class="gq"]/li/img/@src'), e.xpath('//ul[@class="gq"]/li/span/text()')  # xpath获取国家名称和图片地址
    country_name_url_li = []
    for country_url, country_name in zip(country_url_li, country_name_li):
        country_name_url_li.append((country_name, f'http://www.51zzl.com{country_url}'))

    return country_name_url_li

 这个函数的主要功能就是,拿到所有旗帜的url,并同它的国家名以列表的形式返回。

下载图片的函数

# 下载一张图片
def download_one(name: str, url: str) -> Tuple:
    '''
    下载到图片的二进制数据并返回
    '''
    response = get(url, headers=HEADERS)
    content = response.content
    return (name, content)

保存图片的函数

# 保存一张图片
def save_one(name: str, content: bytes) -> None:
    path_ = 'imgs'
    if not path.exists(path_): mkdir(path_)
    save_path = f'{path_}/{name}.gif'
    with open(save_path, 'wb') as f:
        f.write(content)

    # 进度条
    process_bar()

下载并保存一张图片

# 下载并保存一张图片
def download_save_one(name, url):
    name, content = download_one(name, url)
    save_one(name, content)

这个函数其实就是对上面两个函数的调用。

单线程版

# 串行执行
@clokced
def single_execute(country_name_url_li):
    while country_name_url_li:
        download_save_one(*country_name_url_li.pop())

协程版

# 下载 并保存(协程版)
async def async_spider(name, url):
    path_ = 'imgs'
    if not path.exists(path_): mkdir(path_)

    save_path = f'{path_}/{name}.gif'
    session = ClientSession()
    async with session.get(url) as img_data:
        async with aio_open(save_path, 'wb') as f:
            await f.write(await img_data.content.readany())
            await session.close()  # 关闭会话
    # 进度条
    process_bar()

多线程版

# 多线程执行
@clokced
def mul_th_execute(country_name_url_li):
    th_pool = ThreadPoolExecutor(len(country_name_url_li))
    while country_name_url_li:
        th_pool.submit(download_save_one, *country_name_url_li.pop())
    th_pool.shutdown()

多进程版

# 多进程执行
@clokced
def mul_pr_execute(country_name_url_li):
    # 创建和cpu数量相同的进程数
    pr_pool = ProcessPoolExecutor(cpu_count())
    while country_name_url_li:
        pr_pool.submit(download_save_one, *country_name_url_li.pop())
    pr_pool.shutdown()

值得注意的是,因为多各个进程之间是隔离的,所以这里使用了Manager实现进程之间的通信。

 测试结果

单线程测试结果

单线程爬取图片

协程测试结果

多线程测试结果 

 多进程测试结果

 单线程:21s

多进程:19.24s

多线程:0.35s

协程:0.19s

多进程和单线程的速度差不多;多线程和协程的速度比它们快了将近100倍的量级,而协程的速度又差不多是多线程的两倍!

 赏心悦目的测试结果!!!

同步更新于个人博客系统:以爬虫为例,单线程,协程,线程,进程之间性能的比较,原来协程可以这么快?

 源码:

# -*- coding: UTF-8 -*-
'''
   *****************LLL*********************
   * @Project :17.使用future处理并发                       
   * @File    :lll01_下载各国的国旗.py                  
   * @IDE     :PyCharm             
   * @Author  :LLL                         
   * @Date    :2022/5/1 16:05             
   *****************************************
'''
from asyncio import get_event_loop, wait
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from multiprocessing import cpu_count, Manager
from functools import wraps
from sys import stdout
from time import time
from typing import List, Tuple
from aiofiles import open as aio_open
from aiohttp import ClientSession
from requests import get
from os import path, mkdir
from lxml import etree

# 请求头
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
}


# 计时装饰器:用来计算一个函数执行所需花费的时间
def clokced(func):
    @wraps(func)  # 将原函数的函数名等一些属性一并“带过来”
    def clock(*args, **kwargs):
        start = time()
        res = func(*args, **kwargs)
        end = time()
        print(f'  {func.__name__}执行耗时:{end - start}.')
        return res

    return clock


# 进度条
def process_bar():
    global country_name_url_li, original_sum
    now_len = len(country_name_url_li)
    done = original_sum - now_len

    stdout.write('\r{}{}%'.format('#' * int(done * 50 / original_sum), '%.2f' % (done * 100 / original_sum)))  # \r:回到首行
    stdout.flush()


# 获取各国国旗的下载地址
def get_country_img_urls() -> List[Tuple]:
    url = 'http://www.51zzl.com/jiaoyu/guoqi.asp'
    response = get(url, headers=HEADERS)
    response.encoding = 'gb2312'  # 编码格式
    html = response.text
    e = etree.HTML(html)
    country_url_li, country_name_li = e.xpath('//ul[@class="gq"]/li/img/@src'), e.xpath('//ul[@class="gq"]/li/span/text()')  # xpath获取国家名称和图片地址
    country_name_url_li = []
    for country_url, country_name in zip(country_url_li, country_name_li):
        country_name_url_li.append((country_name, f'http://www.51zzl.com{country_url}'))

    return country_name_url_li


# 下载一张图片
def download_one(name: str, url: str) -> Tuple:
    '''
    下载到图片的二进制数据并返回
    '''
    response = get(url, headers=HEADERS)
    content = response.content
    return (name, content)


# 保存一张图片
def save_one(name: str, content: bytes) -> None:
    path_ = 'imgs'
    if not path.exists(path_): mkdir(path_)
    save_path = f'{path_}/{name}.gif'
    with open(save_path, 'wb') as f:
        f.write(content)

    # 进度条
    process_bar()


# 下载并保存一张图片
def download_save_one(name, url):
    name, content = download_one(name, url)
    save_one(name, content)

# 串行执行
@clokced
def single_execute(country_name_url_li):
    while country_name_url_li:
        download_save_one(*country_name_url_li.pop())


# 下载 并保存(协程版)
async def async_spider(name, url):
    path_ = 'imgs'
    if not path.exists(path_): mkdir(path_)

    save_path = f'{path_}/{name}.gif'
    session = ClientSession()
    async with session.get(url) as img_data:
        async with aio_open(save_path, 'wb') as f:
            await f.write(await img_data.content.readany())
            await session.close()  # 关闭会话
    # 进度条
    process_bar()




# 协程执行
@clokced
def async_excute(country_name_url_li):
    loop = get_event_loop()
    tasks = []
    while country_name_url_li:
        tasks.append(loop.create_task(async_spider(*country_name_url_li.pop())))
    loop.run_until_complete(wait(tasks))
    loop.close()


# 多线程执行
@clokced
def mul_th_execute(country_name_url_li):
    th_pool = ThreadPoolExecutor(len(country_name_url_li))
    while country_name_url_li:
        th_pool.submit(download_save_one, *country_name_url_li.pop())
    th_pool.shutdown()


# 多进程执行
@clokced
def mul_pr_execute(country_name_url_li):
    # 创建和cpu数量相同的进程数
    pr_pool = ProcessPoolExecutor(cpu_count())
    while country_name_url_li:
        pr_pool.submit(download_save_one, *country_name_url_li.pop())
    pr_pool.shutdown()


if __name__ == '__main__':
    # 使用Manager实现进程之间的通信
    with Manager() as manager:
        country_name_url_li = get_country_img_urls()
        original_sum = len(country_name_url_li)  # 一开始的总数

    # 串行执行
    single_execute(country_name_url_li)

    # 协程执行
    # async_excute(country_name_url_li)

    # 多线程执行
    # mul_th_execute(country_name_url_li)

    # 多进程执行
    # mul_pr_execute(country_name_url_li)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值