1.多线程爬虫主要分三步实现:
1.发送请求,获取响应
2.提取数据
3.保存
对于上面三部分别使用队列来实现,分为URL队列,响应队列,数据队列。
2.为什么使用URL队列?
在单线程中,我们只需要从列表中一条一条的读取URL,在多个线程中,如果同时读URL列表,结果会是多个线程读取相同的URL地址,访问相同的数据,所以引入URL队列(先入先出的性质)。
多线程实现的流程图如下:
3.多线程爬虫代码(爬取糗事百科)
import requests
from lxml import etree
from queue import Queue
import threading
class QiubaiSpider:
def __init__(self):
self.url_temp = "https://www.qiushibaike.com/text/page/{}/"
self.headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"}
self.url_queue = Queue()
self.html_queue = Queue()
self.content_queue = Queue()
def get_url_list(self):
for i in range(1,4):
self.url_queue.put(self.url_temp.format(i))
def parse_url(self):
while True:
url = self.url_queue.get()
print(url)
response = requests.get(url,headers=self.headers)
self.html_queue.put(response.content.decode())
self.url_queue.task_done()
def get_content_list(self):
while True:
html_str = self.html_queue.get()
html = etree.HTML(html_str)
div_list = html.xpath("//div[@id='content-left']/div")
content_list = []
for div in div_list:
item = {}
item["id"] = div.xpath(".//h2/text()")
item["id"] = [i.replace("\n", "") for i in item["id"]]
item["content"] = div.xpath(".//div[@class='content']/span/text()")
item["content"] = [i.replace("\n", "") for i in item["content"]]
item["author_image"] = div.xpath(".//div/a/img/@src")
item["author_image"] = "https:"+item["author_image"][0] if len(item["author_image"]) > 0 else None
item["age"] = div.xpath(".//div[contains(@class, 'articleGender')]/text()")
item["age"] = item["age"] if len(item["age"]) > 0 else None
item["author_gender"] = div.xpath(".//div[contains(@class, 'articleGender')]/@class")
item["author_gender"] = item["author_gender"][0].split(" ")[-1].replace("Icon", "") if len(item["author_gender"]) > 0 else None
item["stats_vote"] = div.xpath(".//div/span/i/text()")
item["stats_vote"] = item["stats_vote"][0] if len(item["stats_vote"]) > 0 else None
item["user_commons"] = div.xpath(".//span[@class='stats-comments']/a/i/text()")
item["user_commons"] = item["user_commons"][0] if len(item["stats_vote"]) > 0 else None
content_list.append(item)
# return content_list
self.content_queue.put(content_list)
self.html_queue.task_done()
# 打印内容
def save_content_list(self):
while True:
content_list = self.content_queue.get()
for i in content_list:
# print(i)
pass
self.content_queue.task_done()
def run(self):
thread_list = []
# 1.url_list
t_url = threading.Thread(target=self.get_url_list)
thread_list.append(t_url)
# 2.遍历,发送请求,获取响应
for i in range(20):
t_parse = threading.Thread(target=self.parse_url)
thread_list.append(t_parse)
# 3.提取数据
for i in range(2):
t_html = threading.Thread(target=self.get_content_list)
thread_list.append(t_html)
# 4.保存
t_save = threading.Thread(target=self.save_content_list)
thread_list.append(t_save)
for t in thread_list:
t.setDaemon(True) # 把子线程设置为守护线程,该线程不重要主线程结束,子线程结束
t.start()
for q in [self.url_queue, self.html_queue, self.content_queue]:
q.join() # 让主线程等待阻塞,等待队列的任务完成之后再完成
print("主线程结束")
if __name__ == '__main__':
qiubaiSpider = QiubaiSpider()
qiubaiSpider.run()
多线程代码思路:首先导入thread,queue的包,定义了3个队列,分别存放 [网站地址,网页代码,网页提取的数据]。
随后将url一个一个put进去,在parse()中一个一个get,最后,将得到解析后的dict类型数据一个一个打印出来,注意,每次从队列中get数据,在程序结束后需要执行task_down结束进程,以上只是队列处理数据的流程。
接下来介绍多线的实现,在run方法中定义一个thread列表,将每一个队列加入进程,根据需要增加进程数,将进程设置为守护进程,启动进程,最后设置主线程等待阻塞。