×

蘑菇街 item_search 接口对接全攻略:从入门到精通

万邦科技Lex 万邦科技Lex 发表于2025-10-29 09:10:47 浏览218 评论0

抢沙发发表评论

          注册账号免费测试蘑菇街API数据接口

蘑菇街作为聚焦年轻女性用户的时尚电商平台,其商品搜索功能(对应item_search接口,非官方命名)是获取服饰、美妆、家居等品类商品列表的核心工具,广泛应用于电商选品、竞品分析、趋势监控等场景。由于蘑菇街官方 API 对个人开发者限制严格,多数场景需通过页面解析或第三方服务实现搜索对接。本文将系统讲解item_search接口的对接逻辑、参数解析、加密处理及反爬应对,帮助开发者构建稳定的商品搜索数据获取系统。

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

  1. 核心功能蘑菇街item_search接口通过关键词、分类、价格等多维度条件筛选商品,返回符合条件的列表数据,核心字段包括:
    • 基础信息:商品 ID(item_id)、标题、主图、类目、详情页 URL、上架时间

    • 价格信息:原价、现价、折扣(如 “7 折”)、优惠券标识(如 “满 199 减 20”)

    • 交易数据:月销量(如 “月销 5000+”)、评价数、好评率(如 “97% 好评”)

    • 店铺信息:店铺名称、店铺类型(如 “旗舰店”“普通店”)、所在地

    • 特色标签:是否预售、是否正品、是否包邮、是否网红同款

  2. 典型应用场景
    • 电商卖家选品:按 “夏季连衣裙 小个子” 搜索,筛选高销量、高好评商品

    • 竞品监控:跟踪 “韩系彩妆” 关键词下竞品的价格、销量变化

    • 趋势分析:统计 “2024 秋冬外套” 类目的价格分布、款式热度

    • 比价工具:整合蘑菇街与淘宝、拼多多同款商品,提供价格对比

  3. 接口特性
    • 反爬严格:依赖 JS 加密参数(如_m_h5_tk)、IP 限制、Cookie 验证,搜索接口反爬强度高于详情页

    • 动态加载:搜索结果通过 AJAX 接口动态加载,需解析 JSON 数据(非静态 HTML)

    • 分页限制:默认每页 20 条,最多支持 50 页(约 1000 条结果),超过则返回空

    • C 端属性:数据包含消费者视角字段(如折扣、优惠券、网红标签),与批发平台差异显著

二、对接前置准备(环境与参数解析)

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

    • 加密处理:execjs(执行 JS 生成签名)、hashlib(MD5 加密)

    • 反爬工具:fake_useragent(随机 User-Agent)、proxy_pool(代理 IP 池)

    • 数据解析:jsonpath(提取 JSON 数据)、pandas(清洗列表数据)

    • 开发语言:Python(推荐,适合处理加密参数与动态接口)

    • 核心库:

  2. 搜索 URL 与核心参数蘑菇街搜索依赖动态接口(非静态 URL),核心接口为:
    筛选条件参数名示例值说明
    关键词keyword夏季连衣裙 韩系彩妆支持中文、组合词(如 “显瘦 牛仔裤”)
    分类 IDcateId103508(女装)、103512(彩妆)分类 ID 需通过首页分类接口获取
    价格区间(始)priceMin100最低价格(元),如100表示≥100 元
    价格区间(终)priceMax300最高价格(元),如300表示≤300 元
    排序方式sortsale(销量降序)见 “排序参数表”
    分页page1 2 ... 50页码,默认 1,最大 50
    特色筛选isPreSale1(仅预售)1 - 仅显示预售商品,0 - 不限
    发货地location杭州 广州筛选特定地区发货的商品
    • 移动端搜索接口:https://gateway.mogujie.com/api/search/search

      需通过 POST 请求传递参数,核心参数如下(封装在 JSON 中):

  3. 排序参数表蘑菇街搜索支持多种排序方式,对应sort参数值如下:
    排序方式sort参数值适用场景
    综合推荐default默认排序,平衡销量与相关性
    销量降序sale筛选热门商品(高月销优先)
    价格升序priceAsc低价商品筛选
    价格降序priceDesc高端商品筛选
    最新上架new新款监控(如当季新品)
  4. 加密参数解析(核心难点)搜索接口需携带与详情页相同的_m_h5_tk_m_h5_tk_enc参数,生成逻辑如下:
    • 从 Cookie 中提取_m_h5_tktoken前缀(格式为token_xxxx);

    • 拼接token + "&" + timestamp + "&" + appKey + "&" + dataappKey=12574478);

    • 通过 MD5 加密生成签名,最终_m_h5_tk=token_签名

