【Python】大数据挖掘课程作业1——使用爬虫爬取B站评论、弹幕与UP主的投稿视频列表

【Python】大数据挖掘课程作业1——使用爬虫爬取B站评论、弹幕与UP主的投稿视频列表

数据挖掘部分的基本目标是:对于指定的UP主,能够获取其投稿视频列表;对于指定的视频,能够获取其视频标签、评论(包括评论下的回复)、弹幕。

文章默认读者对网络爬虫有一定的基础知识;

文章写作时(2020-06),B站正处于AV号像BV号过度的时期,部分API可能会在今后发生重大变化,请今后的读者注意。

获取指定UP主的投稿视频列表

首先,我们知道每一个B站帐号都有一个对应的数字UID,然后,通过在浏览器中访问用户的个人主页并查看后台请求,可以发现,用户的投稿视频列表信息是从api.bilibili.com/x/space/arc/search获取的,响应为JSON格式,具体的查询参数附加在URL后的查询字符串中,基本的查询参数如下:

参数 含义
mid 用户的UID
ps 返回的结果中需要包含多少个视频的信息
tid 视频的分类
pn 需要获取第几页投稿视频
keyword 搜索关键字
order 返回结果的排序方式

其中,tid视频的分类指的是我们再B站主页上看到的分类信息:
在这里插入图片描述
API的响应会告诉我们不同的分类用哪个数字代表,也会告诉我们这个UP主在不同的分类下各有几个投稿视频:

在这里插入图片描述

需要注意的是,如果一个UP主在某一个分类下没有投稿视频,那么API的响应不会包含这个分类的信息;

order是搜索结果的排序方式,有三种排序方式:按更新时间、按播放数量、按点赞数量,这三种排序方式分别对应order=pubdateorder=clickorder=stow

pn代表你想要第几页的投稿视频,ps代表一页要放下几个投稿视频,想象一下你在浏览网页,这两个参数的作用就不难理解了;

API返回的响应为JSON格式,结构如下:
在这里插入图片描述

其中,tlist包含了视频分类的信息,与API的tid参数有关,如上上张图所示;vlist则包含了我们需要的投稿视频信息,vlist的长度由ps参数决定,vlist中每一个对象的结构如下:

在这里插入图片描述
其中,我能确定意义的属性是:

属性 意义
comment 评论数量
paly 播放数量
pic 封面图片地址
subtitle 小标题
description 视频下方简介
title 视频标题
author UP主昵称
mid UP主UID
created 视频上传日期的UNIX时间戳
length 视频时长
aid av号
bvid bv号

到这里,API:api.bilibili.com/x/space/arc/search的用法就介绍完成了,最后还需要注意两件事情:首先,为了防止爬虫被403,我们需要复制浏览器的请求header,并添加到爬虫中。其次,响应数据使用gzip压缩的,使用前需要解压缩,Python内置有gzip模块。

从视频播放页面中提取视频标签和其他信息

截至本文写作时,可以通过bilibili.com/video/BVxxxxxxx或者bilibili.com/video/AVxxxxxxx获取视频的播放页面,如果我们直接使用urlopen获取播放页面(这时候的页面是没有经过JS动态加载),得到的播放页面的结构如下:

在这里插入图片描述

meta标签中,有两个值得我们注意一下,第一个是拥有属性property="og:url"的meta标签,这个标签的content的属性包含了这个视频使用AV号表示的播放页面(如果在只有BV号的情况下,想获取AV号,可以使用这个方法),第二个值得注意的meta标签拥有属性itemprop="commentCount",而这个标签的content属性记录了视频的评论数(如果需要最新的评论总数,可以使用这个方法)。

接下来,我们需要注意head中的最后两个script(第三个和第四个),第三个script中的内容如下:

在这里插入图片描述

可以看到,这个script标签中包含了大段的JSON数据,JSON数据中有很多URL,而这些URL中都包含了同一个ID:18xxxxx45,不难发现这个ID应该是指向视频的实际文件,但同时,这个ID也指向了视频的弹幕文件,所以在这里我们需要想办法提取出这个ID备用。

head中的第四个script标签中同样也是包含了大段JSON数据的JS代码,其中有一部分数据如下所示:

在这里插入图片描述

可以看到,这就是当前视频对应的标签,在我的课程设计中,我需要通过视频的标签对视频进行过滤,所以这里需要从页面中提取出标签信息。

