一号店 item_search 接口对接全攻略:从入门到精通

一号店(现并入京东生态,保留独立运营入口)作为聚焦快消品的综合电商平台,其商品搜索功能(
item_search
接口,非官方命名)是获取超市生鲜、家居百货、食品饮料等品类列表的核心工具。数据包含会员价、产地溯源、配送时效等快消特有字段,对电商比价、消费趋势分析、供应链研究等场景具有重要价值。由于无公开官方 API,开发者需通过页面解析实现搜索对接。本文系统讲解接口逻辑、参数解析、技术实现及反爬策略,助你构建稳定的快消品列表获取系统。

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

核心功能一号店
item_search
接口通过关键词、分类、价格等条件筛选商品,返回符合条件的列表数据,核心字段聚焦快消特性:

基础信息:商品 ID(
item_id
)、标题(含规格)、主图、品牌、类目(如 “休闲食品”“生鲜水果”)、详情页 URL价格信息:售价、会员价、原价、折扣(如 “满 199 减 50”)、运费政策(如 “满 59 元包邮”)快消属性:产地(如 “山东烟台”)、规格(如 “500g / 盒”)、保质期(食品类)、是否进口交易数据:月销量(如 “已售 1000+”)、评价数、好评率、库存状态(如 “现货”)服务标签:配送时效(如 “今日达”“次日达”)、售后政策(如 “7 天无理由”)、是否京东物流

典型应用场景

快消品比价工具:按 “进口牛奶” 搜索,对比不同品牌的会员价与折扣力度生鲜市场分析:统计 “国产水果” 类目的产地分布、价格区间及销量 Top10 商品促销活动监控:跟踪 “零食礼盒” 在节日期间的折扣变化与库存波动选品辅助:筛选 “今日达 + 月销 500+” 的日用品,优化社区团购供应链

接口特性

平台关联性:部分页面与京东共享技术架构,反爬机制、动态加载逻辑接近京东非官方性:依赖 HTML 解析,无公开 API,页面结构受京东调整影响反爬机制:包含 IP 限制、User-Agent 校验、Cookie 验证(会员价需登录)、请求频率监控分页结构:默认每页 20-30 条,最多支持 50 页(约 1500 条结果),分页参数在 URL 中体现混合加载:基础列表数据静态嵌入 HTML,会员价、实时库存等通过 AJAX 动态加载

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

开发环境

开发语言:Python(推荐,生态丰富,适合快速处理 HTML 与反爬)核心库:
网络请求:
requests
(同步)、
aiohttp
(异步批量搜索)页面解析:
BeautifulSoup
(静态 HTML)、
lxml
(XPath 提取列表数据)反爬工具:
fake_useragent
(随机 UA)、
proxy_pool
(代理 IP 池)数据处理:
re
(正则提取价格、销量)、
urllib.parse
(URL 参数编码)

搜索 URL 结构与核心参数一号店搜索页基础 URL 为:
https://search.yhd.com/c0-0/k{关键词}/
,核心参数通过 URL 路径与查询字符串传递:

筛选条件 参数形式 示例值 说明
关键词 URL 路径
进口牛奶
 → 编码后为
%E8%BF%9B%E5%8F%A3%E7%89%9B%E5%A5%B6
支持商品名、品牌、规格(如 “250ml”)
分类 ID
c
参数

c10087
(休闲食品)、
c10108
(生鲜)
分类 ID 需从首页分类导航解析获取
价格区间(始)
pr1
参数

50
最低价格(元)
价格区间(终)
pr2
参数

100
最高价格(元)
产地
area
参数

1_2800
(山东)、
2_3100
(进口)
地区编码需抓包获取(如 “2_3100” 对应进口)
排序方式
sort
参数

sale-desc
(销量降序)
见 “排序参数表”
分页
page
参数

1
 
2
 … 
50
页码,默认 1,最大 50

排序参数表一号店搜索支持多种排序方式,对应
sort
参数值如下:

排序方式
sort
参数值
适用场景
综合推荐 空值 默认排序,平衡相关性与销量
销量降序
sale-desc
筛选爆款商品(如 “已售 1000+”)
价格升序
price-asc
低价商品筛选(如促销零食)
价格降序
price-desc
高端商品筛选(如进口红酒)
好评率降序
praise-desc
品质优先筛选(如母婴食品)

