×

1688 item_search_img 接口深度分析及 Python 实现

万邦科技Lex 万邦科技Lex 发表于2025-09-01 09:51:14 浏览244 评论0

抢沙发发表评论

注册账号免费测试1688拍立淘接口

1688 的 item_search_img 接口(又称 "1688 拍立淘")是阿里巴巴开放平台提供的图像搜索接口,支持通过图片(URL 或本地图片)搜索 1688 平台上的同款或相似商品。该接口基于阿里巴巴的图像识别技术,能够快速匹配商品库中视觉特征相似的商品,广泛应用于供应商查找、同款比价、供应链管理等场景。

一、接口核心特性分析

1. 接口功能与定位

  • 核心功能:通过图片特征匹配 1688 平台商品,返回相似商品列表及详细信息

  • 技术原理:基于深度学习的图像特征提取与比对,支持商品主体识别、特征匹配

  • 应用场景

    • 供应商查找:通过样品图片找到生产厂家

    • 同款比价:找到同款商品的不同供应商价格

    • 电商选品:根据市场热销商品图片寻找货源

    • 供应链管理:快速匹配产品与供应商

2. 认证机制

1688 开放平台采用 appkey + 签名 认证方式,具体流程:


  • 开发者在 1688 开放平台注册应用,获取 appkey 和 appsecret

  • 每次请求时,将所有参数按 ASCII 码升序排序

  • 拼接参数并结合 appsecret 生成 MD5 签名

  • 服务器验证签名有效性,防止参数篡改

3. 核心参数与响应结构

请求参数

参数名类型是否必填说明
methodString接口方法名,固定为 alibaba.item.search.img
app_keyString应用的 appkey
timestampString时间戳,格式 yyyy-MM-dd HH:mm:ss
formatString响应格式,默认 json
vString接口版本,固定为 2.0
signString签名
image_urlString二选一图片 URL(公网可访问)
imageString二选一本地图片 Base64 编码(不含前缀)
pageNumber页码,默认 1
page_sizeNumber每页数量,默认 40,最大 100
sortString排序方式:price_ascprice_descsales

响应核心字段

  • 分页信息:总商品数、总页数、当前页

  • 商品列表:每个商品包含

    • 基础信息:商品 ID、标题、主图、详情页链接

    • 价格信息:价格区间、起订量、批发价

    • 供应商信息:供应商 ID、名称、所在地、信用等级

    • 交易信息:30 天成交量、买家数、重复采购率

二、Python 脚本实现

以下是调用 1688 item_search_img 接口的完整实现,支持图片 URL 和本地图片两种搜索方式:
import requests
import hashlib
import time
import json
import base64
import logging
from typing import Dict, Optional, List
from requests.exceptions import RequestException
from pathlib import Path

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

