Python爬虫与情感分析实战:从电商评论采集到Web展示
tmall
jd
落盘之际, 我会采用 utf - 8 - sig 这种编码方式进行整体性的统一运用, 以此来避免在上用 Excel 将 CSV 文件打开时出现变成乱码的情形。以下呈现的是关于追加写入的相关代码:
# storage.py
import os
import pandas as pd
def save_comments(comments, platform, file_path):
"""
comments: 每条评论已经转换成统一schema的字典列表
"""
df = pd.DataFrame(comments)
if df.empty:
print("没有数据,跳过写入")
return
df["platform"] = platform
df["crawl_time"] = pd.Timestamp.now()
# 确保只保留统一字段
df = df[["item_id", "user", "content", "rating",
"comment_time", "platform", "crawl_time"]]
# 如果文件不存在则写入表头,否则追加数据
df.to_csv(file_path, mode="a", header=not os.path.exists(file_path),
index=False, encoding="utf-8-sig")
表示追加的情形是, mode="a" 会避免每次保存时都将已有数据覆盖掉 对于不存在 该CSV文件的状况 写出列名的写法 =not os.path.() 是很常用的 若文件已然存在的话那种再去重复写表头的行为就不会出现了 字段方面固定成通用顺序的意义在于 能在进行追加操作的过程中防止列出现错位的情况 记录采集时间是为了方便后期按照批次去开展清洗或者是去重工作。
2.4 采集阶段的三个常见坑
第一个坑是淘宝出现失效情况了, 好多学生把从浏览器里复制过来的内容直接进行硬编码, 结果到了隔天请求回来的却是空列表, 建议将其放置到单独的一个配置文件中, 并且每次在请求以前先去检查评论数是不是一直为0, 要是是的话就提示重新登录来获取, 第二个坑是存在翻页限制, 京东部分商品最多仅仅开放大概100页评论, 淘宝对于较早页之前的评论是不开放的, page增大到某个数值之后返回的是同一批数据, 在这个时候要停止翻页而并非无限请求。有这样一个情况, 第三个坑是字段缺失, 京东存在部分评论没有score字段, 淘宝匿名用户所发表的评论有可能呈现为空字符串的状态, 所以在上一步进行统一操作的时候,需要运用df.get("score")并配合默认值, 以此来避免出现相关问题。
3. 中文商品评论, 情感分析, 从清洗开始, 到分类结束, 3.1, 为何不用词频统计方式进行, 而是采用情感分析方法呢, 标点符号。
数一数“好评”以及“差评”的数量, 这是最为直观的一种做法 , 然而商品评分自身也是存在着偏置情况的 , 好多人打了三星 , 可是在文字当中表达出了强烈的不满。情感分析的目标 , 是从评论文本自身去推断情绪的倾向 , 输出一个连续的分值 , 能够更加细致地捕捉“还行”“不值这个价”这类带有情绪的中文表述。API返回的评分字段是用户给出的星级 , 而情感得分是机器依据文本计算出来的 , 把这两者结合起来 , 能够识别出“评分高但抱怨多”的异常评论。
3.2 情感分析方案选型:词典法、与深度学习
有着做中文情感分析的常见方案存在三种情况, 其一为基于情感词典, 也就是统计积极词以及消极词的数量, 其二是基于朴素贝叶斯的预训练库, 比如某些特定的情况那样, 其三则是基于大规模预训练模型的Fine - tune。这个项目所拥有的数据集乃是淘宝以及京东的商品评论, 评论句子比较简短, 口头用语化的程度极为严重, 在短文本之上训练过电商评论语料, 如此一来可以直接加以使用, 并不需要额外去下载大模型。深度学习方案所呈现的效果比较良好, 然而需要借助GPU以及大量进行标注的数据, 对于毕业设计周期而言成本相对偏高。故而, 在此处将其用作基础版本, 并且, 还为后续把它替换成自行训练的贝叶斯模型预留好接口。
3.3 基于的评论倾向打分实战
其所具备的属性返回处于0至1这个范围之间的为浮点数, 越来越靠近1就意味着越呈现出正向的态势。在针对整批CSV开展情感分析操作的时候能够依照这样子的方式进行批量处理标点。
# sentiment_analysis.py
import pandas as pd
from snownlp import SnowNLP
def analyze_df(df, text_col="content"):
"""
给DataFrame新增情感分和情感标签
"""
df = df.copy()
# 转成字符串并去掉空值
df[text_col] = df[text_col].fillna("").astype(str)
scores = []
for text in df[text_col]:
try:
s = SnowNLP(text)
scores.append(s.sentiments)
except Exception:
scores.append(0.5) # 解析失败时按中性处理
df["sentiment_score"] = scores
# 0~0.4 消极,0.4~0.6 中性,0.6~1.0 积极
df["sentiment_label"] = pd.cut(
df["sentiment_score"],
bins=[0, 0.4, 0.6, 1.0],
labels=["消极", "中性", "积极"]
)
return df
逻辑阐述: pd.cut属于分箱函数, 参数bins用于界定边界。
0, 0.4, 0.6, 1.0
把0到0.4这个范围划分成消极范畴, 将0.4到0.6界定为中性范畴, 把0.6到1.0划定为积极范畴。留意边界值的包含关系, 即0.6分会被划分到积极区间, 要是你期望“恰好0.6”算作中性范畴, 那么可以把bins改成。
0, 0.4, 0.6, 1.0001
对于评论内容完全都是表情符号、或者是特殊字符从而致使抛异常的那种边界情况, 进行了try...的相关处理, 将其兜底设定为0.5中性, 以此来防止出现整个程序中断的状况。
处理后的可以直接保存:
# 使用示例
jd_df = pd.read_csv("JDdata.csv", encoding="utf-8-sig")
result = analyze_df(jd_df)
result.to_csv("jd_with_sentiment.csv", index=False, encoding="utf-8-sig")
3.4 结果评估与自定义语料微调
在部分品类, 像是电子产品、家装方面, 默认模型呈现出“过度乐观”的表现, 许多带有“一般”“还行”的评论被赋予了0.8分。此时能够自行准备正向以及负向语料来对模型进行微调。该模块是支持训练的。
from snownlp import sentiment
# 每行一句,正向和负向分别放在不同文本文件中
sentiment.train("data/positive_comments.txt", "data/negative_comments.txt")
sentiment.save("my_sentiment.marshal")
使用训练好的模型时需要重新加载:
from snownlp import SnowNLP
def custom_sentiment(text):
s = SnowNLP(text)
return s.sentiments
特别留意, 当.train训练完毕之后, 此刻进程的类并不会自行切换成新模型, 务必要再次启动进程, 或者调用.从而加载新模型才行。在实际的工程项目之中, 我会首先通过人工方式标注500~1000条该品类的评论, 接着运用train进行重新训练, 随后再借助独立测试集去检查准确率。要是训练完成之后效果反倒出现了下降的情况, 那就表明标注语料存在较大噪声, 此时可以回退到预训练模型。
4. 针对商品评价系统进行整合, 这里面涉及到数据流水线以及Web查询, 4.1的部分, 要把采集这一环节、分析这一环节以及存储这一环节串织成一条流水线。
以单独运行方式, 爬虫与情感分析分别进行不会出现问题, 然而, 当作为一套存在特定要求的“评价系统”考量时, 其需要具备一个能够同步展开一连串操作的入口, 这儿所说的连续动作涵盖自抓取新评论起, 到保存形成CSV文件, 再到相应情感得分的更新。接下来所提及的函数, 它实现的功能是将其中的两个步骤串联在一起:
# pipeline.py
import pandas as pd
from taobao_comments import fetch_taobao_comments
from jd_comments import fetch_jd_comments
from storage import save_comments
from sentiment_analysis import analyze_df
def run_pipeline(item_id, platform, csv_path):
"""
简单流水线:采集、存储、分析
"""
# 1. 采集
if platform == "jd":
raw = fetch_jd_comments(sku_id=item_id, page=0)
comments = []
for c in raw.get("comments", []):
comments.append({
"item_id": item_id,
"user": c.get("user", {}).get("nickName", ""),
"content": c.get("content", ""),
"rating": c.get("score", 0),
"comment_time": c.get("creationTime", ""),
})
elif platform == "taobao":
# 淘宝返回结构按实际字段自行适配
raw = fetch_taobao_comments(item_id=item_id, page=1)
# ...
else:
raise ValueError("platform must be jd or taobao")
# 2. 落盘
save_comments(comments, platform, csv_path)
# 3. 重新读取整个文件并做情感分析(增量场景可只分析新增数据)
df = pd.read_csv(csv_path, encoding="utf-8-sig")
df = analyze_df(df)
df.to_csv(csv_path, index=False, encoding="utf-8-sig")
return len(comments)
这里采用“整个文件重新分析”这种方式, 是鉴于为了逻辑能够简单, 而当数据量很大的时候则会出现速度变得很慢的情况。更为优化的做法是: 在进行采集的时候, 要给每一条评论生成并且作成唯一的ID, 仅仅只是针对新增的行来执行情感打分。但是一定要注意, 情感分析本身属于CPU密集型操作, 要是一次性读入几十万条评论, 建议首先使用df.("")来进行抽样测试。
4.2 用Flask暴露一个最简评价统计接口
为了将结果呈现出来, 运用Flask写了一个轻量级的用来统计的API, 凭借商品ID以及平台做输入, 把该商品有的积极评论条数、中性评论条数、消极评论条数和平均情感分进行返回。
# web.py
from flask import Flask, request, jsonify
import pandas as pd
app = Flask(__name__)
@app.route("/api/stat")
def stat():
item_id = request.args.get("item_id")
platform = request.args.get("platform", "jd")
csv_path = f"{platform}_comments.csv"
df = pd.read_csv(csv_path, encoding="utf-8-sig")
if item_id:
df = df[df["item_id"] == item_id]
if df.empty:
return jsonify({"error": "no data"}), 404
score = df["sentiment_score"]
result = {
"total": len(df),
"positive": int((score > 0.6).sum()),
"neutral": int(((score >= 0.4) & (score <= 0.6)).sum()),
"negative": int((score < 0.4).sum()),
"average_score": round(float(score.mean()), 4)
}
return jsonify(result)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
这个说明是这样的, score大于0.6的时候, 它返回的是布尔值, 然后通过.sum()去统计True的数量, 这里要注意, 我没有使用那个点, 也就是是假如CSV当中进行了列操作, 并且是由上一步流水线那边写好的。另外, Flask的.args.get是用来读取查询参数的, 比如说当访问/api/stat?=&=jd的时候, 就会返回那条商品的聚合统计, 这是相关的情况。
4.3 环境搭建与启动步骤
这个系统的关键依赖仅有四个, 适宜于快速进行复现, 建议去创建一个干净的虚拟环境。
python -m venv venv
source venv/bin/activate # Windows用 venv\Scripts\activate
pip install requests pandas snownlp flask
启动时先执行一次流水线初始化数据,再启动Web服务:
python pipeline.py # 假设该文件里调用 run_pipeline
python web.py
假使不需求Web界面, 仅仅想于笔记本之中查看结果, 那么能够径直在里 pd.(".csv") , 去绘制一条 柱状图。项目里所自带的 中文商品评论.csv 以及 data.csv 同样能够直接予以读取进而用来做分析, 用不着急着去抓取新的数据。
4.4 部署运行时的编码与反爬调整
在实际开展运行的进程当中, 最为容易遭遇到的情形便是控制台编码方面的问题, 当进行print中文评论操作的时刻常常会出现报错的状况。这是由于控制台编码并非UTF-8所引发导致的, 能够在代码起始的部位添加sys, sys..(="utf-8")。除此之外, 当采集频率过高情况发生的时候, 京东便会返回一个涵盖特定字段的JSON, 这意味着需要进行人机验证。在这个时候就需要暂停下来并且降低请求的频率:
import random
import time
def safe_sleep(base=1.5):
# 随机sleep,避免固定间隔被识别
time.sleep(base + random.uniform(0, 2))
你同样能够于请求头里添入 X--For , 然而这没办法确保绕开验证码, 最为稳当的手段是将并发限定为单线程, 并且把控每分钟请求的次数不超出 20 次。针对毕业设计演示, 只要预先把数据采集好存作 CSV , 演示之际从 CSV 之中读取用于进行情感分析就行了, 无需现场高频爬取。
5. 进阶:用情感得分与商品评分联合识别异常评论
商品评分是由用户所打的星, 情感得分是模型依据文字推导得出的倾向, 这两者并非总是相一致, 这种不一致恰恰能够被用于异常评论检测, 评分要是给到4星或者5星, 然而文言情感得分却低于0.2, 极有可能便是“给好评但实际吐槽”的矛盾评论, 也有可能是刷单机器人复制了毫无意义的夸奖文案 , 反过来, 低评分却有着高情感得分, 一般而言是反讽的语气, 就好像“这质量真是太好了, 三天就坏” , 这个技巧能够助力你在爬取下来的数据当中迅速筛选出值得进行人工复核的评论。
下面这段代码基于统一后的CSV做联合过滤:
# outlier_check.py
import pandas as pd
df = pd.read_csv("jd_with_sentiment.csv", encoding="utf-8-sig")
# 把rating转换成数值,允许缺失值
df["rating"] = pd.to_numeric(df["rating"], errors="coerce")
# 条件1:商品评分 >= 4,但情感得分 < 0.2
high_rating_low_sentiment = df[
(df["rating"] >= 4) & (df["sentiment_score"] < 0.2)
]
# 条件2:商品评分 <= 2,但情感得分 > 0.8(反讽/夸赞型差评)
low_rating_high_sentiment = df[
(df["rating"] <= 2) & (df["sentiment_score"] > 0.8)
]
print(f"高评分低情感: {len(high_rating_low_sentiment)}条")
print(f"低评分高情感: {len(low_rating_high_sentiment)}条")
逻辑阐释: pd.(="")会把那些没办法转化成数字的评分弄成NaN, 以此防止因为某个评分是字符串而在比较的时候出现报错情况。阈值0.2以及0.8得依据你的语料分布来进行调整, 能够先使用df。
""
瞧一瞧四分位数, 而后确定阈值。此联合识别方法所具备的价值在于, 它无需额外进行标注数据, 而是径直借助电商用户打星以及评论情感方面的价值冲突, 便能够找寻到系统当中最为可疑的样本。要是将这些异常评论单独予以导出, 接着配合jieba提取高频词, 那么就能够看出哪些商品存有明显刷单刷单痕迹, 或者哪些商品的描述跟实际体验之间的偏差过于巨大。

更多推荐




所有评论(0)