美文网首页
2020-07-22--爬虫项目-06--爬取简书用户信息保存到

2020-07-22--爬虫项目-06--爬取简书用户信息保存到

作者: program_white | 来源:发表于2020-07-22 15:18 被阅读0次

1.创建项目

在工作目录下cmd中创建scrapy项目:

scrapy startproject jianshupro

使用pycharm打开项目后。在terminal中传递到/spiders目录下,执行“

scrapy genspider jianshu www.jianshu.com

项目结构:

2.项目配置

1.配置settings:

机器人守则
ROBOTSTXT_OBEY = False
#间隔时间
DOWNLOAD_DELAY = 0.5

管道配置先不设置。

2.配置启动文件
在项目下新建main.py,添加:

from scrapy import cmdline

cmdline.execute('scrapy crawl jianshu'.split())

3.start_request()

在这个函数之前,首先要设置请求头的参数,在

import scrapy
from fake_useragent import UserAgent

class JianshuSpider(scrapy.Spider):
    name = 'jianshu'
    # allowed_domains = ['www.jianshu.com']
    # start_urls = ['http://www.jianshu.com/']
    
    # 基础头
    base_headers = {'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4',
                    'Host': 'www.jianshu.com',
                    'Accept-Encoding': 'gzip, deflate, sdch',
                    'X-Requested-With': 'XMLHttpRequest',
                    'Accept': 'text/html, */*; q=0.01',
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36',
                    'Connection': 'keep-alive',
                    'Referer': 'http://www.jianshu.com'}
    # 生成随机的用户代理
    ajax_headers = dict(base_headers, **{"X-PJAX": "true", 'User-Agent': UserAgent().random})

start_requests爬虫生命周期的启动方法,读取起始网址列表,向爬虫发送Request对象:让引擎回调parse().
也就是说这个函数必须yield一个Request对象,包含url,回调函数以及headers参数。

3.1测试start_request()

我们要爬取的数据为简书上的用户信息,所以在简书上的推荐作者页面进行爬取:

在这个页面中没当点击加载更多按钮时,会发起ajax请求,给https://www.jianshu.com/recommendations/users?page=num
num便是页数,返回的数据会以html格式拼接在当前页中。每点一次按钮,num+1。
num =1的时候也是可以展示出来第一页的内容的,与当前的页面显示相同内容。

所以在spiders/jianshu.py:

 # start_requests爬虫生命周期的启动方法,读取起始网址列表,向爬虫发送Request对象:让引擎回调parse()
    def start_requests(self):
        print('执行start_requests...')
        yield scrapy.Request(
            url='https://www.jianshu.com/recommendations/users?page=2',
            callback=self.parse,
            headers=self.ajax_headers
        )


    def parse(self, response):
        print('执行parse...')

配置item.py:


import scrapy


class JianshuproItem(scrapy.Item):
    # define the fields for your item here like:
    nickname = scrapy.Field()           #昵称
    slug = scrapy.Field()                  #用户链接接
    head_pic = scrapy.Field()       #用户头像
    gender = scrapy.Field()           #性别
    is_contract = scrapy.Field()
    following_num = scrapy.Field()       #关注数
    followers_num = scrapy.Field()       #粉丝人数
    articles_num = scrapy.Field()          #文章个数
    words_num = scrapy.Field()            #字数
    be_liked_num = scrapy.Field()      #喜欢数

测试运行:

可以看出向该网址发送了请求,并且parse()被执行了。

测试成功

4.多页爬取

在上一步中只测试了第二页的信息,要进行多页爬取
1.首先将其实url,也就是start_request()中的yield中url改为第一页,表示从第一页爬取。
2.声明类变量start_page = 1,要在parse()中累加生成新的url。

    def start_requests(self):
        print('执行start_requests...')

        self.start_page = 1

        yield scrapy.Request(
            url='https://www.jianshu.com/recommendations/users?page=1',
            headers=self.base_headers
        )

3.在parse()中解析了当前页的response之后,要生成下一页的url,所以在parse()加上:

# 生成新的url
        self.start_page += 1
        new_url = f'https://www.jianshu.com/recommendations/users?page={self.start_page}'
        if self.start_page < 101:
            # 传给调度器,进行循环爬取
            yield scrapy.Request(
                url= new_url,
                callback=self.parse,
                headers=self.ajax_headers
            )

start_page+1,生成新的url,判断是否该页是否存在(有数据),如果有,yield给调度器进行入队,实现循环爬取。

一.用户列表url的爬取(解析parse中的response),获取用户的id信息,进一步爬取用户主页信息和用户粉丝以及用户动态

首先要在作者列表中爬取每个作者的url,进入该作者的主页,才能进一步爬取想要的信息。

