×

刚起步的电商小团队,第一批该接哪几个接口(附 Python 源码)

万邦科技Lex 万邦科技Lex 发表于2026-11-07 09:25:29 浏览31 评论0

抢沙发发表评论

# 刚起步的电商小团队,第一批该接哪几个接口(附 Python 源码)

> 起步团队最容易犯的错,是把「接口清单」当成任务清单,一条条往下接。
>
> 三平台文档里有上千个接口,你真正需要的第一批只有 **4 个**。剩下的不是不重要,是**现在接它们会拖慢你赚到第一笔钱的时间**。
>
> 这篇给一个明确的顺序:第一批 4 个、第二批 3 个、第三批 4 个,以及三个「现在千万别接」的。判断标准只有一条——**这个接口能不能让货卖出去、或者让钱收回来**。
>
> 代码是一个 150 行的单文件,跑完就是一条完整闭环:商品上架 → 客户下单 → 订单进库 → 扣库存 → 日终对账。

## 一、先定一个判断标准

接口的价值不是「技术上重要」,是**它对收入的贡献**。按这个标准,起步期能碰的接口只分三类:

| 类型      | 作用        | 起步期优先级     |
| ------- | --------- | ---------- |
| **换收入** | 商品能上架、能卖  | 最高,第一批     |
| **保现金** | 订单不漏、账对得上 | 最高,第一批     |
| **补体验** | 物流可查、售后顺畅 | 第二批,等有单了再说 |
| **纯优化** | 评价、竞品、素材  | 第三批,或者永远不接 |

一个反例:很多团队第一批就去接「物流轨迹查询」,理由是「客户会问快递到哪了」。但起步期你一天可能就 5 单,客户问了你手动查一下,30 秒的事。**为一天 5 次的查询,写一套订阅 + 去重 + 状态机,是典型的提前优化**(第 3 篇那套方案是对的,但不是现在该做的)。

## 二、第一批:4 个接口,按这个顺序接

| 顺序 | 接口                      | 为什么在这个位置                                |
| -- | ----------------------- | --------------------------------------- |
| ①  | **商品详情**(含 SKU、标价、可售库存) | 没商品就没得卖。三平台的详情接口都一次返回 SKU 列表和标价,别拆成四次请求 |
| ②  | **订单列表**(按更新时间增量)       | 收入的证据。也是「有没有单」的唯一可靠来源                   |
| ③  | **订单详情**(含收货地址、支付时间)    | 发货必需。**列表接口通常不含完整地址**,这个坑第一天就会撞上        |
| ④  | **库存**(可售库存)            | 防超卖。但起步期用「简单账本」撑住就行,别做实时同步              |

### 为什么「先订单、后库存」

这是这篇最反直觉的一条,值得单独说。

按技术难度排,库存比订单简单(一个数字 vs 一堆状态)。但按**收益**排,订单在前:

- **订单**接进来,你立刻能发货、能算账、能知道今天赚了多少。这是正反馈。
- **库存**接进来,你防的是「超卖」。而起步期 SKU 少、单量小,超卖的实际损失远小于「商品还没上架」的损失。

所以正确的做法是:**库存先用静态值撑住,下单即扣本地账本,宁可少卖**。具体就是三行逻辑:

```
可卖数 = 平台可售 − 本地已占      (查询时算,不写库)
下单   → 够就 reserved += n,不够就拦住这笔单
日终   → 平台库存重新拉过之后,reserved 归零重算
```

这么做会偶尔少卖(账面比实际紧),但不会超卖,而且**没有并发问题**——因为你从来不做「实时同步」这件难事。

等单量上来了(比如日单过百,或者开始做多仓),再把第 3 篇那套 `total - reserved - safety` 加上事件校准换上。那时候你已经有收入了,花两周做对它是合理的。

反过来做的团队我见过不少:花两周把库存并发扣减做对,商品还没上架,一分钱没赚。**技术正确,商业上错了。**

## 三、第二批:有第一笔订单之后

大概在第一笔订单之后的 2~4 周,接这三个:

