(1.1)在Github仓库中新建一个学号为名的文件夹,同时在博客正文首行给出作业Github链接。(2’)
我的作业链接–>https://github.com/fzuwxx/depot
一、PSP表格
(2.1)在开始实现程序之前,在附录提供的PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间。(3’)
(2.2)在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(3’)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
· Estimate | · 估计这个任务需要多少时间 | 570 | 655 |
Development | 开发 | 500 | 550 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 130 |
· Design Spec | · 生成设计文档 | 30 | 20 |
· Design Review | · 设计复审 | 15 | 7 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 8 |
· Design | · 具体设计 | 60 | 45 |
· Coding | · 具体编码 | 180 | 290 |
· Code Review | · 代码复审 | 30 | 20 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 75 |
Reporting | 报告 | 60 | 60 |
· Test Repor | · 测试报告 | 45 | 40 |
· Size Measurement | · 计算工作量 | 15 | 8 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 60 |
· 合计 | 600 | 680 |
二、任务要求的实现
(3.1)项目设计与技术栈。 从阅读完题目到完成作业,这一次的任务被你拆分成了几个环节?你分别通过什么渠道、使用什么方式方法完成了各个环节?列出你完成本次任务所使用的技术栈。(5’)
本次任务被我分为6个环节,分别是:任务规划、代码结构、代码编写、代码测试、代码改进以及总结报告。
任务规划: 通过题目所提供的邹欣老师的博客了解了什么是PSP,从而根据PSP表格将任务进行了细分与时间规划,让我能够高效且迅速的完成任务。
代码结构: 通过浏览博客,大致了解了爬取B站视频弹幕的步骤,从而对我的代码结构进行了划分,分别为获取视频bv号、获取视频cid号、爬取弹幕文件、写入excel、绘制词云图、进行数量统计。这样能够使得我的代码模块化,逻辑清晰更加易于实现。
代码编写: 本次作业中我采用的是Python语言,通过csdn中爬虫的相关博客学习到了爬虫的基本流程和代码实现,在复现的基础上加入自己的想法,让代码变的更加完整、优美和易读。
代码测试: 代码编写完成后,先是通过SonarLint进行了Code Quality Analysis;再通过pycharm专业版自带的Profile对代码进行性能的分析,从而发现代码存在的问题和可提升空间。
代码改进: 通过上网查询相关资料,逐步的改进代码使其通过SonarLint的Code Quality Analysis;再通过多进程实现代码性能的提升和运行时间的缩短。最终SonarLint的Code Quality Analysis分析结果如下:
总结报告:在完成上述环节之后,我拿出了一个小时的时间进行这次作业的总结,然后再拿出一个小时的时间进行了博客的撰写,加深对这次作业的理解。
技术栈: requests库、正则表达式、文件的读写、图形的绘画、多进程开发、浏览器开发者工具。
(3.2)爬虫与数据处理。 说明业务逻辑,简述代码的设计过程(例如可介绍有几个类,几个函数,他们之间的关系),并对关键的函数或算法进行说明。(20’)
业务逻辑:通过调用Bilibili的API,搜索指定关键词下的视频,并获取这些视频的弹幕数据。然后将弹幕数据保存到CSV文件中,并对弹幕进行统计,输出出现次数最多的前20个弹幕。此外,还使用WordCloud库生成弹幕的词云图。最后,计算程序运行时间并输出相关信息。
设计思路:通过调用Bilibili的搜索API获取与“日本核污染水排海”关键词相关的前300个视频信息。然后,从视频信息中获取每个视频的bvid号码,并通过bvid号码获取视频的cid。接下来,使用cid获取弹幕数据,并将弹幕数据保存到CSV文件中。同时,对弹幕数据进行统计分析,包括计算每个弹幕出现的次数,并将结果输出为CSV文件。最后,使用jieba库对弹幕数据进行中文分词处理,生成词云图并保存为图片文件。为了提高爬取速度,使用了多进程的方式进行并发爬取。每个进程负责爬取一页视频(20个)的弹幕数据.
关键函数说明:
(一)、该函数通过调用搜索API获取“日本核污染水排海”相关的第page页第pos个视频的弹幕数据。
def get_bvid(page, pos):
"""
通过搜索api“https://api.bilibili.com/x/web-interface/search/all/v2?page=&keyword=”获取前300个视频的bvid,
其中page为1-15,keyword为“日本核污染水排海”
"""
# 构造搜索视频的API请求URL
_url = 'https://api.bilibili.com/x/web-interface/search/all/v2?page=' + str(page+1) + '&keyword=日本核污染水排海'
# 构造请求头,包括用户代理信息和cookie
_headers = {
'''......'''
}
res = requests.get(url=_url, headers=_headers).text # 发起GET请求,获取响应结果的文本形
json_dict = json.loads(res) # 将获取的响应结果解析为Python字典对象
return json_dict["data"]["result"][11]["data"][pos]["bvid"] # 返回视频的bvid号
(二)、根据传入的bvid号,发送API请求并解析响应结果,从中提取出cid,并作为函数返回值。
def get_cid(bvid):
# 构造API请求URL
url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + str(bvid) + '&jsonp=jsonp'
res = requests.get(url).text # 发起GET请求,获取响应结果的文本形式
json_dict = json.loads(res) # 将获取的网页json编码字符串转换为Python对象
print(json_dict) # 打印整个转换后的结果,用于调试
return json_dict["data"][0]["cid"] # 返回获取到的视频信息的cid值
(三)、通过传入的cid,发送API请求并对响应结果进行编码处理,使用正则表达式提取数据,最后将数据作为函数返回值。
def get_data(cid):
try:
# 构造API请求URL
final_url = "https://api.bilibili.com/x/v1/dm/list.so?oid=" + str(cid)
final_res = requests.get(final_url) # 发起GET请求获取数据
final_res.encoding = 'utf-8' # 设置编码为utf-8
final_res = final_res.text # 获取文本形式
pattern = re.compile('<d.*?>(.*?)</d>') # 使用正则表达式提取数据
data = pattern.findall(final_res)
return data # 返回数据
# 异常处理
except Exception as e:
print("执行get_data失败:", e) # 打印错误信息
(四)、使用追加模式以UTF-8编码将数据写入CSV文件。
def save_to_file(data):
try:
# 打开 CSV 文件,以追加模式写入数据
with open('danmu_data.csv', 'a', newline='', encoding='utf-8') as file:
writer = csv.writer(file) # 创建 CSV writer 对象
# 遍历数据列表,逐行写入 CSV 文件
for d in data:
writer.writerow([d])
# 异常处理
except Exception as e:
print("执行保存文件报错:", e) # 打印错误信息
(五)、通过读取CSV文件中的弹幕数据,使用matplotlib库绘制水平条形图可视化数量前20的弹幕以及出现次数。
def print_danmu():
try:
# 读取csv文件,从第一列开始读取数据
all_danmu = pd.read_csv(danmu_file, header=None, encoding='utf-8')
counter = all_danmu.stack().value_counts() # 统计每个弹幕出现的次数
top_20 = counter.head(20).reset_index() # 转换为DataFrame并重置索引
top_20.columns = ['弹幕', '出现次数'] # 重命名列名
print(top_20)
counter.to_csv('danmu_counter.csv') # 统计弹幕出现次数并存入csv
# 异常处理
except Exception as e:
print("执行输出弹幕报错:", e) # 打印错误信息
单元测试:
通过Python自带的unittest库对上述若干个关键函数进行了单元测试,测试结果如下:
实现代码如下:
import unittest
from main_third import get_cid, get_data, get_bvid
bvid = ["BV1Ym4y1K7rg", "BV1Ap4y1E718", "BV1Ym4y1K7rg", "BV1M8411X7DP", "BV1vN411i73M", "BV1NG411d7H5"]
cid = ["1264099708", "1245630731", "1264099708", "1245291456", "1245068396", "1250757149"]
class Mytest(unittest.TestCase):
def test_get_bvid(self):
print("开始测试get_bvid函数") # 打印测试开始的提示信息
# 循环6次进行测试
for i in range(6):
bvid_data = get_bvid(i, 0) # 调用get_bvid函数并获取返回值
self.assertIsNotNone(bvid_data) # 断言返回值不为None
def test_get_cid(self):
print("开始测试get_cid函数") # 打印测试开始的提示信息
cid_num = [] # 创建一个空列表来存储cid_num
# 遍历bvid列表中的每个元素
for i in bvid:
cid_num.append(str(get_cid(i))) # 调用get_cid函数并将返回值转换为字符串,然后添加到cid_num列表中
self.assertEqual(cid, cid_num) # 使用断言来判断cid和cid_num是否相等
def test_get_data(self):
print("开始测试get_data函数") # 打印测试开始的提示信息
# 遍历cid列表中的每个元素
for i in cid:
data = get_data(i) # 调用get_data函数并获取返回值
self.assertIsNotNone(data) # 使用断言来判断data是否为None
if __name__ == '__main__':
unittest.main()
(3.3)**数据统计接口部分的性能改进。**记录在数据统计接口的性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(例如可通过VS /JProfiler的性能分析工具自动生成),并展示你程序中消耗最大的函数。(6’)
数据统计接口部分的性能分析我采用的是pycharm专业版自带的Profile进行图形化分析。分析图如下:
通过分析图可知,程序在获取bvid号、cid号以及弹幕数据时消耗时间较高,这主要是由于cpu采用顺序执行(即需要爬取完一个视频之后才能继续爬取),因此我改进的思路是采用多进程并发执行,采用15个进程分别爬取每页中的视频弹幕,从而提升运行时间。
具体实现代码如下:
pool = multiprocessing.Pool(15) # 创建一个进程池,最大进程数为15
# 循环创建15个进程,并指派给进程池执行
for i in range(15):
pool.apply_async(main(i), args=(i, danmu_file)) # main(i)表示爬取第i页弹幕数据
pool.close() # 关闭进程池,表示不再接受新的任务
pool.join() # 等待所有进程任务完成
print("All workers finished")
改进前运行时间:353.078秒
改进后运行时间:253.192秒
由图可知,在采用多进程并发执行后程序运行时间明显提升,提升近100s。
消耗最大的函数: get_bvid(page, pos)
(3.4)数据结论的可靠性。 介绍结论的内容,以及通过什么数据以及何种判断方式得出此结论(6’)
结论: 弹幕数据表明了B站用户对于日本核污染水排海是坚决抵制的,对保护海洋和地球的呼声非常高,十分关注排放污水的问题。
这个结论是通过弹幕出现次数前20的数据得出的,这些数据出现最多的是“保护海洋”、“保护地球”、“反对排海”,等,数据展示如下:
(3.5)数据可视化界面的展示。 在博客中介绍数据可视化界面的组件和设计的思路。(15’)
数据可视化展示:
使用jieba库对弹幕数据进行分词,然后根据需求自定义词云图的背景,接着利用wordcloud库来绘制词云图。以下为具体实现代码:
def ciyuntu():
try:
df = pd.read_csv(danmu_file) # 使用 pandas 读取 CSV 文件
txt = ''.join(df.astype(str).values.flatten()) # 将DataFrame转换为字符串
txt_list = jieba.lcut(txt) # 使用jieba进行中文分词
string = ' '.join(txt_list) # 将分词结果拼接为字符串
mask_image = "Background2.jpg" # 读取自定义的图片作为词云图背景
mask = np.array(Image.open(mask_image))
# 创建词云图对象
wc = WordCloud(background_color='steelblue',
font_path='C:/Windows/Fonts/SIMLI.TTF',
width=1000, height=700,
contour_width=5, contour_color='white',
mask=mask)
wc.generate(string) # 生成词云图
wc.to_file('wordcloud.png') # 保存词云图
# 异常处理
except Exception as e:
print("词云图制作报错:", e) # 打印错误信息
三、心得体会
(4.1)在这儿写下你完成本次作业的心得体会,当然,如果你还有想表达的东西但在上面两个板块没有体现,也可以写在这儿~(10’)
这次作业是我第一次自主完成的一个小项目。虽然辛苦但也很有意义。通过这次作业,我学到了很多以前不懂的知识。从最开始只是听说过爬虫,到最后真正实现爬虫,我学会了使用浏览器开发者工具、了解了B站的API,还掌握了相关库函数的用法和GitHub仓库的操作。正是通过逐步学习和实站,最终才能完成这次作业。在这过程中,我也遇到了许多问题,例如有关Excel文件导致的编码错误,以及API请求被拦截等,对此我耐心地查询了资料并成功解决了这些问题。
在这次作业中,我花费了大量时间编写代码,其中调试代码是最令人头痛的部分。当我完成了基础代码结构后,我天真的以为,似乎也就那样?但是,后来的调试属实把我困扰住了。改进代码、优化性能、美化可视化界面,以及如何使代码更易读等等看似简单却又不简单的问题陪伴了我许久。
通过这次作业,我还学会了代码的模块化和命名规范化,同时也认识到注释的重要性。拥有好的注释可以帮助我节省大量理解代码的时间。
总之,这次作业对我来说整体上是一次很好的体验。充实我的生活,丰富我的知识,提升我的编码能力,培养我的任务规划和博客撰写水平。受益匪浅!!!
四、附加题
有趣的数据:
我通过爬取B站中关键词为“鸡你太美”综合排序前300的视频弹幕,并将这些数据进行词云图可视化,从而来看看B站用户对于“鸡你太美”这一热梗的喜爱程度。可视化结果如下:
由此能够得出,B站用户对于“鸡你太美”这一热梗的使用程度很高,代表着大家对于这一热梗的喜爱。