分类 ID 与地区编码获取

分类 ID:访问一号店首页(
https://www.yhd.com
),通过开发者工具查看分类菜单的
href
(如
/c10087/
中的
10087
为休闲食品分类 ID);地区编码:选择 “产地” 筛选后,URL 中
area
参数值即为编码(如
area=2_3100
对应进口商品,
area=1_2800
对应山东)。

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

以 “搜索进口牛奶,价格 50-100 元,按销量降序排序” 为例,流程为参数组装→URL 构建→请求发送→列表解析→分页遍历

URL 构建示例组合参数生成目标搜索 URL:

python

运行



keyword = "进口牛奶"
cid = "10107"  # 乳制品分类ID
price_min = 50
price_max = 100
area = "2_3100"  # 进口产地
sort = "sale-desc"  # 销量降序
page = 1
# 关键词URL编码
encoded_keyword = urllib.parse.quote(keyword, encoding="utf-8")
# 构建URL
url = f"https://search.yhd.com/c{cid}-0/k{encoded_keyword}/?pr1={price_min}&pr2={price_max}&area={area}&sort={sort}&page={page}"

请求头与反爬伪装模拟浏览器请求头,需包含登录态 Cookie 以获取会员价:

python

运行



headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
    "Referer": "https://www.yhd.com/",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Cookie": "uuid=xxx; user_key=xxx; JSESSIONID=xxx"  # 登录后获取的Cookie
}

页面解析与数据提取搜索结果列表通常在 HTML 的
<div class="itemBox">
标签内,每条商品信息包含以下核心字段:

字段 解析方式(CSS 选择器示例) 说明
商品 ID
a
标签的
href
中提取(如
/item/123456

123456
唯一标识
标题
.proName
的文本
如 “某品牌纯牛奶 250ml*24 盒”
主图
.productImg img

src
属性
商品主图 URL
售价
.price
的文本(去除 “¥”)
如 “89.9”(元)
会员价
.memberPrice
的文本(需登录 Cookie)
如 “79.9”(元)
产地
.origin
的文本
如 “德国”
月销量
.saleNum
的文本(提取数字)
如 “已售 1200+” 提取 “1200”
配送时效
.delivery
的文本
如 “今日达”

分页处理

分页通过
page
参数控制,前 50 页为有效数据,超过则返回重复内容;终止条件:当前页商品数量 < 20(最后一页)或页码≥50;分页间隔:每页请求间隔 2-4 秒(随机波动),京东系平台对高频访问敏感,需严格控制频率。

四、代码实现示例(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
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class YhdSearchApi:
    def __init__(self, proxy_pool: List[str] = None, cookie: str = ""):
        self.base_url = "https://search.yhd.com/c{cid}-0/k{keyword}/"
        self.ua = UserAgent()
        self.proxy_pool = proxy_pool  # 代理池列表,如["http://ip:port", ...]
        self.cookie = cookie  # 登录态Cookie(用于获取会员价)
        # 分类ID映射(简化版,需根据实际页面更新)
        self.category_map = {
            "休闲食品": "10087",
            "生鲜水果": "10108",
            "乳制品": "10107",
            "家居清洁": "10226"
        }
        # 地区编码映射(简化版)
        self.area_map = {
            "进口": "2_3100",
            "山东": "1_2800",
            "广东": "1_2000",
            "浙江": "1_3300"
        }
 
    def _get_headers(self) -> Dict[str, str]:
        """生成随机请求头"""
        headers = {
            "User-Agent": self.ua.random,
            "Referer": "https://www.yhd.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_sales(self, sales_str: str) -> int:
        """清洗销量字符串(提取数字,处理“1000+”等格式)"""
        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.proName")["href"]
        item_id = re.search(r"/item/(d+)", link).group(1) if link else ""
 
        # 提取会员价(需登录Cookie)
        member_price_str = item_soup.select_one(".memberPrice")?.text.strip() or ""
 
        return {
            "item_id": item_id,
            "title": item_soup.select_one(".proName")?.text.strip() or "",
            "main_image": item_soup.select_one(".productImg img")?.get("src") or "",
            "url": f"https://item.yhd.com{link}" if link.startswith("/") else link,
            "price": {
                "current": self._clean_price(item_soup.select_one(".price")?.text.strip() or ""),
                "original": self._clean_price(item_soup.select_one(".originalPrice")?.text.strip() or ""),
                "member": self._clean_price(member_price_str)
            },
            "product": {
                "origin": item_soup.select_one(".origin")?.text.strip() or "",
                "spec": item_soup.select_one(".spec")?.text.strip() or ""  # 规格(如250ml*24盒)
            },
            "sales": {
                "monthly": self._clean_sales(item_soup.select_one(".saleNum")?.text.strip() or ""),
                "comment_count": self._clean_sales(item_soup.select_one(".comment")?.text.strip() or "")
            },
            "service": {
                "delivery": item_soup.select_one(".delivery")?.text.strip() or "",  # 配送时效
                "after_sale": item_soup.select_one(".afterSale")?.text.strip() or ""
            },
            "brand": item_soup.select_one(".brandName")?.text.strip() or ""
        }
 
    def _parse_page(self, html: str) -> List[Dict]:
        """解析页面的商品列表"""
        soup = BeautifulSoup(html, "lxml")
        # 商品列表容器可能随页面更新变化,需验证
        item_list = soup.select("div.itemBox")
        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(".pageBox")
        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, 
                   area: str = "", 
                   sort: str = "", 
                   page_limit: int = 5) -> Dict:
        """
        搜索一号店商品列表
        :param keyword: 搜索关键词
        :param category: 分类名称(如“乳制品”)或分类ID
        :param price_min: 最低价格(元)
        :param price_max: 最高价格(元)
        :param area: 产地名称(如“进口”)或地区编码(如“2_3100”)
        :param sort: 排序方式(sale-desc/price-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:
                cid = self.category_map[category]
            else:
                cid = category if category else "0"  # cid=0表示全分类
            # 转换地区名称为编码
            if area in self.area_map:
                area_code = self.area_map[area]
            else:
                area_code = area if area 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 price_min is not None:
                    params["pr1"] = price_min
                if price_max is not None:
                    params["pr2"] = price_max
                if area_code:
                    params["area"] = area_code
                if sort:
                    params["sort"] = sort
 
                # 构建URL
                url = self.base_url.format(cid=cid, keyword=encoded_keyword)
 
                # 发送请求(带随机延迟)
                time.sleep(random.uniform(2, 4))  # 京东系平台间隔需控制
                headers = self._get_headers()
                proxy = self._get_proxy()
 
                response = requests.get(
                    url=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
 
            # 去重(基于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,
                "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}
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# 使用示例
if __name__ == "__main__":
    # 代理池(替换为有效代理)
    PROXIES = [
        "http://123.45.67.89:8888",
        "http://98.76.54.32:8080"
    ]
    # 登录态Cookie(从浏览器获取,用于查看会员价)
    COOKIE = "uuid=xxx; user_key=xxx; JSESSIONID=xxx"
 
    # 初始化API客户端
    search_api = YhdSearchApi(proxy_pool=PROXIES, cookie=COOKIE)
 
    # 搜索“进口牛奶”,分类“乳制品”,价格50-100元,进口产地,按销量降序,最多3页
    result = search_api.item_search(
        keyword="进口牛奶",
        category="乳制品",
        price_min=50,
        price_max=100,
        area="进口",
        sort="sale-desc",
        page_limit=3
    )
 
    if result["success"]:
        print(f"搜索成功:共找到 {result['total']} 件商品,处理 {result['page_processed']} 页")
        for i, item in enumerate(result["items"][:5]):  # 打印前5条
            print(f"
商品 {i+1}:")
            print(f"标题:{item['title'][:50]}...")  # 截断长标题
            print(f"价格:¥{item['price']['current']} | 会员价:¥{item['price']['member']} | 原价:¥{item['price']['original']}")
            print(f"产地:{item['product']['origin']} | 规格:{item['product']['spec']}")
            print(f"品牌:{item['brand']} | 月销:{item['sales']['monthly']}件 | 评价:{item['sales']['comment_count']}条")
            print(f"配送:{item['service']['delivery']} | 售后:{item['service']['after_sale']}")
            print(f"详情页:{item['url']}")
    else:
        print(f"搜索失败:{result['error_msg']}(错误码:{result.get('code')})")
五、关键技术难点与解决方案

快消品筛选条件处理(产地、规格)

问题:产地(如 “进口”“山东”)和规格(如 “250ml”)是快消搜索的核心维度,但参数值为编码(如 “2_3100” 对应进口),需映射转换。解决方案
建立名称 – 编码映射表(如
area_map
中 “进口” 对应 “2_3100”),用户输入名称时自动转换为参数;定期抓包更新映射关系(平台可能调整编码),确保筛选条件生效;示例代码中
_parse_item
函数提取
origin

spec
字段,直接反映商品的产地与规格。

会员价与登录态管理

问题:会员价仅对登录用户可见,未登录状态下
memberPrice
字段为空或隐藏。解决方案
提前通过浏览器登录一号店,获取
Cookie
(含
user_key
 
JSESSIONID
等字段),在请求头中携带;检测响应中是否包含 “登录查看会员价” 提示,若有则自动切换有效
Cookie
重试;对未登录场景,在返回结果中明确标记 “会员价不可见(需登录)”,避免数据误解。

反爬机制对抗

问题:一号店并入京东后,反爬机制与京东趋同,高频请求易触发 IP 封锁(403 错误)或验证码。解决方案
代理 IP 轮换:使用高匿代理池,每 2-3 页切换一次代理,优先选择与用户真实地区匹配的 IP;请求频率控制:单 IP 每页请求间隔 2-4 秒(随机波动),模拟用户浏览快消品的决策节奏;Cookie 池策略:维护多个登录态 Cookie,随机携带以降低单一账号风险;动态参数伪装:在 URL 后添加随机时间戳(如
&t=1620000000
),避免请求缓存被识别为爬虫。

平台数据联动处理

问题:部分商品详情页跳转至京东(
item.jd.com
),导致搜索结果中
item_id
失效或解析错误。解决方案
解析商品链接时检测域名,若为京东域名,自动适配京东
item_get
接口解析规则;对跳转商品标记 “已迁移至京东”,并提取京东
item_id
(如从
item.jd.com/123456.html
中提取
123456
);建立商品 ID 映射表,记录一号店与京东的 ID 对应关系,确保数据连贯性。

六、最佳实践与合规要点

系统架构设计采用 “分布式低频率采集” 架构,适配快消品数据特性:

任务分发层:通过消息队列(如 RabbitMQ)分发搜索任务,控制单任务并发数≤2;采集层:多节点并行采集,每个节点绑定独立代理池,节点间请求间隔≥10 秒;存储层:用 Redis 缓存热门搜索结果(1 小时过期,快消品价格波动较快),MySQL 存储历史数据(用于消费趋势分析);监控层:实时监控代理存活率、页面跳转率,异常时通过企业微信告警。

性能优化策略

异步批量搜索:使用
aiohttp
并发处理多关键词(如 “进口牛奶”“酸奶”),控制并发数≤3;按需解析:列表页优先提取
item_id
、价格、产地等核心字段,详情信息通过后续
item_get
接口补充;热点抑制:对同一关键词 + 条件的搜索,1 小时内仅处理 1 次(返回缓存结果)。

合规性与风险控制

频率限制:单 IP 日搜索请求≤300 次,单关键词日搜索≤10 次,避免对服务器造成压力;数据使用边界:不得将数据用于恶意比价、虚假宣传或商业售卖,需注明数据来源 “一号店”;法律风险规避:食品类数据涉及安全信息(如保质期、产地),使用时需遵守《食品安全法》相关规定,不得篡改数据。

七、总结

一号店
item_search
接口的对接核心在于快消品特有筛选条件的精准映射(产地、规格)、会员价与登录态的协同管理低频率高稳定性的采集策略。开发者需重点关注:

名称 – 编码映射表的维护(确保筛选条件生效);代理池与请求频率的精细化控制(应对京东系反爬);平台数据联动的动态适配(处理页面跳转与 ID 失效)。

通过本文的技术方案,可构建稳定的商品搜索系统,为快消品比价、消费趋势分析等场景提供可靠数据支持。实际应用中,需根据平台最新状态动态调整解析规则,平衡数据获取效率与合规性。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容