《文本分析》实操指南

卢圣华,2025年3月2日

本指南详细介绍如何在公共管理研究中运用 Python 进行常用的数据处理与分析方法,包括中文文本的分词与词云可视化、情感分析(关键词匹配和 SnowNLP)、文本分类(监督学习的朴素贝叶斯分类和无监督学习的 LDA 主题建模),以及关键信息提取(正则表达式和 spaCy 实体识别)。我们将使用 Python + Jupyter Notebook 来编写与演示这些过程。代码力求简洁易懂,并配有详细注释,方便同学理解代码逻辑,并可对参数进行自由调节。

目录:


0. 教程概述与配套文件说明

本教程《文本分析实战》旨在为大家介绍文本分析方法在公共管理领域的具体应用,涵盖中文分词、词云生成、情感分析、文本分类以及关键信息提取等内容。通过本教程,你将掌握如何使用 Python 及相关库来实现以上任务。

为了帮助你更好地实践学习,我已准备了一套完整的配套文件

文件名文件说明
training.csv用于训练文本分类模型的示例数据
full.csv用于预测或主题建模的完整示例数据
stopwords.txt中文停用词列表,用于过滤无意义的词语
SimSun.ttf中文字体文件,用于词云生成时避免中文乱码
ntusd-positive.txt中文情感分析的正面情绪词典
ntusd-negative.txt中文情感分析的负面情绪词典
ChnSentiCorp_htl_all.csv常用的中文酒店评论情感分析数据集
wordcloud.png示例词云图片
text_analysis.ipynb配套的Jupyter Notebook代码示例

如何下载和使用配套文件

为了方便大家获取和使用上述文件,我已经将所有文件打包整理并上传到Notion。你只需点击下方链接,进入页面后即可预览文件内容并进行下载:

注意事项:
  • 下载文件并解压后,请确保将所有文件保存在同一个工作目录下,以便顺利运行教程中的代码示例。
  • 推荐使用Jupyter Notebook打开并运行代码文件(text_analysis.ipynb)。

接下来,你可以进入教程的第一章,开始中文分词及词云的学习和实践 🎉


1. 中文分词及词云

背景与目标: 中文文本分析的第一步通常是中文分词。由于中文没有空格来分隔单词,我们需要借助分词工具将句子切分成有意义的词语。常用的中文分词库有 jieba 等。完成分词后,我们可以通过词频统计来了解文本的关键词分布,并使用词云(Word Cloud)将高频词进行可视化,从直观上展示文本中的重要词汇。

本节将介绍如何使用 jieba 对中文文本进行分词,并基于分词结果绘制词云图。我们将使用一段示例中文文本自行演示整个过程。

1.1 中文分词(jieba)

要解决的问题: 将一段中文文本切分为单独的词语序列,以便后续进行统计和分析。

实现思路: 使用 jieba 库的默认分词模式对文本分词。jieba 库具有简单易用的接口,我们可以通过 jieba.cut() 将文本切分成生成器,然后转成列表或用空格拼接。分词的精细程度可以通过调整模式(精确模式、全模式、搜索引擎模式)或自定义词典来控制。

代码示例: 我们将对一段示例文本进行分词,并展示分词结果列表:

# 导入jieba库
import jieba

# 示例中文文本
text = "在公共管理领域,数据驱动决策变得越来越重要。这需要掌握各种研究方法,包括文本分析和数据挖掘方法。"

# 使用jieba进行中文分词
words = jieba.lcut(text)  # 使用精确模式进行分词,返回列表
print("分词结果:", words)

代码详解:

运行上面的代码,将输出一个列表形式的分词结果。例如:

分词结果: ['在', '公共管理', '领域', ',', '数据', '驱动', '决策', '变得', '越来越', '重要', '。', '这', '需要', '掌握', '各种', '研究', '方法', ',', '包括', '文本', '分析', '和', '数据', '挖掘', '方法', '。']

可以看到句子被成功切分成了词语和标点符号。分词的结果将作为后续词频统计和词云生成的基础。

1.2 绘制词云

