【愚公系列】《Python网络爬虫从入门到精通》024-多进程和创建进程的常用方式
| 标题 | 详情 |
|---|---|
| 作者简介 | 愚公搬代码 |
| 头衔 | 华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。 |
| 近期荣誉 | 2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主,2024年华为云十佳博主等。 |
| 博客内容 | .NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。 |
| 欢迎 | 👍点赞、✍评论、⭐收藏 |
🚀前言
在如今这个数据驱动的时代,网络爬虫已成为获取信息的重要工具。然而,当面对复杂的网站结构和庞大的数据量时,传统的单进程爬虫往往难以满足迅速、高效的数据采集需求。为了解决这一问题,多进程爬虫应运而生,它通过并行处理来显著提升爬虫的性能,让我们能够更快地获取所需的信息。
在本期文章中,我们将深入探讨多进程爬虫的基本原理、实现方式以及在实际应用中的优势与挑战。我们将介绍如何利用Python等编程语言,搭建高效的多进程爬虫系统,帮助您在数据采集的过程中事半功倍
🚀一、多进程和创建进程的常用方式
🔎1.什么是进程
在了解进程之前,我们首先需要了解多任务的概念。多任务,顾名思义,就是指操作系统能够同时执行多个任务。例如,使用 Windows 或 Linux 操作系统时,你可以同时进行看电影、聊天、查看网页等操作。此时,操作系统在执行多个任务,而每个任务就是一个进程。
你可以通过打开 Windows 的任务管理器来查看当前操作系统正在执行的进程,图11.8中就显示了这些进程。图中的进程不仅包括各种应用程序(如 QQ、谷歌浏览器等),还包括系统进程。
进程与程序的区别
进程(process)是计算机中已经在运行的程序的实体。它是程序(包括指令和数据)的真正运行实例,而程序本身仅仅是指令、数据及其组织形式的描述,并不具备实际运行功能。
例如,当你没有打开 QQ 时,QQ 只是一个程序。它的代码和数据只是存在磁盘上,还没有被操作系统加载到内存中并执行。但是,当你打开 QQ 时,操作系统为其分配了资源,并启动了一个新的进程来执行 QQ 程序。此时,QQ就成了一个进程。
此外,如果你再打开一个 QQ,操作系统将为第二个 QQ 创建一个新的进程。所以,即便是同一个程序,运行的多个实例也会分别拥有各自独立的进程。图11.9展示了这种情况。
在这里插入图片描述
🔎2.创建进程的常用方式
Python中创建进程的常用模块包括:
-
os.fork():仅适用于UNIX/Linux/Mac系统,不兼容Windows。 -
multiprocessing模块:跨平台支持,适用于复杂进程管理。 -
Pool进程池:跨平台,适合批量创建进程任务。
本文重点介绍 multiprocessing模块和 Pool进程池。
🦋2.1 使用multiprocessing.Process类创建进程
multiprocessing模块提供Process类表示进程对象。
语法与参数
Process(group=None, target=None, name=None, args=(), kwargs={})
-
参数说明: -
group: 保留参数,始终为None。 -
target: 进程启动时调用的可执行对象(函数)。 -
name: 进程别名(默认Process-N,N从1递增)。 -
args: 传递给target的位置参数元组。 -
kwargs: 传递给target的关键字参数字典。
-
基础示例
from multiprocessing import Process
def test(interval):
print("我是子进程")
if __name__ == '__main__':
print("主进程开始")
p = Process(target=test, args=(1,)) # 实例化进程
p.start() # 启动子进程
print("主进程结束")
运行结果 
注意事项
-
在IDLE中运行可能不显示子进程输出,建议使用命令行执行: python 文件名.py。
☀️2.1.1 Process类常用方法
| 方法 | 说明 |
|---|---|
start() |
启动子进程 |
is_alive() |
判断进程是否在运行 |
join([timeout]) |
等待进程结束(可设置超时时间) |
terminate() |
强制终止进程 |
run() |
若未指定target,调用此方法执行逻辑 |
☀️2.1.2 Process类常用属性
| 属性 | 说明 |
|---|---|
name |
进程别名(默认Process-N) |
pid |
进程的PID值 |
☀️2.1.3 示例:Process类方法和属性演示
# -*- coding:utf-8 -*-
from multiprocessing import Process
import time
import os
#两个子进程将会调用的两个方法
def child_1(interval):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time() # 计时开始
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time() # 计时结束
print("子进程(%s)执行时间为'%0.2f'秒"%(os.getpid(),t_end - t_start))
def child_2(interval):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time() # 计时开始
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time() # 计时结束
print("子进程(%s)执行时间为'%0.2f'秒"%(os.getpid(),t_end - t_start))
if __name__ == '__main__':
print("------父进程开始执行-------")
print("父进程PID:%s" % os.getpid()) # 输出当前程序的ID
p1=Process(target=child_1,args=(1,)) # 实例化进程p1
p2=Process(target=child_2,name="mrsoft",args=(2,)) # 实例化进程p2
p1.start() # 启动进程p1
p2.start() # 启动进程p2
#同时父进程仍然往下执行,如果p2进程还在执行,将会返回True
print("p1.is_alive=%s"%p1.is_alive())
print("p2.is_alive=%s"%p2.is_alive())
#输出p1和p2进程的别名和PID
print("p1.name=%s"%p1.name)
print("p1.pid=%s"%p1.pid)
print("p2.name=%s"%p2.name)
print("p2.pid=%s"%p2.pid)
print("------等待子进程-------")
p1.join() # 等待p1进程结束
p2.join() # 等待p2进程结束
print("------父进程执行结束-------")
在这里插入图片描述
运行结果 
🦋2.2 继承Process子类创建进程
通过自定义类继承Process类,重写run()方法实现复杂任务。
示例:子类化Process
# -*- coding:utf-8 -*-
from multiprocessing import Process
import time
import os
#继承Process类
class SubProcess(Process):
# 由于Process类本身也有__init__初识化方法,这个子类相当于重写了父类的这个方法
def __init__(self,interval,name=''):
Process.__init__(self) # 调用Process父类的初始化方法
self.interval = interval # 接收参数interval
if name: # 判断传递的参数name是否存在
self.name = name # 如果传递参数name,则为子进程创建name属性,否则使用默认属性
#重写了Process类的run()方法
def run(self):
print("子进程(%s) 开始执行,父进程为(%s)"%(os.getpid(),os.getppid()))
t_start = time.time()
time.sleep(self.interval)
t_stop = time.time()
print("子进程(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start))
if __name__=="__main__":
print("------父进程开始执行-------")
print("父进程PID:%s" % os.getpid()) # 输出当前程序的ID
p1 = SubProcess(interval=1,name='mrsoft')
p2 = SubProcess(interval=2)
#对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法,
#所以这里会执行p1.run()
p1.start() # 启动进程p1
p2.start() # 启动进程p2
# 输出p1和p2进程的执行状态,如果真正进行,返回True,否则返回False
print("p1.is_alive=%s"%p1.is_alive())
print("p2.is_alive=%s"%p2.is_alive())
#输出p1和p2进程的别名和PID
print("p1.name=%s"%p1.name)
print("p1.pid=%s"%p1.pid)
print("p2.name=%s"%p2.name)
print("p2.pid=%s"%p2.pid)
print("------等待子进程-------")
p1.join() # 等待p1进程结束
p2.join() # 等待p2进程结束
print("------父进程执行结束-------")
在这里插入图片描述
关键点
-
必须调用 super().__init__()以确保父类初始化。 -
start()方法会自动调用子类的run()方法。
🦋2.3 使用进程池Pool创建进程
Pool类用于管理多个进程,适合批量任务处理。
Pool类核心方法
| 方法 | 说明 |
|---|---|
apply_async(func, args, kwds) |
非阻塞方式并行执行任务 |
apply(func, args, kwds) |
阻塞方式顺序执行任务 |
close() |
关闭进程池,禁止添加新任务 |
join() |
等待所有子进程结束(需在close()后调用) |
示例:进程池使用
# -*- coding=utf-8 -*-
from multiprocessing import Pool
import os, time
def task(name):
print('子进程(%s)执行task %s ...' % ( os.getpid() ,name))
time.sleep(1) # 休眠1秒
if __name__=='__main__':
print('父进程(%s).' % os.getpid())
p = Pool(3) # 定义一个进程池,最大进程数3
for i in range(10): # 从0开始循环10次
p.apply_async(task, args=(i,)) # 使用非阻塞方式调用task()函数
print('等待所有子进程结束...')
p.close() # 关闭进程池,关闭后p不再接收新的请求
p.join() # 等待子进程结束
print('所有子进程结束.')
运行结果 
🦋2.4 总结
-
Process类:适合少量进程的精确控制。 -
子类化Process:适合复杂任务封装。 -
Pool进程池:适合批量任务的高效并发处理。
通过灵活选择不同方式,可应对多进程编程中的各类场景。
- 点赞
- 收藏
- 关注作者
评论(0)