【Python网络爬虫课程设计】B站up主——老番茄视频数据爬取+数据可视化分析

发布时间 2023-06-05 21:54:13作者: hhhhhhhhh8888

一、选题背景

1.背景

  随着大数据时代的来临,网络爬虫在互联网中的地位将越来越重要。互联网中的数据是海量的,如何自动高效地获取互联网中我们感兴趣的信息并为我们所用是一个重要的问题,而爬虫技术就是为了解决这些问题而生的。对于身为数据科学与大数据技术专业的学生来说,网络爬虫成为必要的技能之一,结合自己的喜好,这次我选择爬取个人比较喜欢的B站up主老番茄目前(2023-5-20以前)的所有视频数据,并进行简单的可视化分析。

2.预期目标

  老番茄自2013年起在哔哩哔哩平台进行视频创作,是该站首位拥有1000万粉丝的个人UP主,同时他也发布了近500条视频内容,通过对其视频信息的爬取和数据分析,目的是了解老番茄什么时候开始获得流量、播放量和视频上传时间是否存在关联、其主要视频内容分区、每年的视频产量等。

二、B站up主—老番茄视频信息数据爬取方案

1.课题名称

  B站up主—老番茄视频信息数据爬取

2.爬取的内容与数据特征分析

  主要将爬取的内容分为16个字段,分别是:视频标题、视频地址、up主昵称、up主UID、视频上传时间、视频时长、是否合作视频、视频分区、弹幕数、播放量、点赞数、投币量、收藏量、评论数、转发量以及爬取时间;爬取到的478条视频数据,保存到excel表格中。

3.设计方案概述

(1)实现思路:

  • 打开一个B站的网页,打开开发者模式,点击network,刷新。
  • 发起请求,获取响应内容,得到json字符串,进行js解密。
  • 解析自己想要的数据,保存文本。

(2)技术难点:

  • 应对反爬。
  • json解密。
  • 视频内容分区。

三、老番茄B站主页的特征分析

根据鼠标光标确定具体爬取内容的部分,以便分析

 

四、网络爬虫程序设计

1.数据的爬取与采集

爬取网站:https://api.bilibili.com/x/space/wbi/arc/search

(1)导入相关库并且建立一个用户代理池,以解决反爬。用户代理池取自:csdn :https://deepboat.blog.csdn.net/ 【Python】【进阶篇】三、Python爬虫的构建User-Agnet代理池

import requests
import random
import time
import datetime
import pandas as pd
import hashlib
from pprint import pprint
from lxml import etree
#爬取b站up主老番茄全部视频信息
up_mid = '546195'  # 这是老番茄的aid号
max_page = 16     # 爬取最大页数
'''用户代理池,防止反爬 '''
user_agent = [
    "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52"
]
def web_rid(param):
    """js解密"""
    n = "9cd4224d4fe74c7e9d6963e2ef891688" + "263655ae2cad4cce95c9c401981b044a"
    c = ''.join([n[i] for i in
                 [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14,
                  39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56,
                  59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52]][:32])
    s = int(time.time())
    param["wts"] = s
    param = "&".join([f"{i[0]}={i[1]}" for i in sorted(param.items(), key=lambda x: x[0])])
    return hashlib.md5((param + c).encode(encoding='utf-8')).hexdigest(), s

 

(2)对我们要爬取的视频内容进行分类,在开发者界面找到以下部分

代码如下:

""" 进行分类 """
def get_video_type(v_num):
    if v_num == 28:
        return '音乐'
    elif v_num == 17:
        return '游戏'
    elif v_num == 71:
        return '娱乐'
    elif v_num == 138 or v_num == 161 or v_num == 239:
        return '生活'
    elif v_num == 85:
        return '影视'
    elif v_num == 218:
        return '动物圈'
    elif v_num == 214:
        return '美食'
    else:
        return '未知分区'

 

(3)获取前几页视频的url列表

代码如下:

'''获取前几页视频的url列表'''
def get_url_list():
    headers = {
        'User-Agent': random.choice(user_agent),
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Connection': 'keep-alive',
    }
    url_list = []  # 视频地址
    title_list = []  # 视频标题
    author_list = []  # UP主昵称
    mid_list = []  # UP主UID
    create_time_list = []  # 上传时间
    play_count_list = []  # 播放数
    length_list = []  # 视频时长
    comment_count_list = []  # 评论数
    is_union_list = []  # 是否合作视频
    type_list = []  # 分区
    danmu_count_list = []  # 弹幕数
    for i in range(1, max_page + 1):  # 前n页
        print('开始爬取第{}页'.format(str(i)))
        url = 'https://api.bilibili.com/x/space/wbi/arc/search'
        params = {
            'mid': up_mid,
            'ps': 30,
            'tid': 0,
            'pn': i,  # 第几页
            'keyword': '',
            'order': 'pubdate',
            'platform': 'web',
            'web_location': '1550101',
            'order_avoided': 'true',
        }
        # 增加解密参数
        ret = web_rid(params)
        w_rid = ret[0]
        wts = ret[1]
        params['w_rid'] = w_rid
     params['wts'] = wts

 

(4)发送请求

# 发送请求
        r = requests.get(url, headers=headers, params=params)
        print(r.status_code)  # 响应码200
        json_data = r.json()
        video_list = json_data['data']['list']['vlist']
        for i in video_list:
            bvid = i['bvid']
            url = 'https://www.bilibili.com/video/' + bvid
            url_list.append(url)
            title = i['title']
            title_list.append(title)
            author = i['author']
            author_list.append(author)
            mid = i['mid']
            mid_list.append(mid)
            create_time = i['created']
            create_time = trans_date(v_timestamp=create_time)
            create_time_list.append(create_time)
            play_count = i['play']
            play_count_list.append(play_count)
            length = i['length']
            length_list.append(length)
            comment = i['comment']
            comment_count_list.append(comment)
            is_union = '' if i['is_union_video'] == 1 else ''
            is_union_list.append(is_union)
            type_name = get_video_type(v_num=i['typeid'])
            type_list.append(type_name)
            danmu_count = i['video_review']
            danmu_count_list.append(danmu_count)
    return url_list, title_list, author_list, mid_list, create_time_list, play_count_list, length_list, comment_count_list, is_union_list, type_list, danmu_count_list

 

(5)爬取每个视频详细数据,并保存为excel表格

'视频标题': title_list,

'视频地址': url_list,

'UP主昵称': author_list,

'UP主UID': mid_list,

'视频上传时间': create_time_list,

'视频时长': length_list,

'是否合作视频': is_union_list,

'视频分区': type_list,

'弹幕数': danmu_count_list,

'播放量': play_count_list,

'点赞数': like_count_list,

'投币量': coin_count_list,

'收藏量': fav_count_list,

'评论数': comment_count_list,

'转发量': share_count_list,

'实时爬取时间': now_list

'''爬取每个视频的详细数据'''
def get_video_info(v_url):
    headers = {
        'User-Agent': random.choice(user_agent),
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Connection': 'keep-alive',
    }
    r = requests.get(v_url, headers=headers)
    print('当前的url是:', v_url)
    # 用xpath解析页面数据
    html = etree.HTML(r.content)
    try:  # 点赞数
        like_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[1]/div/span/text()')[0]
        if like_count.endswith(''):
            like_count = int(float(like_count.replace('', '')) * 10000)  # 把万转换为数字
    except:
        like_count = ''
    try:  # 投币数
        coin_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[2]/div/span/text()')[0]
        if coin_count.endswith(''):
            coin_count = int(float(coin_count.replace('', '')) * 10000)  # 把万转换为数字
    except:
        coin_count = ''
    try:  # 收藏数
        fav_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[3]/div/span/text()')[0]
        if fav_count.endswith(''):
            fav_count = int(float(fav_count.replace('', '')) * 10000)  # 把万转换为数字
    except:
        fav_count = ''
    try:  # 转发数
        share_count = html.xpath('//*[@class="video-share-info video-toolbar-item-text"]/text()')[0]
        if share_count.endswith(''):
            share_count = int(float(share_count.replace('', '')) * 10000)  # 把万转换为数字
    except Exception as e:
        print('share_count except', str(e))
        share_count = ''
    try:
        union_team = html.xpath('//*[@id="member-container"]')
        for i in union_team:
            url_tail = i.xpath('./div/div/a/@href')
            print(url_tail)
        members = [i.replace('//space.bilibili.com/', '') for i in url_tail]
        print('members is: {}'.format(members))
    except:
        members = None
    return like_count, coin_count, fav_count, share_count, members

