Python爬虫实战:爬取B站Top100视频,分析弹幕、播放量和分类并数据可视化

您所在的位置:网站首页 b站视频播放量排行 Python爬虫实战:爬取B站Top100视频,分析弹幕、播放量和分类并数据可视化

Python爬虫实战:爬取B站Top100视频,分析弹幕、播放量和分类并数据可视化

2024-05-07 13:01| 来源: 网络整理| 查看: 265

前言

最近挺好奇的,B站每天Top100,具体什么视频最多,播放量和视频的弹幕数有没有比例关系。

所以,我们就来写一个Python爬虫,批量看看B站Top100是什么内容吧。

受限篇幅,只展现关键代码。Cron定时任务等,就不做展示啦。代码没有重构,如果有很大小伙伴需要,我重构了放GitHub吧~

最终效果(可视化数据):https://mintimate.github.io/BilibiliSpiderDemo/

环境依赖

首先是Python的环境依赖,Python3自然不用多说。部分的依赖:

bilibili_api==9.0.2 matplotlib==3.3.4 numpy==1.18.2 pandas==1.2.4 Pillow==9.0.0 pyecharts==1.9.0 requests==2.23.0

这里重点介绍两个依赖包:bilibili_api和pyecharts。

bilibili_api

项目地址:https://github.com/MoyuScript/bilibili-api

使用这个库文件,主要是用于解决B站弹幕二进制加密问题:

123456789from bilibili_api import video, sync

# 根据视频BV号,获取视频信息v = video.Video(bvid='BV1AV411x7Gs')# 弹幕dms = sync(v.get_danmakus(0))for dm in dms: print(dm)

另外,这个库可能不会再更新:停止运维

但是,还有另外一个项目:https://github.com/SocialSisterYi/bilibili-API-collect

如果bilibili_api失效,可以用这个代替(比如:B站弹幕获取)。

pyecharts

项目地址:https://pyecharts.org/#/

这个Pyecharts完全可以替换原来的matplotlib库,还不用处理中文字库问题。

之所以刚开始还用matplotlib…… 主要是,我平时Python写的不多,代码写到一半,才发现有Pyecharts这个好用的库⁄(⁄ ⁄ ⁄ω⁄ ⁄ ⁄)⁄

支持的图多:官网

数据爬取

首先,我们需要爬取B站视频Top前100,观察页面,可以看到数据接口:

数据接口

request请求参数

使用request模拟请求:

12345678910POPULAR_URL = "https://api.bilibili.com/x/web-interface/popular"HEADERS = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'referer': 'https://www.bilibili.com/', 'x-csrf-token': '', 'x-requested-with': 'XMLHttpRequest', 'cookie': '' , 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'}

参数已经脱敏

如果再观察上述的数据接口,可以发现,这个请求的参数:

pn:页数。

页数参数

其中,pn=1代表Top20,pn=2代表Top21-40,以此类推。所以需要写一个for循环;配合数据接口内的分析:

数据分析代码如下:

123456789101112131415161718def get_popular_list(): """ 获取排行榜1-100 :param pn: :return: All bvid_list """ bvidList = [] for i in range(1, 6): query = "pn=" + str(i) r = requests.get(POPULAR_URL, headers=HEADERS, params=query) resultList = r.json()['data']['list'] for item in resultList: bvidList.append( bilibili_api.aid2bvid( item['aid'] ) ) return bvidList

bilibili_api.aid2bvid为aid转bvid,由bilibili_api提供。

最后,运行看看效果:

123if __name__ == '__main__': for i in get_popular_list(): print(i)

数据结果

视频详情获取

bilibili_api内提供了获取视频详情的方法,比如:

12345678910111213from bilibili_api import video, sync

def _method_get_videos_info(bvid): # 实例化 Video 类 v = video.Video(bvid=bvid) # 获取视频信息 info = sync(v.get_info()) # 打印视频信息 return info

if __name__ == '__main__': print(_method_get_videos_info("BV1cL411w7RB"))

输出:视频详情

所以,刚刚我们已经用request获取了全部Top100视频的Bv号,现在只需要for循环一次,就可以得到全部视频的信息了。

既然这么简单,我们就多一步,将信息变成文件流,存储到csv文件内:

1234567891011121314151617181920212223242526272829303132def _method_save_to_csv(filename_last, video_info): file_path = ("../数据/videoTop_%s.csv" % filename_last) # 判断路径是否存在 if not os.path.exists("../数据/"): os.makedirs("../数据/") # 如果文件存在,则覆盖写入 f = open(file_path, mode="w", encoding='utf-8', newline='') csv_writer1 = csv.DictWriter(f, fieldnames=[ '视频bvid', '视频aid', 'videos', '视频分类', '版权所有', '视频封面', '视频标题', '上传时间', '公开时间', '视频描述', '播放量', '点赞量'] ) csv_writer1.writeheader() for info in video_info: info = _method_get_videos_info(info) data_dict1 = { '视频bvid': info.get('bvid', "None"), '视频aid': info.get('aid', "None"), 'videos': info.get('videos', "None"), '视频分类': info.get('tname', "None"), '版权所有': info.get('copyright', "None"), '视频封面': info.get('pic', "None"), '视频标题': info.get('title', "None"), '上传时间': info.get('ctime', "None"), '公开时间': info.get('pubdate', "None"), '视频描述': info.get('desc', "None"), '播放量': info.get('stat', "None").get('view', "None"), '点赞量': info.get('stat', "None").get('like', "None") } csv_writer1.writerow(data_dict1) f.close()

最后结果:最后结果

这样,我们的视频详情就获取完毕了。

弹幕获取

弹幕怎么获取呢?其实也很简单,和刚刚一样,用外部包:

获取弹幕

需要注意的是:B站弹幕获取有IP响应次数限制。解决的方法:

使用time.sleep,对主线程休眠。 使用IP池。

还需要注意,一些视频关闭弹幕功能,需要进行try...catch:

123456789try: dms = sync(v.get_danmakus(0))# 敏感视频,关闭弹幕功能except DanmakuClosedException: dms = []except ResponseCodeException: dms = []except KeyError: dms = []

源码就不展示了:

爬取结果

另外,如果你爬取时候不行(因为B站更改了数据接口,而Bilibili_api项目停更了),可以注释源码内的这条数据:

注释

或者你可以使用B站的数据接口:http://api.bilibili.com/x/v2/dm/web/seg.so参数:

参数名 类型 内容 必要性 备注 type num 弹幕类 必要 1:视频弹幕 oid num 视频cid 必要 pid num 稿件avid 非必要 segment_index num 分包 必要 6分钟一包

下载下来是seg.so文件,需要解密,用protobuf编译:https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/grpc_api/bilibili/community/service/dm/v1/dm.proto

就可以解析下载下来的二进制文件:解析二进制文件并解析弹幕

视频前6分钟,有一个弹幕投票…… 所以观众发的都是投票弹幕…… ╮( ̄▽ ̄"")╭

看看存储的效果:

存储效果

接下来就是数据可视化了。

数据可视化

首先,数据可视化前,一定需要有足够的数据。上文数据爬取,其实我在服务器上用cron定期执行了一个月了。所以得到的数据比较多:

数据

所以,数据可视化时候,我先合并了数据。之后,进行画图。首先,获取了视频分类的词频:

1234classify_list = []for item in _method_get_videos_info_documents("../数据"): classify_list.extend(_method_get_classify_info("../数据/" + item))classify_top = collections.Counter(classify_list)

其中,_method_get_videos_info_documents方法:

12345678910111213141516171819def _method_get_videos_info_documents(filepath): ''' 根据弹幕文件夹名获取当天视频Top100文件(videoTop_xxx.csv) :param video_top_file_name: :return: ''' video_top_list = [] for item in method_get_danmu_folders(filepath): video_top_list.append("videoTop_" + item) return video_top_list

def _method_get_classify_info(video_top_file_name): ''' 根据视频信息csv文件,获取视频全部分类 :param video_top_file_name: :return: ''' df = pd.read_csv(video_top_file_name + ".csv", low_memory=False) return df['视频分类'].tolist()

为了做词云,提取全部弹幕,并选取前500词:

123456# 获取清洗好后的弹幕listworld_list = _method_get_danmu_content_by_path("../数据清洗/全部弹幕.csv")# 用collections进行词频统计result = collections.Counter(world_list)# 获取前1000too_100 = result.most_common(500)

现在就可以画图了。

定义页面

首先,我们定义一个页面:

123456789101112131415161718192021def page_simple_layout(data_list, world_list, date_list, view_count_list, danmu_count_list): ''' 画图页面 :param data_list: 视频信息list :param world_list: Top前500弹幕 :param date_list: 日期list :param view_count_list: 播放量list :param danmu_count_list: 每天对应的弹幕数list :return: None ''' print(data_list.most_common(50)) page = Page() page.add( draw_pie(data_list.most_common(10)), draw_line(data_list.most_common(50)), draw_bar(date_list, view_count_list, danmu_count_list), draw_word_cloud(world_list), ) page.render("Total.html")

这个是pyecharm的页面方法,其中page.add内的内容,为其他图的方法名。可以看到,我们依次会渲染:

饼图:视频分类Top10 折线图:视频Top分类50 柱状图:视频播放量和弹幕关系 词云:弹幕词云

page.render为最后写入的地址,需要为HTML,最后Python会进行渲染。

饼图:视频分类Top10

这个很简单,更着官方文档自己写一下就出来了:

1234567891011121314151617def draw_pie(data_list) -> Pie: choose_list = [] values_list = [] for item in data_list: choose_list.append(item[0]) values_list.append(item[1]) c = ( Pie(init_opts=opts.InitOpts(width="100%")) .add("", [list(z) for z in zip(choose_list, values_list)]) .set_colors(["blue", "green", "yellow", "red", "pink", "orange", "purple"]) .set_global_opts( title_opts=opts.TitleOpts(title="Top10"), ) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # .render("饼图.html") ) return c

需要注意,这里是作为对象返回一个Pie实例,用于给Page渲染。提前看看效果:最后效果

折线图:视频Top分类50

折线图也是一样的:

12345678910111213141516171819202122232425def draw_line(data_list) -> Line: x_data = [] y_data = [] for item in data_list: x_data.append(item[0]) y_data.append(item[1]) line = (Line(init_opts=opts.InitOpts(width="100%")).add_xaxis(xaxis_data=x_data) .add_yaxis( series_name="Top50折线堆叠", stack="总计", y_axis=y_data, label_opts=opts.LabelOpts(is_show=True), ) .set_global_opts( title_opts=opts.TitleOpts(title="Top50折线堆叠"), datazoom_opts=[opts.DataZoomOpts()], tooltip_opts=opts.TooltipOpts(trigger="axis"), yaxis_opts=opts.AxisOpts( type_="value", axistick_opts=opts.AxisTickOpts(is_show=True), splitline_opts=opts.SplitLineOpts(is_show=True), ), xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False), ) ) # .render("折线图.html") return line

最后效果:

最后效果

柱状图:视频播放量和弹幕关系

柱状图?应该是最简单的一个了:

12345678910def draw_bar(xaxis, yaxis1, yaxis2): c = ( Bar(init_opts=opts.InitOpts(width="100%")) .add_xaxis(xaxis) .add_yaxis("播放量/500", yaxis1, stack="stack1") .add_yaxis("弹幕", yaxis2, stack="stack1") .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) .set_global_opts(title_opts=opts.TitleOpts(title="播放量和弹幕-日期统计")) ) return c

最后效果:

最后效果

词云:弹幕词云

词语就是前期的collection集合词频处理比较麻烦,不然也是很简单的:

12345678def draw_word_cloud(word_list): wc = (WordCloud(init_opts=opts.InitOpts(width="100%")) .add("", data_pair=word_list, word_size_range=[10, 100], width="90%", height="85%") .set_global_opts( title_opts=opts.TitleOpts(title="弹幕Top100词云图"), ) ) return wc

最后效果:

最后效果

为什么我前文说是Top 500弹幕,结果这里变成Top 100呢?其实是……500太多,页面无法展示全……所以临时改成100……

END

最后,我们来分析一下数据吧:

对于想投入自媒体的用户,建议选择“日常”类 或“搞笑”视频类的的视频,作为自己的创作目标,容易流量变现。

最后,根据这近20天的单天分析,可以轻易得出,周五到周天,普遍的网络用语会更多,应该是周末学生放假,或者上班族休息的原因,可以想到,Bilibili这个平台流量很大,总的用户群体很年轻。

若对文章很感兴趣,可以B站关注我ヾ(≧▽≦*)o

点此跳转“爱发电”页面(○` 3′○)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3