要解决的问题: 将文本中的高频词直观地展示出来,突出重要词语。词云是一种将词频用字体大小表示的可视化方法,频率越高的词显示得越大。

实现思路: 使用 wordcloud 库根据分词结果生成词云图。首先需要统计词频或直接将所有词拼接成字符串,然后传给 WordCloud 对象。为了确保词云更加准确,我们需要去除停用词(如“的”“是”“和”等常见但无意义的词),然后再进行可视化。我们可以设置词云的一些参数,如字体、背景颜色、最大词数等,然后生成并显示图像。

代码示例: 下面演示如何基于上一小节得到的分词结果来绘制词云,并考虑停用词的处理(假设 stopwords.txt 已放在工作目录下)。

# 安装并导入词云库(如果尚未安装,需要使用 pip 安装 wordcloud)
# !pip install wordcloud
from wordcloud import WordCloud

# 读取停用词表(假设 stopwords.txt 放在工作目录下)
with open("stopwords.txt", "r", encoding="utf-8") as f:
    stopwords = set(f.read().splitlines())

# 过滤停用词
filtered_words = [word for word in words if word not in stopwords and len(word) > 1]  # 去掉单字词(通常无意义)

# 将分词结果列表拼接成空格分隔的字符串
text_for_wc = " ".join(filtered_words)

# 配置词云参数并生成词云对象
wc = WordCloud(font_path=None,  # 字体路径,中文必须指定中文字体路径,否则会乱码。这里使用默认字体时可能无法正确显示中文
               background_color="white",  # 背景颜色白色
               max_words=100,             # 显示的最大词数
               width=800, height=400,     # 图片尺寸
               collocations=False         # 是否避免重复单词,这里不需要启用以免影响中文显示
              )
wordcloud = wc.generate(text_for_wc)       # 根据文本生成词云

# 将词云保存为图片文件(在Jupyter中也可以直接显示)
wordcloud.to_file("wordcloud.png")
print("词云已生成并保存为 wordcloud.png 文件。")

代码详解:

词云结果: 经过停用词处理后,生成的词云将更加清晰地突出核心关键词。例如,如果使用上面的示例文本,词云可能会突出显示“数据”“方法”“公共管理”等重要词汇,而不会被“的”“是”“在”等无意义词干扰。

(注意:在实际运行时,请确保提供中文字体路径以正确显示中文。在本手册环境中,我们将代码展示为主要参考,运行效果请在本地 Jupyter Notebook 中执行观察。)


2. 情感分析

背景与目标: 情感分析旨在判断文本所表达的主观情绪倾向,例如评价一句话是正面(积极)还是负面(消极)。在公共管理研究中,情感分析可用于分析公众对政策的态度、社交媒体上的民意倾向等。

情感分析的方法主要分为两类:

本节将分别介绍这两种方法:首先构建一个简单的关键词匹配情感分析器,然后演示使用 SnowNLP 进行情感分析。

2.1 基于关键词的情感计算

要解决的问题: 不借助复杂模型,如何利用预先准备的情感关键词来判断文本情感?

实现思路: 先准备两份情感关键词词典:一份包含常见的正面情绪词(如“好”、“满意”、“赞赏”等),另一份包含负面情绪词(如“差”、“失望”、“愤怒”等)。对于给定文本,我们统计其中出现的正面和负面词数量,或者根据匹配情况打分。简单的方法:计算情感分值 = 正面词计数 - 负面词计数,分值大于0可判为正面,反之负面(或根据具体需求设定阈值)。

代码示例: 下面我们构造简易的正负面词典,并对两条示例句子进行情感判断:

# 定义简单的情感关键词字典
positive_words = ["好", "满意", "赞", "喜欢", "高兴"]   # 正面情感词列表(可以根据需要扩充)
negative_words = ["差", "不好", "失望", "愤怒", "讨厌"]  # 负面情感词列表

# 情感分析函数:计算情感分值(正面词+1,负面词-1)
def sentiment_score(text):
    score = 0
    for word in positive_words:
        if word in text:
            score += 1
    for word in negative_words:
        if word in text:
            score -= 1
    return score

