多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

淘宝商品评论爬虫实战:接口分析、反爬策略与数据清洗全解析

淘宝商品评论爬虫实战:接口分析、反爬策略与数据清洗全解析 1. 项目概述与合规边界1.1 为什么选择淘宝商品评论作为爬虫练手项目先说结论市面上所有爬虫教学项目里淘宝商品评论是综合性价比最高的目标之一。原因有三个层面。第一数据价值密度高。一条带图、带追评、带用户等级的商品评论给到数据分析师手里能拆出十几个维度——颜色词频、尺码偏差、物流时效、质量差评归因这些都是电商运营实际在花钱买的数据。相比爬那种几百条干巴巴的新闻标题评论数据的特征工程空间大得多做出来的项目也更像“真事”。第二技术栈覆盖面全。一个完整的淘宝评论采集器不是简单的requests.get(url)就完事它牵扯到异步接口分析、请求参数签名、Cookie维持、JS渲染页面处理、字体反爬识别、并发控制、增量存储——这一套走下来基本把爬虫工程师日常工作里八成以上的技术点都过了一遍。第三场景真实。平台有反爬接口有签名数据有加密评论区有风控算法。这就意味着你不能像爬那种静态博客一样躺赢必须动脑子去分析协议、找规律、设计降频策略。这种“跟平台规则博弈”的过程恰恰是爬虫项目最有价值的沉淀。1.2 开爬之前必须搞清楚的法律红线这块我不喜欢说教但作为从业者必须提醒你一次因为真出了事没人替你扛。根据目前的法律实践和平台规则采集淘宝商品评论要注意以下几点仅用于个人学习、学术研究不得用于商业转售或竞争性分析。控制采集频率不对目标服务器造成压力性访问。不绕过技术措施获取非公开数据登录态下能看到的数据不等于你有权批量采集。采集后的数据如涉及个人信息昵称、头像、购买记录脱敏处理后再用。注意各大电商平台对非授权采集都明确写在用户协议里。实操中规模小、频率低、非营利的学习型采集只要不冲垮对方服务一般不会引发法律纠纷。但这不构成法律建议项目做完建议尽快销毁原始数据保留分析结论即可。2. 评论区数据从哪来接口分析与请求链路拆解2.1 为什么不能直接解析商品页HTML很多人第一次上手习惯性地拿到商品详情页URL就开干requests.get然后BeautifulSoup去找评论标签。结果十有八九是一堆空的div啥也拿不到。原因很简单淘宝商品详情页的评论区是异步加载的。页面先返回框架HTML评论内容由JavaScript在页面渲染完成后通过XHRXMLHttpRequest异步请求评论接口拿数据再动态渲染进DOM。这种设计早期是为了优化首屏加载速度现在已经是电商平台的标准做法。直接请求详情页顶多拿到一个评论总览几条精选评论无法翻页拿全量数据。所以要爬评论核心工作就是找到那个真正放评论数据的异步接口。2.2 评论接口的参数结构解析打开Chrome开发者工具F12切到Network面板在商品详情页往下滚动翻几页评论就能看到评论相关的XHR请求。淘宝的评论接口主要分两类新版rateDetailQuery接口大部分客户端走这个。旧的rate/list接口部分Web端还在用。以rateDetailQuery为例请求URL长这样https://rate.taobao.com/feedRateList.htm?callbackxxxauctionNumId商品IDuserNumId卖家IDcurrentPageNum页码pageSize20rateType0orderTypesortTypeshowContent1attributeStrnull_ksTS时间戳_回调序号这里的关键参数拆开看参数名含义说明auctionNumId商品ID详情页URL里id后面的那串数字或者从页面源码里搜itemid拿userNumId卖家数字ID店铺维度的标识早期版本必带部分接口可以省currentPageNum当前页码从1开始递增pageSize每页条数固定20比较稳有些接口支持20~40rateType评论类型0代表全部1代表好评2代表中评3代表差评orderType排序方式sortType代表推荐排序default代表时间排序callbackJSONP回调淘宝老接口喜欢用JSONP跨域返回值被包在jsonp...(...)里很多初学者栽在callback和_ksTS这两个参数上以为必须动态生成实际上服务端根本不校验这两位的具体值。callback随便设一个字符串就行_ksTS填当前时间戳加一个自增数字即可纯粹是历史遗留的协议习惯。2.3 拿到商品ID的两种常用方式爬评论之前先得有商品ID这个ID是后面所有请求的主键拿错了一切白搭。方式一从URL直接提取。淘宝PC端商品详情页URL格式是https://item.taobao.com/item.htm?id6214xxxxxxid后面的就是auctionNumId。方式二代码搜索。如果商品链接被短链包装过或者来自App分享需要先请求详情页源码在源码里搜itemId:6214xxxxxx或auctionNumId字段来提取。实战里我习惯写一个函数统一处理兼容item.taobao.com、detail.tmall.com、m.tb.cn短链等多种格式用正则itemId[]?\s*[:]\s*[]?(\d{8,15})提取避免来回切工具。这个小工具函数会在后面的完整代码里一并给你。3. 环境准备与基础代码实现3.1 Python环境与依赖库的坑Python版本建议3.8及以上不要用2.7很多加密库的兼容性在2.7下会搞得人想摔键盘。依赖库上核心就四个pip install requests beautifulsoup4 lxml pandasrequests负责HTTP请求beautifulsoup4加lxml负责解析HTML数据当前项目里主要用正则和JSON解析BS4用来做兜底解析pandas用来最后整理数据。如果对可视化有需求再加一个pyecharts后续可以画差评关键词分布图。这里单独提醒一句不要用Scrapy框架起步。你说的这个“爬虫并发设计到底哪个好”如果放在这个项目里Scrapy的Twisted异步模型在你连接口还没摸清楚的时候只会加大调试成本。先用requests串行跑通全流程数据拿到了再考虑并发改造。Linux和Windows在环境上有几个典型区别需要注意Windows下lxml用pip直接装一般没事但Python 3.12以上偶尔遇到二进制包缺失可以去https://pypi.org/project/lxml/#files下对应系统的whl文件离线安装。Linux服务器上如果报ModuleNotFoundError: No module named requests先确认是不是系统自带的Python和pip版本不匹配建议用python3 -m pip install而不是直接pip install。国内网络环境装依赖慢的话配阿里源或清华源一条命令的事pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple。3.2 完整代码实现串行版评论采集器下面给出一个可直接运行的完整脚本。这个版本设计上优先保证逻辑清晰方便你理解每一步在干什么跑通之后再做并发改造。import re import time import json import random import requests import pandas as pd class TaobaoReviewCrawler: 淘宝商品评论采集器——学习用途版本 def __init__(self, item_id, cookie_str): self.item_id item_id self.cookie_str cookie_str self.session requests.Session() self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: fhttps://item.taobao.com/item.htm?id{item_id}, Accept: application/json, text/javascript, */*; q0.01, Accept-Language: zh-CN,zh;q0.9, }) # 有Cookie就带上没有就匿名爬部分商品需要登录才能看全部评论 if cookie_str: self.session.headers[Cookie] cookie_str self.rate_url ( https://rate.taobao.com/feedRateList.htm ?callbackjsonp_reviews_list auctionNumId{item_id} userNumId0 currentPageNum{page} pageSize20 rateType{rate_type} orderTypesortType showContent1 attributeStrnull _ksTS{ts}_{seq} ) def get_one_page(self, page1, rate_type0, retry3): 获取单页评论数据带简单重试机制 ts int(time.time() * 1000) url self.rate_url.format( item_idself.item_id, pagepage, rate_typerate_type, tsts, seqts random.randint(100, 999) ) for attempt in range(retry): try: resp self.session.get(url, timeout10) resp.raise_for_status() # 去掉JSONP包装返回的是 jsonp_reviews_list(...) 格式 text resp.text.strip() match re.search(r^[^(]*\((.*)\)[;]*$, text, re.S) if not match: return [] data json.loads(match.group(1)) # 兼容返回结构真正的评论列表在 data.tcomList 或 data.rateList 里 if isinstance(data, dict): tcom_list data.get(tcomList) or data.get(rateList) or [] else: tcom_list data return tcom_list except Exception as e: print(f[第{page}页] 请求异常: {e}, 重试 {attempt 1}/{retry}) time.sleep(random.uniform(2, 4)) return [] def parse_review(self, item): 从单条评论数据中提取关键字段 rate_content item.get(rateContent, ) or item.get(content, ) # 评价标签里的颜色分类 sku_info item.get(skuInfo, {}) if isinstance(sku_info, dict): # 形如 {颜色:黑色,尺码:L} 的映射 color sku_info.get(颜色, ) size sku_info.get(尺码, ) or sku_info.get(尺寸, ) else: color size str(sku_info or ) # 时间戳转日期 ts item.get(rateDate, 0) rate_date time.strftime(%Y-%m-%d %H:%M:%S, time.localtime(ts)) if ts else return { rate_id: item.get(rateId, ), user_name: item.get(displayUserNick, ), rate_content: rate_content, rate_date: rate_date, color: color, size: size, rate_type: item.get(rateType, ), reply: item.get(reply, ) or , append_content: item.get(appendComment, {}).get(content, ) if isinstance(item.get(appendComment), dict) else } def crawl(self, max_pages20, rate_type0, sleep_range(1, 3)): 抓取主入口max_pages控制最大页码rate_type控制评论类型 all_reviews [] for page in range(1, max_pages 1): tcom_list self.get_one_page(pagepage, rate_typerate_type) if not tcom_list: print(f[翻页] 第{page}页没有数据提前结束) break parsed [self.parse_review(item) for item in tcom_list] all_reviews.extend(parsed) print(f[抓取] 第{page}页完成提取{len(parsed)}条累计{len(all_reviews)}条) # 随机睡眠模拟人工浏览节奏 time.sleep(random.uniform(*sleep_range)) return all_reviews if __name__ __main__: # 替换成你的目标商品IDCookie按需填写建议填上风控概率低很多 ITEM_ID 6214xxxxxxxxxx COOKIE 你的登录Cookie # 留空字符串则匿名访问 crawler TaobaoReviewCrawler(ITEM_ID, COOKIE) reviews crawler.crawl(max_pages30, rate_type0, sleep_range(2, 4)) df pd.DataFrame(reviews) print(df.head()) df.to_csv(taobao_reviews.csv, indexFalse, encodingutf-8-sig) print(f采集完成共{len(df)}条已保存至 taobao_reviews.csv)这段代码刻意的点有两个re.search(r^[^(]*\((.*)\)[;]*$, text, re.S)这一行是JSONP解包的关键。淘宝返回的是jsonp_reviews_list({...})必须把函数名剥掉再json.loads。很多人卡在这一步不知道返回值是带壳的其实花一分钟想一下接口URL里那个callback参数的作用就明白了。rate_type这个参数在代码里我们可以看到rate_type0代表全部评论、1好评、2中评、3差评。实操的时候我建议先跑全量跑完后用Pandas按rate_type字段分层抽样这样后续做情感分析时正负样本比更健康。3.3 工程化改造失败重试与断点续爬测试阶段这种线性脚本够用但一旦页码多了网络抖动、接口限流、本机断网都会导致爬到一半挂掉。重新来过非常伤尤其是已经爬到几百页的时候。所以真实项目里我始终保留一个本地进度存档机制每一页抓完就把当前页码写进progress.txt下次启动先读进度从断点继续。import os def save_progress(page): with open(progress.txt, w, encodingutf-8) as f: f.write(str(page)) def load_progress(): if os.path.exists(progress.txt): with open(progress.txt, r, encodingutf-8) as f: return int(f.read().strip()) return 1配合上面的类主函数里加一个start_page load_progress()循环从start_page开始每页成功后save_progress(page)。别小看这个改动它能让你的爬虫扛住多次中断而不用重头再来这在长时间采集中属于保命操作。另外重要的响应内容建议做磁盘缓存。如果遇到反爬把你封了你有之前缓存下来的原始JSON还能用离线数据继续做解析和测试不用反复请求线上接口触发更严厉的风控。4. 反爬应对策略与实战心得4.1 登录Cookie的正确获取姿势淘宝的评论接口对游客也开放一部分数据但存在两个限制翻页上限更严格、部分高质量评论带图长评不返回。我建议你登录后操作Cookie获取方式说清楚用Chrome打开淘宝登录你的账号。按F12打开开发者工具切到Network面板。在商品页刷新一次随便点一条XHR请求。在请求头里找到Cookie:字段右键Copy value整段粘贴进代码里的cookie_str参数。这里有一个很多教程不讲的细节Cookie里的_m_h5_tk和_m_h5_tk_enc这两个字段非常关键。淘宝很多接口的签名算法依赖_m_h5_tk的值评论接口虽然不强制校验签名但你带着这俩字段去请求风控系统会把你当“正常登录用户”看待比裸奔的匿名爬虫友好得多。如果发现明明登录了但评论接口还是提示需要登录多半是Cookie里的cookie2和_tb_token_过期了重新按上面步骤复制一份就行。这种失效频率大概是两到三天一次不用焦虑属于正常现象。4.2 被反爬拦截时的典型特征与响应策略拿到一个接口先看返回特征判断是否被拦截。最常见的三种情况返回数据里没有tcomList而是出现error:true或msg:登录之类的字段说明接口要登录态。HTTP状态码是302跳转到了login.taobao.com说明Session失效重新登录换Cookie。返回200但JSONP回调名变成了jsonp231这种带数字的而且数据里只有一页说明触发了限流需要降速。应对限流的核心原则是慢就是快。被限流后最少等15分钟再继续期间不要高频重试同一个接口。设置一个指数退避的延时策略更合理第一次失败等5秒第二次20秒第三次60秒还不行就停半小时。一个冷知识凌晨两三点到早上七点淘宝的评论接口风控阈值明显比白天高。如果你的爬虫数据量比较大把主力采集任务放在凌晨跑挂掉的概率会低不少。这个时间段本身用户请求少平台也相对“宽容”。4.3 并发设计从串行到线程池到底哪个好标题热词里有“爬虫并发设计到底哪个好”这问题放在这个项目里我的答案是先串行跑通再按需上并发。评论接口是IO密集型任务瓶颈在等待网络响应上所以线程池是性价比最高的方案。Python的concurrent.futures.ThreadPoolExecutor足够用不需要上asyncio那种复杂度。但如果追求更高吞吐可以考虑异步方案不过对应的错误处理和调试复杂度会明显上升。并发改造的关键有三条控制线程数量。淘宝这种大厂接口单IP并发超过5个基本就触发限流。我用ThreadPoolExecutor(max_workers4)做了并发实测稳定性和串行版几乎一样但速度能提升三倍。想更快就得搭配代理IP池这个下面单独讲。加全局限速器。就算开了线程池也要在请求函数里加一个threading.Semaphore或令牌桶确保单位时间内的请求总数不超过设定阈值。推荐做法每秒钟不超过2个请求等页面稳定后再匀速抓取。异常处理必须比串行版更严格。并发场景下如果某一个请求卡住超时整批任务都会被拖住。所以timeout10这个参数要保留配合as_completed逐个拿结果谁先返回先处理谁。4.4 关于IP代理的实践建议“python爬虫ip代理”也是高频搜索词。我自己做电商平台采集的经验是淘宝评论接口对IP的敏感度远低于登录和下单接口正常学习用途基本不需要代理IP。单个商品几千条评论的采集量控制频率完全可以跑完。真需要代理的话比如采集多个店铺、多天连续跑建议优先考虑requests库配合自建代理池不要买那种免费代理可用率太低反而浪费时间。市面上的付费代理按流量计费对小项目来说一个月几块钱就够用了。代理配置非常简单proxies { http: http://你的代理IP:端口, https: http://你的代理IP:端口, } session.proxies.update(proxies)注意选HTTPS代理因为淘宝接口走的是HTTPS明文加密链路不配HTTPS代理请求根本发不出去。5. 数据清洗与评论画像的初步分析5.1 评论正文里的“噪音”怎么处理从接口拿到的评论数据直接用还行但要做分析就有点糙。电商评论里普遍存在的问题有三个第一默认好评的假性内容。天猫的大部分订单如果不主动评价系统会在一段时间后自动评论“此用户没有填写评价”。这类数据对情感分析毫无价值必须过滤。判断逻辑很简单rate_content为空或等于“此用户没有填写评价”直接丢弃。第二追加评论和正文的关系。一个用户可能先给好评过了十几天追评说不耐烦。单纯看正文会忽略这个“情绪反转”信号。处理方式是把append_content拼进正文或者单独存一个字段做分析时分别建模看“追评情绪”和“初次评价情绪”的差值分布。第三表情符号和HTML实体。评论区允许发emoji和表情包JSON里会以Unicode转义或特殊符号出现直接读出来是乱码。清洗时用emoji库或正则把非文字字符替换成空格即可。另外item.get(skuInfo)里的颜色和尺码字段经常是JSON字符串嵌套比如{颜色:夜黑,尺码:XL}解析时要多一层json.loads处理。5.2 三行代码画出差评关键词词云采集只是手段分析才是目的。这里给一个快速出效果的方案用jieba分词加pyecharts词云图import jieba from collections import Counter from pyecharts.charts import WordCloud # 假设df是采集结果DataFrame取出差评列 negative df[df[rate_type].astype(str).isin([2, 3])][rate_content].tolist() # 分词并统计过滤单字和常用无意义词 words [w for text in negative for w in jieba.lcut(text) if len(w) 1] counter Counter(words).most_common(50) (WordCloud() .add(, counter, word_size_range[20, 100]) .render(negative_words.html))这一坨跑完你的差评高频词就直观了——比如“质量”“客服”“慢”“色差”“异味”这些实词会占大头运营看一眼就知道下一步该优化哪个环节。整个分析链路走通一个“爬虫数据可视化”的完整项目就闭环了。5.3 保存数据的格式选择CSV、JSON还是数据库小项目用CSV没问题但如果你的评论量奔着几十万条去CSV读写会越来越难受。我的建议是按阶段处理原始采集数据存JSON保留所有字段方便随时改解析逻辑重跑。一行一条JSON文件命名为raw_reviews_第几页.json。清洗后的结构化数据存CSV用utf-8-sig编码这样Excel打开不会乱码。如果后续要做增量采集建议把数据扔进SQLite或MySQL用rate_id做唯一索引做去重避免重复数据污染。SQLite自带轻量、单文件、免安装适合个人项目练手。建表语句不复杂核心就一个去重逻辑CREATE TABLE IF NOT EXISTS reviews ( rate_id INTEGER PRIMARY KEY, item_id INTEGER, user_name TEXT, rate_content TEXT, rate_date TEXT, color TEXT, size TEXT, rate_type TEXT, append_content TEXT );采集时遇到相同的rate_id执行INSERT OR IGNORE天然去重。6. 常见问题与排查技巧实录6.1 问题速查表问题现象可能原因解决方法返回200但数据为空请求被降级返回了兜底空壳检查Cookie是否有效降低频率重试请求报HTTP 413请求头过大多为Cookie过长精简Cookie只保留必要字段报requests.exceptions.SSLError本地SSL证书问题加verifyFalse并用urllib3.disable_warnings()关闭警告评论只有第一页能拿翻页参数格式不对检查currentPageNum是否真的传进URL解析JSON时AttributeError返回结构不是预期dict先在控制台print(resp.text[:500])看原始结构采集过程中被重定向到登录页登录态失效重新获取Cookie更新到代码里追加评论字段总是空接口未返回appendComment字段检查showContent1参数是否传了部分接口需要额外传appendComment1长时间运行后速度明显变慢触发了IP软限流停止30分钟或换代理IP6.2 排查响应结构的标准化流程遇到解析不了的数据不要急着改代码猜结构。两条命令快速定位# 把原始响应保存到文件看前500个字符 python -c import requests; rrequests.get(评论接口URL, headers{User-Agent:Mozilla/5.0}); print(r.text[:500])从[开始就是正常数据从{error开始就是风控返回从html开始说明被重定向到了登录页或验证页。这一眼流程能帮你省掉至少一半的排查时间。如果返回的JSON结构很复杂直接在Python交互式环境里json.loads后一层层看keys()比你对着长串JSON瞎猜要快得多。6.3 字体反爬问题淘宝评论也存在吗评论区确实存在部分字符经过字体反爬处理的情况比如一些数字和敏感词会用自定义字体渲染直接抓下来的内容是乱码或特殊字形。不过相比大众点评、天眼查这类“全站字体加密”的重度选手淘宝评论的反爬字体只覆盖很小一部分内容绝大多数正文字符都能直接拿到所以不用为这个问题过度焦虑。万一真遇到了解决方案是“字体映射表破解”——下载页面引用的woff字体文件用fontTools库解析出每个glyph对应的Unicode和实际汉字的映射关系再做替换。这个技术单独写能水一篇几千字的文章这里点到为止真遇到再单独搜“字体反爬 破解woff 实战”相关专题研究。7. 项目扩展方向与个人经验收尾我个人做爬虫项目的一个心得单点爬虫的价值是有限的真正有价值的是把数据连成网络。比如你爬一个商品的评论只能看到“这个商品有什么问题”。但如果你批量爬取同一品类下二三十个竞品的评论然后横向对比“哪个牌子质量差评率最低”“哪个关键词被吐槽最多”那你拿到的就不是数据是商业洞察。这也是为什么爬虫岗位总是要求懂业务场景——技术永远是为决策服务的。这个淘宝评论采集器后续值得扩展的方向可以往这几个角度去钻定时增量采集用APScheduler做定时任务每天凌晨跑一次新增评论构建评论时间序列观察差评率随商品迭代的变化趋势。情感分析建模把评论按1~5星打分映射成训练标签用BERT或TextCNN做情感二分类准确率基本能到85%以上。同品类商品对比榜单批量获取同类目下竞品的评论数据用关键词聚类和情感分生成竞品分析报告这已经是半商业化的产品了。评论关键词预警监控“漏发”“破损”“假货”“太差”等负面词一旦出现频率异常升高就报警很多品牌方愿意为这种服务买单。最后分享一个踩过很多次的坑采集速度永远不是第一目标稳定和克制才是。爬虫这条路谁都能把数据抓下来区别在于谁能不被封、谁能优雅地处理异常、谁能把数据变成有用的东西。控制好你代码里的time.sleep数值理解每一个请求背后的协议逻辑比盲目追求高并发有价值得多。如果你在跑这个项目的过程中遇到代码层面的报错把报错信息和当时返回的原始响应贴出来记得脱敏多调试几轮很快就能把这块的经验积累下来。
返回列表