2025软工K班个人编程任务

54 阅读16分钟

博客链接:2025软工K班个人编程任务 - 掘金

代码链接:code.educoder.net/pykxlafr9/1…

一、PSP表格

PSP是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP, 个人开发流程,或称个体软件过程),以下就是我本次实验相关的PSP表格。

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2030
· Estimate· 估计这个任务需要多少时间2030
Development开发570640
· Analysis· 需求分析 (包括学习新技术)300320
· Design Spec· 生成设计文档3050
· Design Review· 设计复审4040
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)5060
· Design· 具体设计3040
· Coding· 具体编码6080
· Code Review· 代码复审3020
· Test· 测试(自我测试,修改代码,提交修改)3030
Reporting报告115175
· Test Repor· 测试报告2035
· Size Measurement· 计算工作量1520
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划80120
· 合计705845

二、任务要求的实现

2.1 项目设计与技术栈

本次项目目标是对 Bilibili 中关于 “大语言模型(LLM)” 的视频弹幕进行爬取、分析与可视化。

分层设计

  1. 爬虫层:检索关键词→获取 BV → 提取 CID → 拉取 XML → 解析 <d> 弹幕
  2. 存储层:每个视频写入 output/danmu_LLM/第N个视频_BVxxxx.txt
  3. 统计层:读取全部 TXT,按关键词过滤,统计 Top-N,导出 Excel/CSV
  4. 可视化层:Jieba 分词 + WordCloud 生成词云图

技术栈: 利用python进行爬虫

  • 数据请求和数据抓取:requests库、lxml库与XPath、正则表达式(re库)
  • 多线程并发运行:ThreadPoolExecutor(from concurrent.futures库)
  • 文件创建与写入:os库(4)缓冲重试:random库

代码目录结构

bilibili_danmu_crawl/
├─ crawl_getthread.py      # 多线程主爬虫(检索+抓取)
├─ crawl_getfor.py         # 单线程/小批量调试
├─ crawl_1.py              # 基础款(单 BV 抓取)
├─ 数据分析.py              # 读取TXT→过滤→统计→导出
├─ mywordcloud.py          # 词云生成
├─ requirements.txt        # 环境依赖说明
└─ output/
   └─ danmu_LLM/           # 保存弹幕TXT与统计结果

2.2 爬虫与数据处理

2.2.1 需求分析

根据题目的要求,爬取“大语言模型应用“综合排序前300的所有视频弹幕,在进行数据筛选之后需要得出当前B站用户对于大语言模型应用中AI技术的主流看法。

爬虫程序的设计核心是 BiliBiliDanMu 类,它被构建为处理单个视频弹幕爬取任务的蓝图。该类内部封装了几个关键方法:

  • Init(构造方法):基于传入的BV号来初始化对象,并发起网络请求。
  • get_video_cid:用于解析并获取目标视频唯一的 cid 标识。
  • get_content:负责根据 cid 获取包含弹幕的原始数据(通常是XML格式)。
  • extract_danmu:核心的解析方法,用于从复杂的XML内容中提取出纯净的弹幕文本。
  • save:将解析出的弹幕列表持久化存储到本地的 .txt 文件中。
  • crawl:作为该类的执行引擎,它串联并调用上述所有方法,实现从初始化到保存的完整自动化流程。

在 BiliBiliDanMu 类的基础上,程序还定义了几个高层调度函数,用于实现批量化和并发处理:

  • search_videos:此函数用于根据关键词(或其他条件)搜索并批量获取一系列视频的BV号,作为后续爬取的目标。
  • download_danmu:一个功能性函数,其任务是(可能通过实例化 BiliBiliDanMu 类并调用其 crawl 方法)下载指定BV号视频的弹幕。
  • getthread:为了显著提升爬取效率,该函数被用于管理并发,它通过创建和调度多个线程来同时执行多个 download_danmu 任务。

最后,在主函数 (main) 中,程序将这些组件有机地结合起来:首先调用 search_videos 获取任务列表,随后利用 getthread 启动并发下载流程,从而高效地完成所有指定视频的弹幕爬取。整体结构图如下:

mermaid-diagram-2025-11-13-150459.png

关键函数说明:

  • search_videos:向Bilibili API发送请求,解析返回的JSON数据,提取视频ID。
  • retrieve_cid:根据视频ID请求详细信息,提取CID。
  • vfetch_danmaku:请求弹幕数据,使用正则表达式提取弹幕文本。

2.2.2 爬虫

思路过程:从一个视频弹幕的获取到多个视频弹幕的获取