if __name__ == '__main__':
    url_list, title_list, author_list, mid_list, create_time_list, play_count_list, length_list, comment_count_list, is_union_list, type_list, danmu_count_list = get_url_list()
    pprint(title_list)
    pprint(is_union_list)
    pprint(type_list)

    like_count_list = []  # 点赞数
    coin_count_list = []  # 投币数
    fav_count_list = []  # 收藏数
    share_count_list = []  # 分享数
    now_list = []  # 实时爬取时间
    for url in url_list:
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')  # 实时爬取时间
        like_count, coin_count, fav_count, share_count, members = get_video_info(v_url=url)
        like_count_list.append(like_count)
        coin_count_list.append(coin_count)
        fav_count_list.append(fav_count)
        share_count_list.append(share_count)
        now_list.append(now)
    df = pd.DataFrame(data={'视频标题': title_list,
                            '视频地址': url_list,
                            'UP主昵称': author_list,
                            'UP主UID': mid_list,
                            '视频上传时间': create_time_list,
                            '视频时长': length_list,
                            '是否合作视频': is_union_list,
                            '视频分区': type_list,
                            '弹幕数': danmu_count_list,
                            '播放量': play_count_list,
                            '点赞数': like_count_list,
                            '投币量': coin_count_list,
                            '收藏量': fav_count_list,
                            '评论数': comment_count_list,
                            '转发量': share_count_list,
                            '实时爬取时间': now_list
                            })
    df.to_excel('laofanqie_B站视频数据.xlsx', index=None)

文件部分截图(共438条数据):

 

 

2.对数据进行清洗和处理

(1)导入文件,查看前五行数据

import pandas as pd
df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
df.head(5)  # 查看前五行数据

 (2)查看是否有缺失值

# 由于数据量较大,接下来调用 any 的方法判断是否表中有缺失值
nan_any_rows = df.isnull().any(axis=1)
df[nan_any_rows]

 

 (3)查看是否有重复值

# 调用 duplicated 方法检测是否有重复值
df['视频标题'].duplicated()

结论:不存在重复值 

(4)查看列信息

df.info()  # 查看列信息

 3.数据分析

df.describe()  # 数据分析

结论:评论数min为0.0,可能存在异常值

df2 = df[['视频标题', '视频地址', '弹幕数', '播放量',
          '点赞数', '投币量', '收藏量', '评论数', '转发量', '视频上传时间']]  
df2.loc[df.评论数 == 0]  #评论数是0的数据

 

df2.loc[df.播放量 == df['播放量'].max()]  # 播放量最高的视频
df2.loc[df.弹幕数 == df['弹幕数'].max()]  # 弹幕数最高的视频
df2.loc[df.投币量 == df['投币量'].max()]  # 投币量最高的视频
df2.loc[df.点赞数 == df['点赞数'].max()]  # 点赞数最高的视频
df2.loc[df.收藏量 == df[ '收藏量'].max()]  # 收藏量最高的视频
df2.loc[df.评论数 == df['评论数'].max()]  # 评论数最高的视频
df2.loc[df.转发量 == df[ '转发量'].max()]  # 转发量最高的视频

 

df2.loc[df.投币量 == df['投币量'].min()]  # 投币量最小的视频
df2.loc[df.点赞数 == df[ '点赞数'].min()]  # 点赞数最小的视频
df2.loc[df.收藏量 == df['收藏量'].min()]  # 收藏量最小的视频
df2.loc[df.播放量 == df['播放量'].min()]  # 播放量最小的视频
df2.loc[df.弹幕数 == df['弹幕数'].min()]  # 弹幕数最小的视频
df2.loc[df.评论数 == df['评论数'].min()]  # 评论数最小的视频
df2.loc[df.转发量 == df['转发量'].min()]  # 转发量最小的视频

 

 

df2.nlargest(n=10, columns='播放量')  # 播放量TOP10的视频

 

df2.nsmallest(n=5, columns='转发量')  # 转发量倒数5的视频

 基本都是老番茄早期的视频,没什么流量。

df['视频分区'].value_counts()  # 统计:视频分区

 这里‘未知分区’有点问题,基本都是‘游戏’视频,没有分辨出来

# 查看spearman相关性(得出结论:收藏量&投币量,相关性最大,0.98)
df2.corr(method='spearman')

 4.数据可视化

(1)部分视频信息的关系折线图

import pandas as pd
import matplotlib.pyplot as plt
import xlwings as xw
df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文标签  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
df2.plot(x='视频上传时间', y=['弹幕数', '点赞数', '投币量', '收藏量', '评论数', '转发量'])