| 接口          | 触发条件                  | 不接的代价                  |
| ----------- | --------------------- | ---------------------- |
| **物流轨迹**    | 客户开始问「到哪了」,且每天超过 10 次 | 客服时间被吃掉,但还不致命          |
| **售后 / 退款** | 出现第一笔退款               | **账永远对不上**,你会以为是接口有问题  |
| **发货回写**    | 你做代发,或对接了 ERP         | 只能在平台后台手工点发货,一天几十单就撑不住 |

退款这个特别值得说:起步团队常常只拉「已付款」的订单,退款不管。结果月底一算,GMV 和实际到账差一截,财务来问,你查三天。

**退款不是异常,是正常业务**。它该在第二批,不该在「以后再说」。

## 四、第三批:月订单过千再看

| 接口        | 什么时候值得                            |
| --------- | --------------------------------- |
| 活动价 / 优惠券 | 你开始做促销,且发现「到手价」算不对(第 13 篇的价格对象建模) |
| 类目属性      | 你开始批量铺货、被审核驳回(第 5 篇)              |
| 评价        | 你要做选品或口碑分析(第 16 篇)                |
| 素材 / 图片   | 你要做自动生成主图、多平台分发                   |

这四个都是「业务推着你接」,不是「文档里有所以接」。

## 五、三个现在千万别接的

**① TMC 消息推送。**

听起来很美:订单来了实时推给你。实际要维护长连接、断线重连、消息确认、重复消费——四件事都能坏,坏了都要你查。起步期用「5 分钟轮询 + 时间窗重叠」完全够,而且失败就是这一轮没跑,下一轮补上,心智负担接近于零。

等你日单过千、或者对时效真的有要求了再换。切换成本是一次可控的重构,不是重写。

**② 批量 / 异步接口。**

批量接口的特点是:提交任务 → 拿 task_id → 轮询结果 → 处理部分失败。这套东西要状态机、要重试、要存储中间态。起步期你的商品可能就 200 个,循环调 200 次详情,10 分钟跑完,够用。

**③ 全站 / 竞品数据。**

先把自己店的货卖好。比价、竞品监控是第二阶段的事(第 16 篇),而且它需要的数据源和自营接口是两套逻辑(联盟接口 / 聚合服务),混在一起做会让你的第一版架构变形。

## 六、代码:150 行,一条闭环

起步版的原则:**小到一屏能看懂**。不要配置层、不要抽象层、不要依赖注入——这些东西等你有了第二个平台再谈。

