ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

新能源汽车评论数据集:从爬虫到情感标注的构建实战

新能源汽车评论数据集:从爬虫到情感标注的构建实战 简介该数据集聚焦新能源汽车在线评论依托汽车之家平台爬取共包含逾十三万条用户评论其中七万六千九百零四条已完成正面、中立、负面等情感标注规模可观且领域指向明确。这样的标注语料可直接用于情感分类模型训练、情感词典构建、舆情监测和市场调研等场景也可为相关学术研究提供基准数据。压缩包共9个文件涵盖Python爬虫脚本、ChromeDriver驱动、说明文本、可执行程序及Markdown文档等包体仅8.98MB整体结构紧凑、便于携带和复用。目前已有91人次学习下载。借助其中的爬虫脚本使用者能够理解动态网页抓取思路并针对自身需求调整关键词、翻页逻辑和字段解析持续扩展数据集结合已标注评论则能训练多分类情感模型开展品牌对比、话题聚类与用户满意度分析。说明文件与规范子目录提供了清晰的操作指引和项目背景适合新能源汽车行业研究者、数据分析师、NLP算法工程师以及中文评论挖掘爱好者从中挖掘真实用户反馈辅助产品改进与行业趋势判断。1. 新能源汽车评论数据集为什么值得自己动手爬13万条评论里的标注价值“新能源汽车在线评论数据集”这个标题听起来像是网盘里一个现成的zip压缩包实际上它更像一座没装修的毛坯房13万条来自汽车之家平台的真实用户评论其中76904条已经带情感标注剩下的六万.zip里躺着还没标的数据。很多人拿到这种数据集第一反应是赶紧跑模型结果往往被数据质量打回原形。这个标题能帮你解决的不是从零训练一个大模型而是用最低成本把爬取、清洗、情感标注和验证做成一条可靠流水线最后拿一份干净的数据集去训练情感分类或做用户口碑分析。适合正在做NLP项目的学生、车企市场分析师以及想拿Python爬虫练手又不想只抓静态页面的从业者。2. 从汽车之家页面结构到评论爬虫先搞清数据在哪再写代码很多人问python爬虫爬取网页数据怎么入门我的建议永远是从评论列表开始而不是首页。评论区结构相对固定字段也规整比抓首页那种花里胡哨的楼层布局划算得多。汽车之家这类平台的前端页面早就接口化渲染了直接在浏览器里看HTML经常会怀疑人生滚了半天HTML里永远是同一个空壳。所以第一步不是写代码而是打开开发者工具看真实请求长什么样。2.1 评论列表的接口特征翻页参数和前端渲染拿一个车型的口碑或点评页面来说往下滚动时评论是分批加载的。在Chrome里按F12切到Network面板筛选Fetch/XHR再往下滚一页能看到一个返回评论列表的请求响应体是JSON里面直接包含评论内容、用户昵称、发布时间、车型名、评分和点赞数。抓包时最该记住的是三样东西请求URL路径、翻页参数名、分页字段名。常见的是carId、page、pageSize但不同版本可能叫carid、pageIndex、pagesize甚至有的接口从0开始计数。不要凭直觉写死我见过太多人因为少看一个参数名白跑了一晚上。为什么非要先搞清接口而不是下载HTML第一接口反回的是结构化JSON省去了解析HTML标签的麻烦而且自带评论ID和用户ID这些都是后续去重和关联的钥匙第二评论接口通常不会在HTML里暴露全量数据靠静态页面爬往往只能拿到前两页再往后要么空、要么重复第三接口参数里经常带一个token或sign这个值是前端动态计算的如果你只在爬虫里拼URL很容易被拦截。我处理这类页面时一般先手动翻三页对比URL里哪些参数在变、哪些参数不动再用Python去模拟这几分钟是后面省几个小时的关键。2.2 最小可用的Python爬虫请求头、Cookie和延时下面这段代码是我处理汽车之家评论列表时最常用的一套骨架。注意接口域名和一两个参数名需要按你自己抓到的结果替换思路是通用的。import requests import time import json def fetch_comment_list(car_id, page, page_size20, cookie你的登录Cookie): headers { User-Agent: ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36 ), Accept: application/json, text/plain, */*, Referer: https://k.autohome.com.cn/, # 按实际页面来源换 Cookie: cookie, } params { carId: car_id, page: page, pageSize: page_size, order: time, } resp requests.get( https://接口域名替换成自己抓到的路径/comment/list, paramsparams, headersheaders, timeout10, ) resp.raise_for_status() payload resp.json() return payload.get(data, {}).get(commentList, []) def save_comments(items, page): with open(fraw_comments_{page}.json, w, encodingutf-8) as f: json.dump(items, f, ensure_asciiFalse, indent2) car_id 你的车型ID for page in range(1, 200): comments fetch_comment_list(car_id, page) if not comments: break save_comments(comments, page) print(fpage {page}: {len(comments)} comments) time.sleep(1.5)这段代码的逻辑很直白循环翻页每页拿到评论后直接存成独立JSON文件遇到空列表就停。一个容易被忽略的地方是请求头里的User-Agent和Referer前者不能是Python默认的否则某些平台直接返回403后者要贴近实际页面来源否则服务端会认为你是跨域调用。Cookie建议先在浏览器里登录一次再复制能明显减少弹验证码的概率。time.sleep(1.5)是底线哪怕目标数据量很大我也不建议把这个值调到1秒以下真被封了之前的努力全白费这种翻车我已经见过太多次。参数说明car_id是车型在汽车之家体系内的唯一标识决定了你爬哪个车的评论page从1开始pageSize一般填10或20timeout10让网络卡顿时主动失败而不是无限等待sleep控制在1到3秒之间属于“别把对方服务器当自己家”的合理转速。2.3 增量爬取与断点续爬用已爬ID做去重目标是13万条评论单线程一页页爬会很耗时中间一旦断网或进程被杀之前爬的不能白费。所以从第一天开始我就给爬虫加了断点续爬。核心是维护一个本地seen_ids集合每成功保存一条评论立刻把评论ID追加进文件。下次启动时先读一遍已经见过的ID直接跳过。这样就算脚本中途挂掉从上次的页码附近继续数据流也是完整的。import os def load_seen_ids(pathseen_ids.txt): if not os.path.exists(path): return set() with open(path, encodingutf-8) as f: return {line.strip() for line in f if line.strip()} def mark_seen(comment_id, pathseen_ids.txt): with open(path, a, encodingutf-8) as f: f.write(comment_id \n) seen load_seen_ids() for page in range(start_page, 200): comments fetch_comment_list(car_id, page) if not comments: break for item in comments: cid str(item.get(id)) if cid in seen: continue save_single_comment(item) # 你自己的入库逻辑 mark_seen(cid)这里要解释两点。第一为什么用set而不是list13万条ID用list做成员判断是O(n)复杂度越到后面越慢set是哈希查找基本是常数时间。第二为什么每处理一条就追加一次而不是最后统一写追加能保证意外中断时已处理的ID不丢最多丢一条这是典型的打卡式断点续爬。start_page可以记录在另一个文本里也可以写死成失败前的页码但持续爬时优先以ID去重为准页码只是加速定位。提示评论数据请仅用于个人学习或研究爬取时控制频率、保存结果别把别人的平台当测试场。3. 把爬下来的评论清洗成标准数据集13万条文本的字段设计、去重与存储爬虫跑完你手里是一堆JSON还不是数据集。标题里说的“数据集合”重点在“集合”两个字字段要有统一命名内容要去掉杂质最终落成一个能被pandas直接加载的格式。很多人在这步偷懒后面做情感标注时才发现字段对不上、重复评论满天飞。清洗数据不性感但能省掉大量后悔药所以这章我按自己常走的路径拆开讲。3.1 字段设计从评论ID到情感标签先定字段再写清洗脚本是我踩过坑之后才养成的习惯。第一次做评论分析我边爬边加字段最后脚本乱成一锅粥。现在我会先设计一张表每个字段都有确定类型。字段名类型示例备注comment_idstring23456789平台唯一ID去重键user_namestring新能源车主_123已匿名化publish_timedatetime2023-11-08 14:23统一为%Y-%m-%d %H:%M:%Scar_modelstring比亚迪 海豹车型名称ratingint41-5星评分弱监督信号titlestring“整体满意小毛病多”部分评论有标题contenttext续航冬天打七折...清洗后的纯文本like_countint23热度sentiment_labelstringpos / neg / neu人工标注后补这张表的要点是给每个字段定死类型和示例。rating虽然是现成评分但别直接拿来当情感标签打3星的人在文本里可能全程抱怨打5星的人也可能吐槽两句小问题。后面人工标注时sentiment_label才是真正要费力气的地方。user_name这列在落地成数据集时建议做匿名化保留一个可区分用户的代号即可这是数据集合的基本底线。3.2 用pandas做文本去重和短文本过滤清洗阶段最常用的工具是pandas下面这段能覆盖大部分脏数据。import pandas as pd import glob import json rows [] for path in glob.glob(raw_comments_*.json): with open(path, encodingutf-8) as f: rows.extend(json.load(f)) df pd.DataFrame(rows) print(原始条数:, len(df)) # 1. 按评论ID去重过滤接口重复返回 df df.drop_duplicates(subset[comment_id], keepfirst) # 2. 同一用户发布完全相同的内容只保留最早一条 df df.drop_duplicates(subset[user_name, content], keepfirst) # 3. 过滤纯空白和过短文本 df df[df[content].str.strip().str.len() 10] # 4. 剔除明显的广告段 ad_keywords [加微信, 优惠券, 进群, 代购] mask df[content].apply(lambda x: any(k in x for k in ad_keywords)) df df[~mask] df.to_csv(new_energy_comments.csv, indexFalse, encodingutf-8-sig) print(清洗后条数:, len(df))逻辑说明第一轮按ID去重解决的是接口缓存和断点续爬带来的同一条评论被重复保存的问题第二轮按“用户内容”去重解决同一用户反复刷屏的问题第三轮过滤少于10个字的文本因为“好”“不错”这类短评给情感标注提供的信号太弱第四轮去掉广告不然标注员会被“加微信看车”这类内容带跑偏。每次去重后print条数能直观看出重复主要集中在哪个维度。参数说明drop_duplicates里subset指定判断重复的字段keepfirst保留首次出现的行str.strip()去掉首尾空格str.len()计算长度编码用utf-8-sig是为了让Excel直接打开不乱码。这三处看似简单却是之后所有统计的地基。3.3 导出CSV与zip打包中文文件名和编码的坑清洗后我一般导出CSV再压成zip存档或分享。这一步看起来不起眼实际翻车率很高坑集中在编码和文件名上。import zipfile import os def make_zip(source_csv, zip_name): with zipfile.ZipFile(zip_name, w, zipfile.ZIP_DEFLATED) as zf: # arcname 用英文路径避免跨平台中文乱码 arcname data/ os.path.basename(source_csv) zf.write(source_csv, arcnamearcname) # 附带一个字段说明 zf.writestr(README.txt, sentiment: pos/neg/neu\nrating: 1-5)参数说明ZIP_DEFLATED是压缩方法zipfile也支持ZIP_STORED但那是纯存档不压缩arcname决定压缩包内的路径名我统一用data/前缀加英文文件名主要原因是中文文件名在Windows和macOS之间互解时容易出乱码。如果拿到手的zip是别人压缩的解压时文件名乱码多半是压缩时用了本地编码解决办法在避坑章里细说。先记住一点自己制造zip时文件名能不用中文就不用中文。4. 情感标注76904条标注量怎么来标注规范与一致性校验标题里最值钱的信息不是13万条总数量而是“七万六千九百零四条已完成情感标注”。做过NLP的人都知道未标注文本是原材料标注文本才是可以直接消费的训练资源。这章讲清楚情感标注的标准流程以及拿到这份标注数据后怎么验证它靠不靠谱。4.1 标注体系选型三分类还是五分类常见情感标注体系有两类三分类pos/neg/neu和五分类强正向、弱正向、中立、弱负向、强负向。汽车评论有个典型特点大量“还行”“外观可以但动力肉”“性价比不错续航一般”这类混合倾向表述它们不是纯粹的正或负。所以我的建议是优先做三分类。原因是五分类的边界太模糊两个人标同一句“动力还行”可能一个标弱正向、一个标中立一致性很难达标而对大多数舆情分析和口碑分析场景三分类已经够用。76904条如果按五分类做工时要翻倍收益却不一定值。但三分类也不是没有难题最麻烦的是中立样本。一种做法是把“无明显情绪倾向”标成neu另一种做法是强制二选一规定不准标中立。前者尊重真实分布后者在训练时省事但对标注员要求极高。我在实际项目里一般保留中立因为汽车口碑场景里“偏中性”的比例经常超过20%硬逼到正负两类模型会在“还行”上乱猜。这正是后面模型翻车的黑匣子之一。4.2 标注流程与工具双人标注、争议仲裁与抽样质检76904条不是一个人一口气能标完的。常见做法是找2到3个人组成标注小组先拿500条做试标统一标准后再开工。每条评论至少让两个人独立标注不一致的进入仲裁池由负责人拍板。为了追溯质量标注数据通常保留两个标注员的原始结果而不是只留最终值。我习惯用很朴素的方式管理标注给每个标注员一张CSV里面只有comment_id、content、label列不让他们改原文。这样算一致性时不需要对齐也防止手滑把content改了。comment_id,content,label_annotator_a,label_annotator_b,label_final 1001,提车一周底盘很稳动能回收舒服,pos,pos,pos 1002,冬天续航打六折不敢开暖风,neg,neg,neg 1003,外观见仁见智吧吧,,neu,neu说明final列在仲裁后填写空着表示还没到仲裁阶段。记录两个标注结果的价值是后面能算Kappa也能观察标注员之间的分歧有没有随着时间扩大。如果某个标注员后半天开始连续标“pos”说明他疲劳了需要回炉重标一部分。4.3 Kappa系数标注一致性怎么算标注一致性直接决定这份数据集能不能用。如果两个标注员只对简单样本看法一致对难样本全靠猜这个数据集就是虚假繁荣。最常用的指标是Cohen‘s Kappa计算很简单from sklearn.metrics import cohen_kappa_score # 两个标注员对同一批样本的标注结果顺序一一对应 labels_a [pos, pos, neu, neg, pos, neu, neg, pos] labels_b [pos, neu, neu, neg, pos, neu, neg, pos] kappa cohen_kappa_score(labels_a, labels_b) print(kappa) # 0.71逻辑说明cohen_kappa_score把两个列表里对应位置的标签做比较输出范围是-1到1。0.4以下说明标注规范有严重分歧需要重新培训0.4到0.6是勉强可用但要查分歧集中在哪类0.6以上适合做训练集0.8以上才算高质量。76904条不用全量算按批次抽1000条就够。参数说明两个列表的长度必须相同顺序必须一致否则算出来毫无意义。这是标注数据里最常见的隐形问题Excel排过序之后两个标签列的顺序被悄悄打乱Kappa直接失真。4.4 六万.zip未标注数据怎么利用半监督或自训练拿到标题里的“六万.zip”先别急着删。这六万条未标注评论配合76904条标注数据理论上能做出比只用监督数据更强的模型。常见做法是先用标注数据训练一个分类器再对未标注评论做预测只取高置信度样本作为伪标签加入训练集再做第二轮训练。这种方法叫自训练属于半监督学习。# 半监督自训练逻辑示意 clf train_model(X_labeled, y_labeled) proba clf.predict_proba(X_unlabeled) high_conf_mask proba.max(axis1) 0.9 pseudo_label clf.classes_[proba.argmax(axis1)] X_train_aug concat(X_labeled, X_unlabeled[high_conf_mask]) y_train_aug concat(y_labeled, pseudo_label[high_conf_mask])原理上很容易理解模型对自己确信的样本打伪标签等于用模型自己当标注员。但这套方法每一步都可能放大模型偏差。我之前试过把概率阈值降到0.6结果错误预测大量混入模型在差评样本上的F1不升反降。所以阈值宁可高一点比如正负类取0.9中立类取0.95宁缺毋滥。如果发现伪标签样本的车型分布和原训练集差异很大要警惕概念漂移比如新车型上市后评价口径完全变化这时候模型的老经验未必适用。5. 新能源汽车评论数据集避坑指南爬取、解压、标注的5个常见问题这一章写我处理类似汽车之家评论数据时真实遇到过的问题每条按现象、原因、解决三步记录。标题里从爬取、zip解压到情感标注都有对应坑这里一并讲清楚。5.1 评论楼层加载不全翻页到后面全是重复数据现象爬到300页之后新页面的评论和前面完全重复评论ID集合不再增长继续爬只是浪费带宽。原因评论接口对深度翻页做了限制常见的是只对登录用户返回完整列表或者服务端在发现请求频率过高时开始返回缓存数据。另一个常见原因是翻页参数上限比如接口最多给50页你翻到60页时直接回到第1页。解决先看接口返回里有没有totalPage或hasNext字段有就用它做终止条件不要用固定页数。同时用评论ID去重辅助判断连续3页全是重复ID就自动停止。更彻底的方法是换端汽车之家App端接口和网页端接口的限流策略不一样App端对深度翻页通常更宽容但解析成本也更高。无论用哪端把断点信息存下来别从头再来。5.2 zip包解压乱码文件名乱码但内容正常现象下载的zip文件在Windows上右键解压文件夹名和文件名变成一串乱码但用压缩软件打开时文件内容本身是正常的。原因压缩包里的文件名使用GBK编码而当前操作系统默认按UTF-8解码。Python的zipfile模块在不同平台下的默认行为不一样导致跨平台解压时中文名乱码。解决如果包是你自己压缩的以后用英文作为arcname或者统一用7-Zip压缩并强制UTF-8。如果拿到的是别人压缩的包用Python解压时指定编码import zipfile with zipfile.ZipFile(six_wan.zip) as zf: zf.extractall(extracted, metadata_encodinggbk)这里有个边界要留意如果压缩包里同时混有GBK和UTF-8文件名metadata_encoding会全局生效个别文件仍可能乱码。这种情况建议先看zipfile的infolist只对乱码文件手动改名。5.3 zip伪加密一解压就提示要密码但文件能正常预览现象标题相关的“六万.zip”在Windows资源管理器里双击能正常看文件列表但一解压就弹窗要求输入密码试了所有常见密码都失败。原因zip格式的加密标志位有两种情况。真正加密的zip文件内容是用密码派生的密钥加密的没有密码解不开。另有一种“伪加密”只在zip目录项里把加密标志位置为1文件数据本身没有加密。这种操作属于加密信息误导很多人以为拿到了加密包实际上只是有个标志位挡着。解决先用Python确认是不是伪加密import zipfile with zipfile.ZipFile(six_wan.zip) as zf: for info in zf.infolist(): print(info.filename, hex(info.flag_bits))如果flag_bits的低位为1但文件实际上不需要密码就是伪加密。网上说的“zip密码移除”工具本质就是改这个标志位。自己处理的话可以清零标志位后重新打包# 只有确认为伪加密才能这样操作 info.flag_bits ~0x1注意真正的加密zip无法通过改标志位绕过。遇到来路不明的加密包没有合法密码就果断放弃别在破解上浪费时间。5.4 情感标注里的“中立”样本被低估模型翻车的黑匣子现象模型在验证集上准确率到了88%一上线就被用户投诉“我的评价明明在夸系统判成负面”。原因验证集里大量是“满意”和“不满意”的极端样本模型学到的是关键词信号比如出现“好”就判正向出现“垃圾”就判负向。真实场景里更多是“还行”“外观挺好内饰一般”这种混合倾向样本标注时被强制归到正或负边界很混乱。误判集中出现在中立样本上。解决第一标注规范允许中立样本存在训练时保留neu类第二按类别比例抽样算混淆矩阵重点看neu被分到哪边第三如果业务只需要正负决策就把中立样本单独作为过滤层而不是塞进二分类。我现在的习惯是先跑一个只含正负的子集再对比含中立的版本看指标差异才能定位到底是谁在拖后腿。5.5 数据不平衡好评太多差评太少怎么办现象13万条评论里好评占比可能超过70%差评只有不到15%训练出来的模型对差评样本漏判很多召回率低。原因用户行为天然倾斜满意的用户更愿意留好评不满的用户一部分直接走人一部分留在评论区但量少。如果不处理模型会在多数类上过拟合把少数类当成噪声。解决训练集按车型和评分分层采样负向评论可以全保留正向评论按比例抽让两个类别的量接近。评估时不要只看accuracy要看neg类的F1。如果数据实在不平衡可以对负向评论做轻量回译增强但要注意增强后不能改变原文语义。标题里的数据没给出具体分布拿到后第一步就统计sentiment_label的计数比例这是我所有后续选择的先决条件。6. 拿76904条标注评论跑通一个情感分类基线从TF-IDF到LogisticRegression最后给一个能立刻上手的验证流程目的是确认这份数据集里的情感标注是否和文本内容匹配。不要一上来就上BERT先把简单的基线跑明白再用它决定要不要投入更大的模型。6.1 加载数据并划分训练集/测试集把清洗后的CSV读取进来按情感标签分层划分。这里必须分层否则差评样本可能全跑到测试集里指标失真。import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(comments_76904.csv, encodingutf-8-sig) print(df[sentiment_label].value_counts()) X_train, X_test, y_train, y_test train_test_split( df[content], df[sentiment_label], test_size0.2, random_state42, stratifydf[sentiment_label] )stratify是这里的关键参数它保证训练集和测试集里各类别比例与原数据集一致。如果没有它分层抽样就是玄学结果随时会翻车。6.2 用TF-IDF加LogisticRegression做快速基线from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression vec TfidfVectorizer(max_features20000, ngram_range(1, 2), strip_accentsunicode) X_train_vec vec.fit_transform(X_train) X_test_vec vec.transform(X_test) clf LogisticRegression(max_iter200, C1.0) clf.fit(X_train_vec, y_train) from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_test, clf.predict(X_test_vec))) print(confusion_matrix(y_test, clf.predict(X_test_vec)))参数说明max_features限制词表大小为2万中文场景够用且能降低过拟合ngram_range(1,2)可以捕获“动力肉”“续航崩”这类组合词C是正则强度C越小正则越强一般从1.0开始调max_iter要调到200以上否则某些求解器会报不收敛。6.3 看结果时先查错误样例classification_report只能给你轮廓真正判断数据质量的方法是随机看30条预测错误的结果。如果很多错误是“评论文本明显正面但模型分类为负面”首先要怀疑标注而不是模型。把错误样例和原始标注都打出来逐行看能发现不少标注遗漏。我现在的习惯是无论最后要不要上BERT都先用这个基线过一遍。基线跑完你才知道这份新能源汽车评论数据集的真实水平。希望帮到你。本文还有配套的精品资源点击获取
返回列表