鱼C论坛

 找回密码
 立即注册
查看: 4262|回复: 21

[技术交流] 新手学习+练习-----爬取B站电视剧《风犬少年的天空》弹幕并分析

[复制链接]
发表于 2020-11-23 14:11:40 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 skyrimz 于 2020-11-23 14:32 编辑

这篇文章是我先发在csdn博客上的,不知道论坛可不可以发外站连接,就直接搬过来了。希望各位高手能多多指导



写在前面
在b站跟着小甲鱼学习Pyhton一个半月了,基本知识部分已经学习完毕,想着做点什么练习一下所学的知识。正好碰上一部很喜欢的电视剧,于是打算就着这部喜欢的剧练习一下学习的知识。

想到哪写到哪,慢慢更新慢慢完善,一步一步记录自己学习的过程~

如果有一起学习的新手,大家一起进步,共勉!

在敲代码以及数据分析方面,作者完全是业余爱好,大家笑一笑就好哈。

PS:感谢CSDN,学习过程中95%的问题都在各位大佬的博客中得到了解决


开始!

简单分析一下弹幕数据

《风犬少年的天空》,2020年9月24日上线第一集,10月22日迎来大结局(大会员),非会员则在11月5日能够观看大结局。同时播放完之后似乎有一段时间的限免,可以随意观看,且会员比非会员每一集早一周能够观看。

根据B站官方数据,截至2020-11-16日,总共弹幕量达到399.2万,其中是否包括花絮,周边,MV等暂时未知。

根据B站每一集显示的弹幕量,计算出的总弹幕量为:

11615+15966+11981+11971+11974+12000+12000+11993+11994+11973+11946+15926+11955+11975+11979+15944=191192

(更新:这个数据没有价值,不值得参考)

也就是大概20万条弹幕,为什么只有官方的将近400万条弹幕的5%呢?由于本人对这方面的知识一窍不通,只能在此做出几点猜测,希望有了解的大神指点一二:

分集弹幕已经经过b站本身某种算法的去重,也许剔除了大量的重复,无意义弹幕

也许有些弹幕违规被清理了?但不应该有如此庞大的数量

弹幕量过于巨大,我所用的b站接口并没有全部收录(经过实验,该接口对于弹幕量较少的视频是可以精准爬取每一条弹幕的)

似乎分集弹幕有一定的上限,普通视频1000,电视剧6000(存疑,因为有几集达到了12000,甚至16000的,怀疑这两个也是一种上限)

(更新:找到了b站官方对于弹幕数量问题的解答:确实会按照时间替换早期弹幕,也就是说我爬到的27万条弹幕大概率就是能拿到的所有弹幕了,全部弹幕估计只能b站内部才能看到了。)

而根据我所写的代码,经过去重后,最终得到的弹幕数量为:273778 。似乎比分集弹幕的总和多了8万条,但还远远达不到400万条。(好像相对于400万来说,这点误差也不大?hhh )

能力有限,暂时能拿到的数据只有这些,就当是一切正常,用这个数据样本进行接下来的分析吧。



蠢并痛苦着的学习过程。。。

作为一名机械狗,学习编程完全是导师建议+自己有那么一点兴趣,最后学的乱七八糟,所以后面所有写的代码看起来都可能蠢的不行+bug频出,希望各路大神不吝赐教(希望我能看懂吧)。。。

起初,是打算爬了弹幕然后做个词云图,分析下词频拉倒,没想到这竟然是折磨的开始…

且不说用pycharm安装各种库时候的一次次的失败(大多数情况下pycharm本身安装还是很方便的,除了个别的库对py版本,网络有要求),期间尝试过很多办法,手动pip安装等,好在最后该用的模块和运行库都装上了,一切正常。

除此之外,零基础的我还恶补了好多知识,包括但不限于BeautifulSoup的用法(处理xml格式的数据很好用),正则表达式(至今仍然一头雾水),浏览器审查元素的用法(好多功能在爬弹幕的时候经常用),还有Scrapy(甚至都没用上)。

需要学习的内容:html知识,pandas,numpy,nlp。。。


干(烂)货环节-------弹幕的获取与整理

最最最开始,只会用urllib.request这个功能,配合网上查到的一个弹幕接口:http://comment.bilibili.com/cid.xml,写了个简单的代码,通过urllib.request+BeautifulSoup确实能够实现弹幕的爬取。


                               
登录/注册后可看大图


进行到这里,发现了几个问题:

cid的获取,相较于BV,AV号,这个值并不能直接获得,但却与这两个编号存在联系。

