Python爬虫中的协程

协程

基本概念

协程:当程序执行的某一个任务遇到了IO操作时(处于阻塞状态),不让CPU切换走(就是不让CPU去执行其他程序),而是选择性的切换到其他任务上,让CPU执行新的任务,当原来的任务不处于阻塞状态后,CPU可以快速的回到之前的任务继续执行,这样就不用让原本的程序去排队等待CPU调度。

微观上看,任务是一个一个的切换执行,切换条件就是某一个任务有IO操作, 而宏观上,我们看到的是多个任务一起执行,这就是多任务异步操作。上面的一切的前提就是单线程的情况下,因为多线程可以多个线程同时干多件事。

import time


def func():
    print('first, hi!')
    # 让程序睡眠3秒钟,此时线程处于阻塞状态,CPU不为线程工作
    # 当我们爬取一个网页时,向一个url发送请求,会通过网络传输将请求发送到服务器
    # 然后服务器会处理请求、准备数据、将数据通过网络传输回客户端等工作
    # 这一系列的操作也会耗费时间,所以在从发送请求开始,到接收服务器返回的数据这一段时间内
    # 即在网络请求返回数据之前,程序也处于阻塞状态
    # 程序进行处于IO操作时是处于阻塞状态的
    time.sleep(3)
    print('second, hello..')


if __name__ == '__main__':
    func()

协程和线程的区别(个人理解)

昨天仔细想的时候,感觉协程和线程很像,不知道它们之间的区别在哪,然后百度了一下,在这里说一下自己的理解(很不官方,不懂的可以百度一下,别人写的会比较详细和专业)。

打个比喻,一个公司有很多员工,老板给每个员工分配任务,员工之间各有分工,每个人负责自己的工作,如果遇到一个很会压榨人的老板,就会给每个员工安排很多任务。员工在完成自己分配到的多个任务时,因为自己只有一个人,不能同时把多个任务一起干,所以肯定是某个时刻内只干一件事。但是为了提高工作效率,在某个任务需要等待时,员工肯定不能傻傻的等着,而是利用这个等待的时间去干另一个任务(毕竟手上被万恶的资本家分配了很多活),比如正在跑的一个程序A要运行很久,那么在这个程序A运行的时间里,员工肯定去写另一个程序B了,如果这个程序B写完后也要运行很久,那么员工就会去完成程序C,或者此时程序A运行完了,接着完成程序A.....

上面所说的一个公司有多个员工,那么每个员工相当于一个线程,多个员工各有分工干自己的活,就是多个线程之间独立完成自己的工作。而一个员工充分利用时间完成多个任务(从一段时间上看(宏观),如一周内,员工同时完成多个任务,但是实际上(微观),某个时刻员工只做一件事),这每一个任务就是协程,所以协程实际上是一个单线程,宏观上同步完成多个任务,微观上异步完成多个任务。

协程可以充分的让一个线程忙起来,提高效率,不然当某个任务阻塞时,线程就处于空闲的等待状态,这使得线程资源没有得到充分利用,执行效率也大打折扣,就像老板想让打工人一刻都不停的给他创造价值一样。

用Python编写协程的程序

单个异步任务

有四种方式,但这里只选择其中的一种,如以下代码所示:

import asyncio


# 这种写法就是普通的函数
# def func():
#     print('你好,我是张三!')
#
#
# if __name__ == '__main__':
#     func()

# 在函数前面加async关键字,就表明该函数是异步协程函数
async def func():
    print('你好,我是张三!')

if __name__ == '__main__':
    # func()  # 如果直接调用,会得到一个警告:RuntimeWarning: ...
    g = func()  # 此时函数是一个异步协程函数,执行函数得到一个协程对象
    """
    输出:
    <coroutine object func at 0x000001F823066960>
    sys:1: RuntimeWarning: coroutine 'func' was never awaited
    """
    print(g)
    asyncio.run(g)  # 协程程序的运行需要asyncio模块的支持

多个异步任务

import asyncio
import time


# 在函数前面加async关键字,就表明该函数是异步协程函数
async def func1():
    print('你好,我是张三!')
    time.sleep(3)
    print('你好,我是张三!')

async def func2():
    print('你好,我是李四!')
    time.sleep(2)
    print('你好,我是李四!')