# 示例文本
text1 = "这次服务质量很好,我很满意!"
text2 = "产品问题太多了,让人非常失望。"

# 计算情感分值
score1 = sentiment_score(text1)
score2 = sentiment_score(text2)

print(f"文本1: {text1} -> 情感分值: {score1}")
print(f"文本2: {text2} -> 情感分值: {score2}")

# 根据分值判断情感极性
def sentiment_label(score):
    if score > 0:
        return "正面"
    elif score < 0:
        return "负面"
    else:
        return "中性或无法判断"

print(f"文本1情感极性判断: {sentiment_label(score1)}")
print(f"文本2情感极性判断: {sentiment_label(score2)}")

代码详解:

运行上述代码,可得到类似输出:

文本1: 这次服务质量很好,我很满意! -> 情感分值: 2
文本2: 产品问题太多了,让人非常失望。 -> 情感分值: -1
文本1情感极性判断: 正面
文本2情感极性判断: 负面

优缺点讨论: 基于关键词的情感分析实现简单直观,但也有局限:

在实际应用中,简单的关键词匹配可用于快速粗略分析,但如果需要更高的准确率,通常会引入机器学习模型或更复杂的规则。下面我们将介绍使用机器学习库 SnowNLP 对中文文本进行情感分析。

2.2 使用 SnowNLP 进行情感分析

要解决的问题: 利用现有的中文情感分析工具包,对文本的情感倾向进行自动判断,减少手工构建规则的工作。

实现思路: SnowNLP 是一个专门针对中文的自然语言处理库,支持中文分词、关键词提取、情感分析等功能。SnowNLP 内置了一个中文情感分析模型(据称基于大量带标注的点评数据训练),可以直接对一句话给出一个情感倾向评分。这个评分通常是0到1的浮点数,接近1表示偏向正面,接近0表示偏向负面。

使用 SnowNLP 进行情感分析非常简单:将文本传入 SnowNLP 对象,然后调用其 .sentiments 属性即可得到情感概率值。

代码示例: 演示 SnowNLP 对正面和负面句子的情感判断:

# 安装并导入 SnowNLP 库(如果未安装,需要使用 pip 安装 snownlp)
# !pip install snownlp
from snownlp import SnowNLP

# 示例文本(一个正面,一个负面)
text_pos = "这个政策真是太好了,帮助了很多人。"
text_neg = "这个决策糟透了,引起了广泛的不满。"

# 使用 SnowNLP 进行情感分析
s1 = SnowNLP(text_pos)
s2 = SnowNLP(text_neg)

sentiment_score1 = s1.sentiments  # 返回情感倾向概率,接近1表示正面
sentiment_score2 = s2.sentiments

print(f"文本: '{text_pos}' -> 情感倾向评分: {sentiment_score1:.3f}")
print(f"文本: '{text_neg}' -> 情感倾向评分: {sentiment_score2:.3f}")

# 根据阈值判断正负面
threshold = 0.5
label1 = "正面" if sentiment_score1 >= threshold else "负面"
label2 = "正面" if sentiment_score2 >= threshold else "负面"
print(f"文本1判断为: {label1}, 文本2判断为: {label2}")

代码详解:

运行结果举例:

文本: '这个政策真是太好了,帮助了很多人。' -> 情感倾向评分: 0.928
文本: '这个决策糟透了,引起了广泛的不满。' -> 情感倾向评分: 0.134
文本1判断为: 正面, 文本2判断为: 负面

(具体输出可能因 SnowNLP 内部模型而略有不同,但总的来说第一句评分会远高于0.5,第二句会远低于0.5。)

SnowNLP 方法的特点:

通过对比可以看到,SnowNLP 这种模型方法相较关键词匹配更加智能,能够综合考虑句中的各种信息(如否定词、程度词等)得出情感倾向,且使用更加简单。但需要注意模型可能存在偏见,需要评估其对你要处理的数据集是否适用。


3. 文本分类

