《用python 玩转数据》项目——B站弹幕数据分析

    # -*- coding: utf-8 -*-
    """
    Created on Mon Jul 10 16:34:27 2017
    
    @author: ahchpr
    
    filename: re_zero_bili.py
    """
    
    import requests, csv, re, time
    from bs4 import BeautifulSoup as BS
    from selenium import webdriver
    import datetime
    from multiprocessing import Pool
    import sys   
    
    
    
    # Re:从零开始的异世界生活 的总剧情首页
    first_url = 'https://bangumi.bilibili.com/anime/3461' 
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)'}
    one_url = 'https://bangumi.bilibili.com/anime/3461/play#86298'
    #history_danmu_url = 'https://comment.bilibili.com/dmroll,time,cid'
    #now_danmu_url = 'https://comment.bilibili.com/{}.xml'.format(danmu_id)
    
    def get_danmu_id(url):
        MyDriver = webdriver.PhantomJS()
        MyDriver.get(url)
        time.sleep(3)
        danmu_id = re.findall(r'cid=(\d+)&', MyDriver.page_source)[0]
        return (danmu_id)
        
        
    
    def sele_get_first(url):
        MyDriver = webdriver.PhantomJS()
        MyDriver.get(url)
        time.sleep(5)
        response = MyDriver.page_source.encode('utf-8')
        page = response.decode('utf-8')
        return (page)
       
    
    def sele_get_re_list(page):
        pattern = re.compile('<a.*?href="(.*?)" title="(.*?)" target.*? class="v1-complete-text"><div class=')
        abstract = re.findall(pattern, page)
        return (abstract)
    
    
    def request_get_comment(url):
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)'}
        episode = url.split(" ")[0]
        url = url.split(" ")[1].strip()
        response = requests.get(url=url, headers=headers)
    
        soup = BS(response.text, 'lxml')
        result = soup.find_all('d')
        if  len(result) == 0:
            return (result)
        
        all_list = []
        for item in result:
    #        danmu_list.append(item.get('p').split(",").append(item.string))
            danmu_list = item.get('p').split(",")
            danmu_list.append(item.string)
    #        danmu_list[0] = sec_to_str(danmu_list[0])
    #        danmu_list[4] = time.ctime(eval(danmu_list[4]))
            danmu_list.append(episode)
    #        print(danmu_list)
            all_list.append(danmu_list)
        return (all_list)
            
            
      
    """将秒转换成固定格式 "hh:mm:ss"
    """      
    def sec_to_str(seconds):
        seconds = eval(seconds)
        m, s = divmod(seconds, 60)
        h, m = divmod(m, 60)
        dtEventTime = "%02d:%02d:%02d" % (h, m, s)
        return (dtEventTime)
        
    
    """计算最近30天的每天的时间戳,并返回,用于获取历史弹幕
    """
    def time_to_stamp():
        today = datetime.date.today()    
        end_day = datetime.datetime(today.year, today.month, today.day)
        start_day = end_day - datetime.timedelta(30)
        gap_day_sum = 30
        stamp_list = []
        for i in range(1, gap_day_sum):
            tmp = start_day + datetime.timedelta(i)
            stamp_list.append(int(time.mktime(tmp.timetuple())))
            
        return (stamp_list)
    
    
    def csv_write(tablelist, num):
        tableheader = ['dtTime', 'danmu_model', 'font', 'rgb', 'stamp', 'danmu_chi', 'userID', 'rowID', 'message', 'episode']
        file_name = "now{}.csv".format(num)
        print(file_name)
        with open(file_name, 'w', newline='', errors='ignore') as fd:
            writer = csv.writer(fd)
            writer.writerow(tableheader)
            for row in tablelist:
                writer.writerow(row)
                
        
    if __name__ == "__main__":
        sys.setrecursionlimit(1000000) 
        """爬取首页,获取共25话 《re:从零开始的异世界生活》 的播放连接
        """
        page = sele_get_first(first_url)
        re_list = sele_get_re_list(page)
    #    print(len(re_list))
        
        
        """以字典的形式保存例如:
        {'1': ['初始的终结与结束的开始', 'https://bangumi.bilibili.com/anime/3461/play#85754'],...}
        """
        re_dict = {}
        for item in re_list:
            re_dict[item[1].split(" ")[0]] = [item[1].split(" ")[1], item[0]]
    #    print(re_dict)
        
        """获取每一话的播放连接,保存成列表
        """
        re_url_list = []
        for i in range(1, len(re_dict)+1):
            re_url_list.append( re_dict[str(i)][1] )
            
        """利用进程池,获取每一话的弹幕文件连接,
        """
        re_danmu_id_list = []
        pool = Pool(14)
        re_danmu_id_list = pool.map(get_danmu_id, re_url_list)
        pool.close()    
        pool.join()
        re_danmu_id_dict = {}
        for n, p in enumerate(re_danmu_id_list):
            re_danmu_id_dict[str(n+1)] = p
        
        
        """将25话剧集的各自的最新的弹幕文件连接保存到一个文档 comment.txt,按照剧集的顺序保存
        """
        with open('comment.txt', 'w') as fd:
            for i in range(len(re_danmu_id_list)):   
                fd.write('{} https://comment.bilibili.com/{}.xml\n'.format(i+1, re_danmu_id_list[i]))
                
        
        history_danmu_url_list = []   
        stamp_list = time_to_stamp()
        for i in range(1, len(re_danmu_id_list)+1):
            for stamp in stamp_list :
                history_danmu_url = '{} https://comment.bilibili.com/dmroll,{},{}'.format(i, stamp, re_danmu_id_dict[str(i)])
                history_danmu_url_list.append(history_danmu_url)
            history_danmu_url_list.append('{} https://comment.bilibili.com/{}.xml'.format(i, re_danmu_id_dict[str(i)]))
       
        with open('history_danmu_url.txt', 'w') as fd:
            for line in history_danmu_url_list:
                fd.write("{}\n".format(line))
                
            
        all_list = []
    
        '''把每一集的弹幕文件链接,按照集数,整理到一个字典,
        '''
        url_dict ={}
        with open ("history_danmu_url.txt", 'r') as fd:
            url_whole = fd.readlines()
            print(len(url_whole))
            for i in range(1, len(re_danmu_id_list)+1):
                url_dict[str(i)] = [line for line in url_whole if int(line.split(" ")[0])==i]
        print (len(url_dict))
        
        '''按照集数,取出弹幕链接,进行爬虫,获取弹幕记录,并保存到.csv 文件
        '''
        for i in range(1, len(url_dict)+1):
            n = 0
            tmp_to_get_url = url_dict[(str(i))]
            file_name = "d{}.csv".format(i)
            tableheader = ['dtTime', 'danmu_model', 'font', 'rgb', 'stamp', 'danmu_chi', 'userID', 'rowID', 'message', 'episode']
            with open(file_name, 'a',  newline='', errors='ignore') as fd:
                writer = csv.writer(fd)
                writer.writerow(tableheader)
                for url in tmp_to_get_url :        
                    all_list = request_get_comment(url)
                    if all_list:
                        for row in all_list:
                            writer.writerow(row)
                    print("\n\n\n\n\n")
                    n = n+1
                    print(n)
                    del (all_list)
            del tmp_to_get_url
            
            
            
            
        """获取保存最新历史弹幕文件
        """
        now_danmu_all = {}
        with open ('comment.txt', 'r') as fd:
            for url in fd:
                now_danmu_all[int(url.strip().split(" ")[0])] = request_get_comment(url)
    
        for num, data in now_danmu_all.items():
            csv_write(data, num)
