题目要求
利用分布式爬虫,从百度百科https://baike.baidu.com中爬取每个词条的统计信息,
包括:点赞人数,转发次数;浏览次数,编辑次数和最新更新时间。
分析网页
进入百度百科网页,下拉到最后可以看到很多分类,其中一个大分类包含很多小分类,我们要抓取的是小分类中的内容。

进入分类,会发现有的分类有下一页


进入一个词条,我们要抓取的是点赞人数、转发次数、编辑次数和最新更新时间,分别在图示区域


现在我们要做的是获得这几个数据
获取词条分类数据
注意:在请求数据时,需要加入请求头,否则获取不到数据,请求头可以在百度百科页面右键点击检查,点击network然后刷新页面点击第一个获取,如下图

分析网页可知,词条分类数据在下图所示的地方,即 id=”commonCategories” 的div标签下的dl标签下的dd标签下的所有a标签

获取词条分类数据下所有词条
分析网页可知,词条名字以及url在下图所示的地方,即 class=”grid-list grid-list-spot” 的div标签下的ul标签下的li标签下的
class=”photo” 的div标签下的所有a标签的 href属性和title属性


如果有下一页还需爬取所有页面内容
下一页地址在文字内容为”下一页>”的a标签的href属性

获取词条内信息
- 首先是点赞人数和分享次数

这里需要注意:如果按照Element的内容构造解析函数会发现你解析不到内容,原因是你获取的response响应内容没有这个数据,Element内的点赞数和分享数是由一个接口获取后生成的,这个接口在下图所示的低方,接口url为https://baike.baidu.com/api/wikiui/sharecounter?lemmaId=3193221&method=get 请求参数 lemmaId 为 3193221,方法为get


而请求参数 lemmaId 可以在response响应内容中获取, 如下图

可以在获取该lemmaId参数后构造接口url获取点赞数和分享数数据
- 编辑次数
- 浏览次数在response相应内容内,如下图所示

- 浏览次数
- 浏览次数跟点赞次数和分享次数一样,在另外的接口内获取,接口如下图
- 接口url为 https://baike.baidu.com/api/lemmapv?id=db3f0c117f3f56d5057ca79e&r=0.7697961398744013
- 经过测试,这个接口只需要添加id参数即可,id参数可以在response响应内容中找到,如下图
- 所以构造接口url为 https://baike.baidu.com/api/lemmapv?id= 获取的id



- 更新日期可以在response响应内容内获取 如下图所示

