Python爬虫提速小技巧,多线程与多进程(附源码示例)

Posted 攻城狮白玉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python爬虫提速小技巧,多线程与多进程(附源码示例)相关的知识,希望对你有一定的参考价值。

目录

前言

一、线程与进程

1.1 进程(Process)

1.2 线程(Thread)

1.3 小结

二、Threading模块

2.1 创建多线程

2.1.1 直接调用

2.1.2 继承调用(推荐使用)

2.2 多线程传参

2.2.1 直接调用传参

2.2.2 继承调用传参(推荐使用)

2.3 小结

三、多进程multiprocessing模块

3.1 创建多进程

3.2 多进程传参

3.3 小结

总结

写在后面


前言

当我们在爬虫的时候,比如爬取一个页面的数据是需要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爬虫提速小技巧,多线程与多进程(附源码示例)的主要内容,如果未能解决你的问题,请参考以下文章

Python脚本-爬虫与多线程

Python脚本-爬虫与多线程

多线程 多进程 协程 Queue(爬虫代码)

Python多线程与多进程

python多进程与多线程使用场景

python之多线程与多进程