×

🛡️《eBay 二手专区 × ERP:Vintage/Refurbished 商品属性与平台自动下架防御》(附 Python 源码)

万邦科技Lex 万邦科技Lex 发表于2026-09-13 10:39:05 浏览8 评论0

抢沙发发表评论

已经把前面几篇的"国内/跨境平台适配"推到合规防御层了。eBay 的特殊点是:它不像闲鱼/Back Market 那样"调通 API 就完事",而是成色枚举高度类目化 + 翻新计划有准入 + 账号级限制(MC011 之类)会把活跃 listing 一起干掉。这篇就讲 ERP 怎么在"发布前校验 + 下架后自愈"两头设防。

🛡️《eBay 二手专区 × ERP:Vintage/Refurbished 商品属性与平台自动下架防御》(附 Python 源码)

结论先拍:eBay 二手/翻新不是"把 stuff_status=95 塞进去"那么简单——
  • New=1000 / New other=1500 / Certified Refurbished=2000 / Seller Refurbished=2500 / Used=3000 / For parts=7000按类目生效的;

  • Manufacturer Refurbished 已废弃,美/澳等地 2000 现在是 Certified Refurbished,要用得先过 eBay Refurbished Program 审核;

  • 手机类(9355)里 Seller Refurbished=2500 早就被禁,必须改用 2010/2020/2030 或退到 Used=3000

  • 真正吓人的是账号级限制(如 MC011):不是单条 listing 违规,而是活跃 listing 被移除、打款被hold、新建 listing 被禁。

所以 ERP 要有两层:发布前 ConditionGuard(别把违规条件发上去)+ 下架防御 ListingDefense(Ended 了能分类、能 Revise/Relist、能告警人工)

一、eBay 二手/翻新 ConditionID 实战表

ConditionID
含义
ERP 处理
1000
New
全新,别和二手混
1500
New other (see details)
近全新,描述必填
1750
New with defects
全新但有瑕疵,必须写 defect
2000
Certified Refurbished(原 Manufacturer Refurbished)
需 Refurbished Program 预审,否则建单/改单被 block
2010 / 2020 / 2030
Excellent / Very Good / Good – Refurbished
手机类等类目替代 Seller Refurbished
2500
Seller Refurbished
部分类目禁用(手机美/加/英/德/澳)
3000
Used / Pre-owned
二手主力;时装类进一步拆 Excellent/Good/Fair
4000 / 5000 / 6000
Very Good / Good / Acceptable
图书/收藏/服饰场景
7000
For parts or not working
垃圾机/配件机,别进"可售库存"
关键坑:ConditionID 不是全局常量,是"类目 × 站点 × 卖家资格"的三维函数。ERP 必须调 getItemConditionPolicies(Metadata API)按类目拉白名单,不能写死一张表。

二、自动下架的两种性质(ERP 必须分开处理)

平台动作
├── 1. Listing 级违规
│     · Condition 不在该类目允许列表
│     · Item Specifics / 类目不匹配
│     · 品牌/Catalog 要求未满足
│     → 单条 Ended / Blocking error / Warning→Error
│
└── 2. 账号级限制 (MC011 等)
      · 缺陷率/迟发率/取消率/追踪上传不达标
      · 突然爆量、代发供应链不稳
      · 高风险品类(电子/品牌/医疗)
      → 活跃 listing 被移除 + 打款 hold + 禁止新建
MC011 不是 API 错误码,是信任与安全审查状态。ERP 不能"重试解决",只能:检测 → 暂停自动发布 → 拉指标 → 人工申诉。

三、完整源码:ConditionGuard + ListingDefense

# ebay_secondhand_defense.py
"""
eBay 二手/翻新 ERP 防御层
- ConditionPolicyCache: 按 category_id 拉取允许的 ConditionID (模拟 getItemConditionPolicies)
- ConditionGuard: 发布前校验 (Vintage/Refurbished/For-parts 分流)
- ListingDefense: 轮询/消息发现 Ended -> 分类 -> Revise / Relist / 人工
- AccountHealthProbe: MC011 类账号级风险探测 (缺陷率/迟发/取消/追踪)
复用前几篇: unified_adapter_layer.Product / MarketplaceAdapter 思路
"""
from typing import Dict, List, Optional
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime, timezone

# ==================== Condition 策略 ====================
class EbayCondition(Enum):
    NEW = 1000
    NEW_OTHER = 1500
    CERTIFIED_REFURB = 2000
    EXCELLENT_REFURB = 2010
    VERYGOOD_REFURB = 2020
    GOOD_REFURB = 2030
    SELLER_REFURB = 2500
    USED = 3000
    VERY_GOOD = 4000
    GOOD = 5000
    ACCEPTABLE = 6000
    FOR_PARTS = 7000

