# 速卖通(AliExpress POP)「选品建listing」完整配方 > 2026-09-30 实测跑通:**4/4 进「待发布」,采集箱 0 残留**(RV8I478D / ZQI01T5J / XSSIERBV / XAGOKS9C)。 > 目标态 = 待发布(`dxmState=offline & dxmOfflineState=waitPublish`),**绝不点「发布」**。 ## 0. 页面与接口事实 | 项 | 值 | |---|---| | 创建页 | `https://www.dianxiaomi.com/web/smt/add`(标题「店小秘--添加速卖通产品」) | | 编辑页 | `/web/smt/edit?id=` —— **实测打不开**:`GET /api/smtProduct/edit.json?id=` 无论 id 传什么都返回同一份 12,592 字节的**添加页引导包**(modelList/unitList),不含商品数据 → 不要指望它做回读 | | 保存 | `POST /api/smtProduct/add.json`(点「保存」触发;**不点「发布」**) | | 列表 | `POST /api/smtProduct/pageList.json`,body: `pageNo&pageSize&total&searchType=0&searchValue&shopId=-1&dxmState&dxmOfflineState` | | 列表页 | 待发布 `/web/smt/smtProductList/offline`、采集箱 `…/draft`、在线 `…/online` | | 类目 | 点「选择分类」→ 弹窗标题**「选择类目」**、搜索框 `input[placeholder*=搜索分类]`、结果行 `.search-result-item` | | 单位 | **kg / cm**(Ozon 是 g / mm,别混) | | 店铺 | 10 家:BlessLuckA、Laikeet、Sunbeam、AliMingPro1、Key、Deanoyal… | ## 1. 执行顺序(顺序错了就被挡) ``` ① 店铺(必须最先!类目树随店铺/站点变) ② 类目(中文品类词搜索 → 点结果行,弹窗自动关) ③ 标题(insertText) ④ 来源URL(无 label,用 placeholder 定位) ⑤ 零售价格 / 库存数量 / 货值 / 商品编码 ⑥ 重量(kg)、包装尺寸(cm) ×3、包装后尺寸(cm) ×3 ⑦ 图片(选择图片 → 网络图片 → 粘 URL → 添加) ⑧ 物流属性(铅笔图标弹窗 → 普货 → 确定) ⑨ 是否原箱(下拉,必填)、报价是否含关税(radio value=1 不含关税报价) ⑩ PC端描述(CKEditor 4) ⑪ 保存 → 回读 pageList ``` ## 2. 逐字段硬知识(每条都踩过) 1. **不选店铺 → 「选择分类」根本打不开**(弹窗不出现,表现为 `no-modal`)。脚本里店铺选择必须在类目前。 2. **类目搜索词必须是中文**:`旗帜→横幅(Banners)`、`托特包→Tote Bags`、`护照夹→护照套/护照夹`。 **英文搜不到**(`Tote Bags` → 命中 0 → 保存报「请选择分类」)。 结果行文本形如 `Luggage & Bags(箱包)/Kids' Bags(童包)/Tote Bags(托特包)`(分隔符 `/`,中英双语), **点行即选中并关窗**,没有可用的「选择」确认按钮。 * 已知瑕疵:`托特包` 命中 3 条时会选到第一条「童包」,优先命中父级段(`Women Bags`)的逻辑已加,但速卖通树里未必存在该英文段 → 需要时人工在页面上点一次。 3. **图片门槛 单边 ≥400px**:喂 10 张图,速卖通**静默只收 6 张**(300×224 / 300×195 / 367×344 / 300×213 被丢)。 → 建输入前先按真实尺寸过滤:`build-add-input.py --min-side 400`(会下载探尺寸)。 模块:`.productMainImgModule`,头按钮文案「选择图片」;弹层 tab:本地图片 / **网络图片** / 图片银行 / 引用采集图片; 网络图片 tab 的 textarea placeholder「请填写图片URL地址,多个地址用回车换行」→ 点「添加」。 回读口令:`主图最多选用6张,已经选用了6张`(上限 **6 张主图**)。 4. **保存必填项(缺一个就整单失败)**——错误清单来自 `MYJ.message.error` 钩子 + `.ant-form-item-explain-error`: | 报错原文 | 要填什么 | 正确写法 | |---|---|---| | 请输入货值 | 货值(CNY) | 用成本价兜底(`cost_cny`) | | 物流属性必填! | 物流属性 | **不是普通下拉**:点 `.single-logistics-attr-edit` 铅笔 → `.batch-edit-modal` → 选「普货」→ 确定 | | 包装后尺寸请填写整数! | 包装后尺寸 | **整数 cm**;包装尺寸与包装后尺寸都要填,且**尺寸建议整体向上取整** | | 请选择报价是否含关税! | radio | `input[type=radio][value=1]` = 不含关税报价 | | 请输入重量 | 重量(kg) | **必须精确命中「重量(kg)」**:类目属性里还有「重量(Item Weight)」,用 `/重量/` 会填错字段(护照夹实测被拒) | | 请选择分类 | 类目 | 见第 2 条 | 5. **重量下限**:46g → 0.046 → 字段只留 2 位小数变 `0.04` → 被拒「请输入重量」。 安全写法:`wkg = max(0.05, ceil(g/10)/100)` → 0.046→0.05、0.075→0.08、0.097→0.10。 6. **描述是 CKEditor 4 两个实例**:`ckeditor30`=PC端描述、`ckeditor31`=无线端描述(实测确认)。 **必须按实例所属 form-item 定位**: ```js const hit = Object.keys(CKEDITOR.instances).find(k => { const el = CKEDITOR.instances[k].element?.$; return el && formItem.contains(el); }); CKEDITOR.instances[hit].setData(html); CKEDITOR.instances[hit].updateElement(); ``` * editable 元素的 `id` **不等于**实例名 → 用 `CKEDITOR.instances[ed.id]` 会落空; * 退化成「改 iframe innerHTML / contenteditable」时 model 不同步 = 白填。 * 实测成功回读:`ckeditor:ckeditor30:len=2290`。 7. **`pageList` 不返回 `detail` / `packageWeight` / `packageLength`**(100 行全空)→ **不能**用它判断描述/重量/尺寸是否存进去,只能看保存时有没有报错。别把空值当「没保存」。 ## 3. 脚本 ```bash D=/home/admin9webs/dsh-dingtalk-workspace/listing-spec # ① 生成输入(含 400px 图过滤 + 中文类目词) python3 $D/platform-shared/build-add-input.py pending-package-*.json \ --platform 速卖通 --out /tmp/smt_input.json --min-side 400 # ② 干跑(只填不保存,验证选择器) node $D/platform-smt/smtbatch.mjs --input /tmp/smt_input.json --only --dry # ③ 正式建listing(只保存 → 待发布) node $D/platform-smt/smtbatch.mjs --input /tmp/smt_input.json --only # ④ 回读验收(待发布有没有 + 采集箱有没有本轮新草稿) node $D/platform-smt/verify-landing.mjs --oids , --keywords "kw1,kw2" ``` * `platform-shared/dxm-add-lib.mjs`:公共交互库(CDP 真实点击/滚动、setByLabel(insertText)、 `setNumberByLabel`(原生 setter,避免小数被吃)、`pickCategoryBySearch`、`addImagesByUrl`、 `setRichTextByLabel`(CKEditor)、`setLogisticsAttr`、`listShopOptions`、`readErrors`)。 * `platform-shared/preflight-shops.mjs`:**每轮批量前先跑**——查每个平台有没有授权店铺。 * `platform-shared/build-add-input.py`:把待上架 package 转成建listing输入;图按真实尺寸过滤; 英文类目叶 → 中文搜索词映射表 `CAT_KW`(命中不了就补这张表,别在页面上手点)。 ## 4. 验收口径(实测结果) ``` RV8I478D ✅ 待发布 1 | 草稿 0 | 横幅 ¥6.65 图6 ZQI01T5J ✅ 待发布 1 | 草稿 0 | 托特包 ¥5.30 图6 XSSIERBV ✅ 待发布 1 | 草稿 0 | 护照套/护照夹 ¥6.90 图6 XAGOKS9C ✅ 待发布 1 | 草稿 0 | 托特包 ¥15.90 图6 ``` * 采集箱里**有**大量历史草稿(有些是 2022/2023 年的),按关键词搜会误报残留 → 回读脚本只把**最近 N 分钟**建的草稿算成本轮残留(`--since-min`,默认 180)。 * SKU 前缀不能单独当判据:RV8I478D 的 SKU 是 1688 供应商码 `ZSJLQ_1be`,不含 oid → 回读用「SKU 前缀 **或** 标题关键词」双条件。 ## 5. 仍未解决 / 待补 * 类目分支优选(托特包落「童包」而非「女包」)——需要人工确认或补父级英文段。 * `包装后尺寸` 与 `包装尺寸` 目前都填同一组向上取整值;如需精算需接速卖通计抛规则。 * 变种(多变种)模式未做:本配方用单品/单 SKU 路径;带变种的类目需要补变种行填写。 * 运费模板/服务模板未设置(`freightTemplateId=null`),发布前需人工在页面选或做「运费推荐生成」。 ## 附:账号级店铺盘点(一次接口,最快) `platform-shared/dxm-add-lib.mjs` 的 `readAccountShops(c)`:`GET /api/userIn.json` → `data.shopMap` (key=shopId,value 含 `name`/`platform`)。实测 2026-09-30(子账号): ``` 256 家店 → smt:61 tiktok:98 tiktokGlobal:24 aliChoice:65 ozon:5 walmart:3 ``` ★ **页面上的店铺下拉是子集**:速卖通页只列 10 家,`shopMap` 里是 61 家。 判断「这个登录有没有某平台店铺」用 `readAccountShops`;判断「这个店能不能在这个页面选到」才用 `listShopOptions`。