一、原因
多线程对爬虫的效率提高是非凡的,当我们使用python的多线程有几点是需要我们知道的:
1.Python的多线程并不如java的多线程,其差异在于当python解释器开始执行任务时,受制于GIL(全局解释所),Python 的线程被限制到同一时刻只允许一个程执行这样一个执行模型。
2.Python 的线程更适用于处理 I/O 和其他需要并发行的阻塞操作(比如等待 I/O、等待从数据库获取数据等等),而不是需要多处理器行的计算密集型任务。幸运的是,爬虫大部分时间在网络交互上,所以可以使用多线程来编写爬虫。
3.这点其实和多线程关系不大,scrapy的并发并不是采用多线程来实现,它是一个twisted应用,通过异步非阻塞来达到并发。
4.Python中当你想要提高执行效率,大部分开发者是通过编写多进程来提高运行效率,使用multiprocessing进行并行编程,当然,你可以编写多进程爬虫来爬取信息,缺点是每个进程都会有自己的内存,数据多的话,内存会吃不消。
5.使用线程有什么缺点呢,缺点就是你在编写多线程代码时候,要注意死锁的问题、阻塞的问题、以及需要注意多线程之间通信的问题(避免多个线程执行同一个任务)。
二、了解threading
我们通过 threading 模块来编写多线程代码,或者你可以使用 from concurrent.futures import ThreadPoolExecutor (线程池)也可以达到同样的目的,线程池的知识我会在后续讲。
我们先看怎么编写线程代码,以及它如何使用,简单的示例:
import time
from threading import Thread
def countdown(n):
while n > 0:
print('T-minus', n)
n -= 1
time.sleep(5)
t = Thread(target=countdown, args=(10,))
t.start()
countdown是一个计数的方法,正常执行它,我们一般使用countdown(10),就可以达到执行的目的,当你通过线程去调用它时,首先你需要从threading模块中引入Thread,然后,t = Thread(target=countdown, args=(10,)),当你创建好一个线程对象后,该对象并不会立即执行,除非你调用它的 start方法(当你调用 start() 方法时,它会调用你传递进来的函数,并把你传递进来的数传递给该函数),这就是一个简单的线程执行的例子。
你可以查询一个线程对象的状态,看它是否还执行:
if t.is_alive():
print('Still running')
else:
print('Completed, Go out !')
Python 解释器直到所有线程都终止前仍保持运行。对于需要长时间运行的线程或 者需要一直运行的后台任务,你应当考虑使用后台线程。
t = Thread(target=countdown, args=(10,), daemon=True)
t.start()
如果你需要终止线程,那么这个线程必须通过编程在某个特定点轮询来退 出。你可以像下边这样把线程放入一个类中:
class CountDownTask:
def __init__(self):
self._running = True
def terminate(self):
self._running = False
def run(self, n):
while self._running and n > 0:
print('T-minus', n)
n -= 1
time.sleep(5)
if __name__ == '__main__':
c = CountDownTask()
t = Thread(target=c.run, args=(10,))
t.start()
c.terminate()
t.join()
三、多线程初体验
上面的代码都是单线程,下面我们来看看多线程,并使用它来编写多线程爬虫,不过,在真正编写多线程爬虫之前,我们还要为编写多线程做准备,如何保持各线程之间的通信,在这里,我们使用队列Queue作为多线程之间通