×

《1688物流API接口边界:logistics.trace.get 与 freight.template.list 的隐藏约束》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-09-30 09:18:59 浏览28 评论0

抢沙发发表评论

《1688物流API接口边界:logistics.trace.get 与 freight.template.list 的隐藏约束》(附Python源码)

先拍结论:
1688 物流域有两个“看起来简单、实际上暗坑极多”的接口:
alibaba.logistics.trace.get 不是按运单号查世界的,而是“按订单/物流编号看平台已录入的轨迹”;
freight.template.list(/ delivery.template.list)不是用来算运费的,而是“读商家运费模板结构”的,真算钱要靠重量/件数/地区/子模板自己复刻平台规则。
把“轨迹查询”当实时快递网关、把“模板列表”当运费计算器,是 ERP 接入 1688 物流最常见的两类幻觉。

一、alibaba.logistics.trace.get:轨迹接口的真实边界

1. 它到底吃什么参数

官方文档里关键是这三个:
  • order_id:必须,交易订单号

  • trade_source_type:必须,例如 cbu-trade

  • logistics_id:可选,订单下的物流编号(如 AL8234243)

返回的是:
trace_list[]
  ├─ logistics_id
  ├─ order_id
  ├─ logistics_bill_no      # 真正运单号
  └─ logistics_steps[]
        ├─ accept_time
        ├─ remark
  └─ trace_node_list[]
        ├─ accept_time
        ├─ action        # TRANSPORT / SIGN / UNSIGN
        ├─ facility_name
        ├─ facility_type # 网点 / 分拨中心
        ├─ area_code
        └─ remark
⚠️ 重点:不是 company_code + waybill_no 查全网快递;它是“1688 订单关联的那张物流单,平台现在知道哪些节点”。

2. 隐藏约束(文档不写但会咬人)

  1. 未发货查不到

    • 订单没发货 → 返回成功但 trace_list 空,或别处接口直接报 500_2 订单尚未发货

    • 正确动作:监听“已发货消息”后再查,不要定时盲扫所有单

  2. 轨迹有录入延迟

    • 快递公司已经派送了,1688 侧可能晚几分钟到几小时

    • 不能拿 trace.get 的“无 SIGN 节点”直接判“未签收”

  3. 一个订单可能多物流单

    • 拆单发货:A 仓发 2 件、B 仓发 3 件

    • 只看第一个 trace_list[0] 会丢货

  4. action 不是淘宝那套

    • 1688:TRANSPORT / SIGN / UNSIGN

    • 淘宝:ARRIVE / SIGN / SENT_SCAN

    • 直接复用淘宝状态机 = 签收逻辑错乱

  5. bill_no 可能后期才补

    • 供应商“点了发货”但电子面单没回写 → logistics_bill_no 空

    • 这时候要:等 → 重推 → 再查,不是立刻报异常

  6. QPS 不是无限

    • 物流类也吃开放平台流控,盲轮询 500 单/30s 必被限流


二、freight.template.list / delivery.template.list:模板接口的真实边界

1. 它干什么

查“某个商家/应用下”的运费模板列表:
  • 模板名

  • 计费维度

  • 是否包邮

  • 子模板(快递 / 货运 / 系统模板)

  • 地区费率

2. 计费维度藏在子模板里

第三方/商品域文档里能看到这种结构:
DeliverySubTemplate
  chargeType:
    0 = 按重量
    1 = 按件数
    2 = 按体积
  serviceType:
    0 = 快递
    1 = 货运
    2 = 货到付款
  serviceChargeType:
    0 = 卖家承担
    1 = 买家承担
  firstUnit      # 首重(克) / 首件(件) / 首体积
  firstUnitFee   # 单位:分
  nextUnit
  nextUnitFee
  leastExpenses  # 最低一票
  toAreaCodeText # 上海、福建、广东
重点:接口给你“规则”,不给你“这笔订单运费 = 12.5”。
平台自己算,因为还要叠:订单总重、是否跨模板、是否满包邮、买家地区、是否货到付款。

3. 隐藏约束(成本算错就亏)

  1. 多商品多模板 ≠ 简单累加

    • 老逻辑:各商品按自己模板算,再加总

    • 1688 新逻辑(2024-10 后):同订单多模板时,用总重/总件数分别套每个模板,取对买家更低的那个

  2. 首重单位可能是克不是千克

    • firstUnit=1000 是 1000 克

    • 你用 1.5 kg 直接塞进 firstUnit=1 的模板 → 运费翻 1000 倍

  3. 包邮门槛是“实付货值”还是“含运费”要分清

    • 满 99 包邮:通常是货值满 99,不是货值+运费满 99

  4. 系统模板(isSysTemplate)不能当普通模板改

    • 平台默认偏远地区/默认快递,覆盖逻辑要单独处理

  5. 模板列表 ≠ 商品当前生效模板

    • 商品可能绑了模板 A,但商家刚改成模板 B

    • 下单前要看商品详情里的 freightTemplateId,不是“商家最后一个模板”

  6. 跨境/货到付款/大件货运是不同 serviceType

    • 用快递模板算大件 = 血亏


三、生产向:轨迹归一化 + 运费预估(不是精算)

1. 轨迹客户端

