大家好,给大家分享一下python期末大作业代码200行带批注,很多人还不知道这一点。下面详细解释一下。现在让我们来看看!
【超详细指北】python大作业!
这是笔者最近写python大作业时写的一个实现过程笔记,也就是基本上可以说是本人从0开始上手的一个python练习。程序和本文档从 4.29-5.15日 总共历时17天快码论文。包含了大部分代码内容。
一、获取数据
(1)user-agent和cookie
user-agent
Cookie:
buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; bp_article_offset_154100711=649151673891029000; SESSDATA=fbf8b924%2C1666235070%2C5c5a7%2A41; bili_jct=2f4e142aa58387a4ba58d6610a138881; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=7m78ki9o; CURRENT_QUALITY=0; fingerprint=a0d6414c1242c8cb9c9f7b4f70d4d671; b_ut=5; CURRENT_FNVAL=4048; bsource=search_baidu; b_lsid=AB9536C2_1807AC90CF7; _dfcaptcha=0f5ba157af594817171639f2996e0b43; PVID=1; innersign=1; buvid_fp=a0d6414c1242c8cb9c9f7b4f70d4d671; bp_video_offset_154100711=654934739730300900; fingerprint3=5ad9983134e17174abef4db7b440a5ab
(2)commentData类
该类是获取某一视频的所有评论信息,包括一级评论、二级评论,获取了评论用户的基本信息和评论内容。在该类中,设置headers和Cookie防止反爬,此外还有一个fake_useragent库也可以防止反爬虫,在这里没有使用该库。
写在前面
首先我们来分析一级评论:
一级评论:
根据浏览器f12自带的调试中,我们查找存放评论内容的api。这里给出三个不同视频的评论接口:
三个网页的一级评论api及来源
https://api.bilibili.com/x/v2/reply/main?callback=jQuery17208590914915452643_1651207947683&jsonp=jsonp&next=0&type=1&oid=34491719&mode=3&plat=1&_=1651207949390
【https://www.bilibili.com/video/BV1ot411R7SM?spm_id_from=333.999.0.0】
https://api.bilibili.com/x/v2/reply/main?callback=jQuery33102399794496926384_1651209840924&jsonp=jsonp&next=0&type=1&oid=768445836&mode=3&plat=1&_=1651209840925
【https://www.bilibili.com/video/BV11r4y1J7cH?spm_id_from=333.999.0.0】
https://api.bilibili.com/x/v2/reply/main?callback=jQuery17203622673329462698_1651210156500&jsonp=jsonp&next=0&type=1&oid=721394418&mode=3&plat=1&_=1651210156936
【https://www.bilibili.com/video/BV1fQ4y1q7SB/?spm_id_from=333.788.recommend_more_video.16】
-
https://api.bilibili.com/x/v2/reply/main?callback=jQuery17208590914915452643_1651207947683&jsonp=jsonp&next=0&type=1&oid=34491719&mode=3&plat=1&_=1651207949390
-
https://api.bilibili.com/x/v2/reply/main?callback=jQuery33102399794496926384_1651209840924&jsonp=jsonp&next=0&type=1&oid=768445836&mode=3&plat=1&_=1651209840925
-
https://api.bilibili.com/x/v2/reply/main?callback=jQuery17203622673329462698_1651210156500&jsonp=jsonp&next=0&type=1&oid=721394418&mode=3&plat=1&_=1651210156936
可见在加粗部分是不同的
第一个api中:
https://api.bilibili.com/x/v2/reply/main?callback=jQuery17208590914915452643_1651207947683&jsonp=jsonp&next=0&type=1&oid=34491719&mode=3&plat=1&_=1651207949390
删除第一个和最后一个参数(因为我们不需要js请求,最后一个参数也没有什么影响),得到
一级评论:https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next=0&type=1&oid=34491719&mode=3&plat=1
- next:翻页
- oid:视频编号(aid)
- mode:1、2表示按热度、时间排序; 0、3表示按热度排序,并显示评论和用户信息
二级评论:
二级评论也就是视频评论的评论,也就是有人回复评论时的评论。
https://api.bilibili.com/x/v2/reply/reply?callback=jQuery17202729032535004876_1651213886637&jsonp=jsonp&pn=1&type=1&oid=34491719&ps=10&root=1426909940&_=1651213945276
同上删除首尾参数后得到:
二级评论:https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn=1&type=1&oid=34491719&ps=10&root=1426909940
- pn:翻页
- oid:视频oid
- ps: 单页显示数量(最大为20)
- root:楼主的回复的rpid
视频的oid可通过视频BV号获取,rpid可以通过一级评论获取(随后我们进行获取)
最后一页评论:
我们自己根据一级评论的api,手动查找到最后一页评论,发现当没有评论时,data下的replies为null,机当前api中next的参数值为最后一页的页码,如果有评论时replies不为空。
因此我们在爬取所有评论时可以将replies是否为null作为循环退出条件。
https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&type=1&oid=34491719&mode=0&plat=1&next=28
1.构造函数init
初始化基本内容:
- mid:up主的uid,传入参数
- name:up主的姓名,传输参数
- BV:爬取视频的BV号,传入参数
- mode:排序方式(这里笔者所写的类中其实一直默认的0,也就是默认排序,其他自测):1、2表示按热度、时间排序; 0、3表示按热度排序,并显示评论和用户信息,传入参数
- header:请求时的header,默认值,可自行更改
- cookies:设置header和cookie防止网站反爬,传入参数
- page:评论页数,通过爬取时根据api返回的replies是否为空进行判断是否爬取完毕,对self.page进行累加,来达到计算总共评论的总数量的目的。
- BVName:视频名称,!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- homeUrl:api的网址开头部分
- oid:视频的id,通过**oid_get(self, BV)**函数返回oid值。
- replyUrl:一级评论的api
- rreplyUrl:二级评论的api
- q:创建的队列,将content_get方法返回爬取内容并存入队列,通过csv_writeIn方法从q队列中进行取出存取,方便多线程工作,是一个生产着消费者模式。
- count:当前评论楼数,指定主楼数,区别是评论还是评论的评论
因为在获取BVName和oid时,需要homeUrl,所以我们讲homeUrl放置在BVName和oid之前
def __init__(self, mid, name, BV, mode, cookies):
self.mid = mid #up主的uid
self.name = name #up主的账号名称
self.BV = BV # BV:视频id号
self.mode = mode # mode:1、2表示按热度、时间排序; 0、3表示按热度排序,并显示评论和用户信息
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50'
}
self.cookies = cookies # 设置headers和Cookie防止反爬,还有一个fake_useragent库也可以用
self.page = 0 # page:评论页数,出最后一页每页20条
self.homeUrl = "https://www.bilibili.com/video/"
self.BVName = self.BVName_get(self.BV)
self.oid = self.oid_get(self.BV)
#一级评论和二级评论
self.replyUrl="https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&type=1&oid={oid}&mode={mode}&plat=1&next=".format(oid=self.oid,mode=mode)#next=0
self.rreplyUrl = "https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&type=1&oid={oid}&ps=20&root={root}&pn=".format(oid=self.oid, root="{root}")#pn=1
self.q = queue.Queue() # 用来存放爬取的数据,通过队列可以按顺序,使用多线程存入数据库或csv文件中
# 这里我们用到了队列,好处在于,可以用多线程边爬边存,按顺序先进先出
self.count = 1 # count变量指定主楼数,区别是评论还是评论的评论
2.获取视频oid和获取视频名称方法
方法一:通过正则从response中选择以字符串aid开头的值并将其进行返回。
方法二:通过BeautifulSoup4类获取视频名称,获取含有视频名称的标签,从而通过自带大string方法获取名称
(78条消息) Python中BeautifulSoup库的用法_阎_松的博客-CSDN博客_beautifulsoup库的作用
# 获取视频 oid
def oid_get(self, BV):
# 请求视频页面
response = requests.get(url=self.homeUrl + BV).text
# 用正则表达式 通过视频 bv 号获取 oid
oid = re.findall("\"aid\":([0-9]*),", response)[0]#寻找以字符串aid开头的值
print("oid:" + oid)
return oid
def BVName_get(self,BV):
# 请求视频页面
response = requests.get(url=self.homeUrl + BV).text
soup = BeautifulSoup(response, "html.parser", from_encoding="utf-8")
nameResultSet = soup.find_all(attrs={'class': 'tit'}) # [<span class="tit">城市与山里的差距,真正体验过,我来告诉你!</span>]
result = nameResultSet[0].string #城市与山里的差距,真正体验过,我来告诉你!
print("BVName:" + result)
return result
3.评论内容获取
首先我们请求函数传递url,page(最大页面数,最终代码会删掉这个page参数,因为通过判断replies是否为空来获取所有页码的评论,就不需要指定获取页码的内容了),通过requests库请求数据,需要的数据都在data->replies里面,将该内容用一个列表保存。
评论内容详细分析:其中是该视频的主要评论(也就是一级评论),其下有部分回复该评论的子评论,详细内容包含了评论的id、视频的id、时间戳、评论内容等等,其中主要信息为
- rpid:评论id
- oid:该视频的oid
- mid:账户的uid
- rcount :回复数
- ctime:时间戳
- like:点赞数
- member–>sign:用户标签,即用户的个性签名
- content–>message:评论内容
- replies:评论列表
- replies–>rpid:子评论的id
- replies–>level :用户等级
获取一级评论的数据:
#获取当前页面的评论
def content_get(self, url, page):
now = 0 # 当前页面
while now<=page:
print("page : <{now}>/<{page}>".format(now=now, page=page))
response = requests.get(url=url+str(now), cookies=self.cookies, headers=self.headers, timeout=10).json() # 把response解析为json格式,通过字典获取
replies = response['data']['replies'] # 评论数据在data->replies 里面,每页有 20 条
now += 1
for reply in replies: # 遍历获取每一条,用reply_clean函数提取数据
line = self.reply_clean(reply)
self.count += 1
因为一二级评论格式基本一致,所以将上面获取一级评论的数据的代码修改一下,增加复用性。
这里新增了level_1来判断是否是一级评论,如果是则进行请求下一级,否则不请求。
此外,这里将page参数进行了删除,通过之前分析的,通过判断replies是否为空来判断是否到达评论的最后一页。
#数据获取:获取当前页面的评论
def content_get(self, url, level_1=True):
# level_1判断是否为一级评论。如果为二级评论,则不请求下一级评论(评论的评论)
now = 1
while True:
if level_1:
print("page : <{now}>".format(now=now))
response = requests.get(url=url + str(now), cookies=self.cookies, headers=self.headers).json()
print(url + str(now))
replies = response['data']['replies'] # 评论数据在data->replies 里面,一共有 20 条
if (replies == None)and(now == 1):
#因为当next==0时和next==1时的评论内容是一样的,所以单独写出来一种情况:该视频没有任何评论
self.page=0
print("该页没有评论......")
return
elif replies == None:
self.page = now - 1
print("评论信息获取完成......")
return
elif replies != None:
now += 1
for reply in replies:
# 一级评论则去请求下一级评论
if level_1:
line = self.reply_clean(reply, self.count)
self.count += 1
else:
line = self.reply_clean(reply)
self.q.put(line)
# 这儿我们可以筛选一下,如果有二级评论,调用函数请求二级评论
if level_1 == True and line[-2] != 0:#如果是一级评论且 回复数 不为零 则去请求二级评论
self.content_get(url=self.rreplyUrl.format(root=str(line[-1])), level_1=False) # 递归获取二级评论
4.数据清洗
因为replies下的数据过多而且繁杂,而我们不需要这么多的数据,所以我们进行一下数据的“清洗”,只返回我们需要的数据信息。
将评论时间的时间戳通过time库转换成正常格式。通过之前分析的含义,将需要的信息保存并且返回为列表类型。为了使程序更具用复用性,这里兼容清洗二级评论数据,增加count参数,默认为false,表示是否是二级评论。
如果是二级评论,则返回数据第一个为"回复",否则为楼号。
二级评论没有回复数rcount,三级评论都显示为 回复xxx @谁谁谁
# 数据清洗,将我们需要的数据进行筛选返回
def reply_clean(self, reply, count=False):
# 这个函数可以爬一级评论也能爬二级评论
# count 参数,看看是不是二级评论。
name = reply['member']['uname'] # 名字
sex = reply['member']['sex'] # 性别:男/女/保密
mid = reply['member']['mid'] # 帐号的uid
sign = reply['member']['sign'] # 个性签名
rpid = reply['rpid'] # 评论的id,爬二级评论要用到
rcount = reply['rcount'] # 回复数
level = reply['member']['level_info']['current_level'] # 用户等级
like = reply['like'] # 点赞数
content = reply['content']['message'].replace("\n", "") # 评论内容
t = reply['ctime'] #时间戳
timeArray = time.localtime(t)
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 评论时间,时间戳转为标准时间格式,2022-05-05 19:15:14
# 如果是二级评论,则返回数据第一个为"回复",否则为楼号
# 二级评论没有回复数rcount,三级评论都显示为 回复xxx @谁谁谁
if count:
return [count, name, sex, level, mid, sign, otherStyleTime, content, like, rcount, rpid]
else:
return ["回复", name, sex, level, mid, sign, otherStyleTime, content, like, ' ', rpid]
5.存储评论内容
将信息存放在dirname的文件夹下,在该文件夹下细分为up主自己的文件夹和视频的文件夹。每次写入一行数据,即将line的列表信息进行写入。不断从队列q中取出内容并保存。最后恢复到开始的工作目录。
完整代码:
#csv文件保存数据
def csv_writeIn(self, mid, name, BV, BVName ):
dirname = '视频评论信息'
begin = os.getcwd() # 保存开始文件工作路径
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
fileName = str(mid) + "-" + str(name) # up主的文件夹:uid-name
if not os.path.isdir(fileName):
os.mkdir(fileName)
os.chdir(fileName)
fileName = str(BV) + "-" + str(BVName) # BV视频的文件夹:BV-BVname
if not os.path.isdir(fileName):
os.mkdir(fileName)
os.chdir(fileName)
file = open("bilibili评论_" + BV + ".csv", "w", encoding="utf-8", newline="")
f = csv.writer(file)
line1 = ['楼层', '姓名', '性别', '等级', 'uid', '个性签名', '评论时间', '评论内容', '点赞数', '回复数', 'rpid']
f.writerow(line1)
file.flush()
while True:
try:
line = self.q.get(timeout=10)
except:
break
f.writerow(line)
file.flush()
file.close()
os.chdir(begin) # 恢复文件工作路径
6.commentData类代码
import os
import time
import requests
import re
import queue
import csv
from threading import Thread
from bs4 import BeautifulSoup
#该类实现爬取保存一个视频的评论信息。
class commentData:
#构造函数__init__,设置基础信息
def __init__(self, mid, name, BV, mode, cookies):
self.mid = mid #up主的uid
self.name = name #up主的账号名称
self.BV = BV # BV:视频id号
self.mode = mode # mode:1、2表示按热度、时间排序; 0、3表示按热度排序,并显示评论和用户信息
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50'
}
self.cookies = cookies # 设置headers和Cookie防止反爬,还有一个fake_useragent库也可以用
self.page = 0 # page:评论页数,出最后一页每页20条
self.homeUrl = "https://www.bilibili.com/video/"
self.BVName = self.BVName_get(self.BV)
self.oid = self.oid_get(self.BV)
#一级评论和二级评论
self.replyUrl="https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&type=1&oid={oid}&mode={mode}&plat=1&next=".format(oid=self.oid,mode=mode)#next=0
self.rreplyUrl = "https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&type=1&oid={oid}&ps=20&root={root}&pn=".format(oid=self.oid, root="{root}")#pn=1
self.q = queue.Queue() # 用来存放爬取的数据,通过队列可以按顺序,使用多线程存入数据库或csv文件中
# 这里我们用到了队列,好处在于,可以用多线程边爬边存,按顺序先进先出
self.count = 1 # count变量指定主楼数,区别是评论还是评论的评论
# 获取视频 oid
def oid_get(self, BV):
# 请求视频页面
response = requests.get(url=self.homeUrl + BV).text
# 用正则表达式 通过视频 bv 号获取 oid
oid = re.findall("\"aid\":([0-9]*),", response)[0]#寻找以字符串aid开头的值
print("oid:" + oid)
return oid
def BVName_get(self,BV):
# 请求视频页面
response = requests.get(url=self.homeUrl + BV).text
soup = BeautifulSoup(response, "html.parser", from_encoding="utf-8")
nameResultSet = soup.find_all(attrs={'class': 'tit'}) # [<span class="tit">城市与山里的差距,真正体验过,我来告诉你!</span>]
BVName = nameResultSet[0].string #城市与山里的差距,真正体验过,我来告诉你!
print("BVName:" + BVName)
return BVName
#数据获取:获取当前页面的评论
def content_get(self, url, level_1=True):
# level_1判断是否为一级评论。如果为二级评论,则不请求下一级评论(评论的评论)
now = 1
while True:
if level_1:
print("page : <{now}>".format(now=now))
response = requests.get(url=url + str(now), cookies=self.cookies, headers=self.headers).json()
print(url + str(now))
replies = response['data']['replies'] # 评论数据在data->replies 里面,一共有 20 条
if (replies == None)and(now == 1):
#因为当next==0时和next==1时的评论内容是一样的,所以单独写出来一种情况:该视频没有任何评论
self.page=0
print("该页没有评论......")
return
elif replies == None:
self.page = now - 1
print("评论信息获取完成......")
return
elif replies != None:
now += 1
for reply in replies:
# 一级评论则去请求下一级评论
if level_1:
line = self.reply_clean(reply, self.count)
self.count += 1
else:
line = self.reply_clean(reply)
self.q.put(line)
# 这儿我们可以筛选一下,如果有二级评论,调用函数请求二级评论
if level_1 == True and line[-2] != 0:#如果是一级评论且 回复数 不为零 则去请求二级评论
self.content_get(url=self.rreplyUrl.format(root=str(line[-1])), level_1=False) # 递归获取二级评论
# 数据清洗,将我们需要的数据进行筛选返回
def reply_clean(self, reply, count=False):
# 这个函数可以爬一级评论也能爬二级评论
# count 参数,看看是不是二级评论。
name = reply['member']['uname'] # 名字
sex = reply['member']['sex'] # 性别:男/女/保密
mid = reply['member']['mid'] # 帐号的uid
sign = reply['member']['sign'] # 个性签名
rpid = reply['rpid'] # 评论的id,爬二级评论要用到
rcount = reply['rcount'] # 回复数
level = reply['member']['level_info']['current_level'] # 用户等级
like = reply['like'] # 点赞数
content = reply['content']['message'].replace("\n", "") # 评论内容
t = reply['ctime'] #时间戳
timeArray = time.localtime(t)
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 评论时间,时间戳转为标准时间格式,2022-05-05 19:15:14
# 如果是二级评论,则返回数据第一个为"回复",否则为楼号
# 二级评论没有回复数rcount,三级评论都显示为 回复xxx @谁谁谁
if count:
return [count, name, sex, level, mid, sign, otherStyleTime, content, like, rcount, rpid]
else:
return ["回复", name, sex, level, mid, sign, otherStyleTime, content, like, ' ', rpid]
#csv文件保存数据
def csv_writeIn(self, mid, name, BV, BVName ):
dirname = '视频评论信息'
begin = os.getcwd() # 保存开始文件工作路径
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
fileName = str(mid) + "-" + str(name) # up主的文件夹:uid-name
if not os.path.isdir(fileName):
os.mkdir(fileName)
os.chdir(fileName)
fileName = str(BV) + "-" + str(BVName) # BV视频的文件夹:BV-BVname
if not os.path.isdir(fileName):
os.mkdir(fileName)
os.chdir(fileName)
file = open("bilibili评论_" + BV + ".csv", "w", encoding="utf-8", newline="")
f = csv.writer(file)
line1 = ['楼层', '姓名', '性别', '等级', 'uid', '个性签名', '评论时间', '评论内容', '点赞数', '回复数', 'rpid']
f.writerow(line1)
file.flush()
while True:
try:
line = self.q.get(timeout=10)
except:
break
f.writerow(line)
file.flush()
file.close()
os.chdir(begin) # 恢复文件工作路径
def main(self):
#创建队列,方便多线程程序进行
T = []
T.append(Thread(target=self.content_get, args=(self.replyUrl, )))
T.append(Thread(target=self.csv_writeIn, args=(self.mid,self.name,self.BV,self.BVName)))
print("开始爬取...")
for t in T:
t.start()
for t in T:
t.join()
if __name__ == '__main__':
cookie = "buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; bp_article_offset_154100711=649151673891029000; CURRENT_QUALITY=0; b_ut=5; fingerprint3=5ad9983134e17174abef4db7b440a5ab; CURRENT_FNVAL=4048; PVID=1; SESSDATA=da01081b%2C1667737162%2C066fd%2A51; bili_jct=e257a396d8d258b042d32a8aa9494f9e; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=ldg719nz; fingerprint=19c41c196550f8268e8c94867b19f6d8; buvid_fp=19c41c196550f8268e8c94867b19f6d8; innersign=1; bp_video_offset_154100711=659031889417338900; b_lsid=2A8FAA105_180B3D96C31"
cookies = {}
for c in cookie.split(";"):
b = c.split("=")
cookies[b[0]] = b[1]
commentData = commentData(382193067, '巫托邦', 'BV1344y1u7K8', 0, cookies)
commentData.main()
"""
==========================
@auther:JingDe
@Date:2022/4/29 14:53
@email:
@IDE:PyCharm
==========================
"""
运行该类,结果示意图为:
(3)upData_.py文件
写在前面,因为需要每隔一段时间爬取一次up主的信息,因此我们最好把程序一直放置后台运行。这里,笔者是把需要一直运行的程序放置在安装好Linux系统的树莓派中,让树莓派24h不间断运行,每隔一段时间进行爬取一次数据。这里是获取up主的基本数据,我们把upData类和timer类写在一个文件中,运行该文件,通过24h每隔一小时爬取一次up主的信息并保存。
upData类
这个类是用来获取up主粉丝数量基本相关信息。
1.up主粉丝数,关注数,uid
- follower: 粉丝数(1253342)
- following: up主关注数(6)
- mid: up主uid(382193067)
我们在up主的主页中,可以看到粉丝数量等基本信息。通过f12查找相关api中预览内容,发现粉丝数,关注数,mid的接口如下:
https://api.bilibili.com/x/relation/stat?vmid=382193067&jsonp=jsonp
去掉mid(up主的uid)后即为接口:
https://api.bilibili.com/x/relation/stat?vmid=######&jsonp=jsonp
2.点赞数,播放数,阅读数
- archive:view:播放数 (104483845)
- article:view:阅读数(0)
- likes: 点赞数:(10817191)
同上,我们预览相关接口的内容可以找到点赞数,播放数,阅读数的api接口如下:
https://api.bilibili.com/x/space/upstat?mid=382193067&jsonp=jsonp
去掉mid后即为接口:
https://api.bilibili.com/x/space/upstat?mid=#####&jsonp=jsonp
得到获得的两个api:
-
up主粉丝数,关注数,uid
https://api.bilibili.com/x/relation/stat?vmid=######&jsonp=jsonp
-
点赞数,播放数,阅读数
https://api.bilibili.com/x/space/upstat?mid=#####&jsonp=jsonp
创建好工作文件夹,即准备把up主信息存放的目的文件。
在构造函数init里面进行 初始化headers,cookie。
通过up主的uid和name进行数据获取。定义好之前分析的链接,进行请求,将请求结果保存在response1和response2中,然后选择我们需要的信息进行返回一个数据列表。
获取到数据后,我们讲其存放到本地,保存为csv格式。首先创建一个up主信息文件夹,然后在该文件夹下寻找需存放up主的文件夹,如果存在,则在其后进行追加即可,若不存在,则创建再添加。保存成功后讲其信息进行控制台输出
3.updata类为:
import os
import time
import requests
import csv
class upData:
#构造函数__init__,设置基础信息
def __init__(self, cookies):
# 设置headers和Cookie防止反爬,还有一个fake_useragent库也可以用
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50'
}
self.cookies = cookies
#数据获取:
def content_get(self, mid, name):
# mid:up主的uid
# name:up主的名称
mid = str(mid)
name = str(name)
url1 = "https://api.bilibili.com/x/relation/stat?vmid=" + mid + "&jsonp=jsonp" # up主粉丝数,关注数,阅读数
url2 = "https://api.bilibili.com/x/space/upstat?mid=" + mid + "&jsonp=jsonp" # 点赞数,播放数,阅读数
response1 = requests.get(url=url1, cookies=self.cookies, headers=self.headers).json()#{'code': 0, 'message': '0', 'ttl': 1, 'data': {'mid': 382193067, 'following': 6, 'whisper': 0, 'black': 0, 'follower': 1253218}}
response2 = requests.get(url=url2, cookies=self.cookies, headers=self.headers).json()
for i in response1:
data1 = response1['data']
for j in response2:
data2 = response2['data']
uid = mid #up的uid
upName = name #up的名称
follower = data1['follower'] #粉丝数
following = data1['following'] #关注数
likes = data2['likes'] #点赞数
archive = data2['archive']['view'] #播放数
article = data2['article']['view'] #阅读数
ctime=time.time() #时间戳
data=[ctime, uid, upName, follower, following, likes, archive, article]
return data
#csv文件保存数据
def csv_writeIn(self, mid, name):
dirname = 'up主信息'
flag = False#标志是否写表头,默认不写(默认没有该up主的文件夹,即没有他的信息)
begin=os.getcwd()#保存开始文件工作路径
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
fileName = str(mid) + "-" + str(name) # up主的文件夹:uid+name
if not os.path.isdir(fileName):
flag = True
os.mkdir(fileName)
os.chdir(fileName)
file = open(fileName + ".csv", "a", encoding="utf-8", newline="")
f = csv.writer(file)
if(flag):
line1 = ['时间', 'uid帐号', 'up主名称', '粉丝数', '关注数', '点赞数', '播放数', '阅读数']
f.writerow(line1)
file.flush()
data=self.content_get(mid, name)
f.writerow(data)
file.flush()
file.close()
timeArray = time.localtime(data[0])
print(time.strftime("%Y-%m-%d %H:%M:%S", timeArray) + ":该up基本信息如下......")
print("uid:%s up主名称:%s 粉丝数:%d 关注数:%d 点赞数:%d 播放数:%d 阅读数:%d"%(data[1],data[2],data[3],data[4],data[5],data[6],data[7]))
print("保存完毕......")
os.chdir(begin)#恢复文件工作路径
def main(self, mid, name):
print("开始获取up主基本信息......")
self.csv_writeIn(mid, name)
# if __name__ == '__main__':
# cookie = "buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; bp_article_offset_154100711=649151673891029000; SESSDATA=fbf8b924%2C1666235070%2C5c5a7%2A41; bili_jct=2f4e142aa58387a4ba58d6610a138881; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=7m78ki9o; CURRENT_QUALITY=0; fingerprint=a0d6414c1242c8cb9c9f7b4f70d4d671; b_ut=5; CURRENT_FNVAL=4048; bsource=search_baidu; b_lsid=AB9536C2_1807AC90CF7; _dfcaptcha=0f5ba157af594817171639f2996e0b43; PVID=1; innersign=1; buvid_fp=a0d6414c1242c8cb9c9f7b4f70d4d671; bp_video_offset_154100711=654934739730300900; fingerprint3=5ad9983134e17174abef4db7b440a5ab"
# cookies = {}
# for c in cookie.split(";"):
# b = c.split("=")
# cookies[b[0]] = b[1] #b0和b1分别是cookie的关键字和值,也就是将cookie转换为字典类型
# bilibili = upData(cookies)
# bilibili.main(382193067,'巫托邦')
# bilibili.main(431313625, '小蓝和他的朋友日常号')
# bilibili.main(627888730, '星有野')
# bilibili.main(946974, '影视飓风')
# bilibili.main(163637592, '老师好我叫何同学')
MyTimer类
[(78条消息) Python:录记个做,写写便随_Ambitioner_c的博客-CSDN博客](https://blog.csdn.net/qq_41297934/article/details/105371870?ops_request_misc=&request_id=&biz_id=102&utm_term=Python 实现某个功能每隔一段时间被执行一次的功能方法&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-8-105371870.142v9pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187)
(78条消息) Python 实现某个功能每隔一段时间被执行一次的功能_独一无二的小个性的博客-CSDN博客_python 每隔一段时间
[(78条消息) Python实现定时任务的几种方法_从流域到海域的博客-CSDN博客_python定时任务的实现方式](https://blog.csdn.net/Solo95/article/details/122026111?ops_request_misc=&request_id=&biz_id=102&utm_term=Python 实现某个功能每隔一段时间被执行一次的功能方法&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-5-122026111.142v9pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187)
这是一个timer封装的定时器类,可以每隔一段时间自动执行。以下是需要注意的参数信息,和需要运行的方法。
tmr = MyTimer( start, 60*60, aa.hello, [ "owenliu", 18 ] )
#start 为当前时间,
#60*60 为代码循环周期(这里为 1h),
#aa.hello 为回调函数,
#["owenliu", 18] 为回调函数的参数
def hello(name, age):
print("[%s]\thello %s: %d\n" % (datetime.now().strftime("%Y%m%d %H:%M:%S"), name, age))
Mytimer类内容:
# -*- coding: utf-8 -*-
# ==================================================
# 对 Timer 做以下再封装的目的是:当某个功能需要每隔一段时间被
# 执行一次的时候,不需要在回调函数里对 Timer 做重新安装启动
# ==================================================
__author__ = 'liujiaxing'
from threading import Timer
from datetime import datetime
class MyTimer(object):
def __init__(self, start_time, interval, callback_proc, args=None, kwargs=None):
self.__timer = None
self.__start_time = start_time
self.__interval = interval
self.__callback_pro = callback_proc
self.__args = args if args is not None else []
self.__kwargs = kwargs if kwargs is not None else {}
def exec_callback(self, args=None, kwargs=None):
self.__callback_pro(*self.__args, **self.__kwargs)
self.__timer = Timer(self.__interval, self.exec_callback)
self.__timer.start()
def start(self):
interval = self.__interval - (datetime.now().timestamp() - self.__start_time.timestamp())
print(interval)
self.__timer = Timer(interval, self.exec_callback)
self.__timer.start()
def cancel(self):
self.__timer.cancel()
self.__timer = None
class AA:
@staticmethod
def hello(name, age):
print("[%s]\thello %s: %d\n" % (datetime.now().strftime("%Y%m%d %H:%M:%S"), name, age))
if __name__ == "__main__":
aa = AA()
start = datetime.now().replace(minute=3, second=0, microsecond=0)
tmr = MyTimer(start, 60 * 60, aa.hello, ["owenliu", 18])
tmr.start()
tmr.cancel()
修改循环时间,即MyTimer的第二个参数。
upData类和MyTimer类完成之后,我们讲两个类合并在一起,写到一个文件中并将其在树莓派进行“挂机”执行,值得注意的是,因为我们的cookie是写“死”的,事实上这个cookie每隔一段时间就会过期,需要更新这里给出下次参考链接来自适应更新cookie,笔者这里因为首要目标是完成python大作业,暂时没有完善这个功能,需要的朋友可自行研究。
(78条消息) python session保持cookie_python接口自动化测试八:更新Cookies、session保持会话_冷君聊大片的博客-CSDN博客
回到正题,我们将两个类合并在一起并且命名为auto_.py文件,其实把这两个分开也行,只不过笔者在树莓派运行的时候,因为环境的差异,自己写的模块在import的时候会有问题,因为时间原因,最简单直接的办法就是将这两个合并在一起,事实上我也是这样做的。
auto_.py的内容:
# encoding: utf-8
import os
import time
from threading import Timer
from datetime import datetime
import requests
import csv
class upData:
#构造函数__init__,设置基础信息
def __init__(self, cookies):
# 设置headers和Cookie防止反爬,还有一个fake_useragent库也可以用
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39'
}
self.cookies = cookies
#数据获取:
def content_get(self, mid, name):
# mid:up主的uid
# name:up主的名称
mid = str(mid)
name = str(name)
url1 = "https://api.bilibili.com/x/relation/stat?vmid=" + mid + "&jsonp=jsonp" # up主粉丝数,关注数,阅读数
url2 = "https://api.bilibili.com/x/space/upstat?mid=" + mid + "&jsonp=jsonp" # 点赞数,播放数,阅读数
response1 = requests.get(url=url1, cookies=self.cookies, headers=self.headers).json()#{'code': 0, 'message': '0', 'ttl': 1, 'data': {'mid': 382193067, 'following': 6, 'whisper': 0, 'black': 0, 'follower': 1253218}}
response2 = requests.get(url=url2, cookies=self.cookies, headers=self.headers).json()
for i in response1:
data1 = response1['data']
for j in response2:
data2 = response2['data']
uid = mid #up的uid
upName = name #up的名称
follower = data1['follower'] #粉丝数
following = data1['following'] #关注数
likes = data2['likes'] #点赞数
archive = data2['archive']['view'] #播放数
article = data2['article']['view'] #阅读数
ctime=time.time() #时间戳
data=[ctime, uid, upName, follower, following, likes, archive, article]
return data
#csv文件保存数据
def csv_writeIn(self, mid, name):
dirname = 'up主信息'
flag = False#标志是否写表头,默认不写(默认没有该up主的文件夹,即没有他的信息)
begin=os.getcwd()#保存开始文件工作路径
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
fileName = str(mid) + "-" + str(name) # up主的文件夹:uid+name
if not os.path.isdir(fileName):
flag = True
os.mkdir(fileName)
os.chdir(fileName)
file = open(fileName + ".csv", "a", encoding="utf-8", newline="")
f = csv.writer(file)
if(flag):
line1 = ['时间', 'uid帐号', 'up主名称', '粉丝数', '关注数', '点赞数', '播放数', '阅读数']
f.writerow(line1)
file.flush()
data=self.content_get(mid, name)
f.writerow(data)
file.flush()
file.close()
timeArray = time.localtime(data[0])
print(time.strftime("%Y-%m-%d %H:%M:%S", timeArray) + ":该up基本信息如下......")
print("uid:%s up主名称:%s 粉丝数:%d 关注数:%d 点赞数:%d 播放数:%d 阅读数:%d"%(data[1],data[2],data[3],data[4],data[5],data[6],data[7]))
print("保存完毕......")
os.chdir(begin)#恢复文件工作路径
def main(self, mid, name):
print("开始获取up主基本信息......")
self.csv_writeIn(mid, name)
class MyTimer(object):
def __init__(self, start_time, interval, callback_proc, args=None, kwargs=None):
self.__timer = None
self.__start_time = start_time
self.__interval = interval
self.__callback_pro = callback_proc
self.__args = args if args is not None else []
self.__kwargs = kwargs if kwargs is not None else {}
def exec_callback(self, args=None, kwargs=None):
self.__callback_pro(*self.__args, **self.__kwargs)
self.__timer = Timer(self.__interval, self.exec_callback)
self.__timer.start()
def start(self):
interval = self.__interval - (datetime.now().timestamp() - self.__start_time.timestamp())
# print(interval)
self.__timer = Timer(interval, self.exec_callback)
self.__timer.start()
def cancel(self):
self.__timer.cancel()
self.__timer = None
if __name__ == "__main__":
cookie = "buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; bp_article_offset_154100711=649151673891029000; CURRENT_QUALITY=0; b_ut=5; fingerprint3=5ad9983134e17174abef4db7b440a5ab; CURRENT_FNVAL=4048; bp_video_offset_154100711=658555976981413900; PVID=1; fingerprint=cade2120d2a48e2de0dadfee319a247e; buvid_fp=19c41c196550f8268e8c94867b19f6d8; SESSDATA=da01081b%2C1667737162%2C066fd%2A51; bili_jct=e257a396d8d258b042d32a8aa9494f9e; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=ldg719nz; b_lsid=F8833C51_180AE162A19"
cookies = {}
for c in cookie.split(";"):
b = c.split("=")
cookies[b[0]] = b[1] # b0和b1分别是cookie的关键字和值,也就是将cookie转换为字典类型
bilibili1 = upData(cookies)
start = datetime.now().replace(minute=3, second=0, microsecond=0)
# start 为当前时间,
# 60*60 为代码循环周期(这里为 1h),
# bilibili1.main 为回调函数,
# ["owenliu", 18] 为回调函数的参数
tmr1 = MyTimer(start, 60 * 60, bilibili1.main, [382193067, '巫托邦'])
# tmr2 = MyTimer(start, 20 * 1, bilibili2.main, [431313625, '小蓝和他的朋友日常号'])
# tmr3 = MyTimer(start, 20 * 1, bilibili3.main, [627888730, '星有野'])
# tmr4 = MyTimer(start, 20 * 1, bilibili4.main, [946974, '影视飓风'])
# tmr5 = MyTimer(start, 20 * 1, bilibili5.main, [163637592, '老师好我叫何同学'])
tmr1.start()
flag = input("\n输入\"exit\"停止执行......\n")
if flag == 'exit':
tmr1.cancel()
Linux环境中该auto_.py文件运行结果如下:
(4)videoDataDetection_.py文件
视频总播放数和历史累计弹幕数
查看网页元素和代码,发现播放数就在class为"video-data"的标签下,我们通过BeautifulSoup库对网页进行解析,通过find_all对class为"video-data"进行筛选,找到该标签,并返回一个集合
#[
# <div class="video-data">
# <span class="view" title="总播放数1383293">138.3万播放 · </span>
# <span class="dm" title="历史累计弹幕数3877">总弹幕数3877</span>
# <span>2022-05-04 11:30:00</span>
# <!-- -->
# </div>
# ]
该集合元素个数为1,且该返回类型为**<class ‘bs4.element.ResultSet’>,该集合元素第一个的类型为bs4.element.ResultSet[0]**
在debug中通过查看Total_playback_barrage_Set[0]的内容,发现所包含的标签就在contents属性中,而且该属性是一个list列表,在该列表下前两个元素分别是总播放数和总弹幕数,在第一个孩子Tag中,它的attrs属性值就包含了我们需要播放数。因此分别通过选择这两个值便可得到播放总数和历史弹幕总数。
获取到attrs的值后再通过字符串选择,保留数值即可。
#视频名称
responsePrototype = requests.get(url=self.homeUrl + BV)
response = responsePrototype.text
soup = BeautifulSoup(response, "html.parser", from_encoding="utf-8")
nameResultSet = soup.find_all(attrs={'class': 'tit'}) # [<span class="tit">城市与山里的差距,真正体验过,我来告诉你!</span>]
self.BVName = nameResultSet[0].string # 城市与山里的差距,真正体验过,我来告诉你!
print("BVName:" + self.BVName)
print(responsePrototype.cookies)
#总播放数和历史评论数
Total_playback_barrage_Set=soup.find_all(attrs={'class':'video-data'})
list = Total_playback_barrage_Set[0].contents
total_playback = list[0].attrs['title'][4:] #总播放数
total_barrage = list[1].attrs['title'][7:] #总弹幕数
print("总播放数:" + total_playback)
print("历史累计弹幕数:" +total_barrage)
点赞、投币、收藏、转发
查看网页源码,发现数据都在class为"ops"下的div中,通过find_all方法获取筛选后的结果,分析获取相关数据进行筛选。
但是因为一旦当投币数到达几万(收藏数,转发数也是一样),投币数就不进行显示了,所以这需要找api。经过f12进行筛选网络请求,但是并没有找到理想中的接口。上网查询相关信息,发现接口为:
https://api.bilibili.com/x/web-interface/view?bvid=########
(78条消息) 哔哩哔哩视频播放量、点赞量、评论、收藏、投币与转发信息定时爬虫_Mark_Lee131的博客-CSDN博客
在其后追加BV号即可,浏览器请求发现,data下的stat即为视频基本信息。
- “aid”: 981244086(视频aid)
- “view”: 1433541(总播放数)
- “danmaku”: 3919(弹幕库数)
- “reply”: 1790(评论数)
- “favorite”: 4909(搜藏数)
- “coin”: 21329(投币数)
- “share”: 902(分享数)
- “like”: 52819(点赞数)
- “dislike”: 0
到这里发现,其实一开始就可以通过这个接口进行选择想要的数据,然后进行保存。但笔者这里前面的代码已经写好,而且也是锻炼了爬取网页内容的能力。这里笔者就选择了剩余没有爬取到的数据进行使用,即点赞数、投币数、收藏数,转发数。将request返回的值进行筛选保存。
#点赞、投币、收藏、转发
Like_coin_collect_forward_URL = 'https://api.bilibili.com/x/web-interface/view?bvid=' + BV
response2 = requests.get(url=Like_coin_collect_forward_URL, cookies=self.cookies, headers=self.headers).json()
dict = response2['data']['stat']
like = dict['like'] #喜欢数
coin = dict['coin'] #投币数
favorite = dict['favorite'] #收藏数
share = dict['share'] #分项数
print('喜欢:'+ str(like))
print('投币:'+ str(coin))
print('收藏:'+ str(favorite))
print('分享'+ str(share))
因为要监测视频数据,需要每隔一段时间自动执行一次,因此我们再次引入之前的MyTimer类,编写脚本。
videoDataDetection_.py代码:
# encoding: utf-8
#视频数据监测脚本:每隔一段时间查询一次视频数据:
# 总播放数、历史累计弹幕数
# 点赞、投币、收藏、转发
import os
import time
import requests
import csv
from bs4 import BeautifulSoup
from threading import Timer
from datetime import datetime
#该类实现爬取保存一个视频的评论信息。
class videoDataVariety:
#构造函数__init__,设置基础信息
def __init__(self, mid, name, BV, cookies):
self.mid = mid #up主的uid
self.name = name #up主的账号名称
self.BV = BV # BV:视频id号
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50'
}
self.cookies = cookies # 设置headers和Cookie防止反爬,还有一个fake_useragent库也可以用
self.homeUrl = "https://www.bilibili.com/video/"
self.BVName='(默认视频名称)'
def videoData_content_get(self, BV):
#获取视频基本数据,顺便设置self.BVName
timeNow=time.time()
timeArray = time.localtime(timeNow) # 如果有浮点型的时间戳,则可以写在括号内
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 评论时间,时间戳转为标准时间格式,2022-05-12 00:46:12
#视频名称
responsePrototype = requests.get(url=self.homeUrl + BV)
response = responsePrototype.text
soup = BeautifulSoup(response, "html.parser", from_encoding="utf-8")
nameResultSet = soup.find_all(attrs={'class': 'tit'}) # [<span class="tit">城市与山里的差距,真正体验过,我来告诉你!</span>]
self.BVName = nameResultSet[0].string # 城市与山里的差距,真正体验过,我来告诉你!]
#[
# <div class="video-data">
# <span class="view" title="总播放数1383293">138 .3万播放 · </span>
# <span class="dm" title="历史累计弹幕数3877">总弹幕数3877</span>
# <span>2022-05-04 11:30:00</span>
# <!-- -->
# </div>
# ]
#总播放数和历史评论数
Total_playback_barrage_Set=soup.find_all(attrs={'class':'video-data'})
list = Total_playback_barrage_Set[0].contents
total_playback = list[0].attrs['title'][4:] #总播放数
total_barrage = list[1].attrs['title'][7:] #总弹幕数
#点赞、投币、收藏、转发
Like_coin_collect_forward_URL = 'https://api.bilibili.com/x/web-interface/view?bvid=' + BV
response2 = requests.get(url=Like_coin_collect_forward_URL, cookies=self.cookies, headers=self.headers).json()
dict = response2['data']['stat']
like = dict['like'] #点赞数
coin = dict['coin'] #投币数
favorite = dict['favorite'] #收藏数
share = dict['share'] #转发数
#日志信息
log_str = '时间:' + otherStyleTime + ' 视频名称:《' + self.BVName + \
"》 总播放数:[" + total_playback + "] 历史累计弹幕数:[" +total_barrage + \
'] 点赞:['+ str(like) +'] 投币:['+ str(coin) + '] 收藏:['+ str(favorite) + '] 转发['+ str(share)+']'
print(log_str)
return [timeNow, int(total_playback), int(total_barrage), like, coin, favorite, share] #[1652415252.7468505, 1488493, 3953, 53458, 21430, 4954, 918]
def csv_writeIn(self, mid, name, BV, BVName, line):
writeTableHead = False #默认不写表头
dirname = '视频数据变化分析'
begin = os.getcwd() # 保存开始文件工作路径
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
fileName = str(mid) + "-" + str(name) # up主的文件夹:uid-name
if not os.path.isdir(fileName):
os.mkdir(fileName)
os.chdir(fileName)
fileName = str(BV) + "-" + str(BVName) # BV视频的文件夹:BV-BVname
if not os.path.isdir(fileName):
os.mkdir(fileName)
writeTableHead = True
os.chdir(fileName)
# 如果没有该视频文件则创建文件并写表头
file = open("bilibili视频监测数据_" + BV + ".csv", "a", encoding="utf-8", newline="")
f = csv.writer(file)
if writeTableHead:
line1 = ['时间', '总播放数', '历史累计弹幕数', '点赞', '投币', '收藏', '转发']
f.writerow(line1)
file.flush()
f.writerow(line)
file.flush()
file.close()
os.chdir(begin) # 恢复文件工作路径
def main(self):
line = self.videoData_content_get(self.BV)
self.csv_writeIn(self.mid, self.name, self.BV, self.BVName, line)
class MyTimer(object):
def __init__(self, start_time, interval, callback_proc, args=None, kwargs=None):
self.__timer = None
self.__start_time = start_time
self.__interval = interval
self.__callback_pro = callback_proc
self.__args = args if args is not None else []
self.__kwargs = kwargs if kwargs is not None else {}
def exec_callback(self, args=None, kwargs=None):
self.__callback_pro(*self.__args, **self.__kwargs)
self.__timer = Timer(self.__interval, self.exec_callback)
self.__timer.start()
def start(self):
interval = self.__interval - (datetime.now().timestamp() - self.__start_time.timestamp())
# print(interval)
self.__timer = Timer(interval, self.exec_callback)
self.__timer.start()
def cancel(self):
self.__timer.cancel()
self.__timer = None
if __name__ == '__main__':
cookie = "buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; bp_article_offset_154100711=649151673891029000; CURRENT_QUALITY=0; b_ut=5; fingerprint3=5ad9983134e17174abef4db7b440a5ab; SESSDATA=da01081b%2C1667737162%2C066fd%2A51; bili_jct=e257a396d8d258b042d32a8aa9494f9e; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=ldg719nz; fingerprint=19c41c196550f8268e8c94867b19f6d8; buvid_fp=19c41c196550f8268e8c94867b19f6d8; innersign=1; b_lsid=98AB9BF5_180B85A99EC; CURRENT_FNVAL=4048; bp_video_offset_154100711=659382028029919200; PVID=4"
cookies = {}
for c in cookie.split(";"):
b = c.split("=")
cookies[b[0]] = b[1]
videoDataVariety = videoDataVariety(382193067, '巫托邦', 'BV1344y1u7K8', cookies)
start = datetime.now().replace(minute=3, second=0, microsecond=0)
# tmr1 = MyTimer(start, 10 * 1, videoDataVariety.main, []) # 每10s查询一次数据
tmr1 = MyTimer(start, 60 * 30, videoDataVariety.main, []) #每半个小时查询一次数据
tmr1.start()
flag = input("\n输入\"exit\"停止执行......\n")
if flag == 'exit':
tmr1.cancel()
二、数据可视化
这里除了词云图外,其他主要用了pyecharts库,这个库可以自动生成html文件,在网页中生成一个想要的图表,可交互性强,种类丰富。
快速开始 - pyecharts - A Python Echarts Plotting Library built with love.
(1)词云图
评论区关键字词云图。
(79条消息) python词云图详细教程_全宇宙最最帅气的哆啦A梦小怪兽的博客-CSDN博客_python词云图
1)安装jupyter notebook
打开命令行输入:
pip install jupyter notebook
2)装必要的库
-
wordcloud库
打开网站:https://www.lfd.uci.edu/~gohlke/pythonlibs/
(图片来自上述博客)
命令行进入安装包所在位置,pip安装
pip install wordcloud-1.8.1-cp39-cp39-win_amd64.whl
-
jieba库
pip install jieba
-
pandas库
pip install pandas
3)csv获取评论列数据
读取文件后根据csv操作读取列数据并进行返回。
column_content_get代码
import csv
def column_content_get(self,mid, Name, BV, BVName, column):
#column 第几列
url = f'./视频评论信息/{mid}-{Name}/{BV}-{BVName}/bilibili评论_{BV}.csv'
with open(url, mode='r', encoding='utf-8') as f:
reader = csv.reader(f)
column = [row[column] for row in reader]
return column
4)生成词云图
读取数据后,设置png格式背景图片,设置停用词(过滤词),生成图片。
5)词语图代码
记得加上column_content_get()代码。
import wordcloud as wc
from PIL import Image
import numpy as np
def ci_Yun_Tu(self):
returnValue = self.column_content_get(self.mid, self.Name, self.BV, self.BVName, 7) #在文件中,评论所在列为第8列,下标为7
text = " ".join(returnValue)
# 设置背景形状图片
mask = np.array(Image.open("./fivestar.png"))
# 设置停用词
stopwords = set()
content = [line.strip() for line in open('./stopwords.txt', 'r',enconding='utf-8').readlines()]
stopwords.update(content)
# 画图
word_cloud = wc.WordCloud(scale=10, font_path="C:\Windows\Fonts\msyh.ttc", mask=mask, stopwords=stopwords,
background_color="white") # 大小、字体、背景形状停用词、背景颜色
word_cloud.generate(text)
word_cloud.to_file("词云图-{}-{}.png".format(self.Name, self.BVName)) # 绘制到一个图片里
(2)饼图(环状图)
(79条消息) 【Pyecharts-学习笔记系列之Pie(三)】_浪花卷起千堆雪的博客-CSDN博客
Pie - Pie_radius - Document (pyecharts.org)
1)用户性别分类饼状图
视频评论区用户性别分类饼状图。
用column_content_get()方法读取到视频评论信息中下标为2的数据,也就是视频的评论区中用户的性别分类情况,其中有男、女、保密三种,将读取到的数据进行统计并放置inner_data_pair变量中,通过调用Pie()的方法并设置好环状图的半径,颜色,数据表的标题,图例位置等等。最后进行输出html文件。
在练习过程中,发现有的视频名称如果末尾有 … (三个英文句号)的话,在视频评论信息和视频数据变化分析的文件保存过程中(即commentData类和videoDataDetection_.py文件),Windows会将这三个标点符号进行忽略掉,比如何同学的这个视频。因此,我们修改了之前的代码,直接在给BVName赋值的时候,把“《》”加上即可。这里笔者没有再返回去修改本文之前那两个文件的代码内容,如有需要请自行修改。
环状图结果示意图:
2)饼图代码
记得加上column_content_get()代码
from pyecharts import options as opts
from pyecharts.charts import Pie
#视频评论区用户性别分类饼状图
def bing_tu(self):
series_nameValue = self.BVName + '评论区用户' #鼠标悬浮在图上的提示文字
sexColumn = self.column_content_get(self.mid, self.Name, self.BV, self.BVName, 2)
maleNum = 0
femaleNum = 0
unknownNum = 0
for i in sexColumn:
if (i == '男'): maleNum += 1
elif (i == '女'): femaleNum += 1
elif (i == '保密'): unknownNum += 1
inner_x_data = ["男", "女", "保密"] #分类
inner_y_data = [maleNum, femaleNum, unknownNum] #分类对应的值
inner_data_pair = [list(z) for z in zip(inner_x_data, inner_y_data)] #值的“合集”
outUrl = './输出库/' + str(self.mid) + '-' + self.Name + '-' + self.BV + '-' + self.BVName #输出路径
c = (
Pie()
.add(
series_nameValue,
inner_data_pair,
radius=["50%", "75%"], # 调整半径
)
.set_colors(["#65a8d8", "#f8a3cf", "#9da5ad"]) #颜色
.set_global_opts(
title_opts=opts.TitleOpts(title="性别构成情况"), #标题
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="88%"),# 图例设置
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # 设置标签
.render(outUrl + ".html") #保存输出
)
(3)柱状图
1)评论区点赞TOP20柱状图
视频评论区点赞top20柱状图。
使用pandas库和和饼图同样的pyecharts库,Pandas的名称来自于面板数据(panel data),基于NumPy构建,提供了高级数据结构和数据操作工具。
读取好csv文件后通过DataFrame设置好源数据,删除空值与重复值,然后根据文件中点赞数那一列降序排序,取前多少行,也就是点赞数最多的几条评论数据。在pyecharts中设置好下x轴、y轴的数据,以及标题,保存导出即可。
示意图如下:
2)代码
import csv
from pyecharts import options as opts
from pyecharts.charts import Bar
import pandas as pd
#视频评论区点赞top柱状图
def zhu_zhuang_tu(self, mid, Name, BV, BVName):
TopNum = 20 # 点赞前20
series_nameValue = self.BVName + '点赞TOP' + str(TopNum)
inUrl = './视频评论信息/' + str(mid) + '-' + Name + '/' + BV + '-' + BVName + '/bilibili评论_' + BV + '.csv' # 视频评论文件所在路径
outUrl = './输出库/' + str(mid) + '-' + Name + '-' + BV + '-' + series_nameValue + '柱状图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() #删除空值
df.drop_duplicates() #删除重复值
df1 = df.sort_values(by=['点赞数'], ascending=False).head(TopNum) #根据文件里的'点赞数'列降序排序,取前TopNum行
c = (
Bar()
.add_xaxis(
df1['评论内容'].to_list() # x轴是评论内容
)
.add_yaxis(
"点赞数",
df1["点赞数"].to_list(),
color='#87cff1'
)
.set_global_opts(
title_opts=opts.TitleOpts(title = series_nameValue), #设置标题
datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_="inside")],
)
.render(outUrl + ".html") #保存输出
)
c
(4)漏斗图
1)用户等级分布漏斗图
视频评论区用户等级分布漏斗图。
读取视频评论区数据后,通过pandas库的value_counts方法统计词频,以降序排列,再通过pyecharts库进行绘制图形。
示意图:
2)代码
from pyecharts import options as opts
from pyecharts.charts import Funnel
import pandas as pd
#视频评论区用户等级分布漏斗图
def lou_dou_tu(self, mid, Name, BV, BVName):
series_nameValue = self.BVName + '评论区用户等级分布'
inUrl = './视频评论信息/' + str(mid) + '-' + Name + '/' + BV + '-' + BVName + '/bilibili评论_' + BV + '.csv' # 视频评论文件所在路径
outUrl = './输出库/' + str(mid) + '-' + Name + '-' + BV + '-' + series_nameValue + '漏斗图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() # 删除空值
df.drop_duplicates() # 删除重复值
grade = df['等级'].value_counts().sort_index(ascending=False) #统计词频,降序
gradeNumList = grade.to_list() #等级数量 #[77, 160, 47, 16, 6]
gradeList = grade.index # 等级# Int64Index([6, 5, 4, 3, 2], dtype='int64')
c = (
Funnel()
.add(
"用户等级",
[list(z) for z in zip(gradeList, gradeNumList)],
label_opts=opts.LabelOpts(position="inside"),
)
.set_colors(["#f9b4ab", "#fdebd3", "#264e70", '#679186', '#bbd4ce', '#ebf1f4']) # 颜色
.set_global_opts(title_opts=opts.TitleOpts(title="Funnel-Label(inside)"))
.render(outUrl + ".html") #保存输出
)
(5)折线图
Line - Stacked_line_chart - Document (pyecharts.org)
(80条消息) Echarts|Stacked Line Chart(折线图堆叠)Y轴数据不正确问题_craftsman2020的博客-CSDN博客_echarts 折线图stack
1)UP主数据变化折线图
96h(自定义h)内up主数据变化折线图。
根据之前所爬取的文件信息,有粉丝数、关注数、点赞数、播放数、和阅读数可供使用。时间为时间戳格式,截止目前,笔者这里的数据是之前放置在树莓派爬取到的数据,总共是96条信息,也就是4天的信息。因为数据较少,时间跨度较低,而不同种类数据值差异较大,所以所得的折线图可能没有直观给出变化趋势。
在该方法中我们依旧用pandas库读取数据,再通过pyecharts库进行绘制图形。筛选列数据后分别加入图例中。
示意图如下:
2)代码
from pyecharts.charts import Line
import pandas as pd
import time
# 24h up主粉丝变化折线图
def zhe_xian_tu(self, mid, Name):
interval = 96 # 因为up主粉丝数据是没1h获取一次的,所以这里显示24小时up主的粉丝情况。
titleName = str(mid) + '-' + Name + '-' + str(interval) + 'h基本数据变化'
inUrl = './up主信息/' + str(mid) + '-' + Name + '/' + str(mid) + '-' + Name + '.csv' #up主信息文件所在路径
outUrl = './输出库/' + titleName + '折线图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() # 删除空值
df1 = df.tail(interval)
x_data = df1['时间']
y_data_fans_Num = df1['粉丝数']
y_data_follow_Num = df1['关注数']
y_data_like_Num = df1['点赞数']
y_data_play_Num = df1['播放数']
y_data_read_Num = df1['阅读数']
xx_data = [] #2022年5月15日 10:09:22格式的时间作为x轴
for t in x_data:
timeArray = time.localtime(t)
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 评论时间,时间戳转为标准时间格式,2022-05-05 19:15:14
xx_data.append(otherStyleTime)
(
Line()
.add_xaxis(xaxis_data=xx_data)
.add_yaxis(
series_name="粉丝数",
y_axis=y_data_fans_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="关注数",
y_axis=y_data_follow_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="点赞数",
y_axis=y_data_like_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="播放数",
y_axis=y_data_play_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="阅读数",
y_axis=y_data_read_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.set_global_opts(
title_opts=opts.TitleOpts(title=titleName),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
yaxis_opts=opts.AxisOpts(
type_="value",
axistick_opts=opts.AxisTickOpts(is_show=True),
splitline_opts=opts.SplitLineOpts(is_show=True),
),
xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False),
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="91%"), # 图例设置
)
.render(outUrl + ".html") #保存输出
)
(6)平行坐标系
1)各视频数据平行坐标系图
同一个up,不同视频总播放数、历史累计弹幕数、点赞、投币、收藏、转发的可视化。
pyecharts 设置地图大小错误 - 简书 (jianshu.com)
因为需要横评不同视频的数据,因此我们重新定义一个类differentVideoDataEvaluation,并且在该类下定义一个新方法ping_xing_zuo_biao_xi(),该方法通过接受一个list集合,其中就是同一个up不同视频的数据。例如
data = [
[1760078, 4185, 57384, 22057, 5242, 979, 'BV1344y1u7K8\n《为了不洗碗,我把碗筷\n吃了!》'],
[137866, 968, 27086, 20657, 3424, 392, 'BV1WT4y1B7Df\n《为了打嗝,专门去盖了\n个打阁!》'],
[125085, 316, 23713, 10821, 2001, 190, 'BV1NT4y1k7qU\n《【美食杂交技术】油条\n与薯条的完美结合体!》'],
[400429, 1649, 76976, 72977, 9390, 766, 'BV11r4y1J7cH\n《不搞钱,就搞笑!》']
]
那么如何获取数据呢?我们调用之前写的**videoData_content_get()**方法,查看该方法,
def videoData_content_get(self, BV):
......此处代码省略
return [timeNow, int(total_playback), int(total_barrage), like, coin, favorite, share] #[1652415252.7468505, 1488493, 3953, 53458, 21430, 4954, 918]
发现返回的是一个列表,包含了爬取时间、总播放数、历史累计弹幕数、点赞数、投币数、收藏数和分享数。
我们在此数据上新添加BV的信息和BVName的信息就可以了,并把不同的视频的list写在一个list里面,将其传给**ping_xing_zuo_biao_xi()**方法就好了。
#获取数据列表
videoDataVariety1 = videoDataVariety(382193067, '巫托邦', 'BV1344y1u7K8', cookies) #初始化
datalist1 = videoDataVariety1.videoData_content_get(videoDataVariety1.BV)# 调用之前写的方法返回
videoDataVariety2 = videoDataVariety(382193067, '巫托邦', 'BV1WT4y1B7Df', cookies)
datalist2 = videoDataVariety2.videoData_content_get(videoDataVariety2.BV)
videoDataVariety3 = videoDataVariety(382193067, '巫托邦', 'BV1NT4y1k7qU', cookies)
datalist3 = videoDataVariety3.videoData_content_get(videoDataVariety3.BV)
videoDataVariety4 = videoDataVariety(382193067, '巫托邦', 'BV11r4y1J7cH', cookies)
datalist4 = videoDataVariety4.videoData_content_get(videoDataVariety4.BV)
# 此处添加BV和BVName
datalist1.append(videoDataVariety1.BV)
datalist1.append(videoDataVariety1.BVName)
datalist2.append(videoDataVariety2.BV)
datalist2.append(videoDataVariety2.BVName)
datalist3.append(videoDataVariety3.BV)
datalist3.append(videoDataVariety3.BVName)
datalist4.append(videoDataVariety4.BV)
datalist4.append(videoDataVariety4.BVName)
dataList = [
datalist1,
datalist2,
datalist3,
datalist4
]
在ping_xing_zuo_biao_xi()方法中,我们将list下中第一条视频信息中的第一列的时间戳设置为该平行坐标系的生成时间,并将其赋值给timeArray,设置好titleName和输出路径,通过pyecharts库进行绘制平行坐标系图即可。
注意:由于BV和BVName组成的字符串太长,如果不进行换行,在最终输出的图标中最后一列也就是BV-BVName的坐标轴上的数据显示不全,因此我们需要在合理的位置上添加’\n’。
data = []
different_BV_length = len(videoData_content_get_Return_newListList) #总共有几个视频的横评
BVNameList = [] #BV-BVName构成的list
for i in range(different_BV_length):
BVNameLength = len(videoData_content_get_Return_newListList[i][8])
v1 = videoData_content_get_Return_newListList[i][8]
if BVNameLength > 22:
v1 = videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:22] + '\n' + \
videoData_content_get_Return_newListList[i][8][22:]
elif BVNameLength> 11:
v1= videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:]
str = videoData_content_get_Return_newListList[i][7] + '\n' + v1
BVNameList.append(str)
data.append([])
data[i].append(videoData_content_get_Return_newListList[i][1]) #总播放数
data[i].append(videoData_content_get_Return_newListList[i][2]) #历史累计弹幕数
data[i].append(videoData_content_get_Return_newListList[i][3]) #点赞
data[i].append(videoData_content_get_Return_newListList[i][4]) #投币
data[i].append(videoData_content_get_Return_newListList[i][5]) #收藏
data[i].append(videoData_content_get_Return_newListList[i][6]) #分享
data[i].append(str) #BV-BVName
示意图如下:
2)代码
from bilibiliVideoDataAnalysis.work import videoDataDetection_ #导入之前自己写的模块
from pyecharts import options as opts
from pyecharts.charts import Parallel
import time
#可视化(平行坐标系)
class differentVideoDataEvaluation:
#不同视频的评测
# 平行坐标系
# 同一个up,不同视频总播放数、历史累计弹幕数、点赞、投币、收藏、转发的可视化
def ping_xing_zuo_biao_xi(self, upname, videoData_content_get_Return_newListList):
# videoData_content_get_ReturnListList:
# 该参数是videoDataDetection_.py文件中videoDataVariety类videoData_content_get()方法的不同视频返回数据集合,
# 并且新新添加了两列:BV、BVName
# 例如[
# [1652599039.1057296, 1760078, 4185, 57384, 22057, 5242, 979, 'BV1344y1u7K8', '《为了不洗碗,我把碗筷吃了!》'],
# [1652599039.8925633, 137866, 968, 27086, 20657, 3424, 392, 'BV1WT4y1B7Df', '《为了打嗝,专门去盖了个打阁!》'],
# [1652599041.0497084, 125085, 316, 23713, 10821, 2001, 190, 'BV1NT4y1k7qU', '《【美食杂交技术】油条与薯条的完美结合体!》'],
# [1652599042.146411, 400429, 1649, 76976, 72977, 9390, 766, 'BV11r4y1J7cH', '《不搞钱,就搞笑!》']
# ]
data = []
different_BV_length = len(videoData_content_get_Return_newListList) #总共有几个视频的横评
BVNameList = [] #BV-BVName构成的list
for i in range(different_BV_length):
BVNameLength = len(videoData_content_get_Return_newListList[i][8])
v1 = videoData_content_get_Return_newListList[i][8]
if BVNameLength > 22:
v1 = videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:22] + '\n' + \
videoData_content_get_Return_newListList[i][8][22:]
elif BVNameLength> 11:
v1= videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:]
str = videoData_content_get_Return_newListList[i][7] + '\n' + v1
BVNameList.append(str)
data.append([])
data[i].append(videoData_content_get_Return_newListList[i][1]) #总播放数
data[i].append(videoData_content_get_Return_newListList[i][2]) #历史累计弹幕数
data[i].append(videoData_content_get_Return_newListList[i][3]) #点赞
data[i].append(videoData_content_get_Return_newListList[i][4]) #投币
data[i].append(videoData_content_get_Return_newListList[i][5]) #收藏
data[i].append(videoData_content_get_Return_newListList[i][6]) #分享
data[i].append(str) #BV-BVName
timeArray = time.localtime(videoData_content_get_Return_newListList[0][0]) #获取横评视频时间
genertTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 生成时间,时间戳转为标准时间格式,2022-05-05 19:15:14
titleName = genertTime + '-' + upname + "不同视频横评"
outUrl = './输出库/' + upname + '-不同视频横评平行坐标图' # 输出路径
c = (
Parallel(init_opts=opts.InitOpts(width="1200px", height="700px",page_title=titleName))
.add_schema(
[
opts.ParallelAxisOpts(dim=0, name='总播放数'),
opts.ParallelAxisOpts(dim=1, name='历史累计弹幕数'),
opts.ParallelAxisOpts(dim=2, name='点赞'),
opts.ParallelAxisOpts(dim=3, name="投币"),
opts.ParallelAxisOpts(dim=4, name="收藏"),
opts.ParallelAxisOpts(dim=5, name="转发"),
opts.ParallelAxisOpts(
dim=6,
name="BV-视频名称",
type_="category",
data=BVNameList,
),
]
)
.add(
upname,
data,
)
.set_global_opts(
title_opts=opts.TitleOpts(title = titleName),
) #设置全局配置项
.render(outUrl + ".html") #保存输出
)
c
(7)visualization文件代码
#可视化
import csv
import os
import wordcloud as wc
from PIL import Image
import numpy as np
from pyecharts import options as opts
from pyecharts.charts import Bar, Pie, Funnel, Line
import pandas as pd
import time
from pyecharts.charts import Parallel
from bilibiliVideoDataAnalysis.work.videoDataDetection_ import videoDataVariety
#可视化(词云图、饼图、柱状图、漏斗图、折线图)
class visualization:
def __init__(self, mid, Name, BV, BVName):
self.mid = mid
self.Name = Name
self.BV = BV
self.BVName = BVName
# 评论词云图
def ci_Yun_Tu(self):
dirname = '输出库'
begin = os.getcwd() # 保存开始文件工作路径
returnValue = self.column_content_get(self.mid, self.Name, self.BV, self.BVName, 7) #在文件中,评论所在列为第8列,下标为7
text = " ".join(returnValue)
# 设置背景形状图片
mask = np.array(Image.open("./素材库/fivestar.png"))
# 设置停用词
stopwords = set()
content = [line.strip() for line in open('./素材库/stopwords.txt', 'r',encoding='utf-8').readlines()]
stopwords.update(content)
# 画图
word_cloud = wc.WordCloud(scale=10, font_path="C:/Windows/Fonts/msyh.ttc", mask=mask, stopwords=stopwords,
background_color="white") # 大小、字体、背景形状停用词、背景颜色
word_cloud.generate(text)
# 如果没有该文件夹则创建一个
if not os.path.isdir(dirname):
os.mkdir(dirname)
os.chdir(dirname) # 改变当前工作目录到指定的路径
word_cloud.to_file("词云图-{}-{}.png".format(self.Name, self.BVName)) # 绘制到一个图片里
os.chdir(begin) # 恢复文件工作路径
#视频评论区用户性别分类饼状图
def bing_tu(self):
series_nameValue = self.BVName + '评论区用户' #鼠标悬浮在图上的提示文字
sexColumn = self.column_content_get(self.mid, self.Name, self.BV, self.BVName, 2)
maleNum = 0
femaleNum = 0
unknownNum = 0
for i in sexColumn:
if (i == '男'): maleNum += 1
elif (i == '女'): femaleNum += 1
elif (i == '保密'): unknownNum += 1
inner_x_data = ["男", "女", "保密"] #分类
inner_y_data = [maleNum, femaleNum, unknownNum] #分类对应的值
inner_data_pair = [list(z) for z in zip(inner_x_data, inner_y_data)] #值的“合集”
outUrl = './输出库/' + str(self.mid) + '-' + self.Name + '-' + self.BV + '-' + series_nameValue + '环状图' #输出路径
c = (
Pie()
.add(
series_nameValue,
inner_data_pair,
radius=["50%", "75%"], # 调整半径
)
.set_colors(["#65a8d8", "#f8a3cf", "#9da5ad"]) #颜色
.set_global_opts(
title_opts=opts.TitleOpts(title="性别构成情况"), #标题
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="91%"),# 图例设置
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # 设置标签
.render(outUrl + ".html") #保存输出
)
#视频评论区点赞top柱状图
def zhu_zhuang_tu(self, mid, Name, BV, BVName):
TopNum = 20 # 点赞前20
series_nameValue = self.BVName + '点赞TOP' + str(TopNum)
inUrl = './视频评论信息/' + str(mid) + '-' + Name + '/' + BV + '-' + BVName + '/bilibili评论_' + BV + '.csv' # 视频评论文件所在路径
outUrl = './输出库/' + str(mid) + '-' + Name + '-' + BV + '-' + series_nameValue + '柱状图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() #删除空值
df.drop_duplicates() #删除重复值
df1 = df.sort_values(by=['点赞数'], ascending=False).head(TopNum) #根据文件里的'点赞数'列降序排序,取前TopNum行
c = (
Bar()
.add_xaxis(
df1['评论内容'].to_list() # x轴是评论内容
)
.add_yaxis(
"点赞数",
df1["点赞数"].to_list(),
color='#87cff1'
)
.set_global_opts(
title_opts=opts.TitleOpts(title = series_nameValue), #设置标题
datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_="inside")],
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="91%"), # 图例设置
)
.render(outUrl + ".html") #保存输出
)
c
#视频评论区用户等级分布漏斗图
def lou_dou_tu(self, mid, Name, BV, BVName):
series_nameValue = BVName + '评论区用户等级分布'
inUrl = './视频评论信息/' + str(mid) + '-' + Name + '/' + BV + '-' + BVName + '/bilibili评论_' + BV + '.csv' # 视频评论文件所在路径
outUrl = './输出库/' + str(mid) + '-' + Name + '-' + BV + '-' + series_nameValue + '漏斗图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() # 删除空值
df.drop_duplicates() # 删除重复值
grade = df['等级'].value_counts().sort_index(ascending=False) #统计词频,降序
gradeNumList = grade.to_list() #等级数量 #[77, 160, 47, 16, 6]
gradeList = grade.index # 等级# Int64Index([6, 5, 4, 3, 2], dtype='int64')
c = (
Funnel()
.add(
"用户等级",
[list(z) for z in zip(gradeList, gradeNumList)],
label_opts=opts.LabelOpts(position="inside"),
)
.set_colors(["#f9b4ab", "#fdebd3", "#264e70", '#679186', '#bbd4ce', '#ebf1f4']) # 颜色
.set_global_opts(
title_opts=opts.TitleOpts(title=series_nameValue),
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="91%"), # 图例设置
)
.render(outUrl + ".html") #保存输出
)
# 96h(自定义h)内up主本数据变化折线图
def zhe_xian_tu(self, mid, Name):
interval = 96 # 因为up主粉丝数据是没1h获取一次的,所以这里显示24小时up主的粉丝情况。
titleName = str(mid) + '-' + Name + '-' + str(interval) + 'h基本数据变化'
inUrl = './up主信息/' + str(mid) + '-' + Name + '/' + str(mid) + '-' + Name + '.csv' #up主信息文件所在路径
outUrl = './输出库/' + titleName + '折线图' # 输出路径
df = pd.DataFrame(pd.read_csv(inUrl))
df.dropna() # 删除空值
df1 = df.tail(interval)
x_data = df1['时间']
y_data_fans_Num = df1['粉丝数']
y_data_follow_Num = df1['关注数']
y_data_like_Num = df1['点赞数']
y_data_play_Num = df1['播放数']
y_data_read_Num = df1['阅读数']
xx_data = [] #2022年5月15日 10:09:22格式的时间作为x轴
for t in x_data:
timeArray = time.localtime(t)
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 评论时间,时间戳转为标准时间格式,2022-05-05 19:15:14
xx_data.append(otherStyleTime)
(
Line()
.add_xaxis(xaxis_data=xx_data)
.add_yaxis(
series_name="粉丝数",
y_axis=y_data_fans_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="关注数",
y_axis=y_data_follow_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="点赞数",
y_axis=y_data_like_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="播放数",
y_axis=y_data_play_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.add_yaxis(
series_name="阅读数",
y_axis=y_data_read_Num,
label_opts=opts.LabelOpts(is_show=False),
)
.set_global_opts(
title_opts=opts.TitleOpts(title=titleName),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
yaxis_opts=opts.AxisOpts(
type_="value",
axistick_opts=opts.AxisTickOpts(is_show=True),
splitline_opts=opts.SplitLineOpts(is_show=True),
),
xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False),
legend_opts=opts.LegendOpts(orient="vertical", pos_top="10%", pos_left="91%"), # 图例设置
)
.render(outUrl + ".html") #保存输出
)
#获取csv列数据
def column_content_get(self,mid, Name, BV, BVName, column):
#column 第几列
url = f'./视频评论信息/{mid}-{Name}/{BV}-{BVName}/bilibili评论_{BV}.csv'
with open(url, mode='r', encoding='utf-8') as f:
reader = csv.reader(f)
column = [row[column] for row in reader]
return column
def main(self):
self.ci_Yun_Tu()
self.bing_tu()
self.zhu_zhuang_tu(self.mid, self.Name, self.BV, self.BVName)
self.lou_dou_tu(self.mid, self.Name, self.BV, self.BVName)
self.zhe_xian_tu(self.mid, self.Name )
pass
#可视化(平行坐标系)
class differentVideoDataEvaluation:
#不同视频的评测
# 平行坐标系
# 同一个up,不同视频总播放数、历史累计弹幕数、点赞、投币、收藏、转发的可视化
def ping_xing_zuo_biao_xi(self, upname, videoData_content_get_Return_newListList):
# videoData_content_get_ReturnListList:
# 该参数是videoDataDetection_.py文件中videoDataVariety类videoData_content_get()方法的不同视频返回数据集合,
# 并且新新添加了两列:BV、BVName
# 例如[
# [1652599039.1057296, 1760078, 4185, 57384, 22057, 5242, 979, 'BV1344y1u7K8', '《为了不洗碗,我把碗筷吃了!》'],
# [1652599039.8925633, 137866, 968, 27086, 20657, 3424, 392, 'BV1WT4y1B7Df', '《为了打嗝,专门去盖了个打阁!》'],
# [1652599041.0497084, 125085, 316, 23713, 10821, 2001, 190, 'BV1NT4y1k7qU', '《【美食杂交技术】油条与薯条的完美结合体!》'],
# [1652599042.146411, 400429, 1649, 76976, 72977, 9390, 766, 'BV11r4y1J7cH', '《不搞钱,就搞笑!》']
# ]
data = []
different_BV_length = len(videoData_content_get_Return_newListList) #总共有几个视频的横评
BVNameList = [] #BV-BVName构成的list
for i in range(different_BV_length):
BVNameLength = len(videoData_content_get_Return_newListList[i][8])
v1 = videoData_content_get_Return_newListList[i][8]
if BVNameLength > 22:
v1 = videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:22] + '\n' + \
videoData_content_get_Return_newListList[i][8][22:]
elif BVNameLength> 11:
v1= videoData_content_get_Return_newListList[i][8][0:11] + '\n' + \
videoData_content_get_Return_newListList[i][8][11:]
str = videoData_content_get_Return_newListList[i][7] + '\n' + v1
BVNameList.append(str)
data.append([])
data[i].append(videoData_content_get_Return_newListList[i][1]) #总播放数
data[i].append(videoData_content_get_Return_newListList[i][2]) #历史累计弹幕数
data[i].append(videoData_content_get_Return_newListList[i][3]) #点赞
data[i].append(videoData_content_get_Return_newListList[i][4]) #投币
data[i].append(videoData_content_get_Return_newListList[i][5]) #收藏
data[i].append(videoData_content_get_Return_newListList[i][6]) #分享
data[i].append(str) #BV-BVName
timeArray = time.localtime(videoData_content_get_Return_newListList[0][0]) #获取横评视频时间
genertTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 生成时间,时间戳转为标准时间格式,2022-05-05 19:15:14
titleName = genertTime + '-' + upname + "不同视频横评"
outUrl = './输出库/' + upname + '-不同视频横评平行坐标图' # 输出路径
c = (
Parallel(init_opts=opts.InitOpts(width="1200px", height="700px",page_title=titleName))
.add_schema(
[
opts.ParallelAxisOpts(dim=0, name='总播放数'),
opts.ParallelAxisOpts(dim=1, name='历史累计弹幕数'),
opts.ParallelAxisOpts(dim=2, name='点赞'),
opts.ParallelAxisOpts(dim=3, name="投币"),
opts.ParallelAxisOpts(dim=4, name="收藏"),
opts.ParallelAxisOpts(dim=5, name="转发"),
opts.ParallelAxisOpts(
dim=6,
name="BV-视频名称",
type_="category",
data=BVNameList,
),
]
)
.add(
upname,
data,
)
.set_global_opts(
title_opts=opts.TitleOpts(title = titleName),
) #设置全局配置项
.render(outUrl + ".html") #保存输出
)
c
if __name__ == '__main__':
cookie = "buvid3=11707BB8-8181-70C7-EBE1-FB1609F40FC370555infoc; i-wanna-go-back=-1; _uuid=F4221228-EF95-B7F10-49C1-F710CAC68D109F77140infoc; buvid4=E437889C-0A9F-DEF4-C164-E3F9F456407172347-022032622-MnLxL6Vqo8K/D8N1XzXHLQ%3D%3D; nostalgia_conf=-1; buvid_fp_plain=undefined; blackside_state=1; rpdid=|(J~J|R~m)Jm0J'uYR)Jm~JYR; CURRENT_BLACKGAP=0; hit-dyn-v2=1; LIVE_BUVID=AUTO4316488832212386; CURRENT_QUALITY=0; b_ut=5; fingerprint3=5ad9983134e17174abef4db7b440a5ab; SESSDATA=da01081b%2C1667737162%2C066fd%2A51; bili_jct=e257a396d8d258b042d32a8aa9494f9e; DedeUserID=154100711; DedeUserID__ckMd5=4a5f601a3689140a; sid=ldg719nz; fingerprint=19c41c196550f8268e8c94867b19f6d8; buvid_fp=19c41c196550f8268e8c94867b19f6d8; CURRENT_FNVAL=4048; PVID=1; bp_video_offset_154100711=660026573772030000; b_lsid=51210F6A10_180C576256C; b_timer=%7B%22ffp%22%3A%7B%22333.788.fp.risk_11707BB8%22%3A%22180C59D0C11%22%2C%22333.999.fp.risk_11707BB8%22%3A%22180C59D142F%22%2C%22444.41.fp.risk_11707BB8%22%3A%22180C59D7218%22%7D%7D"
cookies = {}
for c in cookie.split(";"):
b = c.split("=")
cookies[b[0]] = b[1]
visualization_type1 = visualization(382193067, '巫托邦', 'BV1344y1u7K8', '《为了不洗碗,我把碗筷吃了!》')
visualization_type1.main() #生成(词云图、饼图、柱状图、漏斗图、折线图)
visualization_type2 = differentVideoDataEvaluation() #创建平行坐标系类的对象
#获取数据列表
videoDataVariety1 = videoDataVariety(382193067, '巫托邦', 'BV1344y1u7K8', cookies) #初始化
datalist1 = videoDataVariety1.videoData_content_get(videoDataVariety1.BV)# 调用之前写的方法返回
videoDataVariety2 = videoDataVariety(382193067, '巫托邦', 'BV1WT4y1B7Df', cookies)
datalist2 = videoDataVariety2.videoData_content_get(videoDataVariety2.BV)
videoDataVariety3 = videoDataVariety(382193067, '巫托邦', 'BV1NT4y1k7qU', cookies)
datalist3 = videoDataVariety3.videoData_content_get(videoDataVariety3.BV)
videoDataVariety4 = videoDataVariety(382193067, '巫托邦', 'BV11r4y1J7cH', cookies)
datalist4 = videoDataVariety4.videoData_content_get(videoDataVariety4.BV)
# 此处添加BV和BVName
datalist1.append(videoDataVariety1.BV)
datalist1.append(videoDataVariety1.BVName)
datalist2.append(videoDataVariety2.BV)
datalist2.append(videoDataVariety2.BVName)
datalist3.append(videoDataVariety3.BV)
datalist3.append(videoDataVariety3.BVName)
datalist4.append(videoDataVariety4.BV)
datalist4.append(videoDataVariety4.BVName)
dataList = [
datalist1,
datalist2,
datalist3,
datalist4
]
visualization_type2.ping_xing_zuo_biao_xi(videoDataVariety1.name, dataList) #生成平行坐标系
"""
==========================
@auther:JingDe
@Date:2022/5/13 17:18
@email:
@IDE:PyCharm
==========================
"""
三、不足之处
- 在折线图与平行坐标图的制作中,折线图所表示的up主的基本信息体现不够明晰,应该将这两个图所表示的内容进行互换,即用折线图来表示一个新发的视频的变化趋势,直接调用videoDataDetection_.py文件所获取的csv文件读取用pyechartsi制作数据表即可,理想状态下,会充分发挥折线图的优势;用平行坐标图来表示不同up主的基本信息横评,因为在大部分情况下,up主的基本信息已基本成型,在短时间内不会改变,用平行坐标图来体现的话更有优势。
- 可能本文有些地方表述不清。
- 有些类的方法写的不是很“优雅”,比如在最后differentVideoDataEvaluation类方面的实现过程中。
四、搁置项(可忽略)
这一部分就是刚开始做的无用功,对最终结果没有重要帮助,但是我又舍不得删,故放在最后,可跳过。
1.获取User-Agent
登录目标网站:
2013年12月西安空气质量指数AQI_PM2.5日历史数据_中国空气质量在线监测分析平台历史数据 (aqistudy.cn)
查看数据元素
2.分析用户关注得所有up主的api
查找用户关注up主的api。根据api可以得到每次调用显示20个用户。
获的请求api:
https://api.bilibili.com/x/relation/followings?vmid=154100711&pn=1&ps=20&order=desc&order_type=attention&jsonp=jsonp&callback=__jp5
- vimd=后的参数就是用户的mid号
- pn=1指用户的关注的第一面用户
删去callback参数,vmid用户id,我们将pn放到后面方便调用时换页。
https://api.bilibili.com/x/relation/followings?vmid=2&pn=1&ps=20&order=desc&jsonp=jsonp&callback=__jp7
https://api.bilibili.com/x/relation/followings?vmid=######&ps=20&order=desc&jsonp=jsonp&pn=#
筛选需要的up主信息:
- mid:up主uid
- mtime:生日
- desc:官方认证信息
- sign:个性签名
- uname:账号名称
五、参考资料
【1】(108条消息) F12查看headers的含义_不熬夜的程序猿的博客-CSDN博客_f12查看header
【2】(108条消息) python模块–BeautifulSoup4 和 lxml_黄佳俊、的博客-CSDN博客_beautifulsoup4 lxml
【3】(109条消息) 【网络爬虫 | python】bilibili评论信息爬取(基础版)_竹一笔记的博客-CSDN博客_b站评论爬取
【4】(109条消息) python:类基础_不怕猫的耗子A的博客-CSDN博客_python 类
【5】一篇文章带你搞懂Python中的类 (baidu.com)
【6】Python3 面向对象 | 菜鸟教程 (runoob.com)
【7】【python爬虫】每天统计一遍up主粉丝数!-python黑洞网 (pythonheidong.com)
【8】[(78条消息) Python:录记个做,写写便随_Ambitioner_c的博客-CSDN博客](https://blog.csdn.net/qq_41297934/article/details/105371870?ops_request_misc=&request_id=&biz_id=102&utm_term=Python 实现某个功能每隔一段时间被执行一次的功能方法&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-8-105371870.142v9pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187)
【9】[(78条消息) Python实现定时任务的几种方法_从流域到海域的博客-CSDN博客_python定时任务的实现方式](https://blog.csdn.net/Solo95/article/details/122026111?ops_request_misc=&request_id=&biz_id=102&utm_term=Python 实现某个功能每隔一段时间被执行一次的功能方法&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-5-122026111.142v9pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187)
【10】(77条消息) Python 实现某个功能每隔一段时间被执行一次的功能_独一无二的小个性的博客-CSDN博客_python 每隔一段时间
www.suphelp.cn/submit32
【11】https://www.bilibili.com/video/BV1ot411R7SM?spm_id_from=333.999.0.0
【12】https://www.bilibili.com/video/BV11r4y1J7cH?spm_id_from=333.999.0.0
【13】https://www.bilibili.com/video/BV1fQ4y1q7SB/?spm_id_from=333.788.recommend_more_video.16
【14】(78条消息) python session保持cookie_python接口自动化测试八:更新Cookies、session保持会话_冷君聊大片的博客-CSDN博客
【15】(78条消息) Python中BeautifulSoup库的用法_阎_松的博客-CSDN博客_beautifulsoup库的作用
【16】(78条消息) 哔哩哔哩视频播放量、点赞量、评论、收藏、投币与转发信息定时爬虫_Mark_Lee131的博客-CSDN博客
【17】(79条消息) python词云图详细教程_全宇宙最最帅气的哆啦A梦小怪兽的博客-CSDN博客_python词云图
【18】(79条消息) Python将冰冰的第一条vlog并进行数据分析_北山啦的博客-CSDN博客_python爬取b站评论
【19】(79条消息) 【Pyecharts-学习笔记系列之Pie(三)】_浪花卷起千堆雪的博客-CSDN博客
【20】快速开始 - pyecharts - A Python Echarts Plotting Library built with love.
【21】pyecharts 设置地图大小错误 - 简书 (jianshu.com)
m_id_from=333.788.recommend_more_video.16
【14】(78条消息) python session保持cookie_python接口自动化测试八:更新Cookies、session保持会话_冷君聊大片的博客-CSDN博客
【15】(78条消息) Python中BeautifulSoup库的用法_阎_松的博客-CSDN博客_beautifulsoup库的作用
【16】(78条消息) 哔哩哔哩视频播放量、点赞量、评论、收藏、投币与转发信息定时爬虫_Mark_Lee131的博客-CSDN博客
【17】(79条消息) python词云图详细教程_全宇宙最最帅气的哆啦A梦小怪兽的博客-CSDN博客_python词云图
【18】(79条消息) Python将冰冰的第一条vlog并进行数据分析_北山啦的博客-CSDN博客_python爬取b站评论
【19】(79条消息) 【Pyecharts-学习笔记系列之Pie(三)】_浪花卷起千堆雪的博客-CSDN博客
【20】快速开始 - pyecharts - A Python Echarts Plotting Library built with love.