ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫与情感分析:携程景点评价口碑评分实战

Python爬虫与情感分析:携程景点评价口碑评分实战 开头先交代一下背景。去年准备带家里人去周边玩两天本来想在各景点介绍页面上挑一个口碑稳的结果刷了几十个景点评价后彻底放弃好评区一堆“不错”“还行”差评区全是“排队长”“管理混乱”看得越久越不知道去哪。后来我干脆写了个Python小工具把携程上几个备选景点的用户评价全部爬下来用情感评分分析给每条评论打一个情绪分再按景点汇总成一份从0到10的“口碑指数”。效果比我预想的强不少——至少能一眼看出哪个景点是“服务质量拖后腿”哪个是“风景好但配套设施差”。这篇文章就围绕这个项目完整展开携程景点评价怎么抓、数据怎么清洗、中文文本怎么分词、情感评分怎么算以及我最常踩的几个坑和解决办法。想入门Python爬虫的朋友可以照着敲对文本情感分析感兴趣的也能直接复用后半部分的思路。1. 项目定调为什么值得做携程评价爬虫与情感评分分析1.1 一个现实需求出行前被评价淹没旅游决策本质上是个信息筛选问题。携程上的景点评价少则几十条多则几千条每条评论都带着时间、评分、文本内容、点赞数。人眼去读这些内容效率很低而且情绪会被极端评价带着走。这个项目解决的正是“如何把大量非结构化文本变成结构化、可比较的分数”。我选择携程作为数据源一是因为它的景点覆盖全二是因为评价字段相对规范评论内容里包含的实体信息也比较丰富——比如“售票处”“缆车”“导游”“排队”“停车”这些词经常出现很容易聚类出景点的优势或短板。1.2 项目的核心价值表面上的产出是一份评分报表本质上的价值有三个层次对用户来说能得到一个相对客观的口碑分而不是被“好评返现”之类的行为干扰。对分析者来说可以把“好评率”这种单一指标变成“情绪分布”“高频不满点”“节假日口碑变化”等更细的维度。对想入门Python开发的人来说这个项目同时覆盖了网络请求、HTML/JSON解析、数据清洗、中文分词、情感计算、可视化和文件存储是一套非常完整的综合练习。1.3 适合谁参考这篇文章如果你是刚把Python基础语法过了一遍想知道requests到底怎么落地或者你已经写过简单爬虫但对中文文本处理和情感分析完全没头绪再或者你只是想找个练手项目把“爬虫数据分析”串成一个完整闭环——这篇文章都适合你。不过有一点必须先说清楚爬虫只能在合理频率、尊重目标网站规则的前提下使用。项目里的代码请用于学习研究不要大批量采集、不要商业化转发更不要试图绕过平台的风控机制。1.4 整体流程先过一遍我在设计这个项目时把流程拆成了五个大环节数据采集从携程景点评价接口获取原始评论。数据清洗去重、去缺失、过滤无效文本。文本预处理分词、去停用词、构建自定义词典。情感计算基于情感词典和否定词规则给每条评语算出情感得分。结果汇总按景点聚合输出评分和可视化图表。每个环节边界越清晰后期调试就越省事。实际写代码时我建议你也按这个顺序推进不要一上来就想着把所有功能揉在一起。2. 环境准备与技术选型解析2.1 Python环境与核心库清单这个项目不需要多高配的环境Python 3.8以上即可我用的3.10。依赖库也很常规requests2.25.1 pandas1.3.0 beautifulsoup44.9.3 lxml4.6.0 jieba0.42.1 snownlp0.12.3 matplotlib3.4.0 wordcloud1.8.1安装方式就是pip install -r requirements.txt。如果你不打算用SnowNLP也可以只装前六个库情感部分自己写规则同样能跑。2.2 页面数据从哪来接口解析与静态页面取舍刚接触爬虫的人容易一上来就去解析HTML但携程的景点评价并不是一个纯静态页面。直接对HTML做文本提取会遇到三个麻烦热门评论是动态加载的、翻页时URL不变、部分字段藏在接口返回里。所以我在项目里选择了“直接调接口”的方案。操作方法并不复杂打开景点评价页面按F12进入开发者工具切到Network面板刷新页面后逐个查看XHR请求找到返回内容里包含评论文本的接口复制它的URL和请求参数。携程返回的数据一般是JSON格式里面包含评论内容、评分、用户昵称、评论时间等字段。解析JSON比解析HTML稳定很多因为字段结构一致不容易因为页面改版而失效。2.3 为什么用requests而不是Scrapy很多教程上来就推荐Scrapy但在这个项目里我没有用它原因是性价比太低。携程景点评价并不是一个需要定时、大规模、分布式采集的目标用Scrapy会带来额外的学习成本还要处理爬虫中间件、Item Pipeline等概念。用requests的好处是透明、可控每一行代码都知道在干什么。同时requests配合Session对象可以很好地保存Cookie翻页请求也比较自然。等以后你确实需要采集多站点、并发任务特别重再迁移到Scrapy也不迟。2.4 并发与限速的平衡爬取时最容易犯的错就是并发调得过高短时间猛打服务器结果被限制访问甚至影响正常用户。我在这个项目里没有做复杂的并发池只用了一招请求之间加随机延迟。每次请求后随机sleep 1到2秒既保证速度又明显降低了风险。如果你需要采集的景点很多可以考虑用concurrent.futures.ThreadPoolExecutor做一个3到5线程的小并发每线程都持有独立的Session但一定要控制总频率。3. 数据采集携程景点评论爬虫的完整实现3.1 分析评价数据接口以我做过的一个案例为例。打开某景点的用户评价页在Network面板里可以看到一个返回评论列表的请求它的Response大概长这样{ data: { commentList: [ { content: 风景很美但是缆车排队太久, score: 4.0, user: 游客123, addTime: 2024-05-02 15:30:01 } ] } }关键就是把content、score、user、addTime这几个字段取出来。实际接口返回的字段可能带嵌套结构比如user可能是个对象也可能评论内容字段名不是content具体情况要以你抓到的接口为准。我的建议是先把接口返回的JSON保存到本地文件用json.loads解析后再用print(data.keys())一层层看结构。不要凭记忆猜字段名费时费力。3.2 构建爬虫会话与请求头模拟浏览器的核心是请求头。缺少部分Header时接口很容易返回异常数据或验证码。我常用的基础请求头如下import requests import time import random headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, Referer: https://you.ctrip.com/, Origin: https://you.ctrip.com, } session requests.Session() session.headers.update(headers)这里有几个细节容易忽略Referer和Origin都要带上这是服务端校验来源的重要依据Cookie如果登录过也要挂到Session上否则可能只拿到默认排序的部分数据。另外不管headers写得多像浏览器如果请求频率太高照样会被拦截。3.3 翻页参数与终止条件评价接口翻页一般不会用页码而是用类似pagination的参数每一页返回一个游标或者最后一条评论ID下一页要用这个值作为参数。具体参数名我看过不同版本的接口差异很大有的叫pageId有的叫lastId还有直接用页码的不要死记。写代码时我会把翻页写成一个循环循环内先请求当前页再判断返回的数据量。如果返回的评论列表为空或者已经达到目标页数就停止。这种写法比硬编码页数更可靠因为当景区评论较少时接口可能返回不了那么多页。def fetch_comments(session, base_params, max_pages20): comments [] params dict(base_params) for page in range(1, max_pages 1): resp session.get(COMMENT_API_URL, paramsparams, timeout8) if resp.status_code ! 200: print(fpage {page} error: {resp.status_code}) break data resp.json() items extract_comment_items(data) if not items: break comments.extend(items) params.update({pagination: get_next_pagination(data)}) time.sleep(random.uniform(1, 2)) return comments注意这里每次请求结束时都要sleep而且sleep时间要有随机性。固定1秒很容易被识别成脚本随机1到2秒会“像人”得多。3.4 异常处理与断点续爬网络请求最怕的不是错误而是不知道错在哪。我给请求逻辑加了三层保险第一层请求异常时重试3次每次间隔递增。第二层每页抓取成功后就立刻把数据写入CSV而不是攒在内存里等全部完成再写。第三层记录当前第几页已经爬完程序中断后可以接着跑。断点续爬特别重要。我有一次爬到一百多页时网络断了如果没有断点逻辑就得从第一页重新请求不仅浪费时间还增加风险。3.5 数据存储先落CSV再分析抓下来的数据我先统一存成CSV字段只保留后续分析需要的景点名称、评论内容、用户评分、评论时间、点赞数。这样做的原因是分析阶段和采集阶段解耦就算后面情感代码改了一版又一版也不用重新爬一遍。写入CSV时我推荐用pandas.DataFrame.to_csv并加上encodingutf-8-sig。如果不加这个参数在Windows下用Excel打开CSV时中文会乱码。4. 文本预处理从评论文本到可计算的向量4.1 数据清洗规则爬下来的原始数据很脏直接拿去算情感分结果基本是灾难。我总结了几类必须处理的情况长空白、重复标点的评论例如“好好”。明显复制粘贴的广告内容比如“加微信免费领攻略”。相同用户在同一景点发布的重复或近似评论。只包含表情符号的评论。清洗的核心是“保语义”不能为了干净把有用信息删掉。比如“风景好但是门票贵”这种转折句就不能因为看到“门票贵”而把整句判定为负面。清洗阶段主要处理格式语义判断留给情感分析阶段。4.2 jieba分词与自定义词表中文不像英文有天然空格所以要先分词。我用的jieba是Python里生态最成熟的中文分词库支持多种分词模式、自定义词典和词性标注。import jieba text 风景很美但是缆车排队太久整体体验一般。 seg_list jieba.lcut(text) print(seg_list) # [风景, 很, 美, , 但是, 缆车, 排队, 太久, , 整体, 体验, 一般, 。]直接分词会遇到一个问题旅游领域词汇识别不准。比如“白马寺”“云台山”“玻璃栈道”可能被拆成“白马”“寺”或者“玻璃”“栈道”。解决办法是维护一个自定义词表把景点名、景区设施、当地小吃等词加进去。for word in [白马寺, 玻璃栈道, 长隆野生动物世界]: jieba.add_word(word)4.3 停用词过滤停用词指的是“的、了、也、在、是”这类没有实义的高频词。分词后如果不过滤情感分析里的高频词表会被这些词占据词云图也全是废话。我用的是一个公开的百度停用词表再加了旅游场景里常见的“我们”“你们”“就是”等词。过滤时要谨慎不能把“不”这类否定词也停了否则“不好”会变成“好”情感判断完全反转。4.4 长评论切句与短评处理的区别情感分析最好是按“句”计算而不是直接拿一长段评论去算。一段评论里往往包含多个子观点“风景很美”是正面的“缆车排队太久”是负面的整段的综合情绪没法用一个单一标签概括。我的做法是先把评论按句号、感叹号、问号切分成短句然后逐句计算情感分最后把整条评论的情感分聚合为“均值”或“均值加极值惩罚”。对于几条字的短评“还不错”“太坑了”可以分成“还挺好”“一般般”“十分差劲”等几档。5. 情感评分分析构建一个“口味匹配度指数”5.1 方案选择情感词典 vs SnowNLP vs 微调模型在这个项目里我评估了几种常见方案SnowNLP开箱即用自带中文情感分类模型但它训练语料偏电商和微博对旅游场景的效果一般。基于情感词典的自定义打分核心是情感词库加否定词、程度副词规则透明可控能针对旅游数据调整。微调预训练模型效果好但需要标注数据搭配GPU对一个轻量项目来说成本过高。我最终选择了“自建情感词典规则校准SnowNLP辅助”的组合。具体做法是先用SnowNLP算一个基础值再用自建词典对旅游领域词汇进行修正。两者结合比单独用任何一个都稳定。5.2 自建情感词典的计算逻辑情感词典的核心是一场“加减法”。我维护了一个分数在-2到2之间的情感词表“失望”“差”“坑”为负分“美”“赞”“推荐”为正分。计算时遍历评语的所有分词查到情感词就累加得分。但裸做一个情感词累加结果会很粗糙。比如“完全不值得去”“值得”是正分“完全”是程度副词把“值得”的分数翻倍前缀“不”再反转一次最终就变成强烈负分。如果不处理否定和程度这句子可能被误判成中性甚至正向。5.3 否定词与程度副词的处理规则的顺序很关键。我的处理顺序是先找到情感词再看它前一个词是不是否定词最后看否定词前面是否还有程度副词。举个例子“非常好”程度副词“非常”把“好”的2分乘以1.8得3.6分。“不好”“不”把“好”的2分取反得-2分。“不很好”先否定“很”其实是“不好”的意思不能简单取反。这类组合要额外加规则。我简单列了程度副词的分档级别示例词权重弱有点、略、稍0.6中比较、蛮1.2强很、非常、太1.8极强超级、极其2.0对旅游评价“太”字有时候是正面“太好了”有时候是负面“太失望了”所以不能脱离后续情感词单独判断。“太失望了”里的“太”和“失望”组合实际是强负向逻辑上要取“程度副词放大负向词”的处理。5.4 把情感得分映射成0-10评分原始情感得分是一个连续浮点数为了更直观我把它映射到0到10的区间。映射不是简单的线性缩放而是让中间段更平缓两端更有区分度。我用的是类似sigmoid的转换先对情感得分做一个裁剪比如-5到5范围然后用公式def sentiment_score_to_mark(score): # score 是原始情感得分范围大约在 -5 到 5 之间 normalized (score 5) / 10.0 # 压缩到 0~1 mark round(normalized * 10, 1) return max(0.0, min(10.0, mark))这么做有个好处情感得分天然带有“普通人评价打分”的感觉后期给景点排名时可以直接说“云台山口碑分8.2远高于同批平均的6.7”。实际评测中这种评分比只看“用户星评”更贴近评论文本的真实情绪因为很多用户只打3星但文字全是好评情感分析能从文字里把“推荐”的意图捞出来。6. 结果可视化和落地输出6.1 用matplotlib绘制评分分布评分分布图能告诉你的不是“这个景点好不好”而是“它的口碑有多两极分化”。我用matplotlib画了一个直方图横轴是情感评分0到10纵轴是评价数量。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False plt.hist(df[sentiment_score], bins20, color#4C72B0, alpha0.8) plt.title(景点评价情感得分分布) plt.xlabel(情感得分) plt.ylabel(评论数量) plt.tight_layout() plt.savefig(sentiment_distribution.png, dpi150)如果一个景点是高耸的尖峰在8分附近说明口碑比较一致如果同时出现两个峰一个在2分附近一个在9分附近说明这个景点可能“设施陈旧但风景优秀”是典型的名副其实两极分化型评价。6.2 词云提取主题光有分数还不够得知道大家具体在表扬或者抱怨什么。我用wordcloud生成正面和负面词云作为评分报告的补充材料。正面词云里高频词一般是“风景”“推荐”“好玩”“方便”负面词云里则会出现“排队”“门票”“停车”“态度”等词。做词云前记得把高频的地名词保留不然“这个”“那里”这类词会把主题词淹没。我还会把“真的”“相当”这类修饰词单独过滤避免干扰。6.3 输出景点口碑排行榜当有多个备选景点时我会按“平均情感得分”排序同时输出评论数Top20的高频词形成一张一页能看完的对比表。这张表比任何一篇游记都直观因为它不是单个人的感受而是几十上百条评论的统计结果。我把结果导出成CSVsummary df.groupby(spot)[sentiment_score].agg([mean, count, std]).reset_index() summary.columns [景点, 平均情感得分, 评论数, 标准差] summary summary.sort_values(平均情感得分, ascendingFalse) summary.to_csv(spot_summary.csv, indexFalse, encodingutf-8-sig)7. 常见问题与避坑实录7.1 请求被拦截怎么办如果请求返回403、状态码异常或者页面弹验证码最常见的原因是频率太高而不是请求头写得不对。先把并发数降下来把sleep时间拉长再试试加一点随机参数。如果还是被限制就换个网络环境或者把第一次访问页面时拿到的完整Cookie挂到Session上。尽量不要去用各种公共代理不稳定不说还可能带来合规风险。7.2 评论字段解析为空JSON解析为空十有八九是字段嵌套层级没搞对。我犯过的错误是把commentList当成直接返回结果它在data下面还隔了一层。每次解析前先打印一条完整的JSON确认字段层级再写提取逻辑。7.3 中文方框与缺失字体matplotlib默认字体不支持中文画图时会出现一排方框。第一行代码要写plt.rcParams[font.sans-serif] [SimHei]。Linux服务器上如果没有SimHei就需要先安装中文字体或者指定系统自带的其他中文字体。7.4 情感模型误判严重怎么办最典型的误判是“又美又挤”被算成中性甚至正面。原因是“挤”这个负面向词在基础词典里权重不够。我的经验是不断积累旅游场景专属词加到自定义情感词表里慢慢做迭代。另外“但是”这类转折词也要特殊处理转折后的情绪权重应该高于转折前的情绪权重。7.5 合规与采集边界这一点我必须单独强调做爬虫练习要遵守对方网站的用户协议控制请求频率不采集个人隐私信息不把数据用于商业用途。携程的评论文本受著作权保护把爬下来的数据打包传播是很危险的行为。这个项目最大的意义是学习技术、验证想法而不是做一个无限抓数据的机器。8. 最后我踩完坑之后的几点建议如果让我重做一遍我会在动手写代码前先把手动请求的返回结果存成多个测试文件再把解析逻辑和采集逻辑分开。这样做的好处是即使目标网站改版了我只需要重新拿一份测试文件就能快速定位是采集层出问题还是解析层出问题。另外情感评分模型不要追求一次做到完美。第一版规则粗糙没关系先跑通流程再根据错误案例一点点加词典、加规则。这个迭代过程本身就是这个项目最值得学习的地方。等你手里的负面案例越来越少算法对“排队三小时就为了看五分钟风景”这句话能准确给出很低的分数你就能体会到数据分析的快乐了。
返回列表