可以看到,其中maxlimit为500,即最大弹幕量,也就是说通过这种方式获得的弹幕在弹幕数量很大时是不完整的。因此如果想要获取大量弹幕,我们需要更换一个接口。


关于cid的获取

根据我的理解,cid与av,bv号一样,都是每一个视频独一无二的“代号”(分p,电视剧分集也都是唯一的),只不过这个代号似乎并不对普通用户开放,无法直接获得。于是我进行了几次尝试:

首先通过浏览器的“审查元素”功能(通常来说按F12或者右键空白处)中的Elements项查找cid,不知道是不是b站的代码改变了,没能在Elements下找到cid相关值。



                               
登录/注册后可看大图


转到Network项,搜索cid,可以看到在General下的Request URL中有一条包括cid的链接,cid=后面的那串数字就是我们要找的cid值。把这个值带入http://comment.bilibili.com/cid.xml中替换cid,即可得到存储弹幕的xml文件。

到这里,如果是简单爬取少量弹幕,就大功告成了!(代码很简单就不献丑了,相信大家都比我聪明,随便写写就出来了~)



                               
登录/注册后可看大图


上面说过,AV,BV号均与cid有关,且能够相互转化。利用审查元素手动获取cid的方式过于繁琐,后面会用到一种较为简单且可以通过Python自动获取cid的方法。

关于弹幕上限

对于发布时间长,弹幕数量多的视频来说,第一种方法将不再适用。此时考虑通过历史弹幕来获取尽可能多的弹幕(甚至全部弹幕)。

查询历史弹幕的接口为:https://api.bilibili.com/x/v2/dm ... amp;date=2020-10-07

其中:oid即为之前提过的cid,date则是所要获取的历史弹幕的日期。通过修改这两处参数,即可获取不同视频,不同时间的历史弹幕。

然而直接使用该链接,得到的结果是这样的:


                               
登录/注册后可看大图


可以发现,该链接要求用户保持登陆状态才可以查看,也就是说我们要模拟已经登陆b站的情况下再进行查看。此时我们可以在请求中加入headers,填上cookie一起发送,从而使服务器认为我们是在登陆情况下进行的访问。

cookie的获取(很多办法,简单提一种):到Network项下面的Request Headers中查找。

headers_needed = {"cookie": "xxxxxxx", "user-agent": "xxxxxxx"}
req = requests.get(url, headers=headers_needed)

通过requests库的get方法访问,配合BS即可爬取历史弹幕。


                               
登录/注册后可看大图


新的风暴已经出现。。。

虽然进行到这步之后,之前的cid和弹幕上限问题已经得到了解决。但是! 《风犬》作为一部播出将近1个月,片长16集的电视剧,这么一集一集一天一天扒弹幕怕不是要累死呀!于是乎打算搞一个批量获取某一集历史弹幕的功能~(精力有限,以后说不定会拓展到全集)

简单的思路

在探索(受苦)的过程中,我发现了这么一个链接:

https://api.bilibili.com/x/v2/dm ... 5&month=2020-10

打开之后是这样的:


                               
登录/注册后可看大图


data所对应的list中,保存了当月内每一天(如果当天有弹幕)的日期,修改oid和month这两个参数,即可得到任意视频任意月份内的历史弹幕日期。再通过上面爬取单日弹幕的代码,即可实现批量爬取。

(其实自己写一个list也可以,不过考虑到每一集发布时间不同,总体跨度大,用现成的更好一点,省事)

批量处理中的cid获取方法

之前提到过,手动获取cid实在是不怎么效率。如今要通过程序批量爬取弹幕,就更不可能每一集都手动去获取一遍。于是乎找了一个解决办法:(链接均来自于互联网各位大佬分享)

https://api.bilibili.com/x/player/pagelist?bvid=

在bvid后填入视频BV号,即可得到包含cid在内的一组数据

https://www.bilibili.com/widget/getPageList?aid=

同理,aid后填入视频AV号(基本没有用到av的时候了吧,以防万一还是带上吧)

“ tips:和 Elements 与 Network 相同,有一个 Console 项,输入 bvid 或 aid 即可获取视频BV/AV号,适用于无法直接从视频链接获取BV/AV号的情况。”

之后写段代码处理下数据就能得到cid啦~(BS和正则表达式就学了一点点皮毛中的皮毛,想了半天也没写出来处理办法,只能用最笨的办法实现了,见笑)

  1. def bv2cid():
  2.     id_bv = input('输入BV号:')
  3.     convert_url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + id_bv
  4.     response = urllib.request.urlopen(convert_url)
  5.     response_readable = response.read().decode('utf-8')
  6.     begin = response_readable.find('"cid"') + 6
  7.     over = response_readable.find('"page"') - 1
  8.     cid = response_readable[begin:over]
  9.     return cid,id_bv
