×

淘宝商品评论接口实战解析:从抓包到数据抓取全链路技术指南

admin admin 发表于2026-02-13 12:34:25 浏览14 评论0

抢沙发发表评论

一、前言

淘宝商品评论数据是电商数据分析的"金矿"——用户真实反馈、产品痛点、竞品对比都隐藏其中。但淘宝的反爬机制堪称国内电商平台最严密之一,sign签名动态加密滑块验证行为风控层层设防。
本文基于2024-2025年最新技术实践,详解两种主流抓取方案:PC端网页解析(适合小规模)与移动端接口逆向(适合大规模),并提供完整的Python工程代码与反爬对抗策略。

二、技术方案选型

表格
复制
方案适用场景技术难度稳定性数据完整性
PC端网页解析小规模测试/临时抓取⭐⭐中(易触发验证码)中等(需渲染JS)
移动端API逆向大规模生产环境⭐⭐⭐⭐高(需维护签名算法)高(纯JSON数据)
RPA自动化超小规模/精准抓取⭐⭐⭐低(依赖页面结构)完整但效率低
推荐选择:生产环境务必采用移动端接口逆向方案,直接调用淘宝内部API,绕过前端渲染,效率提升10倍以上。

三、方案一:PC端网页解析(入门向)

3.1 接口定位与抓包

打开Chrome开发者工具(F12),进入商品详情页,滚动到评论区:
  1. 筛选请求:Network面板筛选XHR/Fetch,关键词过滤rate/comment/list
  2. 定位接口:典型URL格式:
    plain
    复制
    https://rate.taobao.com/feedRateList.do?itemId=123456789&sellerId=987654321&currentPage=1&pageSize=20&_t=1689200000000
  3. 关键参数识别
    • itemId:商品ID(URL中提取)
    • sellerId:卖家ID(页面源码中查找)
    • currentPage:分页页码
    • _t:时间戳(防缓存)
    • sign动态签名(核心加密参数)

3.2 基础抓取代码

Python
复制
import requestsimport reimport timeimport randomfrom fake_useragent import UserAgentclass TaobaoWebScraper:
    def __init__(self):
        self.ua = UserAgent()
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": self.ua.random,
            "Referer": "https://item.taobao.com/",
            "Accept": "application/json, text/javascript, */*; q=0.01",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "X-Requested-With": "XMLHttpRequest"
        })
    
    def extract_item_id(self, product_url):
        """从商品URL提取itemId"""
        patterns = [
            r"item\.taobao\.com/item\.htm\?.*id=(\d+)",
            r"detail\.tmall\.com/item\.htm\?.*id=(\d+)",
        ]
        for pattern in patterns:
            match = re.search(pattern, product_url)
            if match:
                return match.group(1)
        raise ValueError("无法提取商品ID")
    
    def fetch_comments(self, item_id, page=1):
        """获取单页评论"""
        api_url = "https://rate.taobao.com/feedRateList.do"
        
        params = {
            "itemId": item_id,
            "currentPage": page,
            "pageSize": 20,
            "_t": int(time.time() * 1000),  # 动态时间戳
            "callback": f"jsonp_{int(time.time())}"  # JSONP回调
        }
        
        try:
            response = self.session.get(api_url, params=params, timeout=10)
            response.raise_for_status()
            
            # 解析JSONP格式:jsonp_xxx({...})
            json_str = re.search(r'jsonp_\d+\((.*)\)', response.text)
            if json_str:
                import json
                data = json.loads(json_str.group(1))
                return self._parse_comments(data)
            return None
            
        except Exception as e:
            print(f"请求失败: {e}")
            return None
    
    def _parse_comments(self, data):
        """解析评论数据结构"""
        if data.get("ret", [""])[0] != "SUCCESS::调用成功":
            return None
        
        comments = []
        for item in data.get("data", {}).get("comments", []):
            comments.append({
                "user": item.get("userNick", ""),
                "content": self._clean_text(item.get("content", "")),
                "date": item.get("commentTime", ""),
                "star": item.get("star", 0),
                "useful": item.get("useful", 0),
                "images": item.get("images", [])
            })
        return comments    
    def _clean_text(self, text):
        """清洗文本(去除HTML标签和表情)"""
        text = re.sub(r'<[^>]+>', '', text)
        text = re.sub(r'[\U00010000-\U0010ffff]', '', text, flags=re.UNICODE)
        return text.strip()# 使用示例if __name__ == "__main__":
    scraper = TaobaoWebScraper()
    item_id = scraper.extract_item_id("https://item.taobao.com/item.htm?id=123456789")
    
    for page in range(1, 4):
        comments = scraper.fetch_comments(item_id, page)
        if comments:
            print(f"第{page}页获取{len(comments)}条评论")
        time.sleep(random.uniform(2, 5))  # 随机延迟防封

