NEXT-ROUND-PROMPT 更新到下一轮:EAN 回 S03 置顶、剩余5平台/妙手;补 SHEIN≠Temu 规格差异表、cdp3.mjs 备份与丢失重建、钩子方法论说明

This commit is contained in:
admin9webs committed 2026-09-20 19:50:04 +08:00
1 parent fd4cc6619b
commit c69952bd59
4 files changed
+98 -31

No files matched your search

+42 -30
View File
@@ -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">`)→ **弹窗必须确认关掉** | 同一个弹窗,但**按最后一级路径精确匹配**(否则「口哨」会选到「狗口哨」) |
+5 -1
View File
@@ -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`(免密)
## 十五条会重复踩的坑 ## 十五条会重复踩的坑
+37
View File
@@ -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{}}
}
+14
View File
@@ -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 遍