# 类目 -> 允许 condition + 特殊约束
CATEGORY_POLICY: Dict[str, Dict] = {
    "9355": {  # Cell Phones & Smartphones (US/UK/DE/CA/AU)
        "allowed": [2000, 2010, 2020, 2030, 3000],
        "forbidden": [2500],  # Seller Refurbished 禁用
        "refurb_requires_program": True,
        "note": "手机类必须用 CR/Refurb 分级,2500 已禁",
    },
    "15724": {  # Vintage & Antique 服饰/收藏
        "allowed": [3000, 4000, 5000, 6000],
        "forbidden": [2000, 2500],
        "refurb_requires_program": False,
        "note": "Vintage 走 Used 分级,不允许 Refurbished",
    },
    "31387": {  # Home & Garden 部分叶子类目
        "allowed": [2000, 2500, 3000, 4000, 5000],
        "forbidden": [],
        "refurb_requires_program": True,  # eBay Refurbished Program 叶子类目超2200个
        "note": "Refurb 需程序资格",
    },
}

REFURB_IDS = {2000, 2010, 2020, 2030, 2500}

# ==================== 策略缓存 ====================
class ConditionPolicyCache:
    def __init__(self, policies: Dict = None):
        self.policies = policies or CATEGORY_POLICY

    def allowed(self, category_id: str) -> List[int]:
        return self.policies.get(category_id, {}).get(
            "allowed", [1000, 1500, 3000])

    def is_refurb(self, condition_id: int) -> bool:
        return condition_id in REFURB_IDS

    def refurb_requires_program(self, category_id: str) -> bool:
        return self.policies.get(category_id, {}).get("refurb_requires_program", False)

# ==================== 发布前守卫 ====================
@dataclass
class GuardResult:
    ok: bool
    level: str = "ok"          # ok / warn / block
    reasons: List[str] = field(default_factory=list)
    suggested_condition: Optional[int] = None

class ConditionGuard:
    def __init__(self, cache: ConditionPolicyCache,
                 refurb_program_approved: bool = False):
        self.cache = cache
        self.approved = refurb_program_approved

    def check(self, category_id: str, condition_id: int,
              description: str = "", has_defect: bool = False) -> GuardResult:
        reasons = []
        allowed = self.cache.allowed(category_id)

        # 1. 类目不允许该 condition
        if condition_id not in allowed:
            reasons.append(f"condition {condition_id} 不在类目 {category_id} 白名单")
            # 自动降级建议
            if condition_id in REFURB_IDS and 3000 in allowed:
                return GuardResult(False, "block", reasons, suggested_condition=3000)
            return GuardResult(False, "block", reasons)

        # 2. Refurb 但没过程序
        if self.cache.is_refurb(condition_id) and \
           self.cache.refurb_requires_program(category_id) and not self.approved:
            reasons.append("该类目 Refurbished 需 eBay Refurbished Program 预审")
            if 3000 in allowed:
                return GuardResult(False, "block", reasons, suggested_condition=3000)
            return GuardResult(False, "block", reasons)

        # 3. For parts 不能进可售库存
        if condition_id == 7000 and "for_parts" not in (description or "").lower():
            reasons.append("For parts 商品必须描述中明确不可正常使用")

        # 4. New other / defects 必须有描述
        if condition_id in (1500, 1750) and not description.strip():
            reasons.append(f"condition {condition_id} 必须有 conditionDescription")

        if reasons and condition_id in (1500, 1750):
            return GuardResult(False, "warn", reasons)

        if reasons:
            return GuardResult(False, "block", reasons)

        return GuardResult(True, "ok", ["condition 合规"])
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# ==================== 下架防御 ====================
class ListingState(Enum):
    ACTIVE = "Active"
    ENDED_BY_SELLER = "Ended"
    ENDED_BY_EBAY = "Ended"          # 需结合 EndReason / 违规标记判断
    SOLD_OUT_HIDDEN = "Ended"        # OutOfStockControl 隐藏但可补货

@dataclass
class ListingSnapshot:
    item_id: str
    category_id: str
    condition_id: int
    selling_state: str
    end_reason: Optional[str]
    quantity: int
    violations: List[str] = field(default_factory=list)

