|
|
|
@ -0,0 +1,408 @@
|
|
|
|
|
import bilibili_api
|
|
|
|
|
import csv
|
|
|
|
|
from lxml import etree
|
|
|
|
|
import os # 判断文件是否存在
|
|
|
|
|
import time
|
|
|
|
|
from time import sleep # 设置等待,防止反爬
|
|
|
|
|
import random # 生成随机数
|
|
|
|
|
# 导入请求模块
|
|
|
|
|
import requests
|
|
|
|
|
# 正则模块
|
|
|
|
|
import re
|
|
|
|
|
# 导入json模块
|
|
|
|
|
import json
|
|
|
|
|
# 导入格式化输出模块
|
|
|
|
|
import subprocess
|
|
|
|
|
import pandas as pd # 用于数据分析及文件格式转换
|
|
|
|
|
import jieba # 用于分词统计出现的关键词
|
|
|
|
|
from wordcloud import WordCloud # 筛选词云图展示的词
|
|
|
|
|
import matplotlib.pyplot as plt # 实现绘图可视化
|
|
|
|
|
from imageio import imread # 图片库,读取照片RGB内容,转换照片格式
|
|
|
|
|
# python通过调用warnings模块中定义的warn()函数来发出警告。我们可以通过警告过滤器进行控制是否发出警告消息
|
|
|
|
|
import warnings
|
|
|
|
|
from pyecharts.charts import Bar
|
|
|
|
|
from pyecharts import options as opts
|
|
|
|
|
from pyecharts.faker import Faker
|
|
|
|
|
|
|
|
|
|
#######################################################################################
|
|
|
|
|
#以下代码爬取bilibili视频热门榜前一百的BV号
|
|
|
|
|
POPULAR_URL = "https://api.bilibili.com/x/web-interface/popular"#哔哩哔哩热门的官方网址
|
|
|
|
|
#设置请求头,解决UA反爬
|
|
|
|
|
HEADERS = {
|
|
|
|
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_popular_list():
|
|
|
|
|
"""
|
|
|
|
|
获取排行榜1-100
|
|
|
|
|
:param pn:
|
|
|
|
|
:return: All bvid_list
|
|
|
|
|
"""
|
|
|
|
|
bvidList = []#存bv号
|
|
|
|
|
#一页20个视频,所以获取前5页
|
|
|
|
|
for i in range(1, 6):
|
|
|
|
|
query = "pn=" + str(i)# 构建GET请求的参数,设置当前页数
|
|
|
|
|
r = requests.get(POPULAR_URL, headers=HEADERS, params=query)
|
|
|
|
|
resultList = r.json()['data']['list']# 从响应中获取当前页的视频列表
|
|
|
|
|
for item in resultList:
|
|
|
|
|
bvidList.append(
|
|
|
|
|
bilibili_api.aid2bvid(
|
|
|
|
|
item['aid'] #得到bv号。bilibili_api.aid2bvid为aid转bvid,由bilibili_api提供。
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return bvidList
|
|
|
|
|
#params=query的作用是将我们构建的参数字符串添加到GET请求中,以便于向API请求特定的数据。
|
|
|
|
|
|
|
|
|
|
# resultList = r.json()['data']['list']
|
|
|
|
|
# 当发送GET请求并获取到响应后,响应的内容通常是一个JSON格式的字符串。
|
|
|
|
|
# r.json()表示将响应r的JSON字符串转换为Python中的字典对象。由于响应的JSON对象中,我们需要获取的视频列表保存在"data"字段下的"list"字段中,
|
|
|
|
|
# 因此我们可以使用r.json()['data']['list']的方式获取到视频列表,将其赋值给变量resultList。最终,resultList中存储的就是当前页的视频列表。
|
|
|
|
|
print ("以下为bilibili视频热门榜前一百的BV号")
|
|
|
|
|
for i in get_popular_list():
|
|
|
|
|
print(i)
|
|
|
|
|
############################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#################################################################################################
|
|
|
|
|
#爬取弹幕
|
|
|
|
|
class BiliSpider:
|
|
|
|
|
def __init__(self, BV):
|
|
|
|
|
# 构造要爬取的视频url地址
|
|
|
|
|
self.BVurl = "https://m.bilibili.com/video/" + BV#指定bv号视频的网址
|
|
|
|
|
self.headers = {
|
|
|
|
|
"User-Agent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Mobile Safari/537.36"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 获取该视频网页的内容
|
|
|
|
|
def getHTML_content(self):
|
|
|
|
|
# 获取该视频网页的内容
|
|
|
|
|
# 发送GET请求获取该视频网页的内容
|
|
|
|
|
response = requests.get(self.BVurl, headers=self.headers)
|
|
|
|
|
#将HTTP响应的二进制内容解码为字符串。
|
|
|
|
|
html_str = response.content.decode()
|
|
|
|
|
return html_str
|
|
|
|
|
#为了后续使用XPath表达式从HTML文本中提取数据。
|
|
|
|
|
|
|
|
|
|
def get_script_list(self, str):
|
|
|
|
|
# 将获取到的 HTML 字符串解析为一个 Element 对象,方便后续使用 XPath 语法进行解析。
|
|
|
|
|
html = etree.HTML(str)
|
|
|
|
|
#cid视频编号在script中
|
|
|
|
|
script_list = html.xpath("//script/text()")
|
|
|
|
|
return script_list
|
|
|
|
|
|
|
|
|
|
def gen_xml(self, code):
|
|
|
|
|
# 组装成要请求的xml地址
|
|
|
|
|
xml_url = "https://comment.bilibili.com/{}.xml".format(code)
|
|
|
|
|
return xml_url
|
|
|
|
|
|
|
|
|
|
# 弹幕包含在xml中的<d></d>中,取出即可
|
|
|
|
|
def get_word_list(self, str):
|
|
|
|
|
html = etree.HTML(str)
|
|
|
|
|
word_list = html.xpath("//d/text()")#xpath语法得到
|
|
|
|
|
return word_list
|
|
|
|
|
|
|
|
|
|
# Xpath不能解析指明编码格式的字符串,所以此处我们不解码,还是二进制文本
|
|
|
|
|
def parse_url(self, url):
|
|
|
|
|
#得到内容
|
|
|
|
|
response = requests.get(url, headers=self.headers)
|
|
|
|
|
return response.content
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
# 1.根据BV号获取网页内容
|
|
|
|
|
html_content = self.getHTML_content()
|
|
|
|
|
# 2.请求并获取script数据
|
|
|
|
|
script_list = self.get_script_list(html_content)
|
|
|
|
|
# 解析script数据,获取cid信息
|
|
|
|
|
for script in script_list:
|
|
|
|
|
if '[{"cid":' in script:
|
|
|
|
|
find_script_text = script#遍历 script_list 列表,找到包含 cid 字符串的 script 文本,
|
|
|
|
|
# 并将其保存在变量 find_script_text 中。
|
|
|
|
|
cid = find_script_text.split('[{"cid":')[1].split(',"page":')[0]#取出cid,cid 是视频的编号,它用于拼接评论的 xml 文件地址。
|
|
|
|
|
|
|
|
|
|
#通过两个 split() 方法将 find_script_text 字符串分割成多个部分,最终得到一个包含 cid 值的字符串。
|
|
|
|
|
# 其中,第一个 split() 方法根据 [{"cid": 字符串将 find_script_text 分割成两部分,即 '[{"cid":' 和 '其他部分',
|
|
|
|
|
# 然后取第二个部分(即 '其他部分')。接着,第二个 split() 方法根据 ',"page":' 字符串将第一步得到的字符串分割成两部分,即 cid 值和 '其他部分',
|
|
|
|
|
# 然后取第一个部分(即 cid 值)。这样就成功地提取出了 cid 的值。
|
|
|
|
|
|
|
|
|
|
# 根据cid信息拼接弹幕评论的xml文件url,由于哔哩哔哩的弹幕是保存在另一个网址的,所以我们需要把弹幕网址找出
|
|
|
|
|
xml_url = self.gen_xml(cid)
|
|
|
|
|
print(xml_url)
|
|
|
|
|
# 请求xml文件地址并解析
|
|
|
|
|
xml_str = self.parse_url(xml_url)
|
|
|
|
|
word_list = self.get_word_list(xml_str)
|
|
|
|
|
# 3.打印
|
|
|
|
|
for word in word_list:
|
|
|
|
|
print(word)
|
|
|
|
|
return word_list;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("请输入需要查看信息的视频BV号:")
|
|
|
|
|
BV = input()
|
|
|
|
|
#输出弹幕
|
|
|
|
|
spider = BiliSpider(BV)
|
|
|
|
|
danmu_list=spider.run()
|
|
|
|
|
|
|
|
|
|
#################################################################################################
|
|
|
|
|
# 写入弹幕
|
|
|
|
|
print("将弹幕数据写入danmu.csv中")
|
|
|
|
|
# 打开文件,使用 utf-8 编码,newline='' 参数用来处理每行数据之间存在空格行的问题。
|
|
|
|
|
f = open("danmu.csv", mode='w', encoding='utf-8', newline='') # newline='' 用来处理每行数据之间存在空格行的问题。
|
|
|
|
|
# 创建 csv writer 对象
|
|
|
|
|
csvwriter = csv.writer(f)
|
|
|
|
|
# 写入每条弹幕数据
|
|
|
|
|
for danmu in danmu_list:
|
|
|
|
|
csvwriter.writerow([danmu])
|
|
|
|
|
f.close()
|
|
|
|
|
##############################################################################################################
|
|
|
|
|
|
|
|
|
|
print("下面开始绘制词云图")
|
|
|
|
|
#绘制词云图
|
|
|
|
|
|
|
|
|
|
warnings.filterwarnings("ignore") # 忽略警告信息
|
|
|
|
|
# 读取弹幕CSV文件
|
|
|
|
|
data = pd.read_csv('danmu.csv')
|
|
|
|
|
# 将弹幕数据保存为txt格式,以便进行词云图的绘制,只能用纯文本格式所以用txt
|
|
|
|
|
data.to_csv('danmu.txt', sep='\t', index=False)
|
|
|
|
|
#读取文本文件,并使用lcut()方法进行分词
|
|
|
|
|
with open("../大作业/danmu.txt",encoding="utf-8") as f:
|
|
|
|
|
txt = f.read()
|
|
|
|
|
txt = txt.split()
|
|
|
|
|
data_cut = [jieba.lcut(x) for x in txt]
|
|
|
|
|
#首先,将文本文件读入到变量txt中,然后使用Python内置的字符串方法split()将文本按照空格切分成一个列表。
|
|
|
|
|
# 接下来,使用列表解析式[jieba.lcut(x) for x in txt],对每个字符串进行中文分词,将结果保存到列表data_cut中。
|
|
|
|
|
#其中,jieba.lcut(x)是jieba库中的一个分词函数,用于对中文文本进行分词,返回一个列表。x是待分词的文本。
|
|
|
|
|
|
|
|
|
|
#读取停用词(不展示的词),该文件为提前设置好的,一个词显示为一行
|
|
|
|
|
with open("../大作业/stoplist.txt",encoding="utf-8") as f:
|
|
|
|
|
stop = f.read()
|
|
|
|
|
stop = stop.split()
|
|
|
|
|
stop = [" ","道","说道","说"] + stop
|
|
|
|
|
#得到去掉停用词之后的最终词,为了让词云更好看易懂
|
|
|
|
|
s_data_cut = pd.Series(data_cut) # 将数据转为一维数组,与Python基本的数据结构List也很相近,
|
|
|
|
|
# 其区别是:List中的元素可以是不同的数据类型,而Array(Numpy)和Series中则只允许存储相同的数据类型
|
|
|
|
|
all_words_after = s_data_cut.apply(lambda x:[i for i in x if i not in stop])
|
|
|
|
|
#然后,对 s_data_cut 中每一个分词后的弹幕数据,使用 lambda 函数和列表推导式进行去停用词处理,得到去除停用词后的所有词语列表 all_words_after。
|
|
|
|
|
|
|
|
|
|
#具体来说,lambda x: [i for i in x if i not in stop] 的作用是遍历 x 列表中的每一个元素(即分词后的一个弹幕数据),
|
|
|
|
|
#如果这个元素不在停用词列表 stop 中,就将其添加到返回值列表中。最终,apply() 方法会将这个函数应用到 s_data_cut 中的每一个弹幕数据中,
|
|
|
|
|
# 得到一个包含所有去除停用词后的词语列表的 Series 数据结构 all_words_after。
|
|
|
|
|
#词频统计
|
|
|
|
|
all_words = []#创建一个空列表all_words,用来存储所有的词语。
|
|
|
|
|
for i in all_words_after:#遍历all_words_after中的每一个分词结果,即遍历所有去除停用词后的分词结果。
|
|
|
|
|
all_words.extend(i)#对于每个分词结果,将其中的所有词语添加到all_words列表中。
|
|
|
|
|
word_count = pd.Series(all_words).value_counts()
|
|
|
|
|
#pd.Series()将all_words列表转化为pandas的Series数据类型,方便后续统计词频。
|
|
|
|
|
#value_counts()方法统计每个词语出现的次数,生成词频统计结果word_count。
|
|
|
|
|
|
|
|
|
|
#词云图的绘制
|
|
|
|
|
def view():
|
|
|
|
|
# (1)读取背景图片
|
|
|
|
|
back_picture = imread("../大作业/background.jpg")
|
|
|
|
|
|
|
|
|
|
# (2)设置词云参数
|
|
|
|
|
wc = WordCloud(
|
|
|
|
|
font_path="C:/Windows/Fonts/simfang.ttf",
|
|
|
|
|
background_color="white",
|
|
|
|
|
max_words=2000,
|
|
|
|
|
mask=back_picture,
|
|
|
|
|
max_font_size=200,
|
|
|
|
|
random_state=42#生成随机颜色的种子值
|
|
|
|
|
)
|
|
|
|
|
wc2 = wc.fit_words(word_count) # fit_words() 方法来根据词频信息生成词云图
|
|
|
|
|
|
|
|
|
|
# (3)绘制词云图
|
|
|
|
|
plt.figure(figsize=(16, 8))
|
|
|
|
|
plt.imshow(wc2)
|
|
|
|
|
plt.axis("off")#关闭坐标轴显示
|
|
|
|
|
plt.show()
|
|
|
|
|
wc.to_file("../大作业/ciyun.png") # 生成的图片存储路径
|
|
|
|
|
#词云图绘制完成
|
|
|
|
|
print("词云图绘制完成")
|
|
|
|
|
view()
|
|
|
|
|
|
|
|
|
|
##################################################################################################
|
|
|
|
|
print("下面开始爬取指定视频的评论")
|
|
|
|
|
|
|
|
|
|
# 请求头
|
|
|
|
|
headersp = {
|
|
|
|
|
# 需定期更换cookie,否则location爬不到
|
|
|
|
|
'cookie':'buvid3=114CEDA0-85FF-4DC4-9687-7E230EC3922634763infoc; LIVE_BUVID=AUTO7516290399110767; balh_is_closed=; balh_server_inner=__custom__; i-wanna-go-back=-1; buvid_fp_plain=undefined; CURRENT_BLACKGAP=0; blackside_state=0; is-2022-channel=1; DedeUserID=341891643; DedeUserID__ckMd5=868969f705549646; b_ut=5; fingerprint3=91317b33d1486edea477476111226160; hit-dyn-v2=1; _uuid=14D102ACC-1AC2-5B43-F613-CD2103EA94C9977773infoc; go_old_video=1; i-wanna-go-feeds=-1; b_nut=100; rpdid=|(J|lmRkmuRl0JuYY)l)~)km; buvid4=29001660-0B06-78DE-F2B4-3FD7CA49439233181-022012118-dWg1L%2Bln%2Fbvj%2FYnR0RiUpA%3D%3D; fingerprint=4a27fc308659d4faf13e9361e621c75f; buvid_fp=6d7dd3797b522a2f84b750e9b8fb35ba; header_theme_version=CLOSE; hit-new-style-dyn=1; CURRENT_FNVAL=4048; nostalgia_conf=-1; CURRENT_PID=b369d710-c92e-11ed-b6f8-bf9093b777e4; CURRENT_QUALITY=80; bp_video_offset_341891643=784002501477138600; FEED_LIVE_VERSION=V_LIVE_2; share_source_origin=QQ; bsource=share_source_qqchat; bp_t_offset_341891643=787671082207281189; SESSDATA=742a1cb4%2C1697807586%2C40a4c%2A41; bili_jct=6eb7012a2383b24993a845c885caac7a; b_lsid=45C10432E_187AE8DAF27; sid=5wujmgl0; innersign=1; PVID=5',
|
|
|
|
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.47'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bv2av(bid):
|
|
|
|
|
"""把哔哩哔哩视频的bv号转成av号"""
|
|
|
|
|
table = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'# 哔哩哔哩bv号转av号的对照表
|
|
|
|
|
tr = {} # 储存表格
|
|
|
|
|
for i in range(58):
|
|
|
|
|
tr[table[i]] = i# 生成对照表
|
|
|
|
|
s = [11, 10, 3, 8, 4, 6]# 与对照表对应的权重
|
|
|
|
|
r = 0# 计算结果
|
|
|
|
|
for i in range(6):
|
|
|
|
|
r += tr[bid[s[i]]] * 58 ** i# 根据对应表将bv号转换成av号
|
|
|
|
|
aid = (r - 8728348608) ^ 177451812# 通过异或加密后得到av号
|
|
|
|
|
return aid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_comment(v_aid, v_bid):
|
|
|
|
|
"""
|
|
|
|
|
爬取B站评论数据
|
|
|
|
|
:param v_aid: B站视频的aid号
|
|
|
|
|
:param v_bid: B站视频的bid号
|
|
|
|
|
:return: None
|
|
|
|
|
"""
|
|
|
|
|
# 循环页码爬取评论
|
|
|
|
|
for i in range(max_page):
|
|
|
|
|
try:
|
|
|
|
|
sleep(random.uniform(0, 1)) # 随机等待,防止反爬
|
|
|
|
|
url = "https://api.bilibili.com/x/v2/reply/main?csrf=bf9a78c05400af2f7bdac7947b836cc8&mode=3&next={}&oid={}&plat=1&type=1".format(
|
|
|
|
|
i, v_aid) # 请求地址
|
|
|
|
|
response = requests.get(url, headers=headersp, ) # 发送请求
|
|
|
|
|
data_list = response.json()['data']['replies'] # 解析评论数据
|
|
|
|
|
#response.json()'是将API响应解析为json格式,'[data]'表示选择json中'data'字段的值,
|
|
|
|
|
# '[replies]'表示选择'data'中的'replies'字段的值,最终得到的是评论数据的列表。
|
|
|
|
|
print('正在爬取B站评论[{}]: 第{}页,共{}条评论'.format(bid, i + 1, len(data_list)))
|
|
|
|
|
comment_list = [] # 评论内容
|
|
|
|
|
location_list = [] # IP属地
|
|
|
|
|
user_list = [] # 评论作者
|
|
|
|
|
like_list = [] # 点赞数
|
|
|
|
|
# 循环爬取每一条评论数据
|
|
|
|
|
for a in data_list:
|
|
|
|
|
# 评论内容
|
|
|
|
|
comment = a['content']['message']#键值对,content中的message字段,即评论的正文内容。
|
|
|
|
|
comment_list.append(comment)
|
|
|
|
|
# IP属地
|
|
|
|
|
try:
|
|
|
|
|
location = a['reply_control']['location']
|
|
|
|
|
except:
|
|
|
|
|
location = ""
|
|
|
|
|
location_list.append(location.replace("IP属地:", ""))
|
|
|
|
|
# 评论作者
|
|
|
|
|
user = a['member']['uname']
|
|
|
|
|
user_list.append(user)
|
|
|
|
|
# 点赞数
|
|
|
|
|
like = a['like']
|
|
|
|
|
like_list.append(like)
|
|
|
|
|
# 把列表拼装为DataFrame数据
|
|
|
|
|
df = pd.DataFrame({
|
|
|
|
|
'视频链接': 'https://www.bilibili.com/video/' + v_bid,
|
|
|
|
|
'评论页码': (i + 1),
|
|
|
|
|
'评论作者': user_list,
|
|
|
|
|
'IP属地': location_list,
|
|
|
|
|
'点赞数': like_list,
|
|
|
|
|
'评论内容': comment_list,
|
|
|
|
|
})
|
|
|
|
|
if os.path.exists(outfile):
|
|
|
|
|
header = False
|
|
|
|
|
else:
|
|
|
|
|
header = True
|
|
|
|
|
#检查输出文件是否存在。如果存在,将header设置为False,这样新的数据将被追加到文件末尾而不是覆盖掉原有的数据。
|
|
|
|
|
# 如果输出文件不存在,将header设置为True,这样将创建一个新的文件并添加列名作为文件的第一行。
|
|
|
|
|
# 把评论数据保存到csv文件
|
|
|
|
|
df.to_csv(outfile, mode='a+', encoding='utf_8_sig', index=False, header=header)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print('爬评论发生异常: {},继续..'.format(str(e)))
|
|
|
|
|
|
|
|
|
|
print('爬虫开始执行!')
|
|
|
|
|
bid_list = [BV]
|
|
|
|
|
# 评论最大爬取页(每页20条评论)
|
|
|
|
|
max_page = 10
|
|
|
|
|
# 获取当前时间戳
|
|
|
|
|
now = time.strftime("%Y%m%d%H%M%S", time.localtime())
|
|
|
|
|
# 输出文件名
|
|
|
|
|
outfile = 'b站评论_{}.csv'.format(now)
|
|
|
|
|
# 循环爬取这几个视频的评论
|
|
|
|
|
for bid in bid_list:
|
|
|
|
|
# 转换aid
|
|
|
|
|
aid = bv2av(bid=bid)
|
|
|
|
|
# 爬取评论
|
|
|
|
|
get_comment(v_aid=aid, v_bid=bid)
|
|
|
|
|
print('爬虫正常结束!')
|
|
|
|
|
#################################################################################3
|
|
|
|
|
# 去除重复
|
|
|
|
|
df = pd.read_csv(outfile)
|
|
|
|
|
# 去重
|
|
|
|
|
df.drop_duplicates(subset=['评论作者','评论内容'], inplace=True, keep='first')
|
|
|
|
|
#drop_duplicates() 方法中的 subset 参数指定需要去重的列名列表
|
|
|
|
|
# 再次保存csv文件
|
|
|
|
|
df.to_csv('去重后_' + outfile, index=False, encoding='utf_8_sig')
|
|
|
|
|
print('评论数据清洗完成')
|
|
|
|
|
print("评论爬取成功并已存入指定csv文件")
|
|
|
|
|
########################################################################
|
|
|
|
|
#绘制柱形图
|
|
|
|
|
print("下面开始根据评论数据绘制柱形图")
|
|
|
|
|
# 读取去重后的评论数据文件
|
|
|
|
|
df = pd.read_csv(rf"去重后_b站评论_{now}.csv")
|
|
|
|
|
# 按照点赞数进行排序,选择前20个评论
|
|
|
|
|
df1 = df.sort_values(by="点赞数",ascending=False).head(20)
|
|
|
|
|
# 绘制柱形图,x轴为评论内容,y轴为点赞数
|
|
|
|
|
c1 = (
|
|
|
|
|
Bar()#Bar()类创建了一个柱形图对象
|
|
|
|
|
.add_xaxis(df1["评论内容"].to_list())
|
|
|
|
|
.add_yaxis("点赞数", df1["点赞数"].to_list(), color=Faker.rand_color())
|
|
|
|
|
.set_global_opts(
|
|
|
|
|
title_opts=opts.TitleOpts(title="评论热度Top20"),# 设置图表标题
|
|
|
|
|
datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_="inside")],# 设置数据缩放
|
|
|
|
|
)
|
|
|
|
|
.render(path='bar.html')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print("柱形图绘制成功,保存为bar.html")
|
|
|
|
|
############################################################################################################
|
|
|
|
|
print("下面开始下载指定视频")
|
|
|
|
|
|
|
|
|
|
print("下载视频")
|
|
|
|
|
url = f'https://www.bilibili.com/video/{BV}/'
|
|
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
|
"referer": "https://api.bilibili.com/",
|
|
|
|
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.12022 SLBChan/105"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp = requests.get(url=url, headers=headers)
|
|
|
|
|
# print(resp.text)
|
|
|
|
|
# 视频标题
|
|
|
|
|
title = re.findall('name="title" content="(.*?)_哔哩哔哩_bilibili">', resp.text)[0]
|
|
|
|
|
title = title.replace(" ","")
|
|
|
|
|
#re.findall 匹配 HTML 标签 <meta name="title" content="XXX"> 中的标题信息,匹配到的结果是一个列表,
|
|
|
|
|
# 列表中只有一个元素,即视频的标题。接着使用字符串方法 replace 去掉标题中的空格,最终返回视频的标题。
|
|
|
|
|
|
|
|
|
|
# 视频相关信息
|
|
|
|
|
video_info = re.findall('<script>window.__playinfo__=(.*?)</script', resp.text)[0]
|
|
|
|
|
#视频信息会嵌入在<script>标签中的JavaScript代码中,其中包含了视频的相关信息,包括清晰度、视频链接、音频链接等等,是解析视频的重要数据来源。
|
|
|
|
|
# 使用正则表达式匹配<script>标签内容中的window.__playinfo__字符串所对应的值,即为视频信息的json字符串。
|
|
|
|
|
|
|
|
|
|
# 数据类型转换
|
|
|
|
|
json_data = json.loads(video_info)
|
|
|
|
|
# 字典取值 键值对-->根据冒号左边的内容【键】,提取冒号右边的内容【值】
|
|
|
|
|
|
|
|
|
|
# pprint(json_data)
|
|
|
|
|
# 提取音频url地址
|
|
|
|
|
audio_url = json_data['data']['dash']['audio'][0]['baseUrl'] # 有声音没有画面
|
|
|
|
|
video_url = json_data['data']['dash']['video'][0]['baseUrl'] # 有画面没有声音
|
|
|
|
|
print(title)
|
|
|
|
|
print(audio_url)
|
|
|
|
|
print(video_url)
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
保存数据,保存本地文件夹里面
|
|
|
|
|
'''
|
|
|
|
|
# 发送请求过去二进制数据内容
|
|
|
|
|
audio_content = requests.get(url=audio_url, headers=headers).content # 音频内容
|
|
|
|
|
video_content = requests.get(url=video_url, headers=headers).content # 视频内容
|
|
|
|
|
with open('video\\' + title + '.mp3', mode='wb') as a:
|
|
|
|
|
a.write(audio_content)#音频
|
|
|
|
|
with open('video\\' + title + '.mp4', mode='wb') as v:
|
|
|
|
|
v.write(video_content)
|
|
|
|
|
|
|
|
|
|
cmd = f'ffmpeg -i video\\{title}.mp4 -i video\\{title}.mp3 -c:v copy -c:a aac -strict experimental video\\{title}output.mp4'
|
|
|
|
|
subprocess.run(cmd)
|
|
|
|
|
#调用FFmpeg软件实现将视频文件和音频文件进行合并的操作。cmd变量中的命令是使用FFmpeg软件的命令行参数,
|
|
|
|
|
#指定了输入的视频和音频文件路径以及输出的文件名。最后通过subprocess.run()方法执行该命令
|
|
|
|
|
|
|
|
|
|
#视频下载完毕
|
|
|
|
|
print("视频下载成功,结果保存在video文件夹里面")
|
|
|
|
|
print("工作已完成,请退出")
|