Scrapy爬取新浪微博用户粉丝数据

一般来说pc端的信息是最为全面的,但是防范措施也是最严格的。所以不能走weibo.com这个域名下进行爬取,新浪微博在pc端的反扒措施较为全面。而手机端的数据则相对好爬取,而且数据都是Json格式,解析起来十分方便。新浪微博的m端域名为m.weibo.cn。虽然是手机端,但是我们依然可以在电脑浏览器打开该网站,不需要连接手机设置手机网络代理什么的。

1. 确认爬取目标

本次爬取的目标用户为微博大V“回忆专用小马甲”。


image.png

我们可以看到该用户的粉丝数有接近四千万。

2.分析请求

点击博主的粉丝数进入粉丝列表,往下找到“他的全部粉丝”。


image.png

在这个页面我们就可以查看该博主的全部粉丝。
F12打开浏览器的开发者模式,选择“Network选项”,过滤器切换到XHR选项,然后在粉丝列表页面往下拉,就可以看到很多Ajax请求。


Ajax请求.png

框选出来的请求就是动态加载的Ajax请求。
再查看这些请求返回的数据,返回格式是Json,其中 card_group 字段就是一个个粉丝数据,一个请求共有20个粉丝数据。
响应数据.png

然后再分析比较不同的请求URL,发现URL中只有since_id 这个参数在改变。


image.png

image.png

我们只需向这个url发送请求,然后解析数据就可以爬取相关信息。

3.使用Scrapy框架爬取数据

我们首先要明确要爬取哪些数据。
分析请求返回的Json数据,确定我们爬取的数据为:
粉丝ID:fid
昵称:screen_name
头像:profile_image_url
主页:profile_url
粉丝数:followers_count
关注数:follow_count
简介:desc1


数据结构.png

虽然有gender字段,但是性别默认为null(男)。查看了几个粉丝信息后,发现不管男女这个字段的值始终为null,所以这里没有取用这个信息。用户的详细信息在其主页可以查看。


主页.png

3.1 items

确定好信息字段后就可以编写items.py

class SinaItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    fid = scrapy.Field()
    screen_name = scrapy.Field()
    profile_image_url = scrapy.Field()
    profile_url = scrapy.Field()
    followers_count = scrapy.Field()
    follow_count = scrapy.Field()
    desc1 = scrapy.Field()

3.2 spider/

import scrapy
import json
import jsonpath
from sina.items import SinaItem
class FansSpider(scrapy.Spider):
    name = 'fans'
    allowed_domains = ['m.weibo.cn']
    #  博主url
    url = "https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_3217179555&since_id="
    offset = 1
    start_urls = [url+str(offset),]

    def parse(self, response):
        result = json.loads(response.text)
        cardgroup = jsonpath.jsonpath(result,"$..card_group")[0] # jsonpath返回一个列表
        for fan in cardgroup:
            # type(fan) --> dict
            item = SinaItem()
            item['fid'] = jsonpath.jsonpath(fan, "$..id")
            item['screen_name'] = jsonpath.jsonpath(fan, "$..screen_name")
            item['profile_image_url'] = jsonpath.jsonpath(fan, "$..profile_image_url")
            item['profile_url'] = jsonpath.jsonpath(fan, "$..profile_url")
            item['followers_count'] = jsonpath.jsonpath(fan, "$..followers_count")
            item['follow_count'] = jsonpath.jsonpath(fan, "$..follow_count")
            item['desc1'] = jsonpath.jsonpath(fan, "$..desc1")
            yield item
        self.offset += 1
        yield scrapy.Request(self.url + str(self.offset), callback=self.parse)

offset 为偏移量,解析完成之后再发送请求就可以完成循环爬取。

3.3 pipelines.py

管道文件有两个类,一个负责清洗,一个负责存入数据库。

class Qingxi(object):
    def process_item(self, item, spider):
        item['fid'] = item['fid'][0]
        item['screen_name'] = item['screen_name'][0]
        item['profile_image_url'] = item['profile_image_url'][0]
        item['profile_url'] = item['profile_url'][0]
        item['followers_count'] = item['followers_count'][0]
        item['follow_count'] = item['follow_count'][0]
        item['desc1'] = item['desc1'][0]
        return item