async def func3():
    print('你好,我是王五!')
    time.sleep(4)
    print('你好,我是王五!')

if __name__ == '__main__':
    f1 = func1()
    f2 = func2()
    f3 = func3()
    # 把多个异步任务放到一个列表中
    tasks = [f1, f2, f3]
    t1 = time.time()
    # 一次性启动多个异步任务(协程)
    asyncio.run(asyncio.wait(tasks))
    t2 = time.time()
    print(t2 - t1)

上面三个函数是异步协程操作,理论上执行时间应该会小于9秒,因为异步任务会在某一个任务阻塞时去调用其他任务,但是观察上述代码执行时间,发现和同步执行三个函数效果一样,都是用了9秒多,如下图。出现这种的情况的原因是:函数里的time.sleep()是同步操作,而异步协程函数中出现同步操作的时候,异步就中断了,也就是说,当异步函数中有同步操作时,CPU不会切换去调用其他任务,而是像同步函数那样,执行完一个任务再去执行另一个任务(在这个例子中,就是执行完func1,再执行func2,再执行func3)。

修改上述代码,实现异步操作效果,如下:

import asyncio
import time


# 在函数前面加async关键字,就表明该函数是异步协程函数
async def func1():
    print('你好,我是张三!')
    # time.sleep(3)  # 异步程序中出现同步操作,会中断异步,即不会切换任务执行
    # 异步操作代码,表示挂起任务,让任务睡眠3秒,然后切换CPU去执行其他任务
    await asyncio.sleep(3)
    print('你好,我是张三!')

async def func2():
    print('你好,我是李四!')
    # time.sleep(2)
    await asyncio.sleep(2)
    print('你好,我是李四!')

async def func3():
    print('你好,我是王五!')
    # time.sleep(4)
    await asyncio.sleep(4)
    print('你好,我是王五!')


# 一般不会直接像下面那样调用多个异步任务,而是把它包装在一个异步协程函数里
# if __name__ == '__main__':
#     f1 = func1()
#     f2 = func2()
#     f3 = func3()
#     # 把多个异步任务放到一个列表中
#     tasks = [f1, f2, f3]
#     t1 = time.time()
#     # 一次性启动多个异步任务(协程)
#     asyncio.run(asyncio.wait(tasks))
#     t2 = time.time()
#     print(t2 - t1)

async def main():
    # 写法一(不推荐)
    # await 都是写在异步协程函数里,即与async配套使用
    # await后一般跟协程对象、task等对象
    # await表示挂起某个异步任务,即是执行某个异步任务
    # await asyncio.create_task(func1())
    # await asyncio.create_task(func2())
    # await asyncio.create_task(func3())

    # 写法二(推荐)
    tasks = [
        # asyncio.create_task(func1()) 把协程对象包装成task对象
        asyncio.create_task(func1()),
        asyncio.create_task(func2()),
        asyncio.create_task(func3())
    ]
    # 这里await作用和上面一样,表示挂起协程对象,即会异步执行tasks列表中的异步任务
    await asyncio.wait(tasks)

if __name__ == '__main__':
    t1 = time.time()
    asyncio.run(main())
    t2 = time.time()
    print(t2 - t1)

使用异步模拟爬虫程序

import asyncio

async def download(url):
    print('开始下载...')
    await asyncio.sleep(2)
    print('下载完成!')

async def main():
    tasks = []
    urls = ['url1', 'url2', 'url3']
    for url in urls:
        d = download(url)  # 得到一个异步协程对象
        # asyncio.create_task(d) 把协程对象包装成task对象
        tasks.append(asyncio.create_task(d))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

异步发送http请求

以下代码是根据多个图片地址异步下载图片

import asyncio
# 下载命令:pip install aiohttp
import aiohttp

# 图片地址
urls = [
    "https://img95.699pic.com/photo/50165/7667.jpg_wh860.jpg",
    "https://bpic.588ku.com/back_origin_min_pic/20/04/19/f753e29e3dbe2ad75b8f6d6053199faa.jpg"
]