三、接口调用流程(基于动态接口)

以 “搜索夏季连衣裙,价格 100-300 元,按销量降序排序” 为例,核心流程为参数组装→加密参数生成→接口请求→数据解析→分页遍历
  1. 请求参数组装搜索接口的请求体(data)为 JSON 格式,示例:
    python
    运行
    data = {
        "keyword": "夏季连衣裙",
        "cateId": "",  # 不限制分类
        "priceMin": 100,
        "priceMax": 300,
        "sort": "sale",
        "page": 1,
        "pageSize": 20  # 每页条数,固定20}
  2. 加密参数与请求头构建
    • 生成_m_h5_tk和时间戳:

      python
      运行
      import hashlibimport timedef generate_search_sign(cookie_tk, data_str):
          token = cookie_tk.split("_")[0]
          timestamp = str(int(time.time() * 1000))
          app_key = "12574478"
          sign_str = f"{token}&{timestamp}&{app_key}&{data_str}"
          sign = hashlib.md5(sign_str.encode()).hexdigest()
          return f"{token}_{sign}", timestamp
    • 请求头需包含移动端 User-Agent、Referer、Cookie:

      python
      运行
      headers = {
          "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
          "Referer": "https://m.mogujie.com/search",
          "Cookie": "_m_h5_tk=xxx; _m_h5_tk_enc=xxx; ...",  # 从浏览器获取
          "Content-Type": "application/json"}
  3. 接口响应解析搜索接口返回 JSON 数据,核心商品列表在data.result.list中,单条商品结构示例:
    json
    {
      "itemId": "12345678",
      "title": "夏季碎花连衣裙小个子显瘦",
      "img": "https://img.mogujie.com/xxx.jpg",
      "price": 159,
      "originalPrice": 299,
      "saleCount": 5200,  # 月销量  "commentCount": 1200,
      "shopName": "时尚女装店",
      "tags": ["包邮", "网红同款"]}
  4. 分页处理
    • 分页通过page参数控制,每次请求递增页码;

    • 终止条件:返回的商品列表长度 < 20(最后一页)或页码≥50;

    • 分页间隔:每页请求间隔 2-4 秒(随机波动),避免触发反爬。

四、代码实现示例(Python)

以下是item_search接口的完整实现,包含参数加密、动态接口调用、分页遍历及数据解析:
import requests
import time
import random
import json
import hashlib
from fake_useragent import UserAgent
from typing import List, Dict

class MogujieSearchApi:
    def __init__(self, cookie: str, proxy_pool: List[str] = None):
        self.search_api = "https://gateway.mogujie.com/api/search/search"
        self.app_key = "12574478"
        self.cookie = cookie
        self.cookie_tk = self._extract_cookie_tk()  # 提取_m_h5_tk
        self.ua = UserAgent()
        self.proxy_pool = proxy_pool

    def _extract_cookie_tk(self) -> str:
        """从Cookie中提取_m_h5_tk"""
        for cookie in self.cookie.split(";"):
            if "_m_h5_tk" in cookie:
                return cookie.split("=")[1].strip()
        raise ValueError("Cookie中未找到_m_h5_tk,请检查Cookie有效性")

    def _generate_sign(self, data_str: str) -> tuple:
        """生成加密参数_m_h5_tk和timestamp"""
        token = self.cookie_tk.split("_")[0]
        timestamp = str(int(time.time() * 1000))
        sign_str = f"{token}&{timestamp}&{self.app_key}&{data_str}"
        sign = hashlib.md5(sign_str.encode()).hexdigest()
        return f"{token}_{sign}", timestamp

    def _get_headers(self) -> Dict[str, str]:
        """生成请求头"""
        return {
            "User-Agent": self.ua.mobile,  # 移动端UA反爬较松
            "Referer": "https://m.mogujie.com/search",
            "Origin": "https://m.mogujie.com",
            "Cookie": self.cookie,
            "Content-Type": "application/json"
        }

    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 _parse_items(self, response_data: Dict) -> List[Dict]:
        """解析商品列表数据"""
        items = []
        for item in response_data.get("data", {}).get("result", {}).get("list", []):
            items.append({
                "item_id": item.get("itemId", ""),
                "title": item.get("title", ""),
                "main_image": item.get("img", ""),
                "url": f"https://item.mogujie.com/detail/{item.get('itemId', '')}.htm",
                "price": {
                    "current": item.get("price", 0),
                    "original": item.get("originalPrice", 0),
                    "discount": f"{round(item.get('price', 0)/item.get('originalPrice', 1)*10, 1)}折" 
                    if item.get("originalPrice", 0) > 0 else ""
                },
                "sales": {
                    "monthly": item.get("saleCount", 0),
                    "comment_count": item.get("commentCount", 0)
                },
                "shop": {
                    "name": item.get("shopName", ""),
                    "location": item.get("location", "")
                },
                "tags": item.get("tags", [])
            })
        return items

    def item_search(self, 
                   keyword: str, 
                   cate_id: str = "", 
                   price_min: int = None, 
                   price_max: int = None, 
                   sort: str = "default", 
                   location: str = "", 
                   page_limit: int = 5) -> Dict:
        """
        搜索蘑菇街商品列表
        :param keyword: 搜索关键词
        :param cate_id: 分类ID(可选)
        :param price_min: 最低价格(元)
        :param price_max: 最高价格(元)
        :param sort: 排序方式(default/sale/priceAsc等)
        :param location: 发货地(可选)
        :param page_limit: 最大页数(默认5)
        :return: 标准化搜索结果
        """
        try:
            all_items = []
            current_page = 1

            while current_page <= page_limit:
                # 1. 构建请求数据
                data = {
                    "keyword": keyword,
                    "cateId": cate_id,
                    "sort": sort,
                    "page": current_page,
                    "pageSize": 20
                }
                if price_min is not None:
                    data["priceMin"] = price_min
                if price_max is not None:
                    data["priceMax"] = price_max
                if location:
                    data["location"] = location
                data_str = json.dumps(data, ensure_ascii=False)

                # 2. 生成加密参数
                m_h5_tk, timestamp = self._generate_sign(data_str)
                params = {
                    "_m_h5_tk": m_h5_tk,
                    "_m_h5_tk_enc": "",
                    "timestamp": timestamp,
                    "appKey": self.app_key
                }

                # 3. 发送请求(带随机延迟)
                time.sleep(random.uniform(2, 4))
                headers = self._get_headers()
                proxy = self._get_proxy()

                response = requests.post(
                    url=self.search_api,
                    params=params,
                    data=data_str,
                    headers=headers,
                    proxies=proxy,
                    timeout=10
                )
                response.raise_for_status()
                response_data = response.json()

                # 4. 检查接口状态
                if not response_data.get("success"):
                    error_code = response_data.get("code", -1)
                    if error_code == -1001:
                        return {"success": False, "error_msg": "_m_h5_tk失效,请更新Cookie", "code": -1001}
                    return {"success": False, "error_msg": f"接口返回失败: {response_data.get('msg')}", "code": error_code}

                # 5. 解析商品并分页
                items = self._parse_items(response_data)
                if not items:
                    break  # 无数据,终止分页

                all_items.extend(items)
                # 若当前页商品数<20,说明是最后一页
                if len(items) < 20:
                    break

                current_page += 1

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

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

        except requests.exceptions.HTTPError as e:
            if "403" in str(e):
                return {"success": False, "error_msg": "触发反爬,建议更换代理或Cookie", "code": 403}
            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__":
    # 从浏览器获取的Cookie(需包含_m_h5_tk)
    COOKIE = "_m_h5_tk=xxx; _m_h5_tk_enc=xxx; ..."  # 替换为实际Cookie
    # 代理池(替换为有效代理)
    PROXIES = [
        "http://123.45.67.89:8888",
        "http://98.76.54.32:8080"
    ]

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

    # 搜索“夏季连衣裙”,价格100-300元,按销量降序,最多3页
    result = search_api.item_search(
        keyword="夏季连衣裙",
        price_min=100,
        price_max=300,
        sort="sale",
        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']['current']}元 | 原价{item['price']['original']}元 | {item['price']['discount']}")
            print(f"销量:月销{item['sales']['monthly']}件 | 评价{item['sales']['comment_count']}条")
            print(f"店铺:{item['shop']['name']}({item['shop']['location']})")
            print(f"标签:{','.join(item['tags'])} | 详情页:{item['url']}")
    else:
        print(f"搜索失败:{result['error_msg']}(错误码:{result.get('code')})")

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

  1. 加密参数_m_h5_tk的动态维护
    • 定期(1.5 小时)通过浏览器或 Selenium 自动刷新 Cookie,更新_m_h5_tk

    • 代码中检测-1001错误码,触发 Cookie 更新逻辑(示例代码已包含错误处理);

    • 维护 Cookie 池(3-5 个有效 Cookie),失效时自动切换。

    • 问题_m_h5_tk有效期约 2 小时,失效后接口返回-1001错误,需重新生成。

    • 解决方案

  2. 搜索接口数据碎片化
    • 搜索接口优先提取核心字段(价格、销量、标题),非核心字段通过后续item_get接口补充;

    • 对批量搜索任务,采用 “搜索列表→筛选目标商品→详情页补充” 的分步策略,减少无效请求。

    • 问题:部分字段(如好评率、店铺评分)未在搜索接口返回,需结合详情页补充。

    • 解决方案

  3. 反爬机制对抗
    • 代理 IP 轮换:使用高匿代理池,每 2-3 页切换 IP,避免单一 IP 被标记;

    • 请求频率控制:单 IP 每分钟请求≤3 次,两次请求间隔 2-4 秒(随机波动);

    • UA 与 Cookie 多样化:随机切换移动端 User-Agent,搭配不同 Cookie 池降低风险;

    • 验证码处理:若触发验证码,暂停当前代理并切换 Cookie,使用 Selenium 手动验证(适合低频场景)。

    • 问题:蘑菇街搜索接口反爬严格,高频请求会触发 IP 封锁、参数失效或验证码。

    • 解决方案

  4. 分页限制与数据量控制
    • 合理设置page_limit(建议≤20),平衡数据量与相关性;

    • 对大额需求,通过多关键词拆分搜索(如 “夏季连衣裙 长款”“夏季连衣裙 短款”);

    • 分页时检查商品 ID 去重,避免重复数据(示例代码中unique_items逻辑)。

    • 问题:蘑菇街搜索最多返回 50 页(1000 条),且超过 30 页后数据质量下降(重复或低相关商品)。

    • 解决方案

六、最佳实践与合规要点

  1. 系统架构设计采用 “加密参数管理 + 分布式搜索” 架构,适配蘑菇街严格反爬:
    • 参数管理层:集中生成与缓存_m_h5_tk,定时更新,支持多 Cookie 池切换;

    • 任务调度层:通过消息队列(如 RabbitMQ)分发搜索任务,控制单队列并发数≤2;

    • 采集层:多节点并行搜索,每个节点绑定独立代理与 Cookie,节点间间隔≥5 秒;

    • 存储层:用 Redis 缓存搜索结果(10 分钟过期),MySQL 存储历史趋势数据。

  2. 性能优化策略
    • 异步批量搜索:使用aiohttp并发处理多关键词(控制并发数≤3),共享代理池;

    • 增量搜索:对相同关键词 + 条件,1 小时内返回缓存结果,减少重复请求;

    • 失败重试机制:对403(反爬)、-1001(参数失效)错误,自动重试 2 次(更换资源后)。

  3. 合规性与风险控制
    • 遵守平台规则:蘑菇街开放平台明确禁止未授权爬取,商业使用需申请官方 API;

    • 频率限制:单 IP 日请求量≤300 次,避免对服务器造成压力;

    • 数据用途:不得将数据用于恶意竞争、虚假宣传,需注明来源 “蘑菇街”。

  4. 反爬适应性调整
    • 定期(每周)检查接口参数变化(如appKey或加密逻辑更新),同步更新代码;

    • 当搜索接口升级(如新增sign2参数),通过抓包工具(Charles)逆向最新 JS 代码,提取新规则;

    • 建立降级方案:动态接口失败时,fallback 到 PC 端静态页面解析(适合数据精度要求不高的场景)。

七、总结

蘑菇街item_search接口的对接核心在于加密参数_m_h5_tk的动态维护严格的反爬对抗策略分页数据的高效处理。开发者需重点关注:
  1. 搜索接口的参数加密逻辑(与详情页共用_m_h5_tk生成规则);

  2. 代理池与 Cookie 池的精细化管理(应对高频请求与参数失效);

  3. 分页限制与数据去重(平衡数据量与有效性)。

通过本文的技术方案,可构建稳定的商品搜索系统,为电商选品、市场分析等场景提供数据支持。实际应用中,需优先考虑官方 API 接入,确保合规使用数据。


群贤毕至

访客