ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python电商评论情感分析实战:从爬虫采集到Flask可视化

2026/9/11 4:06:28 拓冰建站 浏览量
Python电商评论情感分析实战:从爬虫采集到Flask可视化 简介这是一套面向计算机相关专业毕业设计场景的完整项目资料以Python爬虫与情感分析为主线覆盖淘宝、京东商品评价数据的采集、清洗、分析与可视化。资源整体包含104个文件压缩包大小约55.57MB。其中7个Python脚本对应爬虫与模型构建主流程37个CSV文件存有商品评论及爬取结果数据集pt与lstmmodel文件为情感分析模型参数另有HTML可视化页面、图片及配套说明文档能从数据到模型再到展示形成闭环。目前已有400人学习下载代码均经过运行验证适合毕业设计、课程设计或Python爬虫与NLP入门者参考。除可动手复现外还可基于现有爬虫框架、情感分类模型和可视化图表进行二次改造快速产出可展示的成果。1. 一套能直接跑的电商评论情感分析毕设先厘清数据文件与系统边界这个 Python 商品评价分析毕设文件清单乍一看很杂TBdata.csv、JDdata1688264584.7149756.csv、带商品 ID 的 comments.csv、online_shopping_10_cats.csv还有一个 chromedriver。顺着数据的流向排一遍它就是一套完整的电商评论采集与情感分析流水线淘宝和京东走两条不同的采集通道拿到原始数据清洗去重后做情感打分最后把结果用 Flask 接口和 ECharts 图表展示出来。对准备毕业设计答辩的学生这套代码能完整解释“数据从哪来、结论怎么算”对已经写过几年爬虫的工程师文件里的时间戳命名和 CSV 粒度差异也暴露了不少实战细节值得拆开看看。2. 淘宝京东双通道采集Requests 抓列表chromedriver 抓评论2.1 先看清文件之间的采集时序TBdata.csv是淘宝搜索页的抓取结果每行对应一个商品包含标题、价格、销量和链接JDdata1688264584.7149756.csv这类带一串数字前缀的文件数字是 Unix 时间戳说明京东评论分多次抓取并按批次落盘53531219728comments.csv和10055368660713comments.csv里的文件名本身是商品的 productId属于按商品归档的评论明细。online_shopping_10_cats.csv是另外引入的公开电商评论数据集覆盖 10 个类别的带标签语料用来做后续情感模型的验证。写分析代码前先确认每个文件的列名和数据量避免在清洗阶段才暴露列名不一致的问题。常见做法是写一个快速预览脚本import pandas as pd for name in [TBdata.csv, JDdata.csv, 53531219728comments.csv]: df pd.read_csv(name, encodingutf-8-sig, nrows5) print(f {name} ) print(df.columns.tolist()) print(df.shape)这里用nrows5只载入前 5 行做结构预览避免大文件把内存打满encodingutf-8-sig是中文 CSV 的标准读法它会自动去掉文件开头的 BOM否则第一列列名经常会变成带\ufeff前缀的怪字符串。2.2 淘宝列表页Requests 加登录 Cookie 的轻量方案淘宝搜索结果页本身有公开接口主要门槛在 Cookie。常规做法是先在浏览器里登录一次淘宝把 Cookie 完整复制出来放进代码里之后一个 GET 请求就能拿回当前页的商品列表。import requests import csv COOKIE 你的淘宝登录后的完整Cookie HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36, Cookie: COOKIE, Referer: https://www.taobao.com/ } params { q: 蓝牙耳机, s: 0, # 列表页起始索引淘宝每页44个商品 page: 1, tab: all } resp requests.get(https://s.taobao.com/search, paramsparams, headersHEADERS, timeout10) if resp.status_code 200: data resp.json() auctions data[mods][itemlist][data][auctions] rows [] for item in auctions: title item.get(title, ).replace(span classH, ).replace(/span, ) rows.append([ title, item.get(price, ), item.get(view_sales, ), https: item.get(detail_url, ) ]) with open(TBdata.csv, a, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerows(rows) else: print(状态码:, resp.status_code)代码里值得注意的三个点params中的s是列表页起始索引淘宝每页约 44 个商品翻页时s要按 44 递增q是搜索词想做多商品对比就把关键词抽出来循环请求item.get(title)里可能残留span标签上面的 replace 去掉的是最常见的高亮标记如果页面结构调整需要再用正则把尖括号内容全部清掉。Cookie 的有效期通常只有几小时。遇到JSONDecodeError说明返回的不是搜索结果 JSON 而是登录页这时回到浏览器重新复制一次 Cookie 即可。2.3 京东评论为什么用 chromedriver 而不是纯 requests京东评论页的 DOM 由接口异步渲染纯 requests 也能拿到数据但对毕业设计这个场景Selenium 方案更容易在现场讲清楚能看到页面真实滚动、能解释每个字段对应页面上哪块内容而且面对页面结构调整时改选择器的成本远低于重写参数签名。chromedriver是 Chrome 的驱动文件必须在项目里和浏览器版本匹配才能启动。from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import random import csv options Options() # 无头模式部署在服务器上没有显示器也能跑 options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-gpu) # 隐藏自动化特征降低被风控识别成机器的概率 options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36) driver webdriver.Chrome(executable_path./chromedriver, optionsoptions) product_id 10055368660713 rows [] for page in range(0, 3): # 抓前3页每页10条 url (https://club.jd.com/comment/productPageComments.action f?productId{product_id}score0sortType5page{page}pageSize10) driver.get(url) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, .comment-item)) ) items driver.find_elements(By.CSS_SELECTOR, .comment-item) for it in items: content it.find_element(By.CSS_SELECTOR, .comment-con).text star it.find_element(By.CSS_SELECTOR, .comment-star).get_attribute(class) date it.find_element(By.CSS_SELECTOR, .comment-date).text rows.append([product_id, content, star, date]) time.sleep(random.uniform(2, 5)) # 随机延时避免固定频率 driver.quit() with open(JDdata.csv, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerow([productId, content, star, date]) writer.writerows(rows)几个关键参数--headlessnew是无头模式首次调试时建议去掉能亲眼看到页面加载到什么程度WebDriverWait配合presence_of_element_located是显式等待比固定time.sleep(10)更稳定——它不会等满 10 秒而是页面元素一出现就继续往下走。京东评论区一页只有 10 条评论量大的商品要翻很多页每次请求之间用random.uniform(2, 5)随机停顿。我踩过最多坑的地方就是固定 sleep 2 秒抓 40 页后一定会触发滑块验证。2.4 采集参数对照不同通道的口径差异通道关键参数实际取值建议高频异常淘宝搜索页s起始索引每页 44翻页 44Cookie 过期返回登录页京东评论接口page、pageSizepage 从 0 开始pageSize10固定频率触发滑块Selenium 浏览器headless、延时随机 2~5 秒不要固定间隔chromedriver 与浏览器版本不匹配这套参数正好对应资源里不同格式的 CSV淘宝结果每行是一个商品京东评论每行是一条评论字段粒度不同后续清洗时不要直接 concat要分两条线处理。清理落脚在这里下一章才能把情感分析的特征讲清楚。3. 评论清洗与情感打分从文本到 0~1 的解释路径3.1 清洗的优先级去重、去噪和分词评论数据里干扰项远多于有效文本。最典型的是“此用户未填写评价内容”的占位文本以及刷单产生的复读式评论。清洗顺序固定为三件事内容级去重、长度过滤、分词统计。import pandas as pd import jieba df pd.read_csv(中文商品评论.csv, encodingutf-8-sig) print(清洗前:, len(df)) # 内容级去重清除复读式刷单数据 df df.drop_duplicates(subset[content]) # 过滤掉好评还行这类无法支撑情感判断的短文本 df df[df[content].str.len() 4] df df[~df[content].str.contains(此用户未填写评价内容)] def cut_text(text): return .join(jieba.lcut(str(text).replace(\n, ))) df[seg] df[content].apply(cut_text) print(清洗后:, len(df)) df.to_csv(cleaned_comments.csv, indexFalse, encodingutf-8-sig)drop_duplicates的subset参数决定按哪一列判断重复评论场景按内容去重比按评论 ID 去重更能清除刷单数据。长度阈值取 4是因为两个字的评论对情感打分没有区分度保留反而会把统计往中间值推。jieba 默认词典是通用语料电商词“性价比”会被切成“性价/比”不影响后续情感判断但影响词云展示效果可以在第 5 章补领域词典。3.2 打分方案选型SnowNLP 与监督学习怎么选毕设里常见的情感分析实现有三条路直接用 SnowNLP 的句法情感打分、用online_shopping_10_cats.csv训练朴素贝叶斯、或者两个方案对比后给出结论。资源里同时出现了带标签的 10 类数据集和雪花式的评论 CSV说明原始作者大概率走了“规则打分 监督验证”的路线。方案标注成本训练成本商品领域适配实现改动量SnowNLP无无弱偏社交语料几行代码朴素贝叶斯需要标签语料低强取决于训练集分词加特征工程词典加权整理正负词表无强可迭代中等我的建议先跑通 SnowNLP 得到 0~1 情感分数再用online_shopping_10_cats.csv抽一部分评论做交叉验证把两种方法在好评、中评、差评上的判定差异写进论文。答辩时这就成了“多方案对比”章节而不是只有一个模型的堆砌。3.3 打分脚本与标签口径SnowNLP 的sentiments属性返回一个 0~1 之间的概率值越接近 1 表示模型判定为积极的可能性越大。它内部是朴素的贝叶斯模型只做情感倾向判断时完全够用不需要 GPU 也不需要微调。from snownlp import SnowNLP df pd.read_csv(cleaned_comments.csv, encodingutf-8-sig) def sentiment_score(text): try: s SnowNLP(str(text)) return round(s.sentiments, 4) except Exception: return 0.5 # 解析失败的评论给中性分 df[score] df[content].apply(sentiment_score) def to_label(x): if x 0.6: return positive if x 0.4: return negative return mid df[label] df[score].apply(to_label) print(df[label].value_counts()) df.to_csv(sentiment_result.csv, indexFalse, encodingutf-8-sig)阈值为什么选 0.6 和 0.4 而不是 0.5 单一阈值因为 SnowNLP 对中性文本的判定集中在 0.4~0.6 区间这段置信度不足。拉开阈值后只有模型比较有把握的评论才进入积极或消极标签剩下的归入中评展示出来的好评率和差评率更接近人工判断。round(..., 4)保证 CSV 里的分数不出现浮点长尾。这里要特别说一个我踩过的坑不要拿情感分数直接对标电商平台星级。平台给 2 星但文本写“这次物流有点慢最近能到就行”SnowNLP 会判成中性偏正向因为文字本身情绪不激烈。毕设里如果画“星级 vs 情感分数”的散点图分布会非常散这在方法论上是正常的解释清楚即可。3.4 商品评论为什么依赖领域词表通用模型在电商评论上的最大问题是反讽和口语化表达“冲了”是积极还是消极通用语料大概率判消极。低成本修正方案是维护一份正负面词表在 SnowNLP 分数基础上做规则加权# 领域词表按商品类目维护不同品类差异很大 pos_words [好用, 值得, 满意, 推荐, 回购, yyds] neg_words [难用, 退货, 漏发, 差评, 避雷, 客服不理] def rule_weight(text): pos_hit sum(1 for w in pos_words if w in text) neg_hit sum(1 for w in neg_words if w in text) return pos_hit - neg_hit df[rule] df[content].apply(rule_weight)规则权重的用法有两种直接把rule非零的评论强制覆盖 label或者和 SnowNLP 分数做加权平均再重新划分。我一般用前者因为词表命中代表文本里有明确的情绪词比概率判断更可信。词表命中率低于 5% 说明词表覆盖不够需要回到词频统计里找高频情绪词补进去。4. 评价结果展示Flask 出接口ECharts 出图表4.1 系统分层与目录组织采集、清洗、展示三个环节要拆成独立目录否则答辩演示时改一处影响到另一处。这套资源的 CSV 分布虽然散但按功能重组后逻辑很清晰。project/ ├── spider/ │ ├── taobao_spider.py │ ├── jd_spider.py │ └── chromedriver ├── analysis/ │ ├── clean.py │ ├── sentiment.py │ └── sentiment_result.csv ├── web/ │ ├── app.py │ └── templates/index.htmlspider只负责输出原始 CSVanalysis只负责读 CSV 产出结果文件web只读sentiment_result.csv。数据文件是层与层之间的唯一约定这样任意一层重跑都不会污染其他环节。4.2 Flask 接口把结果文件变成 JSON 视图展示层用 Flask 写三个最核心的接口整体情感分布、按日期的情感趋势、商品维度的对比。接口直接读上一步生成的 CSV避免每次请求都重算情感。from flask import Flask, jsonify import pandas as pd app Flask(__name__) df pd.read_csv(../analysis/sentiment_result.csv, encodingutf-8-sig) # errorscoerce 把非法日期转成 NaTgroupby 时自动跳过 df[date] pd.to_datetime(df[date], errorscoerce) app.route(/api/overview) def overview(): stats df[label].value_counts().to_dict() return jsonify({ total: int(len(df)), positive: int(stats.get(positive, 0)), mid: int(stats.get(mid, 0)), negative: int(stats.get(negative, 0)) }) app.route(/api/trend) def trend(): # 按日期聚合情感均值观察口碑变化趋势 daily df.groupby(df[date].dt.date)[score].mean().round(3) return jsonify({dates: [str(d) for d in daily.index], scores: [float(v) for v in daily.values]}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)value_counts().to_dict()返回的是 numpy 类型直接 jsonify 会报TypeError: Object of type int64 is not JSON serializable所以外面套了int()转换。pd.to_datetime(..., errorscoerce)把不合法的日期变成NaT后续groupby会自动跳过缺失值这是处理别人导出的评论 CSV 时最常用的容错写法。4.3 ECharts 前端与指标口径前端用 ECharts 的饼图和折线图就能把商品评价系统讲清楚。饼图展示情感分布折线图展示情感均值随时间的变化趋势。// 拉取趋势接口 fetch(/api/trend) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(trend)); chart.setOption({ xAxis: { type: category, data: data.dates }, // 情感分数是0~1概率值锁死y轴避免波动拉偏尺度 yAxis: { type: value, min: 0, max: 1 }, series: [{ type: line, data: data.scores, smooth: true, areaStyle: { opacity: 0.15 } }] }); });这里有一个口径上的细节情感分数是 0~1 的概率值而平台评分是 1~5 的整数两者出现在同一张图里需要先归一化或分轴。min: 0, max: 1直接锁死 y 轴范围让趋势图稳定在一张图里避免某天分数波动把尺度拉走。展示层的接口清单先列清楚答辩时就能讲“我定义了哪几个接口、每个接口返回什么”接口返回内容前端用途/api/overview总数与情感分布计数顶部汇总卡片/api/trend按日期均值的情感分数趋势折线图/api/wordcloud高频分词及词频词云图4.4 新增一个商品需要动的位置把系统从演示淘宝和京东两个已有商品扩展成任意商品可查询只需要改三个地方spider里对应商品的 URL 或productIdanalysis/clean.py里的输入文件路径以及web/app.py里加载 CSV 的路径。如果新抓的评论里没有日期字段/api/trend会直接返回空数组前端折线图空白这时候要给记录补一个抓取日期或者把趋势分析改成按评论 ID 分段。5. 部署边界与排错把项目搬到本地能跑的关键技巧5.1 chromedriver 和 Chrome 的版本对齐资源里自带的chromedriver只能匹配当时开发环境的 Chrome 版本。在自己机器上跑最常见的报错是session not created: This version of ChromeDriver only supports Chrome version xxx。先确认两个版本./chromedriver --version chrome --version # 或 google-chrome --version版本号第一位不一致就去下载对应大版本的驱动替换掉项目里的文件。Windows 下 Chrome 的安装路径通常是C:\Program Files\Google\Chrome\Application\chrome.exeLinux 下用google-chrome --version。提示--headlessnew是新版无头模式老版 Chrome 不支持如果驱动版本较旧改用--headless。5.2 高频报错清单按出现频率排前四个问题覆盖了 80% 的跑不起来情况淘宝返回JSONDecodeErrorCookie 失效或未登录重新登录复制完整 Cookie。Selenium 提示WebDriverException: unknown error: cannot find Chrome binary系统变量里没有 Chrome 路径代码里用binary_location显式指定 chrome.exe 位置。中文乱码写入 CSV 统一encodingutf-8-sig不要用utf-8Excel 打开会乱码。评论数为 0京东评论接口的productId写错或商品已下架先去浏览器访问该商品确认评论区存在。5.3 领域情感词库的落地方式把词表从代码里抽出来按行业维护成 JSON 文件清洗和打分脚本都从这个文件加载{ positive: [好用, 值得, 满意, 推荐, 回购, yyds], negative: [难用, 退货, 漏发, 差评, 避雷, 客服不理] }import json with open(sentiment_words.json, encodingutf-8) as f: word_map json.load(f) df[rule] df[content].apply( lambda t: sum(1 for w in word_map[positive] if w in t) - sum(1 for w in word_map[negative] if w in t) )词库生效程度的判断标准很简单统计rule非零的评论行占比低于 5% 说明覆盖不够回读最高频的 Top 50 词把领域内的情绪词继续补进文件里。本文还有配套的精品资源点击获取