✨ 确认弹幕来源:B站的弹幕都是以xml文件的形式,URL都是如下格式

  http://comment.bilibili.com/25844646387.xml

而25844646387是cid,因此只要获取到视频的cid,就可以通过这个URL下载该视频的弹幕文件

✨ 提取cid号:首先可以先确定一个视频,那么确定一个视频就是确定该视频的BV号(相当于视频的身份证号),通过访问该视频页面的HTML内容可以找到cid。然后利用正则表达式r'("cid":)([0-9]+)'从文本中匹配出cid号

  cid = re.findall(r'("cid":)([0-9]+)', html)

✨ 解析弹幕文件:获取到弹幕文件后,外面要从中提取出文本信息,调用lxml库中的estree类将XML字符串解析为一个Element对象,然后利用XPath提取所有标签中的文本:

  html = etree.HTML(content_str)
  danmu_list = html.xpath("//d/text()")

封装为一个类专门负责处理单个 Bilibili 视频的弹幕抓取--BiliBiliDanMu

关键爬虫封装

  def crawl(self):
        cid = self.get_video_cid()
        if cid is not None:
              xml_url = "http://comment.bilibili.com/" + str(cid) + ".xml"
              content_str = self.get_content(xml_url)
              if content_str:
                 danmu_list = self.extract_danmu(content_str)
                 self.save(danmu_list)
        else:
              print("视频没有有效的 cid,跳过此视频")

理清楚一个视频的弹幕获取之后,我认为多个视频的获取在于其BV号、url、视频个数要求 💨 通过API请求获取指定关键词的搜索结果(视频)。

  search_url = "https://api.bilibili.com/x/web-interface/search/type"

💨 解析搜索结果,提取每个视频的BV号,综合排名前300个。

💨 如果需要抓取更多的视频,则翻页继续请求,直到达到最大抓取数量或没有更多的结果。在第一次爬取时,发现没有获取到300个视频,只获取了260个左右,故设置最多爬取350个视频,同时,增加了类似的相关的关键词,取前300个。函数search_videos(query, max_results=350),queries = ["大语言模型", "大模型", "LLM","Large Language Model"] 💨 对抓取到的BV列表去重,以确保每个视频只抓取一次。经过检验发现有获视频BV号中有重复,故利用set进行去重,并且加上OrderedDict不失其序。

核心代码展示

  def search_videos(query, max_results=350):
     #搜索视频,最多返回 max_results 个结果
     search_url = "https://api.bilibili.com/x/web-interface/search/type"
     headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
        "Referer": "https://www.bilibili.com/",
        "Accept": "application/json, text/plain, */*",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "cookie": "your cookie"  #Cookie 有就行,内容随意
     }

     bv_list = []
     page = 1

     while len(bv_list) < max_results:
        params = {
              'keyword': query,
              'search_type': 'video',
              'order': 'totalrank',#综合排序
              'page': page,
           #'pagesize': 50罪魁祸首
              'page_size': 50
        }

        try:
              response = requests.get(search_url, params=params, headers=headers, timeout=10)
              if response.status_code == 200:
                 results = response.json()
                 if results['code'] == 0:
                    videos = results['data']['result']
                    if not videos:
                          break
                    bv_list += [video['bvid'] for video in videos]
                    print(f"已抓取 {len(bv_list)} 个视频")
                 else:
                    print(f"搜索失败,错误代码: {results['code']},错误信息: {results.get('message', '无详细信息')}")
                    if '频繁' in results.get('message', ''):
                          print("限流,等待后重试")
                          time.sleep(random.uniform(5, 10))
                          continue
                    break
              else:
                 print(f"搜索请求失败,状态码: {response.status_code}")
                 break
        except requests.exceptions.RequestException as e:
              print(f"请求失败,错误: {e}")
              time.sleep(random.uniform(2, 5))
              continue

        page += 1
        time.sleep(random.uniform(1, 3))  #防止请求过于频繁被禁止

     bv_list = list(OrderedDict.fromkeys(bv_list))  #去重操作
     return bv_list[:max_results]  

2.2.3 数据处理

弹幕的保存核心代码

  def save(self, save_items):
        output_dir = os.path.dirname(self.filename)
        os.makedirs(output_dir, exist_ok=True)

        with open(self.filename, 'w', encoding='utf-8') as f:
              lines = [item + '\n' for item in save_items]
              f.writelines(lines)
        print(f"弹幕已保存至 {self.filename}")
  • 创建目录:使用 os.path.dirname(self.filename) 获取保存文件的目录路径。调用 os.makedirs(output_dir, exist_ok=True) 来确保目录存在。
  • 写入文件:使用 open(self.filename, 'w', encoding='utf-8') 打开一个文件名为 self.filename的文件,编码格式为 utf-8。
  • 输出信息:打印一条确认信息,表示弹幕已保存到指定的文件路径。