背景与目标: 文本分类是将文本归入预先定义的类别中的过程。在公共管理研究中,文本分类可用于很多场景,比如将政策文件按领域分类(教育、医疗、环保等),或者将市民意见分类为不同类型的诉求。文本分类方法分为监督学习(有标签的数据训练模型)和无监督学习(无标签情况下自动聚类主题)。

本节我们介绍两种方法:

我们将基于一些实际数据(假设已存放于工作目录中)演示如何使用这两种方法实现文本分类任务,并展示如何训练模型和解释结果。

3.1 监督学习:朴素贝叶斯分类

要解决的问题: 给定一些已经标注好类别的文本数据,如何训练一个分类模型,对新文本进行自动分类?

朴素贝叶斯方法简介: 朴素贝叶斯是一种基于概率的简单高效的分类算法,适用于文本分类等任务。它假设特征(词语)之间相互独立(“朴素”的由来),依据训练数据计算先验概率和似然,然后对新样本使用贝叶斯定理计算后验概率,选择概率最大的类别作为预测结果。尽管独立性假设较强,朴素贝叶斯在很多文本分类场景下表现良好,尤其是当特征数很多、每个特征影响较小的情况。

实现思路: 使用 sklearn 提供的 MultinomialNB (多项式朴素贝叶斯)分类器。具体流程如下:

  1. 导入数据:从文件中读取已标注的文本数据。
  1. 中文分词:使用 jieba 对文本数据进行分词处理。
  1. 特征提取:使用 TfidfVectorizer 将分词后的文本转为数值特征向量。
  1. 训练模型:用标注好的数据训练朴素贝叶斯模型。
  1. 预测分类:对新数据进行类别预测并输出预测结果。

代码示例:

import os
import pandas as pd
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

# 设置工作目录
work_dir = r"E:\OneDrive\PhD Candidate\Python Project\Jupyter Notebook\课程教学\文本分析实战"
os.chdir(work_dir)

# 读取数据
train_df = pd.read_csv("training.csv")
full_df = pd.read_csv("full.csv")

# 中文分词函数
def tokenize(text):
    return " ".join(jieba.cut(text))

# 对数据进行分词处理
train_df["processed_text"] = train_df["新闻标题"].apply(tokenize)
full_df["processed_text"] = full_df["新闻标题"].apply(tokenize)

# 构建分类模型(朴素贝叶斯)
nb_pipeline = Pipeline([
    ('vect', TfidfVectorizer()),
    ('clf', MultinomialNB()),
])

# 使用训练数据训练模型
nb_pipeline.fit(train_df["processed_text"], train_df["分类"])

# 对完整数据集进行分类预测
full_df["predicted_category"] = nb_pipeline.predict(full_df["processed_text"])

# 输出部分分类预测结果
print(full_df[["新闻标题", "predicted_category"]].head(10))

代码详解:

运行结果示例:

                 新闻标题 predicted_category
0     高校图书馆尝试夜间自助借还服务                 教育
1      沿海湿地锐减引发候鸟栖息危机                 教育
2  民营企业融资环境转暖,投资者信心回升                 经济
3       大学教授倡导混合式教学改革                 教育
4   城市污水处理厂排放监测结果不容乐观                 环境
...

3.2 无监督学习:LDA 主题建模

要解决的问题: 当我们没有预先的标签时,如何让模型自动地发现文本集合中潜在的主题分类?这就是主题模型要解决的任务。

LDA 方法简介: LDA (Latent Dirichlet Allocation) 是一种常用的主题模型,它假设每篇文档由若干个主题混合生成,而每个主题是若干词的概率分布。简单来说,LDA 能够根据词语的共现关系,将文档集聚类成若干主题,每个主题对应一组高概率的关键词。与聚类不同的是,一篇文档可以同时属于多个主题(只是比例不同)。

实现思路: 使用 sklearnLatentDirichletAllocation 来进行 LDA。基本步骤:

  1. 数据预处理:对文本数据进行中文分词处理。
  1. 特征表示:使用 TfidfVectorizer 将文本数据转化为特征向量。
  1. 训练 LDA 模型:设置合适的主题数量并训练模型。
  1. 展示主题关键词:输出每个主题对应的关键词。
  1. 查看文档主题分布:了解每篇文档所属的主要主题。