async def download(url):
    file_name = url.rsplit('/', 1)[1]
    # aiohttp.ClientSession()对象等价于requests模块,所以也有get、post方法
    # 且用法差不多
    async with aiohttp.ClientSession() as req:  # => req = aiohttp.ClientSession()
        # 因为是异步操作,所以要加上async关键字
        # with的作用和文件操作中的with类似,可以管理上下文,在使用完req对象之后会自动关闭
        # req.get(url) 发送请求获取图片数据
        async with req.get(url) as resp:  # => resp = req.get(url)
            # 这里的文件读写操作也是IO操作,也是会造成阻塞,所以也可以通过异步协程来完成
            # 具体可以学习aiofiles模块来实现
            with open(file_name, mode='wb') as f:
                # resp.content.read()是异步操作,所以前面要加await表示挂起
                # 挂起的意思就是resp.content.read()什么时候有东西了什么时候写入文件
                # 即什么时候有需要的内容了什么时候进行对应的操作
                # resp.content.read() 表示以字节的形式读取返回的数据的内容
                # 在这里就是读取图片的字节数据,然后存入文件,即保存图片数据
                f.write(await resp.content.read())
        # req.close() 使用with之后不用手动写上这句话
    print(file_name, '下载完成')

async def main():
    tasks = [asyncio.create_task(download(url)) for url in urls]
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

使用异步爬虫爬取西游记小说内容

详见:异步爬取西游记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/297662.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 虚函数virtual的引入和应用

来回顾一下使用引用或指针调用方法的过程。请看下面的代码: BrassPlus ophelia; // 子类对象 Brass * bp; // 基类指针 bp &ophelia; // 让基类指针指向子类对象 bp->ViewAcct(); // ViewAcct() 如果基类和子类都有这个函…

tp8/6 插件PhpOffice\PhpSpreadsheet导入表格

一、安装 composer require phpoffice/phpspreadsheet 官网&#xff1a;phpoffice/phpspreadsheet - Packagist 二、代码 <?php namespace app\services\upload\model; use app\services\BaseServices; use \PhpOffice\PhpSpreadsheet\Spreadsheet; use \PhpOffice\Php…

计算机Java项目|基于SpringBoot+Vue的图书个性化推荐系统

项目编号&#xff1a;L-BS-GX-10 一&#xff0c;环境介绍 语言环境&#xff1a;Java: jdk1.8 数据库&#xff1a;Mysql: mysql5.7 应用服务器&#xff1a;Tomcat: tomcat8.5.31 开发工具&#xff1a;IDEA或eclipse 二&#xff0c;项目简介 图片管理系统是一个为学生和…

Mac打包Unix可执行文件为pkg

Mac打包Unix可执行文件为pkg 方式一&#xff1a;通过packages页面打包 1.下载packages app Distribution&#xff1a;自定义化更高&#xff0c;包括修改安装页面的内容提示 我这里主要演示Distribution模式的项目&#xff1a;通过unix可执行文件postinstall.sh脚本实现通过ma…

浅谈冒泡排序

