☰
旅游景点评论数据挖掘实战:从5万条评论中定位游客抱怨与改进点
2026/10/10 20:49:17 网站建设 项目流程

简介:这份资源面向数据分析初学者与旅游行业从业者,围绕去哪儿网国庆期间景点数据展开实战演练,帮助读者掌握从数据清洗到业务洞察的完整分析流程。压缩包共7个文件,以5个html可视化页面为主,辅以1个xlsx原始数据表和1个py分析脚本,整体约79KB,轻量易上手。内容涵盖各省份旅游景点分布热力图、景区门票销量柱状图、星级分布比例饼状图及热门景点推荐排行等模块,脚本部分涉及Pandas、NumPy与Matplotlib等常用库的数据预处理与呈现。已有1379人学习下载,读者可借此理解地理信息可视化、时间序列趋势分析与数据驱动决策思路,适合作为课程作业、项目练手或旅游运营分析的参考案例。

1. 旅游景点数据分析实战:从5万条评论里挖出游客真正在抱怨什么

手里攥着某平台五万条景点评论,却只会算平均分和词频,这是很多做旅游数据分析的人踩过的第一个坑。旅游景点数据分析实战的核心,不是把数据丢进模型跑个准确率,而是回答三个具体问题:游客在哪个环节体验断崖、哪些抱怨是共性问题、改进哪一项能撬动评分。我做过几个景区的评论挖掘,最反直觉的结论是——评分4.8的景点,差评里出现最多的词不是“门票贵”,而是“排队”和“找不到”。这套方法适合有Python基础、手头有评论或客流数据、想做出可落地结论的运营和数据分析师。下面按数据获取、清洗、主题挖掘、归因分析、可视化验证的顺序拆开讲,每一步都给能直接抄的代码和参数。

2. 数据获取与清洗:把脏评论变成能分析的表格

2.1 评论数据从哪来、字段怎么定

旅游景点评论的常见来源有三类:平台公开评论页、景区自有小程序的评价模块、第三方问卷。我一般优先用平台公开数据,因为样本量大且带评分和时间戳。采集时不要只抓评论文本,下面这几个字段缺一个都会让后续分析翻车:

字段名类型用途缺失后果
comment_idstring去重主键重复评论拉高词频
contentstring文本分析主体无法做主题挖掘
scoreint评分归因无法区分好评差评
publish_timedatetime时间趋势看不出季节性抱怨
user_idstring用户去重水军刷评干扰结论
spot_namestring多景点对比无法横向比较

采集环节我不建议一上来就上分布式爬虫,单景点先用requests加时间休眠跑通,确认字段完整再扩量。常见做法是每页休眠1到2秒,单次采集不超过5000条,避免触发反爬导致数据断档。

2.2 清洗脚本:去重、去噪、分句

原始评论里混着表情符号、重复刷屏、广告导流,直接进模型会污染主题。下面这段清洗代码我用了很多次,核心是三步:去重、去非中文字符、按标点分句。

import re import pandas as pd def clean_comments(df): # 1. 按comment_id去重,保留第一条 df = df.drop_duplicates(subset=['comment_id'], keep='first') # 2. 去掉纯表情、纯符号、广告关键词 ad_pattern = re.compile(r'(加微信|代购|私聊|优惠券|点击链接)') df = df[~df['content'].str.contains(ad_pattern, na=False)] # 3. 只保留含中文的评论,长度过滤掉3字以下的 df = df[df['content'].str.contains(r'[\u4e00-\u9fa5]', na=False)] df = df[df['content'].str.len() >= 3] # 4. 按中文标点分句,展开成句级数据 def split_sentences(text): parts = re.split(r'[。!?;\n]', str(text)) return [p.strip() for p in parts if len(p.strip()) >= 4] df['sentences'] = df['content'].apply(split_sentences) df = df.explode('sentences').dropna(subset=['sentences']) df = df.rename(columns={'sentences': 'sentence'}) return df[['comment_id', 'sentence', 'score', 'publish_time', 'spot_name']] # 调用示例 raw = pd.read_csv('comments_raw.csv') cleaned = clean_comments(raw) cleaned.to_csv('comments_clean.csv', index=False) print(f'清洗后句级数据量:{len(cleaned)}')

逻辑说明:去重放在第一步是因为重复评论会让后续词频和主题权重失真。广告过滤用正则匹配常见导流词,这一步宁可多滤一点,广告句混进主题模型会带出无意义的聚类。分句的目的是把一条长评论拆成多个独立语义单元,比如“风景不错,但厕所太脏”拆成两句后,能分别归到正面和负面主题,不拆的话整条评论的情感会被平均掉。

参数说明:len(p.strip()) >= 4这个阈值可以调,评论短句多就降到3,长评论为主就升到6。ad_pattern里的词按你实际数据补充,不同平台导流话术不一样。分句标点里加上\n是因为很多用户习惯换行写多条感受。

清洗完先别急着上模型,用cleaned['score'].value_counts()看一眼评分分布,如果1分和5分占比超过70%,说明极端评价多,后续主题分析要分评分段做,不能混在一起。

3. 主题挖掘:LDA和BERTopic怎么选、参数怎么调

3.1 两种主题模型的选型理由

旅游评论的主题挖掘,常见做法是LDA和BERTopic二选一。LDA基于词袋,速度快、可解释性强,适合评论量在1万到10万条、主题边界清晰的场景。BERTopic基于预训练句向量,能捕捉语义相似但用词不同的句子,比如“排队两小时”和“等了好久才进去”能归到同一主题,代价是计算资源高、调参更玄学。

我的选择标准很简单:如果差评里大量出现同义不同词的情况,直接上BERTopic;如果评论用词比较统一、主题区分明显,LDA足够且更快出结果。下面两个都给出最小可跑通的代码。

3.2 LDA主题挖掘:sklearn实现与主题数确定

from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import LatentDirichletAllocation import jieba # 中文分词,去掉单字和常见停用词 stopwords = set(['的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这']) def tokenize(text): words = jieba.lcut(text) return [w for w in words if len(w) >= 2 and w not in stopwords] cleaned['tokens'] = cleaned['sentence'].apply(tokenize) cleaned = cleaned[cleaned['tokens'].map(len) >= 2] # 构建词频矩阵,限制最大词数和文档频率 vectorizer = CountVectorizer( tokenizer=lambda x: x, preprocessor=lambda x: x, token_pattern=None, max_features=3000, min_df=5, max_df=0.8 ) dtm = vectorizer.fit_transform(cleaned['tokens']) # LDA训练,主题数先设8,后续用困惑度调 lda = LatentDirichletAllocation( n_components=8, max_iter=20, learning_method='batch', random_state=42 ) lda.fit(dtm) # 打印每个主题的前10个词 feature_names = vectorizer.get_feature_names_out() for topic_idx, topic in enumerate(lda.components_): top_words = [feature_names[i] for i in topic.argsort()[:-11:-1]] print(f'主题{topic_idx}: {" ".join(top_words)}')

逻辑说明:分词后过滤单字是因为单字在中文里歧义太大,进主题模型只会增加噪声。max_features=3000控制词表大小,太大跑得慢且主题发散,太小会丢掉长尾关键词。min_df=5表示至少出现在5个句子里才纳入,滤掉偶然出现的词。max_df=0.8过滤掉80%以上句子都有的词,这类词通常是“景点”“门票”这种无区分度的。

参数说明:n_components是主题数,这是LDA最关键的参数。我的做法是先设8到12跑一遍,看主题词是否重叠,重叠就减,分不清就加。更严谨的方法是用困惑度曲线,但实操中人工看主题词可解释性更快。max_iter=20对多数评论数据够用,如果主题还没稳定就加到50。

3.3 BERTopic:语义聚类与动态主题

from bertopic import BERTopic from sentence_transformers import SentenceTransformer # 用中文预训练模型生成句向量 embedding_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') topic_model = BERTopic( embedding_model=embedding_model, language='chinese', min_topic_size=30, # 一个主题最少30句 nr_topics='auto', # 自动合并相似主题 calculate_probabilities=True ) topics, probs = topic_model.fit_transform(cleaned['sentence'].tolist()) cleaned['topic'] = topics # 查看主题概览 topic_info = topic_model.get_topic_info() print(topic_info.head(20)) # 查看某个主题的关键词 print(topic_model.get_topic(1))

逻辑说明:BERTopic先对句子做向量化,再用UMAP降维、HDBSCAN聚类,最后用c-TF-IDF提取每个簇的关键词。min_topic_size=30是核心参数,太小会碎成很多无意义主题,太大则会把不同问题合并。nr_topics='auto'让模型自动合并相似簇,省去手动调主题数的麻烦。

参数说明:paraphrase-multilingual-MiniLM-L12-v2是多语言模型,中文效果够用且比大模型快。如果评论量超过10万条,UMAP降维会慢,可以先把min_topic_size调大减少簇数量。calculate_probabilities=True会给出每句属于各主题的概率,后续做归因时能按概率加权,但计算时间翻倍,数据量大时可以关掉。

跑完两个模型后,把主题词和实际评论对照看,哪个模型的主题更能对应“排队”“卫生”“指示牌”“票价”这些具体问题,就用哪个。我一般两个都跑,LDA出快速结论,BERTopic验证语义归并是否合理。

4. 归因分析:差评到底集中在哪个环节

4.1 评分与主题的交叉表

主题挖出来只是第一步,真正有用的是知道哪个主题拉低了评分。下面这段代码把主题和评分做交叉,算出每个主题的平均分和差评占比。

import pandas as pd # 假设cleaned里已有topic列,score是1到5 pivot = cleaned.groupby('topic').agg( 评论数=('sentence', 'count'), 平均分=('score', 'mean'), 差评数=('score', lambda x: (x <= 2).sum()) ).reset_index() pivot['差评占比'] = pivot['差评数'] / pivot['评论数'] pivot = pivot.sort_values('平均分') print(pivot.to_string(index=False))

逻辑说明:按主题分组后算平均分和差评占比,平均分低且差评占比高的主题就是优先改进项。这里用score <= 2定义差评,如果你的数据评分分布偏斜,可以改成score <= 3。交叉表出来后,把平均分最低的三个主题对应的原始评论抽出来读20条,确认模型归因是否符合直觉。

参数说明:评论数少于30的主题建议合并或忽略,样本太少结论不稳。差评占比和平均分要一起看,有的主题平均分不低但差评占比高,说明体验两极分化,比如“缆车”可能有人觉得省力有人觉得排队久。

4.2 时间维度:抱怨是不是季节性的

cleaned['month'] = pd.to_datetime(cleaned['publish_time']).dt.month monthly = cleaned[cleaned['score'] <= 2].groupby(['month', 'topic']).size().unstack(fill_value=0) print(monthly)

逻辑说明:把差评按月份和主题交叉,能看出哪些问题是旺季集中爆发。比如“排队”主题如果只在7、8月差评飙升,说明是客流超载而非流程设计问题,改进方向是限流和分时预约,而不是加派人手。如果某个主题全年差评都高,那就是结构性问题,得从流程上改。

参数说明:月份提取用publish_time,如果数据里没有时间字段,这一步跳过,但结论会少一个维度。看月度交叉表时注意区分绝对数量和占比,旺季评论总量大,差评绝对数高不一定代表占比高。

5. 避坑与排查:旅游评论分析里最容易翻车的5个地方

5.1 现象:主题词全是“景点”“门票”“不错”,看不出问题

原因:停用词表没覆盖领域高频无意义词,且max_df设得太高,导致通用词霸占主题。解决:在停用词里加入“景点”“景区”“门票”“值得”“不错”“推荐”这类词,同时把max_df降到0.6,强制模型关注更有区分度的词。

5.2 现象:LDA每次跑出来的主题都不一样

原因:LDA是随机初始化,random_state没固定,且max_iter不够导致未收敛。解决:固定random_state=42,把max_iter加到50,如果主题还在变,说明数据量不够或主题数设错了,先减主题数再跑。

5.3 现象:BERTopic跑完发现大量句子被归到主题-1

原因:min_topic_size设得太大,HDBSCAN把不够成簇的句子全标为噪声。解决:把min_topic_size从30降到15或10,或者用topic_model.reduce_outliers()把噪声句重新分配到最近主题。注意降太小会产生大量碎主题,需要权衡。

5.4 现象:差评占比算出来所有主题都差不多

原因:评分和主题没有真正关联,可能是评分字段本身有问题,比如所有评论都是5分但文本是差评。解决:先做cleaned.groupby('score')['sentence'].count()看评分分布,如果评分集中在4到5分但文本有明显负面词,说明评分不可信,改用情感分析模型重新打标,再和主题交叉。

5.5 现象:分句后句子太短,主题模型跑出来全是无意义词

原因:分句阈值设得太低,把“很好”“不错”这种短句也纳入,这些句子信息量太低。解决:把分句的最小长度从4提到6,或者在分词后过滤掉词数少于3的句子。短句不是不能用,但只适合做情感统计,不适合做主题挖掘。

6. 用情感得分验证主题结论:一个可复用的校验技巧

主题挖掘和归因分析做完后,怎么确认结论不是模型幻觉?我的习惯是加一步情感得分校验。具体做法是用一个轻量情感分析模型对每句打分,然后看每个主题的情感均值是否和评分交叉表的结论一致。如果某个主题在评分交叉表里平均分低,但情感得分却偏高,说明要么评分数据有问题,要么主题归类错了,得回去查。

from snownlp import SnowNLP def get_sentiment(text): try: return SnowNLP(text).sentiments # 返回0到1,越接近1越正面 except: return None cleaned['sentiment'] = cleaned['sentence'].apply(get_sentiment) cleaned = cleaned.dropna(subset=['sentiment']) sentiment_check = cleaned.groupby('topic').agg( 情感均值=('sentiment', 'mean'), 评分均值=('score', 'mean'), 样本数=('sentence', 'count') ).reset_index() sentiment_check['情感评分差'] = sentiment_check['情感均值'] - (sentiment_check['评分均值'] / 5) print(sentiment_check.sort_values('情感评分差'))

逻辑说明:SnowNLP返回0到1的情感分,把它和评分除以5后的值做差,差值大的主题就是评分和文本情感不一致的地方。差值接近0说明两者一致,结论可信;差值绝对值大于0.2就要警惕,可能是评分刷分或者主题归类有误。这个校验步骤花不了几分钟,但能挡掉大部分“模型跑出来但结论不可信”的情况。

参数说明:SnowNLP对旅游评论这种短文本效果尚可,如果数据里反讽句多,它的准确率会下降,这时候可以换用更大的中文情感模型,但计算成本会上去。样本数少于20的主题不看这个指标,样本太少情感均值波动大。

最后说个我自己的习惯:每次跑完主题模型,我都会随机抽10条被归到“差评主题”的原始评论读一遍,人工确认模型没把反讽当好评、没把广告当体验反馈。这个动作看起来笨,但比任何指标都管用。旅游评论里“真是太好了,排队三小时”这种反讽句,情感模型和主题模型都可能翻车,只有人眼能兜住。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询