×

日本 Mercari 煤炉关键字搜索接口:游标分页与结果去重实践

Ace Ace 发表于2026-09-02 16:58:20 浏览17 评论0

抢沙发发表评论

前言

网上多数 Mercari 搜索示例直接使用 page 偏移分页做循环抓取,但煤炉搜索接口存在典型分页漂移问题:商品实时上下架,偏移翻页会出现数据重复、条目丢失。本文不采用简单 page 循环,基于移动端搜索接口,实现游标式遍历、内存集合去重、业务字段过滤,适配对日选品、价格监控业务,规避偏移分页带来的数据错乱问题,输出标准化商品列表。

前置准备

调用 Mercari JP 移动端搜索接口,必须配置 X‑Device‑Id、X‑Platform、Authorization 请求头。access_token 存在有效期,401 代表令牌失效;403/429 为访问限流,需要做退避延时。接口支持关键词、价格区间、售卖状态筛选。
依赖:requests,执行pip install requests

点击获取key和secret

python

import requests
import time

class MercariSearchClient:
    def __init__(self, access_token, device_id):
        self.access_token = access_token
        self.device_id = device_id
        self.base_url = "https://api.mercari.jp/v2/search"
        self.session = requests.Session()
        self.headers = {
            "X‑Device‑Id": self.device_id,
            "X‑Platform": "android",
            "Authorization": f"Bearer {access_token}",
            "User‑Agent": "Mercari/6.200.0 (Linux; Android)"
        }
        self.seen_ids = set() # 全局去重集合

    def search_by_keyword(self, keyword, price_min=0, price_max=0, status="on_sale", cursor=None):
        params = {
            "q": keyword,
            "status": status,
            "limit": 50
        }
        if price_min>0: params["price_min"] = price_min
        if price_max>0: params["price_max"] = price_max
        if cursor: params["cursor"] = cursor

        try:
            resp = self.session.get(self.base_url, headers=self.headers, params=params, timeout=12)
            if resp.status_code == 401:
                return {"success":False,"error":"access_token令牌过期"}
            if resp.status_code in (403,429):
                time.sleep(5)
                resp = self.session.get(self.base_url, headers=self.headers, params=params, timeout=12)
            resp.raise_for_status()
            raw = resp.json()
        except requests.exceptions.RequestException as e:
            return {"success":False,"error":f"网络异常:{str(e)}"}

        data = raw.get("data",{})
        next_cursor = data.get("cursor")
        raw_items = data.get("items",[])
        item_list = []
        status_map = {"on_sale":"在售","sold_out":"已售出","trading":"交易中"}

        for item in raw_items:
            iid = item.get("id")
            if not iid or iid in self.seen_ids:
                continue
            self.seen_ids.add(iid)
            item_list.append({
                "item_id":iid,
                "title":item.get("title",""),
                "price":int(item.get("price",0)),
                "status_text":status_map.get(item.get("status"),"未知"),
                "thumbnail":item.get("thumb_photo_url",""),
                "is_free_shipping": item.get("shipping_payer")=="seller"
            })
        return {"success":True,"items":item_list,"next_cursor":next_cursor}

if __name__ == "__main__":
    cli = MercariSearchClient(access_token="your_token",device_id="your_deviceid")
    res = cli.search_by_keyword(keyword="ポケモンカード",price_min=1000)
    print(res)

代码核心解析

1. 游标 cursor 替代 page 偏移分页,缓解商品动态变更带来的分页漂移,减少漏数据、重复数据。
2. 内置 seen_id 集合做内存去重,同一商品不会重复输出,适合循环多页抓取场景。
3. 统一处理 401 令牌过期、403/429 限流,增加休眠退避,给出明确业务错误。
4. 做状态字段映射,过滤无用埋点字段,只保留选品需要的核心数据。

群贤毕至

访客