3.3 PC端反爬对抗

淘宝PC端风控特征:
  • Cookie验证:需携带有效的cna/tracknick等Cookie
  • Referer校验:必须来自商品详情页
  • 请求频率:单IP每秒>1次即可能触发滑块
应对策略
  1. 使用requests.Session()维持Cookie会话
  2. 插入随机延迟(2-5秒/请求)
  3. 配合代理IP池轮换(推荐付费代理如站大爷、阿布云)

四、方案二:移动端API逆向(生产级)

这是业界主流方案,通过抓包淘宝APP获取内部API,数据为纯净JSON,无需渲染,且反爬阈值更高。

4.1 抓包环境搭建

工具链
  • Charles/Fiddler:HTTPS抓包(需安装系统根证书)
  • Jadx/GDA:APK反编译分析加密逻辑
  • Frida:动态调试Hook加密函数(进阶)
抓包步骤
  1. 手机配置Charles代理(同WiFi下)
  2. 淘宝APP浏览商品评论
  3. Charles中筛选mtop.taobao.detailrate相关请求
  4. 关键发现:淘宝使用MTOP协议(阿里巴巴移动端统一网关)

4.2 MTOP协议核心机制

移动端接口典型特征:
http
复制
GET https://h5api.m.taobao.com/h5/mtop.taobao.rate.detaillist.get/6.0/
Headers:
  x-m-appkey: 12574478
  x-m-timestamp: 1700000000000
  x-m-sign: xxxxxxxxxxxxxxxx  ← 核心加密参数
  x-m-c-trace-id: xxx
  Cookie: _m_h5_tk=xxxxx; _m_h5_tk_enc=xxxxx
签名生成逻辑(通过Jadx逆向得出):
java
复制
// 伪代码:淘宝APP内的签名算法String sign = MD5(
    _m_h5_tk.split("_")[0] +  // Cookie中的token前半段
    "&" + 
    timestamp + 
    "&" + 
    appKey + 
    "&" + 
    requestData  // 业务参数JSON字符串);

4.3 完整逆向实现代码

