据我观察,在那个招聘网站上注册的公司大概有十万家。如果要是让前面那只虫子一个公司一个公司进行爬取的话,咱就按照2秒钟一个公司吧,20W秒……55小时。
咱们开多线程玩吧!
Python的多线程也是蛮神奇的:和Java的多线程不同,Python的多线程不需要继承什么东西、实现什么东西,不需要写特定的方法和类。真的是“一句话实现多线程”。
self.THREADS = []
# 生成线程
for i in range(self.thread):
self.THREADS.append(threading.Thread(target=self.run_thread))
# 开启线程
for i in range(self.thread):
self.THREADS[i].start()
# 等待线程完成
for i in range(self.thread):
self.THREADS[i].join()
简单暴力是吧?
线程安全
然而我们不得不考虑一个问题:线程安全,也就是同步啊什么的。
你看啊,根据我们以前的代码,如果队列不空,就取出队头,进行操作。那好,队列中现在只有一个元素,两个线程跑并发,同时发现了队列不空,先后取队头——瞬间感觉有什么不对了吧?
所以呢,我们只能对队列进行加锁,保证每一次只有一个线程来操作队列。
# 互斥锁
self.queueLock = threading.Lock()
于是我们的代码长度再次加长了:
import csv
import threading
import requests
from bs4 import BeautifulSoup
class CsvSaver:
def __init__(self, path='./', filename='result.txt', method='w+'):
self.path = path
self.filename = filename
self.method = method
# 打开的文件
self.FILE = None
self.WRITER = None
def open(self):
self.FILE = open(self.path + self.filename, self.method)
self.WRITER = csv.writer(self.FILE, quoting=csv.QUOTE_ALL)
def write(self, data):
'参数是一个list'
self.WRITER.writerow(data)
self.FILE.flush()
def close(self):
self.FILE.close()
class Spider:
def __init__(self, dataSaver, failSaver=None, thread=5):
self.thread = thread
self.queue = []
# 爬行数据记录
self.dataSaver = dataSaver
# 失败Log记录
self.failSaver = failSaver
# 线程们
self.THREADS = []
# 互斥锁
self.queueLock = threading.Lock()
def addUrlBlock(self, start=100, end=200):
for index in range(start, end):
url = 'http://www.lagou.com/gongsi/' + str(index) + '.html'
self.addUrl(url)
def addUrl(self, url):
# 得到锁
self.queueLock.acquire()
# 添加URL
self.queue.append(url)
# 释放锁
self.queueLock.release()
def getUrl(self):
while self.queue:
url = None
# 得到锁
self.queueLock.acquire()
# 获得URL
if self.queue:
url = self.queue.pop(0)
# 释放锁
self.queueLock.release()
yield url
raise StopIteration
def runThread(self):
for url in self.getUrl():
try:
response = requests.get(url, timeout=5, allow_redirects=False)
data = BeautifulSoup(response.text)
name = data.h1.a.text.strip()
location = data.find('ul', 'info_list_with_icon').find(attrs={'class': 'location'}).span.text
self.dataSaver.write([name, location])
print("Success %s" % url)
except:
self.failSaver.write(["Fail", url])
def run(self):
self.dataSaver.open()
if self.failSaver:
self.failSaver.open()
self.THREADS = []
# 生成线程
for i in range(self.thread):
self.THREADS.append(threading.Thread(target=self.runThread))
# 开启线程
for i in range(self.thread):
self.THREADS[i].start()
# 等待线程完成
for i in range(self.thread):
self.THREADS[i].join()
self.dataSaver.close()
if self.failSaver:
self.failSaver.close()
if __name__ == '__main__':
dataStore = CsvSaver(filename='result.csv')
logStore = CsvSaver(filename='log.csv')
spider = Spider(dataSaver=dataStore, failSaver=logStore)
spider.addUrlBlock(100, 200)
spider.run()
print('全部完成')
我不知道这样写会不会发生“读-写”或者“写-写”死锁或者异常。但是就我的测试来看,好象是没有问题的。
(因为到现在还没有“动态地向队列里面添加url”)
实测结果
加入多线程后,这个可怜的招聘网站能在两个小时之内被我们爬取完毕。
但是!!Python的多线程很诡异!不是你开几个线程,它就给你跑几个线程的。不像Java那样能把四个CPU使用率都跑到100%。也就是说,Python在这一点上输得很厉害。这个可怜的招聘网站可以用Java拿200个线程来刷,大约半个小时就能全部跑下来了。
发表回复