class ListingDefense:
    """发现 Ended -> 分类 -> 决策"""

    def classify(self, snap: ListingSnapshot) -> str:
        # 1. 账号级限制导致
        if "MC011" in snap.violations or "ACCOUNT_RESTRICTED" in snap.violations:
            return "account_restriction"     # 不能自动 Relist
        # 2. 条件/类目违规
        if any(v.startswith("CONDITION") or v.startswith("CATEGORY") for v in snap.violations):
            return "policy_violation"
        # 3. 卖光但开了 Out-of-Stock 控制
        if snap.quantity == 0 and snap.selling_state == "Ended":
            return "out_of_stock"
        # 4. 正常售罄
        if snap.quantity == 0:
            return "sold_out"
        # 5. 平台无理由结束
        return "platform_ended"

    def decide(self, snap: ListingSnapshot) -> Dict:
        kind = self.classify(snap)
        if kind == "account_restriction":
            return {"action": "halt_automation",
                    "next": "人工申诉 + 拉 Seller Standards",
                    "auto": False}
        if kind == "policy_violation":
            return {"action": "revise_then_relist",
                    "next": "ConditionGuard 修正 condition/category 后 Revise",
                    "auto": True}
        if kind == "out_of_stock":
            return {"action": "keep_hidden_until_replenish",
                    "next": "补货后 Revise quantity,不开新 listing",
                    "auto": True}
        if kind == "sold_out":
            return {"action": "relist_or_archive",
                    "next": "有复用模板则 Relist,否则归档",
                    "auto": True}
        return {"action": "investigate",
                "next": "查 GetItem 的 ListingRecommendations/Violations",
                "auto": False}

# ==================== 账号健康探针 (MC011 预防) ====================
@dataclass
class SellerHealth:
    defect_rate: float = 0.0        # <2% 安全
    late_ship_rate: float = 0.0     # 越低越好
    cancel_rate: float = 0.0        # 越低越好
    tracking_upload_rate: float = 0.0  # 越高越好
    out_of_stock_rate: float = 0.0  # eBay Refurbished 要求 <2%
    snad_return_rate: float = 0.0   # <4%

class AccountHealthProbe:
    THRESHOLDS = {
        "defect_rate": 0.02,
        "late_ship_rate": 0.03,
        "cancel_rate": 0.02,
        "tracking_upload_rate": 0.95,   # 至少 95%
        "out_of_stock_rate": 0.02,
        "snad_return_rate": 0.04,
    }

    def risk(self, h: SellerHealth) -> Dict:
        alerts = []
        if h.defect_rate > self.THRESHOLDS["defect_rate"]:
            alerts.append("缺陷率超 2% → MC011 高风险")
        if h.late_ship_rate > self.THRESHOLDS["late_ship_rate"]:
            alerts.append("迟发率超 3%")
        if h.cancel_rate > self.THRESHOLDS["cancel_rate"]:
            alerts.append("取消率超 2% → 库存/代发问题")
        if h.tracking_upload_rate < self.THRESHOLDS["tracking_upload_rate"]:
            alerts.append("有效追踪上传率 <95%")
        if h.out_of_stock_rate > self.THRESHOLDS["out_of_stock_rate"]:
            alerts.append("Out-of-stock 率超 2% → Refurbished 资格危险")
        if h.snad_return_rate > self.THRESHOLDS["snad_return_rate"]:
            alerts.append("SNAD 退货率超 4%")
        return {
            "mc011_risk": "HIGH" if alerts else "LOW",
            "alerts": alerts,
            "auto_publish_allowed": len(alerts) == 0,
        }
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# ==================== 演示 ====================
if __name__ == "__main__":
    cache = ConditionPolicyCache()
    guard = ConditionGuard(cache, refurb_program_approved=False)
    defense = ListingDefense()
    health = AccountHealthProbe()

    print("=== 1. 手机类用 Seller Refurbished (2500) → 应 block 并降级 Used ===")
    r = guard.check("9355", 2500, description="iPhone 13 seller refurb")
    print(f"  ok={r.ok} level={r.level} suggest={r.suggested_condition} reasons={r.reasons}")

    print("\n=== 2. 手机类用 Certified Refurb (2000) 但没过程序 → block ===")
    r = guard.check("9355", 2000, description="certified refurb iPhone")
    print(f"  ok={r.ok} level={r.level} suggest={r.suggested_condition} reasons={r.reasons}")

    print("\n=== 3. Vintage 类用 Refurbished → block ===")
    r = guard.check("15724", 2500, description="vintage jacket")
    print(f"  ok={r.ok} level={r.level} reasons={r.reasons}")

    print("\n=== 4. 手机类用 Used=3000 → ok ===")
    r = guard.check("9355", 3000, description="used iPhone 13 95% battery")
    print(f"  ok={r.ok} level={r.level} reasons={r.reasons}")

    print("\n=== 5. 下架分类: 账号级限制 ===")
    snap = ListingSnapshot("IT-1", "9355", 3000, "Ended",
                           end_reason=None, quantity=3, violations=["MC011"])
    print(f"  classify={defense.classify(snap)} → {defense.decide(snap)}")

    print("\n=== 6. 下架分类: condition 违规 → revise 后 relist ===")
    snap = ListingSnapshot("IT-2", "9355", 2500, "Ended",
                           end_reason="Policy violation", quantity=2,
                           violations=["CONDITION_NOT_ALLOWED"])
    print(f"  classify={defense.classify(snap)} → {defense.decide(snap)}")

    print("\n=== 7. 下架分类: 卖光但开 Out-of-Stock ===")
    snap = ListingSnapshot("IT-3", "31387", 3000, "Ended",
                           end_reason=None, quantity=0,
                           violations=[])
    print(f"  classify={defense.classify(snap)} → {defense.decide(snap)}")

    print("\n=== 8. 账号健康: 代发翻车前兆 ===")
    h = SellerHealth(defect_rate=0.03, late_ship_rate=0.05,
                     cancel_rate=0.04, tracking_upload_rate=0.88,
                     out_of_stock_rate=0.03, snad_return_rate=0.06)
    print(f"  {health.risk(h)}")
