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

《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. 轨迹客户端

python 复制代码
# 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

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
@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 ""


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. 运费模板"预估器"(只做采购成本下限)

python 复制代码
# 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)

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# 多模板订单:平台新逻辑是"取低"
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 计费内核。

相关推荐
mhmh1232 小时前
Python 对象模型与数据模型:魔术方法、描述符与元类深度解析
开发语言·python
估值探索者2 小时前
【Python量化策略实战 #02】多因子合成mom和vol两因子打分合成
开发语言·c++·python·数据挖掘·c#
FYKJ_20103 小时前
SSM校园失物招领系统41452-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·typescript·spark
头发够用的程序员3 小时前
TensorRT 自定义算子插件实战(一):从零手写 customScaledTanh
c++·人工智能·pytorch·python·深度学习·边缘计算·jetson
xiaoqi01954 小时前
期货量化软件回测方式深度横评:期魔方、文华财经、无限易、TB开拓者、金字塔全面对比
python·机器学习
别动我齐刘海4 小时前
简历技术栈全面复习——UDP / TCP / CAN / ZMQ / Protobuf 通信工程
网络·c++·python·tcp/ip·机器学习·udp·github
天赐范式4 小时前
天赐范式第181天:让耦合开始失稳——竞争耦合与线性失稳边界
python·数字生命·天赐范式·lyapunov指数·动态运行时·耦合失稳·laplacian矩阵
happylifetree4 小时前
Python15:核心语法-数据存储与运算-字符串拼接
python
huisheng_qaq5 小时前
【Python基础篇-09】深入理解python的闭包与装饰器
python·闭包