代码示例:

from sklearn.decomposition import LatentDirichletAllocation

# 特征向量化
vectorizer = TfidfVectorizer()
X_full = vectorizer.fit_transform(full_df["processed_text"])

# 构建并训练LDA模型
lda = LatentDirichletAllocation(n_components=3, random_state=42)
lda.fit(X_full)

# 显示每个主题的关键词
def display_topics(model, feature_names, no_top_words):
    for topic_idx, topic in enumerate(model.components_):
        print(f"主题 {topic_idx+1}: ", " ".join([feature_names[i] for i in topic.argsort()[:-no_top_words - 1:-1]]))

display_topics(lda, vectorizer.get_feature_names_out(), 10)

# 预测每篇文档的主要主题
topic_results = lda.transform(X_full)
full_df["LDA_Topic"] = topic_results.argmax(axis=1)

# 输出部分LDA主题预测结果
print(full_df[["新闻标题", "LDA_Topic"]].head(10))

运行结果示例:

主题 1:  升级 生态 带动 监测 项目 海洋 行业 试点 转型 国内
主题 2:  提升 导致 高校 质量 减少 融资 保护 企业 引发 带动
主题 3:  市场 全球 加速 高校 乡村 改革 上升 模式 显示 学生

                 新闻标题  LDA_Topic
0     高校图书馆尝试夜间自助借还服务          2
1      沿海湿地锐减引发候鸟栖息危机          1
2  民营企业融资环境转暖,投资者信心回升          1
3       大学教授倡导混合式教学改革          1
4   城市污水处理厂排放监测结果不容乐观          0
...

LDA模型的劣势与注意事项:

上述运行结果反映出LDA的典型缺点——主题的含义并不总是十分明确。从结果中我们可以看到:

这些问题在LDA建模中较为常见。为提升主题建模效果,我们通常需要:

小结:

监督学习(朴素贝叶斯)适合已有标注数据时进行精确分类;无监督学习(LDA)适合在没有预定义类别时探索潜在的主题结构。但LDA无法完全自动地生成非常明确、易解释的主题,往往需要进一步人工干预或结合其他模型提升效果。在实际应用中,通常会先用LDA进行初步的主题探索,再结合领域专家意见人工明确分类标准,然后再使用监督学习方法进行更准确的分类预测。


4. 关键信息提取

背景与目标: 在公共管理研究或实际办公场景中,我们经常需要从文本中提取特定信息。例如,从政策公告中提取涉及的机构名称、从合同文本中提取金额和日期、从投诉信中提取公司名称或人名等。这类任务可以看作信息抽取的一部分。

常用的关键信息提取方法包括:

本节我们将介绍上述两种方法的简要应用:首先通过正则表达式提取公司名称和金额等简单模式的信息,然后演示如何使用 spaCy 库的实体识别功能来提取更一般的实体。

4.1 使用正则表达式提取信息

要解决的问题: 根据事先定义的格式,从非结构化文本中抽取出符合该格式的字符串。

实现思路: Python 内置的 re 模块提供了正则表达式匹配功能。我们需要根据待抽取的信息设计相应的正则模式。例如:

代码示例: 使用正则从一段文本中提取公司名称和金额:

import re

# 示例文本,其中包含公司名称和金额信息
text = "根据合同,ABC有限公司将在2023年投资1000万元用于社区服务提升项目。另有XYZ科技公司预计投入500万。"

# 正则模式定义
company_pattern = r"[\u4e00-\u9fa5A-Za-z0-9]+公司"   # 匹配简易公司名称
money_pattern = r"\d+\.?\d*万?元"                    # 匹配金额(元或万元)

# 查找所有匹配项
companies = re.findall(company_pattern, text)
moneys = re.findall(money_pattern, text)

print("提取的公司名称列表:", companies)
print("提取的金额列表:", moneys)

