docs: 建品配方迁至 kb/handbook(留指针)
This commit is contained in:
1 parent
917df5b2d6
commit
ced43b19d3
9 files changed
+27
-1842
No files matched your search
+3
-155
@@ -1,156 +1,4 @@
|
|||||||
# aliChoice(速卖通全托管)配方 — **已跑通**(批量进行中)
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/alichoice.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
路由:`https://www.dianxiaomi.com/web/smtChoice/add` 列表:`/web/smtChoice/{draft=采集箱, offline=待发布}`
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
|
|
||||||
## 已经打通的(批量脚本 `alcbatch.mjs`,单条已验证保存成功)
|
|
||||||
1. **店铺**:`店铺名称` 下拉(实测 2 家:`XYZ Store` / `UniStyle Bag Store`),取第一家 → `shopId=4393191`
|
|
||||||
2. **备货类型**:下拉 `国内履约` / `海外备货` → 取第一个(→ `productType='1'`)
|
|
||||||
3. **类目**:`选择分类` 弹窗 → 用 `sh_build.selectCategory(c, cat_kw, cat_kw)`(与 SHEIN/Temu 同一套)
|
|
||||||
4. **标题** → `smtChoiceAddBasicStore.formState.subject`(⚠️ **不是 Joom 的 `name`**)
|
|
||||||
5. **主图** → `smtChoiceAddProductStore.formState.mainImage[]`(吃 `{url,imgUrl,name,width,height}`)
|
|
||||||
6. **PC端描述** → `smtChoiceAddStore.describeInfoData.detail` + **CKEditor setData**(见下)
|
|
||||||
7. **SKU 行** → `smtChoiceSkuDataStore.formState.skuDataList[]`
|
|
||||||
字段:`skuPrice / skuCode / scItemBarCode / packageWeight / packageLength / packageWidth /
|
|
||||||
packageHeight / logisticAttrListJson / logisticAttrList / imageList`
|
|
||||||
8. **必填类目属性**(品牌 / 高关注化学品 / 产地)→ `fillRequiredSelects()` 现已能自动填
|
|
||||||
(产地会输「中国」过滤后选 `中国大陆`)
|
|
||||||
|
|
||||||
## ✅ 「请选择物流属性」已解(保存两遍)
|
|
||||||
保存时钩子给 `MYJ.error: 校验失败`,DOM 里是一个**没有 label** 的报错块:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="ant-form-item-has-error">
|
|
||||||
<span></span><i class="iconfont icon_edit2 link ml-4"></i>
|
|
||||||
<div class="text-red-500 font-size-12 mr-[20px]">请选择物流属性</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
解法(**保存两遍**):
|
|
||||||
1. 先点一次保存 → 报错块出现
|
|
||||||
2. 点报错块里的 `i.icon_edit2` → 弹出「修改物流属性」modal(20 个复选框,按大类分组:
|
|
||||||
禁运 / 普货 / 纯电 / 强磁 / 特货(液体·粉末·膏体·其他) / 特敏 / 内电(8 种电池) / 外电 / 弱磁)
|
|
||||||
3. 勾 **普货** → 点「确定」→ 再点保存 → `您的产品已保存到「待发布」`
|
|
||||||
|
|
||||||
对应 SKU 行的 `logisticAttrListJson` / `logisticAttrList` 两个字段。
|
|
||||||
|
|
||||||
## ✅ PC端描述是 CKEditor,写 textarea 没用
|
|
||||||
DOM 里那个 `TEXTAREA` 是 `display:none` 的藏匿字段,真实控件是 **CKEditor**
|
|
||||||
(aria-label「所见即所得编辑器, cked…」)。两种赋值方式,实测都写上才稳:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// 1) 给编辑器实例 setData(本例 window.CKEDITOR.instances 有 2 个实例)
|
|
||||||
Object.values(window.CKEDITOR.instances).forEach(x => x.setData(html));
|
|
||||||
// 2) 同时写 store
|
|
||||||
pinia._s.get('smtChoiceAddStore').$state.describeInfoData.detail = html;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 另一个未验证项:4 个规格组都是 0 选项## 另一个未验证项:4 个规格组都是 0 选项
|
|
||||||
`smtChoiceSkuAttrStore.formState.skuAttrs` 有 4 组:
|
|
||||||
`Ships From(200007763) / Color(14) / Number of Pcs(200116262) / Bulk Buy(200483263)`,
|
|
||||||
但 `options` 都是 0(值在 `_options` 或要异步加载),DOM 里能看到值(卡其色/粉红/6Pc/Set…)。
|
|
||||||
**实测:不选变种值也能保存成功**(保存链路里没有因为变种报错),所以批量里先不选。
|
|
||||||
|
|
||||||
## 单条顺序(批量脚本里的固定顺序)
|
|
||||||
`店铺 → 备货类型 → 类目 → 标题/主图/SKU行 → 必填属性(品牌/高关注化学品/产地) → PC端描述(CKEditor)
|
|
||||||
→ 保存① → 物流属性(普货) → 保存② → 待发布`
|
|
||||||
|
|
||||||
## 本轮顺手修好的共享 bug(`shein建品/sh_build.mjs` 的 `fillRequiredSelects`)
|
|
||||||
之前它在 aliChoice 上**一个必填属性都填不上**,三个原因,都已修:
|
|
||||||
1. select 分支的必填判断只看 `label.ant-form-item-required`,而速卖通系的属性是
|
|
||||||
`.attr-label.required`(label 上还常带 `*` 前缀)→ 现在三种标记都认
|
|
||||||
2. 容器项「产品属性」本身也带 `*`,会被当成一个字段去点 → 现在跳过**含嵌套 `.ant-form-item`** 的项
|
|
||||||
3. 点开下拉后按 `.pop()` 取最后一个可见下拉,容易读到**上一个字段残留**的列表
|
|
||||||
(曾把化学物列表当国家列表)→ 现在①每次点前先 `Esc` ②按**与点击点的距离**挑下拉
|
|
||||||
③产地/Origin 先 `Input.insertText('中国')` 过滤
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ 批量首次运行的成败分类(6 条:2 成功 / 4 失败)→ 已停批量
|
|
||||||
|
|
||||||
| 现象 | 根因 | 处置 |
|
|
||||||
|---|---|---|
|
|
||||||
| `请上传文件!`(`商品说明书`) | **健康/医疗类目强制资质文件**(与 HOOK.md 里速卖通 POP 那条同源) | 换不要求说明书的类目;无干净替代类目的商品只能人工处理 |
|
|
||||||
| `请选择产品属性`(`是否包含电池`) | 该下拉**点不开**(`no-dd`),疑似要先把别的属性选完才可用 | 待查:单独 dump 这个控件的依赖关系 |
|
|
||||||
| `请选择物流属性` | 已知,走「保存①→点 icon_edit2→勾普货→保存②」 | 批量里已实现 |
|
|
||||||
| `选类目失败:no-path` | `cat_kw` 在 aliChoice 类目树里搜不到 | **已修**:依次回退 `cat_fb`(tk_input 里给的后备品类词) |
|
|
||||||
|
|
||||||
**结论**:aliChoice 能不能铺,取决于**类目能不能选到不要求资质文件的那一支**。
|
|
||||||
下一轮建议:先把 48 条的 `cat_kw → 实际类目` 跑一遍 dry(不保存),
|
|
||||||
把落到健康/医疗/化妆品等强资质类目的商品挑出来,再决定是换词还是跳过。
|
|
||||||
|
|
||||||
失败记录现在会带 `errItems`(例如 `商品说明书:请上传文件!`),见 `alc_done.json`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第二次批量(带 dry 扫描出来的类目表)进展与新的失败类
|
|
||||||
|
|
||||||
`alcdry.mjs` 先扫类目(不保存),把每个商品**第一个不强制「商品说明书」**的品类词写进
|
|
||||||
`alc_route.json`(47/47 都有可用类目;例:温度计「温度计」强制说明书 → 回退到「家用电器」`说明书=否`)。
|
|
||||||
`alcbatch.mjs` 之后只读这张表,不再猜类目。
|
|
||||||
|
|
||||||
**结果**:`商品说明书` 类失败归零;但冒出两类**新的必填**(都是类目动态属性/SKU 级):
|
|
||||||
|
|
||||||
| 现象(`errItems`) | 猜测与下一步 |
|
|
||||||
|---|---|
|
|
||||||
| `(无label):请选择SKU分类` | SKU 行上有个 **SKU分类** 下拉要选(Temu 的 SKU 表也有这一列)。下一步:定位它的落点(可能 `skuDataList[].skuClassification`)或走 UI 选第一项 |
|
|
||||||
| `材质(Material):` / `尺寸(Size):` | 类目动态属性里**把下拉当文本**的项,`fillRequiredSelects` 选了但值没生效(红色提示为空)→ 下一步 dump 这两项的控件类型(可能是可搜索 select 或 input) |
|
|
||||||
|
|
||||||
成材率从 2/6 提升到 5/8,其余靠这两类补齐。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 根因修正(第 4 轮):那串「材质/尺寸/品牌」报错其实是**类目弹窗没关**
|
|
||||||
|
|
||||||
复现时 `selectCategory` 返回了 `path:… / MODAL-STILL-OPEN` —— 类目弹窗还开着,
|
|
||||||
**后面所有必填属性的点击全被遮罩挡住**,于是保存时报出一串看起来五花八门的必填
|
|
||||||
(品牌/产地/材质/尺寸),让人误以为是「属性选不上」。真正要修的是关弹窗。
|
|
||||||
|
|
||||||
**修法**(`sh_build.mjs` 的 `selectCategory` 收尾循环):3 轮 → **6 轮**,每一轮都试
|
|
||||||
`确定|选择` → 再试 `关闭|取消` → 点右上角 × → 发 `Escape`;第 4 轮起再点一次
|
|
||||||
`.ant-modal-wrap`(遮罩)兜底。返回值里仍带 `MODAL-STILL-OPEN` 便于发现没关干净。
|
|
||||||
|
|
||||||
## 第二类:`材质(Material)` 是**文本输入**,不是下拉
|
|
||||||
`fillRequiredSelects` 只处理 select 和勾选组,遇到 `INPUT.ant-input` 直接跳过,
|
|
||||||
所以材质永远填不上。新增 `fillRequiredTextInputs(c, values)`:
|
|
||||||
- 只挑**必填 + 空 + 含 `input.ant-input`/`textarea.ant-input`** 的叶子表单项
|
|
||||||
- 用原生 setter 写值 + 派发 `input`/`change`/`blur`
|
|
||||||
- `values` 按顺序发(不够就重复最后一个)
|
|
||||||
|
|
||||||
批量里给的候选值:先从标题里猜材质词(plastic/cotton/polyester/stainless steel/metal/
|
|
||||||
silicone/wood/resin/aluminum/alloy/fabric/nylon/glass/ceramic/leather),再退 `Polyester`、`Other`。
|
|
||||||
⚠️ 这是**兜底值**,材质属于商品事实,后续应人工抽查(尤其非纺织类)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第 5 轮:`请选择SKU分类` 是**原生 `<select>`**(不是 antd)
|
|
||||||
|
|
||||||
报错项的 DOM:
|
|
||||||
```html
|
|
||||||
<div class="ant-form-item-has-error">
|
|
||||||
<select class="w-180! shrink-0 g-form-component">
|
|
||||||
<option disabled selected>请选择</option>
|
|
||||||
<option value="1">单品单件</option><option value="2">同款多件</option><option value="3">混合套装</option>
|
|
||||||
…
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
antd 系的 `.ant-select` 选择器一个都匹配不到 → 新增
|
|
||||||
`fillNativeSelects(c, ['单品单件','件/个'])`:遍历可见原生 `select`,
|
|
||||||
值为空/停在下拉占位项时,优先选含「单品单件」「件/个」的项,否则选第一个可用项,
|
|
||||||
`sel.value=…` + 派发 `input`/`change`。**效果:失败从 6/10 降到 8/10**,`SKU分类` 类失败归零。
|
|
||||||
|
|
||||||
## ⚠️ 自己引入的数据质量问题(已修,未落库)
|
|
||||||
`fillRequiredTextInputs` 一开始对**所有**文本必填都发同一个材质词,
|
|
||||||
结果把「颜色(Color)」填成了 `Resin`。已改为**按字段名给值**(helper 支持传函数):
|
|
||||||
- `颜色|Color` → 标题里的颜色词,兜底 `Multicolor`
|
|
||||||
- `材质|Material` → 标题里的材质词,兜底 `Polyester`
|
|
||||||
- `图案|Pattern` → `Solid`;`尺寸|Size` → `Medium`;其余 → `Other`
|
|
||||||
|
|
||||||
查过 `alc_done.json`:**被填错的 `866537106657` 当时是失败状态**(`errItems=["颜色(Color):"]`),
|
|
||||||
没有落成脏数据,已用新代码重跑。
|
|
||||||
|
|
||||||
### 第 5 轮的自我踩坑(留个记录)
|
|
||||||
给「文本必填按字段名给值」打补丁时,我用 `const t = String(it.title)` 存标题——
|
|
||||||
**`t` 是外层任务循环变量**,块级 `const` 把它推进 TDZ,整批 8 条当场挂在
|
|
||||||
`ReferenceError: Cannot access 't' before initialization`。改名 `ttl` 后恢复。
|
|
||||||
教训:往批量脚本里塞局部变量前,先看一眼外层循环变量叫什么(同一函数里 `t` 已经被占用)。
|
|
||||||
+3
-46
@@ -1,47 +1,4 @@
|
|||||||
# Joom 建品配方(已跑通,批量进行中)
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/joom.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
路由:`https://www.dianxiaomi.com/web/joomProduct/add` 列表:`/web/joomProduct/{draft=采集箱, offline=待发布}`
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
|
|
||||||
脚本:`jmbatch.mjs`(`node jmbatch.mjs` 全量 · `node jmbatch.mjs <pid>` 单条 · `--dry` 只构建)
|
|
||||||
断点:`jm_done.json` 逐条日志:`jm_batch.log` 实际落到的类目:`joom_route.json`
|
|
||||||
|
|
||||||
## ⭐ 关键发现:Joom 的表单数据不在 `joom*` store 里
|
|
||||||
商品主体数据落在 **`smtChoiceAddBasicStore`**(速卖通全托管的 store),
|
|
||||||
Joom 与 **aliChoice 共用同一套 `smtChoice*` store**。定位方法:在页面上手输一个
|
|
||||||
哨兵串(`PROBE-TITLE-XYZ`),再遍历所有 pinia store 的 `$state` 找它落在哪。
|
|
||||||
|
|
||||||
| 字段 | 落点 |
|
|
||||||
|---|---|
|
|
||||||
| 店铺 | `smtChoiceAddBasicStore.formState.shopIds[]` / `shopId`(**店铺是复选框组,不是下拉**) |
|
|
||||||
| 类目 | `smtChoiceAddBasicStore.formState.categoryId`(复合串 `1473…-16-2-118-2600…`) |
|
|
||||||
| 标题 / ParentSKU / 描述 / 标签 | `smtChoiceAddBasicStore.formState.{name, parentSku, description, tags[]}` |
|
|
||||||
| 价格 / 库存 / 运费 / MSRP / 报关金额 | `joomAddPriceStore.formState.{price, inventory, shipping, msrp, declaredValue}` |
|
|
||||||
| 主图 / 详情图 | `joomAddImageStore.formState.imageList[]` / `detailImageList[0].images[]`(吃 `{url,imgUrl,name,width,height}` 数组) |
|
|
||||||
| SKU / 颜色 / 尺码 | `joomSkuDataStore.formState.skuDataList[]` / `joomSkuAttrStore.formState.{colorValues,sizeValues}` |
|
|
||||||
| 自定义属性 | `smtChoiceAddAttrStore.formState.customAttrs[]` |
|
|
||||||
| 类目属性(`attrsInfo.attrsList` 10 条) | `joomAddStore.attrsInfo`(`joomAddStore` **没有 formState**) |
|
|
||||||
|
|
||||||
## 单条顺序(不能换)
|
|
||||||
1. **勾店铺**:`店铺名称` 是 `.d-checkbox-group-wrapper`,共 **85 个复选框** = `全部` + 84 家店。
|
|
||||||
勾「第一个非全部」= `Dummer-Pets`。⚠️ **别碰 `全部`**(那是铺满 84 家店)。
|
|
||||||
2. **选类目**:`产品分类` 是可搜索 `.ant-select`(不是 SHEIN/Temu 的弹窗)。点开 →
|
|
||||||
`Input.insertText(cat_kw)` → 在 `.ant-select-item-option` 里选**去掉括注后完全相等**的,
|
|
||||||
没有就退到第一个(Joom 类目比 1688 粗,实测「发圈」只能落到「发夹(Hairpins)」)。
|
|
||||||
3. 填 store(标题/ParentSKU/描述/标签 + 价格/库存/运费/报关金额 + 主图/详情图)
|
|
||||||
4. `fillRequiredSelects()`(复用 SHEIN 的兜底)→ **敏感货类型 = 普通货**
|
|
||||||
5. **详情图语言** → 目标项**标签就叫「详情图」**(不含"语言"二字):
|
|
||||||
下拉是虚拟滚动的,前 10 项没有「英语」→ 先 `Input.insertText('英')` 过滤,
|
|
||||||
再点含「英」的项(否则会落到「阿拉伯语」)
|
|
||||||
6. 保存 → `您的产品已保存到「待发布」`
|
|
||||||
|
|
||||||
## 钩子新抓出的两条「没标必填但必须填」
|
|
||||||
| 被吞的报错 | 真相 |
|
|
||||||
|---|---|
|
|
||||||
| `MYJ.error: 运费不能为空` | 运费**没有** `*`,但必须填(给 `0` 即可) |
|
|
||||||
| `MYJ.error: 请选择详情图语言且至少添加一张详情图` | 语言必选;图片 store 已赋值也要选语言 |
|
|
||||||
|
|
||||||
## 与其它平台的差异
|
|
||||||
- 店铺:Joom 是**复选框组**(多选=铺多店),SHEIN/Temu/Lazada 是下拉
|
|
||||||
- 类目:Joom 是**可搜索下拉平铺列表**,SHEIN/Temu 是「选择类目」弹窗(搜索→点结果→关弹窗)
|
|
||||||
- 数据落点:Joom 用 `smtChoice*`(与 aliChoice 同构),SHEIN/Temu 各有自己的 `shein*` / `temu*`
|
|
||||||
- **不消耗 EAN**(GTIN 非必填、`showGtin=false`)
|
|
||||||
+3
-165
@@ -1,166 +1,4 @@
|
|||||||
# ⏸ Lazada 建品(2026-09-21 决定暂时放弃,勿继续投入)
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/lazada.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
> 恢复入口:本文件「第 12 轮」那节 —— 类目已通(英文词 `Hair Accessories`),
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
> 下一步是 dump **全部** store 找标题/品牌/税/KEY 属性的落点。
|
|
||||||
|
|
||||||
# Lazada 建品侦察 — 未跑通,下一轮从这里接着做
|
|
||||||
|
|
||||||
路由:`https://www.dianxiaomi.com/web/lazada/add` 列表:`/web/lazada/{draft=采集箱, offline=待发布}`
|
|
||||||
|
|
||||||
## 已勘明(`lz_probe1.mjs`)
|
|
||||||
|
|
||||||
### 1. 店铺 = 下拉(按站点后缀分店)
|
|
||||||
第一家 = **`Shehoney_MY`(shopId 3336185)**。下拉前 10 项:
|
|
||||||
`Shehoney_MY / _SG / _TH / _ID / _PH / _VN`、`Nicegar_MY / _SG / _TH / _ID …`
|
|
||||||
—— 同一运营主体按站点拆店,**「自然序第一家」= `Shehoney_MY`**。
|
|
||||||
|
|
||||||
### 2. 类目
|
|
||||||
`分类 ---- 请选择分类 ---- 选择分类`(`选择分类` 按钮在),形态大概率与 SHEIN/Temu/aliChoice
|
|
||||||
同一套弹窗 → 直接用 `shein建品/sh_build.mjs` 的 `selectCategory(c, kw, kw)`。
|
|
||||||
|
|
||||||
### 3. 必填项(表单项标签带 `*`)
|
|
||||||
`店铺 · 分类 · 产品标题(≤255) · 品牌 · 税 · 产品图片`
|
|
||||||
非必填:店小秘分类、来源URL、包装内容(≤1000)、是否预售(白名单功能)、产品视频、产品描述。
|
|
||||||
|
|
||||||
⚠️ **品牌**有坑:提示语写「请选择下拉提示框品牌或者填写 Lazada 后台已有品牌,如无品牌则填写…」
|
|
||||||
(原文被截断)——大概率要求**品牌库里的真实品牌**或填「无品牌」占位,和 Walmart 的「无品牌」同类。
|
|
||||||
|
|
||||||
### 4. store 家族
|
|
||||||
选完店铺时 `pinia` 里**没有任何 `lazada*` store**(`stores:{}`)——说明
|
|
||||||
**store 是选完类目后才懒加载注册的**。下一轮选完类目再 dump 一次 store 名单即可。
|
|
||||||
|
|
||||||
## 下一轮的顺序建议
|
|
||||||
1. 选店铺(`Shehoney_MY`)→ 2. 选类目(弹窗)→ 3. dump `lazada*` store 名单与字段落点
|
|
||||||
→ 4. 标题/图片/品牌/税 → 5. **挂钩子再保存**(Lazada 大概率也有一套层层递进的必填)
|
|
||||||
|
|
||||||
## 已知可复用
|
|
||||||
- `shein建品/sh_lib.mjs`(`newPage`/`closePage`/`installHook`/`readHook`)
|
|
||||||
- `shein建品/sh_build.mjs`:`selectCategory`、`fillRequiredSelects`、`fillRequiredTextInputs`、`fillNativeSelects`
|
|
||||||
- 骨架直接抄 `joom建品/jmbatch.mjs` 或 `alichoice建品/alcbatch.mjs`
|
|
||||||
|
|
||||||
## 第 8 轮补充
|
|
||||||
|
|
||||||
1. **类目标签叫「分类」不叫「产品分类」** —— `sh_build.selectCategory` 原来只认 `/产品分类/`,
|
|
||||||
在 Lazada 上直接 `no-item`。已改成通用匹配:标签含「分类」且**不含**「店小秘」且该项里有
|
|
||||||
`选择分类`(改动在 `shein建品/sh_build.mjs`,对 SHEIN/Temu/aliChoice 同样生效)。
|
|
||||||
2. 改完之后点击能打开了,但**搜索结果是 `no-path`** —— 说明 Lazada 的 `选择分类` 弹窗
|
|
||||||
**不是** SHEIN/Temu 那个「左树右结果、`.search-result-item`」的结构。
|
|
||||||
下一轮第一步:点开 `选择分类`,dump 弹窗 DOM(搜 `搜索分类名称` 输入框在不在、
|
|
||||||
结果项的 class 是什么),再决定是复用还是另写一个 `selectCategoryLazada`。
|
|
||||||
3. 因为类目一直没选上,`lazada*` store 始终为空 —— store 懒加载的结论仍未验证。
|
|
||||||
|
|
||||||
## 第 9 轮:Lazada 选类目弹窗 = 同一套「选择类目」(只差结果项这一环)
|
|
||||||
|
|
||||||
点开 `选择分类` 后 dump 到:
|
|
||||||
- 弹窗标题 **选择类目**,有 **`搜索`** 按钮和 `input[placeholder="搜索分类名称"]`
|
|
||||||
- 左侧树用 `.categories-item` / `.categories-item-name`(顶层 46 项,英文类目名)
|
|
||||||
- 右侧 **未选择分类**
|
|
||||||
- 有 `.ant-modal-close`
|
|
||||||
|
|
||||||
**结论:这就是 SHEIN/Temu/aliChoice 那套弹窗**,`selectCategory` 理论上可直接复用;
|
|
||||||
上一轮 `no-path` 的原因**尚未确认**(候选:搜索按钮没点中、结果项不是 `.search-result-item`、
|
|
||||||
或该弹窗的搜索需要先选站点维度)。下一轮只需:输入关键词 → 点搜索 → dump 弹窗内 class 频次
|
|
||||||
(`.search-result-item` 在不在)→ 在不在就补一个 `.categories-item` 分支。
|
|
||||||
|
|
||||||
⚠️ 我这一轮拼验证脚本时把模板字符串切坏了(页面里报 `SyntaxError: Unexpected token 'return'`),
|
|
||||||
没跑出结果——**这是我的脚本错误,不是平台问题**。下一轮直接在这个 `lz_cat.mjs` 上小改即可
|
|
||||||
(它已经能稳定走到「打开弹窗」),别从零写。
|
|
||||||
|
|
||||||
## 第 10 轮:`no-path` 的真因 —— **Lazada 类目树是英文,中文关键词搜不到**
|
|
||||||
|
|
||||||
`lz_cat3.mjs` 跑完整条链路(选店 → 打开弹窗 → 输入 `发圈` → 点 `搜索`)的结果:
|
|
||||||
|
|
||||||
| 时点 | 弹窗内容 |
|
|
||||||
|---|---|
|
|
||||||
| 打开时 | 标题 `选择类目`,底部按钮 `["搜索","选择","关闭"]`,左侧 `.categories-item` 树(**46 项英文**:Audio / Automotive / Bags and Travel / Beauty / Bedding & Bath / Cameras & Drones …) |
|
|
||||||
| 输入 `发圈` + 点搜索后 | 文本只剩 `选择类目 搜索 初始类目 关闭`;`.search-result-item` **0 条**、`.categories-item` **0 条** → **搜索结果为空** |
|
|
||||||
|
|
||||||
**结论**:搜索确实执行了,只是**中文关键词在英文类目树里匹配不到任何东西**,
|
|
||||||
所以 `selectCategory` 一直返回 `no-path`(它不是结构不同,是**没结果**)。
|
|
||||||
|
|
||||||
### 下一轮的修法(二选一)
|
|
||||||
1. **给 Lazada 单独一张中文→英文类目词表**(最稳):`发圈→Hair Accessories`、
|
|
||||||
`口哨→Toy Whistles`、`笔袋→Pencil Cases`、`保温午餐包→Lunch Bags`、`贴纸→Stickers` 等;
|
|
||||||
2. **从英文标题里取词**(快但糙):`tk_input.json` 里每条都有英文 `title`,
|
|
||||||
取名词短语去搜(如 `Hair Ties`、`Whistle`、`Pencil Case`),搜不到再回退到词表。
|
|
||||||
|
|
||||||
搜到之后的结果项 class 仍需确认(这一轮没拿到非空样例)——**但至少知道方向是对的:
|
|
||||||
换英文词就能出结果**,`selectCategory` 的其余逻辑(点结果、关弹窗)可原样复用。
|
|
||||||
|
|
||||||
## ✅ 第 10 轮验证成功:**Lazada 只要给英文词,`selectCategory` 就能原样复用**
|
|
||||||
|
|
||||||
`lz_cat4.mjs`(只把关键词从 `发圈` 换成 `Hair Accessories`,其余一字未改):
|
|
||||||
|
|
||||||
```
|
|
||||||
输入: typed:Hair Accessories
|
|
||||||
按钮: ["搜索","选择","关闭"]
|
|
||||||
结果: search-result-item ×6, f-red ×6
|
|
||||||
sr[0] Beauty(美女)/Personal Care(个人护理)/Hair Care(洗护发用品)/…/Hair Accessories(发饰)
|
|
||||||
sr[1] Fashion Accessories(时尚饰品)/Accessories(配件)/Hair Accessories(发饰)
|
|
||||||
sr[2] Kids' Fashion(童装)/Baby & Kids' Accessories(…)/…
|
|
||||||
```
|
|
||||||
|
|
||||||
- 结果项 class 就是 **`.search-result-item`**,最后一级在 **`<span class="f-red">`** 里
|
|
||||||
→ 和 SHEIN/Temu **完全同构**,`sh_build.selectCategory` 的「去括注后按最后一级精确匹配」
|
|
||||||
逻辑可原样使用(`Hair Accessories(发饰)` 的 seg 就是 `Hair Accessories`,与关键词相等)
|
|
||||||
- 类目路径本身**中英并列**(英文名 + 中文括注),所以匹配时要按「括号前」比较
|
|
||||||
|
|
||||||
### 因此 Lazada 只差一件事:**中文 → 英文类目词**
|
|
||||||
`tk_input.json` 里 `cat_kw` 是中文(发圈/口哨/笔袋…),Lazada 树是英文。
|
|
||||||
下一轮:
|
|
||||||
1. 建一张词表(已确认 `发圈 → Hair Accessories`);其余按同法一条条试,很快;
|
|
||||||
2. 或直接从英文 `title` 抽名词短语(`Hair Ties` / `Whistle` / `Pencil Case` / `Lunch Bag`);
|
|
||||||
3. 拿到英文词后,**直接抄 `alichoice建品/alcbatch.mjs` 的骨架**(选店 → selectCategory → 填 store → 挂钩子保存),
|
|
||||||
差异只在于:Lazada 必填含**品牌**(要真实品牌或「无品牌」占位)和**税**。
|
|
||||||
|
|
||||||
## 第 11 轮:英文类目词实测表(`lz_kwtest.mjs`,一次会话连测 8 词)
|
|
||||||
|
|
||||||
| 我们的中文 `cat_kw` | 试的英文词 | `.search-result-item` 命中 | 首个结果路径(截断) |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 发圈/发饰 | `Hair Accessories` | **6** ✅ | Beauty/Personal Care/Hair Care/…/Hair Accessories |
|
|
||||||
| 笔袋 | `Pencil Case` | **1** ✅ | Stationery, Craft & Gift Cards/School & Office/… |
|
|
||||||
| 保温午餐包 | `Lunch Bag` | **3** ✅ | Bags and Travel/Functional Bags/Lunch Bags |
|
|
||||||
| 涂鸦贴纸 | `Stickers` | **5** ✅ | Automotive/…/Bumper Stickers, Decals & Ma… |
|
|
||||||
| 袜子 | `Socks` | **6** ✅ | Kids' Fashion/Baby Clothing/…/Socks & Tight… |
|
|
||||||
| 口哨 | `Whistle` | 0 ❌ | 换词:`Toy Whistle` / `Party Favors` / `Whistles` |
|
|
||||||
| 钥匙扣 | `Keychain` | 0 ❌ | 换词:`Key Ring` / `Key Chain` / `Keychains` |
|
|
||||||
| 纽扣 | `Buttons` | 0 ❌ | 换词:`Sewing Buttons` / `Button` |
|
|
||||||
|
|
||||||
**用法**:`cat_kw → 英文词` 先查这张表,未命中的按上面「换词」列逐个试(一个词 25 秒);
|
|
||||||
命中后 `selectCategory(c, 英文词, 英文词)` 可直接用(结果项同构,见上一节)。
|
|
||||||
|
|
||||||
**注意**:命中 ≠ 落对类目。例如 `Stickers` 首个结果是汽车保险杠贴纸,
|
|
||||||
而我们要的是「涂鸦贴纸」——所以**结果项要按最后一级精确匹配**(`selectCategory` 第三参已支持),
|
|
||||||
必要时人工核一遍 `lazada_route.json`。
|
|
||||||
|
|
||||||
## 第 12 轮(最后一轮):两处修正 + 必填清单
|
|
||||||
|
|
||||||
### ❌ 修正上一轮的错误结论:**不是「store 懒加载」,是压根没有 `lazada*` store**
|
|
||||||
用英文词 `Hair Accessories` 把类目选上之后(`分类 = 发饰(Hair Accessories)`),
|
|
||||||
按 `/lazada/i` 过滤 pinia 仍然是 **`{}`**。所以先前「选完类目才注册」的推断是**错的**——
|
|
||||||
Lazada 的建品数据**不放在以平台名命名的 store 里**。
|
|
||||||
下一轮第一步:**不要**再按名字过滤,直接把**全部** store 名单连同 `formState` 键 dump 出来找落点
|
|
||||||
(或者沿用「哨兵串」法:在标题里打 `PROBE-TITLE-XYZ`,遍历所有 store 找它)。
|
|
||||||
|
|
||||||
### ✅ 已选上类目,且拿到必填清单
|
|
||||||
`分类` 行已显示 `发饰(Hair Accessories)`(英文词搜索 + `selectCategory` 精确匹配有效)。
|
|
||||||
选中后表单项(`*` = 必填):
|
|
||||||
|
|
||||||
```
|
|
||||||
*店铺 Shehoney_MY *分类 发饰(Hair Accessories) *产品标题(≤255) 产品标题(马来语)
|
|
||||||
*品牌(要求真实品牌或「无品牌」) 质保类型
|
|
||||||
*KEYHair Types Fine/Long/Normal ← Lazada 的 KEY 属性族
|
|
||||||
*KEYMaterial Lace/Bamboo/cotton
|
|
||||||
*KEYAccessories Features Handmade/…
|
|
||||||
*Hair Accessories Type
|
|
||||||
*税 default 包装内容(≤1000)
|
|
||||||
*产品图片
|
|
||||||
```
|
|
||||||
|
|
||||||
**结论**:Lazada 的难点不在类目(已通),而在 **KEY\* 类目属性 + 品牌 + 税** 这三组必填。
|
|
||||||
`fillRequiredSelects` 能覆盖「下拉/勾选」形态;`KEY*` 若是**多选**,需要按类目给值。
|
|
||||||
|
|
||||||
## 交接(下一轮直接照做)
|
|
||||||
1. dump 全部 store(不要按 `lazada` 过滤)→ 找到标题/品牌/税/KEY 属性的落点
|
|
||||||
2. 抄 `alichoice建品/alcbatch.mjs` 骨架;类目词查本文件第 11 轮的英文词表
|
|
||||||
3. 保存前挂钩子;预计还会有一轮「层层递进」的必填
|
|
||||||
+3
-56
@@ -1,57 +1,4 @@
|
|||||||
# SHEIN 建品配方(已跑通第一条)
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/shein.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
## 完整流程
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
1. `https://www.dianxiaomi.com/web/sheinProduct/add`
|
|
||||||
2. **选店铺**(按品类分店,见 shein_route.json)
|
|
||||||
3. **选类目**:点「选择分类」→ 弹窗 `input[placeholder="搜索分类名称"]` → 搜索 → 点 `.search-result-item`
|
|
||||||
4. **标题**:`sheinAddProductStore.formState.productName`
|
|
||||||
5. **★ 加第二个规格**:点 `.theme-select-box` 里的 `尺寸(Size)`
|
|
||||||
—— 这是之前一直卡住的关键!类目要求**颜色+尺寸两个规格**,只选颜色不行
|
|
||||||
6. **选规格值**:两个 `.sku-info-box` 里各点一个 `.sku-option`(要点里面的 `label.d-checkbox`)
|
|
||||||
7. **填 SKU 行**:`sheinSkuDataStore.formState.skuDataList[]`
|
|
||||||
`skcSupplierCode / skcRetailPrice / supplyPrice / stockVariety / weight / length / width / height`
|
|
||||||
⚠️ **加第二个规格会重建 SKU 行,之前的填充会丢** —— 必须在规格选完之后再填
|
|
||||||
8. **尺码表**:点「编辑尺码表」→ 弹窗内 6 个 `placeholder="请输入"` 的 input → 点「确定」
|
|
||||||
9. **图片**:`sheinImgStore.formState.mainImgData.multipleSpuImgs` → **数组**
|
|
||||||
10. **保存** → `POST /api/sheinProduct/add.json`
|
|
||||||
|
|
||||||
## 关键教训
|
|
||||||
- 报错 `有必选规格未选择` 的真实含义是:**类目定义的规格没选全**,不是主规格没值
|
|
||||||
- 加规格会重置 SKU 行 → **顺序不能错**:先规格、后 SKU、再尺码表
|
|
||||||
- 所有报错都会被 `MYJ.message.error` 吞掉,必须挂钩子才看得到
|
|
||||||
|
|
||||||
## 待批量
|
|
||||||
- 40 条按品类分到 4 家店(美妆18 / 文具13 / 箱包8 / 内睡袜子1)
|
|
||||||
- 需要把上述流程写成 `shbatch.mjs`,逐条跑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 批量(已完成:40/40 进待发布,2026-09-20)
|
|
||||||
|
|
||||||
`node shbatch.mjs`(全部)· `node shbatch.mjs <pid>`(单条)· `node shbatch.mjs --dry <pid>`(只构建不保存)
|
|
||||||
|
|
||||||
- 数据源:`/home/admin9webs/tiktok建品/tk_input.json`(48 商品:title/imgs/cost/price_cny)
|
|
||||||
- 路由:`shein_route.json`(4 家店 × 每条的类目关键词),`sh_done.json` 记逐条结果,可断点续跑
|
|
||||||
- 公共库:`sh_lib.mjs`(newPage/closePage/wait/installHook/readHook)· `sh_build.mjs`(各步骤函数)
|
|
||||||
|
|
||||||
## 单条固定顺序(不能换)
|
|
||||||
`选店铺 → 选类目 → 标题 → 加规格(≤3) → 选规格值 → 填 SKU 行 → 尺码表 → 图片 → 填必填属性 → 保存`
|
|
||||||
|
|
||||||
## 这次新踩的坑
|
|
||||||
1. **选类目弹窗必须确认关掉**:`搜索` → 点 `.search-result-item`(它里面嵌 `<span class="f-red">`,
|
|
||||||
所以别用 `children.length===0` 过滤)→ 若弹窗还在就点「选择/确定」或右上角 ×、按 Esc。
|
|
||||||
弹窗没关时后面的点击全被挡,最终报的却是别的错(Temu 那边表现为「请上传预览图」)。
|
|
||||||
2. **SHEIN 一个产品最多 3 个规格**(颜色已占 1 个)→ `addSpecs` 里加满 3 个就停,
|
|
||||||
否则报「规格最多选择3个,请移除后再添加」。
|
|
||||||
3. **规格要去重**:按标签去重,否则会点出「款式/款式」两个同名规格。
|
|
||||||
4. **类目动态必填属性**用 `.attr-label.required` 标记(不是 `.ant-form-item-required`):
|
|
||||||
- 下拉型空着 → 选第一项
|
|
||||||
- 勾选组(如箱包的「特征 Features」,`.checkbox-group-with-search`)一个都没勾 → 勾第一个
|
|
||||||
- 不填只会得到一句「产品信息中有错误,请检查」
|
|
||||||
5. **店铺发品额度**:①Shein-武汉九个网-箱包 报「商家无可用的发品额度,暂时无法发布商品」,
|
|
||||||
把那条改挂 ②Shein-易世通达-箱包(`SHOP_OVERRIDE='②Shein-易世通达-箱包'`)后成功。
|
|
||||||
该类目在 ② 店只有「不保温午餐包」,与路由表写的「保温午餐包」不完全一致。
|
|
||||||
|
|
||||||
## 结果
|
|
||||||
40 条全部「已保存到待发布」(39 条在路由指定店铺 + 1 条改挂 ② 店)。
|
|
||||||
店小秘 SHEIN 待发布列表核对:共 44 条(含此前手工首条等)。
|
|
||||||
@@ -1,67 +1,4 @@
|
|||||||
# Shopee(全球)「选品建listing」现状:**卡在店铺授权,脚本已就绪**
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/shopee.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
> 2026-09-30 实测结论:`/web/shopeeGlobal/add` 的「商家账号」下拉是 **「暂无数据」** ——
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
> 店小秘账号下**没有 Shopee 授权店铺**。没有店铺 → 类目树打不开(类目随站点)、保存必失败。
|
|
||||||
> 因此**本轮 Shopee 0 条**,不是脚本问题,是账号/授权问题。
|
|
||||||
> 脚本 `platform-shopee/spbatch.mjs` 已写好并在预检处**硬拦**(无店铺直接跳过、不产生垃圾数据)。
|
|
||||||
|
|
||||||
## 1. 已探明的页面事实(可直接复用)
|
|
||||||
|
|
||||||
| 项 | 值 |
|
|
||||||
|---|---|
|
|
||||||
| 路由 | `/web/shopeeGlobal/add`(标题「店小秘--添加Shopee全球产品」) |
|
|
||||||
| 保存接口 | `POST /api/shopeeGlobalProduct/add.json` |
|
|
||||||
| 站点限价 | `POST /api/shopeeCategory/getLimitBySite.json`、`/api/shopeeCategory/getPriceLimit.json` |
|
|
||||||
| 字段 | 商家账号 / **上架后自动发布到店铺(checkbox)** / 产品分类 / 产品属性 / 店小秘分类 / 来源URL / **类型(radio)** / ParentSKU / 产品标题 / 产品描述 / 产品图片 / 产品视频 / 货源价格→价格 / 库存 / 最低购买数量 / 最高购买数量 / 重量 / 尺寸规格(长宽高) / 产品保存状况(全新) / 是否预购(否) |
|
|
||||||
| 类型 radio | `input[type=radio][value=single]` = **单品**,`value=mutiple` = 多变种(**默认 mutiple**) |
|
|
||||||
| 图片模块 | `.shopee-global-product-image-module`(头是 `<header class="product-image-header">`,不是 `.header`) |
|
|
||||||
| 描述 | `textarea.product-desc-textarea`(placeholder「请输入产品描述」,上限 5000) |
|
|
||||||
| 列表字段 | `itemName` **不是** title |
|
|
||||||
|
|
||||||
## 2. 三条必须在代码里守住的规则
|
|
||||||
|
|
||||||
1. **「上架后自动发布到店铺」是 checkbox,必须不勾**:
|
|
||||||
`section.send-site label.ant-checkbox-wrapper input[type=checkbox]`。
|
|
||||||
勾上 = 保存后直接发布,违反第一原则(目标只到待发布)。→ `ensureCheckboxOff(c, /自动发布|自动上架/)`。
|
|
||||||
2. **类型切「单品」**(`setRadioByText(c,'单品','single')`)绕开变种表:多变种模式下价格/库存只能按变种填。
|
|
||||||
切完等 2.5–3s 让表格重渲染,价格/库存/最低最高购买数量/重量/尺寸规格 才出现在单品表单里。
|
|
||||||
3. **尺寸规格是整数 cm**:输入 `27.5` 会被吃成 `27`(不是四舍五入)。脚本里 `Math.round` 后再填。
|
|
||||||
|
|
||||||
## 3. 解除阻塞需要做的事
|
|
||||||
|
|
||||||
1. 让主账号在店小秘「平台授权」里完成 Shopee 店铺授权(子账号 `ejetZhangjiafen` 打开
|
|
||||||
`/web/system/authorization/platform?go=shopee` 只会看到「没有该功能访问权限」→ 必须主账号操作)。
|
|
||||||
2. 授权后跑预检确认下拉有店:
|
|
||||||
```bash
|
|
||||||
node listing-spec/platform-shared/preflight-shops.mjs --routes /web/shopeeGlobal/add
|
|
||||||
```
|
|
||||||
3. 有店之后再跑:
|
|
||||||
```bash
|
|
||||||
python3 listing-spec/platform-shared/build-add-input.py <package>.json --platform Shopee-全球 --out /tmp/sp_input.json --min-side 400
|
|
||||||
node listing-spec/platform-smt/../platform-shopee/spbatch.mjs --input /tmp/sp_input.json --only <oid> --dry
|
|
||||||
node listing-spec/platform-shopee/spbatch.mjs --input /tmp/sp_input.json --only <oid>
|
|
||||||
```
|
|
||||||
(回读:`POST /api/shopeeGlobalProduct/pageList.json`,待发布口径同速卖通 `dxmState=offline&dxmOfflineState=waitPublish`;
|
|
||||||
注意 Shopee 图片门槛 **单边 ≥400、主图建议 ≥800**,以及「附图最多9张」+ 主图 = 10。)
|
|
||||||
|
|
||||||
## 4. 同类风险平台
|
|
||||||
|
|
||||||
同一轮预检里 **Mercado / Shopify / Amazon 路由也拿不到店铺下拉**(`no-dropdown` / 无该字段),
|
|
||||||
**TikTok 有 10 家店、速卖通有 10 家店**。→ **每轮批量前先跑 `preflight-shops.mjs`**,
|
|
||||||
不要等到填完一屏才在保存时发现没有店铺。预检结果落 `/tmp/shop-preflight.json`。
|
|
||||||
|
|
||||||
## 附:账号级证据(2026-09-30 实测,一次接口)
|
|
||||||
|
|
||||||
`GET /api/userIn.json` → `data.shopMap`(key=shopId,value 含 `platform`):
|
|
||||||
|
|
||||||
```
|
|
||||||
256 家店 → smt:61 tiktok:98 tiktokGlobal:24 aliChoice:65 ozon:5 walmart:3
|
|
||||||
```
|
|
||||||
|
|
||||||
**没有任何 `shopee`/`shopeeGlobal` 店铺** —— 与 Shopee 建listing 页「商家账号」下拉「暂无数据」完全一致。
|
|
||||||
即:**当前登录(子账号 ejetZhangjiafen)在账号层面就没有 Shopee 店铺**,
|
|
||||||
而 playbook 历史记录里 Shopee `Ljbgkj` 47 条进待发布是在**当时可见该店铺的登录态**下完成的
|
|
||||||
→ 结论仍是「授权异常/范围变化」,需**主账号**处理。
|
|
||||||
|
|
||||||
用 `platform-shared/dxm-add-lib.mjs` 的 `readAccountShops(c)` 一行拿到全平台店铺数。
|
|
||||||
★ 注意页面下拉只是子集(速卖通页 10 家 vs 账号 61 家)。
|
|
||||||
+3
-830
@@ -1,831 +1,4 @@
|
|||||||
# 跨境电商产品数据字段总清单
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/product-fields.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
> 生成:2026-09-22 · 作者:KVM-2 DSH agent · 用途:作为**唯一字段口径表**,供建品脚本、数据同步、字段补齐、跨平台映射共用。
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
>
|
|
||||||
> **证据优先级**(本文件所有条目都按此分级,未标注的即"未验证"):
|
|
||||||
> - ★★★ 官方/接口事实:店小秘官方导入模板、平台列表接口返回的真实字段、库内真实文档
|
|
||||||
> - ★★ 代码事实:线上脚本读写过的 store 路径 / selector / 单位换算
|
|
||||||
> - ★ 文档事实:需求文档、技能笔记、注释
|
|
||||||
> - ❓ 待确认:仅为推测或来源冲突
|
|
||||||
>
|
|
||||||
> 主要来源:`listing-spec/建品质量需求.md`(需求单一事实源)、`~/ds·/skills/dianxiaomi-multiplatform-listing`、`/tmp/dxm-Template.xlsx`(官方模板)、`products.listings`(库内 5.8 万条真实文档)、`~/1688-xborder/*.csv`(源资料)、`~/<平台>建品/*.mjs`(线上脚本)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠ 0. 先处理:妙手同步已"静默失败",但监控报 OK
|
|
||||||
|
|
||||||
`logs/miaoshou-20260922-031701.log`(最新一轮 03:17):
|
|
||||||
|
|
||||||
```
|
|
||||||
Temu全托管 waitPublish ✗ pddkj/waitPublish: 您的授权已经过期,请重新登录
|
|
||||||
…(22 个平台全部同一错误)
|
|
||||||
== 完成 49.5s 错误 22
|
|
||||||
```
|
|
||||||
|
|
||||||
而 `logs/last-status.txt` 写的是 `miaoshou=OK(库内 waitPublish 文档=2870/在线 55462,…)`。
|
|
||||||
|
|
||||||
**机制**:脚本对每个平台 catch 异常后继续跑,`sync_all.sh` **只看退出码** → cookie 过期导致 **0 新增**,但库内旧数据非 0,于是报 OK。
|
|
||||||
**后果**:巡检判据「文档数=0」抓不到这一类故障;妙手的 2870 / 55462 是**旧快照**,不是当前值。
|
|
||||||
**修法**:妙手登录态来自 `/tmp/dxm7` profile(`msp_session.py`,CDP 9223)→ **需重新登录**;并建议给 `sync_all.sh` 增加"本轮新增=0 且错误数>0 → 判 FAIL"的判据(现在只看退出码)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 三层数据模型(先看清字段在哪一层)
|
|
||||||
|
|
||||||
| 层 | 载体 | 字段量级 | 本文件章节 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **L1 源资料**(选品/采集) | `~/1688-xborder/*.csv`、1688 详情页、内网货盘 | 12–18 列 + 1688 原始 HTML | §1 |
|
|
||||||
| **L2 中台**(店小秘 / 妙手) | Pinia store、官方导入模板、库内 `raw` | 店小秘官方模板 61 列;接口 raw 10–191 字段 | §2 §3 §4 |
|
|
||||||
| **L3 平台**(发布后回读) | 平台接口 `/api/<模块>Product/pageList.json` | 同上(店小秘把平台字段透传进 raw) | §4 §11 |
|
|
||||||
|
|
||||||
**关键认知**:店小秘的 `raw` 字段 ≈ 平台字段超集(速卖通 191 个),我们**归一化了 17 个**用于统计,其余全量躺在 `raw` 里。所以要补任何字段,**先去 `raw` 里找,不要重新爬**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. L1 源产品资料字段(选品与采集层)
|
|
||||||
|
|
||||||
### 1.1 三套 CSV 的真实表头 ★★★
|
|
||||||
|
|
||||||
| 用途 | 文件 | 列数 | 字段 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 选品候选(1536 行) | `candidates-20260914.csv` | 17 | `sku, pid, title_zh, title_en, cost_cny, weight_kg, est_price_usd, est_margin_pct, score, offer_1688, offer_id, supplier, kb_status, pool_status, stock, image, category` |
|
|
||||||
| 富化核验(60 行) | `enriched-20260914.csv` | 12 | `offer_id, sku, cost_cny_in, title_1688, price_min_cny, price_range_raw, supplier_1688, moq, weight_raw, image_url, valid, note` |
|
|
||||||
| 终选(含来源与克重) | `final-candidates-20260914.csv` | 14 | `offer_id, sku, title, price_1688_cny, pool_cost_cny, weight_g, weight_source, supplier_1688, est_price_usd, est_margin_pct, category, offer_url, image, pool_status` |
|
|
||||||
| 上架包(含俄语文案与定价链路) | `t1-上架包.csv` | 18 | `货号, 品名, offer_id, 供应商, 成本CNY, 克重g, 头程CNY, 公式价KZT, 首发价KZT, 全链路成本KZT, 保本价KZT, 每单利润KZT, 首发毛利pct, 公式价毛利pct, WB参照, 俄语标题, 俄语卖点, 俄语关键词` |
|
|
||||||
|
|
||||||
### 1.2 ★ 真·1688 原始字段(`window.context`,最硬证据)
|
|
||||||
|
|
||||||
> **更正**:上一版把"源资料"等同于 `~/1688-xborder/*.csv`,那是**下游产出**,不是原始采集。
|
|
||||||
> 真·原始数据在 `~/1688-xborder/new20-raw/*.html`(17 个 500–676KB 详情页,BDPM 抓取),
|
|
||||||
> 字段内联在 `window.context.result.data.<模块>.fields` 里。涉及模块:
|
|
||||||
> `productPackInfo` · `priceModel` · `skuInfoMap` · `offerBaseInfo` · `od_product_description` · `od_size_chart` · `consign` · `shippingServices` · `frontSellerMemberModel` · `offerSign`
|
|
||||||
|
|
||||||
**基础信息**
|
|
||||||
| 键名 | 语义 | 单位/格式 | 可得性 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `offerId` | 1688 商品 ID | 数字串 | ✅ 17/17 |
|
|
||||||
| `<title>` / `subject` | 商品标题 | 中文 | ✅ 17/17 |
|
|
||||||
| `featureAttributes["货号"].value` | 货号/商家编码 | 文本 | ✅ 14/17(常与 `skuInfoMap.specAttrs` 同值) |
|
|
||||||
| `priceModel.currentPrices[].price` | 阶梯价 | **¥** | ✅ 17/17 |
|
|
||||||
| `priceModel.currentPrices[].beginAmount` | **起订量 MOQ** | 整数 | ✅ 17/17(首档即 MOQ) |
|
|
||||||
| `priceModel.currentPricesWithOnePiece[]` | 一件代发价 | ¥ | ✅ |
|
|
||||||
| `originalPricesWithoutPromotion[]` / `discountPrice` | 原价/折后价 | ¥ | ✅ |
|
|
||||||
| `skuPriceScale` | 价格区间串 | `"2.80-3.50"` | ✅(→ CSV `price_range_raw`) |
|
|
||||||
| `canBookCount` | 可订库存 | 整数 | ✅(值常夸张,如 `99985537`) |
|
|
||||||
| `saledCount` | 销量 | 整数 | ✅(可能虚高) |
|
|
||||||
| `postCategoryId` / `topCategoryId` | 1688 类目 | 整数 | ✅ 但**不可直接映射平台类目** |
|
|
||||||
|
|
||||||
**图片(全外链 `cbu01.alicdn.com`)**
|
|
||||||
| 键名 | 语义 | 说明 |
|
|
||||||
|---|---|---|
|
|
||||||
| `imageList[].fullPathImageURI` | **主图原图** | 直接可用 |
|
|
||||||
| `imageList[].imageURI` | 主图相对路径 | 需拼域名 |
|
|
||||||
| `imageList[].{searchImageURI,size220x220ImageURI,size310x310ImageURI,summImageURI}` | 各尺寸缩略变体 | 还原规则见 `fetch_1688_imgs.py:19-27` |
|
|
||||||
| `od_product_description.fields.detailUrl` | **详情图容器** | ⚠ 是外链 JS bundle(`itemcdn.tmall.com/1688offer/icoss<hash>`),**必须二次抓取** |
|
|
||||||
| `valueImageUrl`(规格/变种图) | 变种图 | ❌ **17 个样本非空 = 0** → 变种图源头拿不到 |
|
|
||||||
| `cbu01.alicdn.com/cms/upload/*` | 页面小图标 | ⚠ 必须滤掉(不是商品图) |
|
|
||||||
|
|
||||||
**物流(重量/尺寸)—— 单位是雷区,见 §1.5**
|
|
||||||
| 键名 | 语义 | 单位 | 可得性 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `productPackInfo.fields.unitWeight` | 商品件重 | **千克 kg** | ⚠ 11/17 有值,**6/17 为 `0`(未填≠零重)** |
|
|
||||||
| `productPackInfo.fields.pieceWeightScale.pieceWeightScaleInfo[].weight` | 单件重量 | **克 g** | ✅ 15/17 |
|
|
||||||
| `…pieceWeightScaleInfo[].length/width/height` | 长/宽/高 | **厘米 cm** | ⚠ 常为 `0.0`(9/17 全零) |
|
|
||||||
| `…pieceWeightScaleInfo[].volume` | 体积 | **cm³** | ✅ 有值时正确 |
|
|
||||||
| `…pieceWeightScaleInfo[].{sku1,sku2,skuId}` | 变体值/SKU ID | 文本/整数 | ✅ |
|
|
||||||
| `…columnList[].label` | 表头定义 | `长(cm)`/`宽(cm)`/`高(cm)`/`体积(cm³)`/`重量(g)` | ✅ **单位口径的权威来源** |
|
|
||||||
| `freightInfo.unitWeight` / `shippingServices.fields.unitWeight` | 运费重量 | kg | ✅ 与 `productPackInfo` 同值 |
|
|
||||||
|
|
||||||
**供应链**
|
|
||||||
| 键名 | 语义 | 可得性 |
|
|
||||||
|---|---|---|
|
|
||||||
| `companyName` | 供应商公司全称 | ✅ 17/17 |
|
|
||||||
| `sellerLoginId` | 供应商店铺名 | ✅(→ CSV `supplier_1688`) |
|
|
||||||
| `sellerUserId` / `sellerMemberId` | 供应商 ID | ✅ |
|
|
||||||
| `winportUrl` | 供应商旺铺 | ✅ |
|
|
||||||
| `featureAttributes["产地"].value` | **产地** | ✅ 9/17,**只到省市**(义乌/金华/浙江/广州/莆田/河北) |
|
|
||||||
| `featureAttributes["是否进口"]` | 是否进口 | ✅ 4/17(实测全"否") |
|
|
||||||
| `<span class="location">` | 发货地 | ⚠ 仅 HTML 文本,不在 JSON |
|
|
||||||
| `deliveryLimitText` | 发货承诺 | ✅(如"承诺48小时发货") |
|
|
||||||
| `featureAttributes["主要下游平台"/"主要销售地区"]` | 渠道/地区 | ✅ 6–7/17 |
|
|
||||||
|
|
||||||
**属性与变种**
|
|
||||||
- 统一容器 `featureAttributes[] = {fid,name,value,values,vids,outputType,isSpecial,…}`
|
|
||||||
- 实测属性名(按出现文件数):`货号`(14) `品牌`(13) `是否跨境出口专供货源`(12) `材质`(9) `产地`(9) `有可授权的自有品牌`(8) `颜色`(7) `上市年份/季节`(7) `处理工艺`(6) `图案`/`流行元素`/`适用送礼场合`(5) `风格`/`包装`/`生产编号`(3) `尺码`/`型号`/`规格`(1–2),另有 80+ 类目长尾属性
|
|
||||||
- 变种:`skuInfoMap` / `skuInfoMapOriginal` ✅ 17/17,值 `{specId,saleCount,canBookCount,specAttrs,priceAmount,skuId,isPromotionSku}`;`isSkuOffer:true` 16/17
|
|
||||||
|
|
||||||
**其他**
|
|
||||||
| 项 | 状态 |
|
|
||||||
|---|---|
|
|
||||||
| 尺码表 `od_size_chart` | ✅ 模块存在,尺码图需从中提取 |
|
|
||||||
| 平台评分/评价数 | ❌ **1688 详情页不存在**(评分只在需求侧 WB/Ozon 有) |
|
|
||||||
| EAN / UPC / 条码 | ❌ **全页 689 个 JSON 键无任何 barcode/upc/ean** |
|
|
||||||
|
|
||||||
### 1.2b 建品输入包(下游包,**不是源资料**)
|
|
||||||
|
|
||||||
`~/<平台>建品/*_input.json`(46–48 项,顶层键 = offer_id),每项只有 **11 个字段**:
|
|
||||||
- `walmart建品/wm_input.json`:`title, bullets, keywords, imgs, weight, ean, pc, cat_index, cat, price_usd, weight_lbs`
|
|
||||||
- `tiktok建品/tk_input.json`:`title, bullets, keywords, imgs, cost, weight, price_cny, price_myr, guess, cat_kw, cat_fb`
|
|
||||||
|
|
||||||
⚠ **`weight` 是硬编码三档 `100/130/10`,不是实测值**(`tk_input.json` 的 `guess` 字段字面写着 `'cost~,w~'` 自认是猜的)。
|
|
||||||
⚠ 全量口径:`最终开品清单.csv` 的 `weight_src` 分布 = `占位100g` **213** 行 vs `L2实核` **20** 行 → **只有 8.6% 的重量是真核过的**。
|
|
||||||
|
|
||||||
### 1.3 源资料字段语义归并(建品脚本实际消费的)
|
|
||||||
|
|
||||||
| 归一字段 | 脚本里的名字 | 语义 | 单位 | 说明 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 货源 ID | `offer_id` / `oid` | 1688 商品 ID | — | 唯一键;来源 URL = `https://detail.1688.com/offer/<oid>.html` ★★ |
|
|
||||||
| 货号 | `sku` | 我方货号 | — | 平台 SKU 由它派生(如 `WM-<oid>`) |
|
|
||||||
| 标题 | `IN.title` / `it.title` / `title_1688` / `title_en` | 商品标题 | — | 1688 原文(中文/英文两版) |
|
|
||||||
| 卖点 | `IN.bullets` / `it.bullets` | 要点列表 | — | 描述与 keyFeatures 的原料 |
|
|
||||||
| 关键词 | `IN.keywords` / `it.keywords` | 搜索词 | — | 逗号分隔;TikTok 取前 10 个 ★★ |
|
|
||||||
| 类目猜词 | `IN.cat_kw` / `it.cat_kw` | 类目匹配关键词 | — | ⚠ 类目靠猜是已知缺陷(需求 §3.1 #1) |
|
|
||||||
| 元卖 | `IN.sp_kw` / `IN.sp_fb` | Shopee 关键词/回退 | — | Shopee 专用 |
|
|
||||||
| 成本 | `IN.cost` / `it.cost` / `cost_cny` / `pool_cost_cny` | 进货成本 | CNY | 定价模型输入 |
|
|
||||||
| 1688 价 | `price_min_cny` / `price_1688_cny` / `price_range_raw` | 供货价 | CNY | `price_range_raw` 是原始区间串 |
|
|
||||||
| 重量 | `IN.weight` / `it.weight` / `weight_g` / `weight_kg` / `weight_raw` | 商品重量 | **口径不一,见 §1.5** | 🔴 最大雷区:源头 kg/g 两个字段并存;下游 `weight` 多为硬编码占位 |
|
|
||||||
| 图片 | `IN.imgs` / `it.imgs` / `image` / `image_url` | 主图+详情图混装 | URL 数组 | ⚠ 混装,必须过 `filterProductImages()` ★★;详情图需二次抓取 |
|
|
||||||
| 供应商 | `supplier_1688` / `supplier` | 供应商名 | — | 产地字段的来源(只到省市) |
|
|
||||||
| 起订量 | `moq` / `priceModel.currentPrices[].beginAmount` | 最小起订 | 件 | ⚠ **实际可得**(旧文档称"取不到"是错的);但 L2 CSV 的 `moq` 列**全空**是脚本没提 |
|
|
||||||
| 库存 | `stock` / `canBookCount` | 1688 库存 | 件 | 值常夸张,仅方向性 |
|
|
||||||
| 定价结果 | `est_price_usd, est_margin_pct, score` + `公式价KZT/保本价KZT/…` | 定价与经济性 | USD/KZT | 选品阶段算出 |
|
|
||||||
| 多语言文案 | `俄语标题/卖点/关键词` | 本地化文案 | — | 目前仅俄语(Ozon/WB 线) |
|
|
||||||
|
|
||||||
### 1.4 源资料的**结构性缺口**(L1 拿不到、必须人工或另找)
|
|
||||||
|
|
||||||
| 缺口 | 影响的平台 | 现实做法 | 证据 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **品牌** | 全平台 | 一律 `无品牌`;**源头 `品牌` 属性 13/17 有值但不可信**(常为 `自主`/`其他`/`否`,或供应商乱填的品牌名,发布前必须清) | ★★★ |
|
|
||||||
| **EAN/UPC/ISBN** | Temu/Walmart/速卖通 SKU | 全页 689 个 JSON 键**无任何 barcode/upc/ean** → 走自家 EAN-13 台账(`69803093` 段),用号必须落 S03 台账 | ★★★ |
|
|
||||||
| **原产国 / 产地** | Walmart(Country of Origin)、速卖通(产地必填) | 源头 `产地` **只到省市**(义乌/浙江/金华/广州/莆田/河北),`是否进口`=否 → 默认 `China` | ★★★ |
|
|
||||||
| **包装尺寸** | Walmart(inch)、Temu、Shopee | ⚠ **9/17 样本长宽高全为 0.0**;建品 CSV 里的 `15/10/3` 是**人工默认值**,与 1688 源无对应关系。真值须实测或问供应商 | ★★★ |
|
|
||||||
| **发运重量** | 全平台 | `unitWeight` 是**单件属性值**,未必含外包装(实测有 `1g` 的离谱值)→ 代码统一加 `--weight-buffer 1.3` | ★★★ |
|
|
||||||
| **净重 vs 毛重** | Temu(净重必须<重量) | 源头只有件重,净重需估算或人工 | ★★ |
|
|
||||||
| **规格/变种图** | TikTok/速卖通/Walmart 变参图 | ❌ `valueImageUrl` **17 个样本非空 = 0** → 无自动来源,需从主图/详情图裁切 | ★★★ |
|
|
||||||
| **详情图** | Shopee/Temu/速卖通 | **不在主 HTML 里**:`od_product_description.fields.detailUrl` 是外链 JS bundle,必须二次抓取 | ★★★ |
|
|
||||||
| **MOQ** | 铺货一般不用 | ✅ **其实拿得到**(`priceModel.currentPrices[0].beginAmount`);旧文档"取不到"的说法源于早期只读属性表文本 | ★★★ |
|
|
||||||
| **HS / ТН ВЭД 编码** | Ozon/Walmart | 源头无此字段 → 走自有 HS 字典(中国 10 位 12,030 / ЕАЭС 9 位 12,937 / 国际 6 位 5,613) | ★★ |
|
|
||||||
| **平台类目映射** | 全平台 | 1688 `postCategoryId/topCategoryId` **不可直接映射**平台类目树 | ★★★ |
|
|
||||||
| **高关注化学品 / 加州65** | 速卖通、Walmart | 填 `none`(速卖通写死 id `23399591357`) | ★★ |
|
|
||||||
| **产品说明书文件** | 速卖通(部分类目强制) | 无来源 → 需生成或跳过该类目 | ★ |
|
|
||||||
| **视频** | 速卖通、TikTok | 无来源 → 需图生视频或人工 | ★★ |
|
|
||||||
| **平台评分/评价数** | 选品参考 | ❌ 1688 详情页**无评分字段**(评分只在需求侧 WB `feedbacks/rating`、Ozon `reviews/rating`) | ★★★ |
|
|
||||||
| **供应商可用性** | 全平台 | 1688 链接有效率:`inactive` 品 **25%**、`active` 品 **62%** → 必须实核,失效标 `invalid`,不许编数据 | ★★ |
|
|
||||||
|
|
||||||
### 1.5 🔴 重量单位口径(本项目最贵的一类事故)
|
|
||||||
|
|
||||||
**同一页有两条重量、两种单位,读错差 1000 倍:**
|
|
||||||
|
|
||||||
| 字段 | 单位 | 说明 |
|
|
||||||
|---|---|---|
|
|
||||||
| `productPackInfo.fields.unitWeight` | **千克 kg** | 如 `0.010` = 10g |
|
|
||||||
| `…pieceWeightScaleInfo[].weight` | **克 g** | 同模块 `columnList.label` 直接写 `重量(g)` |
|
|
||||||
| `…length/width/height` | **厘米 cm** | label `长(cm)`/`宽(cm)`/`高(cm)` |
|
|
||||||
| `…volume` | **cm³** | label `体积(cm³)` |
|
|
||||||
| L2 CSV `weight_raw` | 文本 | 形如 `重量(g)=75`(来自属性表文本,不是 JSON) |
|
|
||||||
|
|
||||||
**17 样本交叉验证结果**:**8 个一致 / 1 个冲突 / 6 个 `unitWeight=0`(未填)/ 2 个无包装表**。
|
|
||||||
🔴 冲突样本 `800850312791`:`unitWeight=0.001`(1g) vs 包装表 `50g`,**差 50×**。
|
|
||||||
|
|
||||||
**三个必须记住的坑**
|
|
||||||
1. **`unitWeight` 是 kg 不是 g**。技能 `1688-cross-border-sourcing/SKILL.md:55` 只写"重量单位是克"(那句针对的是属性表文本),照着读 `unitWeight` 会把 10g 当 0.010g。
|
|
||||||
2. **`unitWeight = 0` 表示"未填",不是"零重"**(6/17)。`ozon_unit.py:86` 直接 `weight_g/1000.0` 而不判 0 → 会算出零运费、虚高毛利。
|
|
||||||
3. **历史事故**(该技能原文):① "数值>50 才除 1000" → 10g 当 10kg,**毛利 -2381%**;② 改成"数值<5 就当 kg" → `重量(g)=3` 又变 3000g。**正确做法是单位优先,其次按列语义兜底**(实现见 `ru_market.py:215-236`)。
|
|
||||||
|
|
||||||
### 1.6 口径冲突清单(不同脚本/文档互相打架,接线前必须裁决)
|
|
||||||
|
|
||||||
| 冲突 | A 说法 | B 说法 | 裁决 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 重量启发式 | `select_candidates.py:95`:">50 视为 g"(面向 Mongo `package_weight`,本身是 kg) | `ru_market.py:215-236`:单位优先,1688 详情无单位时**默认按 g** | 面向不同数据源;**同一合并表里两列并存**(`weight_raw`=g / `weight_kg_pool`=kg),套错列就出事故 |
|
|
||||||
| 抓取正路 | `1688-cross-border-sourcing`(英):详情页**唯一可靠是 Desktop CDP**,Firecrawl/Jina/直连都不行 | `1688-crossborder-sourcing`(中):**BDPM curl 才是正路**(实测 4/4 成功、124–133KB),比派浏览器 agent 可靠 | 实际 `new20-raw/*.html` 是 500–676KB 正常页,**与 BDPM 路线吻合** |
|
|
||||||
| MOQ 可得性 | 两个技能均称"取不到/不可靠" | raw 页 `beginAmount` 出现 20 次 | **B 正确**,规格源于早期实现缺失 |
|
|
||||||
| 库名 | 英技能写 `premiumproducts.products` | 中技能与 `premiumproducts-db` 写 `productpool1.products`(221,874) / `premiumproducts.products` | 未裁决 ⚠ |
|
|
||||||
| 失效引用 | 英技能引用 `~/.hermes/scripts/1688_scrape_cdp.py` 与 `references/*` | — | **本机不存在**(该技能只剩单文件)⚠ |
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 店小秘官方导入模板字段(61 列,权威口径)★★★
|
|
||||||
|
|
||||||
来源:`/tmp/dxm-Template.xlsx`(官方 Template,`build_dxm_import.py` 直接读取表头,不手抄)。
|
|
||||||
表头形如 `*ParentSKU\n(必填)`,代码用 `core()` 归一化后匹配——**坑:直接用短名匹配会全部取空,文件表面看不出必填列全空**(脚本注释原话)。
|
|
||||||
|
|
||||||
| 分组 | 列号 | 字段(官方表头) | 必填 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **标识** | 1 | `*ParentSKU` | ✅ |
|
|
||||||
| | 2 | `*SKU` | ✅ |
|
|
||||||
| | 7 | `UPC` | |
|
|
||||||
| | 8 | `landingPageUrl` | |
|
|
||||||
| **文案** | 3 | `*产品标题` | ✅ |
|
|
||||||
| | 4 | `*产品描述` | ✅ |
|
|
||||||
| | 5 | `产品标签` | |
|
|
||||||
| | 44 | `英文申报名称` | |
|
|
||||||
| | 45 | `申报的本地名称` | |
|
|
||||||
| **品牌/属性** | 6 | `品牌` | |
|
|
||||||
| | 10 | `颜色` | |
|
|
||||||
| | 11 | `尺寸值` | |
|
|
||||||
| | 9 | `msrp` | |
|
|
||||||
| **来源** | 12 | `来源url` | |
|
|
||||||
| **价格** | 13 | `*价格(USD)` | 美元店铺必填 |
|
|
||||||
| | 14 | `*价格(CNY)` | 人民币店铺必填 |
|
|
||||||
| | 15 | `*运费(USD)` | 美元店铺必填 |
|
|
||||||
| | 16 | `*运费(CNY)` | 人民币店铺必填 |
|
|
||||||
| | 49 | `申报价值(USD)` | |
|
|
||||||
| **库存** | 17 | `*库存量` | ✅ |
|
|
||||||
| **图片** | 18 | `主图(URL)地址` | |
|
|
||||||
| | 19 | `变种图(URL)地址` | |
|
|
||||||
| | 20–38 | `附图1…附图19(URL)地址` | |
|
|
||||||
| | 39 | `附图20-100(URL)地址` | |
|
|
||||||
| **物流/海关** | 43 | `重量(g)` | |
|
|
||||||
| | 46/47/48 | `包裹长度/宽度/高度(cm)` | |
|
|
||||||
| | 42 | `海关HS编码` | |
|
|
||||||
| | 50 | `件数` | |
|
|
||||||
| | 51 | `发货地区代码` | |
|
|
||||||
| **合规** | 52–55 | `是否含粉末/液体/电池/金属` | |
|
|
||||||
| | 56 | `加州65号提案警告类型` | |
|
|
||||||
| | 57–61 | `加州65号提案化学品名称1..5` | |
|
|
||||||
| **其他** | 40 | `备注` | |
|
|
||||||
| | 41 | `产品状态` | |
|
|
||||||
|
|
||||||
> 注:这份模板偏 **Walmart/通用** 口径(有 UPC、msrp、landingPageUrl、加州65)。它是"产品级"字段;**平台级的必填项不在这张表里**,必须走各平台添加页(见 §4)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 跨平台通用产品主档(语义归一,建议内部统一命名)
|
|
||||||
|
|
||||||
这是"我们应该在内部统一维护的最小完备集"。`必要性` 列的 ⭐ 表示"几乎所有平台都要"。
|
|
||||||
|
|
||||||
### 3.1 标识与来源
|
|
||||||
| 字段 | 单位/格式 | 来源 | 必要 | 备注 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `oid` | 字符串 | 1688 | ⭐ | 主键;来源 URL 由它拼 |
|
|
||||||
| `sku` / `parentSku` | 字符串 | 我方 | ⭐ | 店小秘模板列 1、2 |
|
|
||||||
| `sourceUrl` | URL | 1688 | ⭐ | 速卖通 #9、Walmart #1 都要求 ★★ |
|
|
||||||
| `ean` / `upc` / `isbn` | 13 位 | EAN 台账 | 平台相关 | Walmart 要 **ISBN**(不是 UPC);Temu 要 EAN ★★ |
|
|
||||||
| `hsCode` | 字符串 | 人工/类目库 | | 模板列 42 |
|
|
||||||
| `platformProductId` | 字符串 | 平台回读 | | 发布后才有 |
|
|
||||||
|
|
||||||
### 3.2 文案
|
|
||||||
| 字段 | 上限 | 规则 | 备注 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `title` | Walmart **100**、Temu **120** | 过 `filterTitle()`;窄义词**同义替换**(策略 B)+ 长度兜底 | ★★ |
|
|
||||||
| `bullets` | — | 逐条过 `filterTitle` | Walmart `keyFeatures`、TikTok 描述 |
|
|
||||||
| `keywords` | TikTok 取前 **10** | — | |
|
|
||||||
| `description`(HTML) | — | **必须含 `<img>` 产品图**(速卖通 #7/TikTok #3/Temu #3) | ★★ |
|
|
||||||
| `mobileDescription`(HTML) | — | 无线端单独字段(Temu `describeInfoData.mobileDetail`) | ★★ |
|
|
||||||
| `searchTerms` | — | TikTok 独立字段 | ★★ |
|
|
||||||
|
|
||||||
### 3.3 交易
|
|
||||||
| 字段 | 单位 | 规则 | 备注 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `price` | 平台币种 | 见 §5 币种 | ★ |
|
|
||||||
| `原价/originalPrice` | 同上 | Ozon 要求 **售价 < 原价**,改价必须同时抬原价 | ★★ |
|
|
||||||
| `declaredValue` | 同上 | **与售价一致**(速卖通 #6) | ★★ |
|
|
||||||
| `stock` | 件 | — | ⭐ |
|
|
||||||
| `currency` | ISO | Ozon/TikTok/Shopee 各不同 | ★★ |
|
|
||||||
| `msrp` | USD | 模板列 9 | |
|
|
||||||
| `运费` | USD/CNY | 模板列 15/16;速卖通走运费模板 | ★ |
|
|
||||||
| `promotionPrice` | — | Walmart 字段 | |
|
|
||||||
|
|
||||||
### 3.4 物流与包装
|
|
||||||
| 字段 | 单位 | 规则 | 备注 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `weight` | 见 §5 | 源克 → 平台单位,**必须显式换算** | ★★ 曾把 130g 写成 130KG |
|
|
||||||
| `netWeight` | 同上 | Temu **净重必须 < 重量** | ★★ |
|
|
||||||
| `packageLength/Width/Height` | cm 或 inch | **不得用重量推算**;Walmart 转英寸 | ★★ |
|
|
||||||
| `shippingWeight` | lbs | Walmart 专用 | ★★ |
|
|
||||||
| `freightTemplate` / `promiseTemplate` | id | 速卖通 | ★★ |
|
|
||||||
| `warehouse` / `发货地区代码` | — | TikTok/Walmart | ★★ |
|
|
||||||
| `含粉末/液体/电池/金属` | bool | 模板列 52–55 | |
|
|
||||||
|
|
||||||
### 3.5 图片与视频
|
|
||||||
| 字段 | 格式 | 规则 |
|
|
||||||
|---|---|---|
|
|
||||||
| `mainImages[]` | 外链数组 `{url,imgUrl,name,width,height(,checked)}` | 上传前过 `filterProductImages()`(剔详情图)★★ |
|
|
||||||
| `variantImages[]` | 同上 | **每个变种只放自己的图,不能串图**(TikTok #4/Walmart #7/速卖通 #8)★★ |
|
|
||||||
| `mainImage`(单图串) | `\|` 分隔 | Walmart/Temu 的落库形态 ★★ |
|
|
||||||
| `squareImage` | 单图 | SHEIN 方图字段 ★★ |
|
|
||||||
| `materialImage` / `packageImages` | 数组 | Temu 专用 ★★ |
|
|
||||||
| `sizeChartImage` | 单图 | Temu 第 4 张 = **cm/inch 双标尺寸图** ★★ |
|
|
||||||
| `whiteBgImage` | ≥800×800 | 内网 FlairGS `/api/white-bg`,3.6s,可用 ★★ |
|
|
||||||
| `sceneImage` | 3:4 | flux 可用但构图不合格;forge 不可用 ★★ |
|
|
||||||
| `video` / `videoUrl` | URL | 速卖通/TikTok 有槽位,当前未填 ★ |
|
|
||||||
|
|
||||||
### 3.6 变种
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| `variantTheme` | 变种主题(颜色/尺码);TikTok 还要求**主题图** ★★ |
|
|
||||||
| `option1/2/3` + `option1Value/...` | TikTok raw 字段命名(最多 3 个变种维)★★ |
|
|
||||||
| `skuDataList[]` | Temu 的 SKU 行:`length,width,height,weight,netWeight` + 货号/EAN/价格 ★★ |
|
|
||||||
| `variantList[]` | 通用:`sku, price, salePrice, quantity, imageList, weight, packageL/W/H` ★★ |
|
|
||||||
| `saleProp` / `propertyInfo` | Lazada/Shopee 变种属性 ★★ |
|
|
||||||
|
|
||||||
### 3.7 合规
|
|
||||||
| 字段 | 取值 | 来源 |
|
|
||||||
|---|---|---|
|
|
||||||
| `brand` | **`无品牌`** | 硬要求 ★★ |
|
|
||||||
| `isNoBrand` | `true` | Walmart **独立开关**(`walmartAttrStore.isNoBrand`),光写 brand 不生效 ★★ |
|
|
||||||
| `countryOfOrigin` | `China` | Walmart `Country of Origin-Substantial Transformation` ★★ |
|
|
||||||
| `manufactureId` / `产地` | 供应商产地或固定 id | 速卖通写死 `9441741844`(待改)★★ |
|
|
||||||
| `高关注化学品` | `none` | 速卖通写死 id `23399591357`(待改)★★ |
|
|
||||||
| `productManual` | 文件 | 速卖通类目相关强制 ★ |
|
|
||||||
|
|
||||||
### 3.8 店铺与类目
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| `shopId` | 店铺;TikTok/Walmart/Ozon 用下拉 |
|
|
||||||
| `siteIds[]` / `sites` | TikTok 多站点(全选后 `["5880928","5880883","5880945"]`)★★ |
|
|
||||||
| `categoryId` / `fullCid` | 类目;**必须与店铺授权类目对应**,不得猜 ★★ |
|
|
||||||
| `categoryName` / `nodePath` | 展示名/路径 |
|
|
||||||
| `attributes` / `productAttributes` | 类目属性(各平台结构不同) |
|
|
||||||
| `sizeChart` / `sizeTemplateId` | 尺码表 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 各平台字段对照(店小秘侧)
|
|
||||||
|
|
||||||
### 4.1 平台清单与规模 ★★★(库内真实数据,`products.listings`)
|
|
||||||
|
|
||||||
**店小秘(source=dianxiaomi)**
|
|
||||||
|
|
||||||
| 平台 | 接口模块 | raw 字段数 | 待发布 | 在线 | 发布失败 |
|
|
||||||
|---|---|---:|---:|---:|---:|
|
|
||||||
| 速卖通 | `smtProduct` | **191** | 293 | 1,682 | — |
|
|
||||||
| 速卖通全托管 | `smtProduct`(cus) | 126 | 34 | 185 | 2 |
|
|
||||||
| Ozon | `ozonProduct` | 10 + `products[]` 45 | 52 | 20,415(组 2,593) | — |
|
|
||||||
| TikTok | `tiktokProduct` | **167** | — | 4,257 | — |
|
|
||||||
| Shopee-全球 | `shopeeProduct` | **141** | 1 | 3,577 | — |
|
|
||||||
| Walmart | `walmartProduct` | 35 | 48 | 2,090(组 1,068) | 6 |
|
|
||||||
| SHEIN | `sheinProduct` | 48 | — | 710 | — |
|
|
||||||
| Lazada | `lazadaProduct` | 61 | — | — | — |
|
|
||||||
| (Joom / aliChoice) | — | — | 未接入同步 | — | — |
|
|
||||||
|
|
||||||
**妙手(source=miaoshou)**
|
|
||||||
|
|
||||||
| 平台 | key | raw 字段数 | 待发布 | 在线 |
|
|
||||||
|---|---|---:|---:|---:|
|
|
||||||
| Temu 全托管 | `pddkj` | 73 | 45 | **55,462** |
|
|
||||||
| TikTok | `tiktok` | 84 | 2,744 | — |
|
|
||||||
| Shopee | `shopee` | 74 | 37 | — |
|
|
||||||
| SHEIN | `shein` | 61 | 44 | — |
|
|
||||||
| Ozon | `ozon` | — | 采集箱 1,555(未发布) | — |
|
|
||||||
|
|
||||||
### 4.2 平台字段落点(写入路径)★★
|
|
||||||
|
|
||||||
> 落点 = Pinia store 路径 或 DOM selector。**改 store ≠ 落库**,见 §10。
|
|
||||||
|
|
||||||
**Walmart**(保存体是 **zip 的 multipart**,内含 `walmartSave.txt`)
|
|
||||||
| 字段 | 落点 |
|
|
||||||
|---|---|
|
|
||||||
| Product Name | `walmartProductStore.formState.productName` |
|
|
||||||
| 产品标识符类型 | `walmartProductStore.formState.productIdType` = **`ISBN`**(原 UPC,已改) |
|
|
||||||
| 产品标识符值 | `walmartProductStore.formState.productId` |
|
|
||||||
| 品牌 | `walmartAttrStore.formState.brand` = `无品牌`;**并** `isNoBrand=true` + 点 `.ant-checkbox-wrapper` |
|
|
||||||
| 价格/库存/运输重量 | `walmartPriceShippingStore.formState.{price, inventory, shippingWeight}`(重量 **lbs**) |
|
|
||||||
| 描述/keyFeatures | `walmartDescStore.formState`(HTML bullets) |
|
|
||||||
| 主图 | `walmartImgStore.formState.mainImage`(**必须 hover→「网络图片」贴 URL**)⚠ 当前不落库 |
|
|
||||||
| 来源URL | **`walmartDxmInfoStore.sourceList`**(2026-09-22 由 Hindsight 记忆回环补充确认的落点) |
|
|
||||||
| Country of Origin | 页面 DOM 字段(已确认存在,待接;目标值 **`China`**) |
|
|
||||||
| 变种 | `basic.hasVariant=false`(**当前被关掉**,需求 #7 要求开启) |
|
|
||||||
|
|
||||||
**Temu**
|
|
||||||
| 字段 | 落点 |
|
|
||||||
|---|---|
|
|
||||||
| productName | `temuAddProductStore.$state.formState.productName` |
|
|
||||||
| 主图/材质图/包装图 | 同 store `.mainImage / .materialImage / .packageImages` |
|
|
||||||
| 描述(PC/无线) | `temuAddStore.$state.describeInfoData.{detail, mobileDetail}` |
|
|
||||||
| 类目 | `temuAddBasicStore.$state.formState.categoryId`(store 直读类目 1038 个) |
|
|
||||||
| SKU 行 | `temuAddSkuDataStore.$state.formState.skuDataList[]`(`length/width/height/weight/netWeight`) |
|
|
||||||
| 变种属性 | `temuAddSkuAttrStore.$state.formState`(用 `.theme-value-add` 手动输值+添加,改 store 无效) |
|
|
||||||
| 其他 | `temuAddOtherStore.$state` |
|
|
||||||
| ⚠ 条件必填 | 产地=中国 → **省份必填**;报错层层递进 |
|
|
||||||
|
|
||||||
**TikTok**(价格 **MYR**)
|
|
||||||
| 字段 | 落点 |
|
|
||||||
|---|---|
|
|
||||||
| 店铺/站点 | `tiktokGlobalAddBasicStore.formState.{shopId, siteIds, categoryId}`(多国+复制用「全选」) |
|
|
||||||
| 标题/关键词/描述 | `tiktokGlobalAddProductStore.formState.{productName, searchTerms, description}`(描述含 `<img>`) |
|
|
||||||
| 品牌 | `tiktokGlobalAddStore.attrsInfo` = `无品牌` |
|
|
||||||
| 货到付款 | `tiktokGlobalAddOtherStore.$state.logisticsFormState.isCodOpen = 1`(**不是** `dxFormState`) |
|
|
||||||
| 主图 | `tiktokGlobalImgStore.formState.mainImage[]` |
|
|
||||||
| 变种图 | `skuDataList[i].image` / `tiktokGlobalSkuUsImageStore.skuUsImageMap`(**美国站专用**,本店 `isSupportSkuUsImage=false`)⚠ 未完成 |
|
|
||||||
| 重量/尺寸 | 重量 `setItem('重量')`;尺寸规格三个 input 按 placeholder 长/宽/高(回读 `长=130 宽=78 高=26`) |
|
|
||||||
| 来源URL | `setItem('来源URL')` = `https://detail.1688.com/offer/<oid>.html` |
|
|
||||||
|
|
||||||
**速卖通**(价格含税;`check_box` 属性值必须是数组)
|
|
||||||
| 字段 | 落点/说明 |
|
|
||||||
|---|---|
|
|
||||||
| 品牌 | 属性:`无品牌` / `NONE(无品牌)` |
|
|
||||||
| 产地 | 属性 id `9441741844`(**硬编码,待改**) |
|
|
||||||
| 高关注化学品 | 属性 id `23399591357`(**硬编码**,应 `none`) |
|
|
||||||
| 图片 | `imageList`,外链对象 `{id,url,name,width,height,imgId,checked}` |
|
|
||||||
| 描述 | CKEDITOR(PC+无线),需插图 + 规范 HTML |
|
|
||||||
| 价格/货值 | `零售价`/`货值`(原硬编码 1.99/0.50,须一致) |
|
|
||||||
| 重量/尺寸 | 原硬编码 `0.02kg` / `10×5×3`(待用 1688 实抓) |
|
|
||||||
| 运费/服务模板 | `freightTemplateId` / `promiseTemplateId` |
|
|
||||||
| SKU | `smtProductSkuList[]`(含 `eanCode, skuPrice, skuStock, skuImage, packageWeight…`) |
|
|
||||||
| 说明书 | `productManual`(类目相关**强制**,缺失会被 `MYJ.message.error` 吞掉) |
|
|
||||||
|
|
||||||
**SHEIN**
|
|
||||||
| 字段 | 落点 |
|
|
||||||
|---|---|
|
|
||||||
| 图片 | `sheinImgStore.formState.squareImage`(方图) + `mainImgData.{multipleSpuImgs, singleSpuImg}` ⚠ **全部写住仍不落库**,见 §10 |
|
|
||||||
| 类目 | 需**两个规格**(点 `.theme-select-box` 加 `尺寸(Size)`) |
|
|
||||||
| 顺序 | 必须「规格 → SKU → 尺码表」(加规格会**重建 SKU 行**) |
|
|
||||||
|
|
||||||
**Shopee**
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| 列表字段名 | `itemName`(**不是** title) |
|
|
||||||
| 模式 | 单件模式 `type='single'` 绕开变种 |
|
|
||||||
| 图片 | `mainImages`(原脚本把主图+详情图全量塞入 → 已加过滤器) |
|
|
||||||
| 重量 | 字段单位 **KG**,输入是**克** → 必须 `g/1000` |
|
|
||||||
| 尺寸 | 原用重量推算(`W = weight×1.3`)→ 必须改 1688 实抓 |
|
|
||||||
|
|
||||||
**Ozon**
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| 价格 | **CNY**;`variantList[].price`(原价) / `salePrice`(售价),**售价必须<原价** |
|
|
||||||
| 保存 | 按钮是「保存并移入待发布」 |
|
|
||||||
| 在线口径 | 返回**合并组**:`productNum/count` 才是子商品数 |
|
|
||||||
|
|
||||||
> ⚠ **Ozon 没有建品脚本**:`~/ozon*` 只有 `ozon定价/`(算价)与 `ozon改价/ozprice_set.mjs`(**只写「价格/原价」两个字段**,入口 `/web/ozonProduct/edit?id=<pid>`,原价 = 售价×1.2 向上取整 2 位,且**唯一有 API 级回读**:`GET /api/ozonProduct/add.json?id=<pid>` → `variantList[].price/.salePrice`)。
|
|
||||||
> 全盘 find 无 `ozon建品` 目录 → **Ozon 字段清单无法从代码抽取,要补必须先写脚本**。
|
|
||||||
|
|
||||||
### 4.3 跨平台字段名对照表 ★★(同一语义,8 个平台叫法完全不同)
|
|
||||||
|
|
||||||
| 语义 | SMT 速卖通 | Shopee | SHEIN | Temu | Joom | aliChoice | Walmart | TikTok |
|
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|
||||||
| 标题 | DOM「产品标题」 | `…ProductStore.title` | `sheinAddProductStore.productName` | `temuAddProductStore.productName` | `smtChoiceAddBasicStore.name` | `smtChoiceAddBasicStore.subject` | `walmartProductStore.productName`(+DOM) | `tiktokGlobalAddProductStore.productName` |
|
|
||||||
| 描述 | CKEditor `setData` | `…descriptionInfo.{descriptionText,descriptionHtml}` | — | — | `smtChoiceAddBasicStore.description` | `smtChoiceAddStore.describeInfoData.detail`+CKEditor | `walmartDescStore.description`(+DOM) | `…AddProductStore.description` |
|
|
||||||
| 卖点 | 并入描述 | 并入 descriptionText | — | — | 并入 description | 并入 detail | `walmartDescStore.keyFeatures` | `…AddProductStore.keyProductFeatures` |
|
|
||||||
| 搜索词/标签 | 并入「Keywords:」 | 并入 descriptionText | — | — | `smtChoiceAddBasicStore.tags` | — | — | `…AddProductStore.searchTerms` |
|
|
||||||
| 价格 | DOM「零售价格」 | DOM「价格」←`price_cny` | `sheinSkuDataStore.skuDataList[i].skcRetailPrice` | `temuAddSkuDataStore…skuDataList[i].suggestedPrice` | `joomAddPriceStore.price` | `smtChoiceSkuDataStore…skuDataList[i].skuPrice` | `walmartPriceShippingStore.price`(+DOM) | SKU 表列「本地展示价」←`price_myr` |
|
|
||||||
| 供货/成本 | — | — | `…supplyPrice` | `…supplierPrice` | 仅 declaredValue | — | — | — |
|
|
||||||
| 货值/报关 | DOM「货值」 | — | — | — | `joomAddPriceStore.declaredValue` | — | — | — |
|
|
||||||
| 库存 | DOM「库存数量」 | DOM「库存」 | `…stockVariety` | (SKU 行无) | `joomAddPriceStore.inventory` | — | `walmartPriceShippingStore.inventory`(+DOM) | SKU 表列「库存」 |
|
|
||||||
| 重量 | DOM「重量(kg)」+「包装后重量」 | DOM「重量」←`weight` | `…skuDataList[i].weight` | `…weight`/`.netWeight` | — | `…packageWeight` | `walmartPriceShippingStore.shippingWeight`←`weight_lbs`(+DOM 运输重量) | DOM「重量」←`weight` |
|
|
||||||
| 尺寸 | placeholder 长/宽/高 +「包装后尺寸」 | placeholder 长/宽/高←**重量推导** | `…length/width/height` | `…length/width/height` | — | `…packageLength/Width/Height` | (**未写**;`dimsToInch` 备而未用) | placeholder 长/宽/高←**重量推导** |
|
|
||||||
| 品牌 | `smtAddAttrStore.dynamicFormData[品牌id]='history-…'` | — | 必填属性兜底 | — | 必填属性兜底 | 必填属性兜底 | `walmartAttrStore.brand`+DOM | — |
|
|
||||||
| 产地 | `dynamicFormData[产地id]='history-…'` | — | 必填属性选「中国/China」 | `temuAddProductStore.productOrigin='CN'`+`region2Id` | — | 必填属性兜底 | — | — |
|
|
||||||
| 类目 | `smtAddBasicStore.categoryId`(弹窗) | `input[placeholder="搜索分类名称"]` | 同左→`sheinAddBasicStore.categoryId` | 同左→`temuAddBasicStore.categoryId` | `.ant-select-selector` 可搜索 | 弹窗 | `.ant-select-selector` **可滚动**下拉全等 | 弹窗 |
|
|
||||||
| SKU/货号 | — | — | `…skcSupplierCode`(`SHEIN-001`) | `…extCode`(`TM-<pid>-<n>`) | `smtChoiceAddBasicStore.parentSku`(`JM-<pid>`) | `…skuCode`(`AC-<pid>-<n>`) | `walmartProductStore.sku`(`WM-<oid>`) | SKU 表列「SKU」(`AM-<oid>`) |
|
|
||||||
| EAN/UPC | — | — | — | `…skuDataList[i].code`(**自算 EAN-13**) | — | — | `productId`←`ean`,`productIdType` | — |
|
|
||||||
| 主图 | `smtAddProductStore.imageList` | `shopeeGlobalImageInfoStore.$state.imageInfo.mainImages` | `sheinImgStore.mainImgData.multipleSpuImgs` | `temuAddProductStore.mainImage` | `joomAddImageStore.imageList` | `smtChoiceAddProductStore.mainImage` | `walmartImgStore.mainImage` | `tiktokGlobalImgStore.$state.formState.mainImage` |
|
|
||||||
| 素材/详情图 | 并入 imageList | 并入 mainImages | — | `.materialImage`+`packageFormState.packageImages` | `.detailImageList[0].images` | — | (`walmartPackStore` 未用) | — |
|
|
||||||
| 变种/规格 | — | `type='single'`(绕开变种) | `.theme-select-item`+`.sku-option`(**上限 3 规格**) | `.theme-value-add`+`temuAddSkuAttrStore.skuAttrs` | — | 原生 select「SKU分类」 | `hasVariant=false` | 「变种属性」`input[class*="!w-200"]`+「添加」 |
|
|
||||||
| 来源URL | — | DOM「来源URL」 | — | — | — | — | (已确认存在,待接) | DOM「来源URL」 |
|
|
||||||
| 物流属性 | `smtAddProductStore.specialProductTypeList`(**直赋**) | — | — | — | — | 报错→`.icon_edit2`→勾「普货」(UI) | — | — |
|
|
||||||
| 运费/包装 | 运费模板/服务模板/是否原箱/含关税 | — | — | `packageShape=1`、`packageType=0` | `shipping='0'`、`msrp=''` | — | — | — |
|
|
||||||
|
|
||||||
**落点分两类(决定改字段该走 store 还是走 UI)** ★★
|
|
||||||
- **① 强一致,可 `$state` 直赋**:SHEIN/aliChoice/Temu 的 SKU 行、SMT 的 `dynamicFormData`/`specialProductTypeList`、**全部平台的图片数组**。
|
|
||||||
- **② 必须走 DOM / 真实鼠标**:Temu 变种值(`tmbatch.mjs:57` 注释明写"直改 store 无效")、Temu SKU 预览图(DOM click 打不开下拉)、SMT 描述(必须走 CKEditor 实例)、aliChoice 物流属性(必须走"先保存逼出报错→编辑弹窗")、**所有平台的分类弹窗**。
|
|
||||||
- 反例(可直改已建草稿):`tm_fixean.mjs:21-25` 证明编辑页 `skuDataList[i].code` 仍可直赋。
|
|
||||||
|
|
||||||
### 4.4 硬编码清单(全部"待改",按平台)★★
|
|
||||||
|
|
||||||
| 平台 | 硬编码项 |
|
|
||||||
|---|---|
|
|
||||||
| 速卖通 | 店铺 `4024039`;零售价 **1.99** / 货值 **0.50** / 库存 100;重量 **0.02kg** ×2;尺寸 **10/5/3**(两处);物流属性 `['1983471271:2025103']`;品牌/产地/化学品 id `201512802/9441741844/23399591357`;是否原箱=否;含关税=不含;运费模板默认 `MY beauty 30g+1美金` |
|
|
||||||
| Shopee | 店铺 `Ljbgkj`;`type='single'`;库存 999;**类目关键词恒回退 `Accessories`** |
|
|
||||||
| SHEIN | 库存 999;SKU 重量/长/宽/高 **100/200/150/30**;尺码表 **10,18,3,2,18,5**;前缀 `SHEIN-`;价默认 12.90;供货价默认 8.50;**规格上限 3**;产地=中国 |
|
|
||||||
| Temu | 店铺 ①Alimingpro;省份 `43000000000002`;`productOrigin='CN'`;`packageShape=1`/`packageType=0`;变种 `['Black','One Size']`;SKU 尺寸 **20/15/3**;重量/净重 **100/90**;EAN 前缀 `69803093` + 起始 3448;价默认 19.90;成本默认 10 |
|
|
||||||
| Joom | 库存 999;`shipping='0'`;`msrp=''`;前缀 `JM-`;价默认 19.90;报关默认 10;**详情图语言=英语**;跳过 pid `676236596821,601964534137` |
|
|
||||||
| aliChoice | `packageWeight=100`;尺寸 **20/15/3**;前缀 `AC-`;价默认 19.90;`valueFor` 全部兜底;`['单品单件','件/个']`;普货;跳过 pid |
|
|
||||||
| Walmart | 店铺 `Snowmountain`;`hasVariant=false`;**`productIdType='UPC'`**(但 `IN.ean` 是 13 位 EAN);前缀 `WM-`;品牌默认 **`Generic`**(应为无品牌);库存 999 |
|
|
||||||
| TikTok | 店铺语义 `AutoMavenShop_MY`;变种默认 `'Default'`;库存 999;描述 style 串;前缀 `AM-`;来源URL 模板 |
|
|
||||||
| Ozon(改价) | 原价 = 售价 × 1.2,向上取整 2 位 |
|
|
||||||
|
|
||||||
### 4.5 建品脚本的输入源矩阵(谁读哪个文件)★★
|
|
||||||
|
|
||||||
| 输入文件 | 消费者 | 键 |
|
|
||||||
|---|---|---|
|
|
||||||
| `tiktok建品/tk_input.json`(48 条) | TikTok / Shopee / SHEIN / Temu / Joom / aliChoice(**6 个平台共用**) | 实测恰好 11 键:`title, bullets, keywords, imgs, cost, weight, price_cny, price_myr, guess, cat_kw, cat_fb` |
|
|
||||||
| ↑ **缺键后果** | Shopee 要 `sp_kw`/`sp_fb` → **0/48 命中 → 类目恒 `Accessories`**;无 `ean` → Temu **自造 EAN-13** | — |
|
|
||||||
| `walmart建品/wm_input.json`(46 条)+ `wm_cats.json`(152 类目) | Walmart | `title, bullets, keywords, imgs, weight(克,未用), ean, pc(未用), cat_index, cat, price_usd, weight_lbs` |
|
|
||||||
| `/tmp/en_all.json` + `/tmp/wm_clean.json` + `/tmp/all48.json` | 速卖通 | title/bullets/keywords;图片 URL 数组;仅用 keys |
|
|
||||||
| `shein_route.json` / `temu_route.json` / `alc_route.json` / `joom_route.json` | 各自平台 | 类目/店铺路由 |
|
|
||||||
| `ean_cursor.json` + `ean_temu_used.txt` | Temu | **EAN 游标,必须落盘防撞号** |
|
|
||||||
|
|
||||||
**⚠ 两个 EAN 体系互不相通**:Temu 用 `69803093`+4 位流水自造;Walmart 用 `wm_input.json[oid].ean`。
|
|
||||||
**⚠ `IN.weight`(克)在 SMT/SHEIN/Temu/aliChoice 4 个平台完全没被读取**;`IN.guess` 全平台 0 命中。
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 单位 / 币种 / 长度口径(最高频事故区)
|
|
||||||
|
|
||||||
### 5.1 单位 ★★
|
|
||||||
| 量 | 源口径 | 目标平台口径 | 换算 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 重量 | 1688:**`unitWeight`=kg / `pieceWeightScaleInfo[].weight`=g(两个字段并存)**;`unitWeight=0` 是"未填"不是零重;下游 `IN.weight` 多为硬编码占位 —— 详见 §1.5 | TikTok/Shopee **KG**;Walmart **lb**;Temu 重量+净重(g/kg) | `kg = g/1000`;`lb = kg×2.2046226`;`gToPound` 已有 |
|
|
||||||
| 尺寸 | 1688 `pieceWeightScaleInfo[].length/width/height` = **cm**(⚠ 9/17 样本为 0) | Walmart **inch**;Temu 尺寸图 cm/inch 双标 | `inch = cm/2.54` |
|
|
||||||
| 净重 | 需 ≤ 毛重 | Temu:**净重必须小于重量**(否则保存被拒) | 需估算或人工 |
|
|
||||||
|
|
||||||
**已发生过的真实事故**:Shopee 把 `130(g)` 直接写成 `130 KG`;尺寸用重量推算得出 `169×101×34 cm`(★★ 需求 §3.5)。
|
|
||||||
|
|
||||||
### 5.2 币种 ★★
|
|
||||||
| 平台 | 币种 | 换算线索 |
|
|
||||||
|---|---|---|
|
|
||||||
| Ozon | **CNY** | 价格带 0–1500₽ ≈ 0–113¥(≈13.27₽/¥) |
|
|
||||||
| TikTok | **MYR** | MYR ≈ CNY × 0.60 |
|
|
||||||
| Shopee | **CNY** | — |
|
|
||||||
| Walmart | USD | — |
|
|
||||||
| USD 通用 | — | `USD = CNY ÷ 6.68525`(店小秘 `siteAndUsCurrencyRate`) |
|
|
||||||
| KZT(哈萨克线) | KZT | `KZT_PER_CNY = 66.84` |
|
|
||||||
|
|
||||||
### 5.3 长度上限
|
|
||||||
| 平台 | 上限 | 证据强度 |
|
|
||||||
|---|---|---|
|
|
||||||
| Walmart | **100** | ★★ 需求 §3.9 实测(改写 9 条、超限 0、触发长度兜底 1 条) |
|
|
||||||
| Temu | **120** | ★★ 需求 §3.9 |
|
|
||||||
| 其他平台 | 脚本里有 `slice(0, 60/90/110/120/150/160/170)` | ❓ **多为日志截断,不等于平台上限**,接规则前须逐平台确认 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 词表与过滤规则 ★★(`lib/listing-rules.mjs`,已单测)
|
|
||||||
|
|
||||||
| 常量 | 内容 | 用途 |
|
|
||||||
|---|---|---|
|
|
||||||
| `BRAND_DEFAULT` | `无品牌` | 全平台默认品牌 |
|
|
||||||
| `NARROW_WORDS` | boy/boys/girl/girls/children/child/kids/kid/school/teen/teenager/toy/toys/student/baby/babies/infant/toddler(25 词) | 窄义(儿童/学生)词 |
|
|
||||||
| `RISK_WORDS` | 中文侵权(侵权/仿/高仿/同款大牌/假货/A货/精仿/原单/山寨/盗版)+ 英文仿冒(replica/fake/knockoff/counterfeit/inspired by/same as/dupe/oem/authorized)+ 硬违禁(weapon/knife/gun/ammo/drug/cbd/medicine/cure/treatment/medical grade/fda approved/antibacterial/sterilize/virus/covid)+ 促销词(best price/hot sale/free shipping/buy now/discount/promotion) | 侵权/违禁 |
|
|
||||||
| `PLATFORM_RULES` | walmart `{narrow:true,risk:true}`;temu `{narrow:false,risk:true}`;smt/alichoice/tiktok/shopee/shein/joom/ozon `{narrow:false,risk:true}` | 各平台过滤集 |
|
|
||||||
| `SYNONYMS`(策略 B 同义替换) | `for kids/children/girls/boys` → `for Ages 3+`;`kids…` → `Ages 3+`;`toys` → `Party Favors`;`toy` → `Party Favor`;`students` → `Learners`;`school(s)` → `Everyday`;`teens/teenagers` → `Ages 12+`;`babies/baby/infant(s)/toddler(s)` → `Ages 0+` | 保搜索语义 |
|
|
||||||
| 约束 | 替换目标**自身不得命中**词表(`synonymSelfCheck()`);连续年龄段折叠成一个;**超长自动退回硬删再截断** | 防"换皮继续违规" |
|
|
||||||
| `filterTitle(title, platform, {mode, maxLen})` | `replace`(默认)/`strip` | 标题 |
|
|
||||||
| `filterDescription(html, platform)` | 只过滤文本节点,标签不动 | 描述 |
|
|
||||||
| `cmToInch / kgToPound / gToPound / dimsToInch / declaredValue` | 换算与货值一致 | §5 |
|
|
||||||
| `filterProductImages(urls, min=4)` | 按 `DETAIL_IMG_RE = /\/img\/ibank\/\d{4}\/\d{1,3}\/\d{1,3}\//`(拍摄日期目录 = **详情卡**)剔除;不足 4 张补回并标 `warn` | 图片卫生 |
|
|
||||||
|
|
||||||
**实测效果**(48 条真实标题):Walmart 改写 9 条、最长 100;Temu 改写 9 条、最长 106;TikTok 0 条(不过滤窄义词)。
|
|
||||||
|
|
||||||
**⚠ 接线状态(grep 实证)**:**没有任何线上建品脚本 `import` 这个规则库**(工作副本 `platform-wmt/`、`platform-temu/`、`platform-tkt/` 三份是补丁版,尚未同步进线上 `~/<平台>建品/`)。后果:
|
|
||||||
- `filterProductImages()` 的 `keep` **没接回** `IN.imgs` 消费点 → 各平台仍上传**未过滤的原始图数组**(含详情卡/儿童 stock 图);
|
|
||||||
- `cmToInch/kgToPound/declaredValue/dimsToInch` 全部备而未用 → 于是出现「速卖通货值 0.50 vs 售价 1.99」(`declaredValue` 本可拦住)、「Walmart 的 `weight_lbs` 只能在输入文件里预先算好」。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 图片与视频规范
|
|
||||||
|
|
||||||
| 项 | 规范 | 证据 |
|
|
||||||
|---|---|---|
|
|
||||||
| 写法 | **全部外链**(alicdn 等),对象形如 `{url, imgUrl, name, width, height(, checked)}`,多数平台存**数组** | ★★ |
|
|
||||||
| 例外 | Walmart 必须走「网络图片」UI,落成 `\|` 分隔字符串;Temu `mainImage` 是 `\|` 分隔外链串且**能落库** | ★★ |
|
|
||||||
| 禁含 | **中文、促销字样、水印、联系方式**(平台会删/拒) | ★★ |
|
|
||||||
| 主图 vs 详情图 | 1688 `IN.imgs` **混装**;上传前过 `filterProductImages()`;实测 48 商品 279 图里 12 张是详情卡 | ★★ |
|
|
||||||
| 儿童/模特图 | 含儿童的 stock 图在 Walmart/Temu 属高风险,一并剔除;模特图经 rembg 后模特仍在,不能当白底图 | ★★ |
|
|
||||||
| 白底图 | FlairGS `POST /api/white-bg`,**800×800 / 3.6s**,rembg 路线,不依赖 Forge ✅ 可用 | ★★ |
|
|
||||||
| 场景图 | flux `POST /api/generate-scene`(768×1024 / 66–76s)技术可用但**构图不合格**;forge 引擎 HTTP 500/503 ❌ | ★★ |
|
|
||||||
| 去水印/去中文 | 依赖 Forge,当前 503 ❌ | ★★ |
|
|
||||||
| 尺寸图 | Temu 第 4 张必须是 **cm/inch 双标尺寸图** | ★★ |
|
|
||||||
| SHEIN 特有 | `squareImage`(方图)+ `mainImgData.{multipleSpuImgs, singleSpuImg}`,且页面有「新版/旧版图片」模式开关 | ★★ |
|
|
||||||
| 视频 | 速卖通(`video/videoUrl/installVideo`)、TikTok(`videoUrl/videoId/videoPostUrl`)均有槽位,**当前未填** | ★★ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 变种/变参体系(当前最弱的一环)
|
|
||||||
|
|
||||||
| 平台 | 现状 | 目标 |
|
|
||||||
|---|---|---|
|
|
||||||
| 速卖通 | **无变参**(`smtProductSkuList` 未填) | 建变参(颜色/尺码)+ 变参图 |
|
|
||||||
| TikTok | 只填了变种属性值;**变种主题图未完成**(字段候选已列出) | 每个变种主题挂图 |
|
|
||||||
| Walmart | `hasVariant=false`(**变参被关掉**) | 开启变参 + 逐变参隔离图片 |
|
|
||||||
| Temu | SKU 行已建(含尺寸/重量/净重),变种属性靠 UI `.theme-value-add` | 已完成主体 |
|
|
||||||
| 通用字段 | `variantTheme, option1/2/3(+Value), variantList[], skuDataList[], variantImageIdMap, variationListStr` | — |
|
|
||||||
|
|
||||||
**硬规则**:变参图**只能有当前变参的图,不能串图**(TikTok #4 / Walmart #7 / 速卖通 #8)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 合规与平台特有硬性要求(逐条可验收)
|
|
||||||
|
|
||||||
| # | 平台 | 要求 | 状态 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | 速卖通 | 类目必须与店铺(4024039)授权类目对应,不得猜 | ❌ 待做(白名单) |
|
|
||||||
| 2 | 速卖通 | 标题用平台热词/长尾词(接 LLM) | ❌ 待做 |
|
|
||||||
| 3 | 速卖通 | 星号必填逐项填(产地/高关注化学品,类目有差异) | ⚠ 写死 id |
|
|
||||||
| 4 | 速卖通 | 主图无中文/促销字;一键生成白底(1:1)/场景(3:4)图 | ⚠ 白底可用、场景不合格 |
|
|
||||||
| 5 | 速卖通 | 产品视频 | ❌ 未填 |
|
|
||||||
| 6 | 速卖通 | 零售价与货值一致 | ⚠ 硬编码 1.99/0.50 |
|
|
||||||
| 7 | 速卖通 | PC/无线端描述带图 + 规范 HTML | ❌ 纯文字 |
|
|
||||||
| 8 | 速卖通 | 变参与变参图 | ❌ |
|
|
||||||
| 9 | 速卖通 | 来源 URL | ❌ |
|
|
||||||
| 10 | TikTok | 多国勾选 + 复制到其他店铺「全选」 | ✅ `siteIds` 已验证 |
|
|
||||||
| 11 | TikTok | 品牌=无品牌 | ✅ |
|
|
||||||
| 12 | TikTok | 描述含产品图 | ✅ |
|
|
||||||
| 13 | TikTok | 变种主题图 | ❌ |
|
|
||||||
| 14 | TikTok | 长/宽必填 | ✅ 回读 130/78/26 |
|
|
||||||
| 15 | TikTok | 货到付款 | ✅ `isCodOpen=1` |
|
|
||||||
| 16 | TikTok | 类目白名单 | ❌ |
|
|
||||||
| 17 | Walmart | 来源 1688 URL | ❌ |
|
|
||||||
| 18 | Walmart | 标题/描述过滤窄义词 | ✅ 已接 |
|
|
||||||
| 19 | Walmart | 标识符 = **ISBN** | ✅ 保存级验证 |
|
|
||||||
| 20 | Walmart | 品牌 = 无品牌 | ⚠ 批量里 store/checked 不一致,待编辑页确认 |
|
|
||||||
| 21 | Walmart | Country of Origin = China | ❌ |
|
|
||||||
| 22 | Walmart | 尺寸→英寸、重量→英镑 | ⚠ 重量已 lbs,尺寸未处理 |
|
|
||||||
| 23 | Walmart | 变参图隔离 | ❌(变参本身被关) |
|
|
||||||
| 24 | Temu | 标题过滤违禁/侵权 | ✅(本批无命中) |
|
|
||||||
| 25 | Temu | 图片无中文;第 4 张 cm/inch 尺寸图 | ❌ |
|
|
||||||
| 26 | Temu | 产品描述 | ✅ 已写 `describeInfoData` |
|
|
||||||
| 27 | Shopee | 剔除详情图(工厂照/证书/流程图/退货/FAQ) | ✅ `filterProductImages()` |
|
|
||||||
| 28 | Shopee | 重量克→千克 | ⚠ 已识别,待接线 |
|
|
||||||
| 29 | SHEIN | 图片落库 | ❌ **写 store 不进保存载荷** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 校验与回读(验收口径,不接受的写法)
|
|
||||||
|
|
||||||
**教训(`建品质量需求.md` §3.11 原话)**:**回读校验必须断言"字段非空且与期望相等",只查"没有禁词"会自欺。**
|
|
||||||
第一版回读读的是 `hit.name`,而 Walmart 列表里标题字段叫 **`productName`** → "没找到字段 → 空串 → 判定无禁词"成了**假阳性**。
|
|
||||||
|
|
||||||
| 平台 | 回读接口 | 关键字段 | 判据 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Walmart | `POST /api/walmartProduct/pageList.json` | `productName / platformTitle / mainImage / productIdType` | 标题**精确相等** + 图片非空 |
|
|
||||||
| Temu | `POST /api/pddkjProduct/pageList.json`(模块名是 pddkjProduct) | `productName` | 精确相等 ✅ |
|
|
||||||
| SHEIN | `POST /api/sheinProduct/pageList.json` + `shopId=-1&dxmState=offline&productSearchType=1`(**没有** `dxmOfflineState`) | `mainImage` | 非空(用错参数 totalSize=0 会误判"没建成") |
|
|
||||||
| 通用 | `POST /api/<模块>Product/pageList.json` | `data.page.list` | — |
|
|
||||||
|
|
||||||
**通用铁律**
|
|
||||||
1. **写 store ≠ 落库**:SHEIN 三个图位全写住仍 `mainImage:""`;Walmart `mainImage` 同类问题(一次保存全过程 22+ 条 `/api/` 请求**没有任何图片上传接口**,最终 POST 载荷仅 12 字节)→ 必须抓保存 POST 载荷判断"没进载荷"还是"要走上传递交"。
|
|
||||||
2. **`idStr ≠ id`**:大整数在 JS 里丢精度,服务端操作(删除/详情)一律用 `idStr`。
|
|
||||||
3. **"点保存没反应"先挂钩子**:校验报错被 `MYJ.message.error` 吞掉,且**层层递进**(修完 A 才显示 B)。
|
|
||||||
4. **改 store 后校验仍失败**:UI 异步同步,**再点一次保存**可能就过。
|
|
||||||
5. **类目不能猜**:必须与店铺授权类目对应,无匹配**跳过不硬选**。
|
|
||||||
6. **跑之前先查重**:否则重试会产生重复(本会话踩过同一商品建 3 份)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 我们自己的落库结构(`products.listings`)★★★
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
source: "dianxiaomi" | "miaoshou", // 同一集合双来源
|
|
||||||
platform: "ozonProduct", // 接口模块名
|
|
||||||
platformName: "Ozon", // 展示名
|
|
||||||
state: "waitPublish" | "publishFail" | "online",
|
|
||||||
stateName: "待发布",
|
|
||||||
docKey: "184807701684308597", // idStr → productId → mergeId → 兜底哈希
|
|
||||||
title, shopId, platformProductId,
|
|
||||||
price, currency, categoryId, categoryName,
|
|
||||||
mainImage, errMsg,
|
|
||||||
createTime, updateTime,
|
|
||||||
childCount: 3, // 合并组/变种子项数
|
|
||||||
syncedAt, firstSeenAt, runId, gone, goneAt,
|
|
||||||
raw: { ...原始 item... } // ★ 平台字段全量在这
|
|
||||||
}
|
|
||||||
```
|
|
||||||
索引:`uniq_listing(source, platform, state, docKey)`、`by_state`、`by_synced`、`by_title`(text);同步账本 `products.sync_runs`。
|
|
||||||
|
|
||||||
**口径铁律**:**Ozon / Walmart 的「在线」返回的是合并组,不是子商品** —— 统计要用 `ΣchildCount`(Ozon 在线:组 2,593 → 子商品 20,415;Walmart:组 1,068 → 2,090)。分页行数不等长且会重复返回,脚本按 `docKey` 去重。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. 妙手(miaoshou)侧
|
|
||||||
|
|
||||||
**平台 key → 名称**(`sync_msp_listings.py`:`ITEM_KEYS` **22 个**是接口实测认的键;`PLATFORM_NAMES` **39 个**另含授权页专用键 —— `*Choice/*Gs/*Scs/*Fc/*Local` 这类**item 接口不认、会返回 HTML**):
|
|
||||||
`pddkj`Temu全托管 · `pddkjChoice`Temu半托管 · `pddkjLocal`Temu本土 · `shopee` · `shopeeScs`Shopee全托管 · `shopeeSwarm`Shopee轻出海 · `tiktok` · `tiktokGs`TikTok全托管 · `lazada` · `lazadaChoice` · `ozon` · `yandex` · `wildberries` · `aliexpress` · `aliexpressChoice`速卖通半托管 · `aliexpressFc`速卖通全托管 · `aliexpressLocal`速卖通本土POP · `aliexpressOverseas`速卖通海外托管 · `amazon` · `coupang` · `alibaba` · `wish` · `shopify` · `daraz` · `joom` · `allegro` · `qoo10` · `shoplazza` · `jumia` · `walmart` · `shopline` · `shein` · `sheinChoice`SHEIN半托管 · `emag` · `noon` · `miravia` · `joybuy` · `mercado`Mercado Libre
|
|
||||||
|
|
||||||
**妙手的状态机**:只有 `waitPublish`(采集箱未发布)/ `online`(发布成功)两态。
|
|
||||||
|
|
||||||
#### 12.1 接口信封(实测确认)★★★
|
|
||||||
| 接口 | 关键参数 | 响应键 |
|
|
||||||
|---|---|---|
|
|
||||||
| `POST /api/auth/shop/getShopList` | `pageNo,pageSize,platform,includeShopeeGlobal=0,excludeShopeeCnscShop=0`(**缺后两个报「页码数据有误」**) | `shopList[]`(取 `shopId`/`id`) |
|
|
||||||
| `POST /api/platform/<key>/item/item/searchItemList`(**在线**) | `titleType=multi` + **必须带 `shopIds[i]`**(否则「没有符合条件的记录」) | `{result,itemList,total}` |
|
|
||||||
| `POST /api/platform/<key>/move/collect_box/searchCollectBoxDetail`(**待发布**) | `claimPublishShopStatus=published&titleType=multi&remarkType=multi&status=notPublished` | `{result,detailList,total}` |
|
|
||||||
| `POST …/collect_box/getTabCountStatMap` | — | `{count,notPublishedCount,publishedCount,timingPublishCount,aiOptimize*}` |
|
|
||||||
| 错误 | — | `{result, reason}`(`reason` 即错误文案) |
|
|
||||||
|
|
||||||
边界:**yandex 回 HTTP 200 + 空体** = 无此功能,脚本按 `_empty` 处理,不报整轮错误。
|
|
||||||
|
|
||||||
#### 12.2 入库字段映射(`source='miaoshou'`)★★
|
|
||||||
| 字段 | 取值候选(按序) | 备注 |
|
|
||||||
|---|---|---|
|
|
||||||
| `docKey` | `collectBoxDetailId`→`itemId`→`id`→`sourceItemId`,兜底 `'h'+hash(item)` | 幂等唯一键 |
|
|
||||||
| `title` | `title`→`itemName`→`name`→`productTitle`→`itemNum` | |
|
|
||||||
| `shopId` | `shopId` / `authShopId` | |
|
|
||||||
| `platformProductId` | `platformItemId` | |
|
|
||||||
| `price` | **`salePrice`(非空优先)** → `price` → `minSkuPrice` | ⚠ **与店小秘顺序相反**(店小秘 `price`→`salePrice`) |
|
|
||||||
| `currency` | `currency` / `sourceCurrency` | |
|
|
||||||
| `categoryId` | `cid` / `categoryId` | |
|
|
||||||
| `categoryName` | **硬编码 `None`** | 妙手侧未取 |
|
|
||||||
| `mainImage` | `picUrl` / `listThumbnail` / `thumbnail` | |
|
|
||||||
| `errMsg` | `reason` | |
|
|
||||||
| `createTime`/`updateTime` | `gmtCreate`/`ts`;`gmtModified` | |
|
|
||||||
| `childCount` | **硬编码 `None`** | 妙手无合并组映射 |
|
|
||||||
| `raw` | 原始 item 全量 | 保底 |
|
|
||||||
> ⚠ 上表 API 侧键名来自脚本 `first_of` 候选列表("脚本认为可能存在");**能确证有值的只有信封的 `total/itemList/detailList`**。
|
|
||||||
|
|
||||||
#### 12.3 妙手平台与计数(`msp-platforms.json` / `msp-collectbox.json`)★★★
|
|
||||||
| 平台键 | 店铺 | 采集箱 count | 待发布 | 已发布 | 在线 |
|
|
||||||
|---|---:|---:|---:|---:|---:|
|
|
||||||
| **pddkj**(Temu全托管) | **14** | 46 | **45** | 1 | **55,462** |
|
|
||||||
| **tiktok** | 0 | 4,341 | **2,744** | 1,597 | — |
|
|
||||||
| **shopee** | 0 | 170 | **37** | 133 | — |
|
|
||||||
| **shein** | 0 | 44 | **44** | 0 | — |
|
|
||||||
| **ozon** | 0 | 1,555 | 0 | 1,555 | —(无店铺,在线同步不了) |
|
|
||||||
| 其余 16 键 | 0 | 0 | 0 | 0 | — |
|
|
||||||
|
|
||||||
- 有「待发布」数据:`pddkj 45 + shopee 37 + tiktok 2744 + shein 44 = **2870**`
|
|
||||||
- 有「在线」数据:**仅 pddkj = 55,462**
|
|
||||||
- ⚠ **口径冲突**:pddkj 采集箱「已发布」只有 1,而在线 55,462;ozon 采集箱「已发布」1,555 却无店铺 → **采集箱「已发布」≠ 产品管理「在线」,勿混用**。
|
|
||||||
- ⚠ `msp-platforms/collectbox/shops.json` **三个 JSON 是孤儿快照**(`sync_msp_listings.py` 不读它们,生成脚本未找到)。`msp-shops.json` 是空 `{}`。
|
|
||||||
|
|
||||||
#### 12.4 妙手刊登/导入参数(有来源)★★
|
|
||||||
| 参数 | 内容 | 来源 |
|
|
||||||
|---|---|---|
|
|
||||||
| **导入「链接采集」8 列 `.xls`** | `产品主编号`(1688 id,**必须**)、`链接地址(必填)`、`产品标题`、`货币类型`(**必须**,空报「不允许此货币类型」)、`SKU售价`(**必须 ≥0.01**)、`SKU库存`(**必须 ≥0**)、`价格(RMB)`、`促销价(RMB)` | `~/miaoshou/RECIPE.md`(实测解析 **47/0/47**) |
|
|
||||||
| 页面下载的官方模板 | 仅 **4 列**(`链接地址/产品标题/价格(RMB)/促销价(RMB)`),**缺 `产品主编号` → 照它填必失败** | 同上 |
|
|
||||||
| ⚠ 文档矛盾 | `archived/miaoshou-batch-import`(2026-04) 断言「严格 5 列,**绝不能加** `产品主编号`」;`RECIPE.md`(2026-09-21) 实测「**必须加**」 → **以 09-21 的 8 列为准**(服务端模板已演进) | — |
|
|
||||||
| 采集箱编辑弹窗 Tab | `基本信息 / 产品属性 / 销售属性 / 产品图片 / 产品视频 / 认证说明 / 物流信息 / 货源链接`(容器 `.collect-box-editor`) | `archived/miaoshou-collect-box`(369 行) |
|
|
||||||
| 标题 / 产品主编号 / 简易描述 / 详细描述 | 标题 `input[type=text]`;主编号 `input[1]`(placeholder「不填将自动生成」);**简易描述 `textarea[0]`** → 用于 **Shopee/Wish/Joom/Mercado Libre/Ozon/Walmart**;**详细描述 `iframe[0]`(富文本)** → 用于 **Lazada/速卖通/TikTok/Coupang/eBay/Allegro** | 同上 |
|
|
||||||
| 促销价(RMB) | 表头注明「**仅可用于采集到 Lazada**」 | `RECIPE.md` |
|
|
||||||
| 采集箱筛选/动作 | 筛选:`关联货源/产品标题/货源ID/货源价格`;动作:`任务记录/认领到/平台认领配置/批量编辑/分组/删除/创建产品/导入产品/列表设置` | 同上 |
|
|
||||||
| 导入任务列表 | `文件名称/导入时间/解析状态/成功条数/失败条数/操作`(文件保留 90 天) | 同上 |
|
|
||||||
| Ozon 刊登必填 | 分类、标题(俄语)、描述、价格(¥×12~15×1.5~2)、库存、图片;店铺需 Ozon `Client ID + API Key` | `1688-to-ozon-listing-workflow` |
|
|
||||||
| Vue 状态同步 | 写值后必须 dispatch `input`/`change`/`blur`,否则点保存**静默丢弃** | `archived/miaoshou-collect-box` |
|
|
||||||
| 链接采集 URL 格式 | `URL` / `URL$$title` / `URL\|\|title` | `miaoshou-erp/SKILL.md` |
|
|
||||||
|
|
||||||
#### 12.5 妙手 vs 店小秘 关键差异 ★★
|
|
||||||
| 维度 | 妙手 | 店小秘 |
|
|
||||||
|---|---|---|
|
|
||||||
| 平台键 | 裸键 `pddkj`,**按平台分路径** `/api/platform/<key>/…` | module 名 `ozonProduct`/`smtProduct`,统一 `POST /api/<module>/pageList.json` |
|
|
||||||
| 状态机 | **2 态** | **3 态**(多 `publishFail`) |
|
|
||||||
| 在线前置 | **必须 `shopIds[i]`** | 无需 |
|
|
||||||
| 发布失败 | 无对应状态,仅 `errMsg ← reason` | 独立状态 + `errMsg/auditFailureReason` |
|
|
||||||
| ID | `collectBoxDetailId→itemId→…`,**无 idStr 概念** | `idStr→productId→mergeId`,**idStr 优先** |
|
|
||||||
| 价格键序 | `salePrice→price→minSkuPrice` | `price→salePrice`(**相反**) |
|
|
||||||
| 子商品 | **`childCount=None`** | `products/productList` 或 `productNum/count`(合并组) |
|
|
||||||
| 分页 | `PAGE_SIZE=500`(111 页 × 500 = 55,462 ✓) | `PAGE_SIZE=200`(**传 500 只回 100**) |
|
|
||||||
| 图片 | **无外链/上传字段证据**;靠「链接采集」由妙手服务端抓源页 | 建品表单**全用外链**;Walmart 例外走「网络图片」UI |
|
|
||||||
| 发布 | 未打通(子账号无发布权限) | 「保存并移入待发布」已跑通 |
|
|
||||||
| Joom | 纳入 | **默认排除 `joomProduct`** |
|
|
||||||
|
|
||||||
#### 12.6 妙手侧"无证据"清单(不编造)❓
|
|
||||||
1. **妙手各平台刊登 API 字段**(重量/尺寸/EAN/品牌/变种/类目差异)——**零证据**,无文档也无代码。
|
|
||||||
2. 是否有 `publishFail` / 定时发布状态(只有 `timingPublishCount` 计数键)。
|
|
||||||
3. `msp-shops.json` 为空;店铺只在运行时 `getShopList` 现取。
|
|
||||||
4. 妙手侧 `title/price/categoryId/mainImage` 候选键**哪些真命中**(无 raw 样本落盘)。
|
|
||||||
5. 妙手图片是外链还是上传——**未验证**。
|
|
||||||
6. 「采集并自动发布」是否真能发布——自述"待实测",权限口径有冲突。
|
|
||||||
|
|
||||||
|
|
||||||
**妙手文档字段(raw,以平台为单位)**:公共骨架高度一致,含
|
|
||||||
`collectBoxDetailId, collectBoxGroupId/Name, commonCollectBoxDetailId, platform, platformItemId, source/sourceItemId/sourceItemUrl/sourceSite/sourceShopName, sourceList[], title, thumbnail/listThumbnail, price, stock, minWeight/maxWeight, minSkuPrice/maxSkuPrice, sitePriceMap, siteAndCatMap, siteAndCidMap, shopIds, pubShopList, timingShopList, importTemplateType, copyType, dataPath, infringementDetectResult, taskProcessStatusList, status, gmtCreate/gmtModified`
|
|
||||||
平台差异:TikTok 多 `packageL/W/H, weight, siteWeightMap, isSupportReplicateProduct, *IncludeVat` 等;Shopee 多 `promotion, hasSavePrice/hasSaveSite/hasTranslate, maxOriginPrice`;Temu 多 `collectBoxDetailShopList[]{brandId, breadcrumb, cid, currency, sizeTemplateId…}`。
|
|
||||||
|
|
||||||
**与店小秘的差异**:妙手用 `collectBox`(采集箱)作为待发布容器,字段名以 `collectBox*` 前缀;店小秘用 `dxmState/dxmOfflineState` 状态位。两者都保留 `sourceItemUrl`(1688 来源)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. 字段可得性矩阵(能不能自动拿到)
|
|
||||||
|
|
||||||
| 字段 | L1 源 | 自动可得 | 需人工/生成 | 备注 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 标题/卖点/关键词 | ✅ | ✅ | | 需过滤 + 可 AI 优化 |
|
|
||||||
| 价格/成本 | ✅ | ✅ | | 定价公式已有 |
|
|
||||||
| 重量 | ⚠ 口径不一 | ⚠ | ✅ 校核 | 单位事故高发 |
|
|
||||||
| 包装尺寸 | ⚠ 常缺 | ⚠ | ✅ | 禁止用重量推算 |
|
|
||||||
| 主图 | ✅ | ✅ | | 必须过滤详情图/中文 |
|
|
||||||
| 白底图 | — | ✅ | | FlairGS 可用 |
|
|
||||||
| 场景图 | — | ❌ | ✅ | flux 构图不合格 |
|
|
||||||
| 视频 | ❌ | ❌ | ✅ | 需生成或人工 |
|
|
||||||
| 变种定义 | ⚠ | ⚠ | ✅ | 1688 规格 → 变种映射 |
|
|
||||||
| 变种图 | ⚠ | ⚠ | ✅ | 串图风险 |
|
|
||||||
| 品牌 | ❌ | — | 固定 `无品牌` | |
|
|
||||||
| EAN/UPC/ISBN | ❌ | ✅ | | 自建台账(S03 落账) |
|
|
||||||
| 原产国/产地 | ⚠ 供应商 | ⚠ | ✅ | 默认 China |
|
|
||||||
| HS 编码 | ❌ | ⚠ | ✅ | 类目映射 |
|
|
||||||
| 高关注化学品 | ❌ | — | 固定 `none` | |
|
|
||||||
| 说明书文件 | ❌ | ❌ | ✅ | 速卖通部分类目强制 |
|
|
||||||
| 类目 | ⚠ 猜 | ❌ | ✅ | 必须店铺授权白名单 |
|
|
||||||
| 尺码表 | ⚠ | ⚠ | ✅ | SHEIN/Shopee 需要 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. 待确认 / 缺口(按优先级)
|
|
||||||
|
|
||||||
**P0(正在流血)**
|
|
||||||
1. **妙手 cookie 已过期**,22 平台全 `授权已经过期`,而监控报 OK(见 §0)→ 需重登 + 修判据。
|
|
||||||
2. **SHEIN / Walmart 的 `mainImage` 不落库**(写 store 不进保存载荷)→ 需抓保存 POST 载荷定位。
|
|
||||||
3. **规则库未接线**:`filterProductImages / cmToInch / kgToPound / declaredValue / dimsToInch` 一个都没被线上脚本 import(见 §6)。
|
|
||||||
|
|
||||||
**P1(字段口径)**
|
|
||||||
4. **重量源口径未统一**:`unitWeight`(kg) vs `pieceWeightScaleInfo.weight`(g),且 `unitWeight=0` 要当缺失(见 §1.5)。
|
|
||||||
5. **`IN.weight`(克) 在 4 个平台完全没被读取**,尺寸在 Shopee/TikTok 仍是重量推导、其余平台硬编码(见 §4.4/§4.5)。
|
|
||||||
6. **两个 EAN 体系互不相通**(Temu 自造 vs Walmart 输入包),且都需回 S03 落账。
|
|
||||||
7. **各平台标题长度上限**:只有 Walmart(100)/Temu(120) 实测;其余脚本里的 `slice` 不能当上限。
|
|
||||||
|
|
||||||
**P2(能力缺口)**
|
|
||||||
8. **Ozon 无建品脚本**(只有改价)→ 要 Ozon 字段清单必须先写脚本。
|
|
||||||
9. **Temu 第 4 张尺寸图**生成方式;**场景图路线**(等 Forge 还是 txt2img+PIL 合成)。
|
|
||||||
10. **变参体系**:1688 规格 → 平台变种主题的对齐规则(含变种图隔离)。
|
|
||||||
11. **妙手各平台刊登字段**:零证据,需要一篇自己的实测(妙手侧没有对应文档)。
|
|
||||||
|
|
||||||
**P3(决策)**
|
|
||||||
12. **存量 47×8 条是否补字段**(走编辑页)还是作废重建。
|
|
||||||
13. **1688 抓取路线裁决**:BDPM curl(实测可行)vs Desktop CDP(旧技能主张)。
|
|
||||||
14. 产品库库名冲突:`premiumproducts.products` vs `productpool1.products`。
|
|
||||||
15. 失效引用清理:`1688-cross-border-sourcing` 技能引用的 `~/.hermes/scripts/1688_scrape_cdp.py` 与 `references/*` 在本机不存在。
|
|
||||||
+3
-296
@@ -1,297 +1,4 @@
|
|||||||
# 妙手 ERP(erp.91miaoshou.com)配方 — 已登录、采集箱可达
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/miaoshou.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
## ✅ 登录(子账号 xiaochaoren2026,2026-09-21 成功)
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
**舰队技能档 `agent-a2-import/skills/ecommerce/miaoshou-erp/SKILL.md` 的选择器已全部失效**,
|
|
||||||
实测的真实登录表单(登录弹窗内):
|
|
||||||
|
|
||||||
| 字段 | 选择器 |
|
|
||||||
|---|---|
|
|
||||||
| 账号 | `input.login-form__input[placeholder="手机号/子账号/邮箱"]` |
|
|
||||||
| 密码 | `input.login-form__input[type=password]` |
|
|
||||||
| 验证码 | `input.captcha-field__input`(`maxlength=4`) |
|
|
||||||
| 登录按钮 | `button.login-form__submit-btn`(文案「立即登录」) |
|
|
||||||
| 验证码图 | `img.captcha-field__img` ← **src 是 base64 JPEG**,可以直接取出来识别 |
|
|
||||||
|
|
||||||
### 登录步骤(可复用)
|
|
||||||
1. 填账号/密码(原生 setter + `input`/`change` 事件)
|
|
||||||
2. **取验证码**:`document.querySelector('.captcha-field__img').src` → 剥掉 `data:image/jpeg;base64,`
|
|
||||||
→ 存成 `.jpg`(⚠️ 字节是 JPEG,存成 `.png` 读图工具会拒绝)
|
|
||||||
3. 点一下 `img.captcha-field__img` 可刷新验证码
|
|
||||||
4. 填入 4 位码 → 点 `.login-form__submit-btn` → 成功会跳到 `/welcome`(标题「妙手-欢迎使用」)
|
|
||||||
|
|
||||||
⚠️ **验证码是强制的**(技能档写的「非强制、可跳过」已失效);失败会计数,**再错 9 次锁 1 小时**
|
|
||||||
⚠️ 本机 `modlens` **没有配置视觉提供方**(报错:No vision provider),验证码是我自己看图读的
|
|
||||||
|
|
||||||
## 采集箱(已进入)
|
|
||||||
- 入口:顶栏 `.function-item` 里点 **产品采集**
|
|
||||||
- 路由:**`https://erp.91miaoshou.com/common_collect_box/index?fetchType=linkCopy`**(标题「妙手-产品采集」)
|
|
||||||
- 页面上的操作按钮:`采集优化` / `采集设置` / `更多` / **`采集并自动认领`** / **`采集并自动发布`** / `我知道了`
|
|
||||||
- 采集方式(左侧/顶部):**`链接采集`**、**`导入采集`**、`关键词采集`、`整店采集`、`插件采集`、`店铺互采`
|
|
||||||
- 其它模块:`公用采集箱`、`AI工作台`、`侵权检测`、`货盘中心`(1688跨境热卖/1688搜同款/跨境产业带)、`数据选品`、`快速上货`(Temu全托管)
|
|
||||||
|
|
||||||
## 权限现状(技能档口径,待实测确认)
|
|
||||||
技能档写明该**子账号**:「采集箱/导入采集 可用,**产品发布不可用**」。
|
|
||||||
页面上 `采集并自动发布` 按钮存在,但能否真正发布要实测(用户已确认:先用子账号把采集箱/导入采集跑到能用)。
|
|
||||||
|
|
||||||
## 下一步(下一步就能动手)
|
|
||||||
1. 走 **`导入采集`**:确认它接受什么输入(链接批量/CSV/Excel/1688 链接?),
|
|
||||||
把 48 条商品的 alicdn 图 + 1688 链接喂进去
|
|
||||||
2. 走 **`链接采集`**:单条/批量粘贴 1688 链接
|
|
||||||
3. 采集入箱后,验证「公用采集箱」里能否批量 `认领`/`发布`(受子账号权限限制)
|
|
||||||
|
|
||||||
## 导入采集(`?fetchType=importCopy`)— 四种模式
|
|
||||||
| 模式 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| **导入链接采集** | **要文件**(面板里有「下载导入模板」+「确认/取消」),提示语:**「链接地址列必填,其他列选填。若填写其他列,以填写为准,若留空以来源为准。」** |
|
|
||||||
| **Excel表格导入** | Excel 表格导入 |
|
|
||||||
| 本地素材包导入 | 本地素材包 |
|
|
||||||
| 聚水潭素材包导入 | 聚水潭素材包 |
|
|
||||||
|
|
||||||
页面下方是已导入文件列表:`文件名称 / 导入时间 / 解析状态 / 成功条数 / 失败条数 / 操作`
|
|
||||||
(**文件仅保留 90 天**,当前「暂无数据」)。
|
|
||||||
|
|
||||||
### ✅ 下一步可直接动手的一条路:用 1688 链接喂「导入链接采集」
|
|
||||||
`tk_input.json` 的 key 就是 **1688 商品 id**(如 `676236596821`),
|
|
||||||
所以每条商品的链接可以直接拼:**`https://detail.1688.com/offer/<id>.html`**(48 条 → 48 行)。
|
|
||||||
|
|
||||||
步骤:
|
|
||||||
1. 在面板里点 **「下载导入模板」** 拿到确切列名与格式(列名/是否要表头/单列或多列)
|
|
||||||
2. 用 48 个 1688 链接生成导入文件(`链接地址` 列必填)
|
|
||||||
3. 上传 → `确认` → 到「已导入的文件列表」看**解析状态 / 成功条数 / 失败条数**
|
|
||||||
4. 解析成功后在 **公用采集箱** 里核对待采集商品,再验证子账号能否 `认领`/`发布`
|
|
||||||
|
|
||||||
⚠️ 未验证:模板的确切列名(要先下载模板)、上传控件的选择器、以及**子账号能否发布**。
|
|
||||||
|
|
||||||
## ✅ 导入链接采集 = 按模板列做 CSV 上传(已跑通到「等待解析」)
|
|
||||||
|
|
||||||
### 模板列(`python3 -m pip install --break-system-packages xlrd` 读出来的)
|
|
||||||
下载的 `导入产品链接模板.xls` 是真·OLE2 文件,本机没有 xlrd/libreoffice,
|
|
||||||
装了 `xlrd`(`pip install --break-system-packages xlrd`)后读出:
|
|
||||||
|
|
||||||
| 列 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| `链接地址(必填)` | 商品链接 |
|
|
||||||
| `产品标题` | 标题 |
|
|
||||||
| `价格(RMB)` | 价格 |
|
|
||||||
| `促销价(RMB)` | 促销价(表头提示:仅可用于采集到 Lazada) |
|
|
||||||
|
|
||||||
### 格式踩坑(三次失败才走对)
|
|
||||||
| 提交内容 | 结果 |
|
|
||||||
|---|---|
|
|
||||||
| `.txt` 一行一个链接 | ❌ `…文件内容不是json格式`(**`.txt` 必须放 JSON**) |
|
|
||||||
| `.json` URL 数组 | ❌ `…文件标题缺失` |
|
|
||||||
| `.json` 带 `title/url/链接地址`(每项) | ❌ 同上 |
|
|
||||||
| `.json` 根节点带 `title/标题` + `links/data/list` | ❌ 同上 |
|
|
||||||
| **`.csv`(表头用模板那 4 列,UTF-8 BOM)** | ✅ **接受!入列显示「等待解析」** |
|
|
||||||
|
|
||||||
### 上传机制(可复用)
|
|
||||||
```js
|
|
||||||
await c.send('DOM.enable');
|
|
||||||
const { root } = await c.send('DOM.getDocument', { depth: -1 });
|
|
||||||
const { nodeId } = await c.send('DOM.querySelector', { nodeId: root.nodeId, selector: 'input.jx-upload__input' });
|
|
||||||
await c.send('DOM.setFileInputFiles', { files: ['/path/ms_import.csv'], nodeId });
|
|
||||||
// 然后点面板里的「确认」
|
|
||||||
```
|
|
||||||
上传控件:`input.jx-upload__input`(隐藏,`accept=.xls,.txt,.zip,.json,.xlsx,.csv`)。
|
|
||||||
**注意**:上传/确认后面板会关闭,下一次上传前要重新点「导入采集 → 导入链接采集」。
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
已导入文件列表里有一条:`ms_import | 2026-09-21 07:40:12 | **等待解析** | …`(47 条链接 + 标题 + 价格)。
|
|
||||||
解析是**异步排队**的,30–60 秒内还没变;下一步就是**等它变成成功/失败并看「成功条数/失败条数」**,
|
|
||||||
再点「查看导入产品」或去 **公用采集箱** 核对采集到的商品。
|
|
||||||
|
|
||||||
### 生成脚本
|
|
||||||
`/home/admin9webs/miaoshou/dl/ms_import.csv`(47 行,已排除无水印图的 `601964534137`),
|
|
||||||
由 `tk_input.json` 生成:链接 = `https://detail.1688.com/offer/<pid>.html`。
|
|
||||||
|
|
||||||
## 导入格式的完整试错记录(第 2 轮,全部失败,附下一步)
|
|
||||||
|
|
||||||
| # | 上传内容 | 结果 |
|
|
||||||
|---|---|---|
|
|
||||||
| 1 | `.txt` 一行一链接 | ❌ `…文件内容不是json格式` |
|
|
||||||
| 2 | `.json` URL 数组 | ❌ `…文件标题缺失` |
|
|
||||||
| 3 | `.json` 每项带 `title/url/链接地址` | ❌ 同上 |
|
|
||||||
| 4 | `.json` 根节点 `title/标题` + `links/data/list` | ❌ 同上 |
|
|
||||||
| 5 | `.csv`(模板 4 列,UTF-8 **带 BOM**) | 接受入列 → 解析失败:`未识别到:【产品主编号】表头` |
|
|
||||||
| 6 | `.csv`(补 `产品主编号` 列,带 BOM) | ❌ 同一句 |
|
|
||||||
| 7 | `.csv`(补 `产品主编号` 列,**无 BOM** UTF-8) | ❌ 同一句(**BOM 不是原因**) |
|
|
||||||
| 8 | **官方模板 `.xls` 原样上传** | 前端直接拒:`未匹配到数据,请检查模板是否正确`(空模板无数据行),**未产生导入任务** |
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
- 解析器要的是**带数据的 `.xls`(或 `.xlsx`)**,且表头里必须有 **`产品主编号`**;
|
|
||||||
- **CSV 看起来不管用**(即使表头齐全、去掉 BOM,仍只报缺 `【产品主编号】`);
|
|
||||||
- 下载到的模板只有 4 列(`链接地址(必填)/产品标题/价格(RMB)/促销价(RMB)`),
|
|
||||||
**缺 `产品主编号`** → 服务端模板与解析器版本不一致(错误提示也写着「请下载最新模版」)。
|
|
||||||
|
|
||||||
### 下一步(就一件事)
|
|
||||||
用能写 Excel 的库生成 `.xls`/`.xlsx`(本机:`pip install --break-system-packages xlwt` 或 `openpyxl`),
|
|
||||||
表头 = `产品主编号, 链接地址(必填), 产品标题, 价格(RMB)`,47 行数据,再上传。
|
|
||||||
若仍报缺表头,则说明**必须改用服务端最新模板**(可能要在页面上换一个平台/入口重新下载一次模板)。
|
|
||||||
|
|
||||||
### 当前队列状态
|
|
||||||
已导入文件列表里有 3 条失败任务(`ms_import` / `ms_import2` / `ms_nobom`),可在页面上「删除文件」清掉。
|
|
||||||
|
|
||||||
## ✅✅ 导入链接采集终于通了:`ms2.xls` 解析成功 47/0/47(2026-09-21 08:55)
|
|
||||||
|
|
||||||
### 能用的 .xls 表头(**照抄这 8 列**)
|
|
||||||
| 列 | 示例值 | 说明 |
|
|
||||||
|---|---|---|
|
|
||||||
| `产品主编号` | `548459586741` | 1688 商品 id,**必须**有 |
|
|
||||||
| `链接地址(必填)` | `https://detail.1688.com/offer/548459586741.html` | 采集源链接 |
|
|
||||||
| `产品标题` | 英文标题 | 可留空(留空以来源为准) |
|
|
||||||
| `货币类型` | `CNY` | **必须**;空值报「不允许此货币类型」 |
|
|
||||||
| `SKU售价` | `34.0` | **必须 ≥ 0.01**;空值报「必须大于等于0.01」 |
|
|
||||||
| `SKU库存` | `999` | **必须 ≥ 0** |
|
|
||||||
| `价格(RMB)` | `34.0` | 模板原有列 |
|
|
||||||
| `促销价(RMB)` | 空 | 模板原有列(表头提示:仅可用于采集到 Lazada) |
|
|
||||||
|
|
||||||
### 关键结论(下一轮别再走弯路)
|
|
||||||
1. **必须是 `.xls`/`.xlsx` 二进制表格**——CSV 一律只报「未识别到【产品主编号】表头」,`txt` 走 JSON 也走不通;
|
|
||||||
2. **页面下载的模板是旧的**:它只有 4 列、**缺 `产品主编号`**,照它填一定失败
|
|
||||||
(错误提示会写「请下载最新模版」,但最新模板实际要的就是上面这 8 列);
|
|
||||||
3. 生成脚本:`pip install --break-system-packages xlwt` → `xlwt.Workbook(encoding='utf-8')` → 写表头+47 行 →
|
|
||||||
存 `.xls` → 用 `DOM.setFileInputFiles` 注入 `input.jx-upload__input` → 点「确认」;
|
|
||||||
4. 失败时**点「查看失败原因」**能看到逐行逐列的精确原因(这次就是靠它拿到 `货币类型/SKU售价/SKU库存` 三个列名)。
|
|
||||||
|
|
||||||
### 产物
|
|
||||||
`/home/admin9webs/miaoshou/dl/ms2.xls`(47 行,可复用为模板);上传脚本 `ms_upload.mjs`;
|
|
||||||
状态查询 `ms_status.mjs`;失败原因 `ms_reason.mjs`。
|
|
||||||
|
|
||||||
### 下一步
|
|
||||||
「查看导入产品」/ **公用采集箱** 里核对这 47 条采集结果,再验证子账号能否 `认领`/`发布`。
|
|
||||||
|
|
||||||
## 公用采集箱与采集任务(第 3 轮观察)
|
|
||||||
|
|
||||||
### 公用采集箱:`https://erp.91miaoshou.com/common_collect_box/items`
|
|
||||||
页面可用按钮(**均为未禁用状态**):`任务记录` / `搜索` / `重置` / `更多筛选` /
|
|
||||||
**`认领到`** / `平台认领配置` / `批量编辑` / `分组` / `删除` / `更多` / `创建产品` / `导入产品` / `列表设置` / `全选`。
|
|
||||||
|
|
||||||
→ **`认领到` 按钮存在且未禁用**,说明子账号**可能**有认领权限(技能档说「不能发布」,但认领≠发布,
|
|
||||||
需要实测点一次才知道)。列表当前为空(尚未采集出结果)。
|
|
||||||
|
|
||||||
### 任务记录
|
|
||||||
点「任务记录」弹层里表头是 `项目 / 提交时间 / 状态 / 翻译配置 / 任务进度 / 操作`,
|
|
||||||
**当前无数据行** —— 说明这 47 条的采集任务还没出结果(导入只登记了链接,
|
|
||||||
真正的抓取是异步队列,可能要看队列积压或需要显式触发)。
|
|
||||||
|
|
||||||
### 下一步(顺序固定)
|
|
||||||
1. 隔一段时间刷新 `/common_collect_box/items` 与「任务记录」,等 47 条采集进箱;
|
|
||||||
2. 进箱后选一条 → 点 **`认领到`** → 选平台/店铺(`平台认领配置` 里可预设)→ 观察是否被权限拦截;
|
|
||||||
3. 若能认领,再看该平台店铺里能否 **发布**(子账号权限的分水岭)。
|
|
||||||
|
|
||||||
### 复用脚本
|
|
||||||
`ms_box.mjs`(进采集箱 dump 按钮/列表)、`ms_task.mjs`(读任务记录)、
|
|
||||||
`ms_items.mjs`(点「查看导入产品」)、`ms_status.mjs`(导入列表状态)、`ms_reason.mjs`(查看失败原因)。
|
|
||||||
|
|
||||||
## 再查一次(仍为空)+ 三个待验证假设
|
|
||||||
|
|
||||||
复核结果:
|
|
||||||
- 导入任务 `ms2`:**解析成功 47/0/47**(文件层面没问题)
|
|
||||||
- 公用采集箱 `/common_collect_box/items`:**仍为空**(`暂无数据`,行数 0)
|
|
||||||
- 「任务记录」:表头在、**无数据行**
|
|
||||||
- 在导入列表点 **「查看导入产品」**(DOM 点击 + **真实鼠标点击**都试过):
|
|
||||||
**无弹窗、无新标签、URL 不变** —— 这个入口不是「开始采集」的意思
|
|
||||||
|
|
||||||
采集箱的筛选字段是:`关联货源 / 产品标题 / 货源ID / 货源价格`。
|
|
||||||
|
|
||||||
### 下一步优先验证(按可能性排序)
|
|
||||||
1. **关联货源筛选**:采集箱默认可能过滤了货源;把 `关联货源` 选成 **1688**(或「全部」)再看
|
|
||||||
—— 我们这批就是 1688 链接,很可能被默认筛选藏住了;
|
|
||||||
2. **导入文件行的「更多」菜单**:导入列表每行有 `更多` → 看里面是否有「开始采集 / 采集设置 / 认领」之类动作;
|
|
||||||
3. **采集设置**:页面顶部有 `采集设置`,可能要求先配置采集参数/翻译/图片来源,否则任务不启动。
|
|
||||||
|
|
||||||
⚠️ 结论:**导入≠已采集**。`解析成功` 只代表表格被接受,真正抓取需要触发(或等到队列跑)。
|
|
||||||
判定标准只有一个:**公用采集箱里出现 47 条**。
|
|
||||||
|
|
||||||
## 第 4 轮:假设 1 被证伪;触发点在采集箱的「导入产品」
|
|
||||||
|
|
||||||
1. **`关联货源` 筛选不是原因**:控件是 `div.jx-select.pro-select`,
|
|
||||||
当前值就是 **「全部关联货源」** —— 没有任何筛选把数据藏住。
|
|
||||||
所以**这 47 条确实还没被采集进箱**,不是被筛掉了。
|
|
||||||
2. **导入列表的成功行没有触发动作**:`ms2` 行只有 `查看导入产品 / 删除文件` 两个操作
|
|
||||||
(失败行多一个 `查看失败原因`),**没有 `更多` 菜单**。
|
|
||||||
而 `查看导入产品` 实测点不动(DOM 与真实鼠标都试过,无弹窗/无新标签/URL 不变)。
|
|
||||||
3. **因此触发点最可能在「公用采集箱」页里的 `导入产品` 按钮**:
|
|
||||||
该页按钮清单是 `任务记录 / 搜索 / 重置 / 更多筛选 / 认领到 / 平台认领配置 / 批量编辑 /
|
|
||||||
分组 / 删除 / 更多 / 创建产品 / **导入产品** / 列表设置 / 全选` ——
|
|
||||||
「导入产品」很可能就是把已解析的导入文件拉进采集箱(并启动采集)的入口。
|
|
||||||
|
|
||||||
### 下一步(明确到一次点击)
|
|
||||||
进 `/common_collect_box/items` → 点 **`导入产品`** → 看它是否列出 `ms2`(或那 47 条)→
|
|
||||||
选择并确认 → 回到列表看是否出现 47 条 → 然后才是 `认领到` → 发布权限验证。
|
|
||||||
|
|
||||||
## 注意:妙手会话易失 + 登录表单在弹窗里(第 5 轮踩到)
|
|
||||||
- 关掉妙手标签后,新开标签访问受保护页会被重定向到
|
|
||||||
`https://erp.91miaoshou.com/?redirect=%2Fcommon_collect_box%2Fitems` —— **等于登出**,需重新登录。
|
|
||||||
- 登录表单**在一个弹窗里**:先点首页的「登录」把弹窗打开,
|
|
||||||
再填 `.login-form__input`(账号/密码)与 `.captcha-field__input`,最后点 `.login-form__submit-btn`。
|
|
||||||
**顺序错了会拿到 `Cannot read properties of null`**(选择器全空)——本轮我就是这么失败一次。
|
|
||||||
- 验证码每次都要重新读(旧码会失效):取 `.captcha-field__img` 的 base64 → 存 `.jpg` → 读图。
|
|
||||||
- **建议**:需要连续操作妙手时,别关那个标签页;中途关闭会白付一次登录(含验证码)。
|
|
||||||
|
|
||||||
### 「导入产品」按钮:本轮没测到
|
|
||||||
点它之前会话就掉了(登录也未成功),所以**触发点仍未验证**。
|
|
||||||
恢复顺序:重新登录 → `/common_collect_box/items` → 点 `导入产品` → 看是否列出 `ms2` → 确认后回列表数 47 条。
|
|
||||||
|
|
||||||
## 第 6 轮:重新登录失败(提交疑似未触发),建议人工登录
|
|
||||||
- 会话掉线后,我用 §登录步骤 重试了两次(验证码分别读作 `7317`、`0326`):
|
|
||||||
- 填表返回 `{a:1,p:1,k:1,btn:true}`(三个输入都填上了)
|
|
||||||
- 提交后 `location.href` 仍是 `https://erp.91miaoshou.com/`,`input[type=password]` 仍在,
|
|
||||||
**且页面上没有任何错误文案**(`err: []`)
|
|
||||||
- 对比第一次成功那次:当时同代码提交后立刻出现「图形验证码不正确」——说明**提交确实触发过**;
|
|
||||||
现在连错误都没有 → 更可能是**提交事件没打进去**(登录弹窗状态不同/需要真实鼠标事件),
|
|
||||||
而不是验证码读错。
|
|
||||||
- 考虑到页面上那句「**再失败 9 次将锁定账号 1 小时**」,我没有继续盲试。
|
|
||||||
|
|
||||||
### 建议的下一条路
|
|
||||||
**人工登录(最稳)**:在 VNC 里手动登录一次 → 我直接接手已登录会话,
|
|
||||||
去做 `导入产品` 那个触发点的验证(`/common_collect_box/items` → `导入产品` → 看是否列出 `ms2`)。
|
|
||||||
若仍需我自动登录:下一步应改为**真实鼠标点 `.login-form__submit-btn`**(`Input.dispatchMouseEvent`),
|
|
||||||
而不是 DOM `dispatchEvent`——这是本轮与前几轮唯一的行为差异。
|
|
||||||
|
|
||||||
## 第 7 轮:修正上一轮的误判 —— 提交其实**是**触发的,失败原因是验证码读错
|
|
||||||
|
|
||||||
只读诊断(没有再次尝试登录)看到:
|
|
||||||
```
|
|
||||||
按钮: {dis:false, txt:"立即登录"}
|
|
||||||
输入: [{ph:"手机号/子账号/邮箱", val:"xia…"}, {ph:"密码", val:"Bin…"}, {ph:"请输入验证码", val:"…"}] ← 空
|
|
||||||
```
|
|
||||||
**关键**:账号和密码的值**还在**,**验证码框被清空了**。
|
|
||||||
这是典型的「提交已发出并被拒 → 前端清空验证码并刷新图片」行为。
|
|
||||||
所以上一轮我写的「提交疑似未触发」是**错的**:提交一直有触发,
|
|
||||||
失败原因是**验证码读错**(错误提示是短暂 toast,我 12 秒后才查 DOM,已经消失)。
|
|
||||||
|
|
||||||
### 验证码识别的实测命中率(人眼读 base64 原图)
|
|
||||||
| 次数 | 读数 | 结果 |
|
|
||||||
|---|---|---|
|
|
||||||
| 1 | `4821` | ❌ 读错 |
|
|
||||||
| 2 | `0699` | ✅ 通过(唯一一次成功登录) |
|
|
||||||
| 3 | `7317` | ❌ |
|
|
||||||
| 4 | `0326` | ❌ |
|
|
||||||
| 5 | `3620` | ❌ |
|
|
||||||
|
|
||||||
→ **约 1/5**。而登录页写着「再失败 9 次将锁定账号 1 个小时」,所以**不该继续靠人眼读码硬试**。
|
|
||||||
|
|
||||||
### 结论与建议
|
|
||||||
- 要自动化妙手,**必须给验证码配 OCR/视觉能力**:
|
|
||||||
本机 `modlens` 未配置视觉提供方(报 `No vision provider`);
|
|
||||||
舰队技能里另有 `ddddocr` / `captcha-solving` 相关技能可先查。
|
|
||||||
- 或者**人工在 VNC 登录一次**,agent 接手已登录会话(零风险、立刻可用)。
|
|
||||||
- 登录后要立刻做的仍是那件事:`/common_collect_box/items` → 点 **`导入产品`** → 看是否列出 `ms2`。
|
|
||||||
|
|
||||||
## ⚠️ 第 8 轮:子账号报「子账号不存在」(账号侧变化)
|
|
||||||
|
|
||||||
- 今天 08:54(UTC)**同一个子账号 `xiaochaoren2026` 是能登录成功的**(落地页 `/welcome`,标题「妙手-欢迎使用」),
|
|
||||||
证据在导入任务与本次会话记录里。
|
|
||||||
- 之后我做过 4–5 次登录尝试(验证码读错为主),最后一次在 VNC 里由人工输入验证码,
|
|
||||||
报错变成 **「子账号不存在」**(只读核对:表单里的账号/密码就是文档里那一组,验证码框为空)。
|
|
||||||
- **判断**:这不是「验证码错」,也不是网络问题,而是**妙手账号侧的状态变化**——
|
|
||||||
该子账号可能被停用/删除,或连续失败触发了账号级处理。
|
|
||||||
页面上那句「再失败 9 次将锁定账号 1 个小时」只解释「锁定」,解释不了「不存在」。
|
|
||||||
- **我无法从这台机器进一步确认**(要登录进 `子账号管理` 才能查,而登录正是当前卡点)。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
1. **改用主账号登录**(最可能是正解):技能档本来就写着这个子账号「只能采集箱/导入采集、不能发布产品」,
|
|
||||||
而我们的目标里最后一步是「发布」——所以**主账号迟早必须**;
|
|
||||||
2. 或在妙手后台核对该子账号是否还在(需要能登录的账号);
|
|
||||||
3. 或等 1 小时后再试一次(如果其实是锁定被误报成「不存在」,等待即可恢复)。
|
|
||||||
+3
-168
@@ -1,169 +1,4 @@
|
|||||||
# 速卖通(AliExpress POP)「选品建listing」完整配方
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/aliexpress.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
> 2026-09-30 实测跑通:**4/4 进「待发布」,采集箱 0 残留**(RV8I478D / ZQI01T5J / XSSIERBV / XAGOKS9C)。
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
> 目标态 = 待发布(`dxmState=offline & dxmOfflineState=waitPublish`),**绝不点「发布」**。
|
|
||||||
|
|
||||||
## 0. 页面与接口事实
|
|
||||||
|
|
||||||
| 项 | 值 |
|
|
||||||
|---|---|
|
|
||||||
| 创建页 | `https://www.dianxiaomi.com/web/smt/add`(标题「店小秘--添加速卖通产品」) |
|
|
||||||
| 编辑页 | `/web/smt/edit?id=<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 行全空)→
|
|
||||||
**不能**用它判断描述/重量/尺寸是否存进去,只能看保存时有没有报错。别把空值当「没保存」。
|
|
||||||
|
|
||||||
### ⚠ 类目弹窗「点了行但没关窗」→ 后面所有字段都填不进去(2026-09-30 实测)
|
|
||||||
|
|
||||||
`pickCategoryBySearch` 点结果行后**有时弹窗不关**。不关的弹窗会挡住后续所有点击:
|
|
||||||
标题/价格/库存/来源URL 全部写不进去(但类目已选,看起来「像成功了」)——
|
|
||||||
上一版就是这样,保存时报的却是别的字段,或直接存成空字段。
|
|
||||||
**现在两处兜底**:① `closeCategoryModal()` 主动确认并 Esc/点「关闭」强关;
|
|
||||||
② **模型自检** `smtModelGaps()` 会在保存前报出 `subject(标题)/skuPrice(价格)/ipmSkuStock(库存)` 为空 → 跳过保存。
|
|
||||||
实测四个品修完后 `gaps=[]`(title 115–127 / price 5.3–15.9 / stock 999 / desc 1990–3252)。
|
|
||||||
|
|
||||||
## 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 <oid> --dry
|
|
||||||
# ③ 正式建listing(只保存 → 待发布)
|
|
||||||
node $D/platform-smt/smtbatch.mjs --input /tmp/smt_input.json --only <oid>
|
|
||||||
# ④ 回读验收(待发布有没有 + 采集箱有没有本轮新草稿)
|
|
||||||
node $D/platform-smt/verify-landing.mjs --oids <oid1>,<oid2> --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`。
|
|
||||||
|
|
||||||
## 附二:Vue 3 + Pinia 模型层自检(2026-09-30 加,**保存前必做**)
|
|
||||||
|
|
||||||
店小秘所有平台添加页都是 **Vue 3.5.4 + Pinia**(`#app.__vue_app__.config.globalProperties.$pinia`)。
|
|
||||||
**DOM 里显示的值 ≠ 表单模型里的值**;填了 DOM 而模型为空时点保存会「静默失败」或存成空字段。
|
|
||||||
→ 保存前读模型自检:`smtModelSnapshot(c)` + `smtModelGaps(model)`,**gaps 非空则跳过保存**。
|
|
||||||
|
|
||||||
速卖通的字段映射(实测,`smtAddBasicStore.formState` / `smtAddProductStore.formState`):
|
|
||||||
|
|
||||||
| 业务字段 | 模型路径 |
|
|
||||||
|---|---|
|
|
||||||
| 店铺 / 类目 / 标题 | `smtAddBasicStore.formState.{shopId,categoryId,subject}` |
|
|
||||||
| 价格 / **货值** / 库存 / 编码 | `smtAddProductStore.formState.{skuPrice,cargoPrice,ipmSkuStock,skuCode}` |
|
|
||||||
| 重量 / 尺寸(长宽高) | `packageWeight` / `packageLength,packageWidth,packageHeight` |
|
|
||||||
| 是否原箱 | `originalBox`("0"=否) |
|
|
||||||
| **物流属性** | `specialProductTypeList`(普货 = `["1983471271:2025103"]`) |
|
|
||||||
| 图片 | `imageList[]` |
|
|
||||||
| **描述** | `smtAddStore.describeInfoData.detail`(PC端)/ `mobileDetail`(无线端) |
|
|
||||||
|
|
||||||
一次干净的 dry 实测(RV8I478D):
|
|
||||||
```
|
|
||||||
shopId=135223 categoryId=201717001 subjectLen=121
|
|
||||||
skuPrice=6.65 cargoPrice=5.5 ipmSkuStock=999 skuCode=ZSJLQ_1be
|
|
||||||
packageWeight=0.10 dims=28/16/1 originalBox=0
|
|
||||||
specialProductTypeList=["1983471271:2025103"] images=6 descLen=1935 → gaps=[]
|
|
||||||
```
|
|
||||||
结论:**描述确实是写进模型的**(1935 字符,在 `describeInfoData.detail`);**物流属性的 UI 路径也确实会提交到模型**。
|
|
||||||
之前「描述可能没保存」的疑虑到此消除;`pageList` 不返回这些字段只是列表投影问题。
|
|
||||||
|
|
||||||
其它平台 store 命名同构(`<platform>` = smt / shopeeGlobal / tiktokGlobal / ozonProduct):
|
|
||||||
`<platform>AddStore`(编排+描述)· `BasicStore` · `ProductStore` · `AttrStore` · `OtherStore`
|
|
||||||
· `SkuDataStore`/`SkuAttrStore`(变种)· `PackageStore`/`DescStore`(Shopee/Ozon)。
|
|
||||||
公共:`activity` `global` `user` `userPlatforms` `batchImageTaskStore` `*Translate*`。
|
|
||||||
用 `vueStores(c)` 列全部、`readPinia(c, storeId, path)` 读任意字段。
|
|
||||||
+3
-60
@@ -1,61 +1,4 @@
|
|||||||
# 店小秘编辑器「保存静默拦截」诊断法
|
> **本文已迁至内网知识库**:`kb/handbook:docs/ecommerce/dianxiaomi/silent-block-hook.md`(2026-10-21)。
|
||||||
|
> 请到 http://192.168.9.206:3000/kb/handbook 阅读最新版;本仓不再维护这份内容。
|
||||||
|
|
||||||
## 症状
|
(原文件内容已移出,历史见本仓 git 记录。)
|
||||||
点「保存」→ **不发任何 POST**、**没有任何界面提示**、**无 `.ant-form-item-has-error`**。
|
|
||||||
四个平台(速卖通/SHEIN/Temu)独立复现同一现象。
|
|
||||||
|
|
||||||
## 根因
|
|
||||||
校验确实失败了,但报错被 **`MYJ.message.error` 吞掉**——它只在屏幕角落闪一下,
|
|
||||||
而且有些报错是**层层递进**的(修完 A 才显示 B),所以肉眼和 DOM 扫描都抓不到。
|
|
||||||
|
|
||||||
## 诊断步骤
|
|
||||||
1. 打开目标平台的建品页,填完所有能填的字段
|
|
||||||
2. **在点保存之前**注入钩子,劫持消息库:
|
|
||||||
|
|
||||||
```js
|
|
||||||
window.__cap=[];
|
|
||||||
function wrap(obj,name,label){
|
|
||||||
const o=obj[name];
|
|
||||||
obj[name]=function(){
|
|
||||||
window.__cap.push(label+': '+[...arguments].map(a=>typeof a==='string'?a:JSON.stringify(a)).join(' '));
|
|
||||||
return o.apply(obj,arguments);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
['error','warning','info','success'].forEach(k=>wrap(MYJ.message,k,'MYJ.'+k));
|
|
||||||
['error','warning','info','success'].forEach(k=>wrap(antd.message,k,'antd.'+k));
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 点「保存」,等 9 秒
|
|
||||||
4. 读 `window.__cap` —— 被吞掉的真实报错就在里面
|
|
||||||
|
|
||||||
## 已定位的结果
|
|
||||||
| 平台 | 被吞掉的报错 | 性质 |
|
|
||||||
|---|---|---|
|
|
||||||
| 速卖通 | `商品说明书 → 请上传文件!` | **类目相关**的资质文件要求(Pet Medical 等类目强制) |
|
|
||||||
| SHEIN | `有必选规格未选择,请完善后再提交` | 类目定义的必选规格未选全 |
|
|
||||||
| Temu | `产地选择中国时,省份必填` → `产品变种不能为空!` | **条件必填**(选中国才出现省份)+ 变种 |
|
|
||||||
|
|
||||||
## 修复要点
|
|
||||||
- **速卖通**:换一个不要求资质文件的类目即可(已验证:温度计从 Pet Medical 换到
|
|
||||||
`Tools/仪器仪表/温湿度` 后立刻存进去)
|
|
||||||
- **SHEIN**:需把类目要求的**全部**规格(不止主规格颜色)都选中
|
|
||||||
- **Temu**:省份必须走 **UI 选择**(直接改 `region2Id` store 无效),且必须生成变种行
|
|
||||||
|
|
||||||
## 教训
|
|
||||||
`必填项扫描`(找 `.ant-form-item-required` 且为空)**不够**——
|
|
||||||
条件必填字段(Temu 省份)和类目相关的文件字段(速卖通说明书)都不在这个扫描范围内。
|
|
||||||
以后遇到「点了没反应」,第一步就该挂这个钩子。
|
|
||||||
|
|
||||||
## 为什么这条要单独记住(整轮唯一的方法论级收获)
|
|
||||||
在 SHEIN 和 Temu 上,我们花了**几十轮**去猜「到底哪个字段没填」:扫 DOM、翻 store、
|
|
||||||
逐个试填——**全部无效**。挂上钩子后,两个平台的真实报错**一次全暴露**
|
|
||||||
(SHEIN `有必选规格未选择`、Temu `产地…省份必填` → `产品变种不能为空` → … 层层递进)。
|
|
||||||
所以它不是「一个调试技巧」,而是**顺序问题**:先让平台把话说出来,再动手填。
|
|
||||||
|
|
||||||
配套的三条(都是这一轮才踩出来的):
|
|
||||||
1. **类目弹窗没确认关掉就往下走** → 后面的点击全被挡住,最后报的却是别的错
|
|
||||||
(Temu 第一轮 13 条都表现为「请上传预览图」)
|
|
||||||
2. **`.attr-label.required`** 这类类目动态属性(下拉、勾选组)不填,只给一句
|
|
||||||
「产品信息中有错误,请检查」——必填扫描同样扫不到
|
|
||||||
3. **同一个弹窗可能是「单格」的**(Temu 预览图:勾「全部」只作用当前行),
|
|
||||||
N 行要走 N 遍
|
|
||||||
Reference in new issue
Block a user