Python
复制
import requestsimport hashlibimport timeimport jsonimport refrom urllib.parse import quoteclass TaobaoMobileAPI:
    def __init__(self):
        self.app_key = "12574478"  # 淘宝H5固定appKey
        self.api_base = "https://h5api.m.taobao.com/h5"
        self.session = requests.Session()
        
        # 初始化时需获取_m_h5_tk(通过首页请求或扫码登录)
        self.m_h5_tk = None
        self._init_token()
    
    def _init_token(self):
        """初始化获取_m_h5_tk(简化版:通过访问首页获取)"""
        # 实际生产环境需模拟登录或使用扫码获取有效Cookie
        headers = {
            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15",
            "Referer": "https://h5.m.taobao.com/"
        }
        # 此处应实现登录逻辑,示例使用预设Cookie
        self.session.headers.update(headers)
        self.session.cookies.update({
            "_m_h5_tk": "your_token_here_xxx",
            "_m_h5_tk_enc": "your_enc_token_here"
        })
        self.m_h5_tk = self.session.cookies.get("_m_h5_tk", "")
    
    def _generate_sign(self, api_name, data, t=None):
        """
        生成MTOP签名(核心算法)
        算法:md5(token&t&appKey&data)
        """
        if not t:
            t = int(time.time() * 1000)
        
        # 提取token前半段(_m_h5_tk格式:token_timestamp)
        token = self.m_h5_tk.split("_")[0] if self.m_h5_tk else ""
        
        # 拼接待加密字符串
        params_str = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
        sign_str = f"{token}&{t}&{self.app_key}&{params_str}"
        
        # MD5加密
        sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
        return sign, t, params_str    
    def fetch_comments(self, item_id, page=1, page_size=20):
        """调用MTOP接口获取评论"""
        api_name = "mtop.taobao.rate.detaillist.get"
        api_version = "6.0"
        
        # 业务参数
        data = {
            "itemId": str(item_id),
            "pageNum": page,
            "pageSize": page_size,
            "sort": "default",  # default/time/score
            "hasPic": False,
            "fold": 0
        }
        
        # 生成签名
        sign, t, data_str = self._generate_sign(api_name, data)
        
        # 构造请求
        url = f"{self.api_base}/{api_name}/{api_version}/"
        params = {
            "jsv": "2.7.2",
            "appKey": self.app_key,
            "t": t,
            "sign": sign,
            "api": api_name,
            "v": api_version,
            "type": "jsonp",
            "dataType": "jsonp",
            "callback": f"mtopjsonp{int(time.time())}",
            "data": data_str        }
        
        try:
            response = self.session.get(url, params=params, timeout=15)
            
            # 解析JSONP
            json_match = re.search(r'mtopjsonp\d+\((.*)\)', response.text)
            if not json_match:
                # 检查是否触发反爬
                if "FAIL_SYS_TOKEN_EXOIRED" in response.text:
                    print("Token过期,需重新获取")
                    self._init_token()
                    return None
                return None
            
            result = json.loads(json_match.group(1))
            
            if result.get("ret", [""])[0] == "SUCCESS::调用成功":
                return result.get("data", {}).get("comments", [])
            else:
                print(f"API错误: {result.get('ret')}")
                return None
                
        except Exception as e:
            print(f"请求异常: {e}")
            return None
    
    def crawl_all_comments(self, item_id, max_pages=10):
        """全量抓取评论"""
        all_comments = []
        
        for page in range(1, max_pages + 1):
            print(f"抓取第 {page}/{max_pages} 页...")
            comments = self.fetch_comments(item_id, page)
            
            if not comments:
                break
            
            all_comments.extend(comments)
            
            # 检查是否最后一页
            if len(comments) < 20:
                break
            
            # 随机延迟(移动端阈值较高,但仍需控制频率)
            time.sleep(random.uniform(0.5, 1.5))
        
        return all_comments# 生产环境建议:封装为服务并添加代理池class ProductionCrawler(TaobaoMobileAPI):
    def __init__(self, proxy_pool=None):
        super().__init__()
        self.proxy_pool = proxy_pool or []
        self.current_proxy = None
    
    def _get_proxy(self):
        """轮询获取代理"""
        if not self.proxy_pool:
            return None
        self.current_proxy = random.choice(self.proxy_pool)
        return {
            "http": f"http://{self.current_proxy}",
            "https": f"http://{self.current_proxy}"
        }
    
    def fetch_comments(self, item_id, page=1, page_size=20):
        """带代理和重试的请求"""
        max_retries = 3
        for i in range(max_retries):
            try:
                proxy = self._get_proxy()
                # 在父类请求基础上添加代理
                # ...(代码省略,实际需重写请求逻辑添加proxies参数)
                return super().fetch_comments(item_id, page, page_size)
            except Exception as e:
                if i == max_retries - 1:
                    raise
                time.sleep(2 ** i)  # 指数退避

4.4 关键逆向技巧

1. Token维护机制
  • _m_h5_tk有效期约24小时,需定时刷新
  • 通过访问https://h5.m.taobao.com/首页自动更新Cookie
2. 签名算法追踪
  • 若淘宝升级加密(如改为HMAC-SHA256),需重新反编译APK
  • 使用Frida Hook com.taobao.wireless.security.adapter.JNICLibrary.doCommandNative 函数动态获取签名
3. 设备指纹模拟
Python
复制
# 模拟淘宝APP设备指纹headers = {
    "x-umidtoken": "Txxx_xxx",  # 通过算法生成或从真机提取
    "x-wap-profile": "https://img.taobao.com/...",
    "x-m-biz-code": "taobao",
    "x-m-biz-data": "..."  # 设备环境加密数据}

五、反爬对抗实战策略

5.1 风控触发场景与应对