老番茄从2019年获得流量

(2)老番茄视频播放量趋势图

import pandas as pd
import matplotlib.pyplot as plt
import xlwings as xw
df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
figure = plt.figure(figsize=(10,4))
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False 
# X轴和Y轴数据标签
x = df['视频上传时间']
y = df['播放量']
plt.grid()
plt.plot(x, y, color = 'c', linewidth = '2', linestyle = 'solid')      # 制作折线图
plt.title(label = '老番茄视频播放量趋势图', fontdict = {'color' : 'black', 'size' : 20}, loc = 'center')    # 添加并设置图表标题

 

(3)老番茄视频投币量散点图

import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
#坐标轴负号的处理
plt.rcParams['axes.unicode_minus']=False
figure = plt.figure(figsize=(10,4))
# 读取数据
df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
# 绘制单条折线图
plt.scatter(df.视频上传时间, # x轴数据
         df.投币量, # y轴数据
         s=100,
        color="plum") 
#对于X轴,只显示x中各个数对应的刻度值
plt.xticks(fontsize=11 )  #改变x轴文字值的文字大小
# 添加y轴标签
plt.xlabel('视频上传时间')
plt.ylabel('投币量')
# 添加图形标题
plt.title('老番茄视频投币量',size=16)
# 显示图形
plt.grid()
plt.show()

 (4)老番茄每年视频播放量饼图

import matplotlib.pyplot as plt  
date = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
plt.rcParams['font.sans-serif'] = ['SimHei']   #解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False    # 解决中文显示问题
date = date.set_index('视频上传时间')                 #把日期列设为索引
date.index = pd.to_datetime(date.index)       #把索引转为时间格式
result = date[['播放量']].groupby(date.index.year).sum()  
plt.pie(result['播放量'], labels=result.index, autopct='%3.1f%%', textprops={ 'size':10, 'weight':'bold'})  #设置显示字体颜色、尺寸、加粗
plt.title('老番茄每年视频播放量', c='black')          
plt.show()

 (5)老番茄视频分区量

from matplotlib import pyplot as plt
from matplotlib import font_manager
a = ['游戏', '未知分区', '生活', '音乐 ', '动物圈', '影视','娱乐'] 
b = [274,130,51,20,1,1,1] 
# 设置图像大小
plt.figure(figsize=(11,5),dpi=80) 
# 绘制条形图
plt.barh(range(len(a)), b, height=0.3, color='orange')  # 区别于竖的条形图 不能使用width 
# 设置字符串到X轴
plt.yticks(range(len(a)), a, fontproperties=my_font) 
plt.grid(alpha=0.3)
# 显示图形
plt.title('老番茄视频分区量',size=15, c='black')          
plt.show()

 (6)不同视频的评论数与收藏量之间的关系

import pandas as pd
import matplotlib.pyplot as plt
import plotly as py
import plotly.graph_objs as go
import numpy as np
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
#坐标轴负号的处理
plt.rcParams['axes.unicode_minus']=False
figure = plt.figure(figsize=(10,4))
# 读取数据
data= pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
#展示不同种类商品Category的售价Sales和利润Profit的关系
colors=['#393E46','#2BCDC2','#F66095']
labels=data['视频分区'].drop_duplicates(inplace=False)#获取种类数
X=[]
Y=[]
for i in labels:
    X.append(data[data['视频分区']==i]['评论数'])
    Y.append(data[data['视频分区']==i]['收藏量'])             
fig=go.Figure()
py.offline.init_notebook_mode()
for s,p,c,l in zip(X,Y,colors,labels):
    fig.add_trace(go.Scatter(
        x=s,
        y=p,
        mode='markers',
        marker_color=c,
        name=l
    ))    
fig.update_layout(
    title='不同视频的评论数与收藏量之间的关系',
    
    xaxis=dict(title='评论数'),
    yaxis=dict(title='收藏量'),)
py.offline.iplot(fig)

 

 5.分析相关系数

(1)收藏量与转发量

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 读取数据
tips= pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
plt.grid(alpha=0.3)
sns.regplot(x='收藏量',y='转发量',color='g', data=tips)

(2)播放量和投币量

