Python分布式爬虫概念及案例

鳄鱼君

发表文章数:523

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python教程 » Python分布式爬虫概念及案例

爬虫的爬取效率是实际生产中一个重要考虑的因素,时间就是金钱,更是一个公司能够生存下去的准则之一。为了提高爬虫的效率,这篇文章会介绍一下异步编程,简单地说,就是利用多进程和多线程实现爬虫开发。

分布式概念

Python执行环境大部分依赖于GIL,而GIL限制了多线程的功能。GIL并不是Python的特性,它是实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言标准,但是可以用不同的编译器来编译可执行代码。Python也一样,同样的代码可以通过CPython、PyPy、Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。因为CPython大部分环境下默认是Python的执行环境,所以在很多人的概念里CPython就是Python,也就认为GIL是Python的缺陷,其实Python完全可以不依赖于GIL。

由于物理上的显示,各个CPU在核心频率上已经被多核取代。为了更好地利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性状态同步的困难。

为了利用多核,Python开始支持多线程,而解决多线程之间数据完整性和同步状态的最简单方法就是加锁。于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认Python内部对象是thread-safe),无需在实现时考虑额外的内存锁和同步操作。

Python在设计的时候,就考虑到要在解释器的主循环中同时只有一个线程在执行,即任意时刻,只有一个线程在解释器中运行。对Python虚拟机的访问→全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python解释器按以下方式执行:
(1)设置GIL。
(2)切换到一个线程去运行。
(3)运行:指定数量的字节码指令或者线程主动让出控制(可以调用time.sleep(0))。
(4)把线程设置为睡眠状态。
(5)解锁GIL,再次重复以上所有步骤。

有人认为Python的多线程比较“鸡肋”,这种说法只是相对而言,Python是仅有的支持多线程的解释型语言(比如Perl的多线程是残疾的,PHP没有多线程)。相对自身而言,如果代码是CPU密集型,并且是线性执行,在这种情况下多线程就是“鸡肋”,效率可能还不如单线程;如果代码是IO密集型的,多线程可以明显提高效率,例如爬虫,在绝大多数时间都在等待服务器返回数据和频繁的IO数据读写。

并发库

Python标准库为我们提供了threadingmultiprocessing模块编写相应的多线程/多进程代码。从Python 3.2开始,标准库提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing更高级的抽象,对编写线程池/进程池提供了直接的支持。

下面通过简单的例子讲解如何使用concurrent.futures,代码如下:

# 导入concurrent.futures 模块
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import datetime
# 线程的执行方法
def print_value(value):
    print('Thread'+str(value))
#每个进程里面的线程
def MyThread(value):
    Thread=ThreadPoolExecutor(max_workers=2)
    Thread.submit(print_value,datetime.datetime.now())
    Thread.submit(print_value,datetime.datetime.now())
#创建两个进程,每个进程执行MyThread方法,MyThread主要将每个进程通过线程来执行
# 如果不填写max_workers=2,根据计算机的CPU核数创建进程,如果四核就创建4个进程
def MyProcess():
    pool=ProcessPoolExecutor(max_workers=2)
    pool.submit(MyThread,datetime.datetime.now())
    pool.submit(MyThread,datetime.datetime.now())
if __name__ == '__main__':
    MyProcess()

在上述代码中,创建了进程ProcessPoolExecutor和线程ThreadPoolExecutor,其中在每个进程中又创建了两个线程。下面简单讲述一下concurrent.futures属性和方法:
(1)Executor:Executor是一个抽象类,它不能被直接使用。为具体的异步执行定义了基本的方法:ThreadPoolExecutor和ProcessPoolExecutor继承了Executor,分别被用来创建线程池和进程池的代码。
(2)创建进程和线程之后,Executor提供了submit()map()方法对其操作。submit()和map()最大的区别是参数类型,map()的参数必须是列表、元组和迭代器的数据类型。
(3)Future:可以理解为一个在未来完成的操作,这是异步编程的基础。通常情况下,执行IO操作和访问URL时,在等待结果返回之前会产生阻塞,CPU不能做其它事情,而Future的引入帮助我们在等待的时间可以完成其他操作。

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《Python分布式爬虫概念及案例》 发布于2020-06-09

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

4 + 2 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册