|
|
|
@ -55,11 +55,140 @@
|
|
|
|
|
|
|
|
|
|
## 2. 爬虫与数据处理
|
|
|
|
|
#### 说明业务逻辑,简述代码的设计过程(例如可介绍有几个类,几个函数,他们之间的关系),并对关键的函数或算法进行说明。(20')
|
|
|
|
|
本程序主要功能是对b站相关视频的弹幕进行数据处理和分析,全程共用到了15个库及其库函数,自定义了13个函数以完成程序的功能设计。程序的四个部分均有一个较为主要的函数。以下是对主要函数的说明:
|
|
|
|
|
本程序主要功能是对b站相关视频的弹幕进行数据处理和分析,全程共用到了15个库及其库函数,自定义了13个函数以完成程序的功能设计。程序的四个部分均有一个较为主要的函数。以下是对主要函数的说明并会附上对应程序的函数代码:
|
|
|
|
|
1) **弹幕数据获取中的正则匹配函数re.findall()。** 该部分的所有函数几乎都用到了re.findall(),所有有效信息的获取也都离不开re.findall()。它一共需要两个参数,一个是匹配字段文本,一个是数据文本,返回值是list形式的匹配字段。
|
|
|
|
|
```
|
|
|
|
|
# 获取当前页码的视频链接地址
|
|
|
|
|
def GetAllSearchVideoUrl(url, headers):
|
|
|
|
|
response = requests.get(url, headers = headers)
|
|
|
|
|
response.encoding = 'utf-8'
|
|
|
|
|
html_data = response.text
|
|
|
|
|
content_list = re.findall('<a href="(.*?)" .*? target="_blank" data-v-4caf9c8c><div class=".*?" data-v-4caf9c8c>', html_data)
|
|
|
|
|
return content_list
|
|
|
|
|
|
|
|
|
|
# 获取当前视频的弹幕接口cid地址
|
|
|
|
|
def GetVideoCid(url, headers):
|
|
|
|
|
response = requests.get(url, headers = headers)
|
|
|
|
|
response.encoding = 'utf-8'
|
|
|
|
|
html_data = response.text
|
|
|
|
|
content = re.findall('"dynamic":.*?,(.*?),"dimension":.*?', html_data)
|
|
|
|
|
back = re.search('"cid":', content[0])
|
|
|
|
|
num = back.span()[1]
|
|
|
|
|
cid = content[0][num:]
|
|
|
|
|
return cid
|
|
|
|
|
|
|
|
|
|
# 获取当前cid地址下的视频弹幕数据
|
|
|
|
|
def GetVideoBarrage(url, headers):
|
|
|
|
|
response = requests.get(url, headers = headers)
|
|
|
|
|
response.encoding = 'utf-8'
|
|
|
|
|
html_data = response.text
|
|
|
|
|
content_list = re.findall('<d p=".*?">(.*?)</d>', html_data)
|
|
|
|
|
return content_list
|
|
|
|
|
```
|
|
|
|
|
2) **数据处理与转存中的数据类型转换函数ChangeDfToString()。** 该部分的后续函数都是基于ChangeDfToString()转换出来的字符串进行进行处理的。用dataframe正常转换出来字符串会有很多的空格以及莫名其妙的字符,通过这个函数可以去除无效的空格,并对每一个有效内容加以逗号分隔。它一共可以接受4个参数,原dataframe,分隔字符sep,是否保存标志isSave,保存路径filePath,返回值是分隔好的字符。
|
|
|
|
|
```
|
|
|
|
|
# 读取弹幕文件
|
|
|
|
|
def ReadXlsx(filePath=''):
|
|
|
|
|
df = pd.read_excel(filePath, sheet_name=0)
|
|
|
|
|
df.dropna(axis=1, how='all')
|
|
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
# 将dataframe类型转为string类型
|
|
|
|
|
def ChangeDfToString(df,sep=',', isSave=False, filePath=''):
|
|
|
|
|
string_data = df.to_string(index=False, header=False, na_rep='')
|
|
|
|
|
string = string_data.replace('\n', ' ')
|
|
|
|
|
str = re.sub(' +', sep, string)
|
|
|
|
|
if isSave:
|
|
|
|
|
with open(filePath, mode="w",encoding='utf-8') as file:
|
|
|
|
|
file.write(str)
|
|
|
|
|
return str
|
|
|
|
|
|
|
|
|
|
# 根据关键词进行检索
|
|
|
|
|
def GetKeyFromList(keyWords, origin_list):
|
|
|
|
|
filtered_list = [item for item in origin_list if any(keyword in item for keyword in keyWords)]
|
|
|
|
|
counter_list = Counter(filtered_list)
|
|
|
|
|
sorted_list = sorted(counter_list.items(), key = lambda x:x[1], reverse=True)
|
|
|
|
|
return sorted_list
|
|
|
|
|
```
|
|
|
|
|
3) **词云绘制部分的词频转换函数ChangeToFreq()。** 该函数可以将分隔好的词语用TF-IDF关键词提取,并统计各类词语的词频,实现正则化,对后续的词云图绘制有较大的帮助。它接受一个参数,分隔好的字符串,返回值是词组频率的字典,键值对是词组和频率。
|
|
|
|
|
```
|
|
|
|
|
# 将弹幕文本分隔成易于处理的字词
|
|
|
|
|
def ReadAndCutWords(filePath):
|
|
|
|
|
with open(filePath, 'r', encoding='utf-8') as file:
|
|
|
|
|
text = file.read()
|
|
|
|
|
words = jieba.cut(text, cut_all=False)
|
|
|
|
|
word_list = ' '.join(words)
|
|
|
|
|
return word_list
|
|
|
|
|
|
|
|
|
|
# 利用TF-IDF将字词按频率划分
|
|
|
|
|
def ChangeToFreq(word_list):
|
|
|
|
|
documents = [word_list]
|
|
|
|
|
vectorizer = TfidfVectorizer()
|
|
|
|
|
tfidf_matrix = vectorizer.fit_transform(documents)
|
|
|
|
|
feature_names = vectorizer.get_feature_names_out()
|
|
|
|
|
word_freq = dict(zip(feature_names, tfidf_matrix.toarray().sum(axis=0)))
|
|
|
|
|
return word_freq
|
|
|
|
|
|
|
|
|
|
# 根据字词频率来生成图云
|
|
|
|
|
def CreateWordCloud(word_freq, width, height, maskImgPath, saveImgPath, save=False):
|
|
|
|
|
if maskImgPath == '':
|
|
|
|
|
mask = None
|
|
|
|
|
else:
|
|
|
|
|
mask = np.array(Image.open(maskImgPath))
|
|
|
|
|
wordcloud = WordCloud(font_path='simhei.ttf',
|
|
|
|
|
mask= mask,
|
|
|
|
|
width=width,
|
|
|
|
|
height=height,
|
|
|
|
|
background_color='white').generate_from_frequencies(word_freq)
|
|
|
|
|
if maskImgPath != '':
|
|
|
|
|
image_colors = ImageColorGenerator(mask)
|
|
|
|
|
wordcloud.recolor(color_func=image_colors)
|
|
|
|
|
plt.imshow(wordcloud, interpolation='bilinear')
|
|
|
|
|
plt.axis('off')
|
|
|
|
|
plt.show()
|
|
|
|
|
if save:
|
|
|
|
|
wordcloud.to_file(saveImgPath)
|
|
|
|
|
```
|
|
|
|
|
4) **弹幕情绪分析部分的模型加载类Taskflow()。** 该类是paddlenlp库中的Taskflow类。panddnlp是基于百度的飞浆平台搭建的自然语言处理(NLP)模型库,对中文语言分析有非常优秀的表现。利用Taskflow()类,可以搭载模型model,设定语言处理的模式schema,最后会返回一个ie模型对象,之后就可以使用ie对文本进行语言处理了。
|
|
|
|
|
```
|
|
|
|
|
# 加载弹幕字符文本
|
|
|
|
|
def loadText(sep, filePath):
|
|
|
|
|
with open(filePath, 'r', encoding='utf-8') as file:
|
|
|
|
|
text = file.read()
|
|
|
|
|
t_list = text.split(sep)
|
|
|
|
|
return t_list
|
|
|
|
|
|
|
|
|
|
# 加载自然语言处理模型
|
|
|
|
|
def loadModel(schema, model):
|
|
|
|
|
ie = Taskflow('information_extraction', schema=schema, model=model)
|
|
|
|
|
return ie
|
|
|
|
|
|
|
|
|
|
# 计算情感方向的数量以及平均的可能性
|
|
|
|
|
def emoChange(emo, pro, count, probability):
|
|
|
|
|
if emo == '正向':
|
|
|
|
|
count[0] += 1
|
|
|
|
|
probability[0] = probability[0] + (pro - probability[0])/count[0]
|
|
|
|
|
else:
|
|
|
|
|
count[1] += 1
|
|
|
|
|
probability[1] = probability[1] + (pro - probability[1])/count[1]
|
|
|
|
|
|
|
|
|
|
# 绘制柱形图
|
|
|
|
|
def createBar(count, probability, savePath):
|
|
|
|
|
x_data = [f'正向(可能性:{probability[0]})', f'负向(可能性:{probability[1]})']
|
|
|
|
|
y_data = count
|
|
|
|
|
plt.rcParams["font.sans-serif"] = ["SimHei"]
|
|
|
|
|
plt.rcParams["axes.unicode_minus"] = False
|
|
|
|
|
plt.figure(figsize=(10, 7))
|
|
|
|
|
for i in range(len(x_data)):
|
|
|
|
|
plt.bar(x_data[i], y_data[i], width=0.7)
|
|
|
|
|
plt.title("弹幕情感方向数量统计")
|
|
|
|
|
plt.text(x_data[0], y_data[0]+0.01, count[0], ha="center", va="bottom", fontsize=17)
|
|
|
|
|
plt.text(x_data[1], y_data[1]+0.01, count[1], ha="center", va="bottom", fontsize=17)
|
|
|
|
|
plt.xlabel("弹幕情感方向")
|
|
|
|
|
plt.ylabel("数量")
|
|
|
|
|
plt.savefig(fname=savePath, dpi=500)
|
|
|
|
|
plt.show()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 3. 数据统计接口部分的性能改进
|
|
|
|
|
#### 记录在数据统计接口的性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(例如可通过VS /JProfiler的性能分析工具自动生成),并展示你程序中消耗最大的函数。(6')
|
|
|
|
|