复制代码


aid2cid中用到了BeautifulSoup 记得import

  1. def aid2cid():
  2.     id_aid = input('输入aid号:')
  3.     convert_url = 'https://www.bilibili.com/widget/getPageList?aid=' + id_aid
  4.     req = requests.get(convert_url)
  5.     req.encoding = 'utf-8'
  6.     soup = BeautifulSoup(req.text, 'lxml')
  7.     soup_find = soup.find(string=re.compile("cid"))
  8.     soup_find_str = str(soup_find)
  9.     begin = soup_find_str.find('"cid"') + 6
  10.     over = soup_find_str.find('}]')
  11.     cid = soup_find_str[begin:over]
  12.     return cid,id_aid
复制代码


通过写的批量爬取程序,可以达到如下效果:


                               
登录/注册后可看大图



                               
登录/注册后可看大图


弹幕处理与保存

如果只需要制作词云的话,提取弹幕中的文字部分即可,不过我对前面的一组数据也很好奇。根据网上的分析,几组数据分别代表:

’弹幕出现时间’, ‘弹幕模式’, ‘字号’, ‘字体颜色’, ‘实际发布时间’, ‘弹幕池’, ‘用户ID’, 'rowID’

我们逐一处理其中的每一项:

用户ID这一项经过加密计算,上网查过之后发现反推是可以的,但似乎属于暴力破解?暂时不考虑转换为真实ID,不会对统计造成影响。

