<think>根据错误信息,问题在于词典文件的格式。错误信息显示:
- 第一行:警告: 忽略无效极性 ‘极性’ 对于词 ‘词语’ (行 1)
- 其他行:警告: 忽略格式错误行
观察发现,第一行的词是“词语”,这可能是由于文件开头的BOM(Byte Order Mark)字符导致的。另外,其他行被忽略是因为这些行没有使用制表符(\t)分隔,而是使用了空格。
具体问题:
1. 文件可能包含BOM(特别是UTF-8 with BOM),导致第一行的第一个词包含不可见的BOM字符。
2. 词典文件中的分隔符可能是空格,但代码中是用制表符(\t)分割的。
解决方案:
1. 去除BOM:在读取文件时,可以忽略BOM,或者使用`encoding='utf-8-sig'`代替`'utf-8'`,这样会自动去除BOM。
2. 允许空格和制表符分割:修改分割逻辑,使用正则表达式分割,允许空格或制表符。
修改步骤:
步骤一:修改文件编码方式
将`open`函数的编码方式从`'utf-8'`改为`'utf-8-sig'`,以去除BOM。
步骤二:修改分割方式
当前代码是:`parts = line.strip().split("\t")`
可以改为:使用正则表达式分割连续的空格或制表符,但注意有些词可能包含空格(如英文短语),但我们的词典是单字词或连续字符串,所以可以按空格和制表符分割。
但是,观察错误信息,有些行是“一往情深 POS”,中间是空格,所以我们可以用`split()`(不带参数)来分割任意空白字符,但这样会分割所有空白,包括多个空格或制表符。
但是注意:有些词可能包含空格(如英文短语),但我们的词典是中文的,目前没有包含空格的词。不过为了通用性,我们可以考虑只分割第一个空白字符。
另一种方法是:使用`split(maxsplit=1)`,这样只分割第一个空白字符,将行分成两部分。
修改后的代码:
```python
parts = line.strip().split(maxsplit=1) # 只分割一次
```
这样,即使词语后面有空格,也能正确分割。
步骤三:处理可能的分割后不足两部分的情况
具体修改如下:
在`load_sentiment_dict`函数中:
```python
# 修改打开文件的编码为utf-8-sig
with open(file_path, "r", encoding="utf-8-sig") as f:
for line_num, line in enumerate(f, 1):
if line.startswith("#") or not line.strip():
continue
# 使用maxsplit=1,只分割一次
parts = line.strip().split(maxsplit=1)
if len(parts) < 2:
print(f"警告: 忽略格式错误行 {line_num}: {line.strip()} (分割后得到{len(parts)}部分)")
continue
word_part = parts[0].strip()
polarity_part = parts[1].strip()
# 现在,polarity_part可能是多个单词,我们只取第一个
# 例如:如果一行是“一往情深 POS”,那么polarity_part是"POS"
# 如果一行是“词语 极性 其他”,那么polarity_part是“极性 其他”,我们只取第一个作为极性
# 所以,我们再将极性部分按空格分割,取第一个
polarity_parts = polarity_part.split()
if not polarity_parts:
print(f"警告: 忽略格式错误行 {line_num}: {line.strip()} (没有极性)")
continue
polarity = polarity_parts[0].strip().upper()
word = re.sub(r'\s+', '', word_part) # 去除词语中的所有空白
# 然后检查极性是否有效
```
但是,注意词典中有些行可能是用多个空格分隔的,例如:“一往情深 POS”,使用`split(maxsplit=1)`可以正确分割成两部分。
然而,在极性部分,我们只取第一个非空字符串作为极性,这样如果极性部分有多个单词,我们只取第一个。
根据词典格式,极性应该是紧跟在词语后面的一个单词(如POS或NEG),所以这样处理是安全的。
修改后的`load_sentiment_dict`函数代码:
```python
def load_sentiment_dict(file_path):
"""
加载情感词典(支持多字词和规范化处理)
格式: 词语 极性 (如: 高兴 POS) 或者 词语\t极性
注意: 允许使用空格或制表符分隔,但只分割第一个空白字符
"""
sentiment_dict = {}
print(f"开始加载情感词典: {file_path}")
try:
# 使用utf-8-sig编码自动去除BOM
with open(file_path, "r", encoding="utf-8-sig") as f:
for line_num, line in enumerate(f, 1):
# 跳过注释行和空行
stripped_line = line.strip()
if not stripped_line or stripped_line.startswith("#"):
continue
# 使用split(maxsplit=1)只分割第一个空白字符(包括空格和制表符)
parts = stripped_line.split(maxsplit=1)
if len(parts) < 2:
print(f"警告: 忽略格式错误行 {line_num} (分割后不足两部分): {stripped_line}")
continue
word_part = parts[0].strip()
# 处理极性部分:可能有多个空白,我们取第一个非空字符串
polarity_str = parts[1].strip()
# 如果极性字符串中有空格,我们只取第一个单词
polarity_parts = polarity_str.split()
if not polarity_parts:
print(f"警告: 忽略格式错误行 {line_num} (无极性): {stripped_line}")
continue
polarity = polarity_parts[0].strip().upper()
word = re.sub(r'\s+', '', word_part) # 去除词语中的所有空白
# 只保留POS和NEG极性
if polarity in ["POS", "NEG"]:
# 避免重复词覆盖
if word in sentiment_dict:
print(f"警告: 词 '{word}' 重复定义 (行 {line_num}), 将使用最新定义")
sentiment_dict[word] = polarity
else:
print(f"警告: 忽略无效极性 '{polarity}' 对于词 '{word}' (行 {line_num})")
except Exception as e:
print(f"加载词典时出错: {str(e)}")
exit(1)
print(f"成功加载情感词典: 共 {len(sentiment_dict)} 个情感词 (仅POS/NEG)")
return sentiment_dict
```
这样修改后,应该能正确加载词典文件。
注意:词典文件中的行应该类似于:
```
词语 极性
```
或
```
词语 极性 其他内容(其他内容会被忽略)
```
其中,极性部分只取第一个非空字符串,且必须是POS或NEG(不区分大小写,但会被转为大写)。
请尝试修改并运行。
另外,注意第一行的错误可能是由于BOM导致的,使用`utf-8-sig`编码后,第一行的词就不会有BOM字符了。
如果还有问题,请检查词典文件的前几行内容。</think>根据您提供的错误信息,问题在于情感词典文件的格式不正确。错误显示大多数行被忽略是因为它们没有正确的制表符分隔格式。让我提供一个完整的解决方案来修复这个问题:
### 问题分析
1. **格式错误**:错误信息显示大多数行被忽略,因为代码期望的是"词语\t极性"格式,但实际文件可能是用空格分隔的
2. **无效极性**:第一行有无效极性 '极性',可能是因为标题行没有被跳过
3. **BOM字符**:第一行的 '词语' 可能是UTF-8 BOM字符导致的
### 解决方案:改进词典加载函数
```python
import os
import re
from collections import defaultdict
# ========================
# 配置路径
# ========================
SEGMENTED_DANMU_FILE = "segmented_danmu.txt" # 分好词的弹幕文件
SENTIMENT_DICT_FILE = "merged_sentiment_dict.txt" # 情感词典
OUTPUT_FILE = "extracted_sentiment_words.txt" # 输出文件
# ========================
# 改进的词典加载函数(处理多种格式)
# ========================
def load_sentiment_dict(file_path):
"""
加载情感词典(支持多种分隔符格式)
支持格式:
词语\t极性 (如: 高兴\tPOS)
词语 极性 (如: 高兴 POS)
词语:极性 (如: 高兴:POS)
"""
sentiment_dict = {}
print(f"开始加载情感词典: {file_path}")
try:
# 使用utf-8-sig编码自动处理BOM字符
with open(file_path, "r", encoding="utf-8-sig") as f:
for line_num, line in enumerate(f, 1):
# 跳过注释行和空行
stripped_line = line.strip()
if not stripped_line or stripped_line.startswith("#"):
continue
# 支持多种分隔符: \t, 空格, :
if '\t' in stripped_line:
parts = stripped_line.split('\t')
elif ' ' in stripped_line:
parts = stripped_line.split(' ', 1) # 最多分割一次
elif ':' in stripped_line:
parts = stripped_line.split(':', 1)
else:
print(f"警告: 忽略无法解析行 {line_num}: {stripped_line}")
continue
# 确保有足够的部分
if len(parts) < 2:
print(f"警告: 忽略格式错误行 {line_num} (分割后不足两部分): {stripped_line}")
continue
# 提取词语和极性
word = parts[0].strip()
polarity = parts[1].strip().upper() # 确保大写
# 移除极性部分的多余内容(如"POS"后面的说明)
polarity = re.split(r'\s+', polarity)[0] # 只取第一个词
# 只保留POS和NEG极性
if polarity in ["POS", "NEG"]:
# 检查词语是否包含非字母数字字符
if not re.match(r'^[\w\u4e00-\u9fff]+$', word):
print(f"警告: 词语 '{word}' 包含特殊字符 (行 {line_num})")
# 避免重复词覆盖
if word in sentiment_dict:
print(f"警告: 词 '{word}' 重复定义 (行 {line_num}), 将使用最新定义")
sentiment_dict[word] = polarity
else:
print(f"警告: 忽略无效极性 '{polarity}' 对于词 '{word}' (行 {line_num})")
except Exception as e:
print(f"加载词典时出错: {str(e)}")
exit(1)
print(f"成功加载情感词典: 共 {len(sentiment_dict)} 个情感词 (仅POS/NEG)")
return sentiment_dict
# ========================
# 情感词提取函数(改进版)
# ========================
def extract_sentiment_words(danmu_file, sentiment_dict):
"""
从分好词的弹幕中提取情感词(支持连续重复词合并)
"""
# 统计结果
results = []
word_stats = defaultdict(lambda: {"count": 0, "polarity": ""})
# 调试:统计所有词频
all_word_freq = defaultdict(int)
print(f"开始处理弹幕文件: {danmu_file}")
try:
with open(danmu_file, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
# 清洗和分割
cleaned_line = line.strip()
if not cleaned_line:
continue
words = cleaned_line.split()
danmu_sentiment_words = []
# 记录原始词频
for word in words:
all_word_freq[word] += 1
# 添加词合并缓冲区
word_buffer = ""
# 检查每个词
for word in words:
# 尝试匹配原始词
if word in sentiment_dict:
polarity = sentiment_dict[word]
danmu_sentiment_words.append((word, polarity))
word_stats[word]["count"] += 1
word_stats[word]["polarity"] = polarity
word_buffer = "" # 匹配成功则重置缓冲区
continue
# 尝试合并连续重复词(如"哈"+"哈"+"哈" -> "哈哈哈")
if not word_buffer:
word_buffer = word
elif word == word_buffer[-1]: # 当前字与前一个字相同
word_buffer += word
else:
# 检查缓冲区中的词是否在词典中
if word_buffer in sentiment_dict:
polarity = sentiment_dict[word_buffer]
danmu_sentiment_words.append((word_buffer, polarity))
word_stats[word_buffer]["count"] += 1
word_stats[word_buffer]["polarity"] = polarity
word_buffer = word # 重置缓冲区
else:
word_buffer = word # 重置缓冲区
# 处理缓冲区末尾
if word_buffer and word_buffer in sentiment_dict:
polarity = sentiment_dict[word_buffer]
danmu_sentiment_words.append((word_buffer, polarity))
word_stats[word_buffer]["count"] += 1
word_stats[word_buffer]["polarity"] = polarity
# 记录有情感词的弹幕
if danmu_sentiment_words:
results.append({
"line": line_num,
"text": cleaned_line,
"sentiment_words": danmu_sentiment_words
})
except Exception as e:
print(f"处理弹幕文件时出错: {str(e)}")
exit(1)
print(f"处理弹幕完成: 共 {len(results)} 条弹幕包含情感词")
# 调试:输出特定词的出现频率
print("\n弹幕词频统计 (调试):")
for word in ["哈", "哈哈哈", "啊", "啊啊啊", "yyds", "泪目"]:
count = all_word_freq.get(word, 0)
print(f" '{word}': {count}次")
return results, word_stats
# ========================
# 结果保存函数
# ========================
def save_results(results, word_stats, output_file):
"""
保存提取结果和统计信息
"""
print(f"开始保存结果到: {output_file}")
try:
# 保存详细结果
with open(output_file, "w", encoding="utf-8") as f:
# 写入标题
f.write("弹幕行号\t弹幕内容\t情感词列表\n")
# 写入每条弹幕的情感词
for item in results:
# 格式化情感词列表: 词[极性]
word_list = ", ".join([f"{word}[{pol}]" for word, pol in item["sentiment_words"]])
f.write(f"{item['line']}\t{item['text']}\t{word_list}\n")
# 添加分隔线
f.write("\n" + "=" * 80 + "\n\n")
# 写入情感词统计
f.write("情感词统计 (按出现频率排序):\n")
f.write("词语\t出现次数\t极性\n")
# 按出现次数排序
sorted_words = sorted(word_stats.items(), key=lambda x: x[1]["count"], reverse=True)
for word, stats in sorted_words:
f.write(f"{word}\t{stats['count']}\t{stats['polarity']}\n")
print(f"结果已成功保存至: {output_file}")
except Exception as e:
print(f"保存结果时出错: {str(e)}")
# ========================
# 极性分析报告
# ========================
def generate_polarity_report(word_stats, report_file="polarity_report.txt"):
"""生成极性分析报告"""
print(f"开始生成极性分析报告: {report_file}")
try:
# 按极性分组
pos_words = []
neg_words = []
for word, stats in word_stats.items():
if stats["polarity"] == "POS":
pos_words.append((word, stats["count"]))
elif stats["polarity"] == "NEG":
neg_words.append((word, stats["count"]))
# 排序
pos_words.sort(key=lambda x: x[1], reverse=True)
neg_words.sort(key=lambda x: x[1], reverse=True)
# 计算总体极性比例
total_count = sum(stats["count"] for stats in word_stats.values())
pos_count = sum(count for _, count in pos_words)
neg_count = sum(count for _, count in neg_words)
# 生成报告
with open(report_file, "w", encoding="utf-8") as f:
f.write("=== 弹幕情感极性分析报告 ===\n\n")
f.write(f"总情感词出现次数: {total_count}\n")
f.write(f"正向情感词: {pos_count}次 ({pos_count/total_count*100:.1f}%)\n")
f.write(f"负向情感词: {neg_count}次 ({neg_count/total_count*100:.1f}%)\n")
f.write("\n=== 高频正向情感词 TOP20 ===\n")
for i, (word, count) in enumerate(pos_words[:20], 1):
f.write(f"{i}. {word}: {count}次\n")
f.write("\n=== 高频负向情感词 TOP20 ===\n")
for i, (word, count) in enumerate(neg_words[:20], 1):
f.write(f"{i}. {word}: {count}次\n")
print(f"极性分析报告已成功保存至: {report_file}")
except Exception as e:
print(f"生成报告时出错: {str(e)}")
# ========================
# 词典调试检查
# ========================
def debug_dict_loading(sentiment_dict):
"""调试词典加载情况"""
print("\n词典调试检查:")
test_words = ["哈哈哈", "啊啊啊", "yyds", "泪目", "🤣", "😂", "无语", "离谱"]
for word in test_words:
if word in sentiment_dict:
print(f" ✓ '{word}' 已加载 -> {sentiment_dict[word]}")
else:
print(f" ✗ '{word}' 未在词典中找到!")
# ========================
# 主函数(改进版)
# ========================
def main():
# 1. 加载词典
sentiment_dict = load_sentiment_dict(SENTIMENT_DICT_FILE)
# 调试:检查特定词是否加载
debug_dict_loading(sentiment_dict)
# 2. 提取情感词
results, word_stats = extract_sentiment_words(SEGMENTED_DANMU_FILE, sentiment_dict)
# 3. 保存结果
save_results(results, word_stats, OUTPUT_FILE)
# 4. 生成极性报告
generate_polarity_report(word_stats)
# 5. 打印摘要统计
total_words = sum(stats["count"] for stats in word_stats.values())
unique_words = len(word_stats)
print("\n=== 摘要统计 ===")
print(f"总情感词出现次数: {total_words}")
print(f"独特情感词数量: {unique_words}")
# 按极性统计
pos_count = sum(stats["count"] for stats in word_stats.values() if stats["polarity"] == "POS")
neg_count = sum(stats["count"] for stats in word_stats.values() if stats["polarity"] == "NEG")
print(f"\n正向情感词比例: {pos_count}次 ({pos_count/total_words*100:.1f}%)")
print(f"负向情感词比例: {neg_count}次 ({neg_count/total_words*100:.1f}%)")
# 打印TOP10情感词
sorted_words = sorted(word_stats.items(), key=lambda x: x[1]["count"], reverse=True)[:10]
print("\nTOP10高频情感词:")
for i, (word, stats) in enumerate(sorted_words, 1):
print(f"{i}. {word}: {stats['count']}次 ({stats['polarity']})")
# ========================
# 执行程序
# ========================
if __name__ == "__main__":
main()
```
### 主要改进说明:
1. **多种分隔符支持**:
```python
# 支持多种分隔符: \t, 空格, :
if '\t' in stripped_line:
parts = stripped_line.split('\t')
elif ' ' in stripped_line:
parts = stripped_line.split(' ', 1) # 最多分割一次
elif ':' in stripped_line:
parts = stripped_line.split(':', 1)
```
2. **BOM字符处理**:
```python
# 使用utf-8-sig编码自动处理BOM字符
with open(file_path, "r", encoding="utf-8-sig") as f:
```
3. **极性清理**:
```python
# 移除极性部分的多余内容(如"POS"后面的说明)
polarity = re.split(r'\s+', polarity)[0] # 只取第一个词
```
4. **格式验证**:
```python
# 检查词语是否包含非字母数字字符
if not re.match(r'^[\w\u4e00-\u9fff]+$', word):
print(f"警告: 词语 '{word}' 包含特殊字符 (行 {line_num})")
```
5. **错误处理增强**:
```python
# 确保有足够的部分
if len(parts) < 2:
print(f"警告: 忽略格式错误行 {line_num} (分割后不足两部分): {stripped_line}")
continue
```
### 使用前准备:
1. **清理词典文件**:
在运行代码前,可以运行以下脚本清理词典文件:
```python
def clean_sentiment_dict(input_file, output_file):
"""清理情感词典文件格式"""
with open(input_file, "r", encoding="utf-8-sig") as f_in:
with open(output_file, "w", encoding="utf-8") as f_out:
for line in f_in:
stripped = line.strip()
# 跳过空行和注释
if not stripped or stripped.startswith("#"):
continue
# 替换多种分隔符为制表符
cleaned = re.sub(r'[::\s]+', '\t', stripped, 1)
# 确保格式正确
if '\t' not in cleaned:
cleaned = cleaned.replace(' ', '\t', 1)
# 只保留词语和极性
parts = cleaned.split('\t')
if len(parts) >= 2:
word = parts[0].strip()
polarity = parts[1].strip().upper()
# 只保留有效的POS/NEG
if polarity in ["POS", "NEG"]:
f_out.write(f"{word}\t{polarity}\n")
else:
print(f"跳过无效极性: {cleaned}")
# 使用示例
clean_sentiment_dict("merged_sentiment_dict.txt", "cleaned_sentiment_dict.txt")
```
2. **更新配置**:
在代码开头,将情感词典路径改为清理后的文件:
```python
SENTIMENT_DICT_FILE = "cleaned_sentiment_dict.txt" # 清理后的情感词典
```
### 运行步骤:
1. 运行清理脚本创建标准格式词典
2. 运行主程序处理弹幕数据
3. 检查输出文件中的统计结果
这个改进版本应该能解决您遇到的所有格式问题,并正确加载所有情感词。