```python
# mvp.py —— 起步版:4 个接口,1 条闭环
# 依赖:pip install httpx
import asyncio
import sqlite3
import time

import httpx

BASE = "https://api.example.com"          # 换成你的网关
KEY = "your_app_key"
SECRET = "your_app_secret"
SHOP = "shop_001"

SCHEMA = """
pragma journal_mode = wal;
create table if not exists items(
  item_id text primary key, title text, price real,
  stock int, skus text, updated_at int);
create table if not exists orders(
  tid text primary key, status text, pay_time int,
  amount real, receiver text, updated_at int);
create table if not exists ledger(
  item_id text primary key, reserved int default 0, last_sync int);
"""

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
async def call(client, path, **params):
    """一个函数搞定所有接口:拼参数、判错、返回 data"""
    r = await client.get(path, params={"shop_id": SHOP, **params})
    r.raise_for_status()
    body = r.json()
    if body.get("code") not in (0, 200, "0", None):
        raise RuntimeError(f"{body.get('code')} {body.get('msg')}")
    return body.get("data", body)


async def pull_items(client, db):
    """① 商品详情:一次带回 SKU、标价、可售库存。
    三平台详情接口都支持,别拆成 SKU 接口 + 价格接口 + 库存接口"""
    ids = [r[0] for r in db.execute("select item_id from items")]
    if not ids:                       # 第一次:从列表接口拿 ID
        lst = await call(client, "/v1/item/list", page_size=100)
        ids = [it["item_id"] for it in lst.get("items", [])]

    for item_id in ids:
        d = await call(client, "/v1/item", item_id=item_id)
        skus = d.get("skus") or []
        stock = sum(int(s.get("stock") or 0) for s in skus) if skus \
            else int(d.get("stock") or 0)
        db.execute(
            "insert into items(item_id,title,price,stock,skus,updated_at) "
            "values(?,?,?,?,?,?) on conflict(item_id) do update set "
            "title=excluded.title, price=excluded.price, stock=excluded.stock, "
            "skus=excluded.skus, updated_at=excluded.updated_at",
            (item_id, d.get("title", ""), float(d.get("price") or 0),
             stock, str(len(skus)), int(time.time())))
    db.commit()
    return len(ids)


async def pull_orders(client, db, since: int):
    """② 订单列表:按更新时间增量,重叠加窗(第 3 篇的边界坑)"""
    start = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(since - 600))
    page, got = 1, 0
    while True:
        lst = await call(client, "/v1/orders",
                         updated_start=start, page=page, page_size=100)
        rows = lst.get("orders", [])
        if not rows:
            break
        for o in rows:
            db.execute(
                "insert into orders(tid,status,pay_time,amount,updated_at) "
                "values(?,?,?,?,?) on conflict(tid) do update set "
                "status=excluded.status, updated_at=excluded.updated_at",
                (o["tid"], o["status"], int(o.get("pay_time") or 0),
                 float(o.get("payment") or 0), int(time.time())))
            got += 1
        page += 1
    db.commit()
    return got


async def fill_receiver(client, db):
    """③ 订单详情:列表接口没有地址,发货前补。
    只补「待发货」的,别全量拉——这一步最容易把额度烧光"""
    todo = db.execute(
        "select tid from orders where receiver is null and status='WAIT_SELLER_SEND'"
    ).fetchall()
    for (tid,) in todo:
        d = await call(client, "/v1/order", tid=tid)
        addr = d.get("receiver") or {}
        db.execute("update orders set receiver=? where tid=?",
                   (f"{addr.get('name','')}|{addr.get('mobile','')}|"
                    f"{addr.get('address','')}", tid))
    db.commit()
    return len(todo)


def available(db, item_id: str) -> int:
    """④ 可卖数 = 平台可售 − 本地已占。
    查询时算,不写库:`items.stock` 是平台镜像,每次拉取覆盖;
    本地占用量单独记在 ledger,两者相减才是能卖的数。
    不写回的好处是不存在「谁覆盖谁」的问题——第 3 篇那个
    「校准取 min」的原则,是给「要把库存写回平台」的场景用的,
    起步期我们不写回,所以用不上。"""
    item = db.execute("select stock from items where item_id=?",
                      (item_id,)).fetchone()
    if item is None:
        return 0
    row = db.execute("select reserved from ledger where item_id=?",
                     (item_id,)).fetchone()
    return max(0, item[0] - (row[0] if row else 0))


def reserve_stock(db, item_id: str, num: int = 1) -> bool:
    """下单占库存:够就扣,不够就拦住这笔单(宁可少卖,不超卖)。
    注意先判后扣——先扣再判,被拦下的单也会留下占用,账面越推越紧。"""
    if available(db, item_id) < num:
        return False

    row = db.execute("select reserved from ledger where item_id=?",
                     (item_id,)).fetchone()
    if row is None:
        db.execute("insert into ledger(item_id,reserved,last_sync) "
                   "values(?,?,?)", (item_id, num, int(time.time())))
    else:
        db.execute("update ledger set reserved=reserved+?, last_sync=? "
                   "where item_id=?", (num, int(time.time()), item_id))
    db.commit()
    return True


def reset_reserved(db):
    """日终:平台库存已经在 pull_items 里重新拉过,本地占用量归零重算。
    必须在拉取成功之后调,否则会把当天的占用抹掉——那就是超卖。"""
    n = db.execute("update ledger set reserved=0 where reserved>0").rowcount
    db.commit()
    return n


async def main():
    db = sqlite3.connect("mvp.db")
    db.executescript(SCHEMA)
    async with httpx.AsyncClient(base_url=BASE, timeout=10,
                                 headers={"X-Secret": SECRET, "X-Key": KEY}) as c:
        n_items = await pull_items(c, db)          # 先拉最新库存
        n_orders = await pull_orders(c, db, int(time.time()) - 3600)
        n_addr = await fill_receiver(c, db)
        n_reset = reset_reserved(db)               # 拉成功之后才清零
    print(f"商品 {n_items} · 订单 {n_orders} · 补地址 {n_addr} · 归零 {n_reset}")


if __name__ == "__main__":
    asyncio.run(main())
```