手写一个冒泡排序的代码。 1.数组 let arr [10, 2, 50, 23, 30, 56, 3]; 2.排序的思路 里层的循环: for (var i 0; i < arr.length; i) {if (arr[i] < arr[i 1]) {var temp arr[i];arr[i] arr[i 1];arr[i 1] temp;} 用途&#xff1a; [2, 10, 23, 30, 50, 3, …

腾讯云取消免费10G CDN流量包:免费CDN时代结束

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 免费送了7-8年的腾讯云10G免费流量包&#xff0c;从2024年开始&#xff0c;停止赠送了!自此&#xff0c;国内绝大多数互联网大厂的CDN都开收费了! 大概从2016年开始&#xff0c;腾讯云为了抢夺CDN客户&#xff0…

基于JavaWeb+SSM+Vue四六级词汇微信小程序系统的设计和实现

基于JavaWebSSMVue四六级词汇微信小程序系统的设计和实现 源码获取入口KaiTi 报告Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 KaiTi 报告 &#xff08;1&#xff09;课题背景 伴随着社会的快速发展, 现代社…

[NAND Flash 5.2] SLC、MLC、TLC、QLC、PLC NAND_闪存颗粒类型

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 前言 闪存最小物理单位是 Cell, 一个Cell 是一个晶体管。 闪存是通过晶体管储存电子来表示信息的。在晶体管上加入了浮动栅贮存电子…

odoo16 销售模块易错的几个操作

odoo16 销售模块易错的几个操作 据168Report调研团队最新报告“全球定制服装市场报告2023-2029”显示&#xff0c;预计2029年全球定制服装市场规模将达到1082.4亿美元&#xff0c;未来几年年复合增长率CAGR为7.8%。一个普通定制的小皮袄竟月销二十多万件&#xff0c;比我们做定…

HTML的简单介绍

文章目录 1. HTML1.1 HTML 基础认识1.2 快速生成代码框架1.3 HTML 基础标签 1. HTML 1.1 HTML 基础认识 什么是HTML呢&#xff1f; HTML叫做超文本标记语言。超文本&#xff1a;例如图片&#xff0c;视频&#xff0c;文本&#xff0c;声音&#xff0c;表格&#xff0c;链接等…

前端--基础 常用标签-超链接标签 外部链接( herf 和 target)

目录 超链接标签 &#xff1a; 超链接的语法格式 &#xff1a; 超链接的属性 &#xff1a; 超链接的分类 &#xff1a; 外部链接 &#xff1a; 超链接标签 &#xff1a; # 在 HTML 标签中&#xff0c;<a> 标签用于定义超链接&#xff0c;作用是从一个页面…

【面试高频算法解析】算法练习6 广度优先搜索

前言 本专栏旨在通过分类学习算法&#xff0c;使您能够牢固掌握不同算法的理论要点。通过策略性地练习精选的经典题目&#xff0c;帮助您深度理解每种算法&#xff0c;避免出现刷了很多算法题&#xff0c;还是一知半解的状态 专栏导航 二分查找回溯&#xff08;Backtracking&…

【C语言:可变参数列表】

文章目录 1.什么是可变参数列表2.可变参数列表的分析与使用2.1使用2.2分析原理2.3分析原码 1.什么是可变参数列表 对于一般的函数而言&#xff0c;参数列表都是固定的&#xff0c;而且各个参数之间用逗号进行分开。这种函数在调用的时候&#xff0c;必须严格按照参数列表中参数…

计算机网络(7):网络安全

网络安全问题 计算机网络上的通信面临以下的四种威胁: (1)截获(interception)攻击者从网络上窃听他人的通信内容。 (2)中断(interruption)攻击者有意中断他人在网络上的通信。 (3)篡改(modification)攻击者故意篡改网络上传送的报文。 (4)伪造(fabrication)攻击者伪造信息在网…

uniapp中uview组件库CircleProgress 圆形进度条丰富的使用方法

目录 #内部实现 #平台差异说明 #基本使用 #设置圆环的动画时间 #API #Props 展示操作或任务的当前进度&#xff0c;比如上传文件&#xff0c;是一个圆形的进度环。 #内部实现 组件内部通过canvas实现&#xff0c;有更好的性能和通用性。 #平台差异说明 AppH5微信小程…

web——德州扑克

1.此案例只用于学习 2.未接入游戏规则 HTML代码部分 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width"><meta name"Poker Skin" content&quo…

软件测试|深入学习 Docker Logs

简介 Docker 是一种流行的容器化技术&#xff0c;它能够帮助用户将应用程序及其依赖项打包成一个可移植的容器。Docker logs 是 Docker 提供的用于管理容器日志的命令&#xff0c;本文将深入学习 Docker logs 的使用和管理&#xff0c;帮助用户更好地监测和解决容器问题。 Do…

Python如何生成个性二维码

Python-生成个性二维码 一、问题描述 通过调用MyQR模块来实现生成个人所需二维码。 安装&#xff1a; pip install myqr 二、代码实现 1.普通二维码 from MyQR import myqr # 普通二维码 myqr.run(wordshttp://www.csdn.net/mayi0312,save_nameqrcode.png ) 效果图&#…

e2studio开发STHS34PF80人体存在传感器(1)----获取人体存在状态

e2studio开发STHS34PF80人体存在传感器.1--获取人体存在状态 概述视频教学样品申请完整代码下载主要特点硬件准备接口最小系统图新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函…

百度地图打点性能优化(海量点、mapv)

文章目录 百度地图打点性能优化&#xff08;海量点、mapv&#xff09;原因优化方法数据获取方面页面加载方面 参考资料 百度地图打点性能优化&#xff08;海量点、mapv&#xff09; 原因 在百度地图api中&#xff0c;默认的点是下图的红点 而这种点位比较多的时候&#xff0c…