[/code]

  

6.2 数据统计可视化分析部分:

```code
    # -*- coding: utf-8 -*-
    """
    Created on Wed Jul 12 08:43:04 2017
    
    @author: ahchpr
    
    filename: static_danmu_comment.py
    
    """
    
    import pandas as pd
    import matplotlib.pyplot as plt
    import os
    from scipy.misc import imread
    from wordcloud import WordCloud
    import jieba
    import jieba.posseg as pseg
    import math
    
    
    
    def danmu_compress_plot(data, num):
        plt.cla()
        plt.xlabel(u"视频时间", fontproperties='SimHei')
        plt.ylabel(u"弹幕量", fontproperties='SimHei')
        plt.title(u'第{}集_时间轴弹幕变化'.format(num), fontproperties='SimHei')
        
        keys = [item for item in data.index ]
       
        values = [item for item in data.dtTime]
     
        """弹幕密度折线图
        """
        plt.plot(keys, values)
        plt.show()
    
    
    def danmu_compress(data):
        df = data.drop_duplicates()
        dd = df.copy()
    #    round_dic = {}
    #    向下取“整秒数”
    #    round_dic['dtTime_new'] = [math.floor(item) for item in dd.dtTime]
        dd['dtTime_new'] = [math.floor(item) for item in dd.dtTime]
    #    dd.sort_values(by='dtTime_new', inplace=True)
    #    print (dd.iloc[1:200, [6, 0, 10]])
        dc = dd.groupby('dtTime_new').count()
        result = dc.sort_index()
        return (result)
    
        '''result  大概的结构:只写出了一列参考,其中dtTime就是在0秒开始时的弹幕数量
                       dtTime    
        dtTime_new                           
        0               75   
        1               37               
        2               34               
        '''
    
    def extract_words(data, num):
        
        df = data.drop_duplicates()
        dd = df.copy()
        message_list = [str(item) for item in dd.message]
        
        stop_words = set(line.strip() for line in open('stopwords.txt', encoding='utf-8'))
        
        newslist = []
        for subject in message_list:
            if subject.isspace():
                continue
            # segment words line by line
            word_list = pseg.cut(subject)
            for word, flag in word_list:
                if not word in stop_words and flag == 'n':
                    newslist.append(word)
    
        d = os.path.dirname(__file__)
        mask_p_w_picpath = imread(os.path.join(d, "qiaodan.png"))
        content = ' '.join(newslist)
        wordcloud = WordCloud(font_path='simhei.ttf', background_color="grey", mask=mask_p_w_picpath, max_words=40).generate(content)
        
        # Display the generated p_w_picpath:
        file_name = u"第{}集_热词云.jpg".format(num)
        plt.imshow(wordcloud)
        plt.axis("off")
        plt.title(file_name, fontproperties='SimHei')
        wordcloud.to_file(file_name)
        plt.show()
        plt.cla()
        plt.close()
    
    
    def static_danmu_length(data):
        df = data.drop_duplicates()
        dd = df.copy()
        dd['message_len'] = [len(str(item)) for item in df.message] #统计每条弹幕的长度
        d1 = dd.loc[:, ['userID', 'message', 'message_len', 'episode']]
        dr = d1.copy()
        return (dr)
    
    def every_episode_usersort(data):
        df = data.drop_duplicates()
        dd = df.groupby("userID").count()
        user_sort = dd.sort_values(by='episode', ascending=False).loc[:,['episode']]
        return (user_sort)
    
    
    def every_episode_user(data):
        '''30天内共有多少用户发弹幕
        '''
        df = data.drop_duplicates()
        dd = df.groupby("userID").count()
        user_sum = len(dd)
        return (user_sum)
    
      
        
    def every_episode_comment(data):
        '''30天内有多少弹幕发出
        '''
        df = data.drop_duplicates()
        danmu_sum = len(df)
        return (danmu_sum)
    
    
    def top_user_danmu(data):
        plt.cla()
        ing = range(5)
        x = data.head(5).episode.index
        y = data.head(5).episode.values
        plt.xticks(ing, x, rotation=30)
        plt.xlabel(u"用户ID", fontproperties='SimHei')
        plt.ylabel(u"发弹幕数量", fontproperties='SimHei')
        plt.title(u"发弹幕数top5用户",fontproperties='SimHei')
        plt.bar(ing, y)
        plt.show()
    
       
    def every_episode_comment_change(episode_comment_dic):      
        plt.cla()
        plt.xlabel(u"剧集", fontproperties='SimHei')
        plt.ylabel(u"弹幕量", fontproperties='SimHei')
        plt.title(u'每集弹幕总量变化', fontproperties='SimHei')
        keys = range(1, len(episode_comment_dic)+1)
        values = []
        for i in keys:
            values.append(episode_comment_dic[i])
           
        """每一集弹幕总量的折线变化图
        """
        plt.plot(keys, values)
        plt.show()
        
        
    def every_episode_danmu_pie(d_tmp, num):
        
        a1 = float(len(d_tmp[(d_tmp.episode>=1) & (d_tmp.episode<=2)]))
        a2 = len(d_tmp[(d_tmp.episode>=3) & (d_tmp.episode<=8)])
        a3 = len(d_tmp[(d_tmp.episode>=9) & (d_tmp.episode<=20)])
        a4 = len(d_tmp[(d_tmp.episode>=21) ])
         
        s =  a1 + a2 + a3 + a4 
        s = float(s)
        li = [a1 , a2 , a3 , a4  ]
        xp = []
        for i in li:
            i = float(i)
            if i<=0:
                t = 0
                xp.append(t)
            else:
                t = (i/s*100)
                xp.append(t)
        
        plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
        plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
        
        plt.figure(figsize=(6,9))
        labels = [u'1-2条', u'2-8条', u'9-20条', u'21条以上' ]
        sizes = xp
        colors = ['red', 'yellow', 'gray', 'lightskyblue']
        explodes = [0 , 0, 0, 0.6]
        
        plt.axis('equal')
        plt.title(u'第{}集_用户发送弹幕数量百分比分布图'.format(num))
        plt.pie(sizes,  labels=labels,explode=explodes, colors=colors, labeldistance=0.5,
                autopct = '%2.2f%%', startangle = 90, pctdistance = 0.8)
        
        plt.show()
        plt.close()
        
        
    def danmu_length_pie(d_tmp, num):   
        a1 = float(len(d_tmp[(d_tmp.message_len>=1) & (d_tmp.message_len<=4)]))
        a2 = float(len(d_tmp[(d_tmp.message_len>=5) & (d_tmp.message_len<=10)]))
        a3 = float(len(d_tmp[(d_tmp.message_len>=11) & (d_tmp.message_len<=15)]))
        a4 = float(len(d_tmp[(d_tmp.message_len>=16) ]))
        s =  a1 + a2 + a3 + a4 
        s = float(s)
        li = [a1 , a2 , a3 , a4  ]
        xp = []
        for i in li:
            i = float(i)
            if i<=0:
                t = 0
                xp.append(t)
            else:
                t = (i/s*100)
                xp.append(t)
        
        plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
        plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
        
        plt.figure(figsize=(6,9))
        labels = [u'1-4个', u'5-10个', u'11-15个', u'16个以上' ]
        sizes = xp
        colors = ['red', 'yellow', 'gray', 'lightskyblue']
        explodes = [0 , 0, 0, 0.09]
        plt.axis('equal')
        plt.title(u'第{}集_用户发送弹幕长度百分比分布图'.format(num))
        plt.pie(sizes,  labels=labels, colors=colors, explode=explodes, labeldistance=0.5,
                autopct = '%2.2f%%', startangle = 90, pctdistance = 0.8)
        
        plt.show()
        plt.close()
    
    
    #秒转换成时间
    def sec_to_str(seconds):
        seconds = eval(seconds)
        m, s = divmod(seconds, 60)
        h, m = divmod(m, 60)
        length_time = "%02d:%02d:%02d" % (h, m, s)
        return (length_time)
    
        
    if __name__ == "__main__":            
        
        path = os.getcwd()
        path_list = []
        for i in range(1, 26):
            path_list.append(path + "\\d{}.csv".format(i))
            
        episode_comment_dic = {}
        user_sum_dic = {}
        user_sort_dic = {}
        danmu_length_dic = {}
        reyun_data_dic = {}
        for item in path_list:
            '''读取csv数据源文件,每一集的近30天弹幕都保存在一个csv文件
            '''
            data = pd.read_csv(item.strip(), encoding='gbk')
            
            '''统计每一集,近30天,弹幕总量,保存在字典:{1:323233, 2:212121, .......}
            '''
            episode_comment_dic[data.loc[1, 'episode']] = every_episode_comment(data)
            
            '''统计每一集,近30天,共有多少用户发了弹幕,保存在字典:{1:3737, 2:34234,......}
            '''
            user_sum_dic[data.loc[1, 'episode']] = every_episode_user(data)
            
            '''统计每一集,30天内的弹幕数量,依据弹幕数量,把用户排序,每一集排序后的结果是一个DataFrame,
               user_sort_dic = {1: DataFrame1, 2:DataFrame2, ......, 25: DataFrame25}
            '''
            user_sort_dic[data.loc[1, 'episode']] = every_episode_usersort(data)
            
            
            '''统计发送弹幕的字符串长度
            '''
            danmu_length_dic[data.loc[1, 'episode']] = static_danmu_length(data)
            
            '''统计每一集的分词,热词,词云
            '''
            reyun_data_dic[data.loc[1, 'episode']] = data.copy()
            
    
            '''统计每一集,近30天的弹幕量,视频里的每一秒的弹幕数量,弹幕密度
            '''
    #        danmu_compress_dic[data.loc[1, 'episode']] =  danmu_compress(data)
        
            
            del data
            
            
        print(episode_comment_dic)
        print(user_sum_dic)
        
        '''把经过排序统计处理后的所有DataFrame 进行concat。然后就可以统计所有用户在30天内,对25集视频
           发送弹幕的数量。最后对用户排序。最终结果:
           d4_alldanmu_sort 是一个DataFrame 变量,将用户按弹幕数,降序排列。
        '''
        d3_all_user = (pd.concat([item for k, item in user_sort_dic.items()]))
        d3_all_user['userID'] = d3_all_user.index
        aSer = d3_all_user.groupby('userID').episode.sum()
        d4_alldanmu_sort = pd.DataFrame(aSer).sort_values(by='episode', ascending=False)
        
        
        '''绘制折线图:25集视频,近30天每集弹幕总数
        '''
        every_episode_comment_change(episode_comment_dic)
        
        
        '''柱状图:25集视频,近30天,所有用户中,发弹幕数量最多的5个用户
        '''
        top_user_danmu(d4_alldanmu_sort)
        
        '''统计一集,用户发送弹幕数量的百分比分布图
        '''
        for i in range(1, len(user_sort_dic)+1):
            d_tmp = user_sort_dic[i]
            every_episode_danmu_pie(d_tmp, i)
            del d_tmp
            
       
        '''统计用户发送弹幕的长度分布百分比
        '''
        for i in range(1, len(user_sort_dic)+1):
            d_tmp = danmu_length_dic[i]
            danmu_length_pie(d_tmp, i)
            del d_tmp
         
     
        '''分析弹幕密度,使用当前最新的历史弹幕文件分析
        '''
        now_danmu_list = []
        for i in range(1, 26):
            now_danmu_list.append(path + "\\now{}.csv".format(i))
        
        danmu_compress_dic = {}
        for item in now_danmu_list:
            data = pd.read_csv(item.strip(), encoding='gbk')
            danmu_compress_dic[data.loc[1, 'episode']] =  danmu_compress(data)
    
        for num, data in danmu_compress_dic.items():
            danmu_compress_plot(data, num)
            
        """绘制热词云图,热词的运行时间太久了,先关闭
        """
        for num, data in reyun_data_dic.items():
            extract_words(data, num)
[/code]

  

转载于:https://blog.51cto.com/hellocjq/1947252


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210608151750993.gif)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值