Python爬虫:线程,进程与协程

devtools/2024/10/21 15:27:57/

以往的爬虫我们都采用单线程和同步的方式,这导致我们的爬虫及其脆弱,因为一点报错都会让它停下来,而且面对比较大的数据,爬虫只能选择等待,这种阻塞会消耗很多时间,为什么我们不把等待的这些时间去干别的事呢?

线程进程

线程进程是相似的 

一,概念梳理

线程: 程序内,可以直接被CPU调用的执行过程,是操作系统能够进行运算的最小单位,它被包含在进程中实际运作的单位。

进程:运行中的程序,每次我们执行应该程序,操作系统会自动地为这个程序准备一些必要的资源(分配内存,创造一个能执行的线程

形象来说,线程就像是员工,进程就是公司,线程组成进程。如果我们想要提升效率,我们可以多招些员工(多线程)或者开一些分公司,连锁(多进程) 

二,代码实现

线程

python">from threading import Thread
# def func(name):
#     for i in range(10):
#         print(name,i)#创建任务
def func_1(name):for i in range(100):print(name,i)if __name__ == '__main__':# func("A")# func("B")# func("C")# #创建线程t1 = Thread(target=func_1,args=("A",))t2 = Thread(target=func_1,args=("B",))t3 = Thread(target=func_1,args=("C",))t1.start()t2.start()t3.start()

实例化线程对象:Thread(taeget=目标函数,不要括号,args=()填参数,元组形式) 

开启线程线程对象的start方法

面向对象写法 

python">from threading import Thread
class MyThread(Thread):def __init__(self,name):super(MyThread, self).__init__()self.name = namedef run(self):for i in range(100):print(self.name,i)if __name__ == '__main__':t1 = MyThread("A")t2 = MyThread("B")t3 = MyThread("C")t1.start()t2.start()t3.start()

需要注意的是,函数执行的任务必须重写到run方法中,其他的一样。

很快,一个一个实例化线程对象的方法无法满足我们了,如果我们要申请20个线程呢?这太繁琐了

 线程的产生就是顺其自然的了

线程

python">from concurrent.futures import ThreadPoolExecutordef func(name):for i in range(50):print(name,i)if __name__ == '__main__':with ThreadPoolExecutor(10) as t:t.submit(func,"A")t.submit(func,"B")t.submit(func,"C")

这大大地减轻了我们的工作量,我们只需要把任务丢进线程池里,它就会自动分配线程为我们处理,当然,最大线程数我们可以设置,上例使用了10个。 

好了,上面的所有情况我们都忽视掉了函数有返回值的情况是,那么,这种情况该怎么处理呢?

python">from concurrent.futures import ThreadPoolExecutordef func(name):print(name)return namedef fun(res):print(res.result())if __name__ == '__main__':with ThreadPoolExecutor(3) as t:t.submit(func,"A").add_done_callback(fun)t.submit(func,"B").add_done_callback(fun)t.submit(func,"C").add_done_callback(fun)

或者

python">from concurrent.futures import ThreadPoolExecutordef func(name):print(name)return namedef fun(res):print(res.result())if __name__ == '__main__':with ThreadPoolExecutor(3) as t:results = t.map(func,["A","B","C"])for result in results:print(result)

 这样我们就可以获取函数返回值了。

进程

python">from multiprocessing import Process
from concurrent.futures import ProcessPoolExecutor
def func(name):for i in range(100):print(name,i)if __name__ == '__main__':p1 = Process(target=func,args=("A",))p2 = Process(target=func,args=("B",))p3 = Process(target=func,args=("C",))p1.start()p2.start()p3.start()

 怎么样,对比一下多线程,你会发现代码上除了调的类不一样以外,就没区别了,多进程进程池和线程池几乎一模一样,这里就不赘述了,可以自己尝试一下,进程池上面代码有名字~

线程进程是相似的 

那么,多进程和多线程的应用场景有什么不同呢?

线程:任务相对统一,互相特别相似

进程:多个任务相互独立,很少有交集

多任务异步协程

协程协程是一种比函数更加强大的控制流结构,它可以挂起(暂停)自身的执行并在稍后从上次挂起的地方恢复执行。协程允许在单个线程内进行非阻塞的协作式多任务处理,这意味着程序可以在等待某个耗时操作(如I/O操作、网络请求)完成的同时,去做其他事情,从而提高整体效率。协程拥有自己的执行上下文(包括局部变量和指令指针),可以在多个协程之间方便地切换。

简单理解,多线程可以理解为我们不停的切换cpu运算对象,任务不动,CPU动。而协程是单线程中,通过移动任务来实现的,比如任务1进入这条线程中,进行计算.....直到产生IO阻塞,任务1就出线程,任务2进入线程,以此类推,当任务1IO阻塞结束,再让它回到线程(如果还有必要的话)。这个过程实质上是CPU不动,任务移动。这其实效率更高,因为CPU调用切换是需要消耗内存的,它是属于系统层面的问题,而协程只需要切换任务,这是代码层面的问题,实际上是不需要多少时间和空间的。

异步协程代码实现

python">import asyncioasync def func():print("我是函数")if __name__ == '__main__':#协程对象想要执行,必须借助于 event_loopf = func()event_loop = asyncio.get_event_loop()event_loop.run_until_complete(f)#eventloop运行协程对象,直到该对象内的内容执行完毕为止

 这是协程的基本结构,async表示异步,因为我们需要IO阻塞,想象一下如果我们申请网页响应中不执行IO阻塞,那在申请的同时代码就已经跑到下面去了,如果我们下面需要这个网页的源码呢?这就必然报错了,所以这就是为什么我们的函数使用了异步操作,本质还是在IO阻塞上。

python">
import asyncioasync def func1():print("我是func1")await asyncio.sleep(1)print("func1结束")async def func2():print("我是func2")await asyncio.sleep(2)print("func2结束")async def func3():print("我是func3")await asyncio.sleep(3)print("func3结束")if __name__ == '__main__':f1 = func1()f2 = func2()f3 = func3()tasks = [f1,f2,f3]asyncio.run(asyncio.wait(tasks))

上例我们创建了三个任务,并将这三个任务添加到了协程中,wait()为何意?这是为了让我们三个任务必须执行完才关闭eventloop,要不然还有任务在io阻塞,协程直接关了怎么办。

异步协程的函数返回值

python">import asyncio
async def func1():print("我是func1")await asyncio.sleep(1)print("func1结束")return "func1的返回值"async def func2():print("我是func2")await asyncio.sleep(2)print("func2结束")return "func2的返回值"async def func3():print("我是func3")await asyncio.sleep(3)print("func3结束")return "func3的返回值"async def main():f1 = func1()f2 = func2()f3 = func3()tasks = [asyncio.create_task(f1),asyncio.create_task(f2),asyncio.create_task(f3)]# done,padding = await asyncio.wait(tasks)# for res in done:#     print(res.result())# gather和wait区别:gather返回值是有顺序的(按照你添加任务的顺序)result = await asyncio.gather(*tasks,return_exceptions=False)#return exception=true表示如果任务中有错误信息,则返回错误信息,其他任务正常执行。print(result)
if __name__ == '__main__':asyncio.run(main())

 解释请看代码注释~

创作不易,点个小赞鼓励一下呗~ 


http://www.ppmy.cn/devtools/32218.html

相关文章

鸿蒙应用开发系列 篇一:鸿蒙系统概述

文章目录 系列文章鸿蒙系统的历史HarmonyOS 与 OpenHarmony鸿蒙系统的技术架构与核心特性内核层:安全与效率的双轮驱动系统服务层:分布式服务,重构连接的维度框架层:开发者的效率与创意舞台应用层:全场景应用生态的繁荣鸿蒙系统与其他操作系统与Android、iOS的比较:与AOS…

时间复杂度空间复杂度 力扣:转轮数组,消失的数字

1. 算法效率 如何衡量一个算法的好坏?一般是从时间和空间的维度来讨论复杂度,但是现在由于计算机行业发展迅速,所以现在并不怎么在乎空间复杂度了下面例子中,斐波那契看上去很简洁,但是复杂度未必如此 long long Fib…

TWS 蓝牙耳机 ESD EOS保护方案

1. TWS 蓝牙耳机 TWS(True Wireless Stereo)蓝牙耳机是指没有传统连接线的完全无线耳机,通常由两个分别放置在耳朵中的独立耳机组成,提供立体声音效。这类耳机在近年来越来越受欢迎,因为它们提供了更自由、更便捷的音…

吴恩达2022机器学习专项课程(一)8.1 过拟合

目录 什么是过拟合?如何解决过拟合?什么是泛化?它跟过拟合有什么关系?过拟合案例线性回归线性回归的欠拟合线性回归较好的拟合线性回归的过拟合 逻辑回归逻辑回归的欠拟合逻辑回归的较好的拟合逻辑回归的过拟合 总结 什么是过拟合…

【kettle008】kettle访问TiDB数据库并处理数据至execl文件(已更新)

1.一直以来想写下基于kettle的系列文章,作为较火的数据ETL工具,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下TiDB分布式NewSQL数据库相关知识体系 3.欢迎批评指正,跪谢一…

zeekeeper总结详解

目录 Zookeeper 概念Zookeeper 命令操作Zookeeper 数据模型ZooKeeper 命令操作Zookeeper 服务端常用命令Zookeeper 客户端常用命令 Curator 介绍Curator API 常用操作ZooKeeper分布式锁原理Curator实现分布式锁API ZooKeeper 集群搭建Zookeeper 集群介绍Zookeeper 集群角色 小结…

【算法每日一练】动态规划,图论(换根dp)会议 ,医院设置

目录 题目: 会议 思路: 题目:医院设置 思路: 题目: 会议 思路: 首先,阅读题目可以看出来,这道题目实际上就是求树的重心。 树的重心: 定义:找到一个点&a…

【进程间通信】管道和命名管道

文章目录 进程间通信的目的管道匿名管道管道的读写规则 命名管道命名管道和匿名管道区别 进程间通信的目的 数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程…