由于JsonPath返回的是一个列表,所以我们需要取出第一个元素,实际上也只有一个元素,不然不好存入数据库。

class SinaPipeline(object):
    def __init__(self, host, user, password, database,port):
        self.host = host
        self.user = user
        self.password = password
        self.database = database
        self.port = port

    def process_item(self, item, spider):
        sql = "insert into HuiYiZhuanYongXiaoMaJia (fid, screen_name, profile_image_url, profile_url, followers_count, follow_count, desc1) values (%s,%s,%s,%s,%s,%s,%s);"
        self.cursor.execute(sql, [item['fid'], item['screen_name'], item['profile_image_url'], item['profile_url'], item['followers_count'], item['follow_count'], item['desc1']])
        self.conn.commit()
        return item

    @classmethod
    def from_crawler(cls,crawler):
        #从settings.py 里获取配置信息
        return cls(
            host=crawler.settings.get('MYSQL_HOST'),
            user=crawler.settings.get('MYSQL_USER'),
            password=crawler.settings.get('MYSQL_PASSWORD'),
            database=crawler.settings.get('MYSQL_DATABASE'),
            port=crawler.settings.get('MYSQL_PORT')
        )

    def open_spider(self,spider):
        """
        当Spider开启时,这个方法被调用
        :param spider: Spider 的实例
        :return:
        """
        self.conn = pymysql.connect(
            host =self.host,
            user=self.user,
            password=self.password,
            database=self.database,
            port=self.port,
            charset='utf8'
        )
        self.cursor = self.conn.cursor()

    def close_spider(self, spider):
        """
        当Spider关闭时,这个方法被调用
        :param spider:
        :return:
        """
        self.cursor.close()
        self.conn.close()

3.4 middlewares.py

在刚开始没有设置User-Agent和下载延迟的时候,请求次数达到40多次后响应状态码变为了418:I am a teapot。特定搜索了一个418什么意思。

“客户端错误响应代码表示服务器拒绝冲泡咖啡,因为它是一个茶壶。 这个错误是超文本咖啡壶控制协议的参考,这是1998年愚人节的笑话。”
新浪还搞了这一手 o_o ....
这里下载中间件主要用来设置随机的User-Agent。

import random
class SinaDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.
    def __init__(self):
        self.user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134',
            'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0',
            'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
            'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko',
            'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)'
        ]

    def process_request(self, request, spider):
        request.headers['User-Agent'] = random.choice(self.user_agents)
        return None

3.5 最后在setting文件里开启相关配置即可。

数据库结果

如果我们要抓取粉丝的详细数据,比如性别,地址,学校,注册时间等可以再发送一个请求给粉丝的主页链接,而主页链接我们已经拿到了:profile_url,之后再写一个解析函数即可。

结论

我们发现最后爬取的结果只有四千多条。(简介有表情符号写不进数据库)
这是因为我们直接拿请求url在浏览器里测试:
https://m.weibo.cn/api/container/getIndex?containerid=231051_-fans-_2656274875&since_id=250,当测试到250页的时候会有返回数据。一页20条,总共5000条。

since_id=250.png

但是当测试到251页的时候,就没有数据了。
since_id=251.png

刚开始怀疑的是难道这个大V博主只有5000多粉丝,但是又觉得不太可能,毕竟粉丝数量显示的是3900多万。后来换了几个博主进行测试同样发现了这个问题,觉得是微博只会显示5000条数据。因为我们抓的是m.weibo.cn,也就是说,我们拿着手机在查看博主全部粉丝的时候如果一直往下滑一直往下滑,最多只会显示5000条数据。(这个结论是测试了央视新闻账号得出的,应该不只5000人关注了央视新闻吧)

PS最后我又测试了一下一个只有3000多粉丝的博主,粉丝结果居然只有5页,也就是100多个粉丝。但是首页显示的是3087,所以大概能得出这个博主也许可能是买的数据吧(因为我去买了2000粉丝,结果粉丝列表只有三个人)( ̄﹏ ̄;)

综上:本次爬取新浪微博用户粉丝数据没有什么卵用。
代码Github地址:https://github.com/wwxxee/Sina

你可能感兴趣的:(Scrapy爬取新浪微博用户粉丝数据)