×

唯品会 item_search 接口对接全攻略:从入门到精通

万邦科技Lex 万邦科技Lex 发表于2025-11-17 08:47:40 浏览46 评论0

抢沙发发表评论

                   注册账号免费测试唯品会API数据接口

唯品会(品牌折扣折扣电商平台)的商品搜索功能(item_search接口,非官方命名)是获取折扣商品列表的核心入口,数据包含品牌折扣价、促销活动、销量热度等关键信息,对价格监控、竞品分析、促销选品等场景具有重要价值。由于平台无公开官方 API,开发者需通过页面解析或逆向工程实现搜索对接。本文系统讲解接口逻辑、参数解析、技术实现及促销场景适配策略,助你构建稳定的唯品会商品搜索系统。

一、接口基础认知(核心功能与场景)

  1. 核心功能唯品会item_search接口通过关键词、分类、价格、品牌等条件筛选商品,返回符合条件的列表数据,核心字段聚焦折扣电商特性:
    • 基础信息:商品 ID(productId)、标题(含品牌 / 折扣,如 “【2.5 折】Adidas 男士卫衣”)、主图、类目(如 “男装 > 卫衣”)、详情页 URL

    • 价格信息:折扣价(如 “¥129”)、原价(划线价,如 “¥499”)、折扣力度(如 “2.5 折”)、会员专享价(如 “SVIP¥119”)

    • 促销信息:活动标签(如 “限时秒杀”“满 200 减 30”)、发货时效(如 “今日达”)、库存状态(如 “仅剩 20 件”)

    • 品牌信息:品牌名称(如 “Adidas”)、品牌等级(如 “国际运动品牌”)

    • 热度数据:30 天销量(如 “已售 5000+”)、收藏数、好评率(如 “97% 好评”)

  2. 典型应用场景
    • 价格监控工具:搜索 “Adidas 卫衣”,按 “折扣≤3 折 + 价格≤200 元” 筛选高性价比商品,跟踪价格波动

    • 促销选品:在 “双 11” 期间搜索 “冬季羽绒服”,按 “限时秒杀 + 销量降序” 筛选热门促销款

    • 竞品分析:对比同品类不同品牌的折扣力度、销量及用户评价,优化选品策略

    • 品牌跟踪:监控特定品牌(如 “Nike”)的上新节奏、折扣分布及库存变化

  3. 接口特性
    • 折扣导向性:筛选条件与数据字段深度结合促销场景(如 “折扣力度”“限时活动”)

    • 动态性强:价格(限时折扣)、库存(售罄预警)实时更新,部分数据通过 AJAX 动态加载

    • 反爬严格:包含 IP 限制、User-Agent 校验、Cookie 验证、签名机制(部分接口需sign参数)

    • 分页结构:默认每页 20 条,最多支持 50 页(约 1000 条结果),分页参数为page

    • 多维度筛选:支持按分类、价格、品牌、折扣、活动类型、销量等组合筛选

