【超详细指北】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类

​ 该类是获取某一视频的所有评论信息,包括一级评论、二级评论,获取了评论用户的基本信息和评论内容。在该类中,设置headersCookie防止反爬,此外还有一个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】
  1. https://api.bilibili.com/x/v2/reply/main?callback=jQuery17208590914915452643_1651207947683&jsonp=jsonp&next=0&type=1&oid=34491719&mode=3&plat=1&_=1651207949390

  2. https://api.bilibili.com/x/v2/reply/main?callback=jQuery33102399794496926384_1651209840924&jsonp=jsonp&next=0&type=1&oid=768445836&mode=3&plat=1&_=1651209840925

  3. 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主的uidself.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=0self.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=1self.q = queue.Queue()  # 用来存放爬取的数据,通过队列可以按顺序,使用多线程存入数据库或csv文件中# 这里我们用到了队列,好处在于,可以用多线程边爬边存,按顺序先进先出self.count = 1  # count变量指定主楼数,区别是评论还是评论的评论

2.获取视频oid和获取视频名称方法

​ 方法一:通过正则从response中选择以字符串aid开头的值并将其进行返回。

​ 方法二:通过BeautifulSoup4类获取视频名称,获取含有视频名称的标签,从而通过自带大string方法获取名称

(78条消息) Python中BeautifulSoup库的用法_阎_松的博客-CSDN博客_beautifulsoup库的作用

 # 获取视频 oiddef oid_get(self, BV):# 请求视频页面response = requests.get(url=self.homeUrl + BV).text# 用正则表达式 通过视频 bv 号获取 oidoid = re.findall("\"aid\":([0-9]*),", response)[0]#寻找以字符串aid开头的值print("oid:" + oid)return oiddef BVName_get(self,BV):# 请求视频页面response = requests.get(url=self.homeUrl + BV).textsoup = 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、时间戳、评论内容等等,其中主要信息为

  1. rpid:评论id
  2. oid:该视频的oid
  3. mid:账户的uid
  4. rcount :回复数
  5. ctime:时间戳
  6. like:点赞数
  7. member–>sign:用户标签,即用户的个性签名
  8. content–>message:评论内容
  9. replies:评论列表
  10. replies–>rpid:子评论的id
  11. 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 += 1for 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 = 1while 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=0print("该页没有评论......")returnelif replies == None:self.page = now - 1print("评论信息获取完成......")returnelif replies != None:now += 1for reply in replies:# 一级评论则去请求下一级评论if level_1:line = self.reply_clean(reply, self.count)self.count += 1else: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']    # 帐号的uidsign = 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-nameif not os.path.isdir(fileName):os.mkdir(fileName)os.chdir(fileName)fileName = str(BV) + "-" + str(BVName)  # BV视频的文件夹:BV-BVnameif 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:breakf.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主的uidself.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=0self.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=1self.q = queue.Queue()  # 用来存放爬取的数据,通过队列可以按顺序,使用多线程存入数据库或csv文件中# 这里我们用到了队列,好处在于,可以用多线程边爬边存,按顺序先进先出self.count = 1  # count变量指定主楼数,区别是评论还是评论的评论# 获取视频 oiddef oid_get(self, BV):# 请求视频页面response = requests.get(url=self.homeUrl + BV).text# 用正则表达式 通过视频 bv 号获取 oidoid = re.findall("\"aid\":([0-9]*),", response)[0]#寻找以字符串aid开头的值print("oid:" + oid)return oiddef BVName_get(self,BV):# 请求视频页面response = requests.get(url=self.homeUrl + BV).textsoup = 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 = 1while 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=0print("该页没有评论......")returnelif replies == None:self.page = now - 1print("评论信息获取完成......")returnelif replies != None:now += 1for reply in replies:# 一级评论则去请求下一级评论if level_1:line = self.reply_clean(reply, self.count)self.count += 1else: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']    # 帐号的uidsign = 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-nameif not os.path.isdir(fileName):os.mkdir(fileName)os.chdir(fileName)fileName = str(BV) + "-" + str(BVName)  # BV视频的文件夹:BV-BVnameif 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:breakf.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_script.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主的uidname进行数据获取。定义好之前分析的链接,进行请求,将请求结果保存在response1和response2中,然后选择我们需要的信息进行返回一个数据列表。

​ 获取到数据后,我们讲其存放到本地,保存为csv格式。首先创建一个up主信息文件夹,然后在该文件夹下寻找需存放up主的文件夹,如果存在,则在其后进行追加即可,若不存在,则创建再添加。保存成功后讲其信息进行控制台输出

