案例:多线程爬虫
目标:爬取糗事百科段子,待爬取页面URL:http://www.qiushibaike.com/8hr/page/1
要求:
- 使用requests获取页面信息,用XPATH/re 做数据提取
- 获取每个帖子里的 用户头像链接、用户主页、用户名、用户性别、用户年龄、段子内容、点赞次数、评论次数
- 保存到本地json文件内
- 采用多线程
queue(队列对象)
queue是python中的标准库,可以直接import queue引用,队列是线程间最常用的交换数据的形式
python下多线程:
对于资源,加锁是个重要的环节。因为python原生的list, dict等,都是not thread safe的。而queue,是thread safe(线程案例)的,因此在满足使用条件下,建议使用队列
- 初始化:class queue.Queue(maxsize) FIFO(先进先出)
- 常用方法:
- queue.Queue.qsize() 返回队列的大小
- queue.Queue.empty() 如果队列为空,返回True,反之返回False
- queue.Queue.full() 如果队列满了,返回True,反之返回False
- queue.Queue.get([block[, timeout]]) 从队列中取出一个值,timeout为等待时间
- 创建一个“队列”对象
- import queue
- myqueue = queue.Queue(maxsize = 10)
- 将一个值放入队列中
- myqueue.put(10)
- 将一个值从队列中取出
- myqueue.get()
- # -*- coding:utf-8 -*-
-
#使用了线程库 import threading #队列 from Queue import Queue from lxml import etree import requests import json class ThreadCrawl(threading.Thread): def __init__(self,threadName,pageQueue,dataQueue): super(ThreadCrawl,self).__init__() #调用父类初始化方法,父类增加不用修改 #threading.Thread.__init__(self) #调用父类初始化方法,父类增加要更改 #线程名字 self.threadName = threadName #页码队列 self.pageQueue = pageQueue #数据队列 self.dataQueue = dataQueue self.heardes = {'User_Agent':'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)'} def run(self): print("启动"+self.threadName) while not CEAWL_EXIT: try : ''' 取出一个数字,先进先出 可选参数block,默认值为True 1.如果队列为空,block为True的话,不会结束,会进入阻塞状态,直到数列有新的数据 2.如果队列为控,block为False的话,就会弹出一个Queue.empty()的异常 ''' page = self.pageQueue.get(False) #组合url url = "https://www.qiushibaike.com/8hr/page/"+str(page)+"/" content = requests.get(url,headers = self.heardes) self.dataQueue.put(content.text) except: pass #抛出Queue.empty()不用处理直接pass掉就行 print("结束"+self.threadName) class ThreadParse(threading.Thread): def __init__(self,threadName,dataQueue,filename): super(ThreadParse,self).__init__() #线程名 self.threadName = threadName #数据队列 self.dataQueue = dataQueue #保存解析后数据的文件名 self.filename = filename def run(self): while not PARSE_EXIT: try: html = self.dataQueue.get(False) self.parse(html) except: pass def parse(self,html): global lock try: text = etree.HTML(html) # 返回所有段子的节点位置,contains(@ ,)模糊查询方法,第一个参数是要匹配的标签,第二个参数是标签名的模糊匹配的部分内容 node_list = text.xpath("//div[contains(@id,'qiushi_tag_')]") items = {} for node in node_list: username = node.xpath('.//h2')[0].text image = node.xpath(".//div[@class='thumb']//@src") content = node.xpath(".//div[@class='content']/span")[0].text zan = node.xpath(".//span/i")[0].text comments = node.xpath(".//span/a/i")[0].text items = { "username": username.strip(), "image": image, "content": content.strip(), "zan": zan, "comments": comments } with lock: try: self.filename.write(json.dumps(items, ensure_ascii=False).encode('utf-8') + '\n') print("写入成功") except TypeError: print("items中又不是json格式的所以无法写入") except Exception as e: print(e) lock = threading.Lock() CEAWL_EXIT = False #相当于一个开关 PARSE_EXIT = False def main(): #页码的队列,表示10个页面 pageQueue = Queue(10) #放入1-10的数字,先选先进 for i in range(1,11): pageQueue.put(i) #采集结果(每页的HTML源码)的数据队列,参数为空表示不限制 dataQueue = Queue() filename = open('duanzi.json','a') # 三个采集线程的名字 crawlList = ['采集线程1号','采集线程2号','采集线程3号'] threadcrawl = [] for threadName in crawlList: thread = ThreadCrawl(threadName,pageQueue,dataQueue) thread.start() threadcrawl.append(thread) #三个解析线程的名字 parseList = ['解析线程1号','解析线程2号','解析线程3号'] #存储三个解析线程 threadparse = [] for threadName in parseList: thread = ThreadParse(threadName,dataQueue,filename) thread.start() threadparse.append(thread) #等待pageQueue队列为空,也就是等待之前的操作执行完毕 while not pageQueue.empty(): pass #如果pageQueue为空,采集线程退出循环 global CEAWL_EXIT CEAWL_EXIT = True global PARSE_EXIT PARSE_EXIT = True print("pageQueue为空") #这里是加的阻塞,等待所有线程完成 for thread in threadcrawl: thread.join() with lock: filename.close() if __name__ == "__main__": main()