# 商品列表查询 API > 官方文档:https://docs.ozon.ru/api/seller/zh/#operation/ProductAPI_GetProductListV3 --- ## 接口信息 | 项 | 值 | |---|---| | 方法 | POST | | 路径 | `/v3/product/list` | | 鉴权 | 需要 `Client-Id` + `Api-Key` | | 用途 | 分页查询商品列表(支持筛选、排序) | --- ## 1. 请求 ### 请求体 ```json { "filter": { "offer_id": ["MY-THERMOS-001"], "product_id": [987654321], "visibility": "ALL" }, "last_id": "", "limit": 100 } ``` ### 参数说明 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | filter | object | 可选 | 筛选条件 | | filter.offer_id | array | 可选 | 货号数组 | | filter.product_id | array | 可选 | Ozon 商品 ID 数组 | | filter.visibility | string | 可选 | 可见性:`ALL`(全部)/ `VISIBLE`(可见)/ `INVISIBLE`(不可见)/ `EMPTY_STOCK`(无库存)。默认 `ALL` | | **last_id** | string | 可选 | **分页游标**(上一页最后一个商品的 ID),首页传空字符串 `""` | | **limit** | integer | 可选 | 每页数量,最大 **1000**,默认 100 | --- ## 2. 响应 ### 成功响应(200) ```json { "result": { "items": [ { "product_id": 987654321, "offer_id": "MY-THERMOS-001" }, { "product_id": 987654322, "offer_id": "MY-THERMOS-002" } ], "total": 256, "last_id": "bnVtYmVyMjo5ODc2NTQzMjI=" } } ``` ### 字段说明 | 字段 | 类型 | 说明 | |---|---|---| | items | array | 商品列表(**仅基本字段**:product_id + offer_id) | | total | integer | 商品总数 | | **last_id** | string | **下一页游标**(Base64 编码,传给下次请求) | ⚠️ **注意**:此接口**仅返回 product_id 和 offer_id**,不返回名称/价格/图片等详情。要获取完整信息,需再调 `/v3/product/info/list`。 --- ## 3. 分页示例 ### 游标分页(推荐) ```python async def fetch_all_products(visibility="ALL"): """拉取所有商品(游标分页)""" all_items = [] last_id = "" while True: resp = await ozon_client.get_product_list( filter={"visibility": visibility}, last_id=last_id, limit=1000 # 单次最多 1000 ) items = resp["result"]["items"] all_items.extend(items) # 无更多数据 if not resp["result"].get("last_id"): break last_id = resp["result"]["last_id"] return all_items ``` ### 分批处理 ```python async def process_products_in_batches(batch_size=100): """分批处理商品(避免一次拉全部)""" last_id = "" while True: resp = await ozon_client.get_product_list( last_id=last_id, limit=batch_size ) items = resp["result"]["items"] if not items: break # 处理当前批次 await process_batch(items) last_id = resp["result"].get("last_id") if not last_id: break ``` --- ## 4. 获取完整信息 ### 方法 A:批量查详情(推荐) ```python async def fetch_products_with_detail(visibility="ALL"): """拉取商品列表 + 完整信息""" # 1. 拉列表(仅 ID) list_resp = await ozon_client.get_product_list( filter={"visibility": visibility}, limit=1000 ) items = list_resp["result"]["items"] product_ids = [item["product_id"] for item in items] # 2. 批量查详情(每次最多 100 个) details = [] for i in range(0, len(product_ids), 100): batch = product_ids[i:i+100] detail_resp = await ozon_client.get_product_info( product_id=batch ) details.extend(detail_resp["result"]["items"]) return details ``` ### 方法 B:按需查详情 ```python # 先列表,用户点击某个商品时再查详情 products = await ozon_client.get_product_list(limit=100) # 用户点击 product_id=987654321 detail = await ozon_client.get_product_info(product_id=[987654321]) ``` --- ## 5. 筛选条件详解 ### visibility 筛选 | 值 | 含义 | 使用场景 | |---|---|---| | **ALL** | 全部商品 | 管理后台(查看所有) | | **VISIBLE** | 可见商品(上架) | 前台展示的商品 | | **INVISIBLE** | 不可见商品(下架/草稿) | 待上架/审核失败/归档 | | **EMPTY_STOCK** | 无库存商品 | 补货提醒 | ### 示例 ```python # 查询所有上架商品 visible = await ozon_client.get_product_list( filter={"visibility": "VISIBLE"}, limit=1000 ) # 查询无库存商品(需补货) empty_stock = await ozon_client.get_product_list( filter={"visibility": "EMPTY_STOCK"}, limit=100 ) ``` --- ## 6. 性能对比 ### `/v3/product/list` vs `/v3/product/info/list` | 维度 | `/v3/product/list` | `/v3/product/info/list` | |---|---|---| | 返回字段 | 仅 product_id + offer_id | 完整字段(图片/属性/状态) | | 单次数量 | 最多 **1000** | 最多 **100** | | 响应速度 | 快(字段少) | 慢(字段多) | | 适用场景 | 列表/分页/ID 收集 | 详情查询/更新前读取 | **策略**: 1. 先用 `/list` 拉 ID 列表(快) 2. 再用 `/info/list` 批量查详情(按需,100 个一批) --- ## 7. 与本地数据库同步 ### 场景:回填 product_id ```python async def sync_product_ids(): """发布后回填 product_id(用 offer_id 匹配)""" # 1. 从数据库取所有「已发布但无 product_id」的商品 local_products = await db.execute( select(Product) .where( Product.stage == "published", Product.ozon_product_id.is_(None), Product.offer_id.isnot(None) ) ) local_products = local_products.scalars().all() if not local_products: return offer_ids = [p.offer_id for p in local_products] # 2. 从 Ozon 查询这些 offer_id 的 product_id ozon_items = [] for i in range(0, len(offer_ids), 100): batch = offer_ids[i:i+100] resp = await ozon_client.get_product_info(offer_id=batch) ozon_items.extend(resp["result"]["items"]) # 3. 回填到数据库 ozon_map = {item["offer_id"]: item["id"] for item in ozon_items} for p in local_products: if p.offer_id in ozon_map: p.ozon_product_id = ozon_map[p.offer_id] await db.commit() ``` ### 场景:定期同步状态 ```python async def sync_product_states(): """定期同步商品状态(审核状态/库存/可见性)""" # 1. 拉取所有 Ozon 商品 ID ozon_resp = await ozon_client.get_product_list(limit=1000) ozon_ids = [item["product_id"] for item in ozon_resp["result"]["items"]] # 2. 批量查详情 details = [] for i in range(0, len(ozon_ids), 100): batch = ozon_ids[i:i+100] resp = await ozon_client.get_product_info(product_id=batch) details.extend(resp["result"]["items"]) # 3. 更新本地数据库 for item in details: await db.execute( update(Product) .where(Product.ozon_product_id == item["id"]) .values( stage="published" if item["visible"] else "failed", # 可同步更多字段:价格/库存/审核状态 ) ) await db.commit() ``` --- ## 8. 常见问题 ### Q1: 为什么 `/list` 只返回 ID? **A**: 性能考虑。商品列表可能有**数万条**,返回完整字段会很慢。设计思路: 1. 先快速拉 ID 列表(轻量) 2. 前端展示分页,只查当前页的详情 3. 或后台批量拉详情,按需处理 ### Q2: 如何获取商品总数? **A**: 响应的 `total` 字段。但注意: - `total` 是当前筛选条件下的总数 - 不保证精确(可能略有延迟) ### Q3: 游标分页与偏移分页的区别? **A**: - **游标分页**(last_id):适合全量遍历,性能稳定 - **偏移分页**(offset):Ozon 不支持(无 offset 参数) ### Q4: 多久同步一次? **A**: 建议策略: - 发布后立即查询(回填 product_id) - 定期同步(每天一次,更新状态/库存) - 用户主动刷新(按需实时查询) --- ## 9. V2 项目集成 ### API 层(待实现) ```python # server/api/products.py @router.get("/products/sync-from-ozon") async def sync_from_ozon( shop_id: str = Query(...), db: AsyncSession = Depends(get_db) ): """从 Ozon 同步商品列表(回填 product_id + 状态)""" shop = await get_shop(db, shop_id) # 1. 拉取 Ozon 商品列表 ozon_items = [] last_id = "" while True: resp = await ozon_client.get_product_list( shop.client_id_dec, shop.api_key_dec, last_id=last_id, limit=1000 ) items = resp["result"]["items"] ozon_items.extend(items) last_id = resp["result"].get("last_id") if not last_id: break # 2. 批量查详情 product_ids = [item["product_id"] for item in ozon_items] details = [] for i in range(0, len(product_ids), 100): batch = product_ids[i:i+100] detail_resp = await ozon_client.get_product_info( shop.client_id_dec, shop.api_key_dec, product_id=batch ) details.extend(detail_resp["result"]["items"]) # 3. 更新本地数据库 updated = 0 for item in details: result = await db.execute( update(Product) .where(Product.offer_id == item["offer_id"]) .values( ozon_product_id=item["id"], stage="published" if item["visible"] else "archived" ) ) updated += result.rowcount await db.commit() return { "synced": len(details), "updated": updated } ``` ### 前端(待实现) ```tsx // studio/src/pages/products/SyncButton.tsx import { Button, message } from 'antd'; import { SyncOutlined } from '@ant-design/icons'; export function SyncFromOzonButton({ shopId }) { const [syncing, setSyncing] = useState(false); const handleSync = async () => { setSyncing(true); try { const resp = await fetch( `/api/products/sync-from-ozon?shop_id=${shopId}` ); const data = await resp.json(); message.success( `已同步 ${data.synced} 个商品,更新 ${data.updated} 条记录` ); } catch (err) { message.error(`同步失败: ${err.message}`); } finally { setSyncing(false); } }; return ( ); } ``` --- ## 10. 高级用法 ### 增量同步 ```python async def incremental_sync(last_sync_time: datetime): """增量同步:只拉取最近更新的商品""" # Ozon 的 /list 接口不支持按更新时间筛选 # 策略:全量拉 ID,对比本地 updated_at,只查变化的 ozon_items = await fetch_all_products() ozon_ids = {item["product_id"] for item in ozon_items} # 查本地已有的 product_id local = await db.execute( select(Product.ozon_product_id, Product.updated_at) .where(Product.ozon_product_id.isnot(None)) ) local_map = { row.ozon_product_id: row.updated_at for row in local.fetchall() } # 找出新增的 ID new_ids = ozon_ids - set(local_map.keys()) # 批量查详情(只查新增的) if new_ids: details = await batch_get_product_info(list(new_ids)) # 插入数据库 ... ``` --- ## 相关文档 - [04-product-import.md](./04-product-import.md) —— 创建/更新商品 - [05-product-info.md](./05-product-info.md) —— 查询商品详情 - [09-stocks.md](./09-stocks.md) —— 库存管理 - [docs/v2/database.md](../v2/database.md) —— products 表结构