Python爬虫提速小技巧,多线程与多进程(附源码示例)
Posted 攻城狮白玉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python爬虫提速小技巧,多线程与多进程(附源码示例)相关的知识,希望对你有一定的参考价值。
目录
前言
当我们在爬虫的时候,比如爬取一个页面的数据是需要10s钟,那么爬取10000个相同的网页的话,时间花费是10*10000=100000s。我们会发现此时的耗时变得特别的长。会有等到黄花菜都凉了的感觉。此时,我们就可以引入多线程或多进程,用于并行执行一些相同的,不互相干扰的请求工作。用于节省时间。简单理解单线程和多线程就好比单车道和多车道,肯定是多车道能够同时容纳通过的车比较多。
一、线程与进程
1.1 进程(Process)
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。比如咱们在打开浏览器的时候,电脑操作系统就运行了一个浏览器的进程。比如我打开了chrome,在windows的任务管理器就可以看到chrome相关的进程。
1.2 线程(Thread)
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以由一个或多个线程组成。比如我打开了chrome,这个进程下面就有很多个线程。
1.3 小结
有了粗浅的概念之后,咱们小结一下。
计算机里运行程序的时候,会给这个程序分配一个内存空间,这个内存空间就是进程。一个进程里会有若干个线程。
- 进程——资源单位,每一个进程里至少会有一个线程
- 线程——执行单位,启动每一个程序默认都会有一个主线程
举一个例子就是,CPU就好比一间公司,每个部门就是每个进程。每个部门至少要有一个人(每个进程至少要有一个线程)。
当一个部门的工作,一个人完成的速度比较慢时,就可以多招几个人来工作。即多线程。
同一个部门内的人的信息是互相交流的(多线程之间共享变量)
当A部门的工作,加人手也赶不及完成的时候,B部门也来帮忙。这就是多进程。
A部门跟B部门之间的数据是不共享的。如果有需要沟通一般由部门经理(特定的通信模块)来互相沟通。
二、Threading模块
python 使用多线程很简单。只需要引入threading库。
有同学可能会问python3不是还有一个多线程库是_thread吗?是的,没错,但是这个库是给python高手用的库。因为这个库更偏底层,它只提供了基本的线程和锁的支持。不像threading库一样,有比较好的线程管理。这里咱们学习,就采用threading库啦。
2.1 创建多线程
2.1.1 直接调用
使用之前需要引入
from threading import Thread
from threading import Thread
def baiyu():
for i in range(1000):
print("baiyu 创建的子线程", i)
if __name__ == '__main__':
t = Thread(target=baiyu) # 创建线程并给线程安排任务
t.start() # 多线程状态为可以开始工作状态,具体的执行时间由CPU决定
for i in range(1000):
print("baiyu 的主线程", i)
我们可以发现此时的主线程跟子线程是一起工作的,这时咱们第一个多线程程序就实现啦~
我们通过Thread()函数的target参数,把我们的子线程程序(任务)给注册到线程里。然后通过start()函数来启动我们这个线程。
2.1.2 继承调用(推荐使用)
上面的程序还有另外一种实现方式,那就是通过类继承的方式来实现。
from threading import Thread
class BaiyuThread(Thread):
def run(self):#固定写法
for i in range(1000):
print("BaiyuThread 继承类的子线程", i)
if __name__ == '__main__':
baiyu = BaiyuThread()
# baiyu.run()#这个是方法的调用 . -> 单线程了
baiyu.start() # 开启线程
for i in range(1000):
print("baiyu 的主线程", i)
此处我们声明了一个BaiyuThread类,继承了Thread类的。在类里面要重写定义run()函数。不要问为什么,就是你把你多线程要实现的程序就写在run()函数里面就好了
- 把你多线程要实现的程序写在run()函数里面就好了
- 把你多线程要实现的程序写在run()函数里面就好了
- 把你多线程要实现的程序写在run()函数里面就好了
固定写法,重要事情说三遍~
因为当我们示例化子类之后,调用程序的start()函数,启动线程时,执行的内容就是run()函数里面的内容。
上述程序运行结果部分截图如下:
2.2 多线程传参
当我们简单实现了多线程程序之后,我们会想,既然多线程执行的时候,我们想区分一下到底是哪个线程。亦或者是调用某一个函数(方法)的时候,需要添加自定义的参数,这些都免不了要传参,那多线程启动的函数能否也进行传参呢?
答案是肯定的,毕竟,人生苦短,我选python嘛。
2.2.1 直接调用传参
在直接调用多线程函数时,我们直接像平时一样定义一个函数就好了,只是在多线程初始化的时候,传参除了要用target参数传递要进行多线程任务注册的函数之外,要用通过args参数,传递一个元组,把函数对应的参数都传进去。
- args参数必须为元组
- args参数必须为元组
- args参数必须为元组
重要事情说三遍。传递参数必须为元组,也就是说,当只有一个参数的时候,传递的参数应该是
args = (参数1,)
这里的逗号不能省略的。
下面上代码,让各位同学能够更加直观的了解到多线程的参数传递方式。
from threading import Thread
def func(name, num):
for i in range(1000):
print(name, num, i)
if __name__ == '__main__':
# 创建线程并给线程安排任务
t1 = Thread(target=func, args=("攻城狮白玉", 1)) # 传参必须是一个元组
t1.start()
t2 = Thread(target=func,args=("baiyu",2))
t2.start()
2.2.2 继承调用传参(推荐使用)
通过继承类来调用线程,如果需要传参,我们只需要在类的__init__()函数在继承Thread类后,我们只需要在__init__()里,把对应要传递的参数进行初始化即可。此处要注意踩坑的一个地方就是,重写的__init__会覆盖父类的__init__,需要在创建子类之前调用父类的构造方法来创建一个父类对象,在父类对象的基础上再追加一些属性。也就是说我们在重写__init__类的时候,一定要把
Thread.__init__(self) # 重写的__init__会覆盖父类的__init__,需要在创建子类之前调用父类的构造方法来创建一个父类对象,
否则的话,代码会报错AssertionError: Thread.__init__() not called
多线程传参类继承的方式实现代码如下:
class MyThread(Thread): # 继承了线程的类
# 重写的__init__会覆盖父类的__init__,
# 需要在创建子类之前调用父类的构造方法来创建一个父类对象,
# 在父类对象的基础上再追加一些属性。
# 比如这里的self.name = name
# 调用父类的构造方法
def __init__(self, name, num):
Thread.__init__(self)# 重写的__init__会覆盖父类的__init__,需要在创建子类之前调用父类的构造方法来创建一个父类对象,
self.name = name
self.num = num
def run(self): # 固定的写法,当线程被执行的时候,被执行的就是run()
for i in range(1000):
print(self.name, self.num, i)
if __name__ == '__main__':
t = MyThread("攻城狮白玉", 1)
t2 = MyThread("baiyu", 2)
t.start() # 开启线程
t2.start() # 开启线程
2.3 小结
这里介绍了两种对于多线程的实例化以及线程传参的方法,我推荐的是使用类继承的方法来进行多线程函数的实现。如果对于类相关概念不熟悉的同学,也可以通过直接调用多线程模块的方法进行多线程的实现。
三、多进程multiprocessing模块
3.1 创建多进程
在python3中,使用多进程我们需要用到multiprocessing模块
from multiprocessing import Process
话不多说,直接上代码。由代码我们会发现,多进程的实现跟多线程的实现在python代码上是差不多的。但其实,实际上两者的底层逻辑是不一样的。只不过是python为了简化我们使用,把两者的API给封装成类似的,方便我们去使用。
from multiprocessing import Process
import time
import os
def baiyu():
for i in range(1000):
time.sleep(1)
print(i, f"baiyu子进程,id是{os.getpid()}")
if __name__ == '__main__':
p = Process(target=baiyu, name='baiyu')
p.start()
for i in range(1000):
time.sleep(1)
print(i, f"主进程,,id是{os.getpid()}")
当我们运行上面的代码的时候,我们可以在任务管理器上面清楚的看到,是有两个python的进程在运行着的
3.2 多进程传参
多进程的传参跟上面咱们多线程讲到的是差不多的,也是通过args参数传入元组来进行参数传递的。直接上代码
from multiprocessing import Process
import time
import os
def baiyu(name):
for i in range(1000):
time.sleep(1)
print(i, f"{name}子进程,id是{os.getpid()}")
if __name__ == '__main__':
p = Process(target=baiyu,args=('baiyu',), name='baiyu')
p.start()
p2 = Process(target=baiyu,args=('zhh',),name='zhh')
p2.start()
由于这里代码我创建了两个子进程,加上原本一个主进程,所以在任务管理器我们看到的是三个python程序。
3.3 小结
这里我简单介绍了多进程模块的调用,但是实际上多进程模块还有很多东西要深入的,不过对于目前是够用的。而且多进程是直接创建一个新的进程实例,会耗费更多的资源,一般不建议使用多进程,能用多线程解决的问题,就不要用多线程
总结
本文简单介绍了多线程与多进程的概念,同时也通过python相应的库,对于多线程和多进程进行实现。在python的API下,多进程的调用方式与多线程的调用方式相似,都很简单,但是,我们自己要清楚,两者的实现方式是不一样的。而且两者对于资源的占用也是不一样的,由于子进程会拷贝复制主进程中的所有资源(变量、函数定义等),所以子进程比子线程耗费资源。线程与线程之间共享全局变量,进程之间不能共享全局变量。当我们在进行爬虫的时候,只需要将一些request请求、文本写入或者是图片、视频的保存放在多线程运行的函数里面,就可以增加爬虫的速度了。
写在后面
如果觉得有用的话,麻烦一键三连支持一下攻城狮白玉,并把本文分享给更多的小伙伴。你的简单支持,我的无限创作动力
以上是关于Python爬虫提速小技巧,多线程与多进程(附源码示例)的主要内容,如果未能解决你的问题,请参考以下文章