Scrapy爬取新浪微博(一)


搭建准备

  • 代理池、Cookies池已经实现并可以正常运行
  • 安装Scrapy、PyMongo库

爬取思路

  1. 对于我们手动采集的垃圾用户,已经有用户的ID,则直接爬取。爬取内容包括:
    • 用户基本特征:等级、注册天数与活跃度、用户头像特征、用户昵称;
    • 用户行为特征:微博内容;
    • 用户社交特征:用户关注数、用户粉丝数。
  2. 对于作为训练样本的正常用户,通过递归爬取,通过遍历粉丝列表获取用户ID。

爬取分析

这里我们选取的爬取站点是:https://m.weibo.cn,此站点是微博移动端的站点。打开该站点会跳转到登录页面,这是因为主页做了登录限制。不过我们可以绕过登录限制,直接打开某个用户详情页面,例如打开周冬雨的微博,链接为:https://m.weibo.cn/u/1916655407,即可进入其个人详情页面,如下图所示。

png

获取关注列表:https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_1916655407_-_1042015:tagCategory_050&featurecode=20000320&type=uid&value=1916655407&page=2

个人基本资料:https://m.weibo.cn/api/container/getIndex?containerid=2302831916655407_-_INFO&title=%E5%9F%BA%E6%9C%AC%E8%B5%84%E6%96%99&luicode=10000011&lfid=2302831916655407&featurecode=20000320&type=uid&value=1916655407

可以获取微博内容:https://m.weibo.cn/api/container/getIndex?type=uid&value=1916655407&containerid=1005051916655407但是受到限制无法获取其关注、粉丝列表。

遂使用https://weibo.cn/页面, 并且可以看到简洁很多:

png

个人主页:https://weibo.cn/u/1916655407

关注列表:https://weibo.cn/1916655407/follow?page={}

微博内容:https://weibo.cn/1916655407/profile?page={}

个人资料:https://weibo.cn/1916655407/info

实现步骤

1. 生成scrapy项目

1
scrapy startproject weibo
1
2
scrapy genspider weibocn weibo.cn
#scrapy genspider <name> <allowed_domains>

2. 在spider中写入要爬取的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#构造的需要爬取的页面链接
#uid和page分别代表用户ID和分页页码
user_url = 'https://weibo.cn/u/{uid}' #用户主页
follow_url = 'https://weibo.cn/{uid}/follow?page={page}'#关注人列表页
fan_url = 'https://weibo.cn/{uid}/fans?page={page}' #粉丝列表页
weibo_url = 'https://weibo.cn/{uid}/profile?page={page}'#微博内容
user_info_url = 'https://weibo.cn/{uid}/info' #个人资料

#开始爬取的大V的ID
start_users = ['3217179555', '1742566624', '2282991915', '1288739185', '3952070245', '5878659096']

def start_requests(self):
for uid in self.start_users:
yield Request(self.user_url.format(uid=uid), callback=self.parse_user)

def parse_user(self, response):
self.logger.debug(response)

3. 解析用户的基本信息并生成Item

根据用户基本特征、用户行为特征和用户社交特征分为三个items

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from scrapy import Item, Field

class UserItem(Item):
collection = 'users'
id = Field()
name = Field() #昵称
avatar = Field() #头像
cover = Field()
gender = Field() #性别
description = Field() #简介
fans_count = Field()
follows_count = Field()
weibos_count = Field()
verified = Field()
verified_reason = Field()
verified_type = Field()
follows = Field()
fans = Field()
crawled_at = Field()

这里定义了collection字段,指明保存的Collection的名称。用户的关注和粉丝列表直接定义为一个单独的UserRelationItem,其中id就是用户的ID,follows就是用户关注列表,fans是粉丝列表,但这并不意味着我们会将关注和粉丝列表存到一个单独的Collection里。后面我们会用Pipeline对各个Item进行处理、合并存储到用户的Collection里,因此Item和Collection并不一定是完全对应的。

4. 提取数据

解析用户的基本信息,实现parse_user()方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def parse_user(self, response):
"""
解析用户信息
:param response: Response对象
"""
result = json.loads(response.text)
if result.get('data').get('userInfo'):
user_info = result.get('data').get('userInfo')
user_item = UserItem()
field_map = {
'id': 'id', 'name': 'screen_name', 'avatar': 'profile_image_url', 'cover': 'cover_image_phone',
'gender': 'gender', 'description': 'description', 'fans_count': 'followers_count',
'follows_count': 'follow_count', 'weibos_count': 'statuses_count', 'verified': 'verified',
'verified_reason': 'verified_reason', 'verified_type': 'verified_type'
}
for field, attr in field_map.items():
user_item[field] = user_info.get(attr)
yield user_item
# 关注
uid = user_info.get('id')
yield Request(self.follow_url.format(uid=uid, page=1), callback=self.parse_follows,
meta={'page': 1, 'uid': uid})
# 粉丝
yield Request(self.fan_url.format(uid=uid, page=1), callback=self.parse_fans,
meta={'page': 1, 'uid': uid})
# 微博
yield Request(self.weibo_url.format(uid=uid, page=1), callback=self.parse_weibos,
meta={'page': 1, 'uid': uid})复制代码