表格
复制
风控等级触发特征应对策略
初级要求滑动验证码接入打码平台(如超级鹰)或使用Selenium过滑块
中级返回"访问频繁"或空数据切换代理IP + 增加请求间隔(>5秒)
高级账号封禁/设备拉黑更换设备指纹 + 使用新账号 + 降低频率

5.2 代理IP池架构

Python
复制
import redisfrom concurrent.futures import ThreadPoolExecutorclass ProxyManager:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
        self.proxy_key = "taobao:proxies"
    
    def validate_proxy(self, proxy):
        """验证代理可用性(访问淘宝测试接口)"""
        test_url = "https://h5api.m.taobao.com/h5/mtop.taobao.wireless.home.load/1.0/"
        try:
            resp = requests.get(test_url, proxy={"https": proxy}, timeout=5)
            return resp.status_code == 200
        except:
            return False
    
    def get_valid_proxy(self):
        """从Redis获取可用代理"""
        proxy = self.redis_client.srandmember(self.proxy_key)
        return proxy.decode() if proxy else None
    
    def feedback_proxy(self, proxy, success):
        """代理质量反馈(失败次数过多则移除)"""
        if not success:
            self.redis_client.hincrby("taobao:proxy_failures", proxy, 1)
            if self.redis_client.hget("taobao:proxy_failures", proxy) > b"5":
                self.redis_client.srem(self.proxy_key, proxy)

5.3 数据存储与监控

Python
复制
from sqlalchemy import create_engine, Column, String, Integer, Text, DateTimefrom sqlalchemy.orm import sessionmakerimport pandas as pdclass CommentStorage:
    def __init__(self, db_url="sqlite:///taobao_comments.db"):
        self.engine = create_engine(db_url)
        self.Session = sessionmaker(bind=self.engine)
        self._init_tables()
    
    def save_comments(self, item_id, comments):
        """批量保存评论"""
        session = self.Session()
        try:
            for c in comments:
                session.add(CommentModel(
                    item_id=item_id,
                    user_nick=c['user'],
                    content=c['content'],
                    star=c['star'],
                    comment_time=c['date'],
                    raw_data=json.dumps(c)
                ))
            session.commit()
        except Exception as e:
            session.rollback()
            raise e        finally:
            session.close()
    
    def export_to_excel(self, item_id, filepath):
        """导出分析报表"""
        session = self.Session()
        comments = session.query(CommentModel).filter_by(item_id=item_id).all()
        df = pd.DataFrame([c.to_dict() for c in comments])
        
        # 简单情感分析
        df['sentiment'] = df['content'].apply(self._simple_sentiment)
        df.to_excel(filepath, index=False)
    
    def _simple_sentiment(self, text):
        """基于关键词的简单情感判断"""
        positive_words = ['好评', '满意', '喜欢', '推荐', '不错']
        negative_words = ['差评', '失望', '垃圾', '假货', '退货']
        score = sum(1 for w in positive_words if w in text) - \                sum(1 for w in negative_words if w in text)
        return "positive" if score > 0 else "negative" if score < 0 else "neutral"

六、合规与法律边界

⚠️ 重要声明:以下行为可能违反《淘宝平台服务协议》及《网络安全法》:
  1. 禁止行为
    • 破解淘宝加密算法用于商业竞品分析
    • 高频抓取导致淘宝服务器负载异常
    • 抓取用户隐私信息(如匿名买家真实身份)
    • 将数据用于刷单、炒信等违规场景
  2. 合规建议
    • 仅采集公开可见的评论内容
    • 请求频率控制在单IP每秒<1次,日请求<1000次
    • 数据仅用于个人学习、学术研究或内部产品优化
    • 考虑接入淘宝官方开放平台API(需申请权限)

七、总结与进阶

本文详解了淘宝评论抓取的两大技术路径:
  • PC端方案:适合快速验证,但受限于反爬,仅适合小规模
  • 移动端逆向:生产环境首选,需持续维护签名算法,配合代理池可实现日采10万+评论
进阶方向
  1. 实时采集:基于WebSocket监听淘宝直播评论(需逆向直播API)
  2. 图文分析:使用OCR提取评论图片中的文字(如快递单、产品瑕疵图)
  3. 知识图谱:构建用户-商品-评论关系图,挖掘水军账号

如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。

群贤毕至

访客