1 import threading #多线程模块
2 import queue #队列模块
3 importrequests4 from lxml importetree5 importtime6 importrandom7 importjson8
9 concurrent = 3 #采集线程数
10 conparse = 3 #解析线程
11
12
13 class Parse(threading.Thread): #解析线程类
14 #初始化属性
15 def __init__(self, number, data_list, req_thread, f):16 super(Parse, self).__init__()17 self.number = number #线程编号
18 self.data_list = data_list #数据队列
19 self.req_thread = req_thread #请求队列,为了判断采集线程存活状态
20 self.f = f #获取文件对象
21 self.is_parse = True #判断是否从数据队列里提取数据
22
23
24 defrun(self):25 print('启动%d号解析线程' %self.number)26 #无限循环,
27 whileTrue:28 #如何判断解析线程的结束条件
29 for t in self.req_thread: #循环所有采集线程
30 if t.is_alive(): #判断线程是否存活
31 break
32 else: #如果循环完毕,没有执行break语句,则进入else
33 if self.data_list.qsize() == 0: #判断数据队列是否为空
34 self.is_parse = False #设置解析为False
35 #判断是否继续解析
36 if self.is_parse: #解析
37 try:38 data = self.data_list.get(timeout=3) #从数据队列里提取一个数据
39 except Exception as e: #超时以后进入异常
40 data =None41 #如果成功拿到数据,则调用解析方法
42 if data is notNone:43 self.parse(data) #调用解析方法
44 else:45 break #结束while 无限循环
46 print('退出%d号解析线程' %self.number)47
48
49 #页面解析函数
50 defparse(self, data):51 html =etree.HTML(data)52 #获取所有段子div
53 duanzi_div = html.xpath('//div[@id="content-left"]/div')54 for duanzi induanzi_div:55 #获取昵称
56 nick = duanzi.xpath('./div//h2/text()')[0]57 nick = nick.replace('\n', '')58 #获取年龄
59 age = duanzi.xpath('.//div[@class="author clearfix"]/div/text()')60 if len(age) >0:61 age =age[0]62 else:63 age =064 #获取性别
65 gender = duanzi.xpath('.//div[@class="author clearfix"]/div/@class')66 if len(gender) >0:67 if 'women' ingender[0]:68 gender = '女'
69 else:70 gender = '男'
71 else:72 gender = '中'
73 #获取段子内容
74 content = duanzi.xpath('.//div[@class="content"]/span[1]/text()')[0].strip()75 #获取好笑数
76 good_num = duanzi.xpath('./div//span[@class="stats-vote"]/i/text()')[0]77 #获取评论
78 common_num = duanzi.xpath('./div//span[@class="stats-comments"]//i/text()')[0]79 item ={80 'nick': nick,81 'age': age,82 'gender': gender,83 'content': content,84 'good_num': good_num,85 'common_num': common_num,86 }87 self.f.write(json.dumps(item, ensure_ascii=False) + '\n')88
89
90 class Crawl(threading.Thread): #采集线程类
91 #初始化
92 def __init__(self, number, req_list, data_list):93 #调用Thread 父类方法
94 super(Crawl, self).__init__()95 #初始化子类属性
96 self.number =number97 self.req_list =req_list98 self.data_list =data_list99 self.headers ={100 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36'
101 }102 #线程启动的时候调用
103
104 defrun(self):105 #输出启动线程信息
106 print('启动采集线程%d号' %self.number)107 #如果请求队列不为空,则无限循环,从请求队列里拿请求url
108 while self.req_list.qsize() >0:109 #从请求队列里提取url
110 url =self.req_list.get()111 print('%d号线程采集:%s' %(self.number, url))112 #防止请求频率过快,随机设置阻塞时间
113 time.sleep(random.randint(1, 3))114 #发起http请求,获取响应内容,追加到数据队列里,等待解析
115 response = requests.get(url, headers=self.headers)116 if response.status_code == 200:117 self.data_list.put(response.text) #向数据队列里追加
118
119
120 defmain():121 #生成请求队列
122 req_list =queue.Queue()123 #生成数据队列 ,请求以后,响应内容放到数据队列里
124 data_list =queue.Queue()125 #创建文件对象
126 f = open('duanzi.json', 'w', encoding='utf-8')127 #循环生成多个请求url
128 for i in range(1, 13 + 1):129 base_url = 'https://www.qiushibaike.com/8hr/page/%d/' %i130 #加入请求队列
131 req_list.put(base_url)132 #生成N个采集线程
133 req_thread =[]134 for i inrange(concurrent):135 t = Crawl(i + 1, req_list, data_list) #创造线程
136 t.start()137 req_thread.append(t)138 #生成N个解析线程
139 parse_thread =[]140 for i inrange(conparse):141 t = Parse(i + 1, data_list, req_thread, f) #创造解析线程
142 t.start()143 parse_thread.append(t)144 for t inreq_thread:145 t.join()146 for t inparse_thread:147 t.join()148 #关闭文件对象
149 f.close()150
151 if __name__ == '__main__':152 main()