从 API 文档到运行:淘宝商品评价实时数据采集开发实录

3 阅读8分钟

在电商数据分析领域,商品评价数据是了解用户需求、优化产品体验的重要依据。本文将记录笔者开发淘宝商品评价实时采集系统的全过程,从 API 文档分析到完整代码实现,为开发者提供可参考的实战经验。

1. API 文档分析与需求理解

淘宝平台提供了多个与商品评价相关的 API 接口,经过详细比对,最终选择接口作为数据采集入口。该接口的主要特点:

  • 功能:获取淘宝客商品的评价列表

  • 请求方式:HTTP POST

  • 参数要求

    • num_iid:商品 ID(必传)
    • fields:返回字段列表(必传)
    • page_no:页码
    • page_size:每页数量(最大 40)
  • 返回格式:JSON

  • 权限要求:需申请商品评价读取权限

  • 调用频率限制:10 次 / 秒(根据应用等级可能不同)

2. 开发环境搭建

技术栈选择

  • 编程语言:Python 3.9
  • 主要依赖库:
requests==2.28.1      # HTTP请求
pandas==1.5.3         # 数据处理
pymysql==1.0.3        # MySQL连接
python-dotenv==1.0.0  # 环境变量管理
apscheduler==3.10.1   # 定时任务

 项目结构

taobao-comment-crawler/
├── config/
│   └── .env            # 配置文件
├── utils/
│   ├── api_client.py   # API调用工具
│   └── db_utils.py     # 数据库工具
├── models/
│   └── comment.py      # 数据模型
├── tasks/
│   └── comment_task.py # 定时任务
└── main.py             # 入口文件

 

3. 认证授权实现

淘宝 API 采用 OAuth2.0 授权码模式,需获取 Access Token 才能调用接口:

# utils/api_client.py
import os
import requests
import json
import time
from urllib.parse import urlencode
from dotenv import load_dotenv

load_dotenv()

class TaobaoClient:
    def __init__(self):
        self.app_key = os.getenv("APP_KEY")
        self.app_secret = os.getenv("APP_SECRET")
        self.redirect_uri = os.getenv("REDIRECT_URI")
        self.token_file = os.getenv("TOKEN_FILE", "taobao_token.json")
        self.api_url = "https://eco.taobao.com/router/rest"
        
    def get_authorization_url(self):
        """生成授权URL"""
        params = {
            "client_id": self.app_key,
            "redirect_uri": self.redirect_uri,
            "response_type": "code",
            "state": "init"
        }
        return f"https://oauth.taobao.com/authorize?{urlencode(params)}"
    
    def get_access_token(self, auth_code):
        """通过授权码获取Access Token"""
        url = "https://oauth.taobao.com/token"
        payload = {
            "grant_type": "authorization_code",
            "client_id": self.app_key,
            "client_secret": self.app_secret,
            "code": auth_code,
            "redirect_uri": self.redirect_uri
        }
        
        response = requests.post(url, data=payload)
        token_data = response.json()
        
        # 保存Token到文件
        with open(self.token_file, "w") as f:
            json.dump(token_data, f)
            
        return token_data
    
    def refresh_access_token(self):
        """刷新Access Token"""
        try:
            with open(self.token_file, "r") as f:
                token_data = json.load(f)
            
            url = "https://oauth.taobao.com/token"
            payload = {
                "grant_type": "refresh_token",
                "client_id": self.app_key,
                "client_secret": self.app_secret,
                "refresh_token": token_data["refresh_token"]
            }
            
            response = requests.post(url, data=payload)
            new_token_data = response.json()
            
            # 更新Token文件
            with open(self.token_file, "w") as f:
                json.dump(new_token_data, f)
                
            return new_token_data
        except Exception as e:
            print(f"刷新Token失败: {str(e)}")
            return None
    
    def get_current_token(self):
        """获取当前有效的Access Token"""
        try:
            with open(self.token_file, "r") as f:
                token_data = json.load(f)
            
            # 检查Token是否过期(提前10分钟刷新)
            if time.time() > token_data.get("expires_in", 0) - 600:
                return self.refresh_access_token()
                
            return token_data
        except (FileNotFoundError, json.JSONDecodeError):
            print("请先完成授权流程获取Access Token")
            return None

 

4. API 请求签名实现

淘宝 API 要求所有请求参数必须经过 MD5 签名:

# utils/api_client.py (续)
import hashlib

