NEXT-ROUND-PROMPT 更新到下一轮:EAN 回 S03 置顶、剩余5平台/妙手;补 SHEIN≠Temu 规格差异表、cdp3.mjs 备份与丢失重建、钩子方法论说明
This commit is contained in:
1 parent
fd4cc6619b
commit
c69952bd59
4 files changed
+98
-31
No files matched your search
+42
-30
@@ -1,7 +1,7 @@
|
|||||||
# 下一轮提示词(整份复制给新会话即可接着干)
|
# 下一轮提示词(整份复制给新会话即可接着干)
|
||||||
|
|
||||||
> ⚠️ **先读 `README.md` 和 `静默拦截诊断/HOOK.md`,再动手。**
|
> ⚠️ **先读 `README.md` 和 `静默拦截诊断/HOOK.md`,再动手。**
|
||||||
> ⭐ **遇到「点了保存没反应」→ 第一件事是挂钩子,不要猜字段**(见下面第 2 节,最显眼那一段)。
|
> ⭐ **遇到「点了保存没反应」→ 第一件事是挂钩子,不要猜字段**(见下面第 2 节,全篇最重要的一段)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
| 文件 | 读它干什么 |
|
| 文件 | 读它干什么 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **`README.md`**(仓库根) | 战果总表、各平台要点速查、通用技术事实、**十条会重复踩的坑**、EAN-13 台账 |
|
| **`README.md`**(仓库根) | 战果总表、各平台要点速查、通用技术事实、**十五条会重复踩的坑**、EAN-13 台账 |
|
||||||
| **`静默拦截诊断/HOOK.md`** | 保存静默拦截的完整诊断步骤 + 已定位的各平台被吞报错 |
|
| **`静默拦截诊断/HOOK.md`** | 保存静默拦截的完整诊断步骤 + 已定位的各平台被吞报错 |
|
||||||
|
|
||||||
仓库(Gitea):`http://192.168.9.206:3000/admin9webs/dianxiaomi-crossborder-playbook`
|
仓库(Gitea):`http://192.168.9.206:3000/admin9webs/dianxiaomi-crossborder-playbook`
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
## 2. ⭐⭐ 最显眼的一条:遇到「点了保存没反应」,第一件事是挂钩子,不要猜字段 ⭐⭐
|
## 2. ⭐⭐ 最显眼的一条:遇到「点了保存没反应」,第一件事是挂钩子,不要猜字段 ⭐⭐
|
||||||
|
|
||||||
|
**为什么单独占一节**:这是整轮唯一一条**方法论级别**的收获。之前在 SHEIN 和 Temu 上
|
||||||
|
反复猜「到底哪个字段没填」,几十轮全部无效;挂上钩子后**两个平台的真报错一次全暴露**。
|
||||||
|
放在这个位置,是为了让新会话在**踩坑之前**就看见,而不是踩完才翻到。
|
||||||
|
|
||||||
**症状**:点「保存」→ **不发 POST**、**无界面提示**、**无 `.ant-form-item-has-error`**。
|
**症状**:点「保存」→ **不发 POST**、**无界面提示**、**无 `.ant-form-item-has-error`**。
|
||||||
**根因**:校验确实失败了,但报错被 **`MYJ.message.error` 吞掉**;而且报错是**层层递进**的
|
**根因**:校验确实失败了,但报错被 **`MYJ.message.error` 吞掉**;而且报错是**层层递进**的
|
||||||
(修完 A 才显示 B),肉眼和 DOM 扫描都抓不到。
|
(修完 A 才显示 B),肉眼和 DOM 扫描都抓不到。
|
||||||
@@ -35,12 +39,12 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a
|
|||||||
|
|
||||||
然后 **点保存 → 等 ~9 秒 → 读 `window.__cap`**,被吞掉的真实报错就在里面。
|
然后 **点保存 → 等 ~9 秒 → 读 `window.__cap`**,被吞掉的真实报错就在里面。
|
||||||
|
|
||||||
- 现成脚本:`静默拦截诊断/shhook.mjs`(SHEIN)、`静默拦截诊断/tmhook.mjs`(Temu)——
|
- 现成脚本:`静默拦截诊断/shhook.mjs`(SHEIN)、`静默拦截诊断/tmhook.mjs`(Temu);
|
||||||
除了钩子还会把 `.ant-form-item-explain-error` 的行内错误一起打出来。
|
批量脚本里用 `shein建品/sh_lib.mjs` 的 `installHook(c)` / `readHook(c)`。
|
||||||
- **「必填项扫描」不够**:扫 `.ant-form-item-required` 且为空,会漏掉**条件必填**
|
- **「必填项扫描」不够**:扫 `.ant-form-item-required` 且为空,会漏掉**条件必填**
|
||||||
(Temu 的省份,只有「产地=中国」才出现)和**类目相关的文件字段**
|
(Temu 的省份,只有「产地=中国」才出现)、**类目相关的文件字段**(速卖通的商品说明书)
|
||||||
(速卖通的商品说明书,有些类目强制、有些不强制)。
|
和 **`.attr-label.required` 这类类目动态属性**(SHEIN 的「特征 Features」,不填只报
|
||||||
- 上一轮在 SHEIN 和 Temu 上盲试了几十轮没进展,挂上钩子后**两个平台一次全暴露**。
|
「产品信息中有错误,请检查」)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -51,41 +55,49 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a
|
|||||||
|
|
||||||
## 4. 已完成
|
## 4. 已完成
|
||||||
|
|
||||||
TikTok 47 / Shopee 47 / 速卖通 47 / Walmart 46 条已在待发布;Ozon 47 条已建、改价 40/47。
|
- TikTok 47 / Shopee 47 / 速卖通 47 / Walmart 46 / Ozon 47(改价 40/47)
|
||||||
各平台的配方脚本、输入数据、逐条日志都在仓库对应目录里。
|
- **SHEIN 40/40**(`shein建品/shbatch.mjs`,按 `shein_route.json` 分 4 家店;1 条因
|
||||||
|
①Shein-武汉九个网-箱包 报「商家无可用的发品额度」改挂 ②Shein-易世通达-箱包)
|
||||||
|
- **Temu 全托管 47**(`temu建品/tmbatch.mjs` 批量 46/46 + 手工首条),逐条结果在 `tm_done.json`
|
||||||
|
|
||||||
## 5. 这轮要做的(按优先级)
|
## 5. 这轮要做的(按优先级)
|
||||||
|
|
||||||
1. **Temu 解「请上传预览图」**
|
0. **EAN 台账回 S03(最优先,硬约束)**
|
||||||
Temu 的报错是层层递进的,前面 4 层我已经解掉了(省份 → 变种 → 尺寸 → 净重<重量),
|
Temu 这 46 个号目前只落在 KVM-2 的 `temu建品/ean_temu_used.txt`
|
||||||
现在只剩 `MYJ.error: 请上传预览图`。SKU 表「预览图」列需要**每个变种行的真实图片**,
|
(pc 3447~3551,游标 `ean_cursor.json` → **3552**)。KVM-2 够不到 S03,
|
||||||
不是文本。大概率是点单元格弹图片空间。参考 Walmart 的解法:**hover 触发的下拉要先
|
**需要在 S03 上把这份台账并进 `/root/ean_*.txt`**,之后从 3552 续号。
|
||||||
`Input.dispatchMouseEvent` 发 `mouseMoved` 再 `click`**。
|
|
||||||
Temu 的规格是 `.theme-value-add` 块(值输入 `input[class*="!w-200"]` + 添加按钮),
|
|
||||||
**必须手动输入值再点添加**,直接改 store 无效。
|
|
||||||
解出来后把 `temu建品/` 的配方补完。
|
|
||||||
|
|
||||||
2. **SHEIN 批量**
|
1. **继续铺剩余店小秘平台**:Joom(84 家)、Lazada(24 家)、aliChoice(64 家)、
|
||||||
`shein建品/RECIPE.md` 已经有跑通第一条的完整步骤。把它写成 `shbatch.mjs` 循环跑 40 条,
|
pddkj(15 家)、fruugo(3 家)。店铺用**字母序/圈号自然序第一家**
|
||||||
按品类分到 4 家店(美妆 18 / 文具 13 / 箱包 8 / 内睡袜子 1),路由见 `shein_route.json`。
|
(除 SHEIN 那种锁品类的)。这 5 个平台的字段落点和第一家店规则都还没勘,属于新探路。
|
||||||
**注意顺序**:先选规格 → 再填 SKU 行 → 再填尺码表(加规格会重建 SKU 行)。
|
|
||||||
|
|
||||||
3. **Temu 批量**(解完预览图之后)
|
2. **妙手各平台**(店小秘全部完成后)
|
||||||
|
|
||||||
4. **继续铺剩余店小秘平台**:Joom(84 家)、Lazada(24 家)、aliChoice(64 家)、
|
3. **可选重构**:`shein建品/sh_lib.mjs` + `sh_build.mjs`(`newPage`/`selectCategory`/
|
||||||
pddkj(15 家)、fruugo(3 家)。店铺用**字母序/圈号自然序第一家**(除 SHEIN 那种锁品类的)。
|
`fillRequiredSelects`/`installHook`)已是平台无关骨架,铺新平台先复用它,
|
||||||
|
别再从头写 `attach(url)`——同 URL 有多个标签时 `attach` 会抢到错的那个。
|
||||||
5. **妙手各平台**(店小秘全部完成后)
|
|
||||||
|
|
||||||
## 6. 硬约束
|
## 6. 硬约束
|
||||||
|
|
||||||
- 平台「首字母第一家店」规则(SHEIN 除外,它锁品类必须分店)
|
- 平台「首字母第一家店」规则(SHEIN 除外,它锁品类必须分店)
|
||||||
- 图片一律**外链 alicdn**,且必须**无水印**
|
- 图片一律**外链 alicdn**,且必须**无水印**
|
||||||
- **EAN 台账在 S03 的 `/root/ean_*.txt`**,KVM-2 够不到。本批已分配 pc 3400~3446(Walmart),
|
- **EAN 台账在 S03 的 `/root/ean_*.txt`**,KVM-2 够不到。Walmart 已用 pc 3400~3446,
|
||||||
Temu 用 3447 起。**用了就要在 S03 落盘**,否则撞号。
|
Temu 已用 pc 3447~3551(游标 3552)。**用了就要在 S03 落盘**,否则撞号。
|
||||||
|
|
||||||
## 7. 环境
|
## 7. 环境
|
||||||
|
|
||||||
- Chrome CDP **9223**,profile `/tmp/dxm7`;VNC `http://192.168.9.122:6097/vnc.html`
|
- Chrome CDP **9223**,profile `/tmp/dxm7`;VNC `http://192.168.9.122:6097/vnc.html`
|
||||||
- CDP helper `/tmp/cdp3.mjs`(ESM,`newTarget`/`attach`/`CDP`/`SAN`,**不能用 require**)
|
- CDP helper `/tmp/cdp3.mjs`(ESM:`newTarget`/`attach`/`CDP`/`SAN`,**不能用 require**)
|
||||||
- 批量跑动辄 1 小时,用 `nohup ... &` 后台跑 + 逐条日志,别前台等
|
⚠️ **它是 /tmp 下的临时文件,重启机器可能就没了**——仓库根目录留了备份 `cdp3.mjs`,
|
||||||
|
丢了就 `cp cdp3.mjs /tmp/cdp3.mjs` 再跑。
|
||||||
|
- 批量动辄 1 小时:`setsid nohup node xxx.mjs > xxx.log 2>&1 < /dev/null &`;
|
||||||
|
脚本自己往 `*_done.json` 写断点,失败直接带 pid 重跑(`node shbatch.mjs <pid>`)。
|
||||||
|
|
||||||
|
## 8. 机械差异,别互套(SHEIN ≠ Temu)
|
||||||
|
|
||||||
|
| | SHEIN | Temu 全托管 |
|
||||||
|
|---|---|---|
|
||||||
|
| 规格控件 | `.theme-select-box` 点主题 → `.sku-info-box` 里勾值 | 两个 `.theme-value-add` 块,`input[class*="!w-200"]` **手动输值 + 点「添加」**(直接改 store 无效) |
|
||||||
|
| 规格数量 | **最多 3 个**(颜色已占 1 个) | 颜色 + 尺码各一个值 |
|
||||||
|
| 图片 | store 直接吃外链数组 | 主图**必须满 5 张**;预览图**逐行**点 `.sku-image` → 引用产品轮播图 → 全部 → 选择 |
|
||||||
|
| 类目选择 | 弹窗搜索 → 点 `.search-result-item`(里面嵌 `<span class="f-red">`)→ **弹窗必须确认关掉** | 同一个弹窗,但**按最后一级路径精确匹配**(否则「口哨」会选到「狗口哨」) |
|
||||||
@@ -67,7 +67,11 @@ function wrap(o, n, l) {
|
|||||||
- Temu 全托管保存接口 `/api/pddkjProduct/add*.json`(拼多多跨境 = Temu 全托管)
|
- Temu 全托管保存接口 `/api/pddkjProduct/add*.json`(拼多多跨境 = Temu 全托管)
|
||||||
- 站点是 Vue3 + Pinia:
|
- 站点是 Vue3 + Pinia:
|
||||||
`document.querySelector('#app').__vue_app__.config.globalProperties.$pinia._s.get('<store>').$state`
|
`document.querySelector('#app').__vue_app__.config.globalProperties.$pinia._s.get('<store>').$state`
|
||||||
- CDP helper:`cdp3.mjs`(`newTarget`/`attach`/`CDP`/`SAN`),ESM,**不能用 require**
|
- CDP helper:`cdp3.mjs`(`newTarget`/`attach`/`closeAllTabs`/`CDP`/`SAN`),ESM,**不能用 require**
|
||||||
|
- 平时跑在 `/tmp/cdp3.mjs`,**是 /tmp 下的临时文件,重启机器会丢** → 仓库根目录有备份,
|
||||||
|
丢了 `cp cdp3.mjs /tmp/cdp3.mjs` 即可
|
||||||
|
- ⚠️ 同一个 URL 开了多个标签时,`attach(url)` 会抢到**最后一个**匹配的标签。
|
||||||
|
批量脚本请用 `shein建品/sh_lib.mjs` 的 `newPage(url)`:按 target 直连,不会抢错
|
||||||
- VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密)
|
- VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密)
|
||||||
|
|
||||||
## 十五条会重复踩的坑
|
## 十五条会重复踩的坑
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const BASE = process.env.CDP_BASE || 'http://127.0.0.1:9223';
|
||||||
|
export const SAN = s => (s||'').replace(/data:[a-z+/-]+;base64,[A-Za-z0-9+/=\s]{40,}/gi,'<IMG>').replace(/[A-Za-z0-9+/]{100,}={0,2}/g,'<B64>').replace(/\s+/g,' ').trim();
|
||||||
|
export async function newTarget(url='about:blank'){
|
||||||
|
const r=await fetch(`${BASE}/json/new?${encodeURIComponent(url)}`,{method:'PUT'});
|
||||||
|
return r.ok? r.json() : (await fetch(`${BASE}/json/new?${encodeURIComponent(url)}`)).json();
|
||||||
|
}
|
||||||
|
export async function attach(match){
|
||||||
|
const list=await (await fetch(`${BASE}/json/list`)).json();
|
||||||
|
const ps=list.filter(t=>t.type==='page'&&(!match||t.url.includes(match)));
|
||||||
|
if(!ps.length) return null;
|
||||||
|
const c=await CDP.connect(ps[ps.length-1].webSocketDebuggerUrl);
|
||||||
|
await c.send('Page.enable'); await c.send('Runtime.enable'); return c;
|
||||||
|
}
|
||||||
|
export async function closeAllTabs(){
|
||||||
|
const list=await (await fetch(`${BASE}/json/list`)).json();
|
||||||
|
for(const t of list.filter(x=>x.type==='page')) await fetch(`${BASE}/json/close/${t.id}`).catch(()=>{});
|
||||||
|
}
|
||||||
|
export class CDP{
|
||||||
|
constructor(ws){this.ws=ws;this.id=0;this.pending=new Map();this.handlers=[];}
|
||||||
|
static async connect(u){const ws=new WebSocket(u);await new Promise((s,j)=>{ws.onopen=s;ws.onerror=j;});
|
||||||
|
const c=new CDP(ws);
|
||||||
|
ws.onmessage=ev=>{const m=JSON.parse(ev.data);
|
||||||
|
if(m.id&&c.pending.has(m.id)){const{res,rej}=c.pending.get(m.id);c.pending.delete(m.id);
|
||||||
|
m.error?rej(new Error(JSON.stringify(m.error))):res(m.result);}
|
||||||
|
else if(m.method){for(const h of c.handlers)h(m);}};
|
||||||
|
return c;}
|
||||||
|
send(method,params={}){const id=++this.id;
|
||||||
|
return new Promise((res,rej)=>{this.pending.set(id,{res,rej});
|
||||||
|
this.ws.send(JSON.stringify({id,method,params}));
|
||||||
|
setTimeout(()=>{if(this.pending.has(id)){this.pending.delete(id);rej(new Error('timeout '+method));}},60000);});}
|
||||||
|
on(f){this.handlers.push(f);}
|
||||||
|
async evalJS(e,aw=true){const r=await this.send('Runtime.evaluate',{expression:e,returnByValue:true,awaitPromise:aw});
|
||||||
|
if(r.exceptionDetails)throw new Error(JSON.stringify(r.exceptionDetails).slice(0,220));return r.result?.value;}
|
||||||
|
async shot(p){const r=await this.send('Page.captureScreenshot',{format:'jpeg',quality:80});
|
||||||
|
const fs=await import('fs'); fs.writeFileSync(p,Buffer.from(r.data,'base64')); return p;}
|
||||||
|
close(){try{this.ws.close();}catch{}}
|
||||||
|
}
|
||||||
@@ -45,3 +45,17 @@ function wrap(obj,name,label){
|
|||||||
`必填项扫描`(找 `.ant-form-item-required` 且为空)**不够**——
|
`必填项扫描`(找 `.ant-form-item-required` 且为空)**不够**——
|
||||||
条件必填字段(Temu 省份)和类目相关的文件字段(速卖通说明书)都不在这个扫描范围内。
|
条件必填字段(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