1. 项目背景与核心需求
最近在对接一个电商数据聚合项目时,需要模拟1688商品详情页的数据结构。这类需求在价格监控、竞品分析、数据迁移等场景中很常见。不同于标准化的电商平台API,1688的返回数据有着独特的字段结构和嵌套逻辑。
这个Python API实现的核心目标有三个:
- 模拟真实1688商品详情页的返回数据
- 生成符合其特有风格的JSON结构
- 保持字段完整性和数据合理性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构分析与建模
2.1 典型字段结构解析
通过抓取分析真实1688商品页,其数据结构主要包含以下层级:
json复制{
"success": true,
"result": {
"productId": "123456789",
"title": "商品标题(含关键词堆砌)",
"priceInfo": {
"price": "¥99.00",
"originalPrice": "¥129.00",
"unit": "件",
"priceRanges": [
{"min": 1, "max": 9, "price": "¥95.00"},
{"min": 10, "max": 49, "price": "¥89.00"}
]
},
"specs": [
{
"name": "颜色",
"values": ["红色", "蓝色"]
}
],
"skuList": [
{
"skuId": "123456789_1",
"specValues": ["红色"],
"price": "¥99.00",
"stock": 9999
}
],
"detailImages": [
"https://example.com/1.jpg",
"https://example.com/2.jpg"
],
"attributes": [
{"name": "材质", "value": "纯棉"},
{"name": "产地", "value": "浙江杭州"}
]
}
}
2.2 特殊字段处理要点
-
价格字段:
- 必须包含¥符号
- 批发价需要priceRanges分级
- 原价字段即使相同也要保留
-
库存显示:
- 1688习惯显示9999这类虚高库存
- 实际库存可能通过起批量控制
-
图片URL:
- 使用alicdn域名或模拟域名
- 需要包含尺寸参数如_400x400.jpg
3. Python实现方案
3.1 基础框架搭建
使用FastAPI构建RESTful接口:
python复制from fastapi import FastAPI
from pydantic import BaseModel
import random
from typing import List, Dict
app = FastAPI()
class PriceRange(BaseModel):
min: int
max: int
price: str
class ProductSpec(BaseModel):
name: str
values: List[str]
class ProductSku(BaseModel):
skuId: str
specValues: List[str]
price: str
stock: int
class ProductResponse(BaseModel):
success: bool = True
result: Dict
@app.get("/product/{product_id}")
async def get_product(product_id: str):
# 实现逻辑将在下一步填充
pass
3.2 数据生成逻辑
python复制def generate_product_data(product_id: str):
# 基础信息
title_suffixes = ["厂家直销", "批发", "一件代发", "支持定制"]
title = f"模拟商品{product_id} " + random.choice(title_suffixes)
# 价格生成
base_price = round(random.uniform(50, 200), 2)
price_ranges = [
{"min": 1, "max": 9, "price": f"¥{base_price:.2f}"},
{"min": 10, "max": 49, "price": f"¥{base_price*0.9:.2f}"},
{"min": 50, "max": 99, "price": f"¥{base_price*0.85:.2f}"}
]
# 规格生成
colors = ["红色", "蓝色", "黑色", "白色"]
sizes = ["S", "M", "L", "XL"]
specs = [
{"name": "颜色", "values": random.sample(colors, 2)},
{"name": "尺寸", "values": random.sample(sizes, 3)}
]
# SKU组合生成
sku_list = []
for color in specs[0]["values"]:
for size in specs[1]["values"]:
sku_list.append({
"skuId": f"{product_id}_{len(sku_list)+1}",
"specValues": [color, size],
"price": price_ranges[0]["price"],
"stock": random.randint(100, 9999)
})
return {
"productId": product_id,
"title": title,
"priceInfo": {
"price": price_ranges[0]["price"],
"originalPrice": f"¥{base_price*1.2:.2f}",
"unit": "件",
"priceRanges": price_ranges
},
"specs": specs,
"skuList": sku_list,
"detailImages": [
f"https://mockcdn.com/{product_id}_{i}.jpg"
for i in range(1, random.randint(3,6))
],
"attributes": [
{"name": "材质", "value": random.choice(["纯棉", "涤纶", "混纺"])},
{"name": "产地", "value": random.choice(["浙江杭州", "广东广州", "江苏苏州"])}
]
}
3.3 接口完善与测试
python复制@app.get("/product/{product_id}")
async def get_product(product_id: str):
try:
data = generate_product_data(product_id)
return {"success": True, "result": data}
except Exception as e:
return {"success": False, "error": str(e)}
# 测试用例
"""
GET /product/12345
Response:
{
"success": true,
"result": {
"productId": "12345",
"title": "模拟商品12345 厂家直销",
...
}
}
"""
4. 高级功能实现
4.1 参数化配置
通过配置文件支持不同类目的数据模板:
python复制# config/categories.yaml
服装:
title_prefix: "时尚"
attributes:
- 材质
- 季节
specs:
- 颜色
- 尺寸
数码:
title_prefix: "新款"
attributes:
- 品牌
- 型号
specs:
- 颜色
- 内存容量
加载配置的改进代码:
python复制import yaml
with open("config/categories.yaml") as f:
CATEGORY_CONFIG = yaml.safe_load(f)
def generate_by_category(product_id: str, category: str):
config = CATEGORY_CONFIG.get(category, {})
title = f"{config.get('title_prefix','')}模拟商品{product_id}"
# 其他字段根据配置生成...
4.2 数据验证中间件
确保返回数据符合1688规范:
python复制from fastapi import Request, HTTPException
@app.middleware("http")
async def validate_data(request: Request, call_next):
response = await call_next(request)
if request.url.path.startswith("/product"):
data = response.json()
if data["success"]:
# 检查必要字段
required_fields = ["productId", "title", "priceInfo"]
for field in required_fields:
if field not in data["result"]:
raise HTTPException(500, f"Missing required field: {field}")
# 检查价格格式
if not data["result"]["priceInfo"]["price"].startswith("¥"):
raise HTTPException(500, "Invalid price format")
return response
5. 性能优化方案
5.1 缓存机制
python复制from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
@app.on_event("startup")
async def startup():
FastAPICache.init(RedisBackend("redis://localhost"))
@app.get("/product/{product_id}")
@cache(expire=300) # 5分钟缓存
async def get_product(product_id: str):
return generate_product_data(product_id)
5.2 异步数据生成
python复制import asyncio
async def async_generate_data(product_id: str):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None, generate_product_data, product_id
)
@app.get("/product/{product_id}")
async def get_product(product_id: str):
return await async_generate_data(product_id)
6. 异常处理与日志
6.1 自定义异常
python复制from fastapi import HTTPException
class ProductException(HTTPException):
def __init__(self, detail: str):
super().__init__(
status_code=400,
detail=detail,
headers={"X-Error": "ProductAPI"}
)
@app.get("/product/{product_id}")
async def get_product(product_id: str):
if not product_id.isdigit():
raise ProductException("Invalid product ID format")
# ...
6.2 结构化日志
python复制import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger("product_api")
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
'%(asctime)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
@app.get("/product/{product_id}")
async def get_product(product_id: str):
logger.info("Request product", extra={
"product_id": product_id,
"client": request.client.host
})
# ...
7. 部署与扩展
7.1 Docker化部署
dockerfile复制FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
7.2 负载均衡配置
使用Nginx作为反向代理:
nginx复制upstream product_api {
server api1:8000;
server api2:8000;
}
server {
listen 80;
location / {
proxy_pass http://product_api;
}
}
8. 实际应用中的经验总结
-
字段顺序很重要:
1688的返回字段有固定顺序,比如priceInfo总是出现在title之后。虽然JSON规范不要求顺序,但很多客户端代码会依赖这个顺序。 -
价格精度处理:
python复制# 错误做法 price = f"¥{19.9:.2f}" # ¥19.90 # 正确做法(匹配1688实际返回) price = f"¥{19.9:.1f}" if price.endswith(".0") else f"¥{price:.2f}" -
图片URL生成技巧:
- 使用不同的子域名模拟CDN
- 添加无意义的查询参数如
?_abtest=123 - 包含尺寸参数但实际不生效
-
性能陷阱:
- 避免在循环中频繁连接字符串
- 预生成常用规格组合
- 使用faker库替代完全随机生成
-
调试建议:
python复制# 在开发环境启用调试模式 @app.get("/product/{product_id}") async def get_product(product_id: str, debug: bool = False): if debug: return { "debug": { "generated_at": datetime.now().isoformat(), "random_seed": random.getstate() }, **generate_product_data(product_id) }
这个实现方案已经成功应用在多个电商数据对接项目中,特别是在需要模拟1688接口进行开发和测试时,可以节省大量对接真实API的时间成本。根据具体需求,还可以扩展出商品列表接口、搜索接口等完整解决方案。