这 150 行就是起步版的全部。它故意没有的东西,是按重要性排的:

| 没有        | 为什么现在不加             | 什么时候加            |
| --------- | ------------------- | ---------------- |
| 配置层(YAML) | 5 个常量改起来比读配置快       | 有第二个部署环境         |
| 多平台适配器    | 先接一个平台,等第二个出现重复代码   | 第二个平台接的时候(第 8 篇) |
| 并发 / 限流   | 200 个商品串行跑 4 分钟,能接受 | 商品上千,或单量上来       |
| 重试 / 熔断   | 失败就下一轮再跑,起步期能忍      | 开始有客户依赖你的数据      |
| 消息推送      | 轮询 5 分钟够用           | 日单过千             |

## 七、验收标准:一条能跑完的闭环

「接口接完了」不等于「能用了」。第一批的正确验收标准是一个动作:**把你自己的一个商品,从头到尾跑一遍**。

1. 商品信息从接口拉下来,落到你的库,页面能展示
2. 用另一个账号在你店里下一单(1 元商品,自己买)
3. 轮询把订单拉进来,地址补全,状态是「待发货」
4. 本地库存被扣掉 1,页面显示的可售数减 1
5. 你在平台后台点发货,回写状态(第二批才有,现在手工)

这五步跑通,第一批就算完成。**注意第 2 步必须真下单**——只用接口返回的模拟数据,你永远不知道字段口径对不对。1 元的成本,换来的是「整条链路真的通」,这是起步期最划算的一笔投入。

跑完你会发现至少两个问题:某个字段为空(通常是权限没开)、某个时间格式不对(时区)。这两个坑每个团队都会踩,早踩比上线后踩好。

## 八、起步期三个最常见的错误

**① 三个平台一起接。**

看起来平行推进效率高,实际是把「三个平台各自的怪癖」同时砸在你头上。第 2 篇那个坑表里的东西——1688 没有 SKU 库存、京东返回体多套一层、淘宝错误藏在 body 里——同时遇到三个,你会怀疑人生。

正确顺序:**一个平台跑通闭环 → 再复制到第二个**。第二个平台的成本大约是第一个的 1/3,因为业务逻辑已经验证过了。

**② 一开始就做增量和高并发。**

起步期商品 200 个、订单一天 20 个,全量拉一遍 4 分钟。这个阶段做增量调度、做并发优化,是把第 7 篇那套 60 倍提速用在 4 分钟上——**优化一个你根本感知不到的问题**。

**③ 一开始就自建。**

第 12 篇算过账:自建爬虫三年 37 万,官方 API 17 万。起步团队还有一笔更贵的隐藏成本:**你在写同步代码的那两周,没有在卖货**。

起步期最该花钱买的就是数据接口,因为它把你从「修管道」里解放出来,去做只有你能做的事——选品、内容、投放。这笔账在收入为零的时候最容易被算错。

## 九、时间预算

按这篇的顺序做,第一周(约 20 小时)应该能到:

| 天     | 内容               | 小时 |
| ----- | ---------------- | -- |
| D1    | 申请、拿 key、跑通商品详情  | 4  |
| D2    | 商品落库 + 页面展示      | 4  |
| D3-D4 | 订单增量 + 地址补全 + 幂等 | 8  |
| D5    | 库存账本 + 日终校准      | 4  |

对比「全都接」的版本:三平台 + 物流 + 售后 + 推送,两个月起步,而且大概率在第二个月放弃。

**起步团队真正的资源不是技术能力,是「还没被复杂度拖住」的那段时间。**

---

这一篇放在系列里的位置比较特殊:前面二十一篇讲的是「怎么把系统做对」,这篇讲「什么时候该做多少」。如果你现在只有一个店、一天十几单,上面 150 行代码就够了——第 3 篇的库存账本、第 7 篇的并发、第 9 篇的五道关,都等你需要的时候再看,那时候你会看得更懂。


群贤毕至

访客