1.首先我们查看用户列表页的元素结构以及用户主页的url结构

parse():

    def parse(self, response):
        print('执行parse...')

        '''解析response'''
        # 解析所有作者的主页链接的列表
        auth_list = response.xpath('//div[@class="wrap"]/a/@href').extract()
        # print(auth_list)

        # 生成新的url
        self.start_page += 1
        new_url = f'https://www.jianshu.com/recommendations/users?page={self.start_page}'
        if self.start_page:
            # 传给调度器,进行循环爬取
            yield scrapy.Request(
                url= new_url,
                callback=self.parse,
                headers=self.ajax_headers,
            )


分析:
解析返回的response,获取所有用户跳转链接。

2.根据用户主页链接截取获取用户主页url,用户粉丝页的url,用户动态页的url

1.首先要分析各个页面的url的格式:

  1. 用户主页url:
  1. 用户粉丝页url:
  1. 用户动态页url:
    动态页使用的是ajax请求进行展示的:

返回的是html格式。

2.编写获取各个页面url的代码,并yield给响应的函数回调执行并测试(在回调函数中获取当前用户的用户id--slug_id)

        for au in auth_list:
            # 获取作者id
            slug_id = au.split('/')[-1]
            # 获取用户主页的url
            au_url = f'https://www.jianshu.com/u/{slug_id}'

            # 传给调度器,回调函数为解析用户主页信息的回调函数
            yield scrapy.Request(
                url = au_url,
                callback=self.parse_auth,
                headers=self.ajax_headers,
                meta={'slug':slug_id}
            )

            # 通过作者url获取粉丝列表页面
            fan_url = f'https://www.jianshu.com/users/{slug_id}/followers'
            yield scrapy.Request(
                url=au_url,
                callback=self.parse_fans,
                headers=self.ajax_headers,
                meta={'slug': slug_id}
            )

            # 用户的操作动态信息页面url
            schedule_url = f'https://www.jianshu.com/users/{slug_id}/timeline'
            yield scrapy.Request(
                url=schedule_url,
                callback=self.parse_schedule,
                headers=self.ajax_headers,
                meta={'slug': slug_id}
            )


    '''解析用户主页信息函数'''
    def parse_auth(self,response):
        print('执行parse_auth...')
        # 获取用户id
        slug = response.meta['slug']
        print(slug)

    '''解析作者粉丝的信息'''
    def parse_fans(self,response):
        print('执行parse_fans...')
        slug = response.meta['slug']
        print(slug)

    '''解析作者动态的信息'''
    def parse_schedule(self,response):
        print('执行parse_schedule...')
        slug = response.meta['slug']
        print(slug)

3.代码分析:

  1. 用户主页url获取:发现用户主页的url的格式为https://www.jianshu.com/u/用户id,与上一步中所解析到的用户跳转链接(/user/用户id)不完全一致,所以主页url要对用户url列表项中截取拼接完成。yield给调度器,回调函数为parse_auth()--处理解析用户主页信息的函数,并将用户id--slug_id作为参数传给回调函数。

  2. 用户粉丝页url获取:
    用户粉丝的页面格式为:
    https://www.jianshu.com/users/ 用户id /followers
    拼接完成后获取该用户的粉丝列表页面的url,yield给调度器,回调函数为parse_fans()--解析用户粉丝页面的函数,并将用户id--slug_id作为参数传给回调函数。

  3. 用户动态的url获取:
    经实验用户动态的ajax请求地址为:https://www.jianshu.com/users/ef4f2422125f/timeline?page=num,(num为页数)。
    拼接完成后获取该用户的动态列表页面的url,yield给调度器,回调函数为parse_schedule()--解析用户粉丝页面的函数,并将用户id--slug_id作为参数传给回调函数。

  4. 测试运行:在每个回调函数中都打印当前用户的id--slug_id。

运行:

乱七八糟看不懂,但是点击每个连接都能跳转到相应页面,说明测试成功。

二.解析三个页面信息

1.解析用户首页的信息

注意:将parse()中yield给调度器的Request()中headers都改为self.base_headers