3.updata类为:
import os
import time
import requests
import csvclass 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的uidupName = 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+nameif not os.path.isdir(fileName):flag = Trueos.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 datetimeclass MyTimer(object):def __init__(self, start_time, interval, callback_proc, args=None, kwargs=None):self.__timer = Noneself.__start_time = start_timeself.__interval = intervalself.__callback_pro = callback_procself.__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 = Noneclass AA:@staticmethoddef 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_script.py文件,其实把这两个分开也行,只不过笔者在树莓派运行的时候,因为环境的差异,自己写的模块在import的时候会有问题,因为时间原因,最简单直接的办法就是将这两个合并在一起,事实上我也是这样做的。

auto_script.py的内容:

# encoding: utf-8
import os
import time
from threading import Timer
from datetime import datetimeimport requests
import csvclass 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的uidupName = 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+nameif not os.path.isdir(fileName):flag = Trueos.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 = Noneself.__start_time = start_timeself.__interval = intervalself.__callback_pro = callback_procself.__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 = Noneif __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_script.py文件运行结果如下:

(4)videoDataDetection_script.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.textsoup = 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].contentstotal_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=' + BVresponse2 = 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_script.py代码:

# encoding: utf-8#视频数据监测脚本:每隔一段时间查询一次视频数据:
# 总播放数、历史累计弹幕数
# 点赞、投币、收藏、转发
import os
import time
import requests
import csv
from bs4 import BeautifulSoupfrom threading import Timer
from datetime import datetime#该类实现爬取保存一个视频的评论信息。
class videoDataVariety:#构造函数__init__,设置基础信息def __init__(self, mid, name, BV, cookies):self.mid = mid          #up主的uidself.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.BVNametimeNow=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.textsoup = 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].contentstotal_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=' + BVresponse2 = 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-nameif not os.path.isdir(fileName):os.mkdir(fileName)os.chdir(fileName)fileName = str(BV) + "-" + str(BVName)  # BV视频的文件夹:BV-BVnameif not os.path.isdir(fileName):os.mkdir(fileName)writeTableHead = Trueos.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 = Noneself.__start_time = start_timeself.__interval = intervalself.__callback_pro = callback_procself.__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 = Noneif __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)装必要的库

  1. wordcloud库

    打开网站:https://www.lfd.uci.edu/~gohlke/pythonlibs/

    (图片来自上述博客)

    命令行进入安装包所在位置,pip安装

    pip install wordcloud-1.8.1-cp39-cp39-win_amd64.whl
    

  1. jieba库

    pip install jieba
    
  2. pandas库

    pip install pandas
    

3)csv获取评论列数据

​ 读取文件后根据csv操作读取列数据并进行返回。

column_content_get代码
import csvdef 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 npdef ci_Yun_Tu(self):returnValue = self.column_content_get(self.mid, self.Name, self.BV, self.BVName, 7) #在文件中,评论所在列为第8列,下标为7text = " ".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_script.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 = 0femaleNum = 0unknownNum = 0for i in sexColumn:if (i == '男'): maleNum += 1elif (i == '女'): femaleNum += 1elif (i == '保密'): unknownNum += 1inner_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  # 点赞前20series_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:14xx_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,不同视频总播放数、历史累计弹幕数、点赞、投币、收藏、转发的可视化。

Document (pyecharts.org)

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' + v1BVNameList.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_script #导入之前自己写的模块
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_script.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构成的listfor 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' + v1BVNameList.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-BVNametimeArray = time.localtime(videoData_content_get_Return_newListList[0][0])      #获取横评视频时间genertTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)  # 生成时间,时间戳转为标准时间格式,2022-05-05 19:15:14titleName = 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_script import videoDataVariety#可视化(词云图、饼图、柱状图、漏斗图、折线图)
class visualization:def __init__(self, mid, Name, BV, BVName):self.mid = midself.Name = Nameself.BV = BVself.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列,下标为7text = " ".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 = 0femaleNum = 0unknownNum = 0for i in sexColumn:if (i == '男'): maleNum += 1elif (i == '女'): femaleNum += 1elif (i == '保密'): unknownNum += 1inner_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  # 点赞前20series_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:14xx_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 columndef 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_script.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构成的listfor 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' + v1BVNameList.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-BVNametimeArray = time.localtime(videoData_content_get_Return_newListList[0][0])      #获取横评视频时间genertTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)  # 生成时间,时间戳转为标准时间格式,2022-05-05 19:15:14titleName = 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")       #保存输出)cif __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和BVNamedatalist1.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_script.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)

【22】Document (pyecharts.org)

【23】60 种常用可视化图表,该怎么用?

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.

【21】pyecharts 设置地图大小错误 - 简书 (jianshu.com)

【22】Document (pyecharts.org)

【23】60 种常用可视化图表,该怎么用?