我使用BeautifulSoup解析页面,并从meta标签和script标签中分离出我需要的信息,需要注意的是,在请求播放页面时仍需要完整的请求header以防止403,响应数据仍就经过gzip压缩,在进行分析前需要进行解压缩。

需要注意的是,这个页面结构只针对一般的视频,如果是电影、番剧、纪录片,页面结构会不一样,请注意。

获取某一视频的弹幕

获取B站弹幕的API是api.bilibili.com/x/v1/dm/list.so,用这个API获取弹幕只需要在查询字符串中添加一个参数oid,而oid就是上面一节那个需要在播放页面的script标签中提取的id。

与其他API不同,获取弹幕的API的响应使用了deflate算法进行压缩而不是gzip压缩,具体到如何使用Python解压缩deflate,->https://www.baidu.com

解压缩后,我们发现弹幕数据是XML格式的,如下所示:

在这里插入图片描述

可以看到,弹幕内容记录在了d标签中,而弹幕的属性记录在了d标签的p属性中,弹幕的属性是几个用逗号分隔的数字组成的字符串,我只能认出这当中第一个数字是弹幕出现在视频中的时间,第五个数字是弹幕被发送的时刻的UNIX时间戳,其余属性的含义我就无能为力了。

使用Python解析XML有很多种方法,我才用了xml.etree中的ElementTree类进行XML解析。

在浏览器请求弹幕的请求头中,有一个字段为Refer,内容为https://www.bilibili.com./video/BVxxxxxxx,所以在请求弹幕数据时,需要一并知道这个视频的BV号。

如果你的浏览器的请求头中还包含了Last-Modified这个字段,在复制请求头时请忽略这个字段(知道Last-Modified是干什么用的就能理解为什么要去掉它了)。

获取某一视频下的评论与评论下的回复

获取视频评论的API是https://api.bilibili.com/x/v2/reply,使用的查询参数如下:

参数 含义
type 我不知道有什么用,设置成1就行了
pn 获取第几页评论
oid 对应视频的AV号
sort 按热度排序的话,设置成0,按时间排序的话,设置成2

返回数据为JSON格式,如下所示:

在这里插入图片描述

其中,replies包含了我们需要的数据,每一个reply的格式如下:

在这里插入图片描述

其中,rpid表示这个评论的id,ctime代表评论时间的UNIX时间戳,comment下的message则是评论的具体内容,replies则代表了这个评论下的回复(不是所有,只有默认显示出来的几条)。

如果需要获取评论下的回复,则使用API:https://api.bilibili.com/x/v2/reply/reply,查询字符串中的参数如下:
参数 | 含义
type | 不知道有什么用,设置成1就行了
oid | 对应视频的AV号
pn | 需要第几页回复
ps | 一页回复中需要几条回复
root | 回复所属于的评论的rpid

返回的数据也是JSON格式,结构与评论数据的结构一样,只是replies下不再会有replies;

注意事项:

  • 返回的数据都是gzip压缩的,需要解压缩后使用;
  • 如果某一视频被关闭评论,则API返回未经压缩的提示信息,需要为此做好异常处理;
  • 浏览器的请求头中包含Refer: https://bilibili.com/video/BVxxxxxxx,严谨起见,在请求评论时一并提供视频的BV号;

相关代码

./liteTool.py

包装一下urlopen函数,如果发生错误会进行再次尝试,最多尝试3次,请求成功后会等待0.3s,防止请求过于频繁。

from urllib.request import urlopen, Request
from http.client import HTTPResponse

import time

firefox_cookie = '请从自己的浏览器获取’


def my_urlopen(url: Request) -> HTTPResponse:
    err = Exception()
    for _ in range(3):
        try:
            resp = urlopen(url=url)  # type: HTTPResponse
        except Exception as e:
            err = e
            print(e)
        else:
            time.sleep(0.3)
            return resp
    with open(file='./errors.data', mode='a', encoding='utf-8') as f:
        f.write(url.get_full_url() + '\n')
        f.write(str(err) + '\n')
        f.write('\n')
    raise Exception('HTTP请求失败!')

./GetBilibiliUploaderInfo.py

包含一个函数get_video_list_from_uploader_id(uid: str, start_time: datetime.datetime, end_time: datetime.datetime) -> list,根据用户的UID获取一定时间段内所有的投稿视频信息。

from urllib.request import Request
from http.client import HTTPResponse

from .liteTool import firefox_cookie, my_urlopen

import json
import gzip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值