sns.lmplot(x='播放量',y='投币量',  data=tips )

 

 6.完整代码

  1 import requests
  2 import random
  3 import time
  4 import datetime
  5 import pandas as pd
  6 import hashlib
  7 from pprint import pprint
  8 from lxml import etree
  9 #爬取b站up主老番茄全部视频信息
 10 up_mid = '546195'  # 这是老番茄的aid号
 11 max_page = 16     # 爬取最大页数
 12 '''用户代理池,防止反爬  取自:csdn :https://deepboat.blog.csdn.net/ 【Python】【进阶篇】三、Python爬虫的构建User-Agnet代理池'''
 13 user_agent = [
 14     "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)",
 15     "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
 16     "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
 17     "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
 18     "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
 19     "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
 20     "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
 21     "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
 22     "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
 23     "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
 24     "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
 25     "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
 26     "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
 27     "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
 28     "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
 29     "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
 30     "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52"
 31 ]
 32 '''参考'''
 33 def web_rid(param):
 34     """js解密"""
 35     n = "9cd4224d4fe74c7e9d6963e2ef891688" + "263655ae2cad4cce95c9c401981b044a"
 36     c = ''.join([n[i] for i in
 37                  [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14,
 38                   39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56,
 39                   59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52]][:32])
 40     s = int(time.time())
 41     param["wts"] = s
 42     param = "&".join([f"{i[0]}={i[1]}" for i in sorted(param.items(), key=lambda x: x[0])])
 43     return hashlib.md5((param + c).encode(encoding='utf-8')).hexdigest(), s
 44 
 45 """获取爬取时间"""
 46 def trans_date(v_timestamp):
 47     timeArray = time.localtime(v_timestamp)
 48     otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
 49     print(otherStyleTime)
 50     return otherStyleTime
 51 
 52 """ 进行分类 """
 53 def get_video_type(v_num):
 54     if v_num == 28:
 55         return '音乐'
 56     elif v_num == 17:
 57         return '游戏'
 58     elif v_num == 71:
 59         return '娱乐'
 60     elif v_num == 138 or v_num == 161 or v_num == 239:
 61         return '生活'
 62     elif v_num == 85:
 63         return '影视'
 64     elif v_num == 218:
 65         return '动物圈'
 66     elif v_num == 214:
 67         return '美食'
 68     else:
 69         return '未知分区'
 70 
 71 '''获取前几页视频的url列表'''
 72 def get_url_list():
 73     headers = {
 74         'User-Agent': random.choice(user_agent),
 75         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 76         'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
 77         'Connection': 'keep-alive',
 78     }
 79     url_list = []  # 视频地址
 80     title_list = []  # 视频标题
 81     author_list = []  # UP主昵称
 82     mid_list = []  # UP主UID
 83     create_time_list = []  # 上传时间
 84     play_count_list = []  # 播放数
 85     length_list = []  # 视频时长
 86     comment_count_list = []  # 评论数
 87     is_union_list = []  # 是否合作视频
 88     type_list = []  # 分区
 89     danmu_count_list = []  # 弹幕数
 90     for i in range(1, max_page + 1):  # 前n页
 91         print('开始爬取第{}页'.format(str(i)))
 92         url = 'https://api.bilibili.com/x/space/wbi/arc/search'
 93         params = {
 94             'mid': up_mid,
 95             'ps': 30,
 96             'tid': 0,
 97             'pn': i,  # 第几页
 98             'keyword': '',
 99             'order': 'pubdate',
100             'platform': 'web',
101             'web_location': '1550101',
102             'order_avoided': 'true',
103         }
104         # 增加解密参数
105         ret = web_rid(params)
106         w_rid = ret[0]
107         wts = ret[1]
108         params['w_rid'] = w_rid
109         params['wts'] = wts
110         # 发送请求
111         r = requests.get(url, headers=headers, params=params)
112         print(r.status_code)  # 响应码200
113         json_data = r.json()
114         video_list = json_data['data']['list']['vlist']
115         for i in video_list:
116             bvid = i['bvid']
117             url = 'https://www.bilibili.com/video/' + bvid
118             url_list.append(url)
119             title = i['title']
120             title_list.append(title)
121             author = i['author']
122             author_list.append(author)
123             mid = i['mid']
124             mid_list.append(mid)
125             create_time = i['created']
126             create_time = trans_date(v_timestamp=create_time)
127             create_time_list.append(create_time)
128             play_count = i['play']
129             play_count_list.append(play_count)
130             length = i['length']
131             length_list.append(length)
132             comment = i['comment']
133             comment_count_list.append(comment)
134             is_union = '' if i['is_union_video'] == 1 else ''
135             is_union_list.append(is_union)
136             type_name = get_video_type(v_num=i['typeid'])
137             type_list.append(type_name)
138             danmu_count = i['video_review']
139             danmu_count_list.append(danmu_count)
140     return url_list, title_list, author_list, mid_list, create_time_list, play_count_list, length_list, comment_count_list, is_union_list, type_list, danmu_count_list
141 '''爬取每个视频的详细数据'''
142 def get_video_info(v_url):
143     headers = {
144         'User-Agent': random.choice(user_agent),
145         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
146         'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
147         'Connection': 'keep-alive',
148     }
149     r = requests.get(v_url, headers=headers)
150     print('当前的url是:', v_url)
151     # 用xpath解析页面数据
152     html = etree.HTML(r.content)
153     try:  # 点赞数
154         like_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[1]/div/span/text()')[0]
155         if like_count.endswith(''):
156             like_count = int(float(like_count.replace('', '')) * 10000)  # 把万转换为数字
157     except:
158         like_count = ''
159     try:  # 投币数
160         coin_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[2]/div/span/text()')[0]
161         if coin_count.endswith(''):
162             coin_count = int(float(coin_count.replace('', '')) * 10000)  # 把万转换为数字
163     except:
164         coin_count = ''
165     try:  # 收藏数
166         fav_count = html.xpath('//*[@id="arc_toolbar_report"]/div[1]/div[3]/div/span/text()')[0]
167         if fav_count.endswith(''):
168             fav_count = int(float(fav_count.replace('', '')) * 10000)  # 把万转换为数字
169     except:
170         fav_count = ''
171     try:  # 转发数
172         share_count = html.xpath('//*[@class="video-share-info video-toolbar-item-text"]/text()')[0]
173         if share_count.endswith(''):
174             share_count = int(float(share_count.replace('', '')) * 10000)  # 把万转换为数字
175     except Exception as e:
176         print('share_count except', str(e))
177         share_count = ''
178     try:
179         union_team = html.xpath('//*[@id="member-container"]')
180         for i in union_team:
181             url_tail = i.xpath('./div/div/a/@href')
182             print(url_tail)
183         members = [i.replace('//space.bilibili.com/', '') for i in url_tail]
184         print('members is: {}'.format(members))
185     except:
186         members = None
187     return like_count, coin_count, fav_count, share_count, members
188 
189 if __name__ == '__main__':
190     url_list, title_list, author_list, mid_list, create_time_list, play_count_list, length_list, comment_count_list, is_union_list, type_list, danmu_count_list = get_url_list()
191     pprint(title_list)
192     pprint(is_union_list)
193     pprint(type_list)
194 
195     like_count_list = []  # 点赞数
196     coin_count_list = []  # 投币数
197     fav_count_list = []  # 收藏数
198     share_count_list = []  # 分享数
199     now_list = []  # 实时爬取时间
200     for url in url_list:
201         now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')  # 实时爬取时间
202         like_count, coin_count, fav_count, share_count, members = get_video_info(v_url=url)
203         like_count_list.append(like_count)
204         coin_count_list.append(coin_count)
205         fav_count_list.append(fav_count)
206         share_count_list.append(share_count)
207         now_list.append(now)
208     df = pd.DataFrame(data={'视频标题': title_list,
209                             '视频地址': url_list,
210                             'UP主昵称': author_list,
211                             'UP主UID': mid_list,
212                             '视频上传时间': create_time_list,
213                             '视频时长': length_list,
214                             '是否合作视频': is_union_list,
215                             '视频分区': type_list,
216                             '弹幕数': danmu_count_list,
217                             '播放量': play_count_list,
218                             '点赞数': like_count_list,
219                             '投币量': coin_count_list,
220                             '收藏量': fav_count_list,
221                             '评论数': comment_count_list,
222                             '转发量': share_count_list,
223                             '实时爬取时间': now_list
224                             })
225     df.to_excel('laofanqie_B站视频数据.xlsx', index=None)
226 #数据分析及可视化
227 import pandas as pd
228 df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
229 df.head(5)  # 查看前三行数据
230 df.shape  # 查看形状,几行几列
231 # 由于数据量较大,接下来调用 any 的方法判断是否表中有缺失值
232 nan_any_rows = df.isnull().any(axis=1)
233 df[nan_any_rows]
234 # 调用 duplicated 方法检测是否有重复值
235 df['视频标题'].duplicated()
236 df.info()  # 查看列信息
237 df.describe()  # 数据分析
238 df['是否合作视频'].value_counts()  # 统计:是否合作视频
239 df['视频分区'].value_counts()  # 统计:视频分区
240 df2 = df[['视频标题', '视频地址', '弹幕数', '播放量',
241           '点赞数', '投币量', '收藏量', '评论数', '转发量', '视频上传时间']]  
242 df2.loc[df.评论数 == 0]  #评论数是0的数据
243 df2.loc[df.播放量 == df['播放量'].max()]  # 播放量最高的视频
244 df2.loc[df.弹幕数 == df['弹幕数'].max()]  # 弹幕数最高的视频
245 df2.loc[df.投币量 == df['投币量'].max()]  # 投币量最高的视频
246 df2.loc[df.点赞数 == df['点赞数'].max()]  # 点赞数最高的视频
247 df2.loc[df.收藏量 == df[ '收藏量'].max()]  # 收藏量最高的视频
248 df2.loc[df.评论数 == df['评论数'].max()]  # 评论数最高的视频
249 df2.loc[df.转发量 == df[ '转发量'].max()]  # 转发量最高的视频
250 df2.loc[df.投币量 == df['投币量'].min()]  # 投币量最小的视频
251 df2.loc[df.点赞数 == df[ '点赞数'].min()]  # 点赞数最小的视频
252 df2.loc[df.收藏量 == df['收藏量'].min()]  # 收藏量最小的视频
253 df2.loc[df.播放量 == df['播放量'].min()]  # 播放量最小的视频
254 df2.loc[df.弹幕数 == df['弹幕数'].min()]  # 弹幕数最小的视频
255 df2.loc[df.评论数 == df['评论数'].min()]  # 评论数最小的视频
256 df2.loc[df.转发量 == df['转发量'].min()]  # 转发量最小的视频
257 df2.nlargest(n=10, columns='播放量')  # 播放量TOP10的视频
258 df2.nsmallest(n=5, columns='转发量')  # 转发量倒数5的视频
259 # 查看spearman相关性(得出结论:收藏量&投币量,相关性最大,0.98)
260 df2.corr(method='spearman')
261 #折线图
262 import pandas as pd
263 import matplotlib.pyplot as plt
264 import xlwings as xw
265 df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
266 plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文标签  # 指定默认字体
267 plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
268 df2.plot(x='视频上传时间', y=['弹幕数', '点赞数', '投币量', '收藏量', '评论数', '转发量'])
269 #老番茄视频播放量趋势图
270 import pandas as pd
271 import matplotlib.pyplot as plt
272 import xlwings as xw
273 df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
274 figure = plt.figure(figsize=(10,4))
275 plt.rcParams['font.sans-serif']=['SimHei']
276 plt.rcParams['axes.unicode_minus'] = False 
277 # X轴和Y轴数据标签
278 x = df['视频上传时间']
279 y = df['播放量']
280 plt.grid()
281 plt.plot(x, y, color = 'c', linewidth = '2', linestyle = 'solid')      # 制作折线图
282 plt.title(label = '老番茄视频播放量趋势图', fontdict = {'color' : 'black', 'size' : 20}, loc = 'center')    # 添加并设置图表标题
283 #老番茄视频投币量散点图
284 import pandas as pd
285 import matplotlib.pyplot as plt
286 plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
287 #坐标轴负号的处理
288 plt.rcParams['axes.unicode_minus']=False
289 figure = plt.figure(figsize=(10,4))
290 # 读取数据
291 df = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
292 # 绘制单条折线图
293 plt.scatter(df.视频上传时间, # x轴数据
294          df.投币量, # y轴数据
295          s=100,
296         color="plum") 
297 #对于X轴,只显示x中各个数对应的刻度值
298 plt.xticks(fontsize=11 )  #改变x轴文字值的文字大小
299 # 添加y轴标签
300 plt.xlabel('视频上传时间')
301 plt.ylabel('投币量')
302 # 添加图形标题
303 plt.title('老番茄视频投币量',size=16)
304 # 显示图形
305 plt.grid()
306 plt.show()
307 #老番茄视频年播放量
308 import matplotlib.pyplot as plt  
309 date = pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
310 plt.rcParams['font.sans-serif'] = ['SimHei']   #解决中文显示问题
311 plt.rcParams['axes.unicode_minus'] = False    # 解决中文显示问题
312 date = date.set_index('视频上传时间')                 #把日期列设为索引
313 date.index = pd.to_datetime(date.index)       #把索引转为时间格式
314 result = date[['播放量']].groupby(date.index.year).sum()  
315 plt.pie(result['播放量'], labels=result.index, autopct='%3.1f%%', textprops={ 'size':10, 'weight':'bold'})  #设置显示字体颜色、尺寸、加粗
316 plt.title('老番茄每年视频播放量', c='black')          
317 plt.show()
318 #老番茄视频分区量
319 from matplotlib import pyplot as plt
320 from matplotlib import font_manager
321 a = ['游戏', '未知分区', '生活', '音乐 ', '动物圈', '影视','娱乐'] 
322 b = [274,130,51,20,1,1,1] 
323 # 设置图像大小
324 plt.figure(figsize=(11,5),dpi=80) 
325 # 绘制条形图
326 plt.barh(range(len(a)), b, height=0.3, color='orange')  # 区别于竖的条形图 不能使用width 
327 # 设置字符串到X轴
328 plt.yticks(range(len(a)), a, fontproperties=my_font) 
329 plt.grid(alpha=0.3)
330 # 显示图形
331 plt.title('老番茄视频分区量',size=15, c='black')          
332 plt.show()
333 #不同视频的评论数与收藏量之间的关系
334 import pandas as pd
335 import matplotlib.pyplot as plt
336 import plotly as py
337 import plotly.graph_objs as go
338 import numpy as np
339 plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
340 #坐标轴负号的处理
341 plt.rcParams['axes.unicode_minus']=False
342 figure = plt.figure(figsize=(10,4))
343 # 读取数据
344 data= pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
345 #展示不同种类商品Category的售价Sales和利润Profit的关系
346 colors=['#393E46','#2BCDC2','#F66095']
347 labels=data['视频分区'].drop_duplicates(inplace=False)#获取种类数
348 X=[]
349 Y=[]
350 for i in labels:
351     X.append(data[data['视频分区']==i]['评论数'])
352     Y.append(data[data['视频分区']==i]['收藏量'])             
353 fig=go.Figure()
354 py.offline.init_notebook_mode()
355 for s,p,c,l in zip(X,Y,colors,labels):
356     fig.add_trace(go.Scatter(
357         x=s,
358         y=p,
359         mode='markers',
360         marker_color=c,
361         name=l
362     ))    
363 fig.update_layout(
364     title='不同视频的评论数与收藏量之间的关系',
365     
366     xaxis=dict(title='评论数'),
367     yaxis=dict(title='收藏量'),)
368 py.offline.iplot(fig)
369 #相关性
370 import pandas as pd
371 import seaborn as sns
372 import matplotlib.pyplot as plt
373 # 读取数据
374 tips= pd.read_excel('laofanqie_B站视频数据.xlsx', parse_dates=['视频上传时间', '实时爬取时间'])  # 读取excel数据
375 plt.grid(alpha=0.3)
376 sns.regplot(x='收藏量',y='转发量',color='g', data=tips)
377 sns.lmplot(x='播放量',y='投币量',  data=tips )

 五、总结

1.结论

  (1)2013年至2023年期间,老番茄的视频内容多以游戏为主,属于游戏区。

  (2)查看spearman相关,得出:收藏量&投币量,相关性最大,为0.98。

  (3)老番茄是从2019年开始获得流量,不论是视频发行量还是‘播放量’,'弹幕数', '点赞数', '投币量', '收藏量', '评论数', '转发量'等都开始呈现上升趋势。

  (4)2020年老番茄的视频播放量达到顶峰,究其原因是因为大家宅在家里,用户群体就会进一步体会到线上娱乐的乐趣,总的来说,离不开疫情之下线上娱乐获得的新突破。

    基本达到预期目标,但还存在不足。

2.反思

  在学习爬虫编写的过程中,我对一些搜索引擎的工作原理有了更深层次的理解,同时获取到了更多的数据源,对其行数据分析和可视化,更直观的看到事物之间的联系,得到更多的价值;而且爬出来的数据及时性和精确度很高。但是对于网站的一些反爬机制和js解密,我是在参考了别人文章后,才使得问题得以解决。今后还需要不断地学习,提高自己的爬虫技术和数据分析的能力。