二、对接前置准备(参数与 URL 结构)

  1. 开发环境
    • 网络请求:requests(同步)、aiohttp(异步批量搜索)

    • 页面解析:BeautifulSoup(静态 HTML)、lxml(XPath 提取列表数据)

    • 反爬工具:fake_useragent(随机 UA)、proxy_pool(代理 IP 池)、execjs(解析签名参数)

    • 数据处理:re(正则提取价格、折扣)、urllib.parse(URL 参数编码)

    • 开发语言:Python(推荐 3.8+)

    • 核心库:

  2. 搜索 URL 结构与核心参数唯品会搜索页基础 URL 为:https://category.vip.com/suggest.php,核心参数通过查询字符串传递:
    筛选条件参数名示例值说明
    关键词keywordAdidas卫衣 → 编码后为%E9%98%BF%E8%BF%AA%E8%BE%BE%E5%8D%E6%9C%8D支持商品名、品牌(如 “Nike”)、属性(如 “加绒”)
    分类 IDcat_id1005159(男装)、1005160(女装)分类 ID 需从导航栏解析获取(如 “男装> 卫衣” 对应1005159_1234
    价格区间(始)price_min100最低折扣价(元)
    价格区间(终)price_max200最高折扣价(元)
    折扣力度discount3最高折扣(如 “3” 表示≤3 折)
    品牌 IDbrand_id1234(Adidas)品牌 ID 需从品牌列表页解析获取
    活动类型activity1(限时秒杀)1 = 限时秒杀,2 = 满减,3 = 会员专享
    排序方式sortsales(销量降序)见 “排序参数表”
    分页page1 2 ... 50页码,默认 1,最大 50
  3. 排序参数表唯品会搜索支持折扣电商场景常用排序方式,对应sort参数值如下:
    排序方式sort参数值适用场景
    综合推荐空值默认排序,平衡折扣力度与销量
    价格升序price_asc低价商品筛选(如基础款 T 恤)
    价格降序price_desc中高端商品筛选(如品牌羽绒服)
    销量降序sales爆款商品筛选(如当季热销款)
    折扣升序discount_asc高折扣商品筛选(如≤3 折商品)
    最新上架new新品折扣跟踪(如刚上线的折扣款)
  4. 分类 ID 与品牌 ID 映射
    • 分类 ID:访问唯品会分类页(https://category.vip.com/),通过开发者工具查看类目链接的href(如/category-1005159_1234/1005159_1234为 “男装> 卫衣” 的 ID);

    • 品牌 ID:从品牌列表页(如https://category.vip.com/brand-1234/)提取brand_id(链接中的数字部分)。

三、接口调用流程(基于页面解析与动态接口)

以 “搜索 100-200 元的 Adidas 卫衣,折扣≤3 折,限时秒杀活动,按销量降序排序” 为例,流程为参数组装→URL 构建→请求发送→列表解析→分页遍历
  1. URL 构建示例组合参数生成目标搜索 URL:
    python
    运行
    keyword = "Adidas卫衣"cat_id = "1005159_1234"  # 男装>卫衣分类IDprice_min = 100price_max = 200discount = 3  # ≤3折brand_id = "1234"  # Adidas品牌IDactivity = 1  # 限时秒杀sort = "sales"  # 销量降序page = 1# 关键词URL编码encoded_keyword = urllib.parse.quote(keyword, encoding="utf-8")# 构建URLurl = f"https://category.vip.com/suggest.php?keyword={encoded_keyword}&cat_id={cat_id}&price_min={price_min}&price_max={price_max}&discount={discount}&brand_id={brand_id}&activity={activity}&sort={sort}&page={page}"
  2. 请求头与反爬伪装模拟用户浏览器行为,需包含登录态 Cookie 以获取完整价格和促销信息:
    python
    运行
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
        "Referer": "https://www.vip.com/",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Cookie": "vip_access_token=xxx; userid=xxx; vip_guid=xxx"  # 登录后从浏览器获取}
  3. 页面解析与数据提取搜索结果列表通常在 HTML 的<div class="goods-list">标签内,每条商品信息包含以下核心字段:
    字段解析方式(CSS 选择器示例)说明
    商品 IDa标签的href中提取(如/detail-123456.html123456唯一标识
    标题.goods-title的文本如 “【2.5 折】Adidas 男士加绒卫衣”
    主图.goods-img imgsrc属性商品主图 URL
    折扣价.discount-price的文本(提取数值)如 “¥129” 提取 “129”
    原价.original-price的文本(提取数值)如 “¥499” 提取 “499”
    折扣力度.discount-tag的文本(提取数值)如 “2.5 折” 提取 “2.5”
    30 天销量.sales-count的文本(提取数字)如 “已售 5000+” 提取 “5000”
    品牌名称.brand-name的文本如 “Adidas”
    活动标签.activity-tag的文本如 “限时秒杀满 200 减 30”
    库存状态.stock-tag的文本如 “仅剩 20 件”
  4. 动态数据补充(部分场景)部分筛选条件(如 “最新上架”)或数据(如实时库存)通过 AJAX 接口加载,需单独调用:
    • 动态列表接口示例:https://mapi.vip.com/vips-mobile/rest/search/product,参数包含keywordpagesign等;

    • 响应包含商品 ID、价格、销量等结构化数据,可直接解析(需处理sign签名,方法同item_get接口)。

  5. 分页处理
    • 分页通过page参数控制,前 50 页为有效数据,超过则返回空结果;

    • 终止条件:当前页商品数量 < 20(最后一页)或页码≥50;

    • 分页间隔:每页请求间隔 3-5 秒(随机波动),避免触发反爬(唯品会对搜索频率限制严格)。

四、代码实现示例(Python)

以下是item_search接口的完整实现,包含多条件筛选、分页遍历、数据解析及反爬处理:
import requests
import time
import random
import re
import urllib.parse
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
from typing import List, Dict

class VipSearchApi:
    def __init__(self, proxy_pool: List[str] = None, cookie: str = ""):
        self.base_url = "https://category.vip.com/suggest.php"
        self.ua = UserAgent()
        self.proxy_pool = proxy_pool  # 代理池列表,如["http://ip:port", ...]
        self.cookie = cookie  # 登录态Cookie(用于完整价格和促销信息)
        # 分类ID映射(简化版)
        self.category_map = {
            "男装-卫衣": "1005159_1234",
            "女装-连衣裙": "1005160_5678",
            "运动-运动鞋": "1005212_9012"
        }
        # 品牌ID映射(简化版)
        self.brand_map = {
            "Adidas": "1234",
            "Nike": "5678",
            "优衣库": "9012"
        }

    def _get_headers(self) -> Dict[str, str]:
        """生成随机请求头"""
        headers = {
            "User-Agent": self.ua.random,
            "Referer": "https://www.vip.com/",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
        }
        if self.cookie:
            headers["Cookie"] = self.cookie
        return headers

    def _get_proxy(self) -> Dict[str, str]:
        """随机获取代理"""
        if self.proxy_pool and len(self.proxy_pool) > 0:
            proxy = random.choice(self.proxy_pool)
            return {"http": proxy, "https": proxy}
        return None

    def _clean_price(self, price_str: str) -> float:
        """清洗价格(去除¥、逗号等)"""
        if not price_str:
            return 0.0
        price_str = re.sub(r"[^\d.]", "", price_str)
        return float(price_str) if price_str else 0.0

    def _clean_discount(self, discount_str: str) -> float:
        """清洗折扣力度(提取数字,如“2.5折”→2.5)"""
        if not discount_str:
            return 0.0
        discount_num = re.search(r"\d+\.?\d*", discount_str)
        return float(discount_num.group()) if discount_num else 0.0

    def _clean_sales(self, sales_str: str) -> int:
        """清洗30天销量(提取数字,如“5000+”→5000)"""
        if not sales_str:
            return 0
        sales_num = re.search(r"\d+", sales_str)
        return int(sales_num.group()) if sales_num else 0

    def _parse_item(self, item_soup) -> Dict[str, str]:
        """解析单条商品数据"""
        # 提取商品ID
        link = item_soup.select_one("a.goods-link")["href"] if item_soup.select_one("a.goods-link") else ""
        product_id = re.search(r"/detail-(\d+)\.html", link).group(1) if link else ""

        # 提取活动标签(多个标签用逗号拼接)
        activity_tags = [tag.text.strip() for tag in item_soup.select(".activity-tag")]

        return {
            "product_id": product_id,
            "title": item_soup.select_one(".goods-title")?.text.strip() or "",
            "main_image": item_soup.select_one(".goods-img img")?.get("src") or "",
            "url": f"https://detail.vip.com{link}" if link.startswith("/") else link,
            "price": {
                "discount": self._clean_price(item_soup.select_one(".discount-price")?.text or ""),
                "original": self._clean_price(item_soup.select_one(".original-price")?.text or ""),
                "discount_str": item_soup.select_one(".discount-price")?.text.strip() or "",
                "original_str": item_soup.select_one(".original-price")?.text.strip() or "",
                "discount_rate": self._clean_discount(item_soup.select_one(".discount-tag")?.text or ""),
                "discount_rate_str": item_soup.select_one(".discount-tag")?.text.strip() or ""
            },
            "sales": {
                "sales_count": self._clean_sales(item_soup.select_one(".sales-count")?.text or ""),
                "sales_str": item_soup.select_one(".sales-count")?.text.strip() or "",
                "good_rate": item_soup.select_one(".good-rate")?.text.strip() or ""
            },
            "brand": {
                "name": item_soup.select_one(".brand-name")?.text.strip() or ""
            },
            "promotion": {
                "tags": activity_tags,
                "stock_status": item_soup.select_one(".stock-tag")?.text.strip() or ""
            }
        }

    def _parse_page(self, html: str) -> List[Dict]:
        """解析页面的商品列表"""
        soup = BeautifulSoup(html, "lxml")
        # 商品列表容器(需根据实际页面结构调整)
        item_list = soup.select("div.goods-item")
        return [self._parse_item(item) for item in item_list if item]

    def _get_total_pages(self, html: str) -> int:
        """获取总页数"""
        soup = BeautifulSoup(html, "lxml")
        page_box = soup.select_one(".pagination")
        if not page_box:
            return 1
        # 提取最后一页页码
        last_page = page_box.select("a")[-1].text.strip()
        return int(last_page) if last_page.isdigit() else 1

    def item_search(self, 
                   keyword: str = "", 
                   category: str = "", 
                   price_min: float = None, 
                   price_max: float = None, 
                   discount: float = None, 
                   brand: str = "", 
                   activity: int = 0, 
                   sort: str = "", 
                   page_limit: int = 5) -> Dict:
        """
        搜索唯品会商品列表
        :param keyword: 搜索关键词
        :param category: 分类名称(如“男装-卫衣”)或分类ID
        :param price_min: 最低折扣价(元)
        :param price_max: 最高折扣价(元)
        :param discount: 最高折扣(如3表示≤3折)
        :param brand: 品牌名称(如“Adidas”)或品牌ID
        :param activity: 活动类型(1=限时秒杀,2=满减,0=全部)
        :param sort: 排序方式(sales/discount_asc等)
        :param page_limit: 最大页数(默认5)
        :return: 标准化搜索结果
        """
        try:
            # 1. 参数预处理
            if not keyword and not category:
                return {"success": False, "error_msg": "关键词(keyword)和分类(category)至少需提供一个"}
            # 转换分类名称为ID
            if category in self.category_map:
                cat_id = self.category_map[category]
            else:
                cat_id = category if category else ""
            # 转换品牌名称为ID
            brand_id = self.brand_map.get(brand, "") if brand else ""
            # 编码关键词
            encoded_keyword = urllib.parse.quote(keyword, encoding="utf-8") if keyword else ""

            all_items = []
            current_page = 1

            while current_page <= page_limit:
                # 构建参数
                params = {
                    "page": current_page
                }
                if encoded_keyword:
                    params["keyword"] = encoded_keyword
                if cat_id:
                    params["cat_id"] = cat_id
                if price_min is not None:
                    params["price_min"] = price_min
                if price_max is not None:
                    params["price_max"] = price_max
                if discount is not None:
                    params["discount"] = discount
                if brand_id:
                    params["brand_id"] = brand_id
                if activity in (0, 1, 2, 3):
                    params["activity"] = activity
                if sort:
                    params["sort"] = sort

                # 发送请求(带随机延迟)
                time.sleep(random.uniform(3, 5))  # 唯品会反爬严格,延迟需更高
                headers = self._get_headers()
                proxy = self._get_proxy()

                response = requests.get(
                    url=self.base_url,
                    params=params,
                    headers=headers,
                    proxies=proxy,
                    timeout=10
                )
                response.raise_for_status()
                html = response.text

                # 解析当前页商品
                items = self._parse_page(html)
                if not items:
                    break  # 无数据,终止分页

                all_items.extend(items)

                # 获取总页数(仅第一页需要)
                if current_page == 1:
                    total_pages = self._get_total_pages(html)
                    # 修正最大页数(不超过page_limit和50)
                    total_pages = min(total_pages, page_limit, 50)
                    if total_pages < current_page:
                        break

                # 若当前页是最后一页,终止
                if current_page >= total_pages:
                    break

                current_page += 1

            # 去重(基于product_id)
            seen_ids = set()
            unique_items = []
            for item in all_items:
                if item["product_id"] not in seen_ids:
                    seen_ids.add(item["product_id"])
                    unique_items.append(item)

            return {
                "success": True,
                "total": len(unique_items),
                "page_processed": current_page,
                "items": unique_items
            }

        except requests.exceptions.HTTPError as e:
            if "403" in str(e):
                return {"success": False, "error_msg": "触发反爬,建议更换代理或Cookie", "code": 403}
            if "401" in str(e):
                return {"success": False, "error_msg": "需要登录,请提供有效Cookie", "code": 401}
            return {"success": False, "error_msg": f"HTTP错误: {str(e)}", "code": response.status_code}
        except Exception as e:
            return {"success": False, "error_msg": f"搜索失败: {str(e)}", "code": -1}

# 使用示例
if __name__ == "__main__":
    # 代理池(替换为有效代理)
    PROXIES = [
        "http://123.45.67.89:8888",
        "http://98.76.54.32:8080"
    ]
    # 登录态Cookie(从浏览器获取,用于完整价格)
    COOKIE = "vip_access_token=xxx; userid=xxx; vip_guid=xxx"

    # 初始化API客户端
    search_api = VipSearchApi(proxy_pool=PROXIES, cookie=COOKIE)

    # 搜索“Adidas卫衣”,分类“男装-卫衣”,价格100-200元,折扣≤3折,限时秒杀,销量降序,最多3页
    result = search_api.item_search(
        keyword="Adidas卫衣",
        category="男装-卫衣",
        price_min=100,
        price_max=200,
        discount=3,
        brand="Adidas",
        activity=1,
        sort="sales",
        page_limit=3
    )

    if result["success"]:
        print(f"搜索成功:共找到 {result['total']} 件商品,处理 {result['page_processed']} 页")
        for i, item in enumerate(result["items"][:5]):  # 打印前5条
            print(f"\n商品 {i+1}:")
            print(f"标题:{item['title'][:50]}...")  # 截断长标题
            print(f"价格:{item['price']['discount_str']}(原价{item['price']['original_str']}) | {item['price']['discount_rate_str']}")
            print(f"销量:30天已售{item['sales']['sales_count']}件 | 好评率{item['sales']['good_rate']}")
            print(f"促销:{', '.join(item['promotion']['tags'])} | 库存:{item['promotion']['stock_status']}")
            print(f"品牌:{item['brand']['name']}")
            print(f"详情页:{item['url']}")
    else:
        print(f"搜索失败:{result['error_msg']}(错误码:{result.get('code')})")

五、关键技术难点与解决方案

  1. 多维度促销参数整合
    • 建立参数映射表(如brand_map将 “Adidas” 转换为1234),统一参数格式;

    • 对折扣、价格等数值参数进行类型校验,确保筛选范围准确(如discount=3仅返回≤3 折商品);

    • 示例代码中item_search函数对brandactivity参数的预处理,确保多条件促销筛选生效。

    • 问题:唯品会搜索依赖折扣力度、活动类型等促销特有参数,参数格式多样(如 “折扣≤3 折” 对应discount=3),易出现筛选逻辑错误。

    • 解决方案

  2. 折扣与价格数据清洗
    • 用正则去除非数字字符(如re.sub(r"[^\d.]", "", price_str)),提取价格和折扣的数值;

    • 同时保留原始文本(如discount_rate_str“2.5 折”),便于用户理解;

    • 示例代码中_clean_price_clean_discount函数实现数值提取,支持后续按折扣区间筛选。

    • 问题:列表页的价格和折扣包含符号(如 “¥129”“2.5 折”),需提取纯数值用于比较和计算(如筛选 “折扣≤3 折且价格≤200 元” 的商品)。

    • 解决方案

  3. 动态接口签名处理(若需)
    • 参考item_get接口的签名破解方法,从 JS 中提取加密算法和密钥;

    • execjs或 Python 复现sign生成逻辑(如基于keywordpagetimestamp的 MD5 加密);

    • 示例代码中预留动态接口调用逻辑,可根据实际需求扩展。

    • 问题:部分搜索场景(如 “最新上架” 排序)的数据通过动态 API 加载,需携带sign参数,该参数通过 JS 加密生成。

    • 解决方案

  4. 严格反爬机制对抗
    • 代理 IP 策略:使用高匿代理池(优先选择国内节点),每 1 页切换一次 IP,避免单一 IP 被封禁;

    • 请求频率控制:单 IP 每分钟请求≤1 次,两次请求间隔 3-5 秒(模拟用户浏览选品的节奏);

    • Cookie 池管理:维护多个登录态 Cookie(通过不同账号获取),随机携带以降低单一账号风险;

    • 动态 UA 与 Referer:每次请求使用fake_useragent生成随机 User-Agent,Referer 设置为唯品会首页。

    • 问题:唯品会对搜索行为的反爬限制严格,高频请求会触发 IP 封锁(403 错误),尤其针对热门品牌和促销活动。

    • 解决方案

六、最佳实践与合规要点

  1. 系统架构设计采用 “分布式低频率采集” 架构,适配唯品会高反爬特性:
    • 任务调度层:通过定时任务(如 Celery)分发搜索任务,控制单任务并发数≤1;

    • 采集层:多节点分散请求,每个节点绑定独立代理池和 Cookie 池,节点间请求间隔≥15 秒;

    • 存储层:用 Redis 缓存热门搜索结果(2 小时过期,促销价格波动快),MySQL 存储品牌和类目信息;

    • 监控层:实时监控代理存活率、Cookie 有效性及数据完整度,异常时通过短信告警。

  2. 性能优化策略
    • 异步批量搜索:使用aiohttp并发处理多关键词(如 “Adidas 卫衣”“Nike 运动鞋”),控制并发数≤1,适配低频率限制;

    • 按需解析:列表页优先提取product_id、折扣价、销量等核心字段,详细规格通过后续item_get接口补充;

    • 热点抑制:对同一关键词 + 参数组合的搜索,1 小时内仅处理 1 次(返回缓存结果)。

  3. 合规性与风险控制
    • 访问限制:单 IP 日搜索请求≤100 次,避免对平台服务器造成压力,符合 robots 协议;

    • 数据使用边界:不得将商品数据用于恶意比价、虚假宣传,需注明来源 “唯品会”;

    • 版权保护:商品图片和品牌信息受版权保护,使用时遵守《著作权法》,不得擅自商用。

七、总结

唯品会item_search接口的对接核心在于促销场景参数的精准整合折扣与价格的结构化提取低频率高稳定性的采集策略。开发者需重点关注:
  1. 折扣力度、活动类型等促销参数的处理(确保筛选条件生效);

  2. 价格和折扣的数值提取逻辑(支撑价格对比与分析);

  3. 代理池、Cookie 池与请求频率的精细化控制(应对严格反爬)。

通过本文的技术方案,可构建稳定的商品搜索系统,为价格监控、促销选品等场景提供可靠数据支持。实际应用中,需根据平台最新页面结构动态调整解析规则,平衡数据获取效率与合规性。
需要进一步了解唯品会分类 ID 完整映射表动态接口签名生成细节,可以告诉我,我会补充相关内容


群贤毕至

访客