class TaobaoClient:
    # ... 已有代码 ...
    
    def generate_sign(self, params):
        """生成API请求签名"""
        # 按参数名排序
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        
        # 拼接参数名和值
        string_to_sign = self.app_secret
        for key, value in sorted_params:
            string_to_sign += f"{key}{value}"
        
        string_to_sign += self.app_secret
        
        # MD5加密
        signature = hashlib.md5(string_to_sign.encode("utf-8")).hexdigest().upper()
        return signature
    
    def call_api(self, method, params):
        """调用淘宝API"""
        token_data = self.get_current_token()
        if not token_data:
            return None
        
        # 构造公共参数
        common_params = {
            "method": method,
            "app_key": self.app_key,
            "session": token_data["access_token"],
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
            "format": "json",
            "v": "2.0",
            "sign_method": "md5"
        }
        
        # 合并参数
        all_params = {**common_params, **params}
        
        # 生成签名
        all_params["sign"] = self.generate_sign(all_params)
        
        # 发送请求
        response = requests.post(self.api_url, data=all_params)
        
        if response.status_code == 200:
            return response.json()
        else:
            print(f"请求失败: {response.status_code} - {response.text}")
            return None

 5. 商品评价采集实现

# tasks/comment_task.py
import time
from utils.api_client import TaobaoClient
from models.comment import CommentModel

class CommentCollector:
    def __init__(self):
        self.client = TaobaoClient()
        self.model = CommentModel()
        self.page_size = 20  # 每页20条评价
        
    def collect_comments(self, item_id, max_pages=10):
        """采集商品评价"""
        print(f"开始采集商品 {item_id} 的评价...")
        all_comments = []
        
        for page in range(1, max_pages + 1):
            print(f"正在采集第 {page}/{max_pages} 页评价...")
            
            # 调用API获取评价
            params = {
                "fields": "comment_id,item_id,content,rate,created,useful",
                "num_iid": item_id,
                "page_no": page,
                "page_size": self.page_size
            }
            
            result = self.client.call_api("taobao.taobaoke.items.comments.get", params)
            
            if not result:
                print("获取评价失败,跳过当前页")
                continue
            
            # 解析评价数据
            comments = self._parse_comments(result, item_id)
            if not comments:
                print("没有更多评价,结束采集")
                break
                
            all_comments.extend(comments)
            
            # 保存到数据库
            self.model.save_comments(comments)
            
            # 检查是否有下一页
            if len(comments) < self.page_size:
                print("已获取全部评价,结束采集")
                break
                
            # 控制采集频率,避免触发限流
            time.sleep(1)
        
        print(f"商品 {item_id} 评价采集完成,共获取 {len(all_comments)} 条评价")
        return all_comments
    
    def _parse_comments(self, api_response, item_id):
        """解析API返回的评价数据"""
        if not api_response:
            return []
            
        # 检查是否有错误
        if "error_response" in api_response:
            error = api_response["error_response"]
            print(f"API错误: {error.get('code')} - {error.get('msg')}")
            return []
            
        # 提取评价列表
        comments = api_response.get("taobaoke_items_comments_get_response", {}) \
                              .get("comments", {}) \
                              .get("n_tbk_item_comment", [])
                              
        parsed_comments = []
        for comment in comments:
            parsed = {
                "comment_id": comment.get("comment_id"),
                "item_id": item_id,  # 确保item_id正确
                "content": comment.get("content"),
                "rate": int(comment.get("rate", 5)),
                "created": comment.get("created"),
                "useful": int(comment.get("useful", 0)),
                "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S")
            }
            parsed_comments.append(parsed)
            
        return parsed_comments

 6. 数据模型与存储

# models/comment.py
import pymysql
import os
from dotenv import load_dotenv

load_dotenv()

class CommentModel:
    def __init__(self):
        self.db_config = {
            "host": os.getenv("DB_HOST", "localhost"),
            "user": os.getenv("DB_USER"),
            "password": os.getenv("DB_PASSWORD"),
            "database": os.getenv("DB_NAME"),
            "charset": "utf8mb4",
            "cursorclass": pymysql.cursors.DictCursor
        }
        
    def save_comments(self, comments):
        """保存评价数据到数据库"""
        if not comments:
            return
            
        try:
            with pymysql.connect(**self.db_config) as conn:
                with conn.cursor() as cursor:
                    # 插入评价数据
                    sql = """
                    INSERT INTO taobao_comments 
                    (comment_id, item_id, content, rate, created, useful, crawl_time)
                    VALUES (%s, %s, %s, %s, %s, %s, %s)
                    ON DUPLICATE KEY UPDATE 
                    content=VALUES(content), rate=VALUES(rate), 
                    useful=VALUES(useful), crawl_time=VALUES(crawl_time)
                    """
                    
                    data = [
                        (
                            comment["comment_id"],
                            comment["item_id"],
                            comment["content"],
                            comment["rate"],
                            comment["created"],
                            comment["useful"],
                            comment["crawl_time"]
                        )
                        for comment in comments
                    ]
                    
                    cursor.executemany(sql, data)
                    conn.commit()
                    
                    print(f"成功保存 {len(comments)} 条评价数据")
        except Exception as e:
            print(f"保存评价失败: {str(e)}")

 7. 定时任务配置