(2020.11.17更新:属于crc32b算法,纠结了半天最终在网上找到了反求的python程序,感谢GitHub上的Aruelius.L大佬的程序,非常好用!代码来源:https://github.com/Aruelius/crc32-crack


弹幕出现时间以及实际发布时间均使用秒计数(如:2583.65000),实际发布时间使用UNIX时间戳的形式(如:1601559058),这个数据以后分析会用到。不过暂时为了好看,我们把它转换成更符合阅读习惯的形式,即:年月日时分秒的形式。这里很简单,使用datetime.timedelta以及datetime.timedelta.fromtimestamp函数转换即可。

根据观察,个人猜测 rowID 这一项为每一条弹幕的“代号”,且唯一。(我的去重程序就是根据这个猜测写的,坑爹的是不一定对,网上找了找没找到相关信息,只能硬着头皮上了,姑且算是正确猜测吧。)

暂时处理这几项***************************************************************************************

到这一步基本的处理就完成了,现在考虑数据的存储方式。 由于经常要用到pandas,遂决定以CSV(Comma-Separated Values)格式(跟excel差不多哈)保存数据。

在Python中,可以通过list生成csv文件,简单方便。

对于xml文件中的每一条数据,即(<d p="207.87000,1,25,16777215,1601558965,0,ed7bd0bf,40628661897920515">哈哈哈哈</d>),分两大部分处理,前几项数据与 ‘弹幕出现时间’, ‘弹幕模式’, ‘字号’, ‘字体颜色’, ‘实际发布时间’, ‘弹幕池’, ‘用户ID’, ‘rowID’ 组合为一个dict,实际弹幕内容与 弹幕

内容 组合为一个dict,最后把两个dict拼接成一个大的dict(利用dict(dict1,**dict2))。

  1. # 数据部分
  2. comments_data = str_each.split('p="')[1].split('">')[0].split(',')
  3. # 弹幕内容
  4. comments_text = str_each.split('p="')[1].split('">')[1].split('</d>')
复制代码


非常无脑的分法,为了分出一一对应的格式。。。

接下来把每一条数据对应的dict存入一个list,用这个list生成csv文件。

关于csv文件,有两点需要注意:

rowID 这一项的内容,数值长度过长,在生成csv文件的时候系统会自作聪明的转化为科学计数法(坑爹啊!),解决办法是:数据末尾+\t即可,不让系统判定为数据而判定为“字符串”就好了。

生成的csv文件可能为乱码。我的环境为win10,经测试会出现此问题。这个问题涉及到编码,即使用gbk或utf-8都无法正常生成csv文件,打开会出现乱码。问题出现的原因是由于写入csv文件时,文件头部会出现一个BOM(Byte Order Mark)用来声明文件编码信息,然而却没有被正常识别,导致编码错乱。解决办法也很简单:使用 utf-8-sig 格式保存即可,能够正确识别且无乱码。

csv文件合并与去重

通过 csv , pandas, glob 等模块即可完成。

合并csv文件虽然很简单,过程却苦不堪言。。。自己写+网上找了好多代码,不是合并之后数据错位就是索引出问题,困扰了好久,最终通过一段代码搞定了csv文件合并以及索引问题。

关于去重,使用了pandas中的.drop_duplicates()函数,去重依据就是上文提到的rowID,即通过去除所有重复rowID来达到剔除重复弹幕的目的(并不代表重复内容一并去除)。

爬取的历史弹幕,每天之间并不会有太大出入,也就是说可能两天之间的弹幕只有几十条不同,剩下几千条都是一模一样的,这些都需要合并后统一去除掉。

贴一段去重+合并文件的代码:


  1. import pandas as pd
  2. import glob
  3. import csv


  4. def csv_drop_duplicates():
  5.     combine_name = input('输入合并后文件名:')
  6.     drop_duplicates_name = input('输入去重后文件名:')
  7.     csv_list = glob.glob('*.csv')
  8.     for i in csv_list:
  9.         fr = open(i, 'r', encoding='utf-8-sig').read()
  10.         with open(combine_name, 'a', encoding='utf-8-sig') as f:
  11.             f.write(fr)
  12.     print('合并完毕!')

  13.     temp_list = []
  14.     with open(combine_name, 'r', encoding='utf-8-sig') as f1:
  15.         reader = csv.reader(f1)
  16.         for row in reader:
  17.             temp_list.append(row)
  18.     df = pd.DataFrame(temp_list)
  19.     df.drop_duplicates(subset=7, inplace=True, keep='first')
  20.     df.to_csv(drop_duplicates_name, encoding='utf-8-sig')
  21.     print('去重完毕!')

  22. if __name__ == '__main__':
  23.    csv_drop_duplicates()
复制代码


通过更改drop_duplicates()中的subset参数,即可对用户ID,弹幕内容等每一项进行去重。如果对用户ID项去重,得到的就是每集发布弹幕的用户数。

以第五集为例:

爬取9月25到11月15共52天的弹幕,每天的csv大小约为612KB,弹幕条数6000条左右。

经过合并后的csv大小约为30.3MB,弹幕条数310711条(其中绝大多数都是重复了N遍的无用数据,这个csv文件主要是用来去重,没有参考意义。)

经过去重之后的csv大小约为2.18MB,弹幕条数20937条(与官方数目有出入,不过我检查过确实没有重复弹幕,弹幕数量姑且算是合理吧。)

简单分析

弹幕不同于文章,有一定的特殊性。弹幕长度一般不会太长,大多数以一句话为基本单位,如果再进行分词,就破坏了“弹幕”这种形式所传递的信息,所以不再进行分词。下面进行简单的分析:

思路及代码实现

每集弹幕的数量我们已经得到(去重后),现在来获取每集出现最多的弹幕,统计弹幕内容列中的每一个值并进行计数和排序就是此处我们的需求。

需要注意的是:尽管已经进行初步去重,但仍有很大一部分弹幕,比如:来了,卧槽,666,哈哈哈等毫无意义的重复弹幕,这里也要一并去除。

此处使用value_counts()函数来进行统计,它是pandas中一个较为常用的函数。具体代码如下,思路也都写在了里面。

  1. import os
  2. import pandas as pd
  3. import csv

  4. #准备工作路径和文件
  5. file_list = []
  6. work_path = os.getcwd()
  7. path = r'D:\bilibili' # 待处理文件目录
  8. files = os.listdir(path)
  9. for file in files:
  10.     if '.csv' in file:
  11.         file_list.append(file)


  12. # 统计高频弹幕,并简单清洗 (如果统计其他项,不需要去重可直接删掉这一块)
  13. drop_words = ['哈哈','来了','来啦','啊啊','hhh'] # 过滤词

  14. def freq_auto():
  15.     for file_name in file_list:
  16.         init_name = file_name
  17.         init_data = pd.read_csv(os.path.join(path,init_name))
  18.         data_dm = init_data['弹幕内容'] # 需要统计的表头,按需更换
  19.         data_dm_list = data_dm.tolist()

  20.         for i in tuple(data_dm_list): # 去掉无意义弹幕
  21.             if len(i) == 1: # 单字
  22.                 data_dm_list.remove(i)
  23.                 continue
  24.             elif i.isdigit() is True: # 纯数字
  25.                 data_dm_list.remove(i)
  26.                 continue
  27.             elif i.encode('utf-8').isalpha() is True: # 纯字母(汉字Unicode识别为字母,需要转换)
  28.                 data_dm_list.remove(i)
  29.                 continue
  30.             else:
  31.                 for j in drop_words:
  32.                     if j in i:
  33.                        data_dm_list.remove(i)
  34.                        break
  35.                 else:
  36.                     pass
  37.         frame = pd.DataFrame(data_dm_list, columns=['弹幕内容'])
  38.         drop_name = '(已清洗)' + file_name
  39.         frame.to_csv(os.path.join(work_path,drop_name), encoding='utf-8-sig')
  40.         print('【%s】清洗完毕!'%file_name)

  41.         # 统计清洗后弹幕频率(注意,转成的DataFrame索引是需要被统计的词,列是词出现的次数)
  42.         data = pd.read_csv(os.path.join(work_path,drop_name))
  43.         data_counts = data['弹幕内容'].value_counts()  # 通过value_counts计算词频
  44.         df_data_counts = pd.DataFrame(data_counts)  # 将词频结果转成DataFrame格式。

  45.         comment_header = ['弹幕内容']
  46.         count_header = ['出现次数']

  47.         comment = df_data_counts.index.values.tolist()  # 把词转成列表
  48.         count = df_data_counts['弹幕内容'].tolist()  # 把词出现的次数转成列表
  49.         dictlist = []

  50.         for k, v in zip(comment, count): # 准备好字典,写入csv
  51.             k_str = k.split('为了换成列表') # str转换为list,下同
  52.             v_str = str(v).split('为了换成列表')
  53.             comment_dict = dict(zip(comment_header, k_str))
  54.             count_dict = dict(zip(count_header, v_str))
  55.             full_dict = dict(comment_dict, **count_dict)
  56.             dictlist.append(full_dict)

  57.         freq_name = '(已统计)' + file_name
  58.         csv_header = ['弹幕内容', '出现次数']
  59.         with open(os.path.join(work_path,freq_name), 'w', newline='', encoding='utf-8-sig') as f:
  60.             f_csv = csv.DictWriter(f, csv_header)
  61.             f_csv.writeheader()
  62.             f_csv.writerows(dictlist)
  63.         os.remove(os.path.join(work_path,drop_name)) # 删掉无用文件
  64.         print('【%s】处理完毕!'%freq_name)
  65.     print('全部处理完成!')

  66. if __name__ == '__main__':
  67.     freq_auto()
复制代码


处理后内容如下(以第十集为例):


                               
登录/注册后可看大图


可以看出,尽管需求已经实现,但仍有无统计价值的弹幕出现,而且数量上无法忽略。比如“爷青结”,“泪目”这种,每集的文件处理后都存在这种现象,由于各集内容都不同,暂时只能手动去除这部分数据。

上面的代码也可以用来统计单一用户每集发布的弹幕数量,原理是一样的,具体代码大同小异。数据如下:


                               
登录/注册后可看大图


这里的用户ID经过CRC32处理过,对制作图表没有影响,后面再进行还原。

数据可视化及分析

拿到数据之后,就可以着手进行可视化的工作了。这部分要用到matplotlib.pyplot模块,功能十分强大,进行简单的图表制作还是不在话下~

matplotlib.pyplot模块绘制饼图,柱状图,折线图等都很方便,代码也很简单。相关的知识还没有深入学习,用到的代码都是很基础的,就不放上来了。

根据得到的弹幕数量和观众数量,可以得出每一集的弹幕/观众数量变化趋势:



                               
登录/注册后可看大图


简单分析如下:

大多数情况下弹幕数量和观众数保持同样的变化趋势,而1-5集的变化趋势却毫无章法。做点推测:剧播完后,1-3集为免费观看,4集之后为会员内容,因此第4集观众数有明显下跌。同时,第4集用户平均弹幕发送量远高于第3集,是否说明会员用户比起非会员用户更喜欢发布弹幕?

第七集和十三集在剧情上均有重大转折。刘闻钦下线,马田被陷害出走等情节都有剧烈的感情变化,激起了观众的讨论。因此这两集无论是弹幕数量还是发布弹幕的观众数都有所增加。需要注意的是:第七集弹幕数如此之多,是因为存在大量的“一生所爱xxx”的表白弹幕,属于与剧情无关的刷屏行为。(按照b站的弹幕获取限制,不知道为什么能得到8W条这个夸张的数字,检查几遍之后也没什么头绪。)

每一集的弹幕情况如下(以第八集为例):



                               
登录/注册后可看大图


分析如下:

绝大多数观众还是很少发弹幕的,属于安静看剧党。

也有狂热粉丝,一集发布了34条弹幕,表达欲望很强烈~

在其他集数,甚至有的用户不足一分钟便会发布一条弹幕,真爱粉无疑了。

风犬这部剧,为我们塑造了7个个性鲜明的少年。那么观众对于每一位角色的关注度又是怎么样的呢?通过弹幕,我们可以略知一二。

对角色相关弹幕进行过滤和统计,最终结果为:



                               
登录/注册后可看大图



                               
登录/注册后可看大图


分析环节:

狗哥作为男主角,关注度讨论度自然最高,远远甩开其他人,独占第一档。不得不说彭昱畅演绎的老狗很棒!

娇姐通过讨喜的人设以及演员精湛的表演,获得了第二名的成绩,不愧是大力娇!甜椒组合紧随狗哥其后~

作为女主,安然的戏份跟弹幕一样的少。这个角色甚至可以说是全剧第一大工具人,哪里剧情需要哪里搬,就是没有属于自己的剧情。当然演员还是很漂亮的~

刘闻钦=嘴哥+咪哥。白月光+意难平的buff属实强力。

上面提到过,有的观众甚至不到一分钟就会发布一条弹幕。那么,纵观全集,谁又是行走的弹幕发射机呢?

之前我们已经实现了高频弹幕的统计,同样的原理,这里依旧使用value_counts()函数来统计观众发送的弹幕量。

上面提到过,用户ID并不是真正的uid,还需要进行还原。这里使用GitHub上的Aruelius.L大佬的程序来进行还原,d1aed527为需要还原的内容,364436978就是真实的用户uid。



                               
登录/注册后可看大图


结果如下:


                               
登录/注册后可看大图


分析:

“翻斗fa园扛把子”同学凭借230条的弹幕量稳坐第一把交椅,不愧是扛把子呀~该大佬每集平均输出14条弹幕,按照风犬每集60分钟计算,dalao每四分半就会发射一条弹幕。这波啊,这波是经典双线程操作,看剧弹幕两不误。

接下来,我们看看这位疯狂发射弹幕的大佬都发了些啥。去掉无意义弹幕和重复弹幕之后,大佬的弹幕内容如下:


                               
登录/注册后可看大图


可以看出,还是有很多剧情相关的弹幕。大佬不愧是大佬,质量和数量,两手抓两手都要硬啊~而且似乎还是彭昱畅的粉丝?



目前就进行到这里,更多内容还在学习中。。。

评分

参与人数 1荣誉 +5 鱼币 +5 贡献 +3 收起 理由
昨非 + 5 + 5 + 3 建议楼主申请精化

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-11-26 14:33:35 | 显示全部楼层
更新:最后一部分情感分析

最后一个部分,轮到弹幕的情感分析。最开始了解到这方面还是csdn上某个大佬的一篇分析提到的,既然数据都拿到了,索性也来试一试。

首先,情感倾向分析这个东西我个人是无法完成的,毕竟这是一门完全陌生的学科,而且数据量很大,逐个分析不现实。(情感倾向分析属于NLP (Natural Language Processing)即自然语言处理下的一个分支,而NLP也是AI的一个子领域)

此时可以借助AI来进行分析,本次测试使用的是某度的AI平台,其中就有情感倾向分析所需要的功能。(主要是注册有赠送使用次数)。

具体过程略去不提,网上教程一抓一大把。应用创建好之后如图:



                               
登录/注册后可看大图


可以看出我已经处理了24242条弹幕数据。相比将近27万条的总数据,只处理这一小部分的原因有几点:

并不是所有弹幕都与角色有关,精准去除所有与角色无关的弹幕对于我个人来说是不可能的。只能选取所有提及角色名字的弹幕进行分析。当然,这种办法也不准确,这些弹幕可能是某些调皮的观众的角色扮演,还可能同一条弹幕提及多个角色,使主体不明确。不过这也是没有办法的办法了。

总共就免费50万次啊!试一次没了!!!

免费的产品QPS上限很低,全部处理需要的时间太长了,就这些还弄了几个小时。。。

下一步就是提取出与角色相关的弹幕了:


  1. import os
  2. import pandas as pd

  3. def char_danmu():
  4.     # 准备工作路径和文件
  5.     file_list = []
  6.     work_path = os.getcwd()
  7.     path = r'D:\bilibili\drop'  # 待处理文件目录
  8.     files = os.listdir(path)
  9.     for file in files:
  10.         if '.csv' in file:
  11.             file_list.append(file)

  12.     # 筛选角色相关弹幕
  13.     key_words1 = ['涂俊', '俊俊', '狗哥', '老狗']
  14.     key_words2 = ['安然']
  15.     key_words3 = ['娇娇', '大力娇', '娇姐']
  16.     key_words4 = ['马田', '马甜']
  17.     key_words5 = ['刘文钦', '刘闻钦', '钦哥']

  18.     def char_keywords():
  19.         for file_name in file_list:
  20.             res = []
  21.             init_name = file_name
  22.             init_data = pd.read_csv(os.path.join(path, init_name))
  23.             data_dm = init_data['弹幕内容']  # 需要统计的表头,按需更换
  24.             data_dm_list = data_dm.tolist()

  25.             for i in key_words5:
  26.                 for j in data_dm_list:
  27.                     if i in j:
  28.                         res.append(j)

  29.             res_frame = pd.DataFrame(res, columns=['弹幕内容'])
  30.             drop_name = '(keywords)' + file_name
  31.             res_frame.to_csv(os.path.join(work_path, drop_name), encoding='utf-8-sig')
  32.             print('【%s】关键词提取完毕!' % file_name)

  33.         print('全部处理完成!')

  34. if __name__ == '__main__':
  35.     char_danmu()
复制代码



得到内容如下:


                               
登录/注册后可看大图


这其中还含有大量的无分析价值弹幕,需要去掉。但时间精力有限,只能全部拿来分析了,这样得出的结果将会有很大的偏差。

接着,调用某度AI平台的API接口,进行情感倾向分析:


  1. import pandas as pd
  2. from aip import AipNlp
  3. import time
  4. import csv
  5. import os

  6. def class_sentiment():
  7.     #配置api接口
  8.     app_id = 'aid'
  9.     api_key = 'ak'
  10.     secret_key = 'sk'
  11.     client = AipNlp(app_id, api_key, secret_key)

  12.     # 准备工作路径和文件
  13.     file_list = []
  14.     work_path = os.getcwd()
  15.     path = r'D:\py+\Bilibili\Danmu\keywords\kw1'  # 待处理文件目录
  16.     files = os.listdir(path)
  17.     for file in files:
  18.         if '.csv' in file:
  19.             file_list.append(file)

  20.     for file_name in file_list:
  21.         data = pd.read_csv(os.path.join(path,file_name),)
  22.         data_text = data['弹幕内容'].tolist()
  23.         csv_header = ['内容', '积极概率', '消极概率', '置信度', '情感极性']
  24.         res_list = []  # api处理后内容,每条为一个dict
  25.         count = 0
  26.         csv_list = []  # writerows所用的list,包含每一个处理好的dict

  27.         for i in data_text:
  28.             try:
  29.                 sen_res = client.sentimentClassify(i)
  30.                 time.sleep(0.6)
  31.                 res_list.append(sen_res)
  32.                 count += 1
  33.                 print('第%d条【%s】处理完毕,延时0.6s!' % (count, i))
  34.             except:
  35.                 count -= 1
  36.                 continue
  37.         print('全部处理完成!')


  38.         for j in res_list:
  39.             new_list = [j['text'], j['items'][0]['positive_prob'], j['items'][0]['negative_prob'],
  40.                         j['items'][0]['confidence'],
  41.                         j['items'][0]['sentiment']]  # 提取每个参数为一个新list
  42.             new_dict = dict(zip(csv_header, new_list))  # 生成将要写入csv的dict
  43.             csv_list.append(new_dict)

  44.         save_name = '(sentiment)' + file_name
  45.         with open(save_name, 'w', encoding='utf-8-sig', newline='')as f:
  46.             f_csv = csv.DictWriter(f, csv_header)
  47.             f_csv.writeheader()
  48.             f_csv.writerows(csv_list)
  49.         print('已保存为【%s】' %save_name)
  50.     print('全部保存完毕!')


  51. if __name__ == '__main__':
  52.    class_sentiment()
复制代码


值得注意的是,一开始并不知道QPS是啥,写的代码访问次数过快导致后面的请求全都被服务器拒绝了,白白浪费好多次机会。QPS(Queries-per-second)即每秒请求次数,顾名思义,懂得都懂。后面加上了time.sleep()函数降低了请求速度,才能正常运行,这也使得数据的处理速度变慢了很多。当然肯定有其他的方法能够突破这个限制,关键我不会啊!
正常运行后,处理过程如图(pycharm中运行):



                               
登录/注册后可看大图



                               
登录/注册后可看大图


其实可以发现,这个AI还是挺不靠谱的,识别能力令人捉急!不过都免费了,还要啥自行车啊,用来学习是足够了。

上面表中的内容,每一项含义为(摘自官方说明文档):

情感极性:表示情感极性分类结果,0:负向,1:中性,2:正向

置信度:表示分类的置信度,取值范围[0,1],越大结果越可信

积极概率:表示属于积极类别的概率 ,取值范围[0,1] 越高几率越大

消极概率:表示属于消极类别的概率,取值范围[0,1] 越高几率越大

由于没有现成的模型可供参考,也没有相关方向建模的经验,只能使用如下模型计算感情倾向分值:

感情倾向分值 = 基础分值(每条弹幕为1) x 置信度 x 情感极性概率(积极为正,消极为负)

将每集中上面的分值求和取平均数,即为该集该角色观众所发弹幕的情感倾向分值。

需要注意的是,该分值代表的是观众此刻对于角色所表露出的感情,而不是角色此刻的感情。而且,由于分类只有正向和负向,没有更细的划分,负向不等于角色不好,正向也不等于角色好,他们表现的都是观众的惊喜,失落,难过等情绪。

最终得出的可视化结果为:



                               
登录/注册后可看大图



                               
登录/注册后可看大图



                               
登录/注册后可看大图



                               
登录/注册后可看大图


认真来说的话,这个变化趋势参考价值不高,每一集之间的变化不是很准确,不过总体来看还是有点可取之处,因为:

样本容量较少
无价值样本没有剔除
AI识别能力不足
建模不准确


完结!!!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-23 16:55:12 | 显示全部楼层
怎么申请精华?不是很懂。
接下来应该还有一部分情感分析的内容,正在学习中。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-11-23 18:35:02 | 显示全部楼层
没对比就没伤害,一个半月我还没学到函数呢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-23 20:22:55 | 显示全部楼层
jtxs0000 发表于 2020-11-23 18:35
没对比就没伤害,一个半月我还没学到函数呢

稳扎稳打
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-24 21:32:30 | 显示全部楼层
终于要全部完成了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-12-8 19:52:04 | 显示全部楼层
全部完工,大家可以来看看
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

发表于 2020-12-8 20:07:46 From FishC Mobile | 显示全部楼层
楼主在哪学习的?线下班?还是线上班?或者是自学?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-12-9 12:25:17 | 显示全部楼层
leijunke 发表于 2020-12-8 20:07
楼主在哪学习的?线下班?还是线上班?或者是自学?

看小甲鱼视频呀
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-1-17 09:02:33 | 显示全部楼层
没人吗。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-1-17 09:43:15 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-1-17 12:38:47 | 显示全部楼层
skyrimz 发表于 2020-11-26 14:33
更新:最后一部分情感分析

最后一个部分,轮到弹幕的情感分析。最开始了解到这方面还是csdn上某个大佬的 ...

。。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-5-3 11:27:15 | 显示全部楼层

怎么读取一个文件夹下所有弹幕文件并处理去重
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-6-16 22:08:39 | 显示全部楼层
cc1236 发表于 2021-5-3 11:27
怎么读取一个文件夹下所有弹幕文件并处理去重

先把所有的数据合并生成一个文件,再对这个文件进行去重就好了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

发表于 2021-6-16 22:47:04 | 显示全部楼层
优秀,你也是个风犬少年!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-6-17 12:04:24 | 显示全部楼层
写得太好了,很有借鉴价值
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-6-17 22:03:12 | 显示全部楼层
Sungne_jer 发表于 2021-6-16 22:47
优秀,你也是个风犬少年!

啦啦啦
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-6-18 12:27:17 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-6-18 22:54:59 | 显示全部楼层
skyrimz 发表于 2021-6-16 22:08
先把所有的数据合并生成一个文件,再对这个文件进行去重就好了

那请问有具体的代码吗
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-6-19 09:06:13 | 显示全部楼层
cc1236 发表于 2021-6-18 22:54
那请问有具体的代码吗
  1.         import pandas as pd
  2. import glob
  3. import csv


  4. def csv_drop_duplicates():
  5.     combine_name = input('输入合并后文件名:')
  6.     drop_duplicates_name = input('输入去重后文件名:')
  7.     csv_list = glob.glob('*.csv')
  8.     for i in csv_list:
  9.         fr = open(i, 'r', encoding='utf-8-sig').read()
  10.         with open(combine_name, 'a', encoding='utf-8-sig') as f_comb:
  11.             f_comb.write(fr)
  12.     print('合并完毕!')

  13.     temp_list = []
  14.     with open(combine_name, 'r', encoding='utf-8-sig') as f_drop:
  15.         reader = csv.reader(f_drop)
  16.         for row in reader:
  17.             temp_list.append(row)
  18.     df = pd.DataFrame(temp_list)
  19.     df.drop_duplicates(subset=6, inplace=True, keep='first')
  20.     df.to_csv(drop_duplicates_name, encoding='utf-8-sig')
  21.     print('去重完毕!')

  22. if __name__ == '__main__':
  23.    csv_drop_duplicates()
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-3-28 17:30

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表