花了两周,我做了个 Python 评论数据分析可视化的项目:把某主流电商平台上某个商品链接下的公开评论全部抓下来,做清洗、分词、情感打分,最后用图表把"用户到底在夸什么、吐槽什么、什么时间段口碑集中爆雷"整成一页能直接打开的 HTML 看板。这套流程不依赖任何私有工具,核心就是 requests、pandas、jieba、snownlp 和 pyecharts,完全可以复用到其他商品的评论分析场景。如果你正在学 Python 数据分析,想找一个贴近业务、有完整数据链路的练手项目,这篇内容应该对你有用。下面我会把从采集、清洗、分析到可视化的每个环节都拆开讲,包括我踩过的坑和调优思路,尽量做到你照着文章就能把整个项目复现出来。1. 项目整体设计:先想清楚再动手1.1 评论数据为什么值得分析电商评论是用户用真金白银换来的反馈,它不像问卷那样有暗示性,很多时候用户会直接说出产品的好与坏。但这些评论天然是非结构化文本,几千条甚至几万条堆在一起,靠人力一条条看,效率太低,而且很容易漏掉关键信息。把评论数据转化成结构化指标后,可以做很多事:了解商品的真实口碑、定位差评集中出现的原因、发现用户高频提及的功能点、观察评论量和评分随时间波动的趋势。对运营和产品团队来说,这些结论比主观经验更可靠,也更容易在会上讲清楚。所以我决定把整个链路跑通:数据获取、数据清洗、文本挖掘、情感分析、可视化输出。做完之后你会形成一种"拿到任何评论数据都能快速加工成报告"的能力,这也是数据分析师的基本功。1.2 技术栈选型与依赖清单语言选 Python,不需要太多解释。整个数据处理链路在 Python 生态里都有成熟方案,而且代码量相对小。核心依赖如下:requests:发送 HTTP 请求,获取评论接口返回的数据pandas:数据清洗、去重、分组聚合jieba:中文分词、关键词提取snownlp:中文情感分析,基于朴素贝叶斯pyecharts:生成交互式图表,能输出 HTMLwordcloud:生成评论词云图matplotlib:词云渲染时的底层绘图支持安装命令一行搞定:pip install requests pandas jieba snownlp pyecharts wordcloud matplotlib选 pyecharts 而不是纯 matplotlib 的原因很简单:pyecharts 生成的图表是网页交互式的,鼠标悬停能看具体数值,缩放、切换指标也方便,更适合做成报告给别人看。matplotlib 则适合放在脚本里快速出静态图,比如词云和简单趋势图。1.3 项目整体流程梳理整个流程可以分六步走:通过商品详情页找到评论接口,了解请求参数和返回结构写一个可复用、支持分页和手动调整速率的采集脚本把原始 JSON 数据转成 DataFrame,做字段校验和去重清洗评论文本,做分词、去停用词、情感打分从评分分布、高频词、时间趋势、差评维度四个角度做量化分析用 pyecharts 和 wordcloud 生成可视化报表流程不复杂,但每一步都有细节。比如接口字段可能变化,文本里有 HTML 标签,snownlp 默认模型对新词判断不准,这些问题不处理,后面的图表就是错的。2. 评论数据采集:把公开数据变成本地数据2.1 先看懂评论是怎么加载出来的很多新手第一反应是对着商品详情页直接解析 HTML,这种做法在当前电商网站里基本走不通。详情页上的评论区域是异步加载的,服务端把数据单独通过接口返回,前端拿到 JSON 后再渲染到页面上。你需要做的就是打开浏览器开发者工具,切到 Network 面板,然后勾选 XHR 和 Fetch,再手动切换评论排序方式,就能看到评论数据对应的接口。不同平台接口命名不一样,有些叫评论列表接口,有些叫评价聚合接口,但返回结构通常是 JSON。我用的这个方法也适用其他商品购买页面:先找到接口,把接口返回的数据在浏览器里预览一下,确认里面确实有评论内容,再写脚本去请求它。2.2 请求参数与返回结构拆解下面是我整理的一份模拟评论接口参数,核心字段一般是这些:参数名含义示例productId商品唯一标识100086page页码1pageSize每页条数20sortType排序方式5aggregateVersion页面版本号3返回 JSON 的结构大概长这样:{ "code": 0, "data": { "comments": [ { "commentId": "C202406010001", "content": "物流很快,客服态度也不错,但续航没有描述里说的那么持久。", "score": 3, "createTime": 1717225200000, "nickname": "用户***" } ], "total": 8600, "hasNext": true } }这里要注意,createTime 是毫秒级时间戳,需要转换后才能用于后续的时间趋势分析。nickname 已经脱敏,你在采集过程中也不要保存手机号、地址这类字段,合规上会安全很多。2.3 一个可复用的采集脚本采集脚本的设计目标有三个:分页自动翻、控制请求频率、断点续采。第一次跑的时候只采前 10 页做实验,确认字段没问题再放开跑全量。import json import random import time import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.example.com/product/100086", "Accept": "application/json, text/plain, */*", } BASE_URL = "https://api.example.com/reviews" PRODUCT_ID = "100086" def fetch_comments(product_id, page, page_size=20): params = { "productId": product_id, "page": page, "pageSize": page_size, "sortType": 5, } resp = requests.get(BASE_URL, params=params, headers=HEADERS, timeout=10) resp.raise_for_status() data = resp.json() if data.get("code") != 0: print(f"页面 {page} 请求失败: {data}") return [], False page_data = data.get("data", {}) comments = page_data.get("comments", []) has_next = page_data.get("hasNext", False) return comments, has_next all_comments = [] page = 1 while page <= 10: comments, has_next = fetch_comments(PRODUCT_ID, page=page) if not comments: break all_comments.extend(comments) print(f"第 {page} 页采集到 {len(comments)} 条") page += 1 if not has_next: break time.sleep(random.uniform(1.5, 3.5)) with open("raw_comments.json", "w", encoding="utf-8") as f: json.dump(all_comments, f, ensure_ascii=False, indent=2)这里面有几个关键点:User-Agent 必须设置,空的 UA 很容易被服务器直接拒绝每次请求之间加随机 sleep,避免固定频率触发限制先限制页数跑通,确认数据没问题再放开每次请求失败打印响应体,方便排查问题2.4 采集过程的实务建议很多人一上来就上并发、上异步协程,结果就是请求过于频繁,IP 被限制,反而拖慢进度。我的建议是先慢后快,单线程加随机 sleep 跑一轮,把数据格式搞清楚,再考虑优化。另外要明确一点:这里采集的是公开商品评论区里的信息,不涉及用户隐私字段。抓取过程不要尝试破解加密签名、跳过低门槛验证,更不要做任何绕过权限的事。做数据分析项目,数据来源合规是底线。如果接口需要登录后才能看到完整评论,不要手动绕过,可以考虑用浏览器登录后手动复制一份 Cookie 到脚本里。但也要记住,Cookie 属于你的隐私凭证,不要提交到公开仓库。如果把项目代码放到网上,记得先去掉 Cookie 和相关 token。3. 数据清洗与预处理:评论里的垃圾信息怎么去3.1 字段规范化与去重原始 JSON 落盘之后,第一步先用 pandas 读进来,然后做字段检查。import pandas as pd df = pd.read_json("raw_comments.json") print(df.shape) print(df.dtypes)通常会出现几个问题:commentId 有重复,content 带有换行和表情符号,createTime 是数值型但我们需要日期。处理方式如下:df = df.drop_duplicates(subset=["commentId"]) df["createTime"] = pd.to_datetime(df["createTime"], unit="ms", errors="coerce") df["score"] = pd.to_numeric(df["score"], errors="coerce") df = df.dropna(subset=["content", "createTime"])注意 score 是打分,但评论接口里可能返回星星数值,比如 1 到 5。如果发现只有好评和中评,没有差评,那很可能是接口默认排序过滤了差评,需要检查 sortType 参数。3.2 文本清洗与中文分词评论文本是最核心的原材料,但里面有很多噪音。比如 HTML 标签、多余的空白、URL、表情符号、无意义的"此用户未填写评价内容"。清洗函数我一般是这么写的:import re def clean_text(text): if not isinstance(text, str): return "" text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"https?://\S+|www\.\S+", "", text) text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9,。!?、:;()""\s]", "", text) text = re.sub(r"\s+", " ", text) return text.strip() df["clean_content"] = df["content"].apply(clean_text)清洗之后就是分词。jieba 对中文支持不错,但一些商品相关的专有词可能被切开,比如某个产品型号、"防水""降噪"这类复合词。应对方式是加载自定义词典:import jieba custom_words = ["防水", "降噪", "续航时间", "便携性"] for word in custom_words: jieba.add_word(word) stop_words = set() with open("stopwords.txt", "r", encoding="utf-8") as f: for line in f: stop_words.add(line.strip()) def cut_words(text): words = jieba.lcut(text) keep = set(["n", "v", "vn", "a", "an", "nz"]) result = [] for word, flag in jieba.posseg.cut(text): if word.strip() and flag in keep and word not in stop_words: result.append(word) return result df["words"] = df["clean_content"].apply(cut_words)用 jieba.posseg 做词性过滤很重要。我之前没过滤,结果分词结果里全是"的""了""就是"这类无意义词,词云图直接没法看。现在保留名词、动词、形容词,关键词质量会明显提升。3.3 情感倾向如何计算情感分析我用了 snownlp,因为它是纯 Python 库,对大篇幅中文文本也不会差到哪里去。接口很简单:from snownlp import SnowNLP def get_sentiment(text): s = SnowNLP(text) return s.sentiments df["sentiment"] = df["clean_content"].apply(get_sentiment)snownlp 返回的是 0 到 1 之间的分数,越接近 1 代表越积极,越接近 0 代表越消极。可以直接按 0.5 划分,更稳妥的做法是按 0.4 和 0.6 划成三档,避免边界误判。不过默认模型对很多电商描述并不准确。比如"一分钱一分货"这句,它的情感倾向其实偏中性,但可能被算成负面。我的处理办法是手动抽几百条评论,标注好正负样本,再用 snownlp 自带的训练模块重新训练一个模型:from snownlp import sentiment # 准备两个文本文件,一行一条样本 # pos.txt 和 neg.txt 放到指定目录 sentiment.train(r"./data/pos.txt", r"./data/neg.txt") sentiment.save("sentiment.marshal")训练完成后,程序启动时会自动加载这个模型文件。自己做标注会花一点时间,但对准确率的提升非常明显,尤其是针对你分析的那一类商品。3.4 构造分析用的数据宽表数据整理成干净的一行一条评论之后,还要造出几个衍生字段,方便后续做统计:df["date"] = df["createTime"].dt.date df["week"] = df["createTime"].dt.to_period("W").astype(str) df["sentiment_label"] = pd.cut( df["sentiment"], bins=[0, 0.4, 0.6, 1.0], labels=["负面", "中性", "正面"] )到这里,我们就有了一张真正可用的分析表:每一行是一天、一条评论、一个打分、一段情感倾向。后面所有图表都是基于这张表生成的。4. 评论量化分析:从数据里读出结论4.1 总体评分分布:先看基本盘拿到干净数据后,第一件要做的事是看星级分布。这一步能快速判断商品口碑是不是两极分化。score_dist = df["score"].value_counts().reindex([1, 2, 3, 4, 5], fill_value=0) print(score_dist)如果发现 1 星和 5 星都很多,3 星很少,说明用户反馈很极端,要么很喜欢,要么很失望。这种商品做分析时,就不要只看平均分,一定要拆开看两端的评论内容。评分分布建议用柱状图展示,配一个百分比标签。如果数据量上千,还需要同时统计各评分的评论条数占比,而不是只数条数,因为不同评分的回复率可能不一样。4.2 高频词与 TF-IDF 找出核心关注点把评论分词后,最直接的分析方式就是统计词频。from collections import Counter all_words = [word for words in df["words"] for word in words] word_freq = Counter(all_words).most_common(30) print(word_freq)高频词可以看到"质量""客服""物流""价格""效果"这类词,但词频高不等于差异化特征强。比如"客服"可能出现在大量好评里,也可能出现在差评里,光看词频看不出问题。所以我会再用一次 jieba.analyse 的 TF-IDF 接口,分别对好评和差评提取关键词,然后对比差异。import jieba.analyse good_reviews = df[df["sentiment_label"] == "正面"]["clean_content"].values bad_reviews = df[df["sentiment_label"] == "负面"]["clean_content"].values good_tags = jieba.analyse.extract_tags(" ".join(good_reviews), topK=20) bad_tags = jieba.analyse.extract_tags(" ".join(bad_reviews), topK=20)这个做法的价值很直接。好评关键词集中在"速度""服务""好用",差评关键词集中在"发货""破损""联系不上",基本就能锁定问题环节是售后和物流。4.3 时间切片变化:找到口碑拐点只看整体结论容易忽略时间维度。我的做法是把评论按天聚合,画出每天的评论量和负面评论比例,这样能发现口碑拐点。daily_stats = df.groupby("date").agg( total=("commentId", "count"), negative_ratio=("sentiment_label", lambda x: (x == "负面").mean()), )比如数据里某天负面比例突然从 15% 跳到 40%,再去翻那几天的评论文本,很可能能找到原因,比如商品改版、物流合作方更换、临时缺货等。这种时间切片分析,是给老板或运营同事看时最有说服力的部分。4.4 差评原因拆解光知道差评比例还不够,要把差评内容归因。我的习惯是先把差评评论拿出来,再做一次聚类或简单归类。简单方式是按关键词分类。比如定义一个规则映射表:rules = { "物流": ["物流", "快递", "发货", "运输"], "质量": ["质量", "破损", "劣质", "缝隙"], "客服": ["客服", "售后", "联系"], "体验": ["卡顿", "难用", "掉线", "无法使用"], } for label, keywords in rules.items(): idx = df["clean_content"].str.contains("|".join(keywords), na=False) print(label, idx.sum())如果要更深入,可以用 TF-IDF 向量化后做 LDA 主题模型,但 LDA 对短文本效果一般,不是每条评论都能分到清晰主题。对小样本项目来说,关键词规则映射性价比更高,也更容易给别人解释。5. 可视化看板实现:让结果自己说话5.1 用 pyecharts 制作核心图表可视化环节我优先用 pyecharts。它生成的图表是网页交互式的,鼠标放上去能看到数值,还能缩放,比较适合放到报告中。评分分布柱状图:from pyecharts import options as opts from pyecharts.charts import Bar score_count = df["score"].value_counts().reindex([1, 2, 3, 4, 5], fill_value=0) bar = ( Bar() .add_xaxis(["1星", "2星", "3星", "4星", "5星"].tolist()) .add_yaxis("评论数", score_count.tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="星级评分分布"), xaxis_opts=opts.AxisOpts(name="评分"), yaxis_opts=opts.AxisOpts(name="评论数"), ) ) bar.render("chart_score.html")时间趋势折线图:from pyecharts.charts import Line daily_stats = daily_stats.sort_index() line = ( Line() .add_xaxis(daily_stats.index.astype(str).tolist()) .add_yaxis("评论量", daily_stats["total"].tolist()) .add_yaxis("负面比例", (daily_stats["negative_ratio"] * 100).round(2).tolist()) .set_global_opts(title_opts=opts.TitleOpts(title="评论量与负面比例趋势")) ) line.render("chart_trend.html")5.2 词云图生成词云适合用来做展示,因为它直观,但信息密度低,不适合当唯一分析手段。生成词云一定要注意中文字体路径,否则会显示成方框。from wordcloud import WordCloud import matplotlib.pyplot as plt wordcloud_text = " ".join(all_words) wc = WordCloud( font_path="msyh.ttc", width=1600, height=900, background_color="white", max_words=200, collocations=False, ).generate(wordcloud_text) plt.figure(figsize=(12, 8)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig("wordcloud.png", dpi=150, bbox_inches="tight")collocations=False 参数要记得设置,否则 wordcloud 默认会把相邻词拼成短语,导致图像出现一堆你根本没想处理的复合同。5.3 多图表组合成报表页面独立图表生成后,可以把多个图表合并成一个页面。pyecharts 的 Page 对象提供了简单的布局能力:from pyecharts.charts import Page, Pie pie = ( Pie() .add("", [list(z) for z in zip(["好评", "中评", "差评"], df["sentiment_label"].value_counts().tolist())]) .set_global_opts(title_opts=opts.TitleOpts(title="情感倾向占比")) ) page = Page(layout=Page.SimplePageLayout) page.add(bar, line, pie) page.render("comment_report.html")生成出来的 HTML 是个独立文件,可以直接发送给别人打开。这个特性在有汇报需求的时候特别好用,不需要对方装 Python 环境,也不需要安装任何依赖。5.4 图表配色和展示顺序的小心得做可视化时,颜色不要用大红大紫,尽量用同一色系。好评用蓝色,差评用橙红色,中性用灰色,整体观感会干净很多。展示顺序上,先放总体评分布,再放时间趋势,然后是关键词和情感占比,这样讲解逻辑是从宏观到微观的。6. 常见问题与排坑实录6.1 接口请求后返回空白或者验证码遇到这种情况,先检查请求头。有些平台会检测库默认的 User-Agent,比如 Python-requests 开头的 UA 很容易被拦截。把浏览器开发者工具里看到的 UA 完整复制下来放进 HEADERS,改成和浏览器一致,往往就能解决。如果还不行,说明接口可能对会话有校验,需要带 Cookie 请求。这时手动在浏览器里登录一次账号,再把 Cookie 复制到脚本里。注意别把 Cookie 放进代码仓库,建议放到本地配置文件里并加入 .gitignore。另一个经验是,评论接口通常有隐藏的反爬逻辑,连续请求几十条后会出现一个间隔期。所以每页之间加随机 1.5 到 4 秒的等待时间,对长期采集更可靠。6.2 CSV 文件用 Excel 打开乱码用 pandas 写 CSV 时,默认编码是 utf-8,Excel 打开会乱码。解决办法是写文件时用 utf-8-sig 编码:df.to_csv("comments.csv", index=False, encoding="utf-8-sig")如果反过来是读 CSV 报错,读文件时显式指定 encoding="utf-8",避免 Python 在不同操作系统默认编码下的不一致问题。6.3 词云和图表的中文显示成方块词云显示方块,大概率是 font_path 没有设置或者指向的字体文件不存在。Windows 系统可以直接用 C:/Windows/Fonts/simhei.ttf 或 msyh.ttc,macOS 可以用 /System/Library/Fonts/PingFang.ttc。pyecharts 图表默认自带字体配置,一般不会出现乱码。如果出现标题中文乱码,检查 pyecharts 版本,必要时升级到最新版。6.4 情感分析结果不准snownlp 默认模型是基于通用的豆瓣评论文本训练的,对带货类评论的语境理解有限。你要是直接清洗完就上情感分析,准确率可能只有六七成。我的处理思路有两步。第一步,在分析前手动抽 300 条评论文本,标好正负中三档,计算模型准确率,心里有底。第二步,如果准确率偏低,就整理一批标注数据重新训练模型。训练样本不要全部用在训练上,留 20% 做验证。另外,阈值不要死守 0.5。对偏保守的评论数据,把负面阈值调到 0.45,正面阈值调到 0.55,可能整体效果更好。每次调整后都要回看几条实际评论,别只看准确率数值。6.5 数据量大时运行速度慢评论数超过十万条后,逐条分词和情感分析会变得非常慢。我的建议是分三步优化:先把数据按评分或商品切块,用 python 的多进程库 Pool 做并行分词。每个进程处理一批文本,完成后分别保存中间结果,最后再合并。情感分析阶段也是 CPU 密集型,同样可以并行。但要注意 snownlp 的模型加载在每个子进程里都要做一次,否则就会报找不到模型文件。再不行,可以考虑对文本做降采样,比如差评全保留,好评随机抽 50%,这样情感分布趋势不会受影响,处理速度却能快一倍。写到最后的一些实际体会这个项目做完,我最大的感受是:数据分析和爬虫并不难,难的是把每一步串起来时处理各种脏数据。评论里藏着大量口语化表达、表情符号和营销刷单痕迹,如果清洗不到位,后边的结论基本不能用。我给自己的建议是每次新平台、新商品的分析,都要保留一套从请求到可视化的标准模板,只改商品 ID 和分析维度参数。第二次做这个项目时,我大概只花了半天时间就从采集跑到了报告输出,这才是做数据分析项目最值得沉淀的部分。如果你也想上手试,我建议先选一个评论量中等、大概几百条评论的商品来练,跑通整条链路后再放大数据量。不要一上来就抓几十万条,那样很容易被细节问题拖住节奏。
花了两周,我做了个 Python 评论数据分析可视化的项目:把某主流电商平台上某个商品链接下的公开评论全部抓下来,做清洗、分词、情感打分,最后用图表把"用户到底在夸什么、吐槽什么、什么时间段口碑集中爆雷"整成一页能直接打开的 HTML 看板。这套流程不依赖任何私有工具,核心就是 requests、pandas、jieba、snownlp 和 pyecharts,完全可以复用到其他商品的评论分析场景。如果你正在学 Python 数据分析,想找一个贴近业务、有完整数据链路的练手项目,这篇内容应该对你有用。下面我会把从采集、清洗、分析到可视化的每个环节都拆开讲,包括我踩过的坑和调优思路,尽量做到你照着文章就能把整个项目复现出来。1. 项目整体设计:先想清楚再动手1.1 评论数据为什么值得分析电商评论是用户用真金白银换来的反馈,它不像问卷那样有暗示性,很多时候用户会直接说出产品的好与坏。但这些评论天然是非结构化文本,几千条甚至几万条堆在一起,靠人力一条条看,效率太低,而且很容易漏掉关键信息。把评论数据转化成结构化指标后,可以做很多事:了解商品的真实口碑、定位差评集中出现的原因、发现用户高频提及的功能点、观察评论量和评分随时间波动的趋势。对运营和产品团队来说,这些结论比主观经验更可靠,也更容易在会上讲清楚。所以我决定把整个链路跑通:数据获取、数据清洗、文本挖掘、情感分析、可视化输出。做完之后你会形成一种"拿到任何评论数据都能快速加工成报告"的能力,这也是数据分析师的基本功。1.2 技术栈选型与依赖清单语言选 Python,不需要太多解释。整个数据处理链路在 Python 生态里都有成熟方案,而且代码量相对小。核心依赖如下:requests:发送 HTTP 请求,获取评论接口返回的数据pandas:数据清洗、去重、分组聚合jieba:中文分词、关键词提取snownlp:中文情感分析,基于朴素贝叶斯pyecharts:生成交互式图表,能输出 HTMLwordcloud:生成评论词云图matplotlib:词云渲染时的底层绘图支持安装命令一行搞定:pip install requests pandas jieba snownlp pyecharts wordcloud matplotlib选 pyecharts 而不是纯 matplotlib 的原因很简单:pyecharts 生成的图表是网页交互式的,鼠标悬停能看具体数值,缩放、切换指标也方便,更适合做成报告给别人看。matplotlib 则适合放在脚本里快速出静态图,比如词云和简单趋势图。1.3 项目整体流程梳理整个流程可以分六步走:通过商品详情页找到评论接口,了解请求参数和返回结构写一个可复用、支持分页和手动调整速率的采集脚本把原始 JSON 数据转成 DataFrame,做字段校验和去重清洗评论文本,做分词、去停用词、情感打分从评分分布、高频词、时间趋势、差评维度四个角度做量化分析用 pyecharts 和 wordcloud 生成可视化报表流程不复杂,但每一步都有细节。比如接口字段可能变化,文本里有 HTML 标签,snownlp 默认模型对新词判断不准,这些问题不处理,后面的图表就是错的。2. 评论数据采集:把公开数据变成本地数据2.1 先看懂评论是怎么加载出来的很多新手第一反应是对着商品详情页直接解析 HTML,这种做法在当前电商网站里基本走不通。详情页上的评论区域是异步加载的,服务端把数据单独通过接口返回,前端拿到 JSON 后再渲染到页面上。你需要做的就是打开浏览器开发者工具,切到 Network 面板,然后勾选 XHR 和 Fetch,再手动切换评论排序方式,就能看到评论数据对应的接口。不同平台接口命名不一样,有些叫评论列表接口,有些叫评价聚合接口,但返回结构通常是 JSON。我用的这个方法也适用其他商品购买页面:先找到接口,把接口返回的数据在浏览器里预览一下,确认里面确实有评论内容,再写脚本去请求它。2.2 请求参数与返回结构拆解下面是我整理的一份模拟评论接口参数,核心字段一般是这些:参数名含义示例productId商品唯一标识100086page页码1pageSize每页条数20sortType排序方式5aggregateVersion页面版本号3返回 JSON 的结构大概长这样:{ "code": 0, "data": { "comments": [ { "commentId": "C202406010001", "content": "物流很快,客服态度也不错,但续航没有描述里说的那么持久。", "score": 3, "createTime": 1717225200000, "nickname": "用户***" } ], "total": 8600, "hasNext": true } }这里要注意,createTime 是毫秒级时间戳,需要转换后才能用于后续的时间趋势分析。nickname 已经脱敏,你在采集过程中也不要保存手机号、地址这类字段,合规上会安全很多。2.3 一个可复用的采集脚本采集脚本的设计目标有三个:分页自动翻、控制请求频率、断点续采。第一次跑的时候只采前 10 页做实验,确认字段没问题再放开跑全量。import json import random import time import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.example.com/product/100086", "Accept": "application/json, text/plain, */*", } BASE_URL = "https://api.example.com/reviews" PRODUCT_ID = "100086" def fetch_comments(product_id, page, page_size=20): params = { "productId": product_id, "page": page, "pageSize": page_size, "sortType": 5, } resp = requests.get(BASE_URL, params=params, headers=HEADERS, timeout=10) resp.raise_for_status() data = resp.json() if data.get("code") != 0: print(f"页面 {page} 请求失败: {data}") return [], False page_data = data.get("data", {}) comments = page_data.get("comments", []) has_next = page_data.get("hasNext", False) return comments, has_next all_comments = [] page = 1 while page <= 10: comments, has_next = fetch_comments(PRODUCT_ID, page=page) if not comments: break all_comments.extend(comments) print(f"第 {page} 页采集到 {len(comments)} 条") page += 1 if not has_next: break time.sleep(random.uniform(1.5, 3.5)) with open("raw_comments.json", "w", encoding="utf-8") as f: json.dump(all_comments, f, ensure_ascii=False, indent=2)这里面有几个关键点:User-Agent 必须设置,空的 UA 很容易被服务器直接拒绝每次请求之间加随机 sleep,避免固定频率触发限制先限制页数跑通,确认数据没问题再放开每次请求失败打印响应体,方便排查问题2.4 采集过程的实务建议很多人一上来就上并发、上异步协程,结果就是请求过于频繁,IP 被限制,反而拖慢进度。我的建议是先慢后快,单线程加随机 sleep 跑一轮,把数据格式搞清楚,再考虑优化。另外要明确一点:这里采集的是公开商品评论区里的信息,不涉及用户隐私字段。抓取过程不要尝试破解加密签名、跳过低门槛验证,更不要做任何绕过权限的事。做数据分析项目,数据来源合规是底线。如果接口需要登录后才能看到完整评论,不要手动绕过,可以考虑用浏览器登录后手动复制一份 Cookie 到脚本里。但也要记住,Cookie 属于你的隐私凭证,不要提交到公开仓库。如果把项目代码放到网上,记得先去掉 Cookie 和相关 token。3. 数据清洗与预处理:评论里的垃圾信息怎么去3.1 字段规范化与去重原始 JSON 落盘之后,第一步先用 pandas 读进来,然后做字段检查。import pandas as pd df = pd.read_json("raw_comments.json") print(df.shape) print(df.dtypes)通常会出现几个问题:commentId 有重复,content 带有换行和表情符号,createTime 是数值型但我们需要日期。处理方式如下:df = df.drop_duplicates(subset=["commentId"]) df["createTime"] = pd.to_datetime(df["createTime"], unit="ms", errors="coerce") df["score"] = pd.to_numeric(df["score"], errors="coerce") df = df.dropna(subset=["content", "createTime"])注意 score 是打分,但评论接口里可能返回星星数值,比如 1 到 5。如果发现只有好评和中评,没有差评,那很可能是接口默认排序过滤了差评,需要检查 sortType 参数。3.2 文本清洗与中文分词评论文本是最核心的原材料,但里面有很多噪音。比如 HTML 标签、多余的空白、URL、表情符号、无意义的"此用户未填写评价内容"。清洗函数我一般是这么写的:import re def clean_text(text): if not isinstance(text, str): return "" text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"https?://\S+|www\.\S+", "", text) text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9,。!?、:;()""\s]", "", text) text = re.sub(r"\s+", " ", text) return text.strip() df["clean_content"] = df["content"].apply(clean_text)清洗之后就是分词。jieba 对中文支持不错,但一些商品相关的专有词可能被切开,比如某个产品型号、"防水""降噪"这类复合词。应对方式是加载自定义词典:import jieba custom_words = ["防水", "降噪", "续航时间", "便携性"] for word in custom_words: jieba.add_word(word) stop_words = set() with open("stopwords.txt", "r", encoding="utf-8") as f: for line in f: stop_words.add(line.strip()) def cut_words(text): words = jieba.lcut(text) keep = set(["n", "v", "vn", "a", "an", "nz"]) result = [] for word, flag in jieba.posseg.cut(text): if word.strip() and flag in keep and word not in stop_words: result.append(word) return result df["words"] = df["clean_content"].apply(cut_words)用 jieba.posseg 做词性过滤很重要。我之前没过滤,结果分词结果里全是"的""了""就是"这类无意义词,词云图直接没法看。现在保留名词、动词、形容词,关键词质量会明显提升。3.3 情感倾向如何计算情感分析我用了 snownlp,因为它是纯 Python 库,对大篇幅中文文本也不会差到哪里去。接口很简单:from snownlp import SnowNLP def get_sentiment(text): s = SnowNLP(text) return s.sentiments df["sentiment"] = df["clean_content"].apply(get_sentiment)snownlp 返回的是 0 到 1 之间的分数,越接近 1 代表越积极,越接近 0 代表越消极。可以直接按 0.5 划分,更稳妥的做法是按 0.4 和 0.6 划成三档,避免边界误判。不过默认模型对很多电商描述并不准确。比如"一分钱一分货"这句,它的情感倾向其实偏中性,但可能被算成负面。我的处理办法是手动抽几百条评论,标注好正负样本,再用 snownlp 自带的训练模块重新训练一个模型:from snownlp import sentiment # 准备两个文本文件,一行一条样本 # pos.txt 和 neg.txt 放到指定目录 sentiment.train(r"./data/pos.txt", r"./data/neg.txt") sentiment.save("sentiment.marshal")训练完成后,程序启动时会自动加载这个模型文件。自己做标注会花一点时间,但对准确率的提升非常明显,尤其是针对你分析的那一类商品。3.4 构造分析用的数据宽表数据整理成干净的一行一条评论之后,还要造出几个衍生字段,方便后续做统计:df["date"] = df["createTime"].dt.date df["week"] = df["createTime"].dt.to_period("W").astype(str) df["sentiment_label"] = pd.cut( df["sentiment"], bins=[0, 0.4, 0.6, 1.0], labels=["负面", "中性", "正面"] )到这里,我们就有了一张真正可用的分析表:每一行是一天、一条评论、一个打分、一段情感倾向。后面所有图表都是基于这张表生成的。4. 评论量化分析:从数据里读出结论4.1 总体评分分布:先看基本盘拿到干净数据后,第一件要做的事是看星级分布。这一步能快速判断商品口碑是不是两极分化。score_dist = df["score"].value_counts().reindex([1, 2, 3, 4, 5], fill_value=0) print(score_dist)如果发现 1 星和 5 星都很多,3 星很少,说明用户反馈很极端,要么很喜欢,要么很失望。这种商品做分析时,就不要只看平均分,一定要拆开看两端的评论内容。评分分布建议用柱状图展示,配一个百分比标签。如果数据量上千,还需要同时统计各评分的评论条数占比,而不是只数条数,因为不同评分的回复率可能不一样。4.2 高频词与 TF-IDF 找出核心关注点把评论分词后,最直接的分析方式就是统计词频。from collections import Counter all_words = [word for words in df["words"] for word in words] word_freq = Counter(all_words).most_common(30) print(word_freq)高频词可以看到"质量""客服""物流""价格""效果"这类词,但词频高不等于差异化特征强。比如"客服"可能出现在大量好评里,也可能出现在差评里,光看词频看不出问题。所以我会再用一次 jieba.analyse 的 TF-IDF 接口,分别对好评和差评提取关键词,然后对比差异。import jieba.analyse good_reviews = df[df["sentiment_label"] == "正面"]["clean_content"].values bad_reviews = df[df["sentiment_label"] == "负面"]["clean_content"].values good_tags = jieba.analyse.extract_tags(" ".join(good_reviews), topK=20) bad_tags = jieba.analyse.extract_tags(" ".join(bad_reviews), topK=20)这个做法的价值很直接。好评关键词集中在"速度""服务""好用",差评关键词集中在"发货""破损""联系不上",基本就能锁定问题环节是售后和物流。4.3 时间切片变化:找到口碑拐点只看整体结论容易忽略时间维度。我的做法是把评论按天聚合,画出每天的评论量和负面评论比例,这样能发现口碑拐点。daily_stats = df.groupby("date").agg( total=("commentId", "count"), negative_ratio=("sentiment_label", lambda x: (x == "负面").mean()), )比如数据里某天负面比例突然从 15% 跳到 40%,再去翻那几天的评论文本,很可能能找到原因,比如商品改版、物流合作方更换、临时缺货等。这种时间切片分析,是给老板或运营同事看时最有说服力的部分。4.4 差评原因拆解光知道差评比例还不够,要把差评内容归因。我的习惯是先把差评评论拿出来,再做一次聚类或简单归类。简单方式是按关键词分类。比如定义一个规则映射表:rules = { "物流": ["物流", "快递", "发货", "运输"], "质量": ["质量", "破损", "劣质", "缝隙"], "客服": ["客服", "售后", "联系"], "体验": ["卡顿", "难用", "掉线", "无法使用"], } for label, keywords in rules.items(): idx = df["clean_content"].str.contains("|".join(keywords), na=False) print(label, idx.sum())如果要更深入,可以用 TF-IDF 向量化后做 LDA 主题模型,但 LDA 对短文本效果一般,不是每条评论都能分到清晰主题。对小样本项目来说,关键词规则映射性价比更高,也更容易给别人解释。5. 可视化看板实现:让结果自己说话5.1 用 pyecharts 制作核心图表可视化环节我优先用 pyecharts。它生成的图表是网页交互式的,鼠标放上去能看到数值,还能缩放,比较适合放到报告中。评分分布柱状图:from pyecharts import options as opts from pyecharts.charts import Bar score_count = df["score"].value_counts().reindex([1, 2, 3, 4, 5], fill_value=0) bar = ( Bar() .add_xaxis(["1星", "2星", "3星", "4星", "5星"].tolist()) .add_yaxis("评论数", score_count.tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="星级评分分布"), xaxis_opts=opts.AxisOpts(name="评分"), yaxis_opts=opts.AxisOpts(name="评论数"), ) ) bar.render("chart_score.html")时间趋势折线图:from pyecharts.charts import Line daily_stats = daily_stats.sort_index() line = ( Line() .add_xaxis(daily_stats.index.astype(str).tolist()) .add_yaxis("评论量", daily_stats["total"].tolist()) .add_yaxis("负面比例", (daily_stats["negative_ratio"] * 100).round(2).tolist()) .set_global_opts(title_opts=opts.TitleOpts(title="评论量与负面比例趋势")) ) line.render("chart_trend.html")5.2 词云图生成词云适合用来做展示,因为它直观,但信息密度低,不适合当唯一分析手段。生成词云一定要注意中文字体路径,否则会显示成方框。from wordcloud import WordCloud import matplotlib.pyplot as plt wordcloud_text = " ".join(all_words) wc = WordCloud( font_path="msyh.ttc", width=1600, height=900, background_color="white", max_words=200, collocations=False, ).generate(wordcloud_text) plt.figure(figsize=(12, 8)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.savefig("wordcloud.png", dpi=150, bbox_inches="tight")collocations=False 参数要记得设置,否则 wordcloud 默认会把相邻词拼成短语,导致图像出现一堆你根本没想处理的复合同。5.3 多图表组合成报表页面独立图表生成后,可以把多个图表合并成一个页面。pyecharts 的 Page 对象提供了简单的布局能力:from pyecharts.charts import Page, Pie pie = ( Pie() .add("", [list(z) for z in zip(["好评", "中评", "差评"], df["sentiment_label"].value_counts().tolist())]) .set_global_opts(title_opts=opts.TitleOpts(title="情感倾向占比")) ) page = Page(layout=Page.SimplePageLayout) page.add(bar, line, pie) page.render("comment_report.html")生成出来的 HTML 是个独立文件,可以直接发送给别人打开。这个特性在有汇报需求的时候特别好用,不需要对方装 Python 环境,也不需要安装任何依赖。5.4 图表配色和展示顺序的小心得做可视化时,颜色不要用大红大紫,尽量用同一色系。好评用蓝色,差评用橙红色,中性用灰色,整体观感会干净很多。展示顺序上,先放总体评分布,再放时间趋势,然后是关键词和情感占比,这样讲解逻辑是从宏观到微观的。6. 常见问题与排坑实录6.1 接口请求后返回空白或者验证码遇到这种情况,先检查请求头。有些平台会检测库默认的 User-Agent,比如 Python-requests 开头的 UA 很容易被拦截。把浏览器开发者工具里看到的 UA 完整复制下来放进 HEADERS,改成和浏览器一致,往往就能解决。如果还不行,说明接口可能对会话有校验,需要带 Cookie 请求。这时手动在浏览器里登录一次账号,再把 Cookie 复制到脚本里。注意别把 Cookie 放进代码仓库,建议放到本地配置文件里并加入 .gitignore。另一个经验是,评论接口通常有隐藏的反爬逻辑,连续请求几十条后会出现一个间隔期。所以每页之间加随机 1.5 到 4 秒的等待时间,对长期采集更可靠。6.2 CSV 文件用 Excel 打开乱码用 pandas 写 CSV 时,默认编码是 utf-8,Excel 打开会乱码。解决办法是写文件时用 utf-8-sig 编码:df.to_csv("comments.csv", index=False, encoding="utf-8-sig")如果反过来是读 CSV 报错,读文件时显式指定 encoding="utf-8",避免 Python 在不同操作系统默认编码下的不一致问题。6.3 词云和图表的中文显示成方块词云显示方块,大概率是 font_path 没有设置或者指向的字体文件不存在。Windows 系统可以直接用 C:/Windows/Fonts/simhei.ttf 或 msyh.ttc,macOS 可以用 /System/Library/Fonts/PingFang.ttc。pyecharts 图表默认自带字体配置,一般不会出现乱码。如果出现标题中文乱码,检查 pyecharts 版本,必要时升级到最新版。6.4 情感分析结果不准snownlp 默认模型是基于通用的豆瓣评论文本训练的,对带货类评论的语境理解有限。你要是直接清洗完就上情感分析,准确率可能只有六七成。我的处理思路有两步。第一步,在分析前手动抽 300 条评论文本,标好正负中三档,计算模型准确率,心里有底。第二步,如果准确率偏低,就整理一批标注数据重新训练模型。训练样本不要全部用在训练上,留 20% 做验证。另外,阈值不要死守 0.5。对偏保守的评论数据,把负面阈值调到 0.45,正面阈值调到 0.55,可能整体效果更好。每次调整后都要回看几条实际评论,别只看准确率数值。6.5 数据量大时运行速度慢评论数超过十万条后,逐条分词和情感分析会变得非常慢。我的建议是分三步优化:先把数据按评分或商品切块,用 python 的多进程库 Pool 做并行分词。每个进程处理一批文本,完成后分别保存中间结果,最后再合并。情感分析阶段也是 CPU 密集型,同样可以并行。但要注意 snownlp 的模型加载在每个子进程里都要做一次,否则就会报找不到模型文件。再不行,可以考虑对文本做降采样,比如差评全保留,好评随机抽 50%,这样情感分布趋势不会受影响,处理速度却能快一倍。写到最后的一些实际体会这个项目做完,我最大的感受是:数据分析和爬虫并不难,难的是把每一步串起来时处理各种脏数据。评论里藏着大量口语化表达、表情符号和营销刷单痕迹,如果清洗不到位,后边的结论基本不能用。我给自己的建议是每次新平台、新商品的分析,都要保留一套从请求到可视化的标准模板,只改商品 ID 和分析维度参数。第二次做这个项目时,我大概只花了半天时间就从采集跑到了报告输出,这才是做数据分析项目最值得沉淀的部分。如果你也想上手试,我建议先选一个评论量中等、大概几百条评论的商品来练,跑通整条链路后再放大数据量。不要一上来就抓几十万条,那样很容易被细节问题拖住节奏。