在这里我们一共完成了两个操作。

  • 解析JSON提取用户信息并生成UserItem返回。我们并没有采用常规的逐个赋值的方法,而是定义了一个字段映射关系。我们定义的字段名称可能和JSON中用户的字段名称不同,所以在这里定义成一个字典,然后遍历字典的每个字段实现逐个字段的赋值。
  • 构造用户的关注、粉丝、微博的第一页的链接,并生成Request,这里需要的参数只有用户的ID。另外,初始分页页码直接设置为1即可。

接下来,我们还需要保存用户的关注和粉丝列表。以关注列表为例,其解析方法为parse_follows(),实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def parse_follows(self, response):
"""
解析用户关注
:param response: Response对象
"""
result = json.loads(response.text)
if result.get('ok') and result.get('data').get('cards') and len(result.get('data').get('cards')) and result.get('data').get('cards')[-1].get(
'card_group'):
# 解析用户
follows = result.get('data').get('cards')[-1].get('card_group')
for follow in follows:
if follow.get('user'):
uid = follow.get('user').get('id')
yield Request(self.user_url.format(uid=uid), callback=self.parse_user)
# 关注列表
uid = response.meta.get('uid')
user_relation_item = UserRelationItem()
follows = [{'id': follow.get('user').get('id'), 'name': follow.get('user').get('screen_name')} for follow in
follows]
user_relation_item['id'] = uid
user_relation_item['follows'] = follows
user_relation_item['fans'] = []
yield user_relation_item
# 下一页关注
page = response.meta.get('page') + 1
yield Request(self.follow_url.format(uid=uid, page=page),
callback=self.parse_follows, meta={'page': page, 'uid': uid})复制代码

那么在这个方法里面我们做了如下三件事。

  • 解析关注列表中的每个用户信息并发起新的解析请求。我们首先解析关注列表的信息,得到用户的ID,然后再利用user_url构造访问用户详情的Request,回调就是刚才所定义的parse_user()方法。
  • 提取用户关注列表内的关键信息并生成UserRelationItemid字段直接设置成用户的ID,JSON返回数据中的用户信息有很多冗余字段。在这里我们只提取了关注用户的ID和用户名,然后把它们赋值给follows字段,fans字段设置成空列表。这样我们就建立了一个存有用户ID和用户部分关注列表的UserRelationItem,之后合并且保存具有同一个ID的UserRelationItem的关注和粉丝列表。
  • 提取下一页关注。只需要将此请求的分页页码加1即可。分页页码通过Request的meta属性进行传递,Response的meta来接收。这样我们构造并返回下一页的关注列表的Request。

抓取粉丝列表的原理和抓取关注列表原理相同,在此不再赘述。

接下来我们还差一个方法的实现,即parse_weibos(),它用来抓取用户的微博信息,实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def parse_weibos(self, response):
"""
解析微博列表
:param response: Response对象
"""
result = json.loads(response.text)
if result.get('ok') and result.get('data').get('cards'):
weibos = result.get('data').get('cards')
for weibo in weibos:
mblog = weibo.get('mblog')
if mblog:
weibo_item = WeiboItem()
field_map = {
'id': 'id', 'attitudes_count': 'attitudes_count', 'comments_count': 'comments_count', 'created_at': 'created_at',
'reposts_count': 'reposts_count', 'picture': 'original_pic', 'pictures': 'pics',
'source': 'source', 'text': 'text', 'raw_text': 'raw_text', 'thumbnail': 'thumbnail_pic'
}
for field, attr in field_map.items():
weibo_item[field] = mblog.get(attr)
weibo_item['user'] = response.meta.get('uid')
yield weibo_item
# 下一页微博
uid = response.meta.get('uid')
page = response.meta.get('page') + 1
yield Request(self.weibo_url.format(uid=uid, page=page), callback=self.parse_weibos,
meta={'uid': uid, 'page': page})复制代码

在这里parse_weibos()方法完成了两件事。

  • 提取用户的微博信息,并生成WeiboItem。这里同样建立了一个字段映射表,实现批量字段赋值。
  • 提取下一页的微博列表。这里同样需要传入用户ID和分页页码。

基本Spider已经完成。后面还需要对数据进行数据清洗存储、对接代理池、Cookies池防止反爬虫。

静觅:https://juejin.im/post/5b052d526fb9a07ac5608078#heading-10