在电商开发这行混了快十年,对接过不少 "非主流" API,京东图片搜索接口的开发经历,至今想起来都觉得 "刺激"。看似只是传张图返回相似商品,实际从图片处理到签名验证,每一步都藏着能让你熬夜调试的坑。今天就把这些年踩过的雷、攒的实战代码掏出来,给同样在做图片搜索功能的朋友搭个桥。
一、第一次翻车:图片格式没处理,接口直接返回 "400 错误"
刚开始做京东图片搜索功能时,以为直接传张原图就能调用接口,结果连续三天返回400 Bad Request
。翻遍文档才发现,京东对图片格式要求极严:必须是 JPG/PNG 格式,尺寸不能超过 2MB,甚至连色深都有限制(不能是 CMYK 模式)。更坑的是,接口对image_base64
参数的编码有特殊要求 ------ 不能包含换行符,且必须去掉data:image/jpeg;base64,
前缀。
那段时间处理了上百张测试图,终于磨出个通用的图片预处理函数:
python
import base64
from PIL import Image
import io
def process_image(image_path):
"""处理图片为京东图片搜索接口要求的格式"""
try:
# 打开图片并转换为RGB模式(避免CMYK报错)
with Image.open(image_path) as img:
if img.mode == "CMYK":
img = img.convert("RGB")
# 压缩图片至2MB以内
max_size = 2 * 1024 * 1024 # 2MB
img_byte_arr = io.BytesIO()
quality = 95
while True:
img_byte_arr.seek(0)
img.save(img_byte_arr, format="JPEG", quality=quality)
if img_byte_arr.tell() <= max_size or quality <= 10:
break
quality -= 5
# 转换为base64,去除前缀和换行
img_base64 = base64.b64encode(img_byte_arr.getvalue()).decode("utf-8").replace("\n", "")
return img_base64
except Exception as e:
print(f"图片处理失败: {e}")
return None
二、签名算法:比京东商品接口多了 "图片参数" 的坑
京东图片搜索接口的签名逻辑,比普通商品接口复杂得多 ------ 不仅要对常规参数(app_key
、timestamp
等)排序,还得把image_base64
或image_url
也加入签名计算。第一次调用时漏了图片参数,结果签名验证通过了,但返回500
内部错误,排查半天才发现是签名时没包含图片数据导致的。
最终调试通过的签名函数:
python
import hashlib
import time
import urllib.parse
def generate_image_search_sign(params, app_secret):
"""生成京东图片搜索接口的签名"""
# 1. 过滤空值并按参数名ASCII排序
sorted_params = sorted([(k, v) for k, v in params.items() if v is not None], key=lambda x: x[0])
# 2. 拼接为key=value&key=value格式(注意对值进行URL编码)
query_str = "&".join([
f"{k}={urllib.parse.quote(str(v), safe='')}"
for k, v in sorted_params
])
# 3. 首尾加app_secret,MD5加密后转大写
sign_str = f"{app_secret}{query_str}{app_secret}"
return hashlib.md5(sign_str.encode()).hexdigest().upper()
# 使用示例
img_base64 = process_image("test.jpg")
params = {
"method": "jd.image.search",
"app_key": "your_app_key",
"timestamp": str(int(time.time())),
"image_base64": img_base64,
"page": 1,
"page_size": 20
}
params["sign"] = generate_image_search_sign(params, "your_app_secret")
三、返回结果处理:相似度过低的商品怎么筛?
调用接口成功后,又遇到个新问题:返回的商品里混了很多 "八竿子打不着" 的结果。比如搜一张 "黑色运动鞋" 的图,结果里居然有黑色背包。翻文档发现,接口会返回similarity
字段(相似度评分,0-100 分),但需要自己设置阈值过滤。
根据实际测试,把阈值设为 60 分以上,能过滤掉大部分不相关商品:
python
import requests
def filter_similar_products(response_data, min_similarity=60):
"""过滤相似度低于阈值的商品"""
products = response_data.get("result", {}).get("products", [])
filtered = []
for p in products:
# 提取相似度(不同接口版本字段可能不同,需注意)
similarity = p.get("similarity", 0)
if similarity >= min_similarity:
filtered.append({
"sku_id": p.get("sku_id"),
"name": p.get("name"),
"price": p.get("price"),
"similarity": similarity,
"image_url": p.get("image_url")
})
return filtered
# 调用示例
response = requests.post("https://api.jd.com/image/search", data=params)
filtered_products = filter_similar_products(response.json())
四、生产环境必踩的两个 "隐形坑"
- 接口超时问题 :图片搜索比普通接口耗时更长(尤其传
image_base64
时),默认超时时间设为 5 秒经常报错,后来改成 10 秒才稳定。 - 限流更严格:京东对图片搜索接口的限流比商品接口严得多 ------ 免费开发者每分钟最多 5 次请求,超过直接封号 24 小时。曾因批量测试触发限流,不得不加了令牌桶限流:
python
import time
from collections import deque
class ImageSearchLimiter:
def __init__(self, max_calls=5, period=60):
self.max_calls = max_calls
self.period = period
self.calls = deque()
def allow(self):
now = time.time()
# 移除过期的调用记录
while self.calls and now - self.calls[0] > self.period:
self.calls.popleft()
if len(self.calls) < self.max_calls:
self.calls.append(now)
return True
return False
# 使用示例
limiter = ImageSearchLimiter()
if limiter.allow():
response = requests.post(api_url, data=params)
else:
print("触发限流,等待1分钟后再试")
这些年做京东图片搜索功能,最大的感悟是:图片类接口的 "坑" 往往不在文档里,而在实际测试的细节里 ------ 比如图片压缩的临界点、相似度评分的真实参考价值、不同类目商品的匹配精度差异等。