当前位置: 移动技术网 > IT编程>脚本编程>Python > python大作业——B站弹幕数据爬取与分析

python大作业——B站弹幕数据爬取与分析

2020年07月08日  | 移动技术网IT编程  | 我要评论

前段时间要写一个Python大作业,选题为B站弹幕数据分析,由于是Python新手,所以参考了以下的文档,再次感谢分享技术的人
同时也因为本次是只是本人记录一次初学Python期间的一次较有意思的大作业,语法简陋勿喷。

参考文档:https://blog.csdn.net/weixin_34161029/article/details/91713988

B站弹幕数据分析

  • 第一部分——使用爬虫抓取弹幕数据

    1. B站弹幕数据分析,首先我们需要抓取到B站视频的弹幕数据,才能进行数据分析

    2. 选取分析的对象是B站UP主 观视频工作室 的**《睡前消息》** 系列视频中的最新15期,即 110-124期视频(2020-05-03 ~ 2020-06-05) 的弹幕作为本次分析的弹幕,爬取的日期从第110期发布的日期开始到爬取的时候**(2020-05-03 ~ 2020-06-09)**

    3. 首先需要从 观视频工作室 的个人主页处找到 睡前消息 频道,爬这里就可以获取到所需要的视频链接,页面是动态页面,所以这里选择用 selenium库 爬取链接,存进Link.txt文件备用

      注意这里我用的是比较偷懒的爬取方法,只能爬到最新15期,要爬其他的可以自己改进一下算法

    4. 在视频页的console处可以输入window.cid来获取cid,但在selenium模拟中往浏览器console处输入的方法,搜索苦久无果,所以想出了如此下策:在视频页打开F12,在elements处搜索cid,发现是有的,这条语句是与网关有关的,通过bs4匹配到该语句,用正则匹配出来即可

      正则前缀/ 后缀-1-

    5. 要爬取B站的弹幕,有两种方法 ( 据我所知 )

      5.1 第一种方法 是https://comment.bilibili.com/{cid}.xml,这种方法可以获得最新的maxlimit数量(一般这个数量是3000)的弹幕,但是没办法获得历史弹幕

      5.2 第二种方法 是https://api.bilibili.com/x/v2/dm/history?type=1&oid={cid}&date={yyyy-mm-dd},这种方法可以获取从某一天开始的maxlimit数量的弹幕,把想要获得的日期遍历一遍,虽然可能会漏掉一些弹幕(比如某一天弹幕数超过了maxlimit,就获取不到超出的部分),但也算是获取了大部分弹幕,比上一种方法好,采取这种方法

      而第二个方法由于是api,所以需要用到cookie,可以用以下的方法来获取cookie

    获取Cookie

    1. 接下来就是分析这个xml文件中的弹幕数据,例子为如下:

      <d p="369.34300,1,25,16777215,1590515762,0,a6fb6c47,33226011652915207">脱发</d>

      这个 p字段 中的参数含义为如下:(百度搜索得到的答案)

      第一个参数 369.34300 记作DM_time,是弹幕在视频中出现的时间,以秒数为单位。

      第二个参数 1 记作DM_mode,是弹幕的模式1…3 滚动弹幕 4底端弹幕 5顶端弹幕 6.逆向弹幕 7精准定位 8高级弹幕

      第三个参数 25 记作DM_font,是字号, 12非常小,16特小,18小,25中,36大,45很大,64特别大

      第四个参数 16777215 记作DM_color,是字体的颜色以HTML颜色的十进制为准

      第五个参数 1590515762 记作DM_realTime,是发送弹幕的时间戳

      第六个参数 0 记作DM_pool,是弹幕池 0普通池 1字幕池 2特殊池(高级弹幕)

      第七个参数 a6fb6c47 记作DM_userID,是发送者的ID,用于“屏蔽此弹幕的发送者”功能

      第八个参数 33226011652915207 记作DM_id,是弹幕在弹幕数据库中rowID,也就是这条弹幕是历史总弹幕的第几条

      p字段以外的 弹幕本体 记作DM_text

    2. 把爬取到的弹幕分别放到多个csv文件中,每一期一个csv文件,方便后面的数据分析

    3. 至此爬取数据部分完成

  • 第二部分——使用Pandas库进行数据分析

    1. 在爬取并保存了所有弹幕数据之后,先进行去重,可对比还未去重时文件的大小去重后文件的大小

      1.1 未去重:(至于110-112和113-124日期不一样,是因为抓113-124的时候被封了IP,只能等到第二天早上抓110-112)
      未去重

      1.2 去重后:
      去重后

    2. 然后进行如下的可视化分析:

      2.1 每一期弹幕总数的变化 折线图
      弹幕总数变化图

      结合上图,我们发现每期的弹幕总数统计波动有些大,其中一些弹幕数,比如116期,甚至在这里统计只有不到5000条,但是在B站上可以看到:
      B站截图1

      这一期其实总弹幕是有1.4W的,统计到的弹幕数仅占1/3左右,出现这种情况是由于 在视频发出的某一天里,大多数用户发过了弹幕,而这部分弹幕超出了B站统计的maxlimit ,导致我们没办法统计到这部分弹幕。

      相对而言,弹幕数多的视频就是弹幕不仅仅在某一天大量被发出,比如115期,这里我们统计到了有**9000+**的弹幕数,而B站上的数据:
      B站截图2

      统计到的弹幕大约占70%,这是由于并没有很多用户在某一个日期大量发出弹幕,导致弹幕库溢出,也从某方面说明了该视频的受众时间较广,是一个较为优质的视频。

      另外,加上我去把每期的标题和弹幕数一对应,发现,涉及到 国与国之间的时评 的时候,在播放量相差不多的情况下,视频的受众时间会比较广,相对的,谈论的时事范围或格局越小,受众时间会相对应减小。当然这些都是通过一小部分数据得出的结论,不能代表完全的趋势就是这样。

      2.2 统计发弹幕总数TOP10 的用户 横向柱状图
      TOP10

      一共15期的节目,根据不完全统计,这位ID为 13631380 的用户一共发了160+条弹幕,平均每一期要发11条弹幕,是真爱粉无误了。

      2.3 统计每期弹幕密度变化图 (图太多,只放了总体的缩略图)
      密度变化缩略图

      首先从个体上观察这些弹幕密度图,发现弹幕都是较为均匀的,但从总体上观察这些弹幕密度图的时候,发现其中112-118期的弹幕密度图极其相似119-124期的弹幕密度图也是极其相似,总的来说,112-124期都会有一个密度大且会持续一小段时间的“弹幕风云”,更巧的是,这部分都集中在视频时间700秒左右,也就是11分-13分之间会有一波弹幕的小高潮。

      为此我特意去再次观看**《睡前消息》,发现在112-118期中,节目大概都在15~20分钟左右,而在11-13分钟的时候,也就是在视频时间过去大概70%**左右的时候,主持人都会作出一些毒辣的点评,从而引起弹幕的激烈讨论。

      2.4 绘制出每期视频的弹幕词云 (取其中几个词云图展示)

      由于我设置了最小字体为10,所以如果是弹幕种类分布得比较广泛的视频,留白处会比较多,这里我选取了较为饱满的弹幕词云图作为展示

      【睡前消息111】深圳房价这样涨下去,会变成第二个香港么?

      111期词云

      【睡前消息112】阅文风波的背后,腾讯真的大到只手遮天了吗?

      112期词云

      【睡前消息118】《后浪》过后,如何看待B站又要《入海》?

      118期词云

      可见弹幕里面的 同意 支持 保护 之类的字眼比较多,**仅观表象的话 **可以说明,虽然该节目主持人虽嘴巴毒辣,在视频弹幕里会造成不少的人激烈讨论,但还是有大部分人同意这些观点的。

    项目代码

    • 项目目录结构

      在这里插入图片描述

    • GetUrl.py

      from bs4 import BeautifulSoup
      from selenium import webdriver
      
      url = 'https://space.bilibili.com/54992199/channel/detail?cid=82529'
      
      print("开始爬取《睡前消息》第110-124期视频地址")
      
      # chrome驱动,需要放在Python安装的目录下
      driver = webdriver.Chrome(r"E:\Python\chromedriver.exe")
      
      driver.get(url)
      data = driver.page_source
      soup = BeautifulSoup(data, 'lxml')
      
      count=1
      res = []
      all = soup.find_all('li', attrs={'class': 'small-item fakeDanmu-item'})
      for li in all:
          if count<=15:
              a=li.find('a',attrs={'class':'cover cover-normal'})
              res.append('https:'+a.get("href"))
              count+=1
          else:
              break
      
      with open('Urls/Link.txt', 'w') as f:
          for link in res:
              f.write(link+'\n')
      
      print("已将全部链接放入到Link.txt文件中")
      
    • GetCid.py

      from bs4 import BeautifulSoup
      from selenium import webdriver
      import re
      import time
      
      cids=[]
      Urls=[]
      with open('Urls/Link.txt', 'r') as f:
          for line in f.readlines():
              Urls.append(line.strip())
      
      print("开始爬取《睡前消息》第110-124期视频的Cid")
      
      # chrome驱动,需要放在Python安装的目录下
      driver = webdriver.Chrome(r"E:\Python\chromedriver.exe")
      
      for url in Urls:
          driver.get(url)
          data = driver.page_source
          soup = BeautifulSoup(data, 'lxml')
      
          all = soup.find_all('script')
          for a in all:
              if str(a).startswith("<script>window."):
                 res=a
      
          links=re.split(r'\:',str(res))
          for url in links:
              # 这个链接前面是域名,中国的都是以cn开头
              if url.startswith("//cn"):
                  link=url
                  break
      
          cid=re.findall(".*/(.*)-1-.*", link)
      
          # 获取到视频的cid,存进数组然后一起存进Cid.txt文件中
          cid=cid[0]
      
          # 处理特殊情况下,长度不符合cid,去除尾部部分
          if len(cid)>9:
              length=len(cid)
              a=length-9
              cid=cid[:-a]
      
          cids.append(cid)
      
          # 每抓完一个网页休眠5秒
          time.sleep(5)
      
      with open('Urls/Cid.txt', 'w') as f:
          for id in cids:
              f.write(id + '\n')
      
      print("已将全部视频的Cid放入到Cid.txt文件中")
      
    • GetBulletChat.py

      from bs4 import BeautifulSoup
      import time
      import pandas as pd
      import requests
      import datetime
      
      headers={
          "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE",
          "Connection": "keep-alive",
          # 这个cookie的获取方法在文档中已说明
          "Cookie":""
      }
      
      sets=124  # 最新一期的数字
      
      dates=[]  # 日期数组,用于填充url
      # 遍历日期  包括begin和end的日期  生成类似2020-05-03的格式的日期
      begin = datetime.date(2020,5,3)
      end = datetime.date(2020,6,9)
      d = begin
      delta = datetime.timedelta(days=1)
      while d <= end:
          dates.append(str(d.strftime("%Y-%m-%d")))
          d += delta
      
      
      Cids=[]  # Cid数组,用于填充url
      with open('Urls/Cid.txt', 'r') as f:
          for line in f.readlines():
              Cids.append(line.strip())
      
      for cid in Cids:
          # 每次都要重置这些数据
          dm_data = []  # 弹幕数据
          dm_text = []  # 弹幕本体
          # 弹幕的八个参数和弹幕本体
          DM_time = []
          DM_mode = []
          DM_font = []
          DM_color = []
          DM_realTime = []
          DM_pool = []
          DM_userID = []
          DM_id = []
          DM_text = []
          print("正在爬取第" + str(sets) + "期的《睡前消息》弹幕...")
          for date in dates:
              url="https://api.bilibili.com/x/v2/dm/history?type=1&oid="+cid+"&date="+date
      
              html=requests.get(url=url,headers=headers) #返回文本信息
              html.encoding='utf8'
              soup=BeautifulSoup(html.text,'lxml') #建立soup对象
      
              all=soup.find_all("d")
              for d in all:
                  # 弹幕数据
                  dm_data.append(str(d.get("p")).split(","))
                  # 弹幕本体
                  dm_text.append(d.get_text())
      
          # 分别把数据存进这几个数组
          for i in dm_data:
              DM_time.append(i[0])
              DM_mode.append(i[1])
              DM_font.append(i[2])
              DM_color.append(i[3])
              DM_realTime.append(i[4])
              DM_pool.append(i[5])
              DM_userID.append(i[6])
              DM_id.append(i[7])
          for i in dm_text:
              DM_text.append(i)
      
          dt={"DM_time":DM_time,"DM_mode":DM_mode,"DM_font":DM_font,"DM_color":DM_color,
              "DM_realTime":DM_realTime,"DM_pool":DM_pool,"DM_userID":DM_userID,"DM_id":DM_id,"DM_text":DM_text}
      
          d=pd.DataFrame(dt)
      
          d.to_csv('./Danmu/Danmu-'+str(sets)+'.csv',encoding='utf-8-sig') #存储弹幕信息
          print("已将弹幕放入到Danmu-"+str(sets)+".csv文件中")
          sets-=1
      
          # 每抓完一个网页休眠7秒
          print("缓冲中...")
          time.sleep(7)
      
      print("已将《睡前消息》第110-124期的弹幕爬取完毕")
      
    • DataAnalysis.py

      import matplotlib.pyplot as plt
      import matplotlib
      import pandas as pd
      import os
      from wordcloud import WordCloud
      import jieba
      
      file_dir="./Danmu/"
      # 获取文件名
      files=[files for root,dirs,files in os.walk(file_dir)]
      
      # 去重
      def duplicate(files):
          for file in files:
              df = pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0)
              data = df.drop_duplicates(subset=['DM_id'], keep='first')
              data.to_csv(file_dir + file,encoding='utf-8-sig',index=True,index_label="")
          print("去重完毕")
      
      # 每一期弹幕总数的变化折线图
      def danmuSumPlot(files):
          print("弹幕总数变化图绘制中...")
          list1 = ['110','111','112','113','114','115','116','117','118','119','120','121','122','123','124']
          data_sum=[]
          for file in files:
              data = pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0)
              data_sum.append(len(data))
      
          matplotlib.rcParams["font.family"] = "SimHei"
          plt.plot(list1, data_sum, "c")
          plt.ylabel("弹幕数")
          plt.xlabel("《睡前消息》期数")
          plt.title("每一期弹幕总数的变化图")
          plt.savefig('./Analysis/弹幕总数变化图', dpi=600)
          plt.show()
          print("绘制完毕")
      
      # 发弹幕总数TOP10的用户柱状图
      def danmuUserTopBarh(files):
          print("弹幕TOP10用户图绘制中...")
          datas=[]
          for file in files:
              datas.append(pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0))
      
          # 先合并全部csv文件,再进行统计
          data=pd.concat(datas)
          data = data.groupby('DM_userID').size().reset_index(name="count")
          data = data.sort_values("count", ascending=False)
      
          label = []  # y轴的值
          width = []  # 给出具体每个直方图的数值
          i = 0
      
          for item in data.values:
              if i < 10:
                  label.append(item[0])
                  width.append(item[1])
                  i += 1
              else:
                  break
      
          matplotlib.rcParams["font.family"] = "SimHei"
          y = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]  # 给出在y轴上的位置
          plt.barh(y=y, width=width, tick_label=label)  # 绘制水平直方图
          plt.ylabel("用户ID")
          plt.xlabel("弹幕数")
          plt.title("发弹幕总数TOP10的用户柱状图")
          plt.subplots_adjust(left=0.17)  # 控制图片左边的间隔  避免显示不全
          plt.savefig('./Analysis/TOP10', dpi=600, left=0.17)
          print("绘制完毕")
      
      # 每期弹幕密度变化图
      def danmuDensityChange(files):
          print("弹幕密度变化图绘制中...")
          sets=110
          for file in files:
              data = pd.read_csv(file_dir + file, encoding="utf-8-sig", index_col=0)
              data = data.sort_values("DM_time")
      
              # 先对弹幕发送时间进行取整
              data['DM_time'] = [int(item) for item in data.DM_time]
              data = data.groupby('DM_time').size().reset_index(name="counted")
      
              list2 = [item for item in data.DM_time]
              data_sum = [item for item in data.counted]
              matplotlib.rcParams["font.family"] = "SimHei"
              plt.plot(list2, data_sum, "c")
              plt.ylabel("弹幕数量")
              plt.xlabel("视频时间轴/(秒)")
              plt.title(str(sets)+"期弹幕密度变化图")
              plt.savefig("./Analysis/弹幕密度变化/"+str(sets)+'期弹幕密度变化图', dpi=600)
              sets+=1
          print("绘制完毕")
      
      # 每期的弹幕词云
      def danmuWordCloud(files):
          print("弹幕词云绘制中...")
          sets = 110
          for file in files:
              data = pd.read_csv(file_dir + file, encoding="utf-8-sig", index_col=0)
              # 先把全部弹幕信息写成一个字符串,再调用方法
              words = ''
              for item in data.DM_text:
                  words += item
      
              words=" ".join(jieba.cut(words))
              # 这个scale参数是画布大小参数,也就是调整分辨率的,10代表是原来的10倍大小,越高分辨率越高
              wd = WordCloud(font_path='simhei.ttf', max_words=40, background_color='white',min_font_size=10,scale=10).generate(words)
              plt.imshow(wd)
              plt.axis("off")
              wd.to_file("./Analysis/词云/第" + str(sets) + "期词云.jpg")
              sets+=1
          print("绘制完毕")
      
      
      if __name__ == '__main__':
          # 去重
          duplicate(files[0])
      
          # 每一期弹幕总数的变化折线图
          danmuSumPlot(files[0])
      
          # 发弹幕总数TOP10的用户柱状图
          danmuUserTopBarh(files[0])
      
          # 每期弹幕密度变化图
          danmuDensityChange(files[0])
      
          # 每期的弹幕词云
          danmuWordCloud(files[0])
      

本文地址:https://blog.csdn.net/lkx_icy/article/details/107178025

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网