多线程爬虫
多线程与网络爬虫
Python的多线程对于IO密集型代码比较友好,网络爬虫能够在获取网页的过程中使用多线程,从而加快速度。
下面将以获取访问量最大的1000个中文网站的速度为例,通过和单线程的爬虫比较,证实多线程方法在网络爬虫速度上的提升。这1000个访问量最大的中文网站是在Alexca.cn上获取的,地址如下:https://github.com/Santostang/PythonScraping/blob/master/Cha%207%20-%E6%8F%90%E5%8D%87%E7%88%AC%E8%99%AB%E7%9A%84%E9%80%9F%E5%BA%A6/alexa.txt
简单单线程爬虫
首先,以单线程(单进程)的方式抓取这1000个网页,代码如下:
import requests import time link_list = [] with open('alexa.txt','r') as file: file_list = file.readlines() for eachone in file_list: link = eachone.split('\t')[1] link = link.replace('\n','') link_list.append(link) start = time.time() for eachone in link_list: try: r=requests.get(eachone) print(r.status_code,eachone) except Exception as e: print('Error: ',e) end = time.time() print('串行的总时间为:',end-start)
运行结果为:
串行的总时间为: 2341.845444202423
Python 3多线程
threading模块则提供了Thread类来处理线程,包括以下方法。
·run():用以表示线程活动的方法。
·start():启动线程活动。
·join([time]):等待至线程中止。阻塞调用线程
·isAlive():返回线程是否是活动的。
·getName():返回线程名。
·setName():设置线程名。
下面介绍使用threading的一个简单的例子,看看多线程是如何运行的。
import threading import time class myThread (threading.Thread): def __init__(self, name, delay): threading.Thread.__init__(self) self.name = name self.delay = delay def run(self): print ("Starting " + self.name) print_time(self.name,self.delay) print ("Exiting " + self.name) def print_time(threadName,delay): counter = 0 while counter <3: time.sleep(delay) print(threadName,time.ctime()) counter +=1 threads = [] #创建新线程 thread1 = myThread("Thread-1",1) thread2 = myThread("Thread-2",2) #开启新线程 thread1.start() thread2.start() #添加线程到线程列表 threads.append(thread1) threads.append(thread2) #等待所有线程完成 for t in threads: t.join() print("Exiting Main Thread")
运行上述代码,得到的结果是:
Starting Thread-1 Starting Thread-2 Thread-1 Wed Jul 18 10:48:42 2018 Thread-2 Wed Jul 18 10:48:43 2018 Thread-1 Wed Jul 18 10:48:43 2018 Thread-1 Wed Jul 18 10:48:44 2018 Exiting Thread-1 Thread-2 Wed Jul 18 10:48:45 2018 Thread-2 Wed Jul 18 10:48:47 2018 Exiting Thread-2 Exiting Main Thread
在上述代码中,我们将任务手动地分到两个线程中,即thread1=myThread("Thread-1",1)。然后在myThread这个类中对线程进行设置,使用run()表示线程运行的方法,当counter小于3时,打印该线程的名称和时间。
然后使用thread.start()开启线程,使用threads.append()将线程加入线程列表,用t.join()等待所有子线程完成才会继续执行
简单多线程爬虫
刚刚我们使用threading完成了多线程的简单代码,现在就将Python多线程的代码应用在获取1000个网页上,并开启5个线程,代码如下:
import threading import requests import time link_list = [] with open('alexa.txt', 'r') as file: file_list = file.readlines() for eachone in file_list: link = eachone.split('\t')[1] link = link.replace('\n','') link_list.append(link) start = time.time() class myThread (threading.Thread): def __init__(self, name, link_range): threading.Thread.__init__(self) self.name = name self.link_range = link_range def run(self): print ("Starting " + self.name)#线程开始 crawler(self.name, self.link_range)#爬取指定范围的网页 print ("Exiting " + self.name)#线程结束 #爬取函数,爬取指定范围的网页, #threadName指定线程名,link_range指定爬取网页的范围 def crawler(threadName, link_range): for i in range(link_range[0],link_range[1]+1): try: r = requests.get(link_list[i], timeout=20)#用requests库获取网页 print (threadName, r.status_code, link_list[i])#打印网页状态码 except Exception as e: print(threadName, 'Error: ', e)#处理异常 thread_list = [] link_range_list = [(0,200),(201,400),(401,600),(601,800),(801,1000)] # 创建新线程 for i in range(0,5):#从0到4,开启5个线程。 thread = myThread("Thread-" + str(i), link_range_list[i]) thread.start() thread_list.append(thread)#将线程加入线程列表之中 # 等待所有线程完成 for thread in thread_list: thread.join() end = time.time() print ('简单多线程爬虫的总时间为:', end-start) print ("Exiting Main Thread")
该程序运行后,得到如下输出:
简单多线程爬虫的总时间为: 331.42331290245056 Exiting Main Thread
上述代码存在一些可以改进之处:因为我们把整个链接列表分成了5等份,所以当某个线程先完成200条网页的爬虫后会退出线程,这样就只剩下有4个线程在运行。相对于5个线程,速度会有所下降,到最后剩下一个线程在运行时,就会变成单线程。
使用Queue的多线程爬虫
有没有一种方式能够在完成1000个网页的抓取之前都使用5个线程的全速爬虫呢?这时可以使用Queue。Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue
将这1000个网页放入Queue的队列中,各个线程都是从这个队列中获取链接,直到完成所有的网页抓取为止,代码如下:
import threading import requests import time import queue as Queue #从文件中获取待爬取的网址 link_list = [] with open('alexa.txt', 'r') as file: file_list = file.readlines() for eachone in file_list: link = eachone.split('\t')[1] link = link.replace('\n','') link_list.append(link) #网页爬取线程:myThread start = time.time() class myThread (threading.Thread): def __init__(self, name, q): threading.Thread.__init__(self) self.name = name self.q = q def run(self): print ("Starting " + self.name) while True: try: crawler(self.name, self.q) except: break print ("Exiting " + self.name) #主爬取函数 def crawler(threadName, q): readme=''' 这就好比银行排队, 单线程表示该支行只有一个窗口,要处理1000个人需要花费很长时间; 简单的多线程是开5个窗口,然后将1000个人平均分到5个窗口中, 因为有些窗口可能处理得比较快,所以先处理完了, 但是其他窗口的人不能去那个已经处理完的窗口排队,这样就造成了资源的闲置。 Queue方法相对于前两种方法而言,是将1000人排一个长队, 5个窗口中哪个窗口有了空位,便叫队列中的第一个过去 (FIFO先入先出方法)。 ''' url = q.get(timeout=2)#获取队列中的链接 try: r = requests.get(url, timeout=20) print (q.qsize(), threadName, r.status_code, url) except Exception as e: print (q.qsize(), threadName, url, 'Error: ', e) threadList = ["Thread-1", "Thread-2", "Thread-3","Thread-4", "Thread-5"] workQueue = Queue.Queue(1000)#建立了一个队列的对象workQueue threads = [] # 创建新线程 for tName in threadList: thread = myThread(tName, workQueue)#将workQueue对象传入了myThread中, thread.start() threads.append(thread) # 填充队列 for url in link_list:#将获得的1000个网址填充到队列之中 workQueue.put(url) # 等待所有线程完成 for t in threads: t.join() end = time.time() print ('Queue多线程爬虫的总时间为:', end-start) print ("Exiting Main Thread")
Queue多线程爬虫的总时间为: 283.01684951782227
Exiting Main Thread
返回 大数据分析
参考文档:《Python爬虫:从入门到实践》