编写代码
- 代码架构
- 代码分为两部分
- 一部分爬取并上传 词条分类、词条名、词条url 到redis集合内(只需要运行一个)
- 一部分获取redis集合内数据并爬取该数据到mongodb数据库(可以运行多个)
- 代码分为两部分
第一部分
爬取并上传 词条分类、词条名、词条url 到redis集合内
import requests from lxml import etree import redis class GetCitiao: '''爬取百度词条url队列并放入redis数据库''' def __init__(self): '''初始化''' # 连接redis数据库 self.r = redis.Redis(host='47.101.222.18',password='wangshiji',port=6379,decode_responses=True) # 请求头信息 self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'Host': 'baike.baidu.com', 'Upgrade-Insecure-Requests': '1', 'Referer': 'http://baike.baidu.com/renwu' } def get_response(self,url,data=None): '''获取xpath响应''' if data is not None: html = requests.get(url=url, headers=self.headers, data=data) else: html = requests.get(url=url, headers=self.headers) html.encoding='utf-8' return html.text def get_citiao_classify(self): '''获取词条分类及url''' index_url = 'https://baike.baidu.com/' response = self.get_response(url=index_url) response = etree.HTML(response) column_content = response.xpath('//div[@id="commonCategories"]//dl') results = [] # 保存所有词条分类及url for dl in column_content: item = {} item['title'] = dl.xpath('.//dd//a/text()') item['href'] = ['http://baike.baidu.com' + i for i in dl.xpath('.//dd//a/@href')] for title,href in zip(item['title'],item['href']): meta = {'title':title,'url':href} results.append(meta) print(results) return results def get_page_citiao(self,title,url): '''获取该页所有词条信息并存入redis数据库''' response = self.get_response(url=url) response = etree.HTML(response) column_content = response.xpath('//div[@class="grid-list grid-list-spot"]//ul//li') for li in column_content: item = {} item['title'] = title item['name'] = li.xpath('./div[@class="photo"]/a/@title')[0] item['url'] = 'http://baike.baidu.com' + li.xpath('./div[@class="photo"]/a/@href')[0] print(item) # 存入redis数据库 self.r.sadd('baiducitiao_requests', str(item)) # 如果该分类词条存在下一页 next_page_url = response.xpath('//a[text()="下一页>"]/@href') if len(next_page_url) > 0: next_page_url = 'http://baike.baidu.com/fenlei/' + next_page_url[0] return next_page_url else: return None def run(self): ''' 1.获取词条分类 2.进入分类,获取所有词条信息 3.将词条信息放入redis数据库 ''' # 获取词条分类 citiaoClassify = self.get_citiao_classify() # 获取所有词条信息 for classify in citiaoClassify: nextPageUrl = self.get_page_citiao(title=classify['title'],url=classify['url']) while nextPageUrl is not None: print(nextPageUrl) nextPageUrl = self.get_page_citiao(title=classify['title'], url=nextPageUrl) if __name__ == '__main__': getcitiao = GetCitiao() getcitiao.run()
第二部分
获取redis集合内数据并爬取该数据到mongodb数据库
from time import sleep import redis import re import requests import threading import pymongo from lxml import etree from bs4 import BeautifulSoup class SaveCitiao: '''从redis数据库获取url爬取信息并保存到mongodb数据库''' def __init__(self): # 初始化数据库 self.r = redis.Redis(host='47.101.222.18',port=6379,password='wangshiji',decode_responses=True) # mongodb数据库配置 (保存到本地) self.mg = pymongo.MongoClient('mongodb://localhost:27017/') self.mydb = self.mg['baiducitiao'] self.mycol = self.mydb['citiao'] # 请求头信息 self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'Host': 'baike.baidu.com', 'Upgrade-Insecure-Requests': '1', 'Referer': 'http://baike.baidu.com/renwu' } def get_proxy(self): '''获取代理''' r = requests.get('http://127.0.0.1:5000/get') proxy = BeautifulSoup(r.text, "lxml").get_text() proxies = {'https': proxy,'http:':proxy} return proxies def get_request_info(self,): '''获取url等数据信息(字典) 描述:{name: 词条名, url: 词条url, title: 词条类型} ''' item = eval(self.r.spop('baiducitiao_requests')) # 判断该词条是否已经爬取 judge = self.r.sadd('citiao_requested',str(item)) while judge == 0: # 词条已爬取,重新获取词条 print('词条已爬取,重新获取词条') item = eval(self.r.spop('baiducitiao_requests')) judge = self.r.sadd('citiao_requested', str(item)) return item def get_html(self,url,data=None): '''获取xpath响应''' #proxy = self.get_proxy() #print(proxy) if data is not None: html = requests.get(url=url, headers=self.headers, data=data,proxies=None) else: html = requests.get(url=url, headers=self.headers,proxies=None) html.encoding='utf-8' return html.text def get_citiao_info(self,content): '''获取词条内信息 1.获取url 2.请求url获取response(xpath)响应 3.请求url获取其它数据 ''' print(content) url = content['url'] response = self.get_html(url=url,data=None) item = {} item['title'] = content['title'] item['name'] = content['name'] html = etree.HTML(response) try: id = html.xpath('//a[@class="lemma-discussion cmn-btn-hover-blue cmn-btn-28 j-discussion-link"]/@href')[0] id = id[id.find('Id') + 3:] except: id = re.findall('newLemmaId:.*?"(.*?)"',response) if len(id)>0: id = id[0] url = 'https://baike.baidu.com/api/wikiui/sharecounter?lemmaId={}&method=get'.format(id) response2 = eval(self.get_html(url=url)) item['likeCount'] = response2['likeCount'] item['shareCount'] = response2['shareCount'] item['dateUpdate'] = html.xpath('//meta[@itemprop="dateUpdate"]/@content') if len(item['dateUpdate']) > 0: item['dateUpdate'] = item['dateUpdate'][0] id = re.findall('newLemmaIdEnc:"(.*?)"', response) if len(id) > 0: id = id[0] url = 'https://baike.baidu.com/api/lemmapv?id={}'.format(id) response3 = eval(self.get_html(url=url)) item['visitCount'] = response3["pv"] item['changeCount'] = re.findall('<li>(.*?)<a href.*?>历史版本</a></li>',response) if len(item['changeCount']) > 0: item['changeCount'] = item['changeCount'][0] print(item) self.save_to_mongodb(data=item) def save_to_mongodb(self,data): '''将数据插入mongodb数据库''' self.mycol.insert_one(data) def run(self): ''' 1.获取url等数据信息 2.解析数据 :return: ''' # 1.获取url等数据信息 content = True while content is not None: content = self.get_request_info() # 2.解析数据 self.get_citiao_info(content=content) sleep(3) if __name__ == '__main__': citiao = SaveCitiao() citiao.run()
运行结果及运行截图
第一部分


第二部分

