在电商系统开发中,实时获取和同步商品信息是核心需求之一。本文将详细介绍如何使用 Node.js 接入淘宝 API,构建一个商品信息实时同步服务,帮助开发者快速实现与淘宝生态的数据对接。
前期准备
1. 开放账号配置
首先需要完成以下操作:
- 注册并完成认证
- 获取 Api Key 和 Api Secret
- 申请所需 API 的调用权限(如商品详情查询 api)
- 配置 IP 白名单(提高接口调用安全性)
2. 技术栈选择
- 核心语言:Node.js (v14+)
- 包管理:npm/yarn
- 主要依赖:
axios:处理 HTTP 请求crypto-js:实现签名算法dotenv:管理环境变量node-schedule:定时任务调度mongoose:MongoDB 数据库操作(可选)
实现步骤
1. 项目初始化
mkdir taobao-api-service
cd taobao-api-service
npm init -y
npm install axios crypto-js dotenv node-schedule mongoose
2. 配置环境变量
创建.env文件:
# 淘宝API配置
APP_KEY=你的AppKey
APP_SECRET=你的AppSecret
API_GATEWAY=https://eco.taobao.com/router/rest
# 数据库配置(可选)
MONGO_URI=mongodb://localhost:27017/taobao_products
# 定时任务配置(分钟)
SYNC_INTERVAL=30
3. 淘宝 API 签名工具实现
创建taobao-sign.js:
const CryptoJS = require('crypto-js');
/**
* 生成淘宝API签名
* @param {Object} params 请求参数
* @param {String} appSecret 应用密钥
* @returns {String} 签名结果
*/
function generateSign(params, appSecret) {
// 1. 按键名排序
const sortedParams = Object.keys(params)
.sort()
.reduce((obj, key) => {
obj[key] = params[key];
return obj;
}, {});
// 2. 拼接参数
let signStr = appSecret;
for (const key in sortedParams) {
if (sortedParams.hasOwnProperty(key) && sortedParams[key] !== '') {
signStr += `${key}${sortedParams[key]}`;
}
}
signStr += appSecret;
// 3. 计算MD5并转为大写
return CryptoJS.MD5(signStr).toString().toUpperCase();
}
module.exports = { generateSign };
4. 淘宝 API 客户端实现
创建taobao-client.js:
const axios = require('axios');
const { generateSign } = require('./taobao-sign');
require('dotenv').config();
const { APP_KEY, APP_SECRET, API_GATEWAY } = process.env;
/**
* 淘宝API客户端
*/
class TaobaoClient {
constructor() {
this.appKey = APP_KEY;
this.appSecret = APP_SECRET;
this.gateway = API_GATEWAY;
}
/**
* 通用API调用方法
* @param {String} method API方法名
* @param {Object} params 业务参数
* @returns {Promise<Object>} API返回结果
*/
async invoke(method, params = {}) {
// 公共参数
const publicParams = {
app_key: this.appKey,
method,
format: 'json',
v: '2.0',
timestamp: new Date().toISOString().replace('T', ' ').split('.')[0],
sign_method: 'md5'
};
// 合并参数
const allParams = { ...publicParams, ...params };
// 生成签名
allParams.sign = generateSign(allParams, this.appSecret);
try {
const response = await axios.post(this.gateway, null, { params: allParams });
if (response.data.error_response) {
throw new Error(`API错误: ${response.data.error_response.msg} (${response.data.error_response.code})`);
}
return response.data;
} catch (error) {
console.error(`API调用失败: ${error.message}`);
throw error;
}
}
/**
* 获取商品详情
* @param {String} numIid 商品ID
* @returns {Promise<Object>} 商品详情
*/
async getProductDetail(numIid) {
return this.invoke('taobao.item.get', {
num_iid: numIid,
fields: 'num_iid,title,price,promotion_price,stock,desc,pics'
});
}
/**
* 批量获取商品信息
* @param {Array<String>} numIids 商品ID列表
* @returns {Promise<Object>} 商品列表信息
*/
async getProductList(numIids) {
return this.invoke('taobao.items.list.get', {
num_iids: numIids.join(','),
fields: 'num_iid,title,price,promotion_price,stock'
});
}
}
module.exports = new TaobaoClient();
5. 数据存储模型(可选)
创建models/Product.js:
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
numIid: { type: String, required: true, unique: true },
title: String,
price: Number,
promotionPrice: Number,
stock: Number,
desc: String,
pics: [String],
updatedAt: { type: Date, default: Date.now }
});
// 更新时间自动维护
productSchema.pre('save', function(next) {
this.updatedAt = Date.now();
next();
});
module.exports = mongoose.model('Product', productSchema);
6. 同步服务实现
创建sync-service.js:
const taobaoClient = require('./taobao-client');
const Product = require('./models/Product');
const schedule = require('node-schedule');
require('dotenv').config();
const { SYNC_INTERVAL } = process.env;
/**
* 同步单个商品信息
* @param {String} numIid 商品ID
*/
async function syncProduct(numIid) {
try {
console.log(`开始同步商品: ${numIid}`);
const result = await taobaoClient.getProductDetail(numIid);
const productData = result.item_get_response.item;
// 转换数据格式
const productInfo = {
numIid: productData.num_iid,
title: productData.title,
price: parseFloat(productData.price),
promotionPrice: productData.promotion_price ? parseFloat(productData.promotion_price) : null,
stock: parseInt(productData.stock),
desc: productData.desc,
pics: productData.pics ? productData.pics.split(',') : []
};
// 保存到数据库
await Product.findOneAndUpdate(
{ numIid: productInfo.numIid },
productInfo,
{ upsert: true, new: true }
);
console.log(`商品同步成功: ${numIid}`);
return productInfo;
} catch (error) {
console.error(`商品同步失败 ${numIid}: ${error.message}`);
}
}
/**
* 批量同步商品
* @param {Array<String>} productIds 商品ID列表
*/
async function batchSyncProducts(productIds) {
console.log(`开始批量同步 ${productIds.length} 个商品`);
try {
// 分批处理,避免超过API限制
const batchSize = 20;
for (let i = 0; i < productIds.length; i += batchSize) {
const batch = productIds.slice(i, i + batchSize);
await Promise.all(batch.map(id => syncProduct(id)));
// 避免请求过于频繁
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log(`批量同步完成`);
} catch (error) {
console.error(`批量同步失败: ${error.message}`);
}
}
/**
* 启动定时同步任务
* @param {Array<String>} productIds 需要同步的商品ID列表
*/
function startSyncSchedule(productIds) {
console.log(`启动定时同步任务,间隔 ${SYNC_INTERVAL} 分钟`);
// 立即执行一次
batchSyncProducts(productIds);
// 设置定时任务
schedule.scheduleJob(`*/${SYNC_INTERVAL} * * * *`, () => {
console.log(`定时任务触发: ${new Date().toLocaleString()}`);
batchSyncProducts(productIds);
});
}
module.exports = {
syncProduct,
batchSyncProducts,
startSyncSchedule
};
7. 主程序入口
创建index.js:
const mongoose = require('mongoose');
const { startSyncSchedule } = require('./sync-service');
require('dotenv').config();
const { MONGO_URI } = process.env;
// 需要同步的商品ID列表
const PRODUCT_IDS = [
'123456789', // 替换为实际商品ID
'987654321',
// 可以添加更多商品ID
];
// 连接数据库
mongoose.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log('数据库连接成功');
// 启动同步服务
startSyncSchedule(PRODUCT_IDS);
})
.catch(err => {
console.error('数据库连接失败:', err);
process.exit(1);
});
// 处理未捕获的异常
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error);
});
服务运行与测试
-
确保 MongoDB 服务已启动(如果使用数据库存储)
-
替换
.env文件中的配置信息和index.js中的商品 ID 列表 -
启动服务:
node index.js
服务启动后,会立即执行一次商品同步,之后按照设定的时间间隔(默认 30 分钟)自动同步商品信息。
优化与扩展建议
- 错误重试机制:为 API 调用添加重试逻辑,提高稳定性
- 请求频率控制:实现更精细的限流策略,避免触发淘宝 API 的频率限制
- 日志系统:集成 winston 等日志库,完善日志记录
- 监控告警:添加健康检查和异常告警机制
- 缓存策略:对频繁访问的商品信息添加缓存,减少 API 调用次数
- 分布式部署:在高并发场景下,可考虑将服务拆分为生产者 - 消费者模式
注意事项
- 淘宝 API 有调用次数限制,需合理规划调用频率
- 签名算法需严格按照官方文档实现,否则会导致调用失败
- 敏感信息(如 App Secret)需妥善保管,避免泄露
- 生产环境中建议添加接口调用监控和告警机制
- 遵守淘宝开放平台的使用规范,避免违规操作
通过本文的实现,你可以快速搭建一个稳定可靠的淘宝商品信息同步服务,为电商系统提供实时的商品数据支持。根据实际业务需求,还可以扩展更多功能,如订单同步、评价获取等。