代码详解:

运行结果:

提取的公司名称列表: ['ABC有限公司', 'XYZ科技公司']
提取的金额列表: ['1000万元']

注意输出中只有“1000万元”,因为“500万”没有跟“元”,未被匹配。若我们想包括“500万”,可以将模式改为 r"\d+\.?\d*万?元?" 使“元”成为可选。这里演示的是严格匹配带“元”的金额形式。

正则方法优缺点:

因此,当我们要提取的信息类型模糊或多样时,可以借助机器学习的实体识别来完成。这就是下一小节要介绍的内容。

4.2 使用 spaCy 进行实体识别

要解决的问题: 自动识别文本中的命名实体,如人名、地名、组织机构名、时间日期、货币金额等,而不需要为每种类型手写规则。

实现思路: spaCy 是一个强大的自然语言处理库,内置了预训练的命名实体识别模型(NER)。spaCy对多种语言有支持,包括英文和中文等。使用spaCy进行NER的步骤如下:

  1. 加载语言模型:对于中文,需要加载中文的模型(如zh_core_web_sm)。对于英文,则加载英文模型(如en_core_web_sm)。
  1. 处理文本:用模型对文本进行分析,得到一个文档对象,其中包含各种NLP分析结果,包括实体。
  1. 提取实体:遍历文档中的实体列表,获得每个实体的文本及其类型标签(如PER人名,ORG组织,GPE地名,DATE日期,MONEY金钱等)。

先决条件: 需要安装spaCy及对应语言模型。例如:

pip install spacy
python -m spacy download zh_core_web_sm

模型安装后,可以通过 spacy.load("zh_core_web_sm") 加载。模型大小较大,初次使用需要下载,本手册不直接执行下载。

代码示例: 演示 spaCy 实体识别(假设已安装中文版模型)。我们将对上一小节的示例句子用spaCy来识别其中的实体:

import spacy

# 加载spaCy的中文模型(小型)
nlp = spacy.load("zh_core_web_sm")

# 待处理文本(与前述正则例子相同)
text = "根据合同,ABC有限公司将在2023年投资1000万元用于社区服务提升项目。另有XYZ科技公司预计投入500万。"

doc = nlp(text)  # 分析文本

# 提取并打印实体及其类型
for ent in doc.ents:
    print(ent.text, ent.label_)

代码详解:

预期输出:

运行对上述 text 的实体识别,输出结果类似于:

ABC有限公司 ORG
2023年 DATE
1000万元 MONEY
500万 CARDINAL

解释:

可以看到,相比我们手写的正则,spaCy自动提取出了我们关注的公司名和金额,并且甚至识别了日期。这很大程度上减轻了人工规则的负担。不过需要注意:

总结

实际应用中,两者经常结合使用:先用NER模型找出可能的实体,再用正则核验格式,或者在NER未覆盖的特殊格式上用正则弥补。


5. 扩展讨论与总结

通过上述内容,我们实践了中文文本分析中的几个重要方法,从基础的分词到高级的主题模型和实体识别。在公共管理研究中,这些方法可以组合应用于政策分析、舆情监控、文本数据挖掘等场景,为数据驱动决策提供支持。

本手册小结:

拓展思考:

上述每个主题都只是相关领域的起步:

使用建议: 同学们可以基于本手册内容,选取自己的文本数据(比如政府工作报告、新闻评论等)进行尝试。在实践中调整参数、观察结果,将有助于加深对各方法的理解。以下是一些有趣的 GitHub 开源数据集,可供同学在文本分析实践中使用:

此外,以下社区资源也可帮助同学更深入地了解相关工具和方法:

一个是工作目录设定,改成自己的;一个是需要先安装对应的小程序,进入dos环境用pip安装,这里涉及到好几个,如jieba、snownlp、pandas等等,import调用的几个程序如果出现问题,一般都是没有安装,pip安装就好;加载spacy中文模型这里,zh_core_web_sm,需要先在dos环境这里下载,C:\Users\Administrator>python -m spacy download zh_core_web_sm