# main.py
from apscheduler.schedulers.blocking import BlockingScheduler
from tasks.comment_task import CommentCollector
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

def scheduled_comment_collection():
    """定时采集商品评价"""
    logger = logging.getLogger("CommentCollector")
    logger.info("开始定时采集商品评价...")
    
    collector = CommentCollector()
    
    # 从配置文件或数据库获取需要监控的商品列表
    items_to_monitor = [
        "687674347856",  # 示例商品ID,替换为实际商品ID
        "687674347857",
        "687674347858"
    ]
    
    # 遍历商品列表,采集评价
    for item_id in items_to_monitor:
        try:
            logger.info(f"开始采集商品 {item_id} 的评价...")
            collector.collect_comments(item_id, max_pages=5)
            # 商品间间隔,避免触发限流
            time.sleep(5)
        except Exception as e:
            logger.error(f"采集商品 {item_id} 评价失败: {str(e)}")
    
    logger.info("定时采集任务完成")

if __name__ == "__main__":
    # 设置定时任务(每天凌晨3点执行)
    scheduler = BlockingScheduler()
    scheduler.add_job(scheduled_comment_collection, 'cron', hour='3')
    
    print("定时任务已启动,按Ctrl+C退出")
    scheduler.start()

 8. 数据库设计

-- 创建数据库
CREATE DATABASE IF NOT EXISTS taobao_data DEFAULT CHARACTER SET utf8mb4;

-- 使用数据库
USE taobao_data;

-- 创建商品评价表
CREATE TABLE IF NOT EXISTS taobao_comments (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    comment_id VARCHAR(50) NOT NULL COMMENT '评价ID',
    item_id VARCHAR(50) NOT NULL COMMENT '商品ID',
    content TEXT COMMENT '评价内容',
    rate TINYINT DEFAULT 5 COMMENT '评分(1-5)',
    created DATETIME COMMENT '评价时间',
    useful INT DEFAULT 0 COMMENT '有用数',
    crawl_time DATETIME COMMENT '采集时间',
    UNIQUE KEY idx_comment_id (comment_id),
    INDEX idx_item_id (item_id),
    INDEX idx_created (created),
    INDEX idx_crawl_time (crawl_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='淘宝商品评价表';

 

9. 开发过程中的挑战与解决方案

  1. API 权限申请困难

    • 挑战:淘宝开放平台对商品评价 API 的权限审核严格
    • 解决方案:提交详细的应用使用说明,强调数据仅用于分析和研究
  2. 签名算法调试耗时

    • 挑战:API 签名错误导致请求失败
    • 解决方案:使用日志详细记录参数拼接过程,对比官方示例逐步排查
  3. 数据处理效率问题

    • 挑战:大量评价数据处理缓慢
    • 解决方案:使用executemany批量插入数据库,优化 SQL 语句
  4. API 限流频繁

    • 挑战:频繁请求导致 IP 被封禁
    • 解决方案:添加请求间隔控制,实现指数退避重试机制

10. 部署与运维

  1. 环境变量配置(.env 文件)
APP_KEY=你的AppKey
APP_SECRET=你的AppSecret
REDIRECT_URI=你的回调URL
TOKEN_FILE=taobao_token.json
DB_HOST=localhost
DB_USER=你的数据库用户名
DB_PASSWORD=你的数据库密码
DB_NAME=taobao_data

 2.部署步骤

# 克隆项目
git clone https://github.com/your-repo/taobao-comment-crawler.git
cd taobao-comment-crawler

# 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Windows使用: venv\Scripts\activate

# 安装依赖
pip install -r requirements.txt

# 配置环境变量
cp .env.example .env
# 编辑.env文件,填入实际配置信息

# 初始化数据库
mysql -u username -p < database/schema.sql

# 首次运行需要授权
python main.py --authorize

# 启动定时任务
python main.py

 

总结

通过本次开发实录,我们完成了从 API 文档分析到完整运行的淘宝商品评价实时采集系统。核心技术要点包括:

  1. OAuth2.0 授权流程实现
  2. API 请求签名算法
  3. 分页数据采集与处理
  4. 数据库设计与优化
  5. 定时任务调度

在实际应用中,还可以根据业务需求扩展更多功能,如情感分析、关键词提取、评价趋势图表等。通过合理的架构设计和性能优化,可以构建出稳定、高效的电商数据采集与分析平台。