Python多线程爬取短视频下载
前言
之前在利用Python写爬虫时,一般就是爬取网页数据,图片img,音乐等等这些小型文件,最近在批量获取某音视频时,发现速度就明显下降,于是想到使用并发--多线程去下载。
并发编程
在说并发编程之前,先了解几个概念!
什么是进程
进程是正在进行的一个过程或者一个任务。而负责执行任务的则是CPU。
进程与程序的区别
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。进程也就是程序的一次执行过程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。同一个程序执行两次是两个进程。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
并发与并行
无论是并行还是并发,在用户看来都是'同时'运行的,而一个cpu同一时刻只能执行一个任务。
- 并发:同一时刻并不是真正的同时执行,是伪并行,即某个时段看起来是同时运行。单个CPU+多道技术就可以实现并发,(并行也属于并发)
- 并行:同一时刻同时执行,只有具备多个CPU才能实现并行。
什么是线程
线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
什么是协程
协程是单线程下的并发,一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
协程的特点
- 优点:协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级,单线程内就可以实现并发的效果,最大限度地利用CPU。
- 缺点:协程的本质是单线程下,无法利用多核(可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程)
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
为什么引入并发编程?
引入并发,就是为了提升程序运行速度。
Python并发编程有三种方式
- 多线程Thread
- 多进程Process
- 多协程Coroutine
Python对并发编程的支持
多线程: threading模块
, 利用CPU和IO可以同时执行的原理
多进程: multiprocessing模块
, 利用多核CPU的能力,真正的并行执行任务
异步IO: asyncio模块
, 在单线程利用CPU和IO同时执行的原理,实现函数异步执行
什么是CPU密集型计算、IO密集型计算
- CPU密集型CPU密集型也叫计算密集型,是指I/O在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率相当高
例如:压缩解压缩、加密解密、正则表达式搜索
- IO密集型IO密集型指的是系统运作大部分的状况是CPU在等I/O (硬盘/内存)的读/写操作,CPU占用率仍然较低。
例如:文件处理程序、网络爬虫程序n读写数据库程序
多线程、多线程、多协程的对比
类型 | 特点 |
---|---|
多进程 | 优点:可以利用多核CPU并行运算 缺点:占用资源最多、可启动数目比线程少 适用于: CPU密集型计算 |
多协程 | 优点:内存开销最少、启动协程数量最多 缺点:python中支持的库有限制,代码实现复杂 适用于: IO密集型计算、需要超多任务运行、但有现成库支持的场景 |
多线程 | 优点:相比进程,更轻量级、点用资源少, 缺点: 相比进程:多线程只能并发执行,不能利用多CPU (GIL) 相比协程:启动数目有限制一占用内存资源,有线程切换开销 适用于: IO密集型计算、同时运行的任务数目要求不多 |
GIL(全局解释器)
Python速度慢的原因:
- Python是动态类型语言,采用解释器解释执行,也就是边解释边执行
- GIL
在非Python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在Python中,无论有多少个核同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL是全局解释器是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核心处理器.上,使用GIL的解释器也只允许同一时间执行一个线程。
GIL的来源是Python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看做是“通行证”,并且在一个Python进程之中,GIL只有一个。拿不到线程的通行证,就不允许进入CPU执行。
它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中。
怎样规避GIL带来的限制
- 多线程threading机制,用于IO密集型计算
因为在I/O 期间,线程会释放GIL,实现CPU和IO的并行,因此多线程用于IO密集型计算依然可以大幅提升速度,但是多线程用于CPU密集型计算时,只会更加拖慢速度 - 使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势
为什么使用多线程
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。
因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境
包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能高得要多。
进程之间不能共享内存,但线程之间共享内存非常容易。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高
利用Python多线程爬取数据
采用单线程下载视频实现:
def signal_thread(video_list):
i = 1
for item in video_list:
print(item)
if item != 'null':
resp_video = requests.get(item, timeout=30, headers=header)
with open('./video/' + str(i) + '.mp4', 'wb') as f:
f.write(resp_video.content)
i = i + 1
采用多线程下载视频实现:
def download(video_url, i):
print(f'正在下载第{i}个视频!')
if video_url != 'null':
resp_video = requests.get(video_url, timeout=30, headers=header)
with open('./video2/' + str(i) + '.mp4', 'wb') as f:
f.write(resp_video.content)
def multi_thread(video_list):
threads = []
i = 1
for video in video_list:
threads.append(
threading.Thread(target=download, args=(video, i))
)
i = i + 1
for thread in threads:
thread.start()
for thread in threads:
thread.join()
采用多线程几乎是秒下载!