_【超详细指北】python大作业!相关推荐

  1. kaggle经典题--“泰坦尼克号”--0.8275准确率--东北大学20级python大作业开源(附详细解法与全部代码以及实验报告)

    kaggle经典题--"泰坦尼克号"--0.8275准确率--东北大学20级python大作业开源(附详细解法与全部代码以及实验报告) 前言 开发环境 一.导入包: 二.实验数据的 ...

  2. Python大作业——两种方法设计计算器(使用wx库)。

    python大作业 本次设计用Pycharm开发工具,Python语言进行主要功能是基于图形用户面的多功能.该计算器的主要功能是加.减.乘.除等常规运算以及指数,对数,三角函数等科学运算功能.该计算器 ...

  3. Python大作业-网络爬虫程序

    简介 此程序是本人大三时期的Python大作业,初学Python后所编写的一个程序,是一个网络爬虫程序,可爬取指定网站的信息. 本程序爬取的网站是Bangumi-我看过的动画,Bangumi是一个专注 ...

  4. Python011: Python大作业之移动的小火车动画(四)代码实现

    书接上文:Python010: Python大作业之移动的小火车动画(三)结果显示 0.注意: ​ 该项目使用的库和资源说明如下: pygame 2.0.1 (SDL 2.0.14, Python 3 ...

  5. Python大作业之就诊卡管理信息系统[原创]

    Python大作业之就诊卡管理信息系统[原创] 项目地址: 网页链接:http://patientcard.dreamtownapi.com/ 后端地址:http://django.dreamtown ...

  6. Python大作业——爬虫+可视化+数据分析+数据库(数据库篇)

    相关链接 Python大作业--爬虫+可视化+数据分析+数据库(简介篇) Python大作业--爬虫+可视化+数据分析+数据库(爬虫篇) Python大作业--爬虫+可视化+数据分析+数据库(数据分析 ...

  7. pygame飞机大战小游戏(python大作业)

    一.项目背景 python大作业,在查看了老师给的链接发现教学视频不完整,所以借用了同学的<Python编程 从入门到实践>中的一个项目,学习模仿. 二.游戏具体介绍 这是一款由辉辉亲自打 ...

  8. python大作业01

    python大作业01 使用组合数据类型(列表,元组,字典,集合)中任意一种存储方式,完成一个简单的学生管理系统,可以多次录入学生信息到系统并完成基本的 增加.删除.修改.查询操作. 学生信息: 学号 ...

  9. Python大作业——爬虫+可视化+数据分析+数据库(数据分析篇)

    个人博客 Python大作业--爬虫+可视化+数据分析+数据库(简介篇) Python大作业--爬虫+可视化+数据分析+数据库(爬虫篇) Python大作业--爬虫+可视化+数据分析+数据库(可视化篇 ...

最新文章

  1. 双一流高校出新规:研究生未经导师同意发论文,不得用于毕业、评奖!
  2. 送一个全新的AirPods Pro
  3. Spring(一)——用Spring IOC容器创建对象
  4. eclipse和myeclipse中如何关闭自动补全括号,花括号,双引号等功能
  5. UI交互设计教程分享:提高界面交互体验的“葵花宝典”
  6. 用数组存储字符C进行回文检测
  7. 个人企业作品网站导航页源码
  8. jq监听input type=file发生改变,即选择文件,并获取文件名称
  9. excel切片器显示错误_Office 2016中报表用户的新Excel切片器功能
  10. visio2003 数据表模型中显示字段类型和注释
  11. 雷云3启动无响应解决办法
  12. 怎么把PDF文件转换成Word?三招教你轻松搞定
  13. PTA~习题8-4 报数 (20分) 2020年11月
  14. 简述计算机软件与硬件,简述计算机硬件与软件的关系。 - 问答库
  15. 晶振电路并联一个电阻作用
  16. 汪子熙趣味接龙游戏实现的参考资源
  17. c8051f单片机c语言看门狗例程,stc单片机看门狗喂狗程序
  18. 技术是可以量化的,稳定性性能和资产个数
  19. window10快捷键
  20. 接口测试入门番外篇——如何在谷歌浏览器中集成postman这款接口测试工具呢?

热门文章

  1. Java实现快递管理系统一(views)
  2. ar vr mr 计算机技术,VR技术是什么?AR、MR又是什么?
  3. 2021年上半年系统集成项目管理工程师上午试题参考解析(二)
  4. rpm -e卸载mysql_rpm
  5. 边缘计算在视频直播场景的应用与实践
  6. python结束线程_python终止线程
  7. 高数_第5章常微分方程_二阶微分方程
  8. GPS北斗模块串口助手输出测试
  9. springcloud微服务
  10. 深度学习模型轻量化(上)