×

洋码头 item_search 接口对接全攻略:从入门到精通

万邦科技Lex 万邦科技Lex 发表于2025-11-10 09:16:31 浏览88 评论0

抢沙发发表评论

            注册账号免费测试洋码头API数据接口

洋码头作为国内头部跨境电商平台,以 “海外直购” 为核心卖点,商品覆盖美妆、奢侈品、母婴、保健品等品类,其搜索功能(item_search接口,非官方命名)是获取跨境商品列表的核心入口。数据包含采购地、关税政策、物流时效等跨境特有字段,对跨境比价、消费趋势分析、选品决策等场景具有重要价值。由于平台无公开官方 API,开发者需通过页面解析实现搜索对接。本文系统讲解接口逻辑、参数解析、技术实现及反爬策略,助你构建稳定的跨境商品列表获取系统。

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

  1. 核心功能洋码头item_search接口通过关键词、分类、价格等条件筛选商品,返回符合条件的列表数据,核心字段聚焦跨境特性:
    • 基础信息:商品 ID(item_id)、标题(含版本 / 规格)、主图、品牌、类目(如 “美妆护肤”“母婴用品”)、详情页 URL

    • 价格信息:售价(含税费)、原价、折扣(如 “黑五 8 折”)、运费政策(如 “满 399 元包直邮”)、关税说明

    • 跨境属性:采购地(如 “日本东京”)、物流类型(直邮 / 保税仓)、配送时效(如 “5-10 天到货”)、是否正品溯源

    • 商品特性:规格(如 “100ml”)、版本(如 “本土版”“国际版”)、适用人群(如 “孕妇可用”)

    • 交易数据:销量(如 “已售 500+”)、评价数、好评率、库存状态(如 “海外现货”)

    • 卖家信息:卖家名称、所在地(如 “韩国首尔”)、资质(如 “认证买手”)

  2. 典型应用场景
    • 跨境比价工具:按 “兰蔻小黑瓶” 搜索,对比不同卖家的含税价、运费及采购地

    • 选品分析:筛选 “保税仓发货 + 月销 300+” 的母婴用品,优化跨境电商供应链

    • 促销监控:跟踪 “黑五” 期间 “奢侈品包袋” 类目的折扣力度与库存变化

    • 消费趋势研究:统计 “欧美彩妆” 的采购地分布、版本偏好(如 “美版” vs “欧版”)

  3. 接口特性
    • 跨境专业性:筛选条件与数据字段深度结合跨境场景(如 “采购地筛选”“关税包含与否”)

    • 非官方性:依赖 HTML 解析,无公开 API,动态加载内容多(如筛选后结果、分页数据)

    • 反爬机制:包含 IP 限制、User-Agent 校验、Cookie 验证、动态参数(如sign)、请求频率监控

    • 分页结构:默认每页 20 条,最多支持 50 页(约 1000 条结果),分页参数通过 URL 或 AJAX 传递

    • 混合加载:基础列表数据静态嵌入 HTML,部分筛选结果通过带签名的 AJAX 接口动态加载

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

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

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

    • 反爬工具:fake_useragent(随机 UA)、proxy_pool(代理 IP 池)、execjs(处理动态签名)

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

    • 开发语言:Python(推荐,生态丰富,适合处理复杂 HTML 与反爬)

    • 核心库:

  2. 搜索 URL 结构与核心参数洋码头搜索页基础 URL 为:https://www.yangmatou.com/search/{关键词},核心参数通过查询字符串传递:
    筛选条件参数名示例值说明
    关键词keyword兰蔻小黑瓶 → 编码后为%E5%85%B0%E8%94%BB%E5%B0%8F%E9%BB%91%E7%93%B6支持商品名、品牌、规格
    分类 IDcategoryId101(美妆护肤)、203(母婴用品)分类 ID 需从首页分类导航解析获取
    价格区间(始)priceMin500最低价格(元,含税费)
    价格区间(终)priceMax1000最高价格(元,含税费)
    采购地purchaseAreaus(美国)、jp(日本)地区编码需抓包获取(如 “us” 对应美国)
    物流类型logisticsdirect(直邮)、bonded(保税仓)筛选发货方式
    排序方式sortsales(销量降序)、price_asc(价格升序)见 “排序参数表”
    分页page1 2 ... 50页码,默认 1,最大 50
  3. 排序参数表洋码头搜索支持多种排序方式,对应sort参数值如下:
    排序方式sort参数值适用场景
    综合推荐空值默认排序,平衡相关性与销量
    销量降序sales筛选爆款商品(如 “已售 500+”)
    价格升序price_asc低价商品筛选(如平价彩妆)
    价格降序price_desc高端商品筛选(如奢侈品)
    最新上架new新品监控(如 “刚上架的海外新品”)
  4. 分类 ID 与采购地编码获取
    • 分类 ID:访问洋码头首页(https://www.yangmatou.com),通过开发者工具查看分类菜单的href(如/category/101中的101为美妆护肤分类 ID);

    • 采购地编码:选择 “采购地” 筛选后,URL 中purchaseArea参数值即为编码(如purchaseArea=jp对应日本)。

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

以 “搜索兰蔻小黑瓶,价格 500-1000 元,采购地法国,按销量降序排序” 为例,流程为参数组装→URL 构建→请求发送→列表解析→分页遍历
  1. URL 构建示例组合参数生成目标搜索 URL:
    python
    运行
    keyword = "兰蔻小黑瓶"category_id = "101"  # 美妆护肤分类IDprice_min = 500price_max = 1000purchase_area = "fr"  # 法国编码logistics = "direct"  # 直邮sort = "sales"  # 销量降序page = 1# 关键词URL编码encoded_keyword = urllib.parse.quote(keyword, encoding="utf-8")# 构建URLurl = f"https://www.yangmatou.com/search/{encoded_keyword}?categoryId={category_id}&priceMin={price_min}&priceMax={price_max}&purchaseArea={purchase_area}&logistics={logistics}&sort={sort}&page={page}"
  2. 请求头与反爬伪装模拟浏览器请求头,需包含登录态 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.yangmatou.com/",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Cookie": "user_id=xxx; token=xxx; session_id=xxx"  # 登录后获取的Cookie}
  3. 页面解析与数据提取搜索结果列表通常在 HTML 的<div class="product-item">标签内,每条商品信息包含以下核心字段:
    字段解析方式(CSS 选择器示例)说明
    商品 IDa标签的href中提取(如/goods/123456123456唯一标识
    标题.product-title的文本如 “兰蔻小黑瓶精华 100ml 法版”
    主图.product-img imgsrc属性商品主图 URL
    售价(含税).price-current的文本(去除 “¥”)如 “899.00”(元)
    原价.price-original的文本(去除 “¥”)如 “1099.00”(元)
    采购地.purchase-area的文本如 “法国・巴黎”
    物流类型.logistics-tag的文本如 “海外直邮”
    月销量.sales-count的文本(提取数字)如 “已售 520 件” 提取 “520”
  4. 动态筛选与签名参数处理部分高级筛选(如 “正品溯源”“卖家等级”)会触发 AJAX 请求,接口需携带动态sign参数:
    • 动态接口示例:https://api.yangmatou.com/search?keyword=xxx&sign=xxx

    • sign参数生成逻辑与item_get接口类似(参数拼接 + 密钥加密),需通过逆向 JS 代码获取;

    • 处理方式:对简单筛选(关键词、价格、分类)优先使用 URL 参数,复杂筛选需调用带签名的动态接口。

  5. 分页处理
    • 分页通过page参数控制,前 50 页为有效数据,超过则返回重复内容;

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

    • 分页间隔:每页请求间隔 3-5 秒(随机波动),跨境平台对高频访问敏感,需严格控制频率。

四、代码实现示例(Python)

以下是item_search接口的完整实现,包含多条件筛选、分页遍历、数据解析及反爬处理(签名生成逻辑需根据实际 JS 逆向补充):
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 YangmatouSearchApi:
    def __init__(self, proxy_pool: List[str] = None, cookie: str = ""):
        self.base_url = "https://www.yangmatou.com/search/{keyword}"
        self.api_url = "https://api.yangmatou.com/search"  # 动态筛选接口
        self.ua = UserAgent()
        self.proxy_pool = proxy_pool  # 代理池列表,如["http://ip:port", ...]
        self.cookie = cookie  # 登录态Cookie
        self.secret_key = "xxx"  # 从JS逆向获取的签名密钥(需实际破解)
        # 分类ID映射(简化版)
        self.category_map = {
            "美妆护肤": "101",
            "母婴用品": "203",
            "奢侈品": "302",
            "保健品": "401"
        }
        # 采购地编码映射(简化版)
        self.area_map = {
            "美国": "us",
            "日本": "jp",
            "法国": "fr",
            "韩国": "kr"
        }

    def _get_headers(self) -> Dict[str, str]:
        """生成随机请求头"""
        headers = {
            "User-Agent": self.ua.random,
            "Referer": "https://www.yangmatou.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 _generate_sign(self, params: Dict[str, str]) -> str:
        """生成动态接口签名(需根据实际JS逆向逻辑实现)"""
        # 示例:按key排序后拼接+密钥,MD5加密
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        sign_str = "&".join([f"{k}={v}" for k, v in sorted_params]) + self.secret_key
        return hashlib.md5(sign_str.encode()).hexdigest()

    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:
        """清洗销量字符串(提取数字)"""
        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.product-title")["href"]
        item_id = re.search(r"/goods/(\d+)", link).group(1) if link else ""

        # 提取版本信息(跨境核心字段)
        title = item_soup.select_one(".product-title")?.text.strip() or ""
        version = re.search(r"[美日法韩中]版", title).group() if re.search(r"[美日法韩中]版", title) else ""

        return {
            "item_id": item_id,
            "title": title,
            "main_image": item_soup.select_one(".product-img img")?.get("src") or "",
            "url": f"https://www.yangmatou.com{link}" if link.startswith("/") else link,
            "price": {
                "current": self._clean_price(item_soup.select_one(".price-current")?.text.strip() or ""),
                "original": self._clean_price(item_soup.select_one(".price-original")?.text.strip() or ""),
                "tax_included": "含税" in (item_soup.select_one(".price-tag")?.text or "")
            },
            "cross_border": {
                "purchase_area": item_soup.select_one(".purchase-area")?.text.strip() or "",
                "logistics": item_soup.select_one(".logistics-tag")?.text.strip() or "",
                "delivery_time": item_soup.select_one(".delivery-time")?.text.strip() or ""
            },
            "sales": {
                "monthly": self._clean_sales(item_soup.select_one(".sales-count")?.text.strip() or ""),
                "comment_count": self._clean_sales(item_soup.select_one(".comment-count")?.text.strip() or "")
            },
            "seller": {
                "name": item_soup.select_one(".seller-name")?.text.strip() or "",
                "level": item_soup.select_one(".seller-level")?.text.strip() or ""
            },
            "version": version  # 版本信息(如“法版”)
        }

    def _parse_page(self, html: str) -> List[Dict]:
        """解析页面的商品列表"""
        soup = BeautifulSoup(html, "lxml")
        # 商品列表容器(需根据实际页面结构调整)
        item_list = soup.select("div.product-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, 
                   purchase_area: str = "", 
                   logistics: str = "", 
                   sort: str = "", 
                   page_limit: int = 5) -> Dict:
        """
        搜索洋码头商品列表
        :param keyword: 搜索关键词
        :param category: 分类名称(如“美妆护肤”)或分类ID
        :param price_min: 最低价格(元)
        :param price_max: 最高价格(元)
        :param purchase_area: 采购地名称(如“法国”)或编码(如“fr”)
        :param logistics: 物流类型(direct/bonded)
        :param sort: 排序方式(sales/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 ""
            # 转换采购地名称为编码
            if purchase_area in self.area_map:
                area_code = self.area_map[purchase_area]
            else:
                area_code = purchase_area if purchase_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 cid:
                    params["categoryId"] = cid
                if price_min is not None:
                    params["priceMin"] = price_min
                if price_max is not None:
                    params["priceMax"] = price_max
                if area_code:
                    params["purchaseArea"] = area_code
                if logistics:
                    params["logistics"] = logistics
                if sort:
                    params["sort"] = sort

                # 构建URL(简单筛选用页面URL,复杂筛选用API)
                if encoded_keyword:
                    url = self.base_url.format(keyword=encoded_keyword)
                else:
                    url = f"https://www.yangmatou.com/category/{cid}"  # 无关键词时用分类页

                # 发送请求(带随机延迟)
                time.sleep(random.uniform(3, 5))  # 跨境平台延迟需更高
                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}

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

    # 初始化API客户端(需补充secret_key,通过JS逆向获取)
    search_api = YangmatouSearchApi(proxy_pool=PROXIES, cookie=COOKIE)

    # 搜索“兰蔻小黑瓶”,分类“美妆护肤”,价格500-1000元,采购地法国,直邮,按销量降序,最多3页
    result = search_api.item_search(
        keyword="兰蔻小黑瓶",
        category="美妆护肤",
        price_min=500,
        price_max=1000,
        purchase_area="法国",
        logistics="direct",
        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']['current']}({'含税' if item['price']['tax_included'] else '不含税'}) | 原价:¥{item['price']['original']}")
            print(f"跨境信息:{item['cross_border']['logistics']} | 采购地:{item['cross_border']['purchase_area']} | 时效:{item['cross_border']['delivery_time']}")
            print(f"版本:{item['version']} | 月销:{item['sales']['monthly']}件 | 评价:{item['sales']['comment_count']}条")
            print(f"卖家:{item['seller']['name']}(等级:{item['seller']['level']})")
            print(f"详情页:{item['url']}")
    else:
        print(f"搜索失败:{result['error_msg']}(错误码:{result.get('code')})")

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

  1. 跨境筛选条件映射(采购地、物流类型)
    • 建立名称 - 编码映射表(如area_map中 “法国” 对应 “fr”),用户输入名称时自动转换为参数;

    • 定期抓包更新映射关系(平台可能调整编码),确保筛选条件生效;

    • 示例代码中通过_parse_item函数提取purchase_arealogistics字段,直接反映商品的跨境属性。

    • 问题:采购地(如 “法国”)、物流类型(如 “直邮”)是跨境搜索的核心维度,但参数值为编码(如 “fr” 对应法国),需精准映射。

    • 解决方案

  2. 动态签名参数处理(复杂筛选场景)
    • 区分筛选复杂度:基础筛选(关键词、价格、分类)优先使用 URL 参数,避免触发动态接口;

    • 逆向签名逻辑:对必须使用动态接口的场景,通过 JS 逆向获取sign生成规则,用 Python 复现(如示例中_generate_sign函数);

    • 缓存签名参数:同一批筛选条件的sign参数短期内可复用,减少重复计算。

    • 问题:使用 “正品溯源”“卖家等级” 等高级筛选时,平台会通过带sign参数的 AJAX 接口返回结果,直接请求 URL 参数无效。

    • 解决方案

  3. 版本信息提取(跨境商品核心)
    • 正则匹配标题中的版本关键词([美日法韩中]版),精准提取版本信息;

    • 结合商品采购地辅助验证(如采购地为法国,版本大概率为 “法版”);

    • 示例代码中通过re.search从标题中提取版本,确保核心差异点不丢失。

    • 问题:版本(如 “法版”“美版”)是跨境商品的关键差异点(影响成分、价格),但常嵌入标题文本中(如 “兰蔻小黑瓶 法版”),提取难度大。

    • 解决方案

  4. 反爬机制对抗
    • 代理 IP 策略:使用高匿代理池(优先选择海外节点,模拟真实用户地域),每 1-2 页切换 IP;

    • 请求频率控制:单 IP 每分钟请求≤1 次,两次请求间隔 3-5 秒(跨境商品决策周期长,符合用户行为);

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

    • 动态参数伪装:在 URL 后添加随机时间戳(如&t=1620000000),避免请求缓存被识别。

    • 问题:洋码头对跨境数据保护严格,反爬机制包括 IP 封锁、签名验证、Cookie 时效限制、请求频率监控,高频访问易触发 403 错误。

    • 解决方案

六、最佳实践与合规要点

  1. 系统架构设计采用 “分布式低频率采集” 架构,适配跨境电商特性:
    • 任务分发层:通过消息队列(如 RabbitMQ)分发搜索任务,控制单任务并发数≤1;

    • 采集层:多节点并行采集,每个节点绑定独立代理池(含海外节点),节点间请求间隔≥10 秒;

    • 存储层:用 Redis 缓存热门搜索结果(6 小时过期,跨境商品价格波动较慢),MySQL 存储历史数据(用于消费趋势分析);

    • 监控层:实时监控代理存活率、签名有效性,异常时通过企业微信告警。

  2. 性能优化策略
    • 异步批量搜索:使用aiohttp并发处理多关键词(如 “兰蔻”“雅诗兰黛”),控制并发数≤1,适配低频率限制;

    • 按需解析:列表页优先提取item_id、价格、采购地等核心字段,详情信息通过后续item_get接口补充;

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

  3. 合规性与风险控制
    • 访问限制:单 IP 日搜索请求≤50 次,避免对平台服务器造成压力,符合跨境电商数据采集规范;

    • 数据使用边界:不得将数据用于虚假宣传、恶意比价或侵犯品牌知识产权,需注明数据来源 “洋码头”;

    • 法律风险规避:跨境商品数据涉及关税、采购地等敏感信息,使用时需遵守《海关法》《电子商务法》,不得篡改溯源信息。

七、总结

洋码头item_search接口的对接核心在于跨境筛选条件的精准映射(采购地、物流类型)、动态签名参数的灵活处理低频率高稳定性的采集策略。开发者需重点关注:
  1. 名称 - 编码映射表的维护(确保筛选条件生效);

  2. 签名生成逻辑的逆向与复现(应对复杂筛选场景);

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

通过本文的技术方案,可构建稳定的跨境商品搜索系统,为跨境比价、选品分析等场景提供可靠数据支持。实际应用中,需根据平台最新反爬机制动态调整签名逻辑与代理策略,平衡数据获取效率与合规性


群贤毕至

访客