class AlibabaImageSearchAPI:
    def __init__(self, appkey: str, appsecret: str):
        """
        初始化1688图片搜索接口客户端
        :param appkey: 1688开放平台appkey
        :param appsecret: 1688开放平台appsecret
        """
        self.appkey = appkey
        self.appsecret = appsecret
        self.base_url = "https://gw.open.1688.com/openapi/http/1/system.oauth2"
        self.session = requests.Session()

    def _generate_sign(self, params: Dict) -> str:
        """生成1688 API签名"""
        # 1. 按参数名ASCII升序排序
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        # 2. 拼接为key=value&key=value格式
        sign_str = "&".join([f"{k}={v}" for k, v in sorted_params])
        # 3. 拼接appsecret并MD5加密
        sign_str += self.appsecret
        return hashlib.md5(sign_str.encode()).hexdigest().upper()

    def _get_timestamp(self) -> str:
        """生成时间戳(yyyy-MM-dd HH:mm:ss)"""
        return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())

    def _local_image_to_base64(self, image_path: str) -> Optional[str]:
        """将本地图片转换为Base64编码"""
        image_file = Path(image_path)
        
        # 检查文件是否存在
        if not image_file.exists() or not image_file.is_file():
            logging.error(f"图片文件不存在: {image_path}")
            return None
            
        # 检查文件格式
        valid_extensions = ['.jpg', '.jpeg', '.png', '.gif']
        if image_file.suffix.lower() not in valid_extensions:
            logging.error(f"不支持的图片格式: {image_file.suffix},支持格式: {valid_extensions}")
            return None
            
        try:
            # 读取并编码图片
            with open(image_path, 'rb') as f:
                image_data = f.read()
                base64_str = base64.b64encode(image_data).decode('utf-8')
            return base64_str
        except Exception as e:
            logging.error(f"图片编码失败: {str(e)}")
            return None

    def search_by_image(self, 
                       image_url: Optional[str] = None, 
                       image_path: Optional[str] = None,
                       page: int = 1, 
                       page_size: int = 40,
                       sort: Optional[str] = None) -> Optional[Dict]:
        """
        按图片搜索1688商品
        :param image_url: 图片URL(二选一)
        :param image_path: 本地图片路径(二选一)
        :param page: 页码
        :param page_size: 每页数量
        :param sort: 排序方式
        :return: 搜索结果
        """
        # 验证图片参数
        if not image_url and not image_path:
            logging.error("必须提供image_url或image_path参数")
            return None
            
        # 构建基础参数
        params = {
            "method": "alibaba.item.search.img",
            "app_key": self.appkey,
            "timestamp": self._get_timestamp(),
            "format": "json",
            "v": "2.0",
            "page": str(page),
            "page_size": str(page_size)
        }
        
        # 添加排序参数
        if sort:
            params["sort"] = sort
            
        # 添加图片参数
        if image_url:
            params["image_url"] = image_url
        else:
            # 处理本地图片
            base64_image = self._local_image_to_base64(image_path)
            if not base64_image:
                return None
            params["image"] = base64_image
            
        # 生成签名
        params["sign"] = self._generate_sign(params)
        
        try:
            # 发送请求
            response = self.session.get(
                self.base_url,
                params=params,
                timeout=20  # 图片搜索耗时较长,设置较长超时
            )
            response.raise_for_status()
            result = response.json()
            
            # 处理错误响应
            if "error_response" in result:
                error = result["error_response"]
                logging.error(f"接口错误: {error.get('msg', '未知错误')} (错误码: {error.get('code', '未知')})")
                return None
                
            # 格式化响应数据
            return self._format_response(result)
            
        except RequestException as e:
            logging.error(f"请求异常: {str(e)}")
            return None
        except json.JSONDecodeError:
            logging.error(f"响应解析失败: {response.text[:200]}...")
            return None

    def _format_response(self, response: Dict) -> Dict:
        """格式化响应数据"""
        result = response.get("alibaba_item_search_img_response", {})
        data = result.get("result", {})
        
        # 提取分页信息
        pagination = {
            "total_items": int(data.get("total_results", 0)),
            "total_pages": (int(data.get("total_results", 0)) + int(data.get("page_size", 40)) - 1) // int(data.get("page_size", 40)),
            "current_page": int(data.get("page", 1)),
            "page_size": int(data.get("page_size", 40))
        }
        
        # 格式化商品列表
        products = []
        for item in data.get("items", {}).get("item", []):
            product = {
                "product_id": item.get("product_id"),
                "title": item.get("title"),
                "main_image": item.get("main_image"),
                "detail_url": item.get("detail_url"),
                "price": {
                    "min_price": float(item.get("min_price", 0)),
                    "max_price": float(item.get("max_price", 0)),
                    "unit": item.get("unit", "")
                },
                "wholesale_price": item.get("wholesale_price", []),  # 批发价格区间
                "moq": int(item.get("moq", 0)),  # 最小起订量
                "transaction": {
                    "sales_count": int(item.get("sales_count", 0)),  # 30天销量
                    "buyer_count": int(item.get("buyer_count", 0)),  # 买家数
                    "repeat_rate": float(item.get("repeat_rate", 0))  # 重复采购率
                },
                "supplier": {
                    "supplier_id": item.get("supplier_id"),
                    "supplier_name": item.get("supplier_name"),
                    "location": item.get("location"),
                    "credit_level": item.get("credit_level"),
                    "year": int(item.get("year", 0))  # 经营年限
                },
                "similarity": float(item.get("similarity", 0))  # 相似度(0-100)
            }
            products.append(product)
            
        return {
            "pagination": pagination,
            "products": products,
            "search_id": data.get("search_id")  # 搜索ID,可用于后续操作
        }

    def search_all_pages(self, image_url: Optional[str] = None, 
                        image_path: Optional[str] = None,
                        max_pages: int = 5) -> List[Dict]:
        """
        获取多页搜索结果
        :param image_url: 图片URL
        :param image_path: 本地图片路径
        :param max_pages: 最大页数限制
        :return: 所有商品列表
        """
        all_products = []
        page = 1
        
        while page <= max_pages:
            logging.info(f"获取第 {page} 页搜索结果")
            result = self.search_by_image(
                image_url=image_url,
                image_path=image_path,
                page=page,
                page_size=100  # 使用最大页大小减少请求次数
            )
            
            if not result or not result["products"]:
                break
                
            all_products.extend(result["products"])
            
            # 检查是否已到最后一页
            if page >= result["pagination"]["total_pages"]:
                break
                
            page += 1
            # 添加延迟,避免触发频率限制
            time.sleep(1.5)
            
        return all_products


