电商软件开发软件实录:京东图片搜索商品列表

在电商开发这行混了快十年,对接过不少 "非主流" 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_keytimestamp等)排序,还得把image_base64image_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())

四、生产环境必踩的两个 "隐形坑"

  1. 接口超时问题 :图片搜索比普通接口耗时更长(尤其传image_base64时),默认超时时间设为 5 秒经常报错,后来改成 10 秒才稳定。
  2. 限流更严格:京东对图片搜索接口的限流比商品接口严得多 ------ 免费开发者每分钟最多 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分钟后再试")

这些年做京东图片搜索功能,最大的感悟是:图片类接口的 "坑" 往往不在文档里,而在实际测试的细节里 ------ 比如图片压缩的临界点、相似度评分的真实参考价值、不同类目商品的匹配精度差异等。