跑出来关键几行:
=== 1. 手机类用 Seller Refurbished (2500) → 应 block 并降级 Used ===
  ok=False level=block suggest=3000 reasons=['condition 2500 不在类目 9355 白名单']

=== 2. 手机类用 Certified Refurb (2000) 但没过程序 → block ===
  ok=False level=block suggest=3000 reasons=['该类目 Refurbished 需 eBay Refurbished Program 预审']

=== 5. 下架分类: 账号级限制 ===
  classify=account_restriction → {'action': 'halt_automation', 'auto': False, ...}

=== 6. 下架分类: condition 违规 → revise 后 relist ===
  classify=policy_violation → {'action': 'revise_then_relist', 'auto': True, ...}

=== 8. 账号健康: 代发翻车前兆 ===
  {'mc011_risk': 'HIGH', 'auto_publish_allowed': False,
   'alerts': ['缺陷率超 2% → MC011 高风险', '迟发率超 3%', '取消率超 2% → 库存/代发问题', ...]}

四、ERP 落地五条铁律

  1. ConditionID 不写死:按 category_id + site + seller_eligibility 动态拉 getItemConditionPolicies,否则手机类用 2500、时尚类用 2000 都会后期被 eBay 强制结束。

  2. Refurbished ≠ 随便标2000/2010/2020/2030 走 eBay Refurbished Program,要预审、要 30 天退货、要保修/物流标准;没资格就用 Used=3000

  3. For parts(7000) 不进可售库存:它和"二手可售"是两套供应链,混在一起会拉高 SNAD 退货率,反过来诱发 MC011。

  4. Ended 必须先分类再动作

    • 账号限制 → 停自动化

    • 条件/类目违规 → Guard 修正后 Revise

    • 0 库存 → 补货后 Revise quantity(别开新 listing 制造重复刊登)

    • 售罄 → Relist/归档

  5. MC011 是经营问题不是 API 问题:追踪上传率、迟发率、取消率、SNAD 率、out-of-stock 率全部进 AccountHealthProbe红色就停自动发布,比"被限制后救账号"便宜 100 倍。


五、和前几篇的衔接

  • 前篇 MarketplaceAdapter 的领域模型里,condition_int 到 eBay 要经 EbayConditionResolver(统一 0~100 → eBay ConditionID + conditionDescription)。

  • 前篇 ComplianceGate 在国内管图片/字段,在 eBay 就变成 ConditionGuard + CategoryPolicyCache

  • 前篇 StockEngine 的"0 库存"语义在 eBay 要接 OutOfStockControlPreference:卖光不结束 listing、补货即恢复,避免频繁 Relist 被算重复刊登。

  • 前篇 ObservabilityMiddleware 在这里多挂一个 AccountHealthProbe 面板:MC011 风险 HIGH = 自动发布开关置灰。
    eBay 二手专区的本质:成色不是字段,是"类目 × 资格 × 信任分"的三维对象;ERP 的活不是发上去,而是别让平台替你删。

要不要我把 ebay_secondhand_defense.py 扩成 commerce-mesh/adapters/ebay/:真实 getItemConditionPolicies 缓存预热、Trading/Inventory API 的 Revise/Relist 幂等、MC011 风险评分落库、以及和闲鱼/Back Market/Mercari 统一进 AccountHealthProbe 的多平台信任分看板?


群贤毕至

访客