一、选题背景
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解密,我是在参考了别人文章后,才使得问题得以解决。今后还需要不断地学习,提高自己的爬虫技术和数据分析的能力。
标签:count,视频,课程设计,plt,Python,list,up,df,import From: https://www.cnblogs.com/parasite-i/p/17456686.html