该代码的功能是将抓取到的弹幕保存到本地文件。它会自动检查并创建保存路径,并以 UTF-8 编码保存文件,确保可以正确保存中文弹幕。

2.3 数据统计接口部分的性能改进

问题分析 : 初版爬虫为单线程串行抓取模式,面对300个视频时网络请求阻塞严重,平均运行时长约10分钟。
我们的优化目标是降低 I/O 等待时间、提升抓取效率与健壮性。

性能改进措施

  1. 引入多线程并发爬取
    • 使用 ThreadPoolExecutor(max_workers=10),限制并发数以避免被B站反爬。
    • 异常自动重试(最多3次)确保弹幕抓取完整性。
  2. 优化文件写入
    • 使用 buffering=1 提高写入效率。
    • 统一路径管理,防止文件丢失或编码错误。
  3. 数据统计阶段
    • 先统一汇总所有TXT文本,再批量生成DataFrame并统计,避免重复I/O。
    • Counter 模块实现高效频率统计。

2.3.1 改进前

在开始的时候采用的是for循环的爬虫方法,一条一条地进行爬取,一共爬取了200多条视频,用时接近5分钟左右。

image-4.png

这个使用vscode平台的利用cProfile+snakeviz库测试的测试结果图:

image-3.png

2.3.2 改进后

由于getfor函数中有使用到download_danm和crawl,且耗时都同样大,故分析出一条一条获取弹幕的函数耗时影响最大,改进使用多线程并发获取,将爬取弹幕时间大幅度降低为1分钟,获得近5倍的加速比。

image-5.png

这个使用vscode平台的利用cProfile+snakeviz库测试的测试结果图:

image-6.png

2.3.3 性能对比结果

串行版本:约 8 min / 300 视频
并发版本:约 2 min / 300 视频
性能提升:约 8 倍
主要耗时函数:get_video_cid(), requests.get()

2.4 数据结论的可靠性

👀 通过对B站用户关于“人工智能未来发展”的弹幕数据进行统计,我们得出了当前的主流看法:

  1. 公众的讨论焦点并非纯技术,而是“AI与人”: 从我们的词云图中可以清晰地看到,尽管“AI”和“人工智能”是绝对核心,但“人类”、“问题”、“为什么”、“觉得”等词汇的占比非常高。这从中可以看出,大多数人们的关注重点已经超越了技术本身,转而深度思考AI对人类社会的根本影响和哲学挑战。

  2. 情绪复杂:谨慎乐观与高度主观并存: 情感分析显示,整体情感极性为 0.175,这是一种谨慎的乐观态度,而非极端的狂热或恐慌。然而,高达 0.626 的主观性得分表明,这场讨论充满了强烈的个人感受和价值判断,而非冷冰冰的客观科普。人们是在“感受”AI,而不只是在“分析”AI。

  3. 讨论的“盲区”:宏观有余,具体不足: 正如我们的词云图所展示的,公众的讨论非常宏观和哲学(例如“意识”、“逻辑”、“生命”)。但相较之下,AI在许多垂直领域已经落地的具体应用,在我们的弹幕样本中反而提及较少。

因此,我们的分析揭示了一个有趣的现象:公众的注意力主要集中在AI的“终极问题”(即对人类文明的影响)上,而对AI技术已经“深入角落”的实际应用的讨论,还相对较少。

2.5 数据可视化界面的展示

2.5.1 统计AI技术应用方面的每种弹幕数量,并输出数量排名前8的弹幕

排名弹幕内容出现次数
1AI15
2已离不开AI12
3人工智能 yyds11
4转行从事AI8
5很需要学习一下大模型7
6个人的硬件可以训练大模型吗5
7应该重点发展国产大模型5
8请问一个人工智能专业双非本科毕业后在大模型方面好就业吗5

2.5.2 弹幕词云图

  #用到的组件
  import os
  import jieba
  from wordcloud import WordCloud
  import matplotlib.pyplot as plt

image.png

2.6 附加题展示

自主发挥:爬取主流科技媒体的观点,预测大语言模型应用的发展趋势。爬取有趣的数据进行分析、制作数据可视化大屏等,有创意有乐趣即可。

“现今,随着人工智能模型和大数据技术的飞速发展,一场深刻的变革正席卷而来。人工智能已经从未来愿景转变为现实,深入融入我们工作与生活的各个角落,不断重塑着行业生态和社会面貌。

