题目要求

利用分布式爬虫,从百度百科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内容

这里需要注意:如果按照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()

运行结果及运行截图

第一部分

代码运行截图
redis数据截图

第二部分

代码运行截图
mongodb数据截图

发表回复