在电商数据分析、竞品监控和市场调研等场景中,商品数据采集是基础且关键的环节。本文将详细介绍如何构建一套京东商品数据采集服务,涵盖 API 接口调用、数据解析、存储落地及服务化封装的完整流程,并提供可复用的代码实现。
一、方案设计思路
京东商品数据采集服务需解决三个核心问题:数据获取渠道、数据处理机制和存储方案。本方案采用以下架构设计:
- 数据来源:通过京东 API 或第三方数据服务接口获取商品数据
- 处理层:实现数据清洗、格式转换和字段映射
- 存储层:采用 MySQL 存储结构化数据,Redis 缓存热门商品信息
- 服务层:封装为可调用的 API 服务,支持定时任务和手动触发
架构优势:兼顾数据实时性与存储效率,同时通过服务化设计提高复用性。
二、前置准备
2.1 开发环境
- 编程语言:Python 3.8+
- 依赖库:requests(HTTP 请求)、pymysql(MySQL 连接)、redis-py(Redis 操作)、schedule(定时任务)
- 数据库:MySQL 8.0、Redis 6.0
2.2 接口准备
京东需申请开发者账号获取api_key和api_secret。若使用第三方服务,需获取对应 API 密钥。
三、核心功能实现
3.1 API 接口调用模块
京东 API 调用需进行签名验证,以下是通用调用封装:
import requests
import time
import hashlib
import json
class JDAPI:
def __init__(self, app_key, app_secret, api_url="https://api.jd.com/routerjson"):
self.app_key = app_key
self.app_secret = app_secret
self.api_url = api_url
def _generate_sign(self, params):
"""生成签名"""
sorted_params = sorted(params.items(), key=lambda x: x[0])
sign_str = self.app_secret + ''.join([f"{k}{v}" for k, v in sorted_params]) + self.app_secret
return hashlib.md5(sign_str.encode()).hexdigest().upper()
def call(self, method, params=None):
"""调用京东API"""
if not params:
params = {}
# 公共参数
public_params = {
"app_key": self.app_key,
"method": method,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"format": "json",
"v": "2.0",
"sign_method": "md5"
}
# 合并参数
all_params = {**public_params,** params}
all_params["sign"] = self._generate_sign(all_params)
try:
response = requests.post(self.api_url, data=all_params, timeout=10)
result = json.loads(response.text)
if "error_response" in result:
print(f"API调用错误: {result['error_response']}")
return None
return result
except Exception as e:
print(f"请求异常: {str(e)}")
return None
def get_product_detail(self, sku_id):
"""获取商品详情"""
return self.call(
method="jd.union.open.goods.detail.query",
params={"skuId": sku_id}
)
def search_products(self, keyword, page=1, page_size=20):
"""搜索商品"""
return self.call(
method="jd.union.open.goods.search.query",
params={
"keyword": keyword,
"pageIndex": page,
"pageSize": page_size
}
)
3.2 数据处理模块
对 API 返回的原始数据进行清洗和转换,提取关键字段:
class DataProcessor:
@staticmethod
def process_product_data(raw_data):
"""处理商品详情数据"""
if not raw_data or "jd_union_open_goods_detail_query_response" not in raw_data:
return None
result = raw_data["jd_union_open_goods_detail_query_response"]["result"]
if not result:
return None
data = json.loads(result)["data"][0]
# 提取关键字段
processed = {
"sku_id": data.get("skuId"),
"name": data.get("name"),
"price": data.get("price"),
"original_price": data.get("originalPrice"),
"brand_name": data.get("brandName"),
"category": data.get("categoryInfo", {}).get("cateName"),
"shop_name": data.get("shopInfo", {}).get("shopName"),
"comment_count": data.get("commentCount"),
"good_rate": data.get("goodRate"),
"image_url": data.get("imageInfo", {}).get("imageList", [{}])[0].get("url"),
"create_time": time.strftime("%Y-%m-%d %H:%M:%S"),
"update_time": time.strftime("%Y-%m-%d %H:%M:%S")
}
return processed
@staticmethod
def process_search_data(raw_data):
"""处理搜索结果数据"""
if not raw_data or "jd_union_open_goods_search_query_response" not in raw_data:
return []
result = raw_data["jd_union_open_goods_search_query_response"]["result"]
if not result:
return []
data_list = json.loads(result)["data"]
return [DataProcessor.process_product_data({"jd_union_open_goods_detail_query_response": {"result": json.dumps({"data": [item]})}})
for item in data_list]
3.3 数据存储模块
实现 MySQL 存储和 Redis 缓存功能:
import pymysql
from redis import Redis
class DataStorage:
def __init__(self, mysql_config, redis_config):
# 初始化MySQL连接
self.mysql_conn = pymysql.connect(
host=mysql_config["host"],
user=mysql_config["user"],
password=mysql_config["password"],
database=mysql_config["db"],
charset="utf8mb4"
)
self.mysql_cursor = self.mysql_conn.cursor(pymysql.cursors.DictCursor)
# 初始化Redis连接
self.redis = Redis(
host=redis_config["host"],
port=redis_config["port"],
password=redis_config.get("password"),
db=redis_config.get("db", 0)
)
# 创建商品表
self._create_product_table()
def _create_product_table(self):
"""创建商品表"""
sql = """
CREATE TABLE IF NOT EXISTS jd_products (
id INT AUTO_INCREMENT PRIMARY KEY,
sku_id BIGINT NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2),
original_price DECIMAL(10,2),
brand_name VARCHAR(100),
category VARCHAR(100),
shop_name VARCHAR(100),
comment_count INT,
good_rate DECIMAL(5,2),
image_url VARCHAR(512),
create_time DATETIME,
update_time DATETIME,
INDEX idx_sku (sku_id),
INDEX idx_category (category)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
self.mysql_cursor.execute(sql)
self.mysql_conn.commit()
def save_product(self, product_data):
"""保存商品数据到MySQL"""
if not product_data or "sku_id" not in product_data:
return False
try:
# 先查询是否存在
self.mysql_cursor.execute("SELECT id FROM jd_products WHERE sku_id = %s", (product_data["sku_id"],))
if self.mysql_cursor.fetchone():
# 更新数据
update_fields = [f"{k} = %s" for k in product_data if k not in ["sku_id", "create_time"]]
sql = f"UPDATE jd_products SET {', '.join(update_fields)} WHERE sku_id = %s"
values = [product_data[k] for k in product_data if k not in ["sku_id", "create_time"]] + [product_data["sku_id"]]
else:
# 插入新数据
fields = ", ".join(product_data.keys())
placeholders = ", ".join(["%s"] * len(product_data))
sql = f"INSERT INTO jd_products ({fields}) VALUES ({placeholders})"
values = list(product_data.values())
self.mysql_cursor.execute(sql, values)
self.mysql_conn.commit()
# 缓存到Redis(过期时间2小时)
self.redis.setex(
f"jd_product:{product_data['sku_id']}",
7200,
json.dumps(product_data, ensure_ascii=False)
)
return True
except Exception as e:
self.mysql_conn.rollback()
print(f"存储失败: {str(e)}")
return False
def batch_save_products(self, products):
"""批量保存商品数据"""
success_count = 0
for product in products:
if self.save_product(product):
success_count += 1
return success_count
def close(self):
"""关闭连接"""
self.mysql_cursor.close()
self.mysql_conn.close()
self.redis.close()
3.4 服务封装与调度
将上述模块整合为完整服务,并支持定时任务:
import schedule
import time
class JDDataCollectionService:
def __init__(self, app_key, app_secret, mysql_config, redis_config):
self.api = JDAPI(app_key, app_secret)
self.processor = DataProcessor()
self.storage = DataStorage(mysql_config, redis_config)
def collect_product_by_sku(self, sku_id):
"""通过SKU采集商品数据"""
raw_data = self.api.get_product_detail(sku_id)
if not raw_data:
return False
product_data = self.processor.process_product_data(raw_data)
return self.storage.save_product(product_data)
def collect_products_by_keyword(self, keyword, pages=1):
"""通过关键词采集商品数据"""
total = 0
for page in range(1, pages + 1):
raw_data = self.api.search_products(keyword, page)
if not raw_data:
continue
products = self.processor.process_search_data(raw_data)
count = self.storage.batch_save_products(products)
total += count
print(f"第{page}页采集完成,成功保存{count}条数据")
time.sleep(2) # 避免请求过于频繁
return total
def schedule_collection(self, keyword, pages=1, interval=24):
"""定时采集任务"""
def job():
print(f"开始定时采集:{keyword}")
self.collect_products_by_keyword(keyword, pages)
print(f"定时采集完成:{keyword}")
# 每interval小时执行一次
schedule.every(interval).hours.do(job)
print(f"已设置定时任务:每{interval}小时采集一次关键词'{keyword}'")
# 启动调度
while True:
schedule.run_pending()
time.sleep(60)
def close(self):
"""关闭服务"""
self.storage.close()
# 使用示例
if __name__ == "__main__":
# 配置信息(实际使用时建议通过环境变量或配置文件加载)
APP_KEY = "your_jd_app_key"
APP_SECRET = "your_jd_app_secret"
MYSQL_CONFIG = {
"host": "localhost",
"user": "root",
"password": "password",
"db": "jd_data"
}
REDIS_CONFIG = {
"host": "localhost",
"port": 6379,
"db": 0
}
# 初始化服务
service = JDDataCollectionService(APP_KEY, APP_SECRET, MYSQL_CONFIG, REDIS_CONFIG)
# 1. 采集单个商品
service.collect_product_by_sku("100012345678")
# 2. 关键词搜索采集(前3页)
service.collect_products_by_keyword("笔记本电脑", pages=3)
# 3. 启动定时任务(每12小时采集一次手机数据)
# service.schedule_collection("手机", pages=2, interval=12)
# 关闭服务
service.close()
四、扩展与优化建议
- 反爬策略:增加请求间隔随机化、代理 IP 池,避免触发接口频率限制
- 分布式部署:对于大规模采集需求,可采用 Celery 分布式任务队列
- 监控告警:添加采集成功率监控和异常告警机制(邮件 / 钉钉)
- 数据增量更新:通过比对商品更新时间,实现增量采集而非全量覆盖
- 字段扩展:根据业务需求补充采集字段(如促销信息、库存状态等)
五、总结
本文构建的京东商品数据采集服务实现了从 API 调用、数据处理到存储落地的完整流程,通过模块化设计保证了代码的可维护性和可扩展性。实际应用中,需根据京东的接口规范和限流策略调整请求参数,同时做好数据合规性处理,确保采集行为符合平台规定和相关法律法规。

2786

被折叠的 条评论
为什么被折叠?