# 示例调用
if __name__ == "__main__":
    # 替换为实际的appkey和appsecret(从1688开放平台获取)
    APPKEY = "your_appkey"
    APPSECRET = "your_appsecret"
    
    # 初始化API客户端
    api = AlibabaImageSearchAPI(APPKEY, APPSECRET)
    
    # 方式1:通过图片URL搜索
    # search_result = api.search_by_image(
    #     image_url="https://img.alicdn.com/imgextra/i3/xxx.jpg",  # 替换为实际图片URL
    #     page=1,
    #     page_size=20,
    #     sort="sales"
    # )
    
    # 方式2:通过本地图片搜索
    search_result = api.search_by_image(
        image_path="./product_sample.jpg",  # 替换为本地图片路径
        page=1,
        page_size=20,
        sort="sales"
    )
    
    # 方式3:获取多页结果
    # search_result = api.search_all_pages(
    #     image_path="./product_sample.jpg",
    #     max_pages=3
    # )
    
    if search_result:
        # 处理单页结果
        if isinstance(search_result, dict) and "products" in search_result:
            print(f"共找到 {search_result['pagination']['total_items']} 件相似商品")
            print(f"当前第 {search_result['pagination']['current_page']}/{search_result['pagination']['total_pages']} 页\n")
            
            # 打印前5件商品信息
            for i, product in enumerate(search_result["products"][:5], 1):
                print(f"{i}. {product['title']} (相似度: {product['similarity']}%)")
                print(f"   价格: {product['price']['min_price']}-{product['price']['max_price']}{product['price']['unit']}")
                print(f"   起订量: {product['moq']}{product['price']['unit']}")
                print(f"   30天销量: {product['transaction']['sales_count']} 件")
                print(f"   供应商: {product['supplier']['supplier_name']} ({product['supplier']['location']})")
                print(f"   链接: {product['detail_url']}")
                print("-" * 100)
        else:
            # 处理多页结果
            print(f"共获取到 {len(search_result)} 件商品")

三、接口调用关键技术与注意事项

1. 图片处理最佳实践

  • 图片质量:清晰的商品主体图片(无水印、无遮挡)识别效果最佳

  • 图片尺寸:建议图片尺寸在 500x500 到 1000x1000 像素之间

  • 图片格式:优先使用 JPG 格式,识别成功率高于 PNG 和 GIF

  • Base64 处理

    • 必须移除 Base64 编码前缀(如data:image/jpeg;base64,

    • 图片大小控制在 5MB 以内,过大可能导致请求失败

  • URL 图片:确保图片 URL 为公网可访问,阿里系 CDN 图片识别速度更快

2. 常见错误及解决方案

错误码说明解决方案
10001签名错误检查参数排序是否正确,appsecret 是否匹配
20002图片格式错误检查图片格式是否为支持的类型,Base64 编码是否正确
20003图片无法访问验证图片 URL 有效性,确保公网可访问
20004图片识别失败更换清晰的商品图片,确保主体明确
429调用频率超限降低调用频率,实现请求限流机制
403权限不足检查应用是否已申请图片搜索接口权限

3. 性能优化建议

  • 请求频率控制:默认 QPS 限制为 5,建议控制在 3-4 次 / 秒

  • 图片预处理:对本地图片进行压缩和裁剪,突出商品主体

  • 结果缓存:相同图片的搜索结果可缓存 30-60 分钟

  • 批量处理:对多张图片搜索,使用队列和异步处理提高效率

  • 分页策略:优先获取前 3 页结果(通常包含最相似的商品)

四、应用场景与扩展

典型应用场景

  • 供应商匹配系统:通过样品图片快速找到生产厂家

  • 同款比价工具:对比相同商品在不同供应商的价格和起订量

  • 电商选品助手:根据市场热销商品图片寻找 1688 货源

  • 反向寻源平台:帮助采购商快速找到特定产品的供应商

扩展建议

  • 结合商品详情接口获取更完整的产品信息

  • 实现相似度过滤,只保留高相似度的商品

  • 开发图片批量搜索功能,支持多图同时查询

  • 添加价格趋势分析,监控供应商价格变化

  • 结合地图服务,按地区筛选供应商


通过合理使用 1688 图片搜索接口,开发者可以构建高效的供应链寻源工具,帮助企业快速找到合适的供应商,降低采购成本,提高供应链效率。使用时需遵守 1688 开放平台的使用规范,确保数据使用的合法性。

群贤毕至

访客