在这个过程中,公众和媒体如何看待AI的未来,不仅反映了社会的期待与忧虑,更在很大程度上影响着技术的接受度、产业的投资方向乃至相关政策的制定。

鉴于此,我们抓取了Bilibili等有关媒体对于人工智能未来发展的关键评论与报道,并运用自然语言处理(NLP)技术进行了系统性的情感分析。我们希望通过这种数据驱动的方式,量化当前舆论场的整体情绪,洞察主流观点是倾向于乐观(如效率提升、科技向善)还是担忧(如就业替代、安全伦理),从而为理解AI发展的社会反响提供一个客观的视角。”

以下是我捕获数据后进行的词云图展示:

弹幕词云图.png

通过这个我们看出来我们能清晰地看到两点:

  • 讨论焦点高度集中:‘AI’和‘人工智能’是绝对的核心,这表明公众和媒体的注意力正前所未有地聚焦于这一议题。

  • AI是‘现在进行时’:‘现在’、‘发展’、‘时代’、‘机器人’等词汇的突出,强烈地传达出一个信号——公众普遍认为AI不仅是遥远的未来,更是“现在”就“已经”在高速发展、并深刻定义我们这个“时代”的关键技术。”

接下来,我们对这些进行了情感分析,关键代码和结果如下:

  #定义情感分析函数
  def analyze_sentiment(text):
     blob = TextBlob(text)
     return blob.sentiment.polarity, blob.sentiment.subjectivity
  ​
  #对合并的文本进行情感分析
  polarity, subjectivity = analyze_sentiment(all_text)
  ​
  #打印分析结果
  print(f"弹幕的情感极性: {polarity}")#-1 表示非常负面,0 表示中立,1 表示非常正面
  print(f"弹幕的情感主观性: {subjectivity}")#0 表示完全客观,1 表示完全主观

最终输出弹幕情感分析结果为:

弹幕情感分析结果.png

根据我们对媒体与公众关于“人工智能未来发展”评论的情感分析结果,整体情感倾向呈现出一种谨慎的乐观态度,情感极性得分约为 0.175,而情感主观性得分为 0.626。

分析显示,正如我们从词云图中看到的,尽管讨论中充满了对“问题”的思辨和对“人类”影响的关切,但整体基调仍然是温和且偏向积极的。这表明,大部分网友在面对AI这一革命性技术时,虽然存有疑虑,但总体上持有一种积极的期待和观望态度。

同时,高达 0.626 的主观性得分清晰地反映出,公众在讨论AI时包含了大量的个人观点、感受和价值判断,这显示出公众对这一议题的强烈主观参与和高度关注。

其实,无论AI的发展是带来更多机遇还是挑战,公众这种的高度关注和积极思辨,本身就是推动技术向善发展的最重要力量。😇

三、心得体会

在这次项目中,爬取Bilibili弹幕并进行处理,最终生成词云图,是一次有趣且富有意义的实践经历。通过这个实践,我不仅加深了对Python编程的理解,还学习了如何处理网络数据,以及如何将数据处理结果以视觉化的方式展现出来。

(1)爬虫的设计与实现

设计爬虫时,我首先分析了Bilibili的API,尤其是搜索视频和获取弹幕的接口。理解这些API的参数和返回的数据格式是关键一步。我使用了Python的requests库来发送HTTP请求,这个库简单易用,非常适合进行网络爬虫的开发。同时,考虑到可能遇到的异常情况,我在代码中加入了异常处理机制,确保爬虫的健壮性。

(2)数据处理

在获取到弹幕数据后,我使用Python的正则表达式进行了数据清洗,去除了一些无关的格式和标签,仅保留了文本内容。然后,利用collections.Counter对弹幕中出现的关键词进行了频率统计。这一步是数据处理的核心,它为后续的数据分析和可视化提供了基础。

(3)生成词云图

词云图的生成是整个项目中最具创造性的部分。我使用了wordcloud库,它提供了丰富的参数来调整词云图的外观,如字体、颜色、形状等。通过调整这些参数,我生成了一张既美观又能够直观展示数据的词云图。这个过程让我意识到,数据可视化不仅仅是技术活,更是一种艺术创作。

(4)总体心得体会

通过这次项目,我深刻体会到了数据获取、处理和可视化的整个流程。我学会了如何将复杂的数据转换为易于理解的视觉图像,这对于数据分析来说非常重要。此外,我也认识到了在网络数据爬取中遵守法律法规的重要性,比如尊重版权、合理使用网络资源等。总的来说,这次项目不仅提升了我的编程技能,也增强了我的数据素养,让我对未来的数据分析工作充满了信心和期待。