# ali1688/logistics_trace.py
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class TraceNode:
    accept_time: str
    action: str          # TRANSPORT / SIGN / UNSIGN
    facility_name: str
    area_code: str
    remark: str


@dataclass
class LogisticsTrack:
    logistics_id: str
    bill_no: Optional[str]
    nodes: list[TraceNode]
    raw: dict = field(default_factory=dict)

    @property
    def signed(self) -> bool:
        return any(n.action == "SIGN" for n in self.nodes)

    @property
    def latest_remark(self) -> str:
        return self.nodes[-1].remark if self.nodes else ""

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class Ali1688TraceClient:
    def __init__(self, top_client, qps_limiter):
        self.top = top_client
        self.limiter = qps_limiter

    def get_track(self, order_id: int, logistics_id: str = None) -> Optional[LogisticsTrack]:
        self.limiter.acquire("logistics.trace.get")

        params = {
            "order_id": order_id,
            "trade_source_type": "cbu-trade",
        }
        if logistics_id:
            params["logistics_id"] = logistics_id

        # resp = self.top.execute("alibaba.logistics.trace.get", params)
        resp = {"trace_list": []}   # 伪响应

        traces = []
        for t in resp.get("trace_list", []):
            nodes = [
                TraceNode(
                    accept_time=n.get("accept_time", ""),
                    action=n.get("action", ""),
                    facility_name=n.get("facility_name", ""),
                    area_code=n.get("area_code", ""),
                    remark=n.get("remark", ""),
                )
                for n in t.get("trace_node_list", [])
            ]
            traces.append(LogisticsTrack(
                logistics_id=t.get("logistics_id", ""),
                bill_no=t.get("logistics_bill_no") or None,
                nodes=nodes,
                raw=t,
            ))

        # 多物流单:返回列表,不让上层只看第一单
        return traces
消费侧铁律:
tracks = client.get_track(order_id)
if not tracks:
    # 可能是未发货 / 延迟录入 → 进“待补查队列”,不报失败
    return {"state": "no_trace_yet"}

all_signed = all(t.signed for t in tracks)
any_bill = [t.bill_no for t in tracks if t.bill_no]

2. 运费模板“预估器”(只做采购成本下限)

# ali1688/freight_estimator.py
from dataclasses import dataclass
from enum import IntEnum


class ChargeType(IntEnum):
    WEIGHT = 0     # 克
    COUNT = 1      # 件
    VOLUME = 2     # 立方厘米/方


@dataclass
class FreightRule:
    charge_type: int
    first_unit: float
    first_fee_fen: int
    next_unit: float
    next_fee_fen: int
    least_fee_fen: int
    area_text: str
    buyer_pays: bool


def estimate_freight(rule: FreightRule, qty: float) -> int:
    """
    qty:
      - 重量场景 = 克
      - 件数场景 = 件
      - 体积场景 = 体积单位
    """
    if qty <= 0:
        return 0

    if qty <= rule.first_unit:
        fee = rule.first_fee_fen
    else:
        extra = qty - rule.first_unit
        steps = (extra + rule.next_unit - 1) // rule.next_unit
        fee = rule.first_fee_fen + int(steps) * rule.next_fee_fen

    return max(fee, rule.least_fee_fen)


# 多模板订单:平台新逻辑是“取低”
def pick_1688_new_logic(rules: list[FreightRule], total_qty: float) -> int:
    fees = [estimate_freight(r, total_qty) for r in rules]
    return min(fees) if fees else 0
注意:这只是采购侧成本预估/异常拦截。
真要对买家收银:还得叠地区匹配、包邮门槛、订单级多模板、货到付款加价——这些平台不给你算好,也不保证你复刻得和 1688 完全一样。

四、和前几篇的拼接

  • 《1688 订单 API》:trace.get 是订单状态机的“物流子状态源”

  • 《1688 商品 API》:freightTemplateId 在商品里,模板规则在 freight.template.list 里

  • 《统一采购适配层》:运费要进“采购成本”,不是直接等于“买家付的运费”

  • 《两套接口边界》:物流轨迹/运费模板是“供货侧元数据”,不能当成“闲鱼/Mercari 买家物流页的直接数据源”


五、上线前检查表

logistics.trace.get
  • [ ] 只在“已发货”后查,不盲轮询

  • [ ] 处理多物流单(拆单)

  • [ ] bill_no 为空进补偿队列

  • [ ] action 用 1688 枚举,不抄淘宝

  • [ ] 无 SIGN 不代表未签收(有延迟)

  • [ ] 物流类接口走令牌桶

freight.template.list
  • [ ] 不把“模板列表”当“订单运费”

  • [ ] 重量用克、件数用件、体积用体积极

  • [ ] 多模板订单用“平台新逻辑取低”

  • [ ] 包邮门槛/地区/最低一票都生效

  • [ ] 商品绑哪个模板以商品详情为准

  • [ ] 运费只做成本预估,不对买家强制精算


六、一句话收口

logistics.trace.get 是“平台知道到哪了”,不是“快递公司官网”;
freight.template.list 是“商家定了个规则”,不是“这笔单运费多少钱”。
1688 物流对接的成熟度 =
轨迹当“事件流”消费,运费当“成本模型”估算,绝不假装自己是 1688 计费内核。


群贤毕至

访客