尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python京东评论爬取与情感分析可视化:从数据采集到词云报告

Python京东评论爬取与情感分析可视化:从数据采集到词云报告 简介这是一套面向Python学习者与数据分析初学者的电商评论挖掘实战资源以京东商品评价为对象完整覆盖爬虫采集、数据清洗、情感分类与可视化展示全流程。压缩包共20个文件大小约5.87MB包含Python脚本、CSV数据集、分类图标、词云图片及说明文档其中既有原始评论与处理后的数据也有朴素贝叶斯等情感分析代码和可视化图表输出便于对照学习。内容还附带了备份文件与依赖配置适合用于课程设计、毕业设计或自学练习。已有76人学习下载资源结构清晰可直接运行体验也能帮助理解自然语言处理与数据可视化在实际项目中的落地方法。1. 先从一条还行的评论说起这个课题到底在解决什么问题“手机屏幕清晰但电池不太耐用”这样的评论到底是好评还是差评如果只按星级判断它可能是个4星好评但放到舆情分析里这句话同时包含了对屏幕的肯定和对续航的失望。基于Python的京东商品评论爬取与情感分析可视化研究做的就是这个拆解工作把京东商品下的海量评论文本自动抓下来再用中文分词和情感分析模型给每一句评论打出情感倾向最后把结果用词云、折线图和分布饼图等可视化手段呈现出来。它解决的痛点是人工翻评论费时且“好评”“中评”标签丢失了大量细节。适合运营、产品、数据分析师也适合用Python做个人项目的开发者参考。2. 京东评论爬取从接口定位到带参翻页与入库先把链路理清楚京东商品评论区的前端页面本身不直接给完整数据真正干活的是一个XHR接口。拿到它的返回值再清洗入库整套流程才能跑通。这一步做完后续情感分析和可视化才有真正的数据基础。2.1 用浏览器开发者工具定位评论接口打开任意一个京东商品详情页按F12进入开发者工具切到Network面板勾选Fetch/XHR过滤条件再点击页面上的“商品评价”Tab。此时页面上会发出一个以productPageComments开头的请求这就是评论数据的来源。常见的接口地址是https://club.jd.com/comment/productPageComments.action? callbackfetchJSON_comment98 productId100000000000 score0 sortType5 page0 pageSize10 isShadowSku0 fold1这个接口返回的内容是JSONP格式也就是在JSON外面包了一层函数调用比如fetchJSON_comment98({...})。直接用requests请求后需要先用正则把外层括号里的内容剥出来再用json.loads解析。参数中最核心的是productId、score、sortType和page、pageSize。productId就是商品ID在详情页URL末尾能直接看到score控制评论类型0表示全部、1差评、2中评、3好评、4追评sortType有两个取值5是按时间排序6是按推荐排序page从0开始翻页pageSize是每页的评论条数京东接口通常只接受10设成30也可能被压回10条。参数名说明建议取值callbackJSONP回调函数名fetchJSON_comment98productId商品ID详情页URL数字部分score评论类型0全部1差评2中评3好评4追评sortType排序方式5按时间6按推荐page页下标从0开始pageSize每页条数10这里有一个容易被忽略的点翻页时的page不是页码而是从0开始的下标。很多人在写爬虫时习惯page1结果拿到的是第二页数据第一页反而被跳过了。常见做法是拿一个列表页先试一下确认返回的comments字段不为空再继续翻页。为什么用requests而不是scrapy来做这一步因为京东评论接口是典型的轻量XHR场景单商品抓几十页requests加循环就够。scrapy要建项目、定义Item和Pipeline对这种单接口任务成本过高只有当目标是抓取全站几千个商品、需要断点续爬和分布式调度时scrapy的优势才体现出来。2.2 带参翻页的最小可运行代码我用requests写一个最精简的版本不做框架封装先跑通再谈扩展。import requests import re import json import time import random def fetch_comments(sku_id, page0, score0, page_size10): url https://club.jd.com/comment/productPageComments.action params { callback: fetchJSON_comment98, productId: sku_id, score: score, # 0全部1差评2中评3好评4追评 sortType: 5, # 5按时间6按推荐 page: page, # 从0开始 pageSize: page_size, isShadowSku: 0, fold: 1 } 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: fhttps://item.jd.com/{sku_id}.html } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 # JSONP剥壳把括号内的JSON提取出来 text resp.text match re.search(r\{.*\}, text, re.S) if not match: return [] data json.loads(match.group()) return data.get(comments, []) if __name__ __main__: sku_id 100012043978 # 替换成你要分析的商品ID all_comments [] for page in range(0, 5): # 先抓5页验证 comments fetch_comments(sku_id, pagepage, score0, page_size10) if not comments: break all_comments.extend(comments) print(fpage {page}, got {len(comments)} comments) time.sleep(random.randint(2, 5)) # 关键控制请求频率 print(total:, len(all_comments))这段代码的逻辑很直白循环里每次请求一页把返回的comments列表累加到一个大列表里。resp.encoding utf-8是为了避免中文乱码正则匹配用了非贪婪模式配合re.S这样拿到的是最外层的一对大括号。sleep里的随机延时是为了贴近人浏览页面的节奏也能降低被风控盯上的概率。参数说明page控制第几页最大页数受京东风控限制实测同一个商品直接翻页大概能到100页左右再多就容易被弹验证码或返回空列表pageSize官方接口最多给到10个别商品类型支持更大值但没必要冒险。score参数在正式分析时很值得分别抓取后面做评分档对比会用到。2.3 数据清洗与结构化存储评论接口返回的原始数据里content字段就是评论文本creationTime是评论时间score是星级1到5productColor和productSize是购买时选的规格。拿到原始数据后第一件事不是分析而是清洗。常见问题是内容里带有HTML实体、空白字符太多以及重复评论。import sqlite3 import html import re def clean_text(text): if not text: return text html.unescape(text) # 处理 amp; 这类实体 text re.sub(r[^], , text) # 去掉残留标签 text re.sub(r\s, , text).strip() return text def save_to_sqlite(comments, db_pathjd_comments.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS comments ( id TEXT PRIMARY KEY, content TEXT, score INTEGER, creation_time TEXT, product_color TEXT, product_size TEXT ) ) for c in comments: conn.execute( INSERT OR IGNORE INTO comments (id, content, score, creation_time, product_color, product_size) VALUES (?, ?, ?, ?, ?, ?), ( str(c.get(id)), clean_text(c.get(content, )), int(c.get(score, 0)), c.get(creationTime, ), c.get(productColor, ), c.get(productSize, ) ) ) conn.commit() conn.close()这里用SQLite而不是CSV原因是后面做情感分析时可能要反复查询按商品、按时间过滤时SQL比pandas反复读文件方便得多。id字段直接作为主键重复抓取时INSERT OR IGNORE可以天然去重。clean_text里先把HTML实体还原再删除可能残留的标签最后合并连续空白。这一步做完数据就具备进入分析阶段的条件了。常见做法是把“抓取”和“清洗”拆成两个脚本抓取脚本只负责把原始JSON落地成一张原始表清洗脚本再单独跑。这样即使某次抓取中断也不会把已抓到的数据丢掉。3. 情感分析评分贴的是标签语义才是人心京东评分是用户自己点的存在一个很现实的问题很多人懒得打分随手点个4星但在评论里写了一大段吐槽。只按分数统计得出的结论和真实口碑往往不一致。情感分析要做的是把评论文本内容转成一条情感分数曲线再和星级、时间做交叉比对。3.1 中文分词与停用词过滤SnowNLP这类工具的输入是文本但文本里有很多“我们”“的”“了”这类对情感判断没有帮助的词。先分词再过滤停用词能让后续的统计和词云都更干净。import jieba stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def tokenize_and_filter(text): words jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords and len(w) 1]jieba.lcut返回的是分词后的列表这里保留了长度大于1的词把单个字和纯符号过滤掉。停用词表可以去GitHub上找常用的中文停用词库也可以自己在分析过程中往里面加词。例如“真的”“感觉”“东西”这类高频但无情绪倾向的词加进停用词后后续的词云图会更有区分度。这里要注意一个细节jieba对“电池不耐用”这类口语化文本分词基本没问题但对品牌名、产品型号常常分错。比如“Redmi K70”可能被切成“Redmi”和“K70”两个词。常见做法是往jieba的自定义词典里加入商品名和品牌名一两行代码就能显著改善分词质量。3.2 用SnowNLP打分并划分情感强度为什么用SnowNLP而不是BERT文本分类原因很实际SnowNLP不需要标注数据装好库直接跑一个句子几十毫秒出结果适合几百到几千条评论的批量分析。BERT类模型在准确率上确实更高但要先标注几百条训练数据还要准备GPU环境对“研究”或“跑通流程”这个目标来说明显超重。SnowNLP的核心用法就一行SnowNLP(text).sentiments返回0到1之间的值越接近1越正面越接近0越负面。from snownlp import SnowNLP def sentiment_score(text): if not text: return 0.5 return SnowNLP(text).sentiments def label_sentiment(score, pos_th0.6, neg_th0.4): if score pos_th: return 正面 if score neg_th: return 负面 return 中性阈值默认是0.5但实际用于电商评论时我一般把正面阈值调到0.6、负面阈值调到0.4中间留出0.2的中性带。原因很简单电商评论偏向口语化“还行”“凑合”这类词在SnowNLP里得分往往在0.45到0.55之间硬把它们分成正面或负面都会带来大量误判。留一个中性带后续分析时可以单独看这批待定评论。Sentiments分数的解读需要结合上下文一个分数本身没有业务意义但同一商品、同一时间窗口内的分数均值会反映口碑变化。这也是后面做可视化时最常用的聚合口径。3.3 多维交叉分析商品、评分档与时间趋势单条评论的分数没太多看头把几百上千条评论的情绪分数聚合成趋势才有价值。我一般会做三个维度的分析按京东星级分档看情感分布、按时间维度看口碑变化、按商品维度做横向对比。下面这段代码把三个口径一次性算出来。import pandas as pd from collections import Counter def build_sentiment_frame(comments): rows [] for c in comments: text clean_text(c.get(content, )) sc sentiment_score(text) rows.append({ content: text, jd_score: c.get(score, 0), creation_time: c.get(creationTime, )[:10], sentiment: sc, label: label_sentiment(sc) }) df pd.DataFrame(rows) return df def analyze_dimensions(df): # 维度1按京东1~5星聚合情感均值 by_jd_score df.groupby(jd_score)[sentiment].mean().round(3) # 维度2按日期聚合情感均值观察口碑随时间的变化 by_date df.groupby(creation_time)[sentiment].mean().round(3) # 维度3按情感标签统计占比 label_dist Counter(df[label]) return by_jd_score, by_date, label_dist这里把情感分和平台星级分开处理。by_jd_score的结果能验证两者是否一致比如4星评论的平均情感分如果低于3星评论说明用户随手打分的情况很严重by_date则能看出某次促销后口碑是升是降label_dist用于后面画饼图。这三个数据结构直接喂给pyecharts时都不用再加工。到这里情感分析的数据产物已经齐了。接下来做可视化时所有图表本质上都是在消费这三个数据结构。4. 可视化输出从词云到可视化报告情感分析的结果如果只停留在CSV里价值有限。可视化这一步把“哪些词被频繁吐槽”“口碑何时掉头向下”“哪个型号差评率最高”变成一眼能看懂的图表。选pyecharts而不是matplotlib的原因很直接pyecharts输出的是HTML自带tooltip、缩放、保存图片等交互不用自己处理中文字体和坐标轴美工适合快速搭建可视化大屏或报告页。4.1 词云与情感分布图词云有两个版本全网评论词云和负面评论词云。全网词云展示提及频率负面词云突出槽点。这里用pyecharts的WordCloud直接生成HTML方便后续整合成报告页面。from pyecharts import options as opts from pyecharts.charts import WordCloud, Pie def make_wordcloud(freq_dict, output_path): data [(k, v) for k, v in freq_dict.items()] wc WordCloud() wc.add(series_name高频词, data_pairdata, word_size_range[12, 80]) wc.set_global_opts(title_optsopts.TitleOpts(title京东评论高频词)) wc.render(output_path) def make_label_pie(label_dist, output_path): pie Pie() pie.add( series_name情感占比, data_pairlist(label_dist.items()), radius[35%, 70%] ) pie.set_global_opts(title_optsopts.TitleOpts(title情感倾向分布)) pie.render(output_path)freq_dict是一个Counter对象键是词值是出现次数。WordCloud的data_pair拉成二元组列表即可。词云尺寸参数word_size_range控制最小和最大字号频次越高的词字越大。Pie的radius参数是内径和外径类似甜甜圈效果展示正面、中性、负面三类占比很直观。render默认输出独立HTML文件双击就能在浏览器里打开。4.2 时间趋势与商品对比图时间趋势用Line图最合适横轴是日期纵轴是每天评论情感均值。为了避免日期过密导致折线糊成一片我一般先按周聚合或直接取前N个热点日期。商品对比用Bar图每个商品一根柱子柱子高度是负面评论占比或情感均值。from pyecharts.charts import Line, Bar def make_trend_line(df, output_path): series df.groupby(creation_time)[sentiment].mean().sort_index() line Line() line.add_xaxis(list(series.index)) line.add_yaxis( series_name情感均值, y_axis[round(v, 3) for v in series.values], is_smoothTrue ) line.set_global_opts( title_optsopts.TitleOpts(title评论情感随时间变化), yaxis_optsopts.AxisOpts(min_0, max_1) ) line.render(output_path) def make_sku_compare_bar(sku_scores, output_path): bar Bar() bar.add_xaxis(list(sku_scores.keys())) bar.add_yaxis(情感均值, [round(v, 3) for v in sku_scores.values()]) bar.set_global_opts(title_optsopts.TitleOpts(title多商品情感对比)) bar.render(output_path)Line图的y轴范围固定为0到1否则均值波动小的时候图形会显得很平看不出变化。多商品对比时要注意各商品评论数量如果差别很大直接比均值会失真。我一般会在图上同时标注评论量或先用评论量做过滤只保留评论数超过30条的商品。这一步属于可视化之外的分析常识但很容易被忽略。4.3 用Page组合出可筛选报告上面几个图表单独看都是静态HTML。如果想在本地给同事交付一份“能看”的报告用pyecharts的Page把它们拼成一个长页面就行。追求交互的话还可以在Page里嵌入Tab组件把好评、差评、追评分成不同子页。from pyecharts.charts import Page, Tab def build_report(charts, output_pathreport.html): page Page(layoutPage.SimplePageLayout) for chart in charts: page.add(chart) page.render(output_path) def build_tab_report(charts_by_score, output_pathtab_report.html): tab Tab() for tab_name, chart in charts_by_score.items(): tab.add(chart, tab_name) tab.render(output_path)Page.SimplePageLayout是流式布局多个图表会自动上下排列不必手动调位置。Tab适合把全量、好评、差评三个视图分开避免一张页面塞太多信息。到这里整个“爬取-分析-可视化”的闭环就通了。5. 避坑与常见问题跑完这批评论我踩过的5个坑这个方向看起来简单真正跑起来翻车点不少。下面这些坑全部来自实际跑数据时的血泪经验按照“现象→原因→解决”来记录。5.1 数据获取期的坑坑1接口返回空列表现象用代码请求接口时偶尔返回的data里comments是空数组但浏览器里却能看见评论。原因最常见的两个原因一是productId填错URL末尾那串数字和接口里的productId不是同一个二是请求头缺失京东对没有User-Agent或User-Agent过于随机的请求会直接拒绝。解决先用浏览器开发者工具里的请求复制一份curl命令对照检查参数名和请求头。productId一定填详情页URL里的纯数字去掉.html后缀。代码里再加一个最简单的判断如果连续三页返回空列表直接打印URL和响应体前200个字符看到底是被拦截还是参数错。坑2翻页翻到第100页左右突然拿不到数据现象前几十页都正常翻到某个页数后返回的新评论开始重复再往后变成空列表。原因京东评论接口的实际逻辑不是无限翻页同一个时间排序下能翻的深度有限翻到末尾时后端会补齐前面的数据甚至直接返回空。另外pageSize设置过大会加剧这个问题。解决翻页循环里加一个去重判断如果新抓到的id全部已在列表里就终止循环。这样既不会浪费请求也不会把重复数据写进数据库。如果需要更多评论切换sortType或score参数再抓相当于换了一个排序分桶。坑3评论内容里的中文乱码或表情丢失现象存到SQLite里的中文正常但某些特殊符号变成了问号或者表情符号整个消失。原因一种情况是响应被错误解码另一种是清洗时用正则把非中英文字符全删了emoji被连带误删。解决请求后显式设置resp.encoding utf-8。清洗时不要一刀切删掉所有非中文字符只去掉控制字符和多余的空白。如果想保留表情可以在UNICODE层面过滤掉非法字符但大多数分析场景里emoji对情感判断的贡献有限删除后影响不大。5.2 分析期的坑坑4SnowNLP把“一般般吧还行”判成正面现象“一般般”“凑合”“也就那样”这类评论情感分数普遍在0.6以上被贴上正面标签。原因SnowNLP的训练语料偏向微博和新闻对电商口语里的反讽和委婉表达识别能力有限。“还行”在语料里可能经常与正面语境共现。解决先按评分做一个先验修正。京东1到3星评论里出现的“还行”大概率是负面5星里的“还行”才是正面。常见的做法是结合评分加权把1到3星评论的情感分强制向下偏移0.1到0.2。更彻底的办法是扩充自定义情感词典把“一般般”“凑合”“不值”等词按明显负面加入词典然后重新打分。坑5pyecharts在Jupyter里渲染不出来现象代码执行不报错但图表区域显示一段空白或者只输出一段HTML源码。原因pyecharts版本不同渲染API差异很大。v1.x之后如果没调用对应API图表不会自动显示在部分新版Jupyter里还需要额外配置。解决最简单的做法是别依赖Jupyter内联显示统一用render(xxx.html)输出文件再用浏览器打开。想保留内联体验就确认pyecharts版本并调用对应API。另一个常见毛病是中文字体缺失导致图例里的中文显示成方块解决方法是给页面指定本地中文字体或使用SVG渲染器。6. 验证方法与进阶怎么证明你的结论不是玄学很多人做情感分析做到“出了图”就停了但分析结果到底准不准其实没有验证过。我习惯的做法是人工标注100条评论然后和模型打分做对比算出准确率。这一步看起来费时间但能帮你发现阈值和词典的问题。import random def evaluate(comments, sample_size100): sample random.sample(comments, min(sample_size, len(comments))) correct 0 for c in sample: text clean_text(c.get(content, )) model_label label_sentiment(sentiment_score(text)) # 这里需要你人工判断并输入实际情感0负面 1中性 2正面 true_label input(f{text}\n真实情感(0负面/1中性/2正面): ) map_label {0: 负面, 1: 中性, 2: 正面} if model_label map_label[int(true_label)]: correct 1 return correct / len(sample)这段代码运行后会挨个打印评论文本你人工判断并输入0/1/2最终输出准确率。第一次跑通常准确率在六到七成别慌这说明阈值和词典有调整空间。把预测错的那几条收集起来找出共性问题如果是“一般般”这类委婉词就扩充情感词典如果全是长文本误判就考虑用更重的模型。改完再抽100条验证直到准确率稳定到八成以上。进阶的方向有几个。其一是把SnowNLP的词典换成自己标注的小语料重新训练其二是引入预训练模型做细粒度情感识别但要做数据标注成本高出不少其三是把每天的情感均值做成滚动窗口找出口碑异常波动的具体日期再去翻当天的评论原文定位原因。这些方向都建立在“你已经有一份被验证过的情感分析流程”的基础上。最后说一个我自己的习惯每次跑完一批数据我会把阈值、词典版本、采样的商品ID和抓取日期记录在一个配置文件里方便几个月后回溯。因为情感分析的结果受词典、阈值影响很大没有这些记录几个月后再跑同一批数据得出的图表可能完全不一样。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取
返回列表