在parse_auth()中,编写解析用户主页的函数。
直接上代码:

    def parse_auth(self,response):
        print('执行parse_auth...')
        # print(response.text)
        # 获取用户id
        slug = response.meta['slug']
        # print(slug)
        # 实例化item类对象
        po = JianshuproItem()

        # 获取包含用户信息的div元素
        div_main_top = response.xpath('//div[@class="main-top"]')
        po['nickname'] = div_main_top.xpath('.//div[@class="title"]/a/text()').extract_first()         #昵称
        po['slug'] = slug                                                                         #用户id
        po['head_pic'] = div_main_top.xpath('.//a[1]/img/@src').extract_first()          # 用户头像
        gender = div_main_top.xpath('.//div[@class="title"]/i/@class').extract_first()           # 性别
        if gender:
            po['gender'] = gender.split('-')[-1]
        else:
            po['gender']  ='NO SEX'

        po['following_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[1]/div/a/p/text()').extract_first())   #关注数
        po['followers_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[2]/div/a/p/text()').extract_first())    # 粉丝个数
        po['articles_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[3]/div/a/p/text()').extract_first())    # 文章个数
        po['words_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[4]/div/p/text()').extract_first())         # 字数
        po['be_liked_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[5]/div/p/text()').extract_first())     # 喜欢数
        yield po

这里边有几个需要注意的点:

  1. 就是有的用户没有设置性别,这样的话获取到的数据为None,需要进行判断手动设定。
  2. 获得认证头衔以及资产先不进行爬取,在后边的粉丝列表页的爬取中,我们只是在粉丝列表页进行爬取基本信息,只能爬取到nickname,关注数,粉丝数,文章数,字数,喜欢数。爬取不到头衔以及资产项
    那么这时就有一个问题:当一个有资产的用户关注了当前爬取的用户时,我们只爬取到一部分呢信息存入数据库中,在继续爬取时,在遇到该用户时,虽然爬取到器资产项或头衔等完整信息,但是加不进数据库中,因为我们后边设置数据库插入为不重复的插入,这样的话就造成了数据的错误,一旦数据发生错误,爬虫就没有意义。
    为了此项目进度,先这样设置,后续的话进行优化,将粉丝列表中的超链接获取出来,循环该列表,截取拼接成新的用户url,yield给调度器,回调函数为parse_auth(),获取完整信息。

记得将settings中的管道放开。

运行:

2.解析用户粉丝页面

因为用户粉丝页的粉丝众多,是分页显示的,也就是说:
www.jianshu.com/users/51b4ef597b53/followers
www.jianshu.com/users/51b4ef597b53/followers?page=1
是等价的。
每当用户划到一定程度时,会自动发起ajax请求给page递增的url。返回的数据为html格式的内容:

返回的内容都是li列表项,拼接在当前页面中d:

我们要获取的数据:

但是经过实验,page不能一味的增加,当超出指定范围后,会出现重新回到前几页数据的页面。
所以解决的方案是在获取到该作者的信息后,将该作者的粉丝数记录下来,在爬取其粉丝时,进行计数,当计数器超过粉丝数时,停止爬取。

有两种方式获取到粉丝的信息,一种与上述内容相结合,另一种单独在最后展示并解释。

  1. 在start_requests()中声明一些变量用于初始化和存储粉丝数量的变量声明
    def start_requests(self):
        print('执行start_requests...')
        # 因为在start_reuqest中已经读取了第一页
        self.start_page = 1
        # 存储当前作者粉丝数
        self.follow_num = {}

        yield scrapy.Request(
            url='https://www.jianshu.com/recommendations/users?page=1',
            headers=self.base_headers
        )

2.获取到作者信息后,要进行初始化工作:

# 作者列表函数
    def parse(self, response):
        print('执行parse...')

        '''解析response'''
        # 解析所有作者的主页链接的列表
        auth_list = response.xpath('//div[@class="wrap"]/a/@href').extract()
        print(auth_list)
        for au in auth_list:
            # 获取作者id
            slug_id = au.split('/')[-1]
            # print(slug_id)
            
            # 初始化存储该用户粉丝数的字典
            self.follow_num[slug_id] = 0
            # 设置粉丝页起始页码
            self.fans_start_page = 1
            # 设置粉丝数量
            self.fans_num = 0
            # 动态起始页
            self.timeline_start_page = 1 
            # 获取用户主页的url
            au_url = f'https://www.jianshu.com/u/{slug_id}'
            # 传给调度器,回调函数为解析用户主页信息的回调函数
            yield scrapy.Request(
                url = au_url,
                callback=self.parse_auth,
                headers=self.base_headers,
                meta={'slug':slug_id}
            )

            # 通过作者url获取粉丝列表页面
            fan_url = f'https://www.jianshu.com/users/{slug_id}/followers'
            yield scrapy.Request(
                url=fan_url,
                callback=self.parse_fans,
                headers=self.base_headers,
                meta={'slug': slug_id}
            )

            # 用户的操作动态信息页面url
            schedule_url = f'https://www.jianshu.com/users/{slug_id}/timeline'
            yield scrapy.Request(
                url=schedule_url,
                callback=self.parse_schedule,
                headers=self.base_headers,
                meta={'slug': slug_id}
            )

        # 生成新的url
        self.start_page += 1
        new_url = f'https://www.jianshu.com/recommendations/users?page={self.start_page}'
        if self.start_page:
            # 传给调度器,进行循环爬取
            yield scrapy.Request(
                url= new_url,
                callback=self.parse,
                headers=self.base_headers,
            )

3.在用户主页解析之后将该用户的粉丝数存入初始化时创建的字典follow_num中。

        po['followers_num'] = int(div_main_top.xpath('.//div[@class="info"]//ul/li[2]/div/a/p/text()').extract_first())    # 粉丝个数
        # 存储当前用户的粉丝数
        self.follow_num[slug]  =po['followers_num']

4.粉丝页解析代码:

    '''解析作者粉丝的信息'''
    def parse_fans(self, response):
        print('执行parse_fans...')
        # 解析作者对应粉丝信息
        slug = response.meta['slug']
        # print(f'解析作者对应粉丝信息,作者标识:{slug}')
        # 获取当前粉丝列表信息
        ulist = response.xpath('//div[@id="list-container"]/ul[@class="user-list"]/li')
        for u in ulist:
            # 解析每个粉丝对象,并发送到管道中
            item = JianshuproItem()

            # 昵称
            nick = u.xpath('./div[@class="info"][1]/a/text()').extract_first()
            # 获取关注、粉丝数、文章数
            temp = u.xpath('./div[@class="info"][1]/div[@class="meta"][1]/span/text()').extract()
            arr_temp = []
            for t in temp:
                arr = t.split(" ")[-1]
                arr_temp.append(arr)
            following_num = int(arr_temp[0])
            followers_num = int(arr_temp[1])
            articles_num = int(arr_temp[2])
            # print(f'粉丝昵称:{nick}')

            item['nickname'] = nick
            item['following_num'] = following_num
            item['followers_num'] = followers_num
            item['articles_num'] = articles_num

            # 获取字数和获得喜欢数
            meta_text = u.xpath('.//div[@class="meta"]')[1].xpath('text()').extract_first()
            meta_num = re.findall('\d+', meta_text)
            item['words_num'] = int(meta_num[0])
            item['be_liked_num'] = int(meta_num[1])

            yield item
            # 对应粉丝进行计数
            self.fans_num += 1
            # 判断粉丝获取是否结束
            if self.fans_num > self.follow_num[slug]:
                # print(self.fans_num,self.follow_num[slug])
                break

        # 是否获取下一页的url以及传给调度器
        if self.fans_num < self.follow_num[slug]:
            # 获取粉丝分页url
            self.fans_start_page += 1
            xurl = f'https://www.jianshu.com/users/86b81ed8e35c/followers?page={self.fans_start_page}'
            yield scrapy.Request(url=xurl, callback=self.parse_fans,
                          headers=self.base_headers, meta={'slug': slug})

在解析的过程中,如果计数器大于粉丝数,那么跳出循环,停止解析。
如果不大于,解析之后获取新的url发送给调度器进行下一页的数据爬取。

用户动态获取

用户动态页面的相应格式与粉丝大概一致,都是返回li标签,拼接在页面中,但是每次访问的url不仅page是动态的,还有一个max_id也是动态的。
经过发现,这个max_id的值为上一页返回的最后一个元素的li标签的id值-1:

所以我们首先要获取这个max-id值,用于获取下一页新的url:

'''解析作者动态的信息'''
    def parse_schedule(self,response):
        print('执行parse_schedule...')
        slug = response.meta['slug']
        # print(slug)

        # 获取信息的li列表
        li = response.xpath('//ul[@class="note-list"]/li')
        if not li:
            return
        #for it in li:
         #   # 获取li元素的id属性值
        #    xid = it.xpath('./@id').extract_first()
         #   print(xid)             # feed-645346514

        # 获取下一页的max_id取值
        tid = li[-1].xpath('./@id').extract_first()
        tid = int(tid.split('-')[-1])-1

        # 生成新的url
        self.timeline_start_page+=1
        xurl = f'https://www.jianshu.com/users/{slug}/timeline?max_id={tid}&page={self.timeline_start_page}'
        yield scrapy.Request(
            url=xurl,
            callback=self.parse_schedule,
            headers=self.base_headers,
            meta={'slug':slug}
        )

由于每次返回的都是li标签拼接在页面中,所以利用response解析到所有的列表项组成li列表。如果该列表不不存在,说明没有获取到元素,说明动态没有了,那么直接return。

如果有值,就获取列表的最后一项,进行解析出id值,转为int后-1,获的max_id。

相关文章

网友评论

      本文标题:2020-07-22--爬虫项目-06--爬取简书用户信息保存到

      本文链接:https://www.haomeiwen.com/subject/mjczkktx.html