
简介面向计算机相关专业学生及毕业设计开发者这份资源围绕携程酒店评论构建了从爬虫采集、数据清洗预处理到情感分类分析的可视化完整链路。基于Python与Jupyter Notebook实现覆盖评论分词、情感词典构建、特征值提取、模型分析与预测并产出词云、热力图等可视化结果对文本挖掘与情感分析入门实践有较高参考价值。资源包共23个文件约15.87MB核心包括py脚本、ipynb分析笔记、txt说明与数据、zip压缩的爬虫/预处理结果、HTML可视化页面及docx报告目录划分清晰便于按阶段学习。目前已有638人学习浏览项目均通过运行验证功能正常适合作为课程设计、大作业或初期立项演示可直接参考或扩展二次开发。 说真的酒店评论是情感分析最适合练手的场景之一文本短、表达直接、正负情感词特别密集而且用户关心的问题高度集中。我把携程的酒店评论爬下来走完清洗、分词、情感打分、可视化这一整套流程最后用热力图把“评论情感得分”和“用户打分”的关系呈现出来整个过程比想象中坑多但跑通之后非常有成就感。这篇文章就把这个项目的完整思路、关键代码和踩坑记录整理出来适合正在学爬虫、NLP或者数据分析的读者参考。项目里包含了可运行的爬虫脚本、数据预处理模块、基于情感词典的情感分类代码、可视化分析脚本以及一份写得很详细的说明文档。如果只想快速跑通流程按说明装好库就能出结果如果想弄懂背后的原理下面每一节都是按照实际执行顺序写的照着走一遍基本能复现整个项目。1. 为什么选携程评论做情感分析项目目标与技术组合1.1 从评论里能读到什么携程的酒店评论有个很典型的特点用户会在一条评论里同时提到位置、卫生、服务、价格、噪音等多个维度而且情感倾向往往是混合的。比如“房间很干净但隔音太差楼下酒吧吵到半夜”——前半句是正向后半句是负向。如果只用“用户给了4分”就判定为满意会丢失大量信息但直接训练一个深度学习模型又需要标注数据对个人项目来说成本太高。所以这个项目定了一个更务实的目标不追求把每条评论识别得100%准确而是用情感词典给每条评论打一个“情感倾向分数”然后结合用户评分、评论长度、酒店星级等结构化字段做对比分析。这样既能看出“哪些酒店虽然评分高但评论情感偏向负面”也能横向比较不同地段、不同星级酒店的舆论口碑。1.2 为什么不用现成API而用情感词典市面上的情感分析API确实方便传一段文本就返回正负向概率但问题也很明显一是调用量有限制爬几千条评论可能就触发限流二是黑盒模型不容易解释结果不知道它为什么把“卫生间太小”判成中性三是无法针对酒店领域定制词表像“隔音”“床垫”“前台”这类词在通用模型里权重不明显。情感词典方案不需要训练逻辑透明还能根据酒店评论的特点不断扩充词典。配合否定词、程度副词规则准确率在短文本场景下足够用。这也是很多商业舆情系统早期采用的方案。所以这个项目选择了“情感词典规则加权”的技术路线核心是可控、可解释、可迭代。2. 携程评论爬虫从页面到结构化数据2.1 评论接口的定位与参数构造携程酒店详情页的评论数据并不是静态HTML里一次性输出的。打开开发者工具切到Network面板刷新评论列表能看到一个XHR请求返回的是JSON格式的评论数据。这个接口的URL里有几个关键参数酒店ID、页码、评论类型全部/好评/差评、排序方式等。实际写爬虫时只需要先手动在页面上翻一页复制出请求URL和表单数据用requests库模拟发送就行。这里不要自己去拼完整URL因为携程的接口参数经常调整。更稳定的做法是先用浏览器复制出完整的请求头再在代码里保留关键字段。我当时保存的请求头大概长这样headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://hotels.ctrip.com/, Accept: application/json, text/plain, */*, }参数构造时先把固定参数写成一个字典每翻一页改变pageIndex的值。这样代码看起来清晰也方便后续对接别的酒店ID。注意有些接口的评论内容会做Unicode转义直接请求返回的JSON里可能是\u597d这样的字符需要调用json.loads解析不能直接当字符串处理。2.2 请求伪装与频率控制携程的反爬策略不算特别激进但也不傻。单线程连续快速请求很容易被识别为脚本轻则返回验证码页面重则封IP。我的建议是请求之间至少间隔3到5秒最好用time.sleep(random.uniform(3, 5))做随机延时。如果爬取量比较大可以准备一个代理IP池轮换但个人学习项目用本地IP慢慢爬就够了。另外一定要设置超时时间。因为部分请求可能被服务器丢弃不设超时的话脚本会一直卡住。我的习惯是requests.get(url, headersheaders, paramsparams, timeout10)失败就记录日志并重试连续失败三次就跳过当前页。这样哪怕中途断网重跑时也不会从头开始。2.3 评论字段提取与存储评论JSON里能拿到的字段很多但这个项目只保留了最核心的几个用户名、评分、评论内容、评论时间、房型。把这些字段整理成表格后续做分析和可视化就方便了。提取时要注意有些字段是嵌套字典比如“酒店回复”可能不存在需要用get方法加默认值避免KeyError。存储格式我选了CSV而不是数据库因为数据量在几千到几万条CSV用pandas读起来非常快也方便直接进入预处理阶段。写CSV时要指定utf-8-sig编码不然用Excel打开会乱码。每爬完一页就把DataFrame追加到文件里而不是等全部爬完再写这样能减少内存占用也避免中途失败导致数据全丢。3. 数据预处理把评论变成能用的语料3.1 数据清洗去重、去空、去HTML标签爬下来的评论不会干干净净。首先会有重复数据可能是接口返回了同一评论也可能是我们自己重试造成的。用pandas的drop_duplicates(subsetcontent_id)按评论ID去重最稳妥没有ID就按“用户时间内容”组合去重。然后是空评论和噪声。携程的部分评价只有评分没有文字这类数据在做情感分析时没有价值直接丢掉。评论内容里偶尔会出现HTML标签、换行符、特殊字符用正则清理import re def clean_text(text): if not isinstance(text, str): return text re.sub(r[^], , text) # 去HTML标签 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 保留中文英文数字空格 text re.sub(r\s, , text) # 合并空白 return text.strip()这一步特别重要。如果不清理后面分词时会把“房间很干净”切出奇怪的结果甚至把表情符号当成词干直接影响情感打分。3.2 分词与停用词处理中文分词我用的是jieba因为它在社区普及度最高安装简单不开箱即用。默认分词对普通文本效果不错但酒店评论里有很多品牌名、地域名、俗称比如“外滩”“亚朵”“全季”这些词默认可能会被切分成“外/滩”或者“亚/朵”。所以需要提前加载自定义词典把专有名词固定下来import jieba jieba.load_userdict(hotel_dict.txt)hotel_dict.txt每一行是“词语 词频 词性”比如“全季酒店 100 nf”“外滩 50 ns”。这样分词时就不会拆散这些词。停用词表也非常关键。中文里“的”“了”“是”“而且”这类词出现频率极高但对情感判断没有帮助不删除的话会让后续的词频统计失真。哈工大停用词表、百度停用词表都可以在网上找到把它们合并起来再额外加入“酒店”“房间”“入住”这类虽然和酒店相关但情感中性的通用词避免它们霸占词云。3.3 自定义词典与领域词补充酒店评论里有一些特殊表达比如“隔音差”“位置好找”“性价比高”。如果用通用情感词典“隔音差”里的“差”会被识别为负向词这个没问题但“性价比高”里的“高”在通用词典里是中性词它和“性价比”组合后其实变成了正向表达。这类情况需要靠领域词表和规则弥补。我维护了一个“酒店领域词典”包含两部分一部分是领域概念词如“床垫”“淋浴”“前台”“早餐”“停车”等用于分词后提取主题另一部分是领域情感词如“安静”“干净”“便捷”“贴心”“温馨”等标注了情感极性。这些词可以自己在爬下来的评论里统计高频词后手动整理也可以用网上的酒店评论情感词表做底再按实际数据增删。4. 基于情感词典的评论情感打分实现4.1 情感词典的构建与扩充项目的情感词典采用一个简单的格式词、极性、权重。极性用1表示正向-1表示负向权重表示情感强烈程度比如“喜欢”权重1“超级喜欢”权重2“讨厌”权重-2。网上的通用情感词典可以直接用比如BosonNLP情感词典、大连理工情感词汇本体库但需要注意覆盖率和领域偏差。比如通用词典里没有“吵”“脏”“旧”这类在酒店场景高频出现的负面词如果漏了它们一条“房间很吵很脏很旧”的评论可能会被判成中性。处理办法是先拿通用词典跑一遍爬下来的评论把所有情感分数为0但用户评分很低的评论筛出来查看里面出现了哪些词典里没有的词再把这些词按人工标注加入领域词典。这样迭代两三次覆盖率就能达到不错的效果。4.2 否定词、程度副词与感叹号的加权规则光有词典还不够中文的否定和程度表达会翻转或加强情感。比如“不满意”和“满意”就差一个“不”字如果只是逐词累计分值就会把“不满意”判断成正向。所以打分时要做窗口判断先对句子分词然后遍历每个词如果遇到情感词就向前看紧邻的几个词如果存在否定词不、没、别、未、莫就把情感值取反如果存在程度副词很、太、超、非常、极其就把情感值乘以对应的权重。具体实现可以是这样的伪代码def score_sentence(words, sentiment_dict, neg_words, degree_words): total 0 for i, word in enumerate(words): if word in sentiment_dict: score sentiment_dict[word] * sentiment_dict_weight.get(word, 1) if i 0 and words[i-1] in neg_words: score -score if i 1 and words[i-2] in degree_words: score score * degree_words[words[i-2]] total score return total实际代码中还要考虑否定词与程度副词的组合顺序比如“不是太好”程序应该识别出“不是”后面的“好”被打折而不是完全反转。这个可以通过设置否定词只对最近一个情感词生效来近似。另外评论中的感叹号会强化情感我给每条评论的感叹号数量乘了一个小系数加进总分。4.3 情感得分与情感倾向的转化每条评论最终会得到一个数值分数。把所有评论的分数画出来基本呈一个双峰分布正向集中在较高区间负向集中在较低区间。可以设定一个经验阈值比如大于0.5为正向小于-0.5为负向中间为中性。但这个阈值最好根据数据分布动态调整我用的是正负分数均值的中间点再加一个偏移这样能减少“用户打了高分但情感分不高”的系统偏差。这里有一个技巧把用户评分也纳入校准。用户给5分的评论如果情感分却低于-1说明这条评论很可能是在“表扬中夹杂强烈批评”值得单独拿出来看。这类评论往往比一面倒的差评更有分析价值。5. 数据可视化热力图、词云与情感分布5.1 用户评分与情感得分的关系热力图热力图是这个项目里最直观的一个输出。我用seaborn.heatmap画了一张二维密度热力图横轴是用户评分1-5分纵轴是评论情感得分区间颜色越深代表该组合下的评论数量越多。图出来之后能明显看到大部分评论集中在“5分-高情感分”区域但“5分-低情感分”区域也有一条明显的拖尾那就是“好评但情感负面”的特殊群体。画热力图之前先要把连续的情感得分离散化比如每0.5分一个桶。然后建一个透视表行是情感得分区间列是用户评分值是评论数量。这样直接传给heatmap即可import seaborn as sns import matplotlib.pyplot as plt pivot df.pivot_table(indexemotion_bins, columnsuser_rating, valuescount, aggfuncsum) sns.heatmap(pivot, cmapYlOrRd, annotTrue, fmtd) plt.show()这个图比散点图好读因为酒店评论数量大散点会重叠到看不清分布。用热力图能快速看出评论情感与评分的匹配程度也是项目报告里最有说服力的图表。5.2 高频词与情感词云词云是另一张能快速展示特征的图。我把清洗后的评论分词、去停用词用Counter统计词频然后喂给wordcloud.WordCloud生成词云。但单纯的全量词云信息量太低大家都会做。我建议按情感分组正向评论组和负向评论组分别生成词云对比看。正向词云里会出现“干净”“方便”“热情”“好吃”负向词云里会出现“噪音”“隔音”“脏”“态度差”“排队”。这种对比比单张词云更能说明问题。生成词云时注意两点一是要设置font_path指定中文字体否则中文全显示成方块二是wordcloud默认会过滤掉单字词但有些单字情感词比如“脏”“吵”反而是重要信号可以用max_words和collocations微调或者直接传入自定义词频字典。5.3 时间维度与酒店维度的情感趋势除了热力图我还画了“情感得分随评论时间的变化”折线图。按周聚合计算平均情感得分能看出酒店的口碑是持续向好还是下滑。这个对用户选酒店很有参考价值。比如某家酒店近三个月平均情感分从1.2降到-0.5那就说明近期体验变差了即使总评分还是4.5分也要谨慎。如果爬了多家酒店还可以画一个“酒店ID-情感得分”的箱线图把不同酒店的情感分布并列展示比单纯比评分更有意义。因为用户评分的主观性太强有些人习惯打5分有些人习惯打3分但评论内容里的情感词更能反映真实体验。6. 完整源码运行说明与踩坑记录6.1 项目文件结构与运行步骤整个zip解压后大概是这样的目录project/ ├── crawler/ # 爬虫相关 │ ├── ctrip_spider.py │ └── headers.json ├── data/ # 原始数据与清洗后数据 │ ├── raw_comments.csv │ └── clean_comments.csv ├── preprocessing/ # 数据预处理 │ ├── clean.py │ ├── segment.py │ └── stopwords.txt ├── sentiment/ # 情感分析 │ ├── sentiment_dict.txt │ ├── degree_words.txt │ └── score.py ├── analysis/ # 可视化 │ ├── heatmap.py │ ├── wordcloud.py │ └── trend.py ├── requirements.txt └── README.md运行顺序是先跑ctrip_spider.py爬数据再跑clean.py清洗然后segment.py分词接着score.py情感打分最后运行analysis目录下的脚本出图。requirements.txt里主要是requests、pandas、jieba、matplotlib、seaborn、wordcloud安装命令就是pip install -r requirements.txt。6.2 爬虫返回乱码或者字段缺失怎么办第一次跑爬虫时我遇到的典型问题是返回的JSON里评论内容字段不确定。有时候是content有时候是comment还有嵌套在extInfo里。不要硬编码字段名而是先把整个返回JSON用pprint打印出来人工确认一层结构再写提取逻辑。如果服务器返回了Gzip压缩的数据requests会自动解压但如果手动设置了Accept-Encoding可能需要自己gzip.decompress。如果出现乱码检查response.encoding通常需要显式设置成utf-8或者从响应头里获取charset。最保险的做法是用response.text前先response.encoding utf-8。6.3 分词结果不理想怎么办一个常见坑是没加载自定义词典导致“星级酒店”被切成“星级/酒店”。如果分词后发现大量专有名词被拆开需要用jieba.suggest_freq提高某个词出现的频率比如jieba.suggest_freq(全季酒店, True)。另一个坑是停用词表过滤掉了太多个性化词导致句子里只剩下一堆动词和名词。这时候要回看停用词表把“什么”“怎么”这类疑问词保留下因为“服务不怎么样”里的“怎么样”是情感判断的关键。6.4 情感词典覆盖不全的自我迭代方法没有哪个现成情感词典是完美适配酒店评论的。我在项目里写了一个辅助脚本把情感得分为0但用户评分极低或极高的评论导出人工扫描这些评论中出现的高频词再补充进情感词典。比如“衣架”本身是中性词但“没有衣架”就是明确的负面信号通用情感词典不会把“衣架”标为负所以只能靠领域规则。我的做法是在情感词典里加入诸如“没有X”“X差”“X坏”的短语模式用正则先做一层匹配再交给词典打分。这种半自动迭代的方式虽然费时间但每轮迭代之后准确率提升都很明显。如果你也要做类似项目建议把这一步当成正式流程而不是一次性用例。最后按照我个人的实际经验不要指望情感分数和用户评分完全一致两者不一致的地方反而才是值得深挖的亮点。你可以把“高评分-低情感分”和“低评分-高情感分”这两类评论单独打印出来读一读往往能发现很多有趣的数据洞察。本文还有配套的精品资源点击获取