做电商数据分析的朋友应该都有体会商品评论区就是个金矿。这个基于Python的京东评论数据分析可视化项目目标很直接把某件商品的几万条用户评论爬下来清洗成干净的结构化数据做分词、词频统计、情感分析最后用pyecharts生成一眼能看懂的图表。整个过程涉及爬虫、数据处理、文本挖掘、可视化四条线对于想独立完成一个完整数据项目的Python入门者来说是非常合适的练手题对于电商运营或者产品经理来说这套流程做完能直接从评论里看出商品的真实口碑、用户痛点以及好评和差评分别集中在哪些维度。我最初做这个项目是因为需要评估一款竞品的用户反馈人工翻评论翻到崩溃。后来发现把评论采集和分析流程化以后一个小时就能处理完过去一周都看不完的数据。这篇文章把完整方案拆开来讲从接口分析、爬虫编写、数据清洗到情感分析、可视化呈现每一步都会给出可运行的代码和实际踩坑记录希望能帮你少走一些弯路。1. 项目整体拆解先做对方案再写代码做这类项目最容易犯的错误是一上来就写爬虫结果爬到一半发现字段对不上、接口改了、数据没法清洗。所以先把方案设计想清楚后面会顺利很多。1.1 这个项目到底在解决什么问题商品评论数据本质上是一批非结构化的短文本。区别于销量、价格这类数值数据评论文本里藏着用户的真实使用感受散热好不好、续航行不行、尺码准不准、客服态度怎么样这些信息在商品参数页里根本看不到。把评论数据分析可视化以后能解决几个实际场景的问题选品调研分析某类目下多个商品的评论能快速知道哪个竞品的负面槽点最少哪个卖点被提及最多。产品迭代找出高频负面词集中解决用户吐槽最多的几个问题比闭门造车改产品有效得多。口碑监控按时间维度看评论情感变化趋势能发现商品是不是在某次版本更新后口碑变差了。内容创作做商品评测、带货文案之前用词云和情感占比快速了解用户关注点写出来的内容更有针对性。这些场景有一个共同需求把大量非结构化文本变成结构化的量化指标。这就是这个项目的核心价值所在。1.2 技术选型解析为什么是requests jieba pyechartsPython做这类项目的优势在于生态完备几乎每个环节都有成熟的第三方库不需要重复造轮子。我的选型如下环节工具选型理由数据采集requests评论接口返回的是结构化JSON不需要用浏览器渲染requests足够稳接口数据解析json re接口带JSONP回调包裹需要用正则剥离多余部分数据清洗处理pandas处理表格型数据、去重、时间转换、分组统计非常高效中文分词jieba中文分词的事实标准支持自定义词典简单易用情感分析SnowNLP开箱即用基于电商评论场景训练对入门项目足够可视化pyecharts交互式图表输出HTML方便分享和汇报这几个库的组合非常经典和用Java或Go做相比Python的方案开发速度快数据分析和可视化链路最短。特别是pandas和pyecharts配合中间几乎不用写胶水代码。1.3 整体流程设计一条数据从采集到出图要走几步整个项目的链路可以划分为五个环节采集层请求评论接口翻页拿到原始JSON数据存储层把评论ID、内容、时间、评分、点赞数等字段整理成CSV文件清洗层去重、去空、过滤无意义内容、处理时间格式分析层中文分词、词频统计、情感打分、按评分分组聚合展示层用pyecharts生成评分分布、时间趋势、词云、情感占比图关键点在于清洗和分析的质量直接决定可视化结果有没有价值。如果爬下来一万条数据里面有三千条“此用户未填写评价内容”不做处理直接词云出来的图全是废话。2. 评论数据采集先找接口再写爬虫效率翻倍爬虫部分不是技术难点难点在于找到正确的数据接口并处理细节。我第一次做的时候傻乎乎去解析整个商品页面的HTML又慢又容易失效。后来打开开发者工具看了一下网络请求发现评论区数据是单独通过异步接口加载的直接请求JSON就好了。2.1 找到评论数据的真实接口别去解析HTML打开京东商品页按下F12进入开发者工具切到Network面板筛选XHR请求往下翻评论区能看到一个名为productPageComments.action的请求这就是评论数据的来源。接口地址大致长这样https://club.jd.com/comment/productPageComments.action?productId100012043978score0sortType5page0pageSize10isShadowSku0fold1核心参数含义如下参数说明常见取值productId商品ID打开商品详情页URL中能看到如 100012043978score评分筛选0全部、1差评、2中评、3好评、4追评sortType排序方式5推荐排序、6时间排序page页码从0开始pageSize每页条数实测填10最稳填太大可能拿不到完整数据isShadowSku是否获取SKU维度评论固定0即可这里有个容易踩坑的点接口返回的不是纯JSON而是带JSONP回调包裹的内容形如fetchJSON_comment98({...})。用requests拿到的text是fetchJSON_comment98(...)这样一串不能直接用response.json()解析需要先把外层包裹剥离。2.2 写一个稳定可断点续爬的采集脚本下面是一个可直接运行的采集脚本框架注释里写了关键细节import requests import json import re import csv import time import random def fetch_comments(product_id, page): url https://club.jd.com/comment/productPageComments.action params { productId: product_id, score: 0, # 全部评价 sortType: 5, # 推荐排序 page: page, # 页码从0开始 pageSize: 10, isShadowSku: 0, fold: 1 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: fhttps://item.jd.com/{product_id}.html } resp requests.get(url, paramsparams, headersheaders, timeout10) # JSONP格式: fetchJSON_comment98({...}) raw_text resp.text json_text re.search(r\((.*)\), raw_text, re.S).group(1) data json.loads(json_text) return data.get(comments, []) def collect_all(product_id, max_pages100): all_comments [] for page in range(max_pages): try: comments fetch_comments(product_id, page) if not comments: print(f第{page}页无数据停止采集) break for item in comments: all_comments.append({ id: item.get(id), content: item.get(content, ).strip(), score: item.get(score), time: item.get(creationTime), nickname: item.get(nickname, ), productSize: item.get(productSize, ), usefulVoteCount: item.get(usefulVoteCount, 0) }) print(f第{page}页采集完成累计{len(all_comments)}条) # 随机延时模拟人工操作节奏 time.sleep(random.uniform(2, 5)) except Exception as e: print(f第{page}页出错: {e}) time.sleep(10) continue return all_comments if __name__ __main__: product_id 100012043978 # 替换为目标商品ID rows collect_all(product_id, max_pages50) with open(jd_comments.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows) print(f共采集{len(rows)}条评论)几个细节说明存储CSV时用utf-8-sig编码而不是utf-8这样可以避免Excel打开时中文乱码。Referer一定要设置否则评论接口可能返回空数据。这个问题排查了很久才意识到。延时用random.uniform(2, 5)避免请求间隔完全固定太规律的请求容易被识别。虽然评论接口的反爬不算严格但基本的谨慎还是要有。2.3 反爬避坑频率、请求头、时间窗口京东评论接口整体上对爬虫不算苛刻但有一些细节必须注意第一pageSize不要填大。很多新手觉得每页填100一次性拉完效率高实测填20经常只返回几条填100甚至直接不返回数据。老老实实pageSize10翻页稳定比效率重要。第二请求频率一定要控制。我试过不延时连续请求大概七八页之后返回的JSON里就直接没有comments字段了整个页面像是被临时限流。后来改成2到5秒随机延时单线程跑几百页都没有出问题。第三注意评论只能爬到一定深度。京东不会把所有历史评论一次性开放到底热门商品翻到几百页之后接口会持续返回空列表。这不是你的代码有问题是平台侧的访问策略限制开头的一两千条数据已经足够做分析了不用纠结全量。第四尽量选择非促销时段跑脚本。大促期间接口压力大对这个评论接口的访问会更敏感平时爬得好好的脚本活动期间可能连续报错。3. 数据清洗与分词分析质量好不好全看这一步很多教程把大量篇幅放在爬虫上对数据清洗一笔带过。但实际做完一遍以后你会发现清洗才是决定分析结论有没有价值的关键环节。爬下来的原始数据里垃圾文本和有效信息的比例经常能到四六开。3.1 去重、时间转换、字段规整第一步处理的是数据规整主要做四件事import pandas as pd df pd.read_csv(jd_comments.csv) # 1. 去重按评论ID去重防止翻页过程中数据重复 df df.drop_duplicates(subset[id]) # 2. 去空评论内容为空白的直接删除 df df[df[content].notna()] df df[df[content].str.strip() ! ] # 3. 时间字段转为标准格式 df[time] pd.to_datetime(df[time]) # 4. 评分字段转为整数便于后续分组 df[score] df[score].astype(int) # 5. 过滤无意义文本 noise_keywords [此用户未填写评价内容, 默认好评, 好评, 收货后追加] for kw in noise_keywords: df df[~df[content].str.contains(kw)]这一步做完数据量可能少掉三成不用心疼。这些被去掉的文本对分析没有贡献留着反而会污染词频统计。关于“好评”这个词要特别说明直接按包含“好评”两个字符过滤会有风险因为很多真实评论里会说“商品很好值得好评”。稳妥做法是精确匹配“默认好评”或内容长度小于5的短评而不是简单包含过滤。3.2 文本清洗把噪声字符一次处理干净评论里经常混着HTML标签、表情符号、特殊字符和大量空白。不处理直接分词词频统计里会出现一堆“rdquo”“nbsp”之类的无意义词条。import re def clean_text(text): # 去除HTML标签 text re.sub(r[^], , str(text)) # 去除URL text re.sub(rhttps?://\S, , text) # 去除特殊符号和数字 text re.sub(r[^\u4e00-\u9fa5a-zA-Z\s], , text) # 合并多余空白 text re.sub(r\s, , text).strip() return text df[clean_content] df[content].apply(clean_text)正则表达式里的[^\u4e00-\u9fa5a-zA-Z\s]是保留中文、英文字母和空白其他的都替换掉。这个匹配范围对电商评价场景比较合适因为中文短文本里数字和特殊字符的信息量很低提前去掉能明显提升词频统计的干净程度。3.3 jieba分词与词频统计让关键词自己浮出来分词和词频统计是整个分析流程里最出效果的一步。用jieba做中文分词结合自定义词表能准确识别出“散热好”“续航强”“物流快”这类用户关心的关键词。import jieba from collections import Counter # 加载自定义词典提升专有名词识别准确率 # jieba.add_word(某型号手机) # jieba.add_word(散热表现) # 停用词表这些词对分析没有价值需要过滤 stopwords set([的, 了, 是, 在, 我, 有, 和, 就, 不, 都, 很, 也, 还, 这个, 那个, 东西, 商品, 感觉]) def get_words(text): words jieba.lcut(text) # 过滤停用词、单字、纯数字 filtered [w for w in words if w not in stopwords and len(w.strip()) 1] return filtered # 批量分词 df[words] df[clean_content].apply(get_words) # 词频统计 word_counter Counter() for words in df[words]: word_counter.update(words) # 输出词频Top 50 for word, count in word_counter.most_common(50): print(f{word}: {count})实际做这个项目踩过两个坑第一个坑是品牌名和型号名会被错误拆开。比如“苹果手机”可能被分成“苹果”和“手机”“某型号”可能被拆成两三个碎片。解决办法是给jieba添加自定义词表在分词前把商品名、品牌名、型号名都加进去能明显提高后续词频统计质量。第二个坑是“东西”“商品”“京东”这类词频虚高。它们和商品真正的优缺点无关但对词频排名影响很大。我最后是把这些词都加进了停用词表或者用“词频阈值且不在停用词表”双重条件来筛选。4. 可视化呈现用pyecharts把结论变成图表可视化是整个项目最直观展示成果的环节。pyecharts生成的是交互式HTML图表可以放大缩小、悬浮查看数据比静态图片更适合放到汇报材料或者分享给团队其他人。4.1 四个最值得做的可视化维度做完大量评论数据分析以后我总结出四个高频使用、信息密度高的可视化图表建议优先做评分分布图一眼看出商品整体口碑是集中在4到5分还是低分占比较大。评论时间趋势折线图能发现评论量在什么时间段激增一般对应促销节点或负面事件爆发期。高频词词云图直观展示用户讨论最多的主题词是“质量好”“物流快”还是“有异味”“售后差”。情感占比图把评论分成正向、中性、负向三类量化整体口碑倾向。这四个维度组合起来基本回答了一个核心问题这个商品被大家接受吗如果不接受主要问题出在哪。4.2 pyecharts代码实操柱状图、饼图、折线图、词云先看评分分布柱状图注意pyecharts 2.x版本的API写法from pyecharts import options as opts from pyecharts.charts import Bar, Pie, Line, WordCloud # 评分分布柱状图 score_counts df[score].value_counts().sort_index() bar ( Bar() .add_xaxis([str(s) for s in score_counts.index]) .add_yaxis(评论数, score_counts.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title商品评分分布), xaxis_optsopts.AxisOpts(name评分), yaxis_optsopts.AxisOpts(name评论数) ) ) bar.render(score_distribution.html)时间趋势折线图需要先把数据按时间聚合# 按日期统计评论数量 df[date] df[time].dt.date daily_counts df.groupby(date).size().sort_index() line ( Line() .add_xaxis([str(d) for d in daily_counts.index]) .add_yaxis(评论数, daily_counts.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title评论数量时间趋势), xaxis_optsopts.AxisOpts(name日期), yaxis_optsopts.AxisOpts(name评论数) ) ) line.render(time_trend.html)情感占比饼图这里需要先计算情感分类结果from snownlp import SnowNLP def sentiment_label(text): s SnowNLP(str(text)) score s.sentiments # 0到1之间越接近1越正向 if score 0.7: return 正向 elif score 0.3: return 负向 else: return 中性 df[sentiment] df[clean_content].apply(sentiment_label) sentiment_counts df[sentiment].value_counts() pie ( Pie() .add(, [list(z) for z in zip(sentiment_counts.index, sentiment_counts.values)]) .set_global_opts(title_optsopts.TitleOpts(title评论情感倾向分布)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c} ({d}%))) ) pie.render(sentiment_pie.html)词云图使用pyecharts自带的WordCloud组件# word_counter是前面词频统计得到的结果 word_data [(word, count) for word, count in word_counter.most_common(100)] wc ( WordCloud() .add(, word_data, word_size_range[20, 100], shapediamond) .set_global_opts(title_optsopts.TitleOpts(title评论高频词词云)) ) wc.render(wordcloud.html)pyecharts 2.x版本自带词云组件不需要额外下载词云字体库直接用就很方便。如果你的环境是1.x版本部分API会有差异建议直接安装最新版。4.3 一张图表怎么读出有效结论图做完以后关键一步是解读。图表本身不会告诉你答案但不同维度的组合能帮你建立完整的判断。举个例子某款手机评论分析中评分分布图看起来是典型的好评型曲线4到5分占比超过80%词云里却出现了“发热”“耗电快”“卡顿”这几个高频负面词。这时候如果只看评分会误判商品口碑但结合词云和情感分析就能发现大部分好评集中在物流、外观、屏幕这些维度而差评集中在性能体验上。这就是组合分析的价值。单一图表很容易误导多维度交叉验证才能得出相对可靠的结论。5. 常见问题排查爬虫、编码、情感分析全踩过的坑这个项目做完一遍踩坑记录能写一长串。下面这几个是高频问题整理成速查表供排查参考。5.1 评论区接口请求异常现象可能原因解决方案返回空数据JSON里没有comments字段缺少Referer请求头在headers中加入商品详情页地址作为Referer爬几页后连续报错请求频率过高触发限流加大延时到5到8秒暂停几分钟再继续返回内容不是JSON格式参数productId错误或商品无评价检查商品ID换个有评价的商品测试翻页到某页后一直为空接口只能访问最新若干页设置max_pages上限默认拿前几百页即可最让人困惑的是第一类问题请求能返回数据但解析出来是空。我那时候反复检查参数和解析逻辑最后发现就是缺少Referer。浏览器里正常访问是因为浏览器会自动带上来源页面的信息用代码模拟请求时必须自己手动加上。5.2 中文乱码与JSON解析失败编码问题在中文爬虫项目里几乎避不开。京东评论接口本身返回UTF-8编码正常情况下requests会自动处理但有些环境下面还是会出现乱码。处理思路是第一步确认响应编码用resp.encoding查看第二步设置resp.encoding utf-8强制指定。如果数据是从文本文件读取的注意文件本身保存成UTF-8格式。CSV存储统一用utf-8-sig这样Excel打开不乱码。JSON解析失败的常见原因是没处理JSONP外层包裹直接用json.loads解析原始文本。处理办法是先用正则提取括号内的内容再解析import re import json raw_text resp.text json_part re.search(r\((.*)\), raw_text, re.S).group(1) data json.loads(json_part)正则里的re.S参数容易漏掉如果不加遇到跨行的情况就匹配不到。5.3 pyecharts图表渲染不出来pyecharts常见的坑集中在版本差异上。1.x版本和2.x版本在导入路径和API写法上有明显区别。拿图表初始化来说1.x的写法是from pyecharts import Bar2.x变成了from pyecharts.charts import Bar配置项也从直接传参变成了add_global_opts()这种统一写法。如果按照旧教程写代码很容易报错。解决办法很简单创建虚拟环境后直接pip install pyecharts安装最新版然后按2.x的API文档写。图表渲染出来的HTML文件直接用浏览器打开即可查看不需要起一台本地服务器。另外一个容易忽略的点是图表HTML文件里有大量的JS资源是从CDN加载的如果处于网络受限环境加载慢会导致图表显示空白。解决办法是下载HTML文件时确认网络通畅或者在pycharts初始化时传入assets_host参数指定本地资源目录。5.4 情感分析结果不可信SnowNLP对电商评论的通用场景表现尚可但经常出现“明明是好评却判断成中性甚至负向”的情况。比如“外观惊艳就是发货有点慢”这类带转折的句子情感倾向其实是正向的但模型可能算成中偏负。提高准确率的思路有几个第一结合评分做弱监督修正。简单来说如果用户给了4分或5分但SnowNLP算出很低的情感分很大概率是误判可以强制修正为正向。同理1分到2分的高情感值评论也值得怀疑。def correct_sentiment(row): score row[score] sentiment_value row[sentiment_value] if score 4 and sentiment_value 0.4: return 正向 if score 2 and sentiment_value 0.6: return 负向 if sentiment_value 0.7: return 正向 if sentiment_value 0.3: return 负向 return 中性第二针对细分领域做定向优化。SnowNLP的通用模型训练语料和数码3C、美妆、服装等品类的语言习惯有差异。如果项目面向某个固定品类可以自己标注几百条数据结合评分做二次训练或规则修正。第三优先级策略。对于争议样本先看评分再结合关键词比如评论里出现“垃圾”“受不了”“退款”不管情感分多少都直接归为负向。这种关键词规则和模型打分混合的方案在工程上最实用。最后分享一点实际体会这个项目做完以后最大的收获不是“我会爬虫了”而是理解了数据处理流程里每一条链路之间的关系。爬虫只是把数据拿到手里清洗决定了数据能不能用分词决定了分析有没有颗粒度可视化决定了结论能不能传达给别人任何一个环节掉链子整个项目都会卡住。有一点我想特别提醒做爬虫类项目一定要有边界意识。控制请求频率、不爬取非公开数据、只用于学习研究这是基本功。评论数据虽然公开可访问但大规模、高频率抓取对平台服务器会产生压力练习时用少量数据验证流程就可以了。如果后续想在这个项目上继续扩展可以考虑几个方向一是把分析对象从单个商品扩展到同品类的多个竞品做对比分析二是引入文本聚类把评论文本按主题自动分组三是把脚本封装成定时任务每次促销活动后自动抓取一批新评论做口碑追踪。项目本身像一个数据管道的雏形接上更多数据源和分析模块就能变成一套实用的选品辅助工具。