店小秘多平台铺货配方:7 平台建品脚本 + 静默拦截诊断法 + 项目状态

- TikTok/Shopee/速卖通/Walmart 各 46-47 条已在待发布
- Ozon 47 条已建、改价 40/47
- SHEIN 配方跑通第一条;Temu 差「预览图」一步
- 核心产出:MYJ.message.error 钩子诊断法(解开了三个平台的保存静默拦截)
- 含下一轮提示词 NEXT-ROUND-PROMPT.md
This commit is contained in:
admin9webs committed 2026-09-20 17:29:38 +08:00
commit f5720ad613
94 files changed
+21561

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
*_logs/
*_log/
*.jpeg
*.jpg
*.png
+57
View File
@@ -0,0 +1,57 @@
# 下一轮提示词(直接复制给新会话)
---
我在做「同一批 48 个 1688 商品铺到店小秘各平台待发布」的活儿。上一轮的完整配方和踩坑记录
已经推到 Gitea:`http://192.168.9.206:3000/admin9webs/dianxiaomi-crossborder-playbook`
(本机也有一份:`/home/admin9webs/gitea-push/dianxiaomi-crossborder-playbook/`)。
**先读仓库的 `README.md` 和 `静默拦截诊断/HOOK.md`**,再动手。
## 已完成
TikTok 47 / Shopee 47 / 速卖通 47 / Walmart 46 条已在待发布;Ozon 47 条已建、改价 40/47。
各平台配方脚本、输入数据、逐条日志都在仓库对应目录里。
## 这轮要做的(按优先级)
1. **Temu 解「请上传预览图」**
Temu 的报错是层层递进的,前面 4 层我已经解掉了(省份 → 变种 → 尺寸 → 净重<重量),
现在只剩 `MYJ.error: 请上传预览图`。SKU 表「预览图」列需要**每个变种行的真实图片**,
不是文本。大概率是点单元格弹图片空间。参考 Walmart 的解法:**hover 触发的下拉要先
`Input.dispatchMouseEvent` 发 `mouseMoved` 再 `click`**。
Temu 的规格是 `.theme-value-add` 块(值输入 `input[class*="!w-200"]` + 添加按钮),
**必须手动输入值再点添加**,直接改 store 无效。
解出来后把 `temu建品/` 的配方补完。
2. **SHEIN 批量**
`shein建品/RECIPE.md` 已经有跑通第一条的完整步骤。把它写成 `shbatch.mjs` 循环跑 40 条,
按品类分到 4 家店(美妆 18 / 文具 13 / 箱包 8 / 内睡袜子 1),路由见 `shein_route.json`。
**注意顺序**:先选规格 → 再填 SKU 行 → 再填尺码表(加规格会重建 SKU 行)。
3. **Temu 批量**(解完预览图之后)
4. **继续铺剩余店小秘平台**:Joom(84 家)、Lazada(24 家)、aliChoice(64 家)、
pddkj(15 家)、fruugo(3 家)。店铺用**字母序/圈号自然序第一家**(除 SHEIN 那种锁品类的)。
5. **妙手各平台**(店小秘全部完成后)
## 硬约束
- 平台「首字母第一家店」规则(SHEIN 除外,它锁品类必须分店)
- 图片一律**外链 alicdn**,且必须**无水印**
- **EAN 台账在 S03 的 `/root/ean_*.txt`**,KVM-2 够不到。本批已分配 pc 3400~3446(Walmart),
Temu 用 3447 起。**用了就要在 S03 落盘**,否则撞号。
## 遇到「点了保存没反应」怎么办
**第一件事是挂钩子**,不要猜字段:
```js
window.__cap=[];
function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...arguments].map(a=>typeof a==='string'?a:JSON.stringify(a)).join(' '));return f.apply(o,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));
```
点保存 → 读 `window.__cap`。上一轮我在 SHEIN 和 Temu 上盲试了几十轮没进展,
挂上钩子后**两个平台一次全暴露**。
## 环境
- 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`)
- 批量跑动辄 1 小时,用 `nohup ... &` 后台跑 + 逐条日志,别前台等
+93
View File
@@ -0,0 +1,93 @@
# 店小秘多平台铺货配方(dianxiaomi crossborder playbook)
同一批 **48 个 1688 商品**铺到店小秘各平台的「待发布」。本仓库是**可复用的配方 + 踩坑记录**,
不是一次性脚本堆积——每个平台一个目录,含配方脚本、输入数据、逐条结果。
## 战果
| 平台 | 店铺 | 待发布 | 状态 |
|---|---|---|---|
| TikTok | AutoMavenShop_MY | **47** | 完成 |
| Shopee | Ljbgkj | **47** | 完成 |
| 速卖通 | 4024039 | **47** | 完成(含清理 4 份重复) |
| Walmart | Snowmountain | **46** | 完成 |
| Ozon | Yejet | **47** | 已建;改价 40/47 |
| SHEIN | 4 家分品类 | 0 | 配方跑通第一条,40 条待批量 |
| Temu 全托管 | ①Alimingpro | 0 | 差「预览图」一步 |
> `601964534137`(遥控器保护套)18 张图全带水印、无干净图,所有平台统一跳过。
## ⭐ 最重要的一节:保存静默拦截诊断法
**症状**:点「保存」→ 不发 POST、无界面提示、无 `.ant-form-item-has-error`。
**根因**:校验确实失败了,但报错被 **`MYJ.message.error` 吞掉**;而且报错是**层层递进**的
(修完 A 才显示 B),所以肉眼和 DOM 扫描都抓不到。
**解法**——在点保存**之前**挂钩子:
```js
window.__cap = [];
function wrap(o, n, l) {
const f = o[n];
o[n] = function () {
window.__cap.push(l + ': ' + [...arguments].map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' '));
return f.apply(o, 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));
```
点保存 → 读 `window.__cap`。
**为什么之前的「必填扫描」不够**:扫 `.ant-form-item-required` 且为空,漏掉两类——
**条件必填**(Temu 的省份,只有「产地=中国」才出现)和**类目相关的文件字段**
(速卖通的商品说明书,有些类目强制、有些不强制)。
详见 `静默拦截诊断/HOOK.md`。
## 各平台要点速查
| 平台 | 目录 | 最独特的一点 |
|---|---|---|
| Ozon | `ozon改价/` | 保存按钮是**「保存并移入待发布」**;价格是 **CNY**;改价必须同时抬高原价,否则报「售价需小于原价」 |
| TikTok | `tiktok建品/` | 价格 **MYR**;图片 store 直接吃**外链数组** |
| Shopee | `shopee建品/` | 用**单件模式**绕开变种;价格 **CNY**;列表字段是 `itemName` 不是 title |
| Walmart | `walmart建品/` | 保存体是 **zip 的 multipart**;Brand 必须点「无品牌」;图片必须 **hover** 开下拉走「网络图片」 |
| 速卖通 | `速卖通建品/` | `check_box` 属性值**必须是数组**;`idStr ≠ id` |
| SHEIN | `shein建品/` | 店铺**锁品类**;类目要**两个规格**;**加规格会重建 SKU 行** |
| Temu | `temu建品/` | 类目 1038 个从 store 直读;规格是 `.theme-value-add`(与 SHEIN 不同构) |
## 通用技术事实
- Chrome CDP 端口 **9223**,profile `/tmp/dxm7`
- 站点是 Vue3 + Pinia:
`document.querySelector('#app').__vue_app__.config.globalProperties.$pinia._s.get('<store>').$state`
- CDP helper:`cdp3.mjs`(`newTarget`/`attach`/`CDP`/`SAN`),ESM,**不能用 require**
- VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密)
## 十条会重复踩的坑
1. **保存静默拦截** → 挂钩子,别猜
2. **`idStr ≠ id`** → 大整数精度丢失,服务端操作一律用 `idStr`
3. **`!w-200` 不是合法 CSS 类选择器** → 用 `input[class*="!w-200"]`
4. **antd select 吃 DOM click**,但有些组件只吃真实鼠标事件
5. **hover 触发的下拉**点不开 → 先 `mouseMoved` 再 `click`
6. **加规格会重建 SKU 行** → 顺序「规格 → SKU → 尺码表」
7. **store 赋值后 UI 异步同步** → 当次校验可能仍失败,再点一次保存
8. **类目决定是否强制资质文件**(速卖通某些类目要上传说明书)
9. **SHEIN 店铺锁品类** → 不能用「首字母第一家店」
10. `pkill -f <pat>` 会自杀 → 用 `[.]` 技巧(`pgrep -f 'foo[.]sh'`)
## EAN-13 台账(湖北易千亿彩 · 69803093 段)
- 结构:8 位前缀 `69803093` + 4 位产品码 + 1 位校验
- 校验位:`(10 - sum(d*(1 if i%2==0 else 3) for i,d in enumerate(d12)) % 10) % 10`
(左起权重 1,3,1,3…;UPC-A 权重相反,别混用)
- 台账唯一来源:**S03 主机 `/root/ean_*.txt`**(KVM-2 够不到)
- 已用 3190,下一号 pc 3400 → `6980309334003`
- **本批已分配**:Walmart pc **3400~3446**(见 `walmart建品/ean_69803093_3400-3446.txt`);
Temu 拟用 pc **3447+**
- ⚠️ **必须在 S03 落盘**,否则下次续号会撞号
- 说明书 §1 的例子 `pc 1000 → 6980309310004` **是错的**,正确是 `6980309310007`
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""EAN-13 生成本地复算(湖北易千亿彩 · 69803093 段)
结构: 8位前缀 69803093 + 4位产品码 + 1位校验
校验: 前12位左起权重 1,3,1,3... ; check=(10-sum%10)%10"""
def ean13_check(d12: str) -> int:
total = sum(int(d) * (1 if i % 2 == 0 else 3) for i, d in enumerate(d12))
return (10 - total % 10) % 10
def make(pc: int) -> str:
d12 = "69803093" + f"{pc:04d}"
return d12 + str(ean13_check(d12))
if __name__ == "__main__":
# 1) 复算文档里的例子,验证算法
print("=== 算法自检 ===")
for pc, expect in ((2900, "6980309329009"), (3400, "6980309334003"), (9999, "6980309399996")):
got = make(pc)
print(f" pc {pc:>4} → {got} {'✅' if got == expect else '❌ 期望 ' + expect}")
doc_wrong = make(1000)
print(f" pc 1000 → {doc_wrong} (文档写 6980309310004,实为 {doc_wrong} ← 文档该例有误)")
print(f" pc 0000 → {make(0)}")
print()
# 2) 为 Walmart 分配 47 个(pc 3400..3446)
START, N = 3400, 47
codes = [make(START + i) for i in range(N)]
assert len(set(codes)) == N, "批内重复"
for c in codes:
assert ean13_check(c[:12]) == int(c[12]), f"校验位错 {c}"
print(f"=== 分配 pc {START}~{START+N-1} 共 {N} 个(校验位全部复核通过)===")
for i, c in enumerate(codes):
print(f" {START+i:>4} {c}")
open("/home/admin9webs/walmart建品/ean_69803093_%d-%d.txt" % (START, START + N - 1), "w").write("\n".join(codes) + "\n")
print(f"\n已落盘: ean_69803093_{START}-{START+N-1}.txt")
print(f"下一号应为 pc {START+N} → {make(START+N)};剩余 {9999-(START+N)+1} 个")
File diff suppressed because it is too large. Load diff
+37
View File
@@ -0,0 +1,37 @@
import json, collections
j=json.load(open('/tmp/oz_draft_all.json')); items=j['items']
RATE=1500/113.0
MARGINS=[0,0.13,0.15,0.20,0.25,0.30,0.35,0.40]
def F(g): return 4.2+0.045*g
def price(b,g,r): return (b+F(g))/(1-0.2-r) if r<0.8 else None
out={}
stats=collections.Counter()
for p in items:
sp=p['vars'][0].get('sp'); eu=p['eu']
b=float(sp) if sp else (float(eu)*6.68 if eu else None)
w=max([float(v.get('w') or 0) for v in p['vars']] or [0])
key=p['oid'] or p['id']
tier = 'extra_small_1-500g' if 0<w<=500 else ('budget_500-2500g' if 500<w<=2500 else ('small_1-2000g' if 2000<w else ('UNKNOWN_w0' if w==0 else 'other')))
stats[('tier',tier)]+=1
if b is None: stats[('nocost',tier)]+=1
rec={'oid':p['oid'],'id':p['id'],'name':p['name'],'cost_cny':b,'weight_g':w,'tier':tier,
'freight_cny':round(F(w),2),'total_cost_cny':round(b+F(w),2) if b is not None else None,
'price_cny':{f'{int(r*100)}%':round(price(b,w,r),2) for r in MARGINS} if b is not None else None,
'price_rub':{f'{int(r*100)}%':round(price(b,w,r)*RATE,1) for r in MARGINS} if b is not None else None}
out.setdefault(key,[]).append(rec)
json.dump(out,open('/tmp/oz_price_500.json','w'),ensure_ascii=False,indent=1)
print('=== 分层统计 ===')
for k,v in sorted(stats.items()):
if k[0]=='tier': print(f' {k[1]:<20} {v}')
print('=== 缺成本 ===')
for k,v in sorted(stats.items()):
if k[0]=='nocost': print(f' {k[1]:<20} {v}')
es=[r for v in out.values() for r in v if r['tier']=='extra_small_1-500g']
print(f'\n≤500g 档: {len(es)} 条;其中成本齐全 {sum(1 for r in es if r["cost_cny"] is not None)}')
print('\n=== ≤500g 定价阶梯(含运费, 单位¥ / ₽) ===')
for b in (5,10,20,30):
print(f'\n 进货价 {b}元')
print(' 重量 | '+' | '.join(f'{int(r*100):>3}%' for r in MARGINS))
for g in (50,100,200,300,400,500):
cells=' | '.join(f'{price(b,g,r):5.1f}' for r in MARGINS)
print(f' {g:>4}g | {cells} → 25%档 {price(b,g,0.25)*RATE:6.0f}₽')
+320
View File
@@ -0,0 +1,320 @@
{
"708420302844": {
"price": "36.45",
"readback": "API 回读: {\"n\":12,\"prices\":[43.74,43.74,43.74],\"sales\":[36.45,36.45,36.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
36.45,
36.45,
36.45
]
},
"547183997437": {
"price": "36.45",
"readback": "API 回读: {\"n\":1,\"prices\":[43.74],\"sales\":[36.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
36.45
]
},
"657617635303": {
"price": "36.45",
"readback": "API 回读: {\"n\":5,\"prices\":[43.74,43.74,43.74],\"sales\":[36.45,36.45,36.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
36.45,
36.45,
36.45
]
},
"641673262387": {
"price": "36.45",
"readback": "API 回读: {\"n\":32,\"prices\":[43.74,43.74,43.74],\"sales\":[36.45,36.45,36.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
36.45,
36.45,
36.45
]
},
"764423265986": {
"price": "34.0",
"readback": "{\"n\":2,\"prices\":[2.81,2.81],\"sales\":[2.81,2.81]}",
"save": "[]",
"ok": false,
"sales": [
2.81,
2.81
]
},
"735147938123": {
"price": "34.0",
"readback": "API 回读: {\"n\":1,\"prices\":[40.8],\"sales\":[34]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
34.0
]
},
"548459586741": {
"price": "34.0",
"readback": "API 回读: {\"n\":1,\"prices\":[40.8],\"sales\":[34]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
34.0
]
},
"610164857063": {
"price": "34.0",
"readback": "API 回读: {\"n\":10,\"prices\":[40.8,40.8,40.8],\"sales\":[34,34,34]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
34.0,
34.0,
34.0
]
},
"676236596821": {
"price": "34.0",
"readback": "API 回读: {\"n\":1,\"prices\":[40.8],\"sales\":[34]}",
"save": "保存请求: [\"POST\"]",
"ok": true,
"sales": [
34.0
]
},
"866537106657": {
"price": "34.0",
"readback": "{\"n\":9,\"prices\":[1.14,1.27,1.27],\"sales\":[1.14,1.27,1.27]}",
"save": "[]",
"ok": false
},
"591994902720": {
"price": "31.91",
"readback": "API 回读: {\"n\":4,\"prices\":[38.3,38.3,38.3],\"sales\":[31.91,31.91,31.91]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"836512561982": {
"price": "30.2",
"readback": "API 回读: {\"n\":4,\"prices\":[36.24,36.24,36.24],\"sales\":[30.2,30.2,30.2]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"714492799386": {
"price": "28.36",
"readback": "API 回读: {\"n\":7,\"prices\":[34.04,34.04,34.04],\"sales\":[28.36,28.36,28.36]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"902440558099": {
"price": "27.27",
"readback": "API 回读: {\"n\":1,\"prices\":[32.73],\"sales\":[27.27]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"823838532886": {
"price": "27.0",
"readback": "API 回读: {\"n\":6,\"prices\":[32.4,32.4,32.4],\"sales\":[27,27,27]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"661190598139": {
"price": "26.64",
"readback": "API 回读: {\"n\":28,\"prices\":[31.97,31.97,31.97],\"sales\":[26.64,26.64,26.64]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"685456757807": {
"price": "26.64",
"readback": "API 回读: {\"n\":26,\"prices\":[31.97,31.97,31.97],\"sales\":[26.64,26.64,26.64]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"893592264148": {
"price": "26.45",
"readback": "API 回读: {\"n\":6,\"prices\":[31.74,31.74,31.74],\"sales\":[26.45,26.45,26.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"639409307567": {
"price": "25.82",
"readback": "{\"n\":2,\"prices\":[5.48,5.48],\"sales\":[5.48,5.48]}",
"save": "[]",
"ok": false
},
"896469995553": {
"price": "25.18",
"readback": "API 回读: {\"n\":2,\"prices\":[30.22,30.22],\"sales\":[25.18,25.18]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"719391062058": {
"price": "23.71",
"readback": "API 回读: {\"n\":6,\"prices\":[28.46,28.46,28.46],\"sales\":[23.71,23.71,23.71]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"698782391248": {
"price": "23.6",
"readback": "API 回读: {\"n\":1,\"prices\":[28.32],\"sales\":[23.6]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"823560564174": {
"price": "23.11",
"readback": "API 回读: {\"n\":9,\"prices\":[27.74,27.74,27.74],\"sales\":[23.11,23.11,23.11]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"857781294719": {
"price": "22.73",
"readback": "API 回读: {\"n\":3,\"prices\":[27.28,27.28,27.28],\"sales\":[22.73,22.73,22.73]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"707826905566": {
"price": "22.55",
"readback": "API 回读: {\"n\":4,\"prices\":[27.06,27.06,27.06],\"sales\":[22.55,22.55,22.55]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"1027332215847": {
"price": "22.55",
"readback": "API 回读: {\"n\":10,\"prices\":[27.06,27.06,27.06],\"sales\":[22.55,22.55,22.55]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"607330049419": {
"price": "22.24",
"readback": "API 回读: {\"n\":9,\"prices\":[26.69,26.69,26.69],\"sales\":[22.24,22.24,22.24]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"944470567338": {
"price": "22.18",
"readback": "API 回读: {\"n\":2,\"prices\":[26.62,26.62],\"sales\":[22.18,22.18]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"787261097583": {
"price": "22.18",
"readback": "{\"n\":12,\"prices\":[3.48,3.48,3.48],\"sales\":[3.48,3.48,3.48]}",
"save": "[]",
"ok": false
},
"694637537727": {
"price": "22.18",
"readback": "API 回读: {\"n\":5,\"prices\":[26.62,26.62,26.62],\"sales\":[22.18,22.18,22.18]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"621300323670": {
"price": "21.89",
"readback": "API 回读: {\"n\":1,\"prices\":[26.27],\"sales\":[21.89]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"682195499823": {
"price": "21.35",
"readback": "{\"n\":3,\"prices\":[3.01,3.01,3.01],\"sales\":[3.01,3.01,3.01]}",
"save": "[]",
"ok": false
},
"623011262210": {
"price": "21.27",
"readback": "API 回读: {\"n\":1,\"prices\":[25.53],\"sales\":[21.27]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"833753630925": {
"price": "21.27",
"readback": "API 回读: {\"n\":1,\"prices\":[25.53],\"sales\":[21.27]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"854003999648": {
"price": "21.15",
"readback": "API 回读: {\"n\":27,\"prices\":[25.38,25.38,25.38],\"sales\":[21.15,21.15,21.15]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"703822717997": {
"price": "20.77",
"readback": "API 回读: {\"n\":2,\"prices\":[24.93,24.93],\"sales\":[20.77,20.77]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"863524134410": {
"price": "20.73",
"readback": "API 回读: {\"n\":1,\"prices\":[24.88],\"sales\":[20.73]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"853414558425": {
"price": "20.45",
"readback": "API 回读: {\"n\":22,\"prices\":[24.54,24.54,24.54],\"sales\":[20.45,20.45,20.45]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"750271307455": {
"price": "20.18",
"readback": "API 回读: {\"n\":3,\"prices\":[24.22,24.22,24.22],\"sales\":[20.18,20.18,20.18]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"726442972676": {
"price": "20.09",
"readback": "API 回读: {\"n\":24,\"prices\":[24.11,24.11,24.11],\"sales\":[20.09,20.09,20.09]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"953869908708": {
"price": "19.91",
"readback": "API 回读: {\"n\":21,\"prices\":[23.9,23.9,23.9],\"sales\":[19.91,19.91,19.91]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"775057912118": {
"price": "19.84",
"readback": "API 回读: {\"n\":26,\"prices\":[23.81,23.81,23.81],\"sales\":[19.84,19.84,19.84]}",
"save": "保存请求: [\"POST\"]",
"ok": true
},
"863384503032": {
"price": "19.82",
"readback": "{\"n\":1,\"prices\":[2.21],\"sales\":[2.21]}",
"save": "[]",
"ok": false
},
"739147684759": {
"price": "19.64",
"readback": "{\"n\":3,\"prices\":[2.07,2.07,2.07],\"sales\":[2.07,2.07,2.07]}",
"save": "[]",
"ok": false
},
"774494945453": {
"price": "19.45",
"readback": "{\"n\":1,\"prices\":[23.34],\"sales\":[19.45]}",
"save": "[\"POST\"]",
"ok": true
},
"997450831819": {
"price": "18.07",
"readback": "{\"n\":1,\"prices\":[21.69],\"sales\":[18.07]}",
"save": "[\"POST\"]",
"ok": true
},
"601964534137": {
"price": "17.91",
"readback": "API 回读: {\"n\":5,\"prices\":[21.5,21.5,21.5],\"sales\":[17.91,17.91,17.91]}",
"save": "保存请求: [\"POST\"]",
"ok": true
}
}
+74
View File
@@ -0,0 +1,74 @@
[
{
"oid": "866537106657",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
},
{
"oid": "639409307567",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
},
{
"oid": "787261097583",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
},
{
"oid": "682195499823",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
},
{
"oid": "863384503032",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
},
{
"oid": "739147684759",
"red": [
"变种图片",
"请视频上传7天内完成产品发布",
"请填写SKU",
"请填写售价",
"包装尺寸请输入1~13500之间的数值!",
"重量请输入1~1000000之间的数值!",
"变种图不能为空"
]
}
]
+331
View File
@@ -0,0 +1,331 @@
[
{
"oid": "708420302844",
"pid": "184807701684297335",
"price": 36.45,
"w": 130,
"cost": 10.0
},
{
"oid": "547183997437",
"pid": "184807701684297331",
"price": 36.45,
"w": 130,
"cost": 10.0
},
{
"oid": "657617635303",
"pid": "184807701684297329",
"price": 36.45,
"w": 130,
"cost": 10.0
},
{
"oid": "641673262387",
"pid": "184807701684297327",
"price": 36.45,
"w": 130,
"cost": 10.0
},
{
"oid": "764423265986",
"pid": "184807701684297353",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "735147938123",
"pid": "184807701684297347",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "548459586741",
"pid": "184807701684297343",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "610164857063",
"pid": "184807701684297341",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "676236596821",
"pid": "184807701684297339",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "866537106657",
"pid": "184807701684297333",
"price": 34.0,
"w": 100,
"cost": 10.0
},
{
"oid": "591994902720",
"pid": "184807701684307653",
"price": 31.91,
"w": 130,
"cost": 7.5
},
{
"oid": "836512561982",
"pid": "184807701684307649",
"price": 30.2,
"w": 130,
"cost": 6.56
},
{
"oid": "714492799386",
"pid": "184807701684307651",
"price": 28.36,
"w": 100,
"cost": 6.9
},
{
"oid": "902440558099",
"pid": "184807701684308549",
"price": 27.27,
"w": 130,
"cost": 4.95
},
{
"oid": "823838532886",
"pid": "184807701684308547",
"price": 27.0,
"w": 130,
"cost": 4.8
},
{
"oid": "661190598139",
"pid": "184807701684297355",
"price": 26.64,
"w": 10,
"cost": 10.0
},
{
"oid": "685456757807",
"pid": "184807701684297349",
"price": 26.64,
"w": 10,
"cost": 10.0
},
{
"oid": "893592264148",
"pid": "184807701684308575",
"price": 26.45,
"w": 130,
"cost": 4.5
},
{
"oid": "639409307567",
"pid": "184807701684308551",
"price": 25.82,
"w": 100,
"cost": 5.5
},
{
"oid": "896469995553",
"pid": "184807701684308597",
"price": 25.18,
"w": 130,
"cost": 3.8
},
{
"oid": "719391062058",
"pid": "184807701684308589",
"price": 23.71,
"w": 130,
"cost": 2.99
},
{
"oid": "698782391248",
"pid": "184807701684308545",
"price": 23.6,
"w": 100,
"cost": 4.28
},
{
"oid": "823560564174",
"pid": "184807701684308537",
"price": 23.11,
"w": 130,
"cost": 2.66
},
{
"oid": "857781294719",
"pid": "184807701684308587",
"price": 22.73,
"w": 130,
"cost": 2.45
},
{
"oid": "707826905566",
"pid": "184807701684308543",
"price": 22.55,
"w": 100,
"cost": 3.7
},
{
"oid": "1027332215847",
"pid": "184807701684308541",
"price": 22.55,
"w": 100,
"cost": 3.7
},
{
"oid": "607330049419",
"pid": "184807701684308573",
"price": 22.24,
"w": 130,
"cost": 2.18
},
{
"oid": "944470567338",
"pid": "184807701684308595",
"price": 22.18,
"w": 100,
"cost": 3.5
},
{
"oid": "787261097583",
"pid": "184807701684308593",
"price": 22.18,
"w": 100,
"cost": 3.5
},
{
"oid": "694637537727",
"pid": "184807701684307645",
"price": 22.18,
"w": 130,
"cost": 2.15
},
{
"oid": "621300323670",
"pid": "184807701684308533",
"price": 21.89,
"w": 130,
"cost": 1.99
},
{
"oid": "682195499823",
"pid": "184807701684308539",
"price": 21.35,
"w": 100,
"cost": 3.04
},
{
"oid": "623011262210",
"pid": "184807701684307647",
"price": 21.27,
"w": 100,
"cost": 3.0
},
{
"oid": "833753630925",
"pid": "184807701684308591",
"price": 21.27,
"w": 100,
"cost": 3.0
},
{
"oid": "854003999648",
"pid": "184807701684308571",
"price": 21.15,
"w": 130,
"cost": 1.58
},
{
"oid": "703822717997",
"pid": "184807701684307641",
"price": 20.77,
"w": 130,
"cost": 1.375
},
{
"oid": "863524134410",
"pid": "184807701684307643",
"price": 20.73,
"w": 100,
"cost": 2.7
},
{
"oid": "853414558425",
"pid": "184807701684307655",
"price": 20.45,
"w": 130,
"cost": 1.2
},
{
"oid": "750271307455",
"pid": "184807701684308581",
"price": 20.18,
"w": 130,
"cost": 1.05
},
{
"oid": "726442972676",
"pid": "184807701684308579",
"price": 20.09,
"w": 130,
"cost": 1.0
},
{
"oid": "953869908708",
"pid": "184807701684307639",
"price": 19.91,
"w": 130,
"cost": 0.9
},
{
"oid": "775057912118",
"pid": "184807701684308577",
"price": 19.84,
"w": 130,
"cost": 0.86
},
{
"oid": "863384503032",
"pid": "184807701684308585",
"price": 19.82,
"w": 100,
"cost": 2.2
},
{
"oid": "739147684759",
"pid": "184807701684308535",
"price": 19.64,
"w": 100,
"cost": 2.1
},
{
"oid": "774494945453",
"pid": "184807701684308583",
"price": 19.45,
"w": 100,
"cost": 2.0
},
{
"oid": "997450831819",
"pid": "184807701684307657",
"price": 18.07,
"w": 100,
"cost": 1.24
},
{
"oid": "601964534137",
"pid": "184807701684308531",
"price": 17.91,
"w": 100,
"cost": 1.15
}
]
+100
View File
@@ -0,0 +1,100 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── Ozon 改价(≤500g 档,售价 = 合计成本 ÷ (1 − 20% − 利润率))──
node ozprice_set.mjs <pid> <priceCNY> [dry]
dry = 只填不保存 */
const PID = process.argv[2];
const PRICE = process.argv[3];
const DRY = process.argv[4] === 'dry';
if (!PID || !PRICE) { console.log('用法: node ozprice_set.mjs <pid> <priceCNY> [dry]'); process.exit(1); }
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && x.url.includes('ozonProduct/edit'))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理残留编辑页 ${k} 个`); await wait(1100); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: `https://www.dianxiaomi.com/web/ozonProduct/edit?id=${PID}` });
await wait(32000);
for (let i = 0; i < 8; i++) {
const x = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
if(!ms.length)return'n'; ms.forEach(m=>{const cb=m.querySelector('.ant-modal-close'); if(cb&&cb.offsetParent!==null)cb.click();}); return 'c';})()`);
if (x === 'n') break; await wait(1000);
}
await wait(2000);
// 1. 找 SKU 表 + 价格列
const map = await c.evalJS(`(()=>{
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
for(let ti=0;ti<tbls.length;ti++){
const ths=[...tbls[ti].querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
if(!ths.some(h=>/价格|售价|₽|RUB/i.test(h))) continue;
const rows=[...tbls[ti].querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
if(!rows.length) continue;
return JSON.stringify({ti, ths, rowCount: rows.length,
vals: rows.slice(0,3).map(tr=>[...tr.querySelectorAll('input')].map(i=>(i.value||'').slice(0,12)))});
}
return 'no-table';})()`);
log('SKU 表:', SAN(String(map)).slice(0, 500));
if (map === 'no-table') { log('❌ 找不到价格表'); process.exit(1); }
const M = JSON.parse(String(map));
const IDX_PRICE = M.ths.findIndex(h => /价格|售价|₽|RUB/i.test(h) && !/划线|原价|市场价|最低/i.test(h));
const IDX_OLD = M.ths.findIndex(h => /划线|原价|市场价/i.test(h));
const OLD_PRICE = (Math.ceil(Number(PRICE) * 1.2 * 100) / 100).toFixed(2);
log('价格列:', IDX_PRICE, JSON.stringify(M.ths[IDX_PRICE]), '| 原价列:', IDX_OLD, IDX_OLD >= 0 ? JSON.stringify(M.ths[IDX_OLD]) : '(无)', '→ 原价设为', OLD_PRICE);
// 2. 填价
const r = await c.evalJS(`(()=>{
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
const tbl=tbls[${M.ti}]; if(!tbl)return'no-table';
const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
let n=0;
rows.forEach(tr=>{ const tds=[...tr.querySelectorAll('td')]; const td=tds[${IDX_PRICE}]; if(!td)return;
const inp=td.querySelector('input'); if(!inp)return; setV(inp,${JSON.stringify(PRICE)}); n++;
const od=tds[${IDX_OLD}]; if(od){ const oi=od.querySelector('input'); if(oi) setV(oi, ${JSON.stringify(OLD_PRICE)}); } });
return 'set:'+n+'/'+rows.length;})()`);
log('填价:', r);
await wait(2500);
// 3. 回读
const back = await c.evalJS(`(()=>{const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
const tbl=tbls[${M.ti}]; const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
return JSON.stringify(rows.slice(0,5).map(tr=>[...tr.querySelectorAll('td')].map(td=>{const i=td.querySelector('input'); return i?i.value:'';})));})()`);
log('回读:', SAN(String(back)).slice(0, 300));
if (DRY) { log('dry-run,不保存'); process.exit(0); }
// 4. 保存(优先「保存并移入待发布」,与 fixone3 一致)
await c.evalJS(`window.scrollTo(0,0)`); await wait(700);
const reqs = [];
c.on(m => { if (m.method === 'Network.requestWillBeSent') { const u = m.params.request.url; if (/ozonProduct\/add\.json/.test(u)) reqs.push(m.params.request.method); } });
await c.send('Network.enable').catch(()=>{});
const sv = await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
let x=xs.find(e=>/保存并移入待发布/.test((e.innerText||'').trim())); let tag='保存并移入待发布';
if(!x){ x=xs.find(e=>/^保\\s*存$/.test((e.innerText||'').trim())); tag='保存'; }
if(!x) return 'nf';
for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return tag;})()`);
log('保存按钮:', sv);
await wait(13000);
await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
for(const m of ms){const b=[...m.querySelectorAll('button')].find(x=>/确定|确认/.test(x.innerText||'')); if(b)b.click();} return 1;})()`);
await wait(5000);
log('保存请求:', JSON.stringify(reqs));
// 5. API 回读(权威)
const back2 = await c.evalJS(`(async()=>{const rr=await fetch('/api/ozonProduct/add.json?id=${PID}&_t='+Date.now(),{headers:{'X-Requested-With':'XMLHttpRequest','Cache-Control':'no-cache'}});
const tx=await rr.text(); if(!tx.startsWith('{'))return'(err)';
const p=(JSON.parse(tx).data||{}).product||{}; const vl=p.variantList||[];
return JSON.stringify({n:vl.length, prices:vl.slice(0,3).map(v=>v.price), sales:vl.slice(0,3).map(v=>v.salePrice)});})()`, true);
log('★ API 回读:', SAN(String(back2)).slice(0, 260));
process.exit(0);
+25
View File
@@ -0,0 +1,25 @@
# SHEIN 建品配方(已跑通第一条)
## 完整流程
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`,逐条跑
+22
View File
@@ -0,0 +1,22 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const r = await c.evalJS(`(()=>{const box=[...document.querySelectorAll('.theme-select-box')].filter(e=>e.offsetParent!==null).pop();
if(!box)return'nf';
const chips=[...box.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/^\\s*尺寸\\(Size\\)\\s*$/.test((e.innerText||'').trim()));
if(!chips.length)return'no-chip';
const el=chips[chips.length-1];
for(const ev of ['mousedown','mouseup','click']) el.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'clicked';})()`);
console.log('点尺寸:', String(r));
await wait(4000);
const o = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
const boxes=[...document.querySelectorAll('.sku-info-box')].filter(e=>e.offsetParent!==null).map((b,i)=>({
i, txt:(b.innerText||'').replace(/\\s+/g,' ').slice(0,90), opts:b.querySelectorAll('.sku-option').length,
sels:[...b.querySelectorAll('.ant-select')].map(s=>(s.innerText||'').replace(/\\s+/g,' ').slice(0,16)),
ins:[...b.querySelectorAll('input')].filter(i=>i.offsetParent!==null&&i.type!=='checkbox').map(i=>({ph:i.placeholder||'',cls:String(i.className||'').slice(0,40)})).slice(0,4)}));
const btns=[...document.querySelectorAll('button')].filter(b=>b.offsetParent!==null&&/添加/.test(b.innerText||'')).map(b=>({t:(b.innerText||'').trim(),dis:b.disabled}));
return JSON.stringify({attrs:(a.formState.skuAttrs||[]).map(x=>({n:x.name, opts:(x.options||[]).length})), boxes, btns},null,1);})()`);
console.log(String(o).slice(0,1400));
process.exit(0);
+21
View File
@@ -0,0 +1,21 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
const btns = await c.evalJS(`(()=>[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).map((b,i)=>{
const r=b.getBoundingClientRect(); return {i, t:(b.innerText||'').replace(/\\s+/g,' ').trim().slice(0,14), y:Math.round(r.y), dis:b.disabled};}).filter(x=>x.t))()`);
console.log('按钮:', JSON.stringify(btns).slice(0,700));
// 点“保存”并立刻轮询任何反馈
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x){ for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); } return 1;})()`);
const seen=[];
for(let i=0;i<18;i++){ await wait(700);
const m=await c.evalJS(`(()=>{const a=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal-confirm,.ant-modal')].filter(e=>e.offsetParent!==null)
.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,130)).filter(Boolean); return JSON.stringify(a.slice(0,3));})()`);
JSON.parse(String(m)).forEach(x=>seen.push(x)); }
console.log('反馈:', [...new Set(seen)].slice(0,4).join(' || ').slice(0,400));
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,4)));
process.exit(0);
+22
View File
@@ -0,0 +1,22 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'nf'; return JSON.stringify({txt:(it.innerText||'').replace(/\\s+/g,' ').slice(0,120), html:it.innerHTML.slice(0,600)});})()`);
console.log('分类控件:', String(o).slice(0,900));
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'[]'; const el=it.querySelector('.ant-select-selector')||it.querySelector('input');
if(!el)return'[]'; el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(pos.length){ for(const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:pos[0],y:pos[1],button:'left',clickCount:1}); await wait(4000); }
const o2 = await c.evalJS(`(()=>{
const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(m=>({t:(m.querySelector('.ant-modal-title')||{}).innerText||'',
inputs:[...m.querySelectorAll('input')].map(i=>i.placeholder||i.type).slice(0,5),
btns:[...m.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).slice(0,6),
txt:(m.innerText||'').replace(/\\s+/g,' ').slice(0,300)}));
const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).map(d=>(d.innerText||'').replace(/\\s+/g,' ').slice(0,260));
return JSON.stringify({modals:ms, dropdowns:dd},null,1);})()`);
console.log(String(o2).slice(0,1400));
process.exit(0);
+26
View File
@@ -0,0 +1,26 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const pick = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf'; const box=it.querySelector('.sku-info-box')||it;
const opts=[...box.querySelectorAll('.sku-option')].filter(e=>e.offsetParent!==null);
const hit=opts.find(e=>/Black\\(/.test(e.innerText||''))||opts[0];
if(!hit)return'no-opt';
const lab=hit.querySelector('label'); const inp=hit.querySelector('input[type=checkbox]');
if(lab) for(const ev of ['mousedown','mouseup','click']) lab.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
if(inp && !inp.checked) inp.click();
return 'clicked:'+(hit.innerText||'').trim()+' checked='+(inp?inp.checked:'?');})()`);
console.log('点色:', pick);
await wait(7000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,15),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const d=JSON.parse(JSON.stringify(p._s.get('sheinSkuDataStore').$state));
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
return JSON.stringify({tbls, n:(d.formState.skuDataList||[]).length, row0:(d.formState.skuDataList||[])[0],
chosen:(a.formState.skuAttrs||[]).map(x=>({name:x.name, vals:(x.attributeValues||[]).map(v=>v.attribute_value||v)}))},null,1);})()`);
console.log(String(o).slice(0,1800));
process.exit(0);
+34
View File
@@ -0,0 +1,34 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
// 打开主规格颜色下拉
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(3000);
const opts = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const its=[...dd.querySelectorAll('.ant-select-item-option')];
return JSON.stringify({n:its.length, first:its.slice(0,6).map(e=>(e.innerText||'').trim())});})()`);
console.log('颜色选项:', String(opts).slice(0,400));
// 选 Multicolor / Black(找不到就第一个)
const pick = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const its=[...dd.querySelectorAll('.ant-select-item-option')];
let hit=its.find(o=>/Multicolor|Multi-color/i.test(o.innerText||'')) || its.find(o=>/^\\s*Black\\(/.test((o.innerText||'').trim())) || its[0];
if(!hit)return'no-opt';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked:'+(hit.innerText||'').trim();})()`);
console.log('选择:', pick);
await wait(6000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,14),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const d=JSON.parse(JSON.stringify(p._s.get('sheinSkuDataStore').$state));
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
return JSON.stringify({tbls, skuDataList:(d.formState.skuDataList||[]).slice(0,2), n:(d.formState.skuDataList||[]).length,
chosen:(a.formState.skuAttrs||[]).map(x=>({name:x.name, n:(x.attributeValues||x._chosen||[]).length}))},null,1);})()`);
console.log(String(o).slice(0,1800));
process.exit(0);
+62
View File
@@ -0,0 +1,62 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const SHOP = process.argv[2] || '③shein-易捷天宇-美妆';
const CATKW = process.argv[3] || '发夹';
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x=>x.type==='page'&&/shein/i.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(()=>{});
await wait(1000);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/sheinProduct/add'});
await wait(21000);
// 店铺
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=(it||document).querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(2500);
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(SHOP)});
if(hit) for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(8000);
// 开分类下拉并选目标
const cp = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:cp[0],y:cp[1],button:'left',clickCount:1});
await wait(3500);
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const its=[...dd.querySelectorAll('.ant-select-item-option')];
const hit=its.find(o=>new RegExp('^\\\\s*'+${JSON.stringify(CATKW)}).test((o.innerText||'').trim()));
if(!hit)return'no-hit:'+its.slice(0,5).map(o=>o.innerText.trim()).join(',');
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked:'+(hit.innerText||'').trim();})()`);
console.log('类目:', pk);
await wait(12000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,24).padEnd(25)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,22);
}).filter(x=>x.trim().length>4);
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const cut=(x,d=0)=>(d>3?'…':(Array.isArray(x)?(x.length?'['+x.length+'] '+JSON.stringify(x[0]).slice(0,220):'[]'):(x&&typeof x==='object'?Object.fromEntries(Object.entries(x).map(([k,v])=>[k,cut(v,d+1)])):x)));
const dump={};
for(const n of ['sheinAddProductStore','sheinSkuDataStore','sheinSkuAttrStore','sheinAddBasicStore','sheinImgStore'])
{ try{dump[n]=cut(JSON.parse(JSON.stringify(p._s.get(n).$state)));}catch(e){dump[n]='ERR';} }
return JSON.stringify({items, tables, dump},null,1);})()`);
fs.writeFileSync('/tmp/shein_deep.json', String(o));
const j=JSON.parse(String(o));
console.log('=== form ==='); j.items.forEach(x=>console.log(' '+x));
console.log('=== tables ==='); console.log(JSON.stringify(j.tables));
console.log('=== stores ===');
for(const [n,v] of Object.entries(j.dump)) console.log(' ##',n,JSON.stringify(v).slice(0,520));
process.exit(0);
+189
View File
@@ -0,0 +1,189 @@
{
"items": [
"* 店铺账号 [select] ③shein-易捷天宇-美妆",
"* 产品分类 [select] 发夹(Hair Clips)",
" 产品属性 [select] No(否)",
" 品牌 [input] ",
" 是否含木质材料 (Whether it cont [select] No(否)",
" 敏感类别 (Hazard Category) [input] ",
" 关税种类 (Tariff Type) [select] Hairpin(发夹)",
" 材质 (Material) [select] Zinc Alloy(锌合金)",
" 风格 (Style) [input] ",
" 数量 (Quantity) [select] piece(s)(件)",
" 季节 (Season) [input] ",
" 细化图案 (Specific Pattern) [input] ",
" 类型 (Type) [input] ",
" 特征 (Features) [input] ",
" 证书编号/检测机构 (Certificate N [input] ",
" 是否有型号 (Is there a Model [input] ",
" 电池使用方式 (Battery Usage Ty [input] ",
" 版型 (Version) [input] ",
" 包装类型 (type of packaging) [input] ",
" 成分 (Composition) [input] ",
" 次要材质 (Other Material) [input] ",
" 发圈类型 (hair tie type) [input] ",
" 元素 (Element) [select] None(无)",
" 制作工艺 (Craftsmanship Type [input] ",
" 企划风格 (Design Style) [input] ",
" 产品型号 (Product Model) [input] ",
" 镀贵金属类型 (Plating) [input] ",
" 镶嵌类型 (Inlay type) [input] ",
" 织造方式 (Fabric Weave Type) [input] ",
" NCM-BR专属 (Ncm-Br Exclusi [input] ",
" 系列 (Series) [input] ",
" 是否多套件 [-] ",
" 店小秘分类 [-] ",
" 来源URL [input] ",
" [input] ",
"* 产品标题: [input] ",
" 商品IP [input] ",
" 图片模式 [input] 1",
" 产品视频 [select] SHEIN国际站",
"* 主规格 [select] 颜色(Color)",
" 其他规格 [-] ",
"* 尺码表 [-] "
],
"tables": [],
"dump": {
"sheinAddProductStore": {
"formState": {
"imageType": 0,
"productName": ""
},
"dataState": {
"newSizeGroups": "[]",
"newBootSizeGroups": "[]"
}
},
"sheinSkuDataStore": {
"formState": {
"identifierCodeType": 1,
"supplierType": "EAN",
"skuDataList": "[]",
"deleteCombinationSkus": "[]",
"siteTableInfoData": "[]",
"sampleInfoObj": {
"sampleJudgeType": "2",
"reserveSampleFlag": "2",
"spotFlag": "1"
}
},
"dataState": {
"skcRetailPriceEdit": true,
"stockEdit": true,
"specialPriceEdit": true,
"supplyPriceEdit": true,
"priceEdit": true
},
"siteStockStr": "_stock",
"recordMainVariant": {}
},
"sheinSkuAttrStore": {
"formState": {
"skuAttrs": "[1] {\"attributeId\":27,\"name\":\"颜色\",\"nameEn\":\"Color\",\"_options\":[{\"attribute_value\":\"Apricot\",\"attribute_value_en\":\"Apricot\",\"attribute_value_id\":78,\"is_show\":1,\"supplier_id\":0,\"attribute_value_cn\":\"杏色\"},{\"attribute_value\":\"Be",
"sizeTableInfo": "[]"
},
"dataState": {
"mainVariantId": 27,
"mainVariantList": "[1] {\"id\":4810372362706115,\"puid\":70000,\"shopId\":6226408,\"categoryId\":0,\"productTypeId\":603,\"businessMode\":\"5\",\"attributeId\":27,\"name\":\"颜色\",\"nameEn\":\"Color\",\"show\":1,\"label\":1,\"mode\":2,\"source\":1,\"status\":3,\"type\":1,\"dimensi",
"variantList": "[3] {\"id\":4810372362706123,\"puid\":70000,\"shopId\":6226408,\"categoryId\":0,\"productTypeId\":603,\"businessMode\":\"5\",\"attributeId\":87,\"name\":\"尺寸\",\"nameEn\":\"Size\",\"show\":1,\"label\":0,\"mode\":2,\"source\":1,\"status\":3,\"type\":1,\"dimensio",
"isHasSizeChart": true,
"cacheIsHasSizeChart": true,
"sizeTableData": "[6] {\"id\":4810372362706189,\"puid\":70000,\"shopId\":6226408,\"categoryId\":0,\"productTypeId\":603,\"businessMode\":\"5\",\"attributeId\":55,\"name\":\"长度 (cm)\",\"nameEn\":\"Length (cm)\",\"show\":1,\"label\":0,\"mode\":0,\"source\":1,\"status\":3,\"type\"",
"isAllowEditSkcData": "[]",
"collectDetailImages": ""
},
"customAttrOptions": {
"27": "[]"
},
"recordData": {
"oldOption": "[]",
"newOption": "[]"
}
},
"sheinAddBasicStore": {
"formState": {
"shopId": "6226408",
"categoryId": 2734
},
"shopList": "[11] {\"id\":5986682,\"puid\":70000,\"name\":\"⑧Shein-易世通达-文具\",\"apiKey\":null,\"merchantId\":\"14349C4899B04FE59CECE6A4EA6CDB9C\",\"merchantUserName\":\"2\",\"awsAccessKeyId\":\"zh-cn|CNY\",\"marketPlaceId\":\"4328424\",\"lastSyncTimeProduct\":null,\"l",
"dataState": {
"categories": "[67] {\"id\":4810372411213177,\"puid\":70000,\"shopId\":6226408,\"categoryId\":2735,\"productTypeId\":604,\"parentId\":2732,\"name\":\"发爪\",\"nameEn\":\"Hair Claws\",\"nodePath\":\"服饰装饰品(Apparel Accessories)/女士配饰(Women Accessories)/女士发饰(Women Hair ",
"categoryPathName": "服饰装饰品(Apparel Accessories) > 女士配饰(Women Accessories) > 女士发饰(Women Hair Accessories) > 发夹(Hair Clips)",
"shopIds": "[1] \"6226408\"",
"euSiteList": "[]",
"shopSiteList": "[57] {\"id\":4810364349509615,\"puid\":70000,\"shopId\":6226408,\"currency\":\"USD\",\"siteAbbr\":\"shein-www\",\"siteName\":\"SHEIN国际站\",\"mainSite\":\"shein\",\"mainSiteName\":\"SHEIN\",\"storeType\":0,\"symbolLeft\":\"$\",\"symbolRight\":\"\",\"rateInfo\":\"[{\\",
"warehouseList": "[]",
"euSiteStockShopList": "[]",
"categoryUpdated": false
},
"currencyCode": "CNY",
"isEurSite": false,
"isAllowEditOnlineProductFlag": false,
"infoDisplayRules": {
"image_module_show": true,
"reference_module_show": false,
"allow_switch_spu_picture": true,
"sample_spec_show": false,
"sample_spec_required": false,
"minimum_stock_quantity_show": false,
"minimum_stock_quantity_required": false,
"reference_product_link_show": false,
"reference_product_link_required": false,
"proof_of_stock_show": false,
"proof_of_stock_required": false,
"skc_retail_price_show": true,
"skc_retail_price_required": false,
"quantity_info_show": true,
"quantity_info_required": false,
"package_type_show": true,
"package_type_required": false,
"supplier_barcode_show": false,
"supplier_barcode_required": false,
"goods_ip_show": true,
"goods_ip_required": false,
"product_video_show": true,
"product_video_required": false,
"shelf_require_show": true,
"shelf_require_required": false,
"title_max_length": 150
},
"imageDisplayRules": {
"switch_spu_picture": false,
"spu_image_detail_show": false,
"spu_image_detail_required": false,
"spu_image_detail_single": false,
"spu_image_square_show": false,
"spu_image_square_required": false,
"skc_image_detail_show": true,
"skc_image_detail_required": true,
"skc_image_detail_single": false,
"skc_image_square_show": true,
"skc_image_square_required": true,
"sku_image_detail_show": true,
"sku_image_detail_required": false,
"package_type_show": false,
"package_type_required": false,
"supplier_barcode_show": false,
"supplier_barcode_required": false,
"shelf_require_show": false,
"shelf_require_required": false,
"title_max_length": 1000
},
"isMxSite": false,
"isJapanSite": false,
"isKoreanSite": false
},
"sheinImgStore": {
"formState": {
"mainImgData": {
"multipleSpuImgs": "[]"
}
},
"dataState": {
"newSizeGroups": "[]",
"newBootSizeGroups": "[]"
}
}
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"stores": [
"sheinAddStore",
"sheinAddProductStore",
"sheinSkuDataStore",
"sheinSkuAttrStore",
"sheinAddBasicStore",
"sheinAddOtherStore",
"sheinQualoficaStore",
"sheinProductVideoStore",
"sheinImgStore"
],
"items": [
"* 店铺账号 [select] ①Shein-武汉九个网-箱包",
"* 产品分类 [input] ",
" 是否多套件 [-] ",
" 店小秘分类 [-] ",
" 来源URL [input] ",
" [input] ",
"* 产品标题: [input] ",
" 图片模式 [input] 1"
],
"dump": {
"sheinAddStore": {
"attrsInfo.showSizeAttr": "false",
"sharedData.isFeedBacking": "false",
"sharedData.isAllowEditOnline": "false",
"sharedData.variantImageNum": "0",
"dataState.isVip": "true"
},
"sheinAddProductStore": {
"formState.imageType": "0"
},
"sheinSkuDataStore": {
"formState.identifierCodeType": "1",
"formState.supplierType": "EAN",
"dataState.skcRetailPriceEdit": "true",
"dataState.stockEdit": "true",
"dataState.specialPriceEdit": "true",
"dataState.supplyPriceEdit": "true",
"dataState.priceEdit": "true",
"siteStockStr": "_stock"
},
"sheinSkuAttrStore": {
"dataState.isHasSizeChart": "false",
"dataState.cacheIsHasSizeChart": "false"
},
"sheinAddBasicStore": {
"formState.shopId": "6229819",
"dataState.categoryUpdated": "false",
"currencyCode": "CNY",
"isEurSite": "false",
"isAllowEditOnlineProductFlag": "false",
"infoDisplayRules.image_module_show": "false",
"infoDisplayRules.reference_module_show": "false",
"infoDisplayRules.allow_switch_spu_picture": "false",
"infoDisplayRules.sample_spec_show": "false",
"infoDisplayRules.sample_spec_required": "false",
"infoDisplayRules.minimum_stock_quantity_show": "false",
"infoDisplayRules.minimum_stock_quantity_required": "false",
"infoDisplayRules.reference_product_link_show": "false",
"infoDisplayRules.reference_product_link_required": "false",
"infoDisplayRules.proof_of_stock_show": "false",
"infoDisplayRules.proof_of_stock_required": "false",
"infoDisplayRules.skc_retail_price_show": "false",
"infoDisplayRules.skc_retail_price_required": "false",
"infoDisplayRules.quantity_info_show": "false",
"infoDisplayRules.quantity_info_required": "false",
"infoDisplayRules.package_type_show": "false",
"infoDisplayRules.package_type_required": "false",
"infoDisplayRules.supplier_barcode_show": "false",
"infoDisplayRules.supplier_barcode_required": "false",
"infoDisplayRules.goods_ip_show": "false",
"infoDisplayRules.goods_ip_required": "false",
"infoDisplayRules.product_video_show": "false",
"infoDisplayRules.product_video_required": "false",
"infoDisplayRules.shelf_require_show": "false",
"infoDisplayRules.shelf_require_required": "false",
"infoDisplayRules.title_max_length": "1000",
"imageDisplayRules.switch_spu_picture": "false",
"imageDisplayRules.spu_image_detail_show": "false",
"imageDisplayRules.spu_image_detail_required": "false",
"imageDisplayRules.spu_image_detail_single": "false",
"imageDisplayRules.spu_image_square_show": "false",
"imageDisplayRules.spu_image_square_required": "false",
"imageDisplayRules.skc_image_detail_show": "true",
"imageDisplayRules.skc_image_detail_required": "true",
"imageDisplayRules.skc_image_detail_single": "false",
"imageDisplayRules.skc_image_square_show": "true",
"imageDisplayRules.skc_image_square_required": "true",
"imageDisplayRules.sku_image_detail_show": "true",
"imageDisplayRules.sku_image_detail_required": "false",
"imageDisplayRules.package_type_show": "false",
"imageDisplayRules.package_type_required": "false",
"imageDisplayRules.supplier_barcode_show": "false",
"imageDisplayRules.supplier_barcode_required": "false",
"imageDisplayRules.shelf_require_show": "false",
"imageDisplayRules.shelf_require_required": "false",
"imageDisplayRules.title_max_length": "1000",
"isMxSite": "false",
"isJapanSite": "false",
"isKoreanSite": "false"
},
"sheinAddOtherStore": {},
"sheinQualoficaStore": {
"formData.isGrayShow": "false",
"formData.isShow": "false"
},
"sheinProductVideoStore": {
"dataState.supported": "false"
},
"sheinImgStore": {}
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"美妆": {
"shop": "③shein-易捷天宇-美妆",
"kw": {
"661190598139": "大肠圈",
"902440558099": "大肠圈",
"823560564174": "大肠圈",
"607330049419": "大肠圈",
"854003999648": "大肠圈",
"735147938123": "发箍",
"610164857063": "发箍",
"833753630925": "发箍",
"676236596821": "发夹",
"944470567338": "发夹",
"787261097583": "发夹",
"857781294719": "发夹",
"863384503032": "发夹",
"726442972676": "发夹",
"893592264148": "发夹",
"836512561982": "化妆包",
"714492799386": "化妆包",
"719391062058": "化妆包"
}
},
"文具": {
"shop": "⑥Shein-南京亿千-文具",
"kw": {
"953869908708": "涂鸦贴纸",
"863524134410": "涂鸦贴纸",
"623011262210": "涂鸦贴纸",
"997450831819": "涂鸦贴纸",
"639409307567": "涂鸦贴纸",
"698782391248": "涂鸦贴纸",
"591994902720": "笔袋",
"682195499823": "笔袋",
"657617635303": "回形针",
"703822717997": "回形针",
"621300323670": "桌面摆件",
"823838532886": "卡套",
"1027332215847": "文具收纳盒"
}
},
"箱包": {
"shop": "①Shein-武汉九个网-箱包",
"kw": {
"641673262387": "其他收纳包",
"707826905566": "卡包",
"694637537727": "保温午餐包",
"708420302844": "包包挂件",
"750271307455": "包包挂件",
"774494945453": "包包挂件",
"775057912118": "包包挂件",
"896469995553": "包包挂件"
}
},
"内睡袜子": {
"shop": "⑤Shein-惠州九衢-内睡(袜子)",
"kw": {
"627879474686": "袜子"
}
}
}
+42
View File
@@ -0,0 +1,42 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
const IMGS=['https://cbu01.alicdn.com/img/ibank/O1CN01IHtNAHKMzNC1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01UGEWafTzqTH1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01d0hA4N72jzD1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01edgGxIlmdtJ1chua_!!2222444614399-0-cib.jpg'];
const r = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const d=p._s.get('sheinSkuDataStore').$state.formState;
const rows=d.skuDataList||[]; let n=0;
rows.forEach((x,i)=>{ x.skcSupplierCode='SHEIN-TEST-'+(i+1); x.skcRetailPrice='12.90'; x.supplyPrice='8.50';
x.stockVariety='999'; x.weight='100'; x.length='200'; x.width='150'; x.height='30'; n++; });
const im=p._s.get('sheinImgStore').$state.formState;
im.mainImgData.multipleSpuImgs=${JSON.stringify(IMGS.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800})))};
return JSON.stringify({rows:n, imgs:im.mainImgData.multipleSpuImgs.length, imgType:Array.isArray(im.mainImgData.multipleSpuImgs)?'array':typeof im.mainImgData.multipleSpuImgs});})()`);
console.log('填充:', r);
await wait(3500);
const chk = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/图片/.test(e.innerText||'')&&e.querySelectorAll('img').length>0);
return JSON.stringify({domImgs:it.length?it[0].querySelectorAll('img').length:0});})()`);
console.log('UI 图片:', String(chk));
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
const sv = await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
let x=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()))||xs.find(e=>/保存/.test(e.innerText||''));
if(!x)return'nf'; for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return (x.innerText||'').trim();})()`);
console.log('保存按钮:', sv);
await wait(13000);
await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
for(const m of ms){const b=[...m.querySelectorAll('button')].find(x=>/确定|确认/.test(x.innerText||'')); if(b)b.click();} return 1;})()`);
await wait(4000);
console.log('POST 请求:', JSON.stringify([...new Set(reqs)].slice(0,5)));
const red = await c.evalJS(`(()=>{const red=[]; document.querySelectorAll('*').forEach(el=>{ if(el.offsetParent===null||el.children.length)return;
const t=(el.innerText||'').trim(); if(!t||t.length>60)return;
const s=getComputedStyle(el); const m=(s.color||'').match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/); if(!m)return;
if(+m[1]>160&&+m[2]<140&&+m[3]<140) red.push(t); });
const toasts=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
return JSON.stringify({red:[...new Set(red)].slice(0,10), toasts:[...new Set(toasts)].slice(0,3)});})()`);
console.log('红字/提示:', String(red).slice(0,600));
process.exit(0);
+38
View File
@@ -0,0 +1,38 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
// 两个规格组各点一个 .sku-option
const r = await c.evalJS(`(()=>{const boxes=[...document.querySelectorAll('.sku-info-box')].filter(e=>e.offsetParent!==null);
const done=[];
boxes.forEach((b,i)=>{ const opts=[...b.querySelectorAll('.sku-option')].filter(e=>e.offsetParent!==null);
if(!opts.length){done.push(i+':no-opt');return;}
const inp=opts[0].querySelector('input[type=checkbox]');
if(inp&&inp.checked){done.push(i+':already');return;}
const lab=opts[0].querySelector('label')||opts[0];
for(const ev of ['mousedown','mouseup','click']) lab.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
if(inp&&!inp.checked) inp.click();
done.push(i+':'+(opts[0].innerText||'').trim().slice(0,18)); });
return done.join(' | ');})()`);
console.log('选规格值:', String(r));
await wait(7000);
const st = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
const d=JSON.parse(JSON.stringify(p._s.get('sheinSkuDataStore').$state));
return JSON.stringify({attrs:(a.formState.skuAttrs||[]).map(x=>({n:x.name, chosen:(x.options||[]).length})),
skuRows:(d.formState.skuDataList||[]).length});})()`);
console.log('状态:', String(st));
// 保存
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(12000);
const cap = await c.evalJS(`JSON.stringify(window.__cap||[])`);
console.log('捕获:', String(cap).slice(0,300));
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,3)));
const t = await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,130)).filter(Boolean).slice(0,3))`);
console.log('提示:', String(t).slice(0,400));
process.exit(0);
+60
View File
@@ -0,0 +1,60 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const SHOP='③shein-易捷天宇-美妆', CATKW='发夹';
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const x of tl.filter(y=>y.type==='page'&&/shein/i.test(y.url))) await fetch(`http://127.0.0.1:9223/json/close/${x.id}`).catch(()=>{});
await wait(900);
const nt = await newTarget('about:blank');
const c = await CDP.connect(nt.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/sheinProduct/add'});
await wait(21000);
// 店铺
const sp=JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=(it||document).querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:sp[0],y:sp[1],button:'left',clickCount:1});
await wait(2400);
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(SHOP)});
if(hit) for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(8000);
// 类目
const cp=JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:cp[0],y:cp[1],button:'left',clickCount:1});
await wait(3500);
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const its=[...dd.querySelectorAll('.ant-select-item-option')];
const hit=its.find(o=>new RegExp('^\\\\s*'+${JSON.stringify(CATKW)}).test((o.innerText||'').trim()));
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return (hit.innerText||'').trim();})()`)));
await wait(12000);
// 标题
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品标题/.test(l.innerText||'');});
const el=it.querySelector('input:not([type=hidden])'); el.focus();
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,'Test Hair Clip Set for Women Girls Daily Use Fashion Accessory');
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true}));
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
p._s.get('sheinAddProductStore').$state.formState.productName='Test Hair Clip Set for Women Girls Daily Use Fashion Accessory';
return 1;})()`);
await wait(1500);
// 选主规格颜色(取第一个可选值)—— 先看控件结构
const mg = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf';
const sels=[...it.querySelectorAll('.ant-select')].map(s=>({txt:(s.innerText||'').replace(/\\s+/g,' ').slice(0,40), cls:String(s.className).slice(0,50)}));
const cbs=[...it.querySelectorAll('.ant-checkbox-wrapper,.ant-tag,[class*=tag]')].slice(0,6).map(e=>(e.innerText||'').trim().slice(0,16));
return JSON.stringify({sels, cbs, html:it.innerHTML.slice(0,400)});})()`);
console.log('主规格控件:', String(mg).slice(0,800));
// 图片字段测试:数组 vs 字符串
const imgTest = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const im=p._s.get('sheinImgStore').$state.formState;
const before=JSON.stringify(im).slice(0,120);
return JSON.stringify({before});})()`);
console.log('图片字段现状:', String(imgTest).slice(0,300));
process.exit(0);
+50
View File
@@ -0,0 +1,50 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const SHOP = process.argv[2] || '①Shein-武汉九个网-箱包';
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x=>x.type==='page'&&/shein/i.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(()=>{});
await wait(1200);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/sheinProduct/add'});
await wait(22000);
// 选店铺
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=(it||document).querySelector('.ant-select-selector');
if(!s)return'[]'; s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(2500);
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(SHOP)});
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
console.log('选店铺:', pk, SHOP);
await wait(9000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const stores=[...p._s.keys()].filter(k=>/shein/i.test(k));
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,26).padEnd(27)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,26);
}).filter(x=>x.trim().length>4);
const dump={};
for(const n of stores){ try{ const s=p._s.get(n).$state; const o2={};
for(const k of Object.keys(s)){ const v=s[k];
if(v===null||['string','number','boolean'].includes(typeof v)){ if(v!==''&&v!==null&&String(v).length<55) o2[k]=String(v); }
else if(v&&typeof v==='object'&&!Array.isArray(v)){ for(const k2 of Object.keys(v)){ const v2=v[k2];
if(v2===null||['string','number','boolean'].includes(typeof v2)){ if(v2!==''&&v2!==null&&String(v2).length<45) o2[k+'.'+k2]=String(v2); } } } }
dump[n]=o2; }catch(e){ dump[n]='ERR'; } }
return JSON.stringify({stores, items, dump},null,1);})()`);
fs.writeFileSync('/tmp/shein_map.json', String(o));
const j=JSON.parse(String(o));
console.log('stores:', j.stores.join(', '));
console.log('--- form ---'); j.items.forEach(x=>console.log(' '+x));
console.log('--- store 关键字段 ---');
for(const [n,v] of Object.entries(j.dump)){ const hit=Object.entries(v||{}).filter(([k])=>/shop|site|currency|cat|brand|price|weight/i.test(k));
if(hit.length) console.log(' ##',n,hit.map(([k,val])=>k+'='+val).join(' | ').slice(0,320)); }
process.exit(0);
+27
View File
@@ -0,0 +1,27 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
const t=await newTarget('about:blank');
const c=await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await c.send('Network.enable');
const caps=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent' && /shop/i.test(m.params.request.url))
caps.push({m:m.params.request.method,u:m.params.request.url.replace('https://www.dianxiaomi.com',''),p:String(m.params.request.postData||'').slice(0,150)}); });
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/ozonProduct/draft'});
await new Promise(r=>setTimeout(r,24000));
for(let i=0;i<8;i++){
const x=await c.evalJS(`(()=>{const v=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
if(!v.length)return'n'; const b=v[0].querySelector('.ant-modal-close'); if(b){b.click();return'c';} return's';})()`);
if(x==='n'){await new Promise(z=>setTimeout(z,900));continue;} if(x==='s')break;
await new Promise(z=>setTimeout(z,1200));
}
console.log(' ══ 含 shop 的接口 ══');
[...new Set(caps.map(x=>x.m+' '+x.u))].slice(0,10).forEach(x=>console.log(' '+x));
// 直接调最可能的那个拿映射
const r=await c.evalJS(`(async()=>{
const u=( ${JSON.stringify(caps.map(x=>x.u).filter(x=>/shop/i.test(x)))} )[0];
if(!u) return 'no-url';
const res=await fetch(u,{headers:{'X-Requested-With':'XMLHttpRequest'}});
const tx=await res.text();
return u+' -> '+tx.slice(0,900);})()`, true);
console.log('\n ══ 店铺列表内容 ══');
console.log(' '+SAN(String(r)).slice(0,900));
c.close();
+45
View File
@@ -0,0 +1,45 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
import fs from 'fs';
const URLX = process.argv[2];
const TAG = process.argv[3] || 'x';
const wait = ms => new Promise(z => setTimeout(z, ms));
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x => x.type==='page' && x.url.includes(TAG))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(()=>{});
await wait(1000);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:URLX});
await wait(22000);
console.log('URL:', await c.evalJS('location.href'));
const loc = await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null);
for(let i=0;i<items.length;i++){ const l=items[i].querySelector('label'); if(l&&/店铺/.test(l.innerText||'')){
const s=items[i].querySelector('.ant-select'); if(s){ const r=s.getBoundingClientRect(); return JSON.stringify({i, label:l.innerText.trim(), x:Math.round(r.x+r.width/2), y:Math.round(r.y+r.height/2), cur:(s.querySelector('.ant-select-selection-item')||{}).innerText||''}); } } }
const s=[...document.querySelectorAll('.ant-select')].filter(e=>e.offsetParent!==null)[0];
if(!s) return 'no-select'; const r=s.getBoundingClientRect();
return JSON.stringify({i:-1, label:'(第一个)', x:Math.round(r.x+r.width/2), y:Math.round(r.y+r.height/2), cur:''});})()`);
console.log('店铺选择器:', loc);
const L = JSON.parse(String(loc));
if (L.x) {
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:L.x,y:L.y,button:'left',clickCount:1});
await wait(2500);
const all = new Set();
for (let i=0;i<20;i++){
const got = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null);
if(!dd.length)return'[]'; const d=dd[dd.length-1];
return JSON.stringify([...d.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').trim()).filter(Boolean));})()`);
const arr=JSON.parse(String(got)); const before=all.size; arr.forEach(x=>all.add(x));
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null);
if(!dd.length)return 0; const d=dd[dd.length-1];
const rc=d.querySelector('.rc-virtual-list-holder')||d; rc.scrollTop=rc.scrollTop+800; return rc.scrollTop;})()`);
await wait(700);
if (all.size===before && i>2) break;
}
const list=[...all];
fs.writeFileSync('/tmp/shops_'+TAG+'.json', JSON.stringify(list,null,1));
console.log('店铺数:', list.length);
list.sort((a,b)=>a.localeCompare(b));
list.forEach((n,i)=>console.log(' '+String(i+1).padStart(2)+'. '+n));
}
process.exit(0);
+32
View File
@@ -0,0 +1,32 @@
import { CDP, SAN } from '/tmp/cdp3.mjs';
const list=await (await fetch('http://127.0.0.1:9223/json/list')).json();
const t=list.find(x=>x.type==='page'&&/dianxiaomi/.test(x.url));
if(!t){console.log(' ❌');process.exit(0);}
const c=await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Runtime.enable');
const r=await c.evalJS(`(async()=>{
const out=[];
const cands=[['/api/ozonProduct/getShop.json','GET'],['/api/shop/getShopList.json','GET'],
['/api/ozonProduct/shopList.json','GET'],['/api/ozonProduct/getAuthShopList.json','GET'],
['/api/shop/list.json','GET'],['/api/ozonProduct/getShopList.json','POST']];
for(const [u,m] of cands){
try{
const res=await Promise.race([
fetch(u, m==='POST'?{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},body:''}
:{headers:{'X-Requested-With':'XMLHttpRequest'}}),
new Promise((_,rj)=>setTimeout(()=>rj(new Error('to')),10000))]);
const tx=await res.text();
if(res.status===200 && !tx.includes('<!DOCTYPE')) out.push(u+' -> '+tx.slice(0,420));
}catch(e){}
}
return JSON.stringify(out);})()`, true);
const arr=JSON.parse(r);
if(!arr.length) console.log(' 店铺接口都没找到');
arr.forEach(x=>console.log(' ★ '+SAN(x).slice(0,420)));
// 直接在页面上找店铺下拉里的映射
const dom=await c.evalJS(`(()=>{
const txt=document.body.innerText;
const i=txt.indexOf('Yejet');
return JSON.stringify({has:txt.includes('Yejet'), around:i>=0?txt.slice(Math.max(0,i-60),i+60).replace(/\\n/g,' | '):''});})()`);
console.log('\n 页面含 Yejet:', SAN(dom).slice(0,220));
c.close();
+21
View File
@@ -0,0 +1,21 @@
import { CDP, SAN } from '/tmp/cdp3.mjs';
const list=await (await fetch('http://127.0.0.1:9223/json/list')).json();
const t=list.find(x=>x.type==='page'&&/dianxiaomi/.test(x.url));
if(!t){console.log(' ❌');process.exit(0);}
const c=await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Runtime.enable');
const r=await c.evalJS(`(async()=>{
const out={};
// userIn 里通常带店铺
try{ const res=await fetch('/api/userIn.json',{headers:{'X-Requested-With':'XMLHttpRequest'}});
const tx=await res.text(); out.userIn=tx.slice(0,1200); }catch(e){ out.userIn='ERR'; }
// 页面上是否嵌了 shop 映射
out.htmlHas4248777 = document.documentElement.innerHTML.includes('4248777');
const m=document.documentElement.innerHTML.match(/.{0,80}4248777.{0,120}/g);
out.ctx = m? m.slice(0,3).map(x=>x.replace(/\\s+/g,' ')) : [];
return JSON.stringify(out);})()`, true);
const d=JSON.parse(r);
console.log(' userIn 片段:', SAN(String(d.userIn)).slice(0,600));
console.log('\n HTML 含 4248777:', d.htmlHas4248777);
(d.ctx||[]).forEach(x=>console.log(' 上下文: '+SAN(String(x)).slice(0,190)));
c.close();
+18
View File
@@ -0,0 +1,18 @@
import { CDP, SAN } from '/tmp/cdp3.mjs';
const list=await (await fetch('http://127.0.0.1:9223/json/list')).json();
const t=list.find(x=>x.type==='page'&&/dianxiaomi/.test(x.url));
if(!t){console.log(' ❌');process.exit(0);}
const c=await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Runtime.enable');
const r=await c.evalJS(`(async()=>{
const j=await (await fetch('/api/userIn.json',{headers:{'X-Requested-With':'XMLHttpRequest'}})).json();
const sm=(j.data||{}).shopMap||{};
const rows=Object.keys(sm).map(k=>({id:k, name:String(sm[k].name||''), plat:String(sm[k].platform||sm[k].site||'')}));
const target=rows.find(x=>x.id==='4248777');
return JSON.stringify({n:rows.length, target, yejet:rows.filter(x=>/yejet/i.test(x.name)).slice(0,8)});})()`, true);
const d=JSON.parse(r);
console.log(' shopMap 共', d.n, '个店铺');
console.log(' ══ shopId=4248777 是 ══');
console.log(' ', SAN(JSON.stringify(d.target)).slice(0,220));
console.log('\n 名称含 yejet 的店铺:', SAN(JSON.stringify(d.yejet)).slice(0,300));
c.close();
+40
View File
@@ -0,0 +1,40 @@
import { attach, SAN } from '/tmp/cdp3.mjs';
const c = await attach('/web/ozonProduct/draft') || await attach('dianxiaomi.com');
if(!c){ console.log('✗ 无页面'); process.exit(1); }
await c.send('Runtime.enable');
const r = await c.evalJS(`(async()=>{
const rr = await fetch('/api/userInfo.json?_t='+Date.now(), {headers:{'X-Requested-With':'XMLHttpRequest'}});
const tx = await rr.text();
if(!tx.startsWith('{')) return JSON.stringify({err:'notjson', head:tx.slice(0,120)});
const j = JSON.parse(tx); const d = j.data||{};
const out = { topKeys: Object.keys(d) };
// shopMap 结构
const sm = d.shopMap || d.shops || {};
out.shopMapType = Array.isArray(sm)?'array':typeof sm;
if (Array.isArray(sm)) {
out.shops = sm.map(s=>({id:s.id, idStr:s.idStr, name:s.name||s.shopName, platform:s.platform||s.platformName, site:s.site}));
} else {
out.shopMapKeys = Object.keys(sm).slice(0,40);
out.shops = [];
for (const [k,v] of Object.entries(sm)) {
if (Array.isArray(v)) v.forEach(s=>out.shops.push({platform:k, id:s.id, idStr:s.idStr, name:s.name||s.shopName, site:s.site}));
else if (v && typeof v === 'object') out.shops.push({platform:k, id:v.id, idStr:v.idStr, name:v.name||v.shopName});
}
}
// 平台列表
out.platformList = d.platformList || d.platforms || d.shopPlatformList || null;
return JSON.stringify(out);
})()`, true);
try{
const d = JSON.parse(String(r));
if (d.err) { console.log(' ✗', d.err, d.head); process.exit(0); }
console.log(' userInfo 顶层字段:', (d.topKeys||[]).join(', ').slice(0,300));
console.log(' shopMap 类型:', d.shopMapType);
if (d.shopMapKeys) console.log(' shopMap 键(平台):', d.shopMapKeys.join(', '));
console.log();
console.log(' ── 全部店铺 ──');
(d.shops||[]).forEach(s=>console.log(' ', String(s.platform).padEnd(16), 'id='+String(s.id||s.idStr).padEnd(12), s.name||''));
if (d.platformList) console.log();
if (d.platformList) console.log(' platformList:', SAN(JSON.stringify(d.platformList)).slice(0,400));
}catch(e){ console.log('err', e.message, SAN(String(r)).slice(0,600)); }
c.close();
+43
View File
@@ -0,0 +1,43 @@
import { attach, SAN } from '/tmp/cdp3.mjs';
const c = await attach('/web/ozonProduct/draft') || await attach('dianxiaomi.com');
await c.send('Runtime.enable');
const r = await c.evalJS(`(async()=>{
const rr = await fetch('/api/userInfo.json?_t='+Date.now(), {headers:{'X-Requested-With':'XMLHttpRequest'}});
const d = JSON.parse(await rr.text()).data;
const out = {};
// shopMap 一个条目的完整结构
const sm = d.shopMap||{};
const firstKey = Object.keys(sm)[0];
out.shopMapSample = JSON.stringify(sm[firstKey]).slice(0, 400);
// shopSmtTypeMap
out.smtTypeMap = JSON.stringify(d.shopSmtTypeMap||{}).slice(0, 400);
out.smtShopId = String(d.smtShopId);
out.shopId = String(d.shopId);
// platformPermission
out.platformPermission = JSON.stringify(d.platformPermission||{}).slice(0, 500);
// shopDataMap
const sdm = d.shopDataMap||{};
const k2 = Object.keys(sdm)[0];
out.shopDataMapSample = k2? (k2+' => '+JSON.stringify(sdm[k2]).slice(0,300)) : '(空)';
// 按 platform 归类
const byPlat = {};
for (const [k,v] of Object.entries(sm)) {
const p = (v&&(v.platform||v.platformName||v.type))||'?';
(byPlat[p]=byPlat[p]||[]).push((v&&(v.name||v.shopName))||k);
}
out.byPlatform = byPlat;
return JSON.stringify(out);
})()`, true);
try{
const d = JSON.parse(String(r));
console.log(' smtShopId:', d.smtShopId, '| shopId:', d.shopId);
console.log();
console.log(' shopMap 条目结构:'); console.log(' ', SAN(d.shopMapSample).slice(0,380));
console.log(' shopDataMap 样例:'); console.log(' ', SAN(d.shopDataMapSample).slice(0,300));
console.log(' shopSmtTypeMap:'); console.log(' ', SAN(d.smtTypeMap).slice(0,380));
console.log(' platformPermission:'); console.log(' ', SAN(d.platformPermission).slice(0,480));
console.log();
console.log(' ── 按平台归类 ──');
for (const [p, arr] of Object.entries(d.byPlatform||{})) console.log(' ' + p + ' (' + arr.length + '): ' + arr.slice(0,8).join(', '));
}catch(e){ console.log('err', e.message, SAN(String(r)).slice(0,700)); }
c.close();
+13
View File
@@ -0,0 +1,13 @@
[
"⑧Shein-易世通达-文具",
"⑦shein-易捷天宇-家居",
"⑩Shein-南京九个网-美妆",
"⑨Shein-南京亿千-运动",
"③shein-易捷天宇-美妆",
"⑤Shein-惠州九衢-内睡(袜子)",
"④Shein-武汉玖璨-家居",
"⑥Shein-南京亿千-文具",
"①Shein-武汉九个网-箱包",
"②Shein-易世通达-箱包",
"⑪-南京九个网-家居"
]
+17
View File
@@ -0,0 +1,17 @@
[
"①Alimingpro",
"②ALuckycao",
"③Bagbelle",
"④Caoup",
"⑤Bookinq",
"⑥Wxzgz",
"⑦Zdcjl",
"⑧Ewhe",
"⑨wowimartq",
"⑩JoyJumbe",
"⑪ Chugupi",
"⑫Qapple",
"⑬Wbalana",
"⑮Tapeach",
"⑭HoHaven"
]
+12
View File
@@ -0,0 +1,12 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf'; const box=it.querySelector('.sku-info-box')||it;
const first=[...box.querySelectorAll('.sku-option')].filter(e=>e.offsetParent!==null)[0];
if(!first)return'no-opt';
return JSON.stringify({outer:first.outerHTML.slice(0,600),
parentCls:String(first.parentElement.className||'').slice(0,60),
sibCount:first.parentElement.children.length});})()`);
console.log(String(o).slice(0,900));
process.exit(0);
+31
View File
@@ -0,0 +1,31 @@
import { attach, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
const all=JSON.parse(fs.readFileSync('/tmp/all48.json','utf8'));
const wm=JSON.parse(fs.readFileSync('/tmp/wm_verdicts.json','utf8'));
const clean=JSON.parse(fs.readFileSync('/tmp/wm_clean.json','utf8'));
const SHORT=['1027332215847','601964534137','750271307455'];
const c=await attach('/web/smt/smtProductList/draft')||await attach('dianxiaomi.com');
await c.send('Runtime.enable');
for(const oid of SHORT){
const info=all[oid];
const raw=await c.evalJS(`(async()=>{
const rr=await fetch('/api/ozonProduct/add.json?id=${info.pid}&_t='+Date.now(),{headers:{'X-Requested-With':'XMLHttpRequest'}});
const tx=await rr.text(); if(!tx.startsWith('{'))return'{}';
const p=JSON.parse(tx).data.product;
const s=new Set(); if(p.mainImage) s.add(p.mainImage);
(p.extraImages||'').split(';').filter(Boolean).forEach(u=>s.add(u));
return JSON.stringify({name:(p.name||'').slice(0,40), main:p.mainImage, urls:[...s]});
})()`,true);
const d=JSON.parse(String(raw));
console.log('\n ════ '+oid+' ════');
console.log(' '+d.name);
console.log(' 干净图: '+(clean[oid]||[]).length+' 张');
console.log(' ── 全部候选图的判定 ──');
(d.urls||[]).slice(0,18).forEach((u,i)=>{
const v=wm[u];
const tag = !v ? '?' : (v.err? '✗'+v.err : (v.wm||v.contact? '⚠水印':'✅干净'));
const txt = v && (v.text||v.reason) ? (' | '+String(v.text||v.reason).slice(0,50)) : '';
console.log(` [${i}] ${tag.padEnd(8)} ${u.split('/').pop().split('_')[0].slice(0,26)}${txt}`);
});
}
c.close();
+24
View File
@@ -0,0 +1,24 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const pick = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf'; const box=it.querySelector('.sku-info-box')||it;
const opts=[...box.querySelectorAll('.sku-option')].filter(e=>e.offsetParent!==null);
const hit=opts.find(e=>/Multicolor|Black\\(/.test(e.innerText||''))||opts[0];
if(!hit)return'no-opt';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked:'+(hit.innerText||'').trim();})()`);
console.log('选色:', pick);
await wait(6000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,15),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const d=JSON.parse(JSON.stringify(p._s.get('sheinSkuDataStore').$state));
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
return JSON.stringify({tbls, n:(d.formState.skuDataList||[]).length, row0:(d.formState.skuDataList||[])[0],
chosen:(a.formState.skuAttrs||[]).map(x=>({name:x.name, vals:(x.attributeValues||[]).map(v=>v.attribute_value||v) }))},null,1);})()`);
console.log(String(o).slice(0,1900));
process.exit(0);
+53
View File
@@ -0,0 +1,53 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
// 重填 SKU 行
const r = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const d=p._s.get('sheinSkuDataStore').$state.formState;
const rows=d.skuDataList||[]; let n=0;
rows.forEach((x,i)=>{ x.skcSupplierCode='SHEIN-'+String(i+1).padStart(3,'0'); x.skcRetailPrice='12.90';
x.supplyPrice='8.50'; x.stockVariety='999'; x.weight='100'; x.length='200'; x.width='150'; x.height='30'; n++; });
return 'rows:'+n;})()`);
console.log('重填 SKU:', r);
await wait(2500);
// 重填尺码表(走弹窗)
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/尺码表/.test(l.innerText||'');});
if(!it)return 0; const el=it.querySelector('.link')||it.querySelector('a,span'); if(!el)return 0;
el.scrollIntoView({block:'center'});
for(const ev of ['mousedown','mouseup','click']) el.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(5000);
const f = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const ins=[...m.querySelectorAll('input')].filter(i=>i.offsetParent!==null&&i.placeholder==='请输入');
const vals=['10','18','3','2','18','5']; let n=0;
ins.forEach((el,i)=>{ if(i>=vals.length)return; el.focus();
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,vals[i]);
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); n++; });
return 'filled:'+n;})()`);
console.log('尺码表:', f);
await wait(1200);
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(m){const b=[...m.querySelectorAll('button')].find(x=>/^\\s*确定\\s*$/.test(x.innerText||'')); if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));} return 1;})()`);
await wait(4000);
// 重填图片(可能也被清了)
await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const im=p._s.get('sheinImgStore').$state.formState;
const cur=im.mainImgData.multipleSpuImgs;
const urls=${JSON.stringify(['https://cbu01.alicdn.com/img/ibank/O1CN01IHtNAHKMzNC1chua_!!2222444614399-0-cib.jpg','https://cbu01.alicdn.com/img/ibank/O1CN01UGEWafTzqTH1chua_!!2222444614399-0-cib.jpg','https://cbu01.alicdn.com/img/ibank/O1CN01d0hA4N72jzD1chua_!!2222444614399-0-cib.jpg','https://cbu01.alicdn.com/img/ibank/O1CN01edgGxIlmdtJ1chua_!!2222444614399-0-cib.jpg'])};
if(!Array.isArray(cur)||cur.length<4){ im.mainImgData.multipleSpuImgs=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800})); }
return 'imgs:'+(Array.isArray(im.mainImgData.multipleSpuImgs)?im.mainImgData.multipleSpuImgs.length:'?');})()`);
await wait(2500);
// 保存
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(12000);
console.log('捕获:', String(await c.evalJS(`JSON.stringify(window.__cap||[])`)).slice(0,300));
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,3)));
const t = await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,130)).filter(Boolean).slice(0,3))`);
console.log('提示:', String(t).slice(0,400));
process.exit(0);
+35
View File
@@ -0,0 +1,35 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const SHOP = process.argv[2];
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x=>x.type==='page'&&/shein/i.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(()=>{});
await wait(1000);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/sheinProduct/add'});
await wait(21000);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=(it||document).querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(2500);
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(SHOP)});
if(hit) for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(8000);
// 开分类下拉
const cp = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:cp[0],y:cp[1],button:'left',clickCount:1});
await wait(3500);
const o = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd';
const opts=[...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
return JSON.stringify({n:opts.length, opts:opts.slice(0,26)});})()`);
console.log('════', SHOP);
console.log(String(o).slice(0,1100));
process.exit(0);
+21
View File
@@ -0,0 +1,21 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/尺码表/.test(l.innerText||'');});
if(!it)return'[]'; const el=it.querySelector('.link')||it.querySelector('a,span');
if(!el)return'[]'; el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
console.log('编辑尺码表坐标:', JSON.stringify(pos));
if(pos.length){ for(const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:pos[0],y:pos[1],button:'left',clickCount:1}); }
await wait(6000);
const o = await c.evalJS(`(()=>{
const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(m=>({t:(m.querySelector('.ant-modal-title')||{}).innerText||'',
inputs:[...m.querySelectorAll('input')].map(i=>i.placeholder||i.type).slice(0,8),
btns:[...m.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).slice(0,8),
txt:(m.innerText||'').replace(/\\s+/g,' ').slice(0,320)}));
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
let sz=null; try{ sz=JSON.stringify(p._s.get('sheinSkuAttrStore').$state.formState.sizeTableInfo).slice(0,200);}catch(e){}
return JSON.stringify({modals:ms, sizeTableInfo:sz},null,1);})()`);
console.log(String(o).slice(0,1400));
process.exit(0);
+18
View File
@@ -0,0 +1,18 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
// 打开尺码表弹窗
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/尺码表/.test(l.innerText||'');});
if(!it)return'[]'; const el=it.querySelector('.link')||it.querySelector('a,span'); if(!el)return'[]';
el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(pos.length){ for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1}); }
await wait(6000);
const o = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal';
const rows=[...m.querySelectorAll('tr')].slice(0,10).map(tr=>[...tr.querySelectorAll('td,th')].map(td=>{
const i=td.querySelector('input'); return i?('IN:'+(i.placeholder||i.value||'')):(td.innerText||'').replace(/\\s+/g,' ').trim().slice(0,18);}));
return JSON.stringify({title:(m.querySelector('.ant-modal-title')||{}).innerText||'', rows, btns:[...m.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).slice(0,10)},null,1);})()`);
console.log(String(o).slice(0,1500));
process.exit(0);
+47
View File
@@ -0,0 +1,47 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
// 填 6 格
const f = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const ins=[...m.querySelectorAll('input')].filter(i=>i.offsetParent!==null);
const vals=['10','18','3','2','18','5']; let n=0;
ins.forEach((el,i)=>{ if(i>=vals.length)return;
el.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el, vals[i]);
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true}));
el.dispatchEvent(new Event('blur',{bubbles:true})); n++; });
return 'filled:'+n+'/'+ins.length;})()`);
console.log('填尺码表:', f);
await wait(1500);
// 确定
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return 0; const b=[...m.querySelectorAll('button')].find(x=>/^\\s*确定\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(5000);
const st = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=p._s.get('sheinSkuAttrStore').$state.formState;
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/尺码表/.test(l.innerText||'');});
return JSON.stringify({sizeTableInfo:String(JSON.stringify(a.sizeTableInfo)).slice(0,200), ui:(it?it.innerText:'').replace(/\\s+/g,' ').slice(0,60)});})()`);
console.log('尺码表状态:', String(st).slice(0,300));
// 保存
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
const x=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim())); if(!x)return 0;
for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(14000);
await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
for(const m of ms){const b=[...m.querySelectorAll('button')].find(x=>/确定|确认|继续编辑/.test(x.innerText||'')); if(b)b.click();} return 1;})()`);
await wait(4000);
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,4)));
const red = await c.evalJS(`(()=>{const red=[]; document.querySelectorAll('*').forEach(el=>{ if(el.offsetParent===null||el.children.length)return;
const t=(el.innerText||'').trim(); if(!t||t.length>60)return;
const s=getComputedStyle(el); const m=(s.color||'').match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/); if(!m)return;
if(+m[1]>160&&+m[2]<140&&+m[3]<140) red.push(t); });
const toasts=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean);
return JSON.stringify({red:[...new Set(red)].slice(0,10), toasts:[...new Set(toasts)].slice(0,3)});})()`);
console.log('红字/提示:', String(red).slice(0,600));
process.exit(0);
+38
View File
@@ -0,0 +1,38 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/shein/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
const f = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal';
const ins=[...m.querySelectorAll('input')].filter(i=>i.offsetParent!==null&&i.placeholder==='请输入');
const vals=['10','18','3','2','18','5']; let n=0;
ins.forEach((el,i)=>{ if(i>=vals.length)return;
el.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el, vals[i]);
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true}));
el.dispatchEvent(new Event('blur',{bubbles:true})); n++; });
return 'filled:'+n+'/'+ins.length+' vals='+ins.map(i=>i.value).join(',');})()`);
console.log('填:', f);
await wait(1500);
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return 0; const b=[...m.querySelectorAll('button')].find(x=>/^\\s*确定\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(5000);
const st = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=p._s.get('sheinSkuAttrStore').$state.formState;
const openM=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null&&/编辑尺码表/.test(e.innerText||'')).length;
return JSON.stringify({sizeTableInfo:String(JSON.stringify(a.sizeTableInfo)).slice(0,240), modalOpen:openM});})()`);
console.log('尺码表:', String(st).slice(0,340));
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(!x)return 0; for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(14000);
await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
for(const m of ms){const b=[...m.querySelectorAll('button')].find(x=>/确定|确认|继续编辑/.test(x.innerText||'')); if(b)b.click();} return 1;})()`);
await wait(4000);
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,4)));
const t = await c.evalJS(`(()=>[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120)).filter(Boolean).slice(0,3))()`);
console.log('提示:', String(t).slice(0,400));
process.exit(0);
+15
View File
@@ -0,0 +1,15 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf';
const box=it.querySelector('.sku-info-box')||it;
// 列出所有叶子的 tag/class/文本
const leaves=[...box.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&!e.children.length&&(e.innerText||'').trim())
.slice(0,14).map(e=>({tag:e.tagName, cls:String(e.className||'').slice(0,55), t:(e.innerText||'').trim().slice(0,18)}));
const clickable=[...box.querySelectorAll('button,.ant-btn,.ant-tag,.ant-checkbox-wrapper,[class*=tag],[class*=item],[class*=option]')]
.filter(e=>e.offsetParent!==null).slice(0,12).map(e=>({tag:e.tagName, cls:String(e.className||'').slice(0,50), t:(e.innerText||'').trim().slice(0,16)}));
return JSON.stringify({leafCount:box.querySelectorAll('*').length, leaves, clickable},null,1);})()`);
console.log(String(o).slice(0,2000));
process.exit(0);
+30
View File
@@ -0,0 +1,30 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
// 1. 主规格控件
const o1 = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/主规格/.test(l.innerText||'');});
if(!it)return'nf'; return JSON.stringify({txt:(it.innerText||'').replace(/\\s+/g,' ').slice(0,160), html:it.innerHTML.slice(0,500)});})()`);
console.log('主规格:', String(o1).slice(0,700));
// 2. 尺码表控件
const o2 = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/尺码表/.test(l.innerText||'');});
if(!it)return'nf'; return JSON.stringify({txt:(it.innerText||'').replace(/\\s+/g,' ').slice(0,160), html:it.innerHTML.slice(0,400)});})()`);
console.log('尺码表:', String(o2).slice(0,600));
// 3. 图片区
const o3 = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/图片/.test(l.innerText||'');});
if(!it)return'nf'; return JSON.stringify({lab:(it.querySelector('label')||{}).innerText||'', txt:(it.innerText||'').replace(/\\s+/g,' ').slice(0,200)});})()`);
console.log('图片区:', String(o3).slice(0,500));
// 4. 主规格可选值(第一个属性)
const o4 = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=JSON.parse(JSON.stringify(p._s.get('sheinSkuAttrStore').$state));
const at=(a.formState.skuAttrs||[])[0]||{};
return JSON.stringify({attrName:at.name, attrId:at.attributeId, optCount:(at._options||[]).length,
opt0:(at._options||[])[0], chosen:at.attributeValues||at.checked||null});})()`);
console.log('主规格属性:', String(o4).slice(0,600));
// 5. 是否有必填标记的项
const o5 = await c.evalJS(`(()=>[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null&&e.querySelector('.ant-form-item-required'))
.map(x=>{const l=x.querySelector('label'); return l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';}).filter(Boolean).slice(0,15))()`);
console.log('必填项:', String(o5).slice(0,400));
process.exit(0);
+186
View File
@@ -0,0 +1,186 @@
{
"548459586741": {
"ok": true,
"cat": "picked[0/52]:Health(保健)/Sexual Wellness(情趣用品)/Sex Toys(情趣玩具) "
},
"764423265986": {
"ok": true,
"cat": "picked[0/1]:Health(保健)/Medical Supplies(医疗保健)/Health Monitors & Tests(健康仪器与测试仪)/Thermometers(温度计) "
},
"685456757807": {
"ok": true,
"cat": "picked[0/1]:Home Appliances(家用电器)/Small Household Appliances(小型家电)/Sewing Machines & Accessories(缝纫机 & 配件) "
},
"735147938123": {
"ok": true,
"cat": "picked[1/8]:Fashion Accessories(时尚配饰)/Hair Accessories(发饰)/Hair Clips & Hair Pins(发夹) "
},
"627879474686": {
"ok": true,
"cat": "picked[0/8]:Baby & Kids Fashion(童装)/Baby & Kids Accessories(婴儿与儿童配饰)/Socks(袜子) "
},
"610164857063": {
"ok": true,
"cat": "picked[1/8]:Fashion Accessories(时尚配饰)/Hair Accessories(发饰)/Hair Clips & Hair Pins(发夹) "
},
"676236596821": {
"ok": true,
"cat": "picked[0/8]:Baby & Kids Fashion(童装)/Baby & Kids Accessories(婴儿与儿童配饰)/Hair Accessories(发饰) "
},
"708420302844": {
"ok": true,
"cat": "picked[0/3]:Automobiles(汽车类)/Automotive Keychains & Key Covers(汽车钥匙扣和钥匙套) "
},
"866537106657": {
"ok": true,
"cat": "picked[0/1]:Home Appliance (家用电器)/Small Hou ehold Appliance (小型家电)/Sewing Machine & Acce orie (缝纫机 & 配件) "
},
"547183997437": {
"ok": true,
"cat": "picked[1/3]:Fa hion Acce orie (时尚配饰)/Fine Jewelry(轻奢珠宝)/Necklace (项链) "
},
"657617635303": {
"ok": true,
"cat": "picked[0/56]:Stationery(文具)/Art Supplie (美术用品)/Acrylic Paint(亚克力颜料) "
},
"641673262387": {
"ok": true,
"cat": "picked[14/15]:Home & Living(家居生活)/Home Organizer (居家收纳)/Jewelry Organizer (首饰收纳) "
},
"953869908708": {
"ok": true,
"cat": "picked[0/8]:Automobile (汽车类)/Automobile Exterior Acce orie (车外配件)/Sticker , Logo & Emblem (车贴、车标改装) "
},
"703822717997": {
"ok": true,
"cat": "picked[0/56]:Stationery(文具)/Art Supplie (美术用品)/Acrylic Paint(亚克力颜料) "
},
"863524134410": {
"ok": true,
"cat": "picked[0/8]:Automobile (汽车类)/Automobile Exterior Acce orie (车外配件)/Sticker , Logo & Emblem (车贴、车标改装) "
},
"694637537727": {
"ok": true,
"cat": "picked[5/67]:Beauty(美妆保健)/Beauty Tool (美容工具)/Makeup Acce orie (化妆工具)/Makeup Bag & Organizer (化妆包与收纳袋) "
},
"623011262210": {
"ok": true,
"cat": "picked[0/8]:Automobile (汽车类)/Automobile Exterior Acce orie (车外配件)/Sticker , Logo & Emblem (车贴、车标改装) "
},
"836512561982": {
"ok": true,
"cat": "picked[0/1]:Beauty(美妆保健)/Beauty Tool (美容工具)/Makeup Acce orie (化妆工具)/Makeup Bag & Organizer (化妆包与收纳袋) "
},
"714492799386": {
"ok": true,
"cat": "picked[0/1]:Beauty(美妆保健)/Beauty Tool (美容工具)/Makeup Acce orie (化妆工具)/Makeup Bag & Organizer (化妆包与收纳袋) "
},
"591994902720": {
"ok": true,
"cat": "picked[0/1]:Stationery(文具)/School & Office Equipment(学校与办公用品)/Pencil Ca e (文具盒/笔袋) "
},
"853414558425": {
"ok": true,
"cat": "picked[0/3]:Automobile (汽车类)/Automotive Keychain & Key Cover (汽车钥匙扣和钥匙套) "
},
"997450831819": {
"ok": true,
"cat": "picked[0/8]:Automobile (汽车类)/Automobile Exterior Acce orie (车外配件)/Sticker , Logo & Emblem (车贴、车标改装) "
},
"639409307567": {
"ok": true,
"cat": "picked[0/1]:Home & Living(家居生活)/Decoration(装饰)/Wallpaper & Wall Sticker (墙纸与窗贴) "
},
"902440558099": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"823838532886": {
"ok": true,
"cat": "picked[0/2]:Men Bag (男包)/Wallet (钱包)/Card Holder (卡包) "
},
"698782391248": {
"ok": true,
"cat": "picked[0/8]:Automobile (汽车类)/Automobile Exterior Acce orie (车外配件)/Sticker , Logo & Emblem (车贴、车标改装) "
},
"707826905566": {
"ok": true,
"cat": "picked[1/13]:Men Bag (男包)/Wallet (钱包)/Bifold & Trifold Wallet (双折&三折钱包) "
},
"1027332215847": {
"ok": true,
"cat": "picked[0/2]:Home & Living(家居生活)/Home Organizer (居家收纳)/Shoe Storage Boxe (鞋盒) "
},
"682195499823": {
"ok": true,
"cat": "picked[0/56]:Stationery(文具)/Art Supplie (美术用品)/Acrylic Paint(亚克力颜料) "
},
"823560564174": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"739147684759": {
"ok": true,
"cat": "picked[0/1]:Home Appliance (家用电器)/Small Hou ehold Appliance (小型家电)/Sewing Machine & Acce orie (缝纫机 & 配件) "
},
"621300323670": {
"ok": true,
"cat": "picked[0/56]:Stationery(文具)/Art Supplie (美术用品)/Acrylic Paint(亚克力颜料) "
},
"896469995553": {
"ok": true,
"cat": "picked[0/3]:Automobile (汽车类)/Automotive Keychain & Key Cover (汽车钥匙扣和钥匙套) "
},
"944470567338": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"787261097583": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"833753630925": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"719391062058": {
"ok": true,
"cat": "picked[0/2]:Home & Living(家居生活)/Home Organizer (居家收纳)/Shoe Storage Boxe (鞋盒) "
},
"857781294719": {
"ok": true,
"cat": "picked[0/8]:Baby & Kid Fa hion(童装)/Baby & Kid Acce orie (婴儿与儿童配饰)/Hair Acce orie (发饰) "
},
"863384503032": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"774494945453": {
"ok": true,
"cat": "picked[0/3]:Automobile (汽车类)/Automotive Keychain & Key Cover (汽车钥匙扣和钥匙套) "
},
"750271307455": {
"ok": true,
"cat": "picked[0/3]:Automobile (汽车类)/Automotive Keychain & Key Cover (汽车钥匙扣和钥匙套) "
},
"726442972676": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"775057912118": {
"ok": true,
"cat": "picked[0/3]:Automobile (汽车类)/Automotive Keychain & Key Cover (汽车钥匙扣和钥匙套) "
},
"893592264148": {
"ok": true,
"cat": "picked[0/8]:Baby & Kid Fa hion(童装)/Baby & Kid Acce orie (婴儿与儿童配饰)/Hair Acce orie (发饰) "
},
"607330049419": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
},
"854003999648": {
"ok": true,
"cat": "picked[1/8]:Fa hion Acce orie (时尚配饰)/Hair Acce orie (发饰)/Hair Clip & Hair Pin (发夹) "
}
}
+1
View File
@@ -0,0 +1 @@
{"n":172,"items":[{"id":"184807703510036273","oid":"854003999648","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510035741","oid":"607330049419","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510035057","oid":"893592264148","shop":6142973,"title":"","cat":101029,"catPath":""},{"id":"184807703510034417","oid":"775057912118","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510033855","oid":"726442972676","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510033465","oid":"750271307455","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510033029","oid":"774494945453","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510032637","oid":"863384503032","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510032293","oid":"857781294719","shop":6142973,"title":"","cat":101029,"catPath":""},{"id":"184807703510031901","oid":"719391062058","shop":6142973,"title":"","cat":101255,"catPath":""},{"id":"184807703510031383","oid":"833753630925","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510030917","oid":"787261097583","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510030569","oid":"944470567338","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510030139","oid":"896469995553","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510029645","oid":"621300323670","shop":6142973,"title":"","cat":101365,"catPath":""},{"id":"184807703510029069","oid":"739147684759","shop":6142973,"title":"","cat":100173,"catPath":""},{"id":"184807703510028623","oid":"823560564174","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510028233","oid":"682195499823","shop":6142973,"title":"","cat":101365,"catPath":""},{"id":"184807703510027575","oid":"1027332215847","shop":6142973,"title":"","cat":101255,"catPath":""},{"id":"184807703510026781","oid":"707826905566","shop":6142973,"title":"","cat":100611,"catPath":""},{"id":"184807703510025839","oid":"698782391248","shop":6142973,"title":"","cat":101435,"catPath":""},{"id":"184807703510025179","oid":"823838532886","shop":6142973,"title":"","cat":100608,"catPath":""},{"id":"184807703510024541","oid":"902440558099","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510023971","oid":"639409307567","shop":6142973,"title":"","cat":101157,"catPath":""},{"id":"184807703510023389","oid":"997450831819","shop":6142973,"title":"","cat":101435,"catPath":""},{"id":"184807703510022689","oid":"853414558425","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510022085","oid":"591994902720","shop":6142973,"title":"","cat":101359,"catPath":""},{"id":"184807703510021797","oid":"714492799386","shop":6142973,"title":"","cat":101650,"catPath":""},{"id":"184807703510021497","oid":"836512561982","shop":6142973,"title":"","cat":101650,"catPath":""},{"id":"184807703510021291","oid":"623011262210","shop":6142973,"title":"","cat":101435,"catPath":""},{"id":"184807703510021059","oid":"694637537727","shop":6142973,"title":"","cat":101650,"catPath":""},{"id":"184807703510020731","oid":"863524134410","shop":6142973,"title":"","cat":101435,"catPath":""},{"id":"184807703510020443","oid":"703822717997","shop":6142973,"title":"","cat":101365,"catPath":""},{"id":"184807703510020027","oid":"953869908708","shop":6142973,"title":"","cat":101435,"catPath":""},{"id":"184807703510019455","oid":"641673262387","shop":6142973,"title":"","cat":101260,"catPath":""},{"id":"184807703510018695","oid":"657617635303","shop":6142973,"title":"","cat":101365,"catPath":""},{"id":"184807703510018191","oid":"547183997437","shop":6142973,"title":"","cat":102064,"catPath":""},{"id":"184807703510017925","oid":"866537106657","shop":6142973,"title":"","cat":100173,"catPath":""},{"id":"184807703510017639","oid":"708420302844","shop":6142973,"title":"","cat":100753,"catPath":""},{"id":"184807703510017247","oid":"676236596821","shop":6142973,"title":"","cat":101029,"catPath":""},{"id":"184807703510016863","oid":"610164857063","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510016495","oid":"627879474686","shop":6142973,"title":"","cat":101032,"catPath":""},{"id":"184807703510016073","oid":"735147938123","shop":6142973,"title":"","cat":100147,"catPath":""},{"id":"184807703510015731","oid":"685456757807","shop":6142973,"title":"","cat":100173,"catPath":""},{"id":"184807703510014969","oid":"764423265986","shop":6142973,"title":"","cat":100423,"catPath":""},{"id":"184807703510014619","oid":"548459586741","shop":6142973,"title":"","cat":100142,"catPath":""},{"id":"184807703510012679","oid":"661190598139","shop":6142973,"title":"","cat":101029,"catPath":""},{"id":"4810365555229229","oid":"733058615988","shop":6100336,"title":"","cat":101349,"catPath":""},{"id":"4810365430386949","oid":"693118601337","shop":3377110,"title":"","cat":101639,"catPath":""},{"id":"4810365324256099","oid":"573Line truncated
+172
View File
@@ -0,0 +1,172 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── Shopee (Ljbgkj, shopId 6142973) 单件建品配方 ──
node spbatch.mjs <oid> [catKeyword]
选店铺 → 分类弹窗 → 单件 → 标题/描述 → 价格(CNY)/库存 → 重量/尺寸
→ 图片 → 来源URL → 保存 → 回读 */
const OID = process.argv[2];
const CAT_KW = process.argv[3] || '';
if (!OID) { console.log('用法: node spbatch.mjs <oid> [catKeyword]'); process.exit(1); }
const IN = JSON.parse(fs.readFileSync('/tmp/tk_input.json', 'utf8'))[OID];
if (!IN) { console.log('无输入数据', OID); process.exit(1); }
const SHOP = 'Ljbgkj';
const IS_KID = /baby|kid|infant|toddler|children|newborn/i.test(IN.title);
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const DIMS = (() => { const W = Math.max(100, Math.round(IN.weight * 1.3));
return { L: Math.max(60, Math.min(400, W)), Wi: Math.max(40, Math.min(300, Math.round(W * 0.6))), H: Math.max(10, Math.min(200, Math.round(W * 0.2))) }; })();
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && /shopee/i.test(x.url))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理 shopee 页 ${k} 个`); await wait(1200); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/shopeeGlobal/add' });
await wait(22000);
// 1. 店铺
async function pickShop() {
for (let a = 0; a < 3; a++) {
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺|商家账号/.test(l.innerText||'');});
const s=it?it.querySelector('.ant-select-selector'):[...document.querySelectorAll('.ant-select-selector')].filter(e=>e.offsetParent!==null)[0];
if(!s)return'[]'; s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if (!pos.length) return 'no-select';
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: pos[0], y: pos[1], button: 'left', clickCount: 1 });
await wait(2200);
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>new RegExp('^\\\\s*${SHOP}\\\\s*$','i').test(o.innerText||''));
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'picked';})()`);
await wait(7000);
const cur = String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺|商家账号/.test(l.innerText||'');});
const s=it&&it.querySelector('.ant-select-selection-item'); return s?s.innerText.trim():'';})()`));
if (new RegExp(SHOP, 'i').test(cur)) return 'ok:' + cur;
log(' 店铺重试', a + 1, r, JSON.stringify(cur));
}
return 'fail';
}
// 2. 分类(同 TikTok 结构)
async function pickCategory(kw) {
await c.evalJS(`(()=>{document.querySelectorAll('.ant-select-dropdown').forEach(d=>{if(d.offsetParent!==null)d.style.display='none';}); document.body.click(); return 1;})()`);
await wait(900);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'[]'; const b=[...it.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*选择分类\\s*$/.test((e.innerText||'').trim()));
if(!b.length)return'[]'; const el=b[b.length-1]; el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if (!pos.length) return 'no-btn';
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: pos[0], y: pos[1], button: 'left', clickCount: 1 });
await wait(3500);
const r = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const inp=m.querySelector('input[placeholder="搜索分类名称"]'); if(!inp)return'no-input';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(kw)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
const b=[...m.querySelectorAll('button')].find(x=>/^\\s*搜索\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'searched';})()`);
if (r !== 'searched') return r;
await wait(4000);
const pick = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const its=[...m.querySelectorAll('.search-result-item')]; if(!its.length)return'no-result';
const BAD=/二手|Barang Terpakai|Second-?hand|Used|Pre-?owned|Refurb|Sexual|Sex Toy|情趣|Adult Toy|Dewasa/i;
const KID=/Bayi|Baby|Kids|Kid|Infant|Toddler|Children|Kanak/i;
const ISK=${IS_KID};
const KW=${JSON.stringify(kw)}.toLowerCase();
const texts=its.map(e=>(e.innerText||'').replace(/\s+/g,' ').trim());
const lastSeg=t=>{const a=t.split('/'); return (a[a.length-1]||'').replace(/\(.*?\)/g,'').trim().toLowerCase();};
let idx=texts.findIndex(t=>!BAD.test(t) && lastSeg(t).includes(KW));
if(idx<0) idx=texts.findIndex(t=>!BAD.test(t) && t.toLowerCase().includes(KW) && (ISK || !KID.test(t)));
if(idx<0) idx=texts.findIndex(t=>!BAD.test(t) && (ISK || !KID.test(t)));
if(idx<0) idx=texts.findIndex(t=>!BAD.test(t));
if(idx<0) idx=0;
const hit=its[idx];
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked['+idx+'/'+texts.length+']:'+texts[idx];})()`);
await wait(4500);
return pick;
}
async function pickCategoryAny(kws) {
const tried = [];
for (const kw of kws) {
const r = String(await pickCategory(kw));
tried.push(kw + '\u2192' + r.slice(0, 46));
if (!/no-result|no-input|no-modal|no-btn/.test(r)) return { r, tried };
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(m){const x=m.querySelector('.ant-modal-close'); if(x&&x.offsetParent!==null)x.click();} return 1;})()`);
await wait(1500);
}
return { r: 'ALL-FAILED', tried };
}
log('=== Shopee 建品 ===', OID, IN.title.slice(0, 46));
log('1) 店铺:', await pickShop());
const kw = CAT_KW || IN.sp_kw || 'Accessories';
const catRes = await pickCategoryAny([kw].concat(IN.sp_fb || []));
log('2) 分类:', SAN(String(catRes.r)).slice(0, 120), '| 尝试:', catRes.tried.join(' / '));
if (/ALL-FAILED|no-result|no-input|no-btn/.test(String(catRes.r))) { log('\u274c 分类未选中,跳过保存'); process.exit(2); }
// 3. 单件模式 + 文案
const wr = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const ps=p._s.get('shopeeGlobalProductStore').$state.formState;
ps.type='single';
ps.title=${JSON.stringify(IN.title)};
const txt=${JSON.stringify(IN.bullets.map(b => '• ' + b).join('\n') + '\n\nKeywords: ' + (IN.keywords || ''))};
ps.descriptionInfo={descriptionText:txt, descriptionHtml:'<p>'+${JSON.stringify(IN.bullets.join('<br>'))}+'</p>'};
return 'ok:'+ps.title.length;})()`);
log('3) 文案:', wr);
await wait(2000);
// 4. 价格 / 库存 / 重量 / 尺寸
const setItem = (rx, sel, val) => c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(rx)}).test(l.innerText||'');});
const el=it?it.querySelector(${JSON.stringify(sel)}):null; if(!el)return'nf';
const proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el, ${JSON.stringify(String(val))});
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); return 'ok';})()`);
log('4) 价格(CNY):', await setItem('价格', 'input:not([type=hidden])', IN.price_cny));
log(' 库存:', await setItem('库存', 'input:not([type=hidden])', '999'));
log(' 重量:', await setItem('重量', 'input:not([type=hidden])', IN.weight));
const dim = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&/尺寸规格/.test(l.innerText||'');});
if(!it)return'nf'; const ins=[...it.querySelectorAll('input')];
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const L=ins.find(i=>i.placeholder==='长')||ins[0], W2=ins.find(i=>i.placeholder==='宽')||ins[1], H=ins.find(i=>i.placeholder==='高')||ins[2];
if(L)setV(L,${DIMS.L}); if(W2)setV(W2,${DIMS.Wi}); if(H)setV(H,${DIMS.H}); return 'ok:'+ins.length;})()`);
log('5) 尺寸:', dim, JSON.stringify(DIMS));
// 5. 图片
const ir = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const st=p._s.get('shopeeGlobalImageInfoStore').$state;
const urls=${JSON.stringify(IN.imgs)};
st.imageInfo.mainImages=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800,checked:true}));
return 'imgs:'+st.imageInfo.mainImages.length;})()`);
log('6) 图片:', ir, '(共', IN.imgs.length, ')');
await wait(2500);
log('7) 来源URL:', await setItem('来源URL', 'input:not([type=hidden])', 'https://detail.1688.com/offer/' + OID + '.html'));
await wait(1200);
const st = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const b=p._s.get('shopeeGlobalBasicStore').$state;
return JSON.stringify({shopId:b.formState.shopId, cur:b.dataState.currency});})()`);
log('8) 保存前:', st);
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('9) 保存:', sv);
await wait(9000);
const msg = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null)
.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,170)).filter(Boolean); return JSON.stringify({url:location.href, msgs:ms.slice(0,4)});})()`);
log('10) 结果:', SAN(String(msg)).slice(0, 400));
await c.shot('/tmp/sp_pilot.jpeg').catch(() => {});
process.exit(0);
+94
View File
@@ -0,0 +1,94 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
/* 就地改 Shopee 产品类目:node spfixcat.mjs <productId> <title> <kw1,kw2,...> */
const PID = process.argv[2];
const TITLE = process.argv[3] || '';
const KWS = (process.argv[4] || '').split(',').filter(Boolean);
if (!PID || !KWS.length) { console.log('用法: node spfixcat.mjs <productId> <title> <kw1,kw2>'); process.exit(1); }
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const IS_KID = /baby|kid|infant|toddler|children|newborn/i.test(TITLE);
const IS_AUTO = /car|auto|moto|vehicle|truck|bicycle|bike/i.test(TITLE);
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x => x.type === 'page' && /shopee/i.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {});
await wait(1200);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: `https://www.dianxiaomi.com/web/shopeeGlobal/edit?id=${PID}` });
await wait(26000);
for (let i = 0; i < 8; i++) {
const x = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
if(!ms.length)return'n'; ms.forEach(m=>{const cb=m.querySelector('.ant-modal-close'); if(cb&&cb.offsetParent!==null)cb.click();}); return 'c';})()`);
if (x === 'n') break; await wait(1000);
}
await wait(2000);
log('URL:', await c.evalJS('location.href'));
log('店铺:', await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺|商家账号/.test(l.innerText||'');});
const s=it&&it.querySelector('.ant-select-selection-item'); return s?s.innerText.trim():'';})()`));
async function pickCategory(kw) {
await c.evalJS(`(()=>{document.querySelectorAll('.ant-select-dropdown').forEach(d=>{if(d.offsetParent!==null)d.style.display='none';}); document.body.click(); return 1;})()`);
await wait(900);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'[]'; const b=[...it.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*选择分类\\s*$/.test((e.innerText||'').trim()));
if(!b.length)return'[]'; const el=b[b.length-1]; el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if (!pos.length) return 'no-btn';
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: pos[0], y: pos[1], button: 'left', clickCount: 1 });
await wait(3500);
const r = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const inp=m.querySelector('input[placeholder="搜索分类名称"]'); if(!inp)return'no-input';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(kw)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
const b=[...m.querySelectorAll('button')].find(x=>/^\\s*搜索\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'searched';})()`);
if (r !== 'searched') return r;
await wait(4000);
const pick = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const its=[...m.querySelectorAll('.search-result-item')]; if(!its.length)return'no-result';
const BAD=/二手|Second-?hand|Used|Pre-?owned|Refurb|Sexual|Sex Toy|情趣|Adult Toy|Dewasa/i;
const KID=/Bayi|Baby|Kids|Kid|Infant|Toddler|Children|Kanak/i;
const AUTO=/Automobile|Automotive|Motosikal|汽车|车贴|Emblem/i;
const ISK=${IS_KID}, ISA=${IS_AUTO};
const KW=${JSON.stringify(kw)}.toLowerCase();
const texts=its.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
const ok=t=>!BAD.test(t)&&(ISK||!KID.test(t))&&(ISA||!AUTO.test(t));
const lastSeg=t=>{const a=t.split('/'); return (a[a.length-1]||'').replace(/\\(.*?\\)/g,'').trim().toLowerCase();};
let idx=texts.findIndex(t=>ok(t)&&lastSeg(t).includes(KW));
if(idx<0) idx=texts.findIndex(t=>ok(t)&&t.toLowerCase().includes(KW));
if(idx<0) idx=texts.findIndex(t=>ok(t));
if(idx<0) idx=texts.findIndex(t=>!BAD.test(t));
if(idx<0) idx=0;
const hit=its[idx];
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked['+idx+'/'+texts.length+']:'+texts[idx];})()`);
await wait(4500);
return pick;
}
let done = null;
for (const kw of KWS) {
const r = String(await pickCategory(kw));
log(' ', kw, '→', SAN(r).slice(0, 130));
if (!/no-result|no-input|no-modal|no-btn/.test(r)) { done = r; break; }
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(m){const x=m.querySelector('.ant-modal-close'); if(x&&x.offsetParent!==null)x.click();} return 1;})()`);
await wait(1500);
}
if (!done) { log('❌ 全部关键词无结果'); process.exit(2); }
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('保存:', sv);
await wait(9000);
const msg = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice')].filter(e=>e.offsetParent!==null)
.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,140)).filter(Boolean); return JSON.stringify(ms.slice(0,3));})()`);
log('结果:', SAN(String(msg)).slice(0, 260));
process.exit(done ? 0 : 2);
+58
View File
@@ -0,0 +1,58 @@
# Temu 全托管(①Alimingpro, shopId 4021529)进展
## 已勘明
- 类目:1038 个,直接从 `temuAddBasicStore.dataState.categories` 读(不用滚动)
- 选类目:下拉需**先 scrollTop=0**(默认残留上次滚动位置)→ 滚动找 `.ant-select-item-option`
- 外包装形状:不规则 / 长方体 / 圆柱体
- 外包装类型:硬包装 / 软包装+硬物 / 软包装+软物
- 字段落点:
- `temuAddProductStore.formState.mainImage` → **数组**
- `temuAddProductStore.formState.materialImage` → **对象**(单个)
- `temuAddOtherStore.packageFormState` → `{packageShape, packageType, packageImages, inventoryRegion}`
- `temuAddSkuAttrStore.formState.skuAttrs` → `[{type:2,parentSpecId:1001,parentSpecName:"颜色",options:[]}]`
- `temuAddSkuDataStore.formState.skuDataList` → SKU 行
- 必填 9 项:店铺/分类/标题/产地/轮播图/素材图/外包装形状/外包装类型/外包装图片
## 未解(卡点)
1. 点「保存」不发任何 POST,且无校验红字 → 静默拦截
2. 程序化塞 `skuAttrs[0].options` **不生成** `skuDataList` 行(需走 UI)
3. 变种区只渲染出主题选择器(颜色/风格/材质…),**值输入控件没找到**;变种本身也非必填
4. `产品素材图` 在 DOM 里仍显示为空(store 已设)
5. 表单下方未发现价格/库存输入(可能随变种行生成)
---
## 更新:静默拦截已定位(用 MYJ.message.error 钩子)
Temu 被吞掉的报错是**层层递进**的:
1. ~~`产地选择中国时,省份必填,请完善后提交`~~ → 已解决
路径:`temuAddProductStore.formState.region2Id`(候选在 `region2Id` 同级 `regionCode`,31 个省)
注意:直接赋值 store 当时不生效,但**异步同步后 UI 会显示「北京市」**,再点保存即通过
2. **`产品变种不能为空!`** ← 当前唯一剩下的卡点
需要真正生成变种行(`temuAddSkuDataStore.formState.skuDataList`)
程序化塞 `temuAddSkuAttrStore.skuAttrs[0].options` **无效**
## 变种区控件(待继续)
- `.ant-select` #16 = 颜色主题选择器(主题候选:颜色/风格/材质/口味/…共18个)
- `.ant-select` #17 = 尺码
- 未找到独立的值输入框;值控件可能随主题选中后异步渲染
---
## 更新2:照 SHEIN 的解法推进,报错序列已剥到最后一层
Temu 的规格机制和 SHEIN **不同构**——没有 `theme-select-box`,
而是 **两个 `.theme-value-add` 块**(颜色 / 尺码),每块各有「值输入框(`input[class*="!w-200"]`) + 添加按钮」。
**值必须手动输进去再点「添加」**,程序化塞进 `skuAttrs[0].options` 完全无效。
### 报错序列(每修完一层才露出下一层)
| # | 报错 | 状态 | 解法 |
|---|---|---|---|
| 1 | 产地选择中国时,省份必填 | ✅ | `temuAddProductStore.formState.region2Id`(异步同步,需再点一次保存) |
| 2 | 产品变种不能为空! | ✅ | 两个 theme-value-add 各输一个值点「添加」→ `skuDataList` 生成 2 行 |
| 3 | 尺寸不能为0或空 | ✅ | SKU 表「尺寸(cm)」列每行有 **3 个子输入**(长/宽/高),要全填 |
| 4 | 净重必须小于重量 | ✅ | 净重填 90、重量填 100(不能相等) |
| 5 | **请上传预览图** | ⬜ 当前 | SKU 表「预览图」列需真实图片,不是文本 |
### SKU 表列(Temu 全托管)
`预览图 | 颜色 | 尺码 | SKU货号 | EAN UPC ISBN | 申报价格(CNY) | 尺寸(cm) | 重量(g) | 净重(g) | 建议售价 | SKU分类`
⚠️ EAN 用公司台账下一段:pc 3447 起(Walmart 已用 3400~3446)
+479
View File
@@ -0,0 +1,479 @@
[
"墙壁装饰图案",
"遥控器",
"瓷砖贴玻璃贴",
"活页夹、回形针",
"纽扣",
"美发刷",
"线缆收纳包和盒子",
"护照夹、证件包",
"行李牌",
"女士钥匙环、钥匙链",
"急救包",
"手工艺胶带",
"牙刷架",
"登山扣饰和钥匙扣",
"创可贴",
"圆环活页夹",
"其他(男装配饰)",
"其他(墙贴和壁饰)",
"办公隔间挂钩",
"女士瑜伽瑜伽袜",
"鼠标垫",
"彩色文件夹",
"手机装饰品",
"女士胸针、领针、丝巾扣",
"学科笔记本",
"汽车挂饰",
"别针夹子盒",
"桌面订书机",
"手动卷发器",
"化妆粉扑和海绵",
"MP3 播放器保护套和外壳",
"男士眼镜盒",
"头发定型别针",
"气球",
"其他(化妆工具)",
"荧光笔",
"记事簿",
"笔记本电脑贴花",
"安全别针",
"剪刀",
"其他(橡皮)",
"贺卡",
"反光衣/反光安全装备",
"其他(包裹耗材)",
"挤压玩具",
"婴幼儿抽屉锁",
"水族馆装饰主题摆件",
"其他(新奇玩具)",
"女士时尚束发带",
"辅助挂钩",
"盥洗洗漱包",
"头发侧梳",
"油漆笔和马克笔",
"记事簿配件",
"电话支架",
"空白标签牌",
"便携式包袋挂钩",
"搞笑整蛊玩具",
"其他(发型设计配件)",
"女士薄丝袜",
"美工刀",
"削笔刀",
"其他(日历及个人规划用品)",
"胶水胶棒",
"其他(教学耗材)",
"线缆带",
"其他(教室装饰)",
"棉签",
"拉链",
"女婴袜子",
"化妆品收纳盒",
"书立",
"自行车把套",
"自行车喇叭",
"门档",
"耳塞",
"海绵",
"鞋垫",
"午餐包、午餐袋",
"工艺扇",
"女婴发夹",
"吉他背带",
"冰块模具和托盘",
"活页纸",
"其他(办公家具配件)",
"明信片",
"其他(马克笔荧光笔标记笔)",
"办公簿册装订环",
"图钉",
"手机挂绳与腕带",
"智能手机拉环支架",
"眉毛修剪器",
"抽绳健身包",
"洗脸毛巾",
"其他(钓鱼用品)",
"室内装饰石",
"手动牙刷",
"保险杠贴纸",
"扁平餐具收纳",
"行李整理包",
"起钉器",
"卷尺",
"直尺",
"自动铅笔",
"绘图工具和套件",
"显示器支架",
"办公纸夹",
"办公胶带座",
"工艺粘合剂打蜡",
"脱毛镊子",
"其他(飞钓饵)",
"小雕像摆件",
"女士时尚垂坠式耳环",
"其他大家电配件",
"杯垫",
"玻璃贴膜",
"礼品袋",
"卡纸",
"流苏",
"自行车水壶架",
"真空压缩袋",
"奶嘴链",
"透明胶",
"睫毛膏",
"化妆造型用品",
"女士发带",
"纸张打孔器",
"隔页板",
"木质印章",
"书托架",
"其他(信封)",
"办公家具靠垫、坐垫",
"指南针",
"其他(男士袜子)",
"急救胶带",
"搓澡巾",
"舌刷、舌刮",
"其他(派对装饰)",
"猫薄荷玩具",
"其他(益智思维游戏)",
"女童新奇钱包",
"口袋插笔套",
"面部装饰贴",
"礼品包装袋",
"瓶塞",
"圆髻和皇冠发型成型器",
"可再填充的化妆品喷雾瓶",
"缝纫绳扣和调节扣",
"化妆刷套件",
"内衬袜、船袜",
"齿间牙签",
"车用香水、空气清新",
"三环装订相册",
"美甲模板",
"装饰挂钩",
"棒棒糖、蛋糕模具",
"手动头部按摩器",
"化妆刷清洁用品",
"办公收纳用品",
"切纸机刀片",
"键盘托架",
"桌面吸尘器",
"钱袋",
"文具记事本",
"橡皮擦",
"自行车座杆",
"其他(健身、训练用品)",
"儿童画画衣",
"微型创意玩具",
"男婴袜子",
"汽车摆件",
"眉笔、眉粉",
"清洁刷",
"浇水壶",
"眼影刷",
"多用工具",
"嫁接睫毛工具",
"马桶垫",
"手工折纸",
"女士腰带",
"手机防水袋",
"蜡式脱毛刮片",
"拉力带",
"其他(皮肤护理)",
"时尚、休闲腰包",
"粉底刷",
"其他(手机配件)",
"折纸",
"毛球、绒毛去除器",
"食品过滤器",
"装饰纸花球",
"手机支架",
"悬挂式鞋子收纳袋",
"保湿脚套",
"帽子",
"阿育吠陀香膏和药膏",
"猫清洁按摩刷",
"婴幼儿防撞用品",
"女士情趣连体衣、连体袜",
"烫衣板",
"其他(女婴发饰)",
"女士钱包",
"口红",
"化妆品",
"婴幼儿温度计",
"S形挂钩",
"其他(清洁工具)",
"办公室计算器配件",
"其他(商务公文包及电脑包)",
"其他(切纸刀)",
"双肩电脑包",
"高线画笔",
"粘合剂、胶水",
"笔式橡皮",
"强力胶",
"索引卡归档和名片归档",
"钥匙箱",
"快递文件袋",
"针尖式水笔",
"D环活页夹",
"女士小腿袜",
"医用压缩丝袜",
"茶包碟",
"固定夹",
"茶勺",
"个人用电风扇",
"压蒜器、蒜泥器",
"钩子线",
"狗飞盘",
"手指套",
"运动风汽车贴纸",
"下水道疏通器",
"编织制作工具",
"狗保护罩",
"女士新奇短袜",
"女婴帽子",
"角色扮演骰子",
"运动胶带、绷带",
"其他(大家电配件)",
"后变速器",
"化妆镜",
"生日蜡烛",
"派对用品套装",
"食物存放和整理套装",
"电源转换器",
"手写笔",
"透明印章",
"女童装扮假发",
"墙纸、壁纸",
"其他(挂件吊饰)",
"钓鱼帽",
"派对钥匙圈礼品包",
"女士斜挎包",
"狗用理容梳",
"女士盒剃刀",
"双眼皮贴",
"其他(饰品收纳)",
"口腔温度计",
"垃圾袋收纳器",
"毛绒动物配件",
"自行车配件",
"百洁布",
"其他(刷子)",
"睡袍和浴巾挂钩",
"小便池配件",
"马桶刷、架",
"游泳鼻夹",
"渔具卡扣",
"磨脚石",
"门吊挂钩",
"首饰托盘",
"门锁",
"行李秤",
"旅行鞋子收纳包",
"吉他拨片",
"展示画架",
"马桶替换件",
"女士时尚贴颈项链",
"女士户外袜子",
"胶粘防撞垫",
"男士新奇补丁贴布",
"车迷钥匙链",
"男士运动束发带",
"日常脚部和指甲护理产品",
"其他(面部护理工具)",
"倒酒器",
"商用去毛器",
"USB 数据线",
"其他(派对装饰品和用品)",
"替换胶绵",
"手动背部按摩器",
"PlayStation Vita 保护套",
"其他(熨烫用品)",
"束发带",
"时尚束发带",
"加长型睫毛膏和打底膏",
"女士运动紧身袜",
"化妆棉",
"日用鞋扣和鞋带辅助用品",
"室内灯串",
"水烟炭夹具",
"缠绕护膝/缠绕运动绷带",
"烧烤工具套件",
"指甲清洁刷",
"其他(端口转接头)",
"女孩钱包",
"洒水器",
"男士新奇钥匙链",
"日程表本",
"挂孔纸",
"考勤卡",
"手提、单肩电脑包",
"男士证件夹",
"扫描仪配件",
"可清除标签",
"文件夹标签",
"信件和信笺横格纸",
"校对夹",
"杂志架",
"其他(索引卡归档产品)",
"木制彩色铅笔",
"其他(运动医学产品)",
"管道胶带",
"多功能书写工具",
"圆规",
"织物胶粘剂、粘合剂",
"圣诞袜、节日装饰袜",
"女袜袜套",
"笔记本散热器",
"女士围脖",
"儿童装扮面罩",
"一次性吸管",
"水族箱入门套件",
"其他(键盘乐器配件)",
"公用叉勺服务餐具套装",
"其他(装扮珠宝首饰)",
"摩托车头盔面罩",
"飘带",
"手机防尘插头",
"雕刻刀",
"发条、弹簧类玩具",
"挞盘",
"婴幼儿牙刷",
"拼图游戏",
"女婴半身裙、裙裤",
"其他(女童服饰配件)",
"汽车牌照套",
"其他(女童帽子)",
"大码女士薄丝袜",
"女婴连指、分指手套",
"安抚奶嘴",
"牙胶",
"粘土",
"游泳耳塞",
"锅具配件",
"毛绒公仔",
"女士腕包",
"相片角贴",
"专业水笔",
"女士时尚个性戒指",
"锅盖架",
"烤炉垫和地垫",
"邮差包",
"其他(眼睛护理)",
"指甲刷",
"指甲油固化灯",
"药丸压碎器和切药丸器",
"收纳篮",
"洗车工具",
"灶具旋钮",
"其他(面部护肤)",
"防磨脚贴",
"熨衣板盖",
"家庭马桶清洁剂",
"男士项链",
"实验室洗瓶",
"其他(工艺品和缝纫用品储物)",
"眼罩",
"空白便条卡套装",
"装饰性铃",
"其他(女士手提包和钱包)",
"相机隐私保护盖",
"仿真食物",
"中转袋",
"眼线刷",
"女士时尚珠子吊饰",
"熨烫配件",
"钢丝刷",
"浴衣和毛巾挂钩",
"婴幼儿指甲刀",
"空顶遮阳帽",
"男士户外帽子",
"墙贴和壁饰",
"文胸收纳包",
"骰子游戏",
"车载水杯架",
"方格置物架",
"自行车刹车片衬垫",
"地毯",
"女士空顶遮阳帽",
"杯垫、隔热垫",
"网格本与数据本",
"女士时尚手串",
"其他(卫浴毛巾)",
"车身维修凹坑清除工具",
"反光装置",
"缝纫穿线器",
"摩托车把手配件",
"户外休闲袜",
"商用食品袋封口器",
"户外筷子、刀、叉",
"其他(公用叉勺服务餐具)",
"土豆捣碎器",
"魔鬼蛋托盘",
"浴室托盘",
"战术袋",
"甜点叉",
"珠宝戒指坯与配件",
"花园微缩雕塑",
"首饰盒",
"水果和蔬菜去核机",
"虱子梳",
"裤子衣架",
"徒步旅行背包",
"壁挂酒架",
"其他(镜头配件)",
"充气设备配件",
"胡桃夹子",
"餐饮服务工具和餐具",
"巧克力模具",
"饼干模具",
"创意造型蛋糕模具",
"小动物喂食碗",
"刀盒、刀具收纳和保护器",
"配件",
"房车配件",
"装饰杆头",
"蛋糕装饰",
"派对照相亭道具",
"悬挂厨房篮",
"开瓶器",
"床单紧固件",
"游泳装备包",
"汽车遮阳板",
"玻璃杯",
"男士编织手链",
"地刮、玻璃刮板",
"女士时尚手链",
"脚垫",
"女士时尚开口手镯",
"女士脚链",
"收纳箱",
"双筒望远镜、相机、摄像机挂带及背包",
"猫抓板",
"乐谱文件夹",
"相册和相簿",
"清洁海绵",
"首饰架",
"工艺品收纳盒和整理器",
"发饰(头发装饰)",
"女士防寒连指手套",
"其他(面部护理和面膜)",
"其他(狗清洁用品)",
"浴球",
"粘土模具",
"其他(线缆管理)",
"首饰链子",
"派对头饰",
"珠饰套件",
"卫生纸架",
"吉他维修工具",
"墙贴",
"女士公交卡套",
"平板电脑贴纸",
"车用收纳",
"海绵粉扑",
"面膜"
]
File diff suppressed because it is too large. Load diff
+133
View File
@@ -0,0 +1,133 @@
{
"items": [
"* 店铺名称 [select] ①Alimingpro",
"* 产品分类 [input] ",
" 产品属性 [-] ",
" 店小秘分类 [-] ",
" 来源URL [input] ",
" [input] ",
"* 产品标题 [input] ",
" 英文标题 [input] ",
" 产品货号 [input] ",
"* 产地 [select] 中国大陆",
" 制造地点 [select] 中国大陆",
" 产地说明书 [input] ",
" 站外产品链接 [input] ",
" 敏感属性 [input] 1",
" 定制产品 [input] 1",
"* 产品轮播图 [input] on",
"* 产品素材图 [-] ",
" 产品视频 [-] ",
"* 外包装形状 [input] ",
"* 外包装类型 [input] ",
"* 外包装图片 [input] on",
" 产品说明书 [input] ",
" 描述视频 [-] ",
" 产品描述 [-] "
],
"tables": [],
"dump": {
"temuAddProductStore": {
"formState": {
"mainImage": "[]",
"videoData": {},
"productNumber": "",
"productName": "",
"productEnName": "",
"outerGoodsUrl": "",
"productOrigin": "CN",
"region2Id": "",
"region1ShortNames": "[1] \"CN\"",
"originFileUrl": "",
"checkedSensitiveList": "[]",
"sensitiveState": 0,
"batteryCapacity": "",
"maxLiquidCapacity": "",
"maxKnifeLength": "",
"knifeAngle": "",
"personalizationSwitch": 0,
"technology": 1,
"firstType": "",
"twiceType": "[]",
"invitation": {
"invitationName": "",
"siteName": "",
"id": ""
}
},
"dataState": {
"newSizeGroups": "[]",
"newBootSizeGroups": "[]"
},
"countryCode": "[282] {\"value\":\"CN\",\"label\":\"中国大陆\"}",
"regionCode": "[32] {\"value\":\"\",\"label\":\"请选择省份\"}",
"title_limit": {
"nameMin": 0,
"nameMax": 500
},
"img_limit": {
"popMinImgNum": 3,
"pddkjMinImgNum": 5,
"maxImgNum": 10
}
},
"temuAddSkuDataStore": {
"formState": {
"skuDataList": "[]",
"sitePrice": false,
"codeType": "1"
},
"dataState": {
"warehouseList": "[]",
"goodsTemplateList": "[]",
"warehouseCheckedList": "[]"
}
},
"temuAddSkuAttrStore": {
"formState": {
"skuAttrs": "[]",
"basePlateType": 0,
"sizeChartList": "[]"
},
"dataState": {
"newBootSizeGroups": "[]",
"newSizeGroups": "[]",
"customThemeOptions": "[]",
"templateList": "[]"
},
"customAttrOptions": {}
},
"temuAddBasicStore": {
"formState": {
"shopId": "4021529",
"productSemiManagedReq": "100"
},
"dataState": {
"categories": "[1038] {\"id\":4810363372086185,\"puid\":70000,\"platform\":\"pddkj\",\"site\":null,\"shopId\":4021529,\"categoryId\":\"1432\",\"num\":385,\"classType\":-1,\"createTime\":null,\"updateTime\":null,\"createId\":null,\"nameZh\":\"证件内页\",\"nameEn\":\"证件内页\",\"nodePath\":\"办公用品/日常办公用品/标签指",
"promiseTemplateList": "[]",
"freightTemplateList": "[]",
"commonHasNewBootSize": false,
"commonHasNewSize": false,
"categoryPathName": "",
"categoryPathIds": "[]"
},
"keywordShowData": {
"attrKeywords": "[]",
"hotKeywords": "[]",
"upKeywords": "[]",
"lessKeywords": "[]"
}
},
"temuAddAttrStore": {
"formState": {
"customAttrs": "[]",
"motorIds": "[]",
"motorAfid": "[]",
"templateData": "[]"
},
"dataState": {
"motorTemplate": "[]"
}
}
}
}
+80
View File
@@ -0,0 +1,80 @@
{
"stores": [
"temuCarDataStore",
"temuAddProductStore",
"temuAddStore",
"temuAddAttrStore",
"temuAddSkuDataStore",
"temuAddSkuAttrStore",
"temuAddBasicStore",
"temuAddOtherStore"
],
"items": [
"* 店铺名称 [select] ①Alimingpro",
"* 产品分类 [input] ",
" 产品属性 [-] ",
" 店小秘分类 [-] ",
" 来源URL [input] ",
" [input] ",
"* 产品标题 [input] ",
" 英文标题 [input] ",
" 产品货号 [input] ",
"* 产地 [select] 中国大陆",
" 制造地点 [select] 中国大陆",
" 产地说明书 [input] ",
" 站外产品链接 [input] ",
" 敏感属性 [input] 1",
" 定制产品 [input] 1",
"* 产品轮播图 [input] on",
"* 产品素材图 [-] ",
" 产品视频 [-] ",
"* 外包装形状 [input] ",
"* 外包装类型 [input] ",
"* 外包装图片 [input] on",
" 产品说明书 [input] ",
" 描述视频 [-] ",
" 产品描述 [-] "
],
"dump": {
"temuCarDataStore": {
"supportCarAttr": "false",
"supportCarCategory": "false",
"showBackStatus": "loadingFinish"
},
"temuAddProductStore": {
"formState.productOrigin": "CN",
"formState.sensitiveState": "0",
"formState.personalizationSwitch": "0",
"formState.technology": "1",
"title_limit.nameMin": "0",
"title_limit.nameMax": "500",
"img_limit.popMinImgNum": "3",
"img_limit.pddkjMinImgNum": "5",
"img_limit.maxImgNum": "10"
},
"temuAddStore": {
"attrsInfo.categoryType": "-1",
"attrsInfo.customThemeNum": "0",
"dataState.supportBasePlat": "false"
},
"temuAddAttrStore": {},
"temuAddSkuDataStore": {
"formState.sitePrice": "false",
"formState.codeType": "1"
},
"temuAddSkuAttrStore": {
"formState.basePlateType": "0"
},
"temuAddBasicStore": {
"formState.shopId": "4021529",
"formState.productSemiManagedReq": "100",
"dataState.commonHasNewBootSize": "false",
"dataState.commonHasNewSize": "false"
},
"temuAddOtherStore": {
"dataState.isJoinChoice": "false",
"dataState.isShowQualificationModal": "false",
"packageFormState.inventoryRegion": "1"
}
}
}
+190
View File
@@ -0,0 +1,190 @@
{
"548459586741": {
"cat": "口哨",
"catId": "18723"
},
"661190598139": {
"cat": "发圈和头绳",
"catId": "19285"
},
"764423265986": {
"cat": "口腔温度计",
"catId": "8167"
},
"685456757807": {
"cat": "编织绳",
"catId": "39534"
},
"735147938123": {
"cat": "女士发箍",
"catId": "29276"
},
"627879474686": {
"cat": "女婴袜子",
"catId": "26923"
},
"610164857063": {
"cat": "女士发箍",
"catId": "29276"
},
"676236596821": {
"cat": "发夹",
"catId": "19287"
},
"708420302844": {
"cat": "女士钥匙环、钥匙链",
"catId": "29284"
},
"866537106657": {
"cat": "纽扣",
"catId": "39592"
},
"547183997437": {
"cat": "女士时尚链式项链",
"catId": "29548"
},
"657617635303": {
"cat": "办公收纳用品",
"catId": "1239"
},
"641673262387": {
"cat": "其他(饰品收纳)",
"catId": "31137"
},
"953869908708": {
"cat": "墙贴",
"catId": "12169"
},
"703822717997": {
"cat": "办公收纳用品",
"catId": "1239"
},
"863524134410": {
"cat": "贴纸",
"catId": "39474"
},
"694637537727": {
"cat": "午餐包、午餐袋",
"catId": "10292"
},
"623011262210": {
"cat": "墙贴",
"catId": "12169"
},
"836512561982": {
"cat": "化妆包",
"catId": "19669"
},
"714492799386": {
"cat": "化妆包",
"catId": "19669"
},
"591994902720": {
"cat": "笔袋",
"catId": "40458"
},
"853414558425": {
"cat": "女士钥匙环、钥匙链",
"catId": "29284"
},
"997450831819": {
"cat": "贴纸",
"catId": "39474"
},
"639409307567": {
"cat": "墙贴",
"catId": "12169"
},
"902440558099": {
"cat": "发圈和头绳",
"catId": "19285"
},
"823838532886": {
"cat": "证件卡夹卡套",
"catId": "1431"
},
"698782391248": {
"cat": "贴纸",
"catId": "39474"
},
"707826905566": {
"cat": "女士卡包、证件包",
"catId": "29318"
},
"1027332215847": {
"cat": "首饰盒",
"catId": "12735"
},
"682195499823": {
"cat": "笔袋",
"catId": "40458"
},
"823560564174": {
"cat": "发圈和头绳",
"catId": "19285"
},
"739147684759": {
"cat": "纽扣",
"catId": "39592"
},
"621300323670": {
"cat": "办公收纳用品",
"catId": "1239"
},
"896469995553": {
"cat": "登山扣饰和钥匙扣",
"catId": "34174"
},
"944470567338": {
"cat": "发夹",
"catId": "19287"
},
"787261097583": {
"cat": "发夹",
"catId": "19287"
},
"833753630925": {
"cat": "女士发箍",
"catId": "29276"
},
"719391062058": {
"cat": "化妆品收纳盒",
"catId": "11800"
},
"857781294719": {
"cat": "发夹",
"catId": "19287"
},
"863384503032": {
"cat": "发夹",
"catId": "19287"
},
"774494945453": {
"cat": "女士钥匙环、钥匙链",
"catId": "29284"
},
"750271307455": {
"cat": "女士钥匙环、钥匙链",
"catId": "29284"
},
"726442972676": {
"cat": "发夹",
"catId": "19287"
},
"775057912118": {
"cat": "女士钥匙环、钥匙链",
"catId": "29284"
},
"893592264148": {
"cat": "发夹",
"catId": "19287"
},
"607330049419": {
"cat": "发圈和头绳",
"catId": "19285"
},
"854003999648": {
"cat": "发圈和头绳",
"catId": "19285"
}
}
+18
View File
@@ -0,0 +1,18 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
await c.evalJS(`window.scrollTo(0,document.body.scrollHeight)`); await wait(2000);
const o = await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
const r=x.getBoundingClientRect();
return {y:Math.round(r.y), s:(req?'* ':' ')+lab.slice(0,20)+' ['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,20)};
}).filter(x=>x.s.trim().length>4);
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
return JSON.stringify({tables, low:items.filter(x=>x.y>0).slice(-14).map(x=>x.s)},null,1);})()`);
console.log(String(o).slice(0,1400));
process.exit(0);
+22
View File
@@ -0,0 +1,22 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'nf'; return JSON.stringify({txt:(it.innerText||'').replace(/\\s+/g,' ').slice(0,120), html:it.innerHTML.slice(0,500)});})()`);
console.log('分类控件:', String(o).slice(0,700));
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
if(!it)return'[]'; const el=it.querySelector('.ant-select-selector')||it.querySelector('input'); if(!el)return'[]';
el.scrollIntoView({block:'center'}); const r=el.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(pos.length){ for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1}); await wait(4000); }
const o2 = await c.evalJS(`(()=>{
const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(m=>({t:(m.querySelector('.ant-modal-title')||{}).innerText||'',
inputs:[...m.querySelectorAll('input')].map(i=>i.placeholder||i.type).slice(0,5),
btns:[...m.querySelectorAll('button')].map(b=>(b.innerText||'').trim()).slice(0,6),
txt:(m.innerText||'').replace(/\\s+/g,' ').slice(0,280)}));
const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).map(d=>(d.innerText||'').replace(/\\s+/g,' ').slice(0,220));
return JSON.stringify({modals:ms, dropdowns:dd},null,1);})()`);
console.log(String(o2).slice(0,1200));
process.exit(0);
+16
View File
@@ -0,0 +1,16 @@
import { attach } from '/tmp/cdp3.mjs';
import fs from 'fs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const s=p._s.get('temuAddBasicStore').$state.dataState;
let cats=s.categories; if(typeof cats==='string') cats=JSON.parse(cats);
return JSON.stringify(cats.map(x=>({id:String(x.categoryId), name:x.nameZh, path:x.nodePath||''})));})()`);
const list = JSON.parse(String(o));
fs.writeFileSync('/tmp/temu_cats_full.json', JSON.stringify(list,null,1));
console.log('Temu 全量类目:', list.length);
const KEYS=['发夹','发饰','发圈','发箍','头绳','贴纸','墙贴','钥匙','笔袋','卡包','卡夹','化妆包','袜','收纳','项链','书签','纽扣','口哨','毛线','温度计','围巾'];
for(const k of KEYS){
const hits=list.filter(x=>x.name.includes(k)).slice(0,5);
if(hits.length) console.log(` [${k}] ` + hits.map(h=>h.name).join(' / '));
}
process.exit(0);
+19
View File
@@ -0,0 +1,19 @@
import { attach } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const all = new Set();
for (let i=0;i<80;i++){
const got = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'[]'; return JSON.stringify([...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean));})()`);
const arr=JSON.parse(String(got)); const before=all.size; arr.forEach(x=>all.add(x));
const sc = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return 0; const rc=dd.querySelector('.rc-virtual-list-holder')||dd; rc.scrollTop=rc.scrollTop+600; return rc.scrollTop;})()`);
await wait(400);
if (all.size===before && i>4 && Number(sc)===0) break;
}
const list=[...all];
fs.writeFileSync('/tmp/temu_cats.json', JSON.stringify(list,null,1));
console.log('Temu 类目数:', list.length);
list.forEach((x,i)=>console.log(` ${String(i).padStart(3)} ${x}`));
process.exit(0);
+22
View File
@@ -0,0 +1,22 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
await c.evalJS(`document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));document.body.click();`);
await wait(1200);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const el=it.querySelector('.ant-select-selector'); el.scrollIntoView({block:'center'});
const r=el.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
console.log('坐标:', JSON.stringify(pos));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(3500);
const o = await c.evalJS(`(()=>{
const dds=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null);
if(!dds.length) return 'no-dd';
const dd=dds[dds.length-1];
const opts=[...dd.querySelectorAll('.ant-select-item-option')];
const anyItems=[...dd.querySelectorAll('[class*=item]')].slice(0,5).map(e=>String(e.className).slice(0,40));
return JSON.stringify({n:opts.length, first:opts.slice(0,4).map(e=>JSON.stringify(e.innerText)), anyItems,
txt:(dd.innerText||'').replace(/\\s+/g,' ').slice(0,200)});})()`);
console.log('下拉:', String(o).slice(0,800));
process.exit(0);
+35
View File
@@ -0,0 +1,35 @@
import { attach } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const CAT='发饰(头发装饰)';
// 选类目
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(CAT)});
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
console.log('类目:', pk);
await wait(12000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,22).padEnd(23)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,22);
}).filter(x=>x.trim().length>4);
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const cut=(x,d=0)=>(d>3?'…':(Array.isArray(x)?(x.length?'['+x.length+'] '+JSON.stringify(x[0]).slice(0,240):'[]'):(x&&typeof x==='object'?Object.fromEntries(Object.entries(x).map(([k,v])=>[k,cut(v,d+1)])):x)));
const dump={};
for(const n of ['temuAddProductStore','temuAddSkuDataStore','temuAddSkuAttrStore','temuAddBasicStore','temuAddAttrStore'])
{ try{dump[n]=cut(JSON.parse(JSON.stringify(p._s.get(n).$state)));}catch(e){dump[n]='ERR';} }
return JSON.stringify({items, tables, dump},null,1);})()`);
fs.writeFileSync('/tmp/temu_deep.json', String(o));
const j=JSON.parse(String(o));
console.log('=== form ==='); j.items.forEach(x=>console.log(' '+x));
console.log('=== tables ==='); console.log(JSON.stringify(j.tables));
console.log('=== stores ===');
for(const [n,v] of Object.entries(j.dump)) console.log(' ##',n,JSON.stringify(v).slice(0,560));
process.exit(0);
+55
View File
@@ -0,0 +1,55 @@
import { attach } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const CAT=process.argv[2]||'发夹';
await c.evalJS(`document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));document.body.click();`);
await wait(1200);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const el=it.querySelector('.ant-select-selector'); el.scrollIntoView({block:'center'});
const r=el.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(3000);
let res='nf';
for(let i=0;i<90;i++){
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const target=${JSON.stringify(CAT)};
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===target);
if(!hit)return'scroll';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
if(r==='picked'){res='picked';break;}
if(r==='no-dd'){res='no-dd';break;}
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return 0; const rc=dd.querySelector('.rc-virtual-list-holder')||dd; rc.scrollTop=rc.scrollTop+500; return rc.scrollTop;})()`);
await wait(280);
}
console.log('类目:', res, CAT);
if(res!=='picked') process.exit(1);
await wait(14000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,22).padEnd(23)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,22);
}).filter(x=>x.trim().length>4);
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const cut=(x,d=0)=>(d>3?'…':(Array.isArray(x)?(x.length?'['+x.length+'] '+JSON.stringify(x[0]).slice(0,260):'[]'):(x&&typeof x==='object'?Object.fromEntries(Object.entries(x).map(([k,v])=>[k,cut(v,d+1)])):x)));
const dump={};
for(const n of ['temuAddProductStore','temuAddSkuDataStore','temuAddSkuAttrStore','temuAddBasicStore','temuAddAttrStore'])
{ try{dump[n]=cut(JSON.parse(JSON.stringify(p._s.get(n).$state)));}catch(e){dump[n]='ERR';} }
const b=p._s.get('temuAddBasicStore').$state;
return JSON.stringify({items, tables, dump, catId:b.formState.categoryId, path:b.dataState.categoryPathName},null,1);})()`);
fs.writeFileSync('/tmp/temu_deep.json', String(o));
const j=JSON.parse(String(o));
console.log('catId:', j.catId, '| path:', j.path);
console.log('=== form ==='); j.items.forEach(x=>console.log(' '+x));
console.log('=== tables ==='); console.log(JSON.stringify(j.tables));
console.log('=== stores ===');
for(const [n,v] of Object.entries(j.dump)) console.log(' ##',n,JSON.stringify(v).slice(0,520));
process.exit(0);
+52
View File
@@ -0,0 +1,52 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/temu/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
const IMGS=['https://cbu01.alicdn.com/img/ibank/O1CN01IHtNAHKMzNC1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01UGEWafTzqTH1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01d0hA4N72jzD1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01edgGxIlmdtJ1chua_!!2222444614399-0-cib.jpg'];
const r = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('temuAddProductStore').$state.formState;
const imgs=${JSON.stringify(IMGS)};
// 试探 mainImage 形状
if(Array.isArray(f.mainImage)) f.mainImage=imgs.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800}));
else f.mainImage=JSON.stringify(imgs.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800})));
f.productName='Hair Clips Set for Women Girls Fashion Hair Accessories';
const a=p._s.get('temuAddSkuAttrStore').$state.formState;
if(a.skuAttrs&&a.skuAttrs[0]) a.skuAttrs[0].options=[{optionName:'Black',name:'Black',value:'Black',optionId:1}];
return JSON.stringify({imgIsArray:Array.isArray(${JSON.stringify(IMGS)}), mainImgType:Array.isArray(f.mainImage)?'array':typeof f.mainImage,
n:Array.isArray(f.mainImage)?f.mainImage.length:'?', opts:a.skuAttrs&&a.skuAttrs[0]?a.skuAttrs[0].options.length:0});})()`);
console.log('填充:', r);
await wait(3500);
// 找外包装形状并选“长方体”
await c.evalJS(`document.body.click()`); await wait(600);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/外包装形状/.test(l.innerText||'');});
if(!it)return'[]'; const s=it.querySelector('.ant-select-selector'); if(!s)return'[]';
s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(pos.length){ for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1}); await wait(2500); }
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>/长方体/.test(o.innerText||''));
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'picked';})()`);
console.log('外包装形状:', pk);
await wait(2000);
// 保存并抓错误
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(10000);
const err = await c.evalJS(`(()=>{const ex=[...document.querySelectorAll('.ant-form-item-explain-error')].map(e=>(e.innerText||'').trim()).filter(Boolean);
const red=[]; document.querySelectorAll('*').forEach(el=>{ if(el.offsetParent===null||el.children.length)return;
const t=(el.innerText||'').trim(); if(!t||t.length>60)return;
const s=getComputedStyle(el); const m=(s.color||'').match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/); if(!m)return;
if(+m[1]>160&&+m[2]<140&&+m[3]<140) red.push(t); });
const toasts=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean);
return JSON.stringify({ex:[...new Set(ex)].slice(0,12), red:[...new Set(red)].slice(0,14), toasts:[...new Set(toasts)].slice(0,3)});})()`);
console.log('校验:', String(err).slice(0,1000));
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,4)));
process.exit(0);
+19
View File
@@ -0,0 +1,19 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const out={};
for(const n of [...p._s.keys()].filter(k=>/temu/i.test(k))){
const s=p._s.get(n).$state; const ks=[];
const walk=(x,path,d)=>{ if(d>2||!x||typeof x!=='object')return;
if(Array.isArray(x)) return;
for(const k of Object.keys(x)){ if(/img|image|pic|photo|material/i.test(k)) ks.push(path+'.'+k+'='+JSON.stringify(x[k]).slice(0,40));
else walk(x[k],path+'.'+k,d+1); } };
walk(s,n,0); if(ks.length) out[n]=ks.slice(0,8);
}
// 外包装类型 是不是 input
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/外包装类型/.test(l.innerText||'');});
const info=it?{html:it.innerHTML.slice(0,360)}:{};
return JSON.stringify({out, info},null,1);})()`);
console.log(String(o).slice(0,1800));
process.exit(0);
+52
View File
@@ -0,0 +1,52 @@
import { newTarget, CDP } from '/tmp/cdp3.mjs';
import fs from 'fs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const SHOP='①Alimingpro';
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const x of tl.filter(y=>y.type==='page'&&/temu/i.test(y.url))) await fetch(`http://127.0.0.1:9223/json/close/${x.id}`).catch(()=>{});
await wait(900);
const nt = await newTarget('about:blank');
const c = await CDP.connect(nt.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:'https://www.dianxiaomi.com/web/temu/add'});
await wait(22000);
console.log('url:', await c.evalJS('location.href'));
// 店铺选择器
const sp = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=(it||document).querySelector('.ant-select-selector')||[...document.querySelectorAll('.ant-select-selector')].filter(e=>e.offsetParent!==null)[0];
if(!s)return'[]'; s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:sp[0],y:sp[1],button:'left',clickCount:1});
await wait(2500);
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===${JSON.stringify(SHOP)});
if(!hit)return'no-hit:'+[...dd.querySelectorAll('.ant-select-item-option')].slice(0,4).map(o=>o.innerText.trim()).join(',');
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
console.log('选店铺:', pk);
await wait(9000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const stores=[...p._s.keys()].filter(k=>/temu|pddkj/i.test(k));
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,24).padEnd(25)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,24);
}).filter(x=>x.trim().length>4);
const dump={};
for(const n of stores){ try{ const s=p._s.get(n).$state; const o2={};
for(const k of Object.keys(s)){ const v=s[k];
if(v===null||['string','number','boolean'].includes(typeof v)){ if(v!==''&&v!==null&&String(v).length<60) o2[k]=String(v); }
else if(v&&typeof v==='object'&&!Array.isArray(v)){ for(const k2 of Object.keys(v)){ const v2=v[k2];
if(v2===null||['string','number','boolean'].includes(typeof v2)){ if(v2!==''&&v2!==null&&String(v2).length<50) o2[k+'.'+k2]=String(v2); } } } }
dump[n]=o2; }catch(e){ dump[n]='ERR'; } }
return JSON.stringify({stores, items, dump},null,1);})()`);
fs.writeFileSync('/tmp/temu_map.json', String(o));
const j=JSON.parse(String(o));
console.log('stores:', j.stores.join(', '));
console.log('--- form ---'); j.items.forEach(x=>console.log(' '+x));
console.log('--- store 关键字段 ---');
for(const [n,v] of Object.entries(j.dump)){ const hit=Object.entries(v||{}).filter(([k])=>/shop|site|currency|cat|brand|price|weight/i.test(k));
if(hit.length) console.log(' ##',n,hit.map(([k,val])=>k+'='+val).join(' | ').slice(0,300)); }
process.exit(0);
+49
View File
@@ -0,0 +1,49 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const CAT = process.argv[2] || '发夹';
const dd = () => `[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop()`;
// 滚回顶部
await c.evalJS(`(()=>{const d=${dd()}; if(!d)return 0; const rc=d.querySelector('.rc-virtual-list-holder')||d; rc.scrollTop=0; return rc.scrollTop;})()`);
await wait(1200);
let res='nf';
for(let i=0;i<120;i++){
const r = await c.evalJS(`(()=>{const d=${dd()}; if(!d)return'no-dd';
const target=${JSON.stringify(CAT)};
const hit=[...d.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===target);
if(!hit)return'scroll';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
if(r==='picked'){res='picked';break;}
if(r==='no-dd'){res='no-dd';break;}
await c.evalJS(`(()=>{const d=${dd()}; if(!d)return 0; const rc=d.querySelector('.rc-virtual-list-holder')||d; rc.scrollTop=rc.scrollTop+400; return rc.scrollTop;})()`);
await wait(230);
}
console.log('类目:', res, CAT);
if(res!=='picked') process.exit(1);
await wait(14000);
const o = await c.evalJS(`(()=>{
const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).map(x=>{
const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const req=!!x.querySelector('.ant-form-item-required');
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
return (req?'* ':' ')+lab.slice(0,22).padEnd(23)+'['+(ta?'ta':(sel?'select':(inp?'input':'-')))+'] '+(sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''))).slice(0,22);
}).filter(x=>x.trim().length>4);
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
const cut=(x,d=0)=>(d>3?'…':(Array.isArray(x)?(x.length?'['+x.length+'] '+JSON.stringify(x[0]).slice(0,260):'[]'):(x&&typeof x==='object'?Object.fromEntries(Object.entries(x).map(([k,v])=>[k,cut(v,d+1)])):x)));
const dump={};
for(const n of ['temuAddProductStore','temuAddSkuDataStore','temuAddSkuAttrStore','temuAddBasicStore','temuAddAttrStore'])
{ try{dump[n]=cut(JSON.parse(JSON.stringify(p._s.get(n).$state)));}catch(e){dump[n]='ERR';} }
const b=p._s.get('temuAddBasicStore').$state;
return JSON.stringify({items, tables, dump, catId:b.formState.categoryId, path:b.dataState.categoryPathName},null,1);})()`);
import('fs').then(fs=>fs.writeFileSync('/tmp/temu_deep.json', String(o)));
const j=JSON.parse(String(o));
console.log('catId:', j.catId, '| path:', j.path);
console.log('=== form ==='); j.items.forEach(x=>console.log(' '+x));
console.log('=== tables ==='); console.log(JSON.stringify(j.tables));
console.log('=== stores ===');
for(const [n,v] of Object.entries(j.dump)) console.log(' ##',n,JSON.stringify(v).slice(0,500));
process.exit(0);
+30
View File
@@ -0,0 +1,30 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
await c.evalJS(`document.body.click()`); await wait(700);
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/外包装类型/.test(l.innerText||'');});
if(!it)return'[]'; const s=it.querySelector('.ant-select-selector'); if(!s)return'[]';
s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
console.log('坐标:', JSON.stringify(pos));
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1});
await wait(3000);
const o = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; return JSON.stringify([...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').trim()).slice(0,10));})()`);
console.log('外包装类型选项:', String(o).slice(0,400));
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const its=[...dd.querySelectorAll('.ant-select-item-option')]; if(!its.length)return'no-opt';
const hit=its.find(o=>/袋|Box|盒|Bag/i.test(o.innerText||''))||its[0];
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked:'+(hit.innerText||'').trim();})()`);
console.log('选择:', pk);
await wait(2000);
// 素材图 形状
const o2 = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('temuAddProductStore').$state.formState;
return JSON.stringify({matType:Array.isArray(f.materialImage)?'array':typeof f.materialImage,
mat:String(JSON.stringify(f.materialImage)).slice(0,220), mainN:Array.isArray(f.mainImage)?f.mainImage.length:'?',
pkg:(()=>{const q=p._s.get('temuAddOtherStore').$state; return JSON.stringify(q.packageFormState).slice(0,200);})()});})()`);
console.log('字段形状:', String(o2).slice(0,600));
process.exit(0);
+13
View File
@@ -0,0 +1,13 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{
const req=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null&&e.querySelector('.ant-form-item-required'));
const empty=[], filled=[];
req.forEach(x=>{ const l=x.querySelector('label'); const lab=l?(l.innerText||'').replace(/\\s+/g,' ').trim():'';
const inp=x.querySelector('input:not([type=hidden])'); const ta=x.querySelector('textarea'); const sel=x.querySelector('.ant-select-selection-item');
const v=sel?sel.innerText.trim():(inp?inp.value:(ta?ta.value:''));
(v?filled:empty).push(lab+' ['+(ta?'ta':(sel?'select':(inp?'input':'-')))+']='+String(v).slice(0,16)); });
const expl=[...document.querySelectorAll('.ant-form-item-explain-error')].map(e=>(e.innerText||'').trim()).filter(Boolean);
return JSON.stringify({empty, filled, expl:[...new Set(expl)].slice(0,10)},null,1);})()`);
console.log(String(o).slice(0,1600));
process.exit(0);
+38
View File
@@ -0,0 +1,38 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const reqs=[];
c.on(m=>{ if(m.method==='Network.requestWillBeSent'){const r=m.params.request;
if(/temu/i.test(r.url)&&/\.json/.test(r.url)&&r.method==='POST') reqs.push(r.url.replace('https://www.dianxiaomi.com','')); }});
await c.send('Network.enable').catch(()=>{});
const IMGS=['https://cbu01.alicdn.com/img/ibank/O1CN01IHtNAHKMzNC1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01UGEWafTzqTH1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01d0hA4N72jzD1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01edgGxIlmdtJ1chua_!!2222444614399-0-cib.jpg',
'https://cbu01.alicdn.com/img/ibank/O1CN01wCkR921xkoEgfOWb0_!!2222444614399-0-cib.jpg'];
const r = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('temuAddProductStore').$state.formState;
const imgs=${JSON.stringify(IMGS)};
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
if(Array.isArray(f.mainImage)) f.mainImage=imgs.map(mk); else f.mainImage=JSON.stringify(imgs.map(mk));
f.materialImage=imgs.slice(0,3).map(mk);
const q=p._s.get('temuAddOtherStore').$state;
if(q.packageFormState){ q.packageFormState.packageImages=imgs.slice(0,3).map(mk); q.packageFormState.packageShape=1; q.packageFormState.packageType=0; }
return JSON.stringify({main:Array.isArray(f.mainImage)?f.mainImage.length:'str', mat:Array.isArray(f.materialImage)?f.materialImage.length:'obj',
pkg:(q.packageFormState?q.packageFormState.packageImages.length:'-')});})()`);
console.log('填充:', r);
await wait(3500);
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(11000);
const err = await c.evalJS(`(()=>{const ex=[...document.querySelectorAll('.ant-form-item-explain-error')].map(e=>(e.innerText||'').trim()).filter(Boolean);
const red=[]; document.querySelectorAll('*').forEach(el=>{ if(el.offsetParent===null||el.children.length)return;
const t=(el.innerText||'').trim(); if(!t||t.length>60)return;
const s=getComputedStyle(el); const m=(s.color||'').match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/); if(!m)return;
if(+m[1]>160&&+m[2]<140&&+m[3]<140) red.push(t); });
const toasts=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean);
return JSON.stringify({ex:[...new Set(ex)].slice(0,10), red:[...new Set(red)].slice(0,12), toasts:[...new Set(toasts)].slice(0,2)});})()`);
console.log('校验:', String(err).slice(0,800));
console.log('POST:', JSON.stringify([...new Set(reqs)].slice(0,4)));
process.exit(0);
+20
View File
@@ -0,0 +1,20 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
await c.evalJS(`document.body.click()`); await wait(600);
const pos = JSON.parse(String(await c.evalJS(`(()=>{
const boxes=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/颜色/.test(e.innerText||'')&&e.querySelector('input[type=search]')&&e.querySelectorAll('*').length<300);
const box=boxes[boxes.length-1]; if(!box)return'[]';
const s=box.closest('.ant-select')||box.querySelector('.ant-select')||box;
const sel=s.querySelector('.ant-select-selector')||s;
sel.scrollIntoView({block:'center'}); const r=sel.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
console.log('坐标:', JSON.stringify(pos));
if(pos.length){ for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1}); }
await wait(3000);
const o = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd';
const opts=[...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').trim());
return JSON.stringify({n:opts.length, first:opts.slice(0,10), txt:(dd.innerText||'').replace(/\\s+/g,' ').slice(0,180)});})()`);
console.log('下拉:', String(o).slice(0,600));
process.exit(0);
+12
View File
@@ -0,0 +1,12 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const d=JSON.parse(JSON.stringify(p._s.get('temuAddSkuDataStore').$state));
const a=JSON.parse(JSON.stringify(p._s.get('temuAddSkuAttrStore').$state));
const tables=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).map(t=>({
th:[...t.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).slice(0,12),
rows:[...t.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length).length}));
return JSON.stringify({tables, skuN:(d.formState.skuDataList||[]).length, row0:(d.formState.skuDataList||[])[0],
attrs:(a.formState.skuAttrs||[]).map(x=>({name:x.parentSpecName, n:(x.options||[]).length}))},null,1);})()`);
console.log(String(o).slice(0,1400));
process.exit(0);
+39
View File
@@ -0,0 +1,39 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
// 1. 主规格/变种区结构
const o1 = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const a=JSON.parse(JSON.stringify(p._s.get('temuAddSkuAttrStore').$state));
const at=(a.formState.skuAttrs||[])[0]||{};
return JSON.stringify({attrKeys:Object.keys(at), attr:{type:at.type,parentSpecId:at.parentSpecId,parentSpecName:at.parentSpecName,
optCount:(at.options||[]).length, opt0:(at.options||[])[0], vals:at.values||at.checked||null},
customs:(a.dataState.customThemeOptions||[]).slice(0,2)});})()`);
console.log('变种属性:', String(o1).slice(0,900));
// 2. 页面里变种可点元素
const o2 = await c.evalJS(`(()=>{const boxes=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/颜色|Color/.test(e.innerText||'')&&e.querySelectorAll('*').length<200);
const box=boxes[boxes.length-1]; if(!box)return'nf';
const opts=[...box.querySelectorAll('.ant-tag,.ant-checkbox-wrapper,[class*=option],[class*=spec]')].filter(e=>e.offsetParent!==null).slice(0,8)
.map(e=>({tag:e.tagName,cls:String(e.className||'').slice(0,45),t:(e.innerText||'').trim().slice(0,14)}));
const inputs=[...box.querySelectorAll('input')].slice(0,4).map(i=>({ph:i.placeholder||'',cls:String(i.className||'').slice(0,40)}));
return JSON.stringify({opts, inputs});})()`);
console.log('变种区:', String(o2).slice(0,800));
// 3. 外包装形状/类型 的选项
await c.evalJS(`document.body.click()`); await wait(700);
for (const lab of ['外包装形状','外包装类型']) {
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(lab)}).test(l.innerText||'');});
if(!it)return'[]'; const s=it.querySelector('.ant-select-selector'); if(!s)return'[]';
s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(pos.length){ for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1}); await wait(2500); }
const o = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; return JSON.stringify([...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').trim()).slice(0,12));})()`);
console.log(lab+':', String(o).slice(0,300));
await c.evalJS(`document.body.click()`); await wait(700);
}
// 4. 图片字段类型
const o4 = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('temuAddProductStore').$state.formState;
return JSON.stringify({mainImageType:typeof f.mainImage, mainImage:String(f.mainImage).slice(0,60)});})()`);
console.log('图片字段:', String(o4).slice(0,300));
process.exit(0);
+11
View File
@@ -0,0 +1,11 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{
const boxes=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/颜色/.test(e.innerText||'')&&e.querySelectorAll('input').length>0&&e.querySelectorAll('*').length<300);
const box=boxes[boxes.length-1]; if(!box)return'nf';
const ins=[...box.querySelectorAll('input')].map(i=>({ph:i.placeholder||'',type:i.type,cls:String(i.className||'').slice(0,45)}));
const btns=[...box.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&(e.innerText||'').trim()).slice(0,10).map(e=>({tag:e.tagName,t:(e.innerText||'').trim().slice(0,14)}));
return JSON.stringify({txt:(box.innerText||'').replace(/\\s+/g,' ').slice(0,260), ins, btns,
html:box.innerHTML.slice(0,400)},null,1);})()`);
console.log(String(o).slice(0,1500));
process.exit(0);
+11
View File
@@ -0,0 +1,11 @@
import { attach } from '/tmp/cdp3.mjs';
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
const o = await c.evalJS(`(()=>{
// 找含“颜色”且含“添加/请选择”的容器
const cands=[...document.querySelectorAll('div')].filter(e=>e.offsetParent!==null&&/颜色/.test(e.innerText||'')&&e.querySelectorAll('input').length>=1&&e.querySelectorAll('*').length<400);
const box=cands[cands.length-1]; if(!box)return'nf';
const ins=[...box.querySelectorAll('input')].filter(i=>i.offsetParent!==null).map(i=>({type:i.type,ph:i.placeholder||'',cls:String(i.className||'').slice(0,50),dis:i.disabled}));
const btns=[...box.querySelectorAll('button')].filter(e=>e.offsetParent!==null).map(b=>({t:(b.innerText||'').trim().slice(0,12),dis:b.disabled,cls:String(b.className||'').slice(0,40)}));
return JSON.stringify({n:cands.length, txt:(box.innerText||'').replace(/\\s+/g,' ').slice(0,320), ins, btns},null,1);})()`);
console.log(String(o).slice(0,1500));
process.exit(0);
+182
View File
@@ -0,0 +1,182 @@
{
"548459586741": {
"ok": false,
"cat": ""
},
"764423265986": {
"ok": true,
"cat": "picked[0/1]:Peralatan Dapur(厨房用品)/Perkakas & Alat Dapur(厨房器具)/Termometer Dapur(厨房温度计)"
},
"685456757807": {
"ok": false,
"cat": ""
},
"735147938123": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Cekak Rambut(发带发箍)"
},
"610164857063": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Cekak Rambut(发带发箍)"
},
"676236596821": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪)"
},
"708420302844": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kemas & Aksesori Kostum(平价饰品)/Rantai Kunci(钥匙扣)"
},
"866537106657": {
"ok": true,
"cat": "picked[0/1]:Bayi & Bersalin(母婴用品)/Penjagaan & Kesihatan Bayi(婴儿护理与健康)/Lampin(尿布)/Aksesori Lampin Kain(布尿布配件)/P"
},
"547183997437": {
"ok": true,
"cat": "picked[0/14]:Aksesori & Hasil Barang Kemas(珠宝与衍生品)/Ambar(琥珀)/Rantai & Loket Ambar(琥珀项链&吊坠) | 尝试: 项链→picked[0/14]:Aksesori & Hasil Barang Kemas(珠宝"
},
"657617635303": {
"ok": true,
"cat": "picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Rumah(家居收纳)/Kotak & Tong Penyimpanan(收纳盒) | 尝试: 收纳盒→picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Ruma"
},
"641673262387": {
"ok": true,
"cat": "picked[0/1]:Bagasi & Beg(箱包)/Beg Bagasi & Perjalanan(旅行箱包)/Pengaturan Perjalanan(旅行收纳袋) | 尝试: 首饰袋→no-result / 首饰盒→no-result / 收纳袋→picked[0/1]:Bagasi & Beg(箱包)/Beg Bagasi & Per"
},
"953869908708": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 窗贴→no-result / 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"703822717997": {
"ok": true,
"cat": "picked[0/19]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Alatan Penulisan & Pembet | 尝试: 笔夹→no-result / 笔夹→no-result / 文具→picked[0/19]:Komputer & Peralatan Pejabat(电脑办"
},
"863524134410": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"694637537727": {
"ok": true,
"cat": "picked[0/1]:Bagasi & Beg(箱包)/Beg Fungsian(功能箱包)/Beg Pendingin(保温袋) | 尝试: 保温袋→picked[0/1]:Bagasi & Beg(箱包)/Beg Fungsian(功能箱"
},
"623011262210": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 窗贴→no-result / 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"836512561982": {
"ok": true,
"cat": "picked[0/2]:Kecantikan & Penjagaan Diri(美妆个护)/Mekap(美妆)/Alatan Solekan(化妆工具)/Beg dan Bekas Solek(化妆包及化妆盒) | 尝试: 化妆包→picked[0/2]:Kecantikan & Penjagaan Diri(美妆个护)"
},
"714492799386": {
"ok": true,
"cat": "picked[0/2]:Kecantikan & Penjagaan Diri(美妆个护)/Mekap(美妆)/Alatan Solekan(化妆工具)/Beg dan Bekas Solek(化妆包及化妆盒) | 尝试: 化妆包→picked[0/2]:Kecantikan & Penjagaan Diri(美妆个护)"
},
"591994902720": {
"ok": true,
"cat": "picked[0/19]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Alatan Penulisan & Pembet | 尝试: 笔袋→no-result / 笔袋→no-result / 文具→picked[0/19]:Komputer & Peralatan Pejabat(电脑办"
},
"853414558425": {
"ok": true,
"cat": "picked[0/1]:Telefon & Elektronik(手机与数码)/Aksesori Telefon(手机配件)/Tali & Hiasan Telefon(手机挂绳与挂件) | 尝试: 手机挂绳→picked[0/1]:Telefon & Elektronik(手机与数码)/Akses"
},
"997450831819": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"639409307567": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 墙贴→no-result / 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"902440558099": {
"ok": true,
"cat": "picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fesyen Bayi(婴儿时尚配件)/Aksesori Rambut Bayi(婴儿发饰) | 尝试: 发圈→no-result / 发饰→picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fe"
},
"823838532886": {
"ok": true,
"cat": "picked[0/2]:Bagasi & Beg(箱包)/Beg Lelaki(男包)/Dompet(钱包) | 尝试: 卡套→no-result / 卡包→no-result / 卡套→no-result / 钱包→picked[0/2]:Bagasi & Beg(箱包)/Beg Lelaki(男包)/D"
},
"698782391248": {
"ok": true,
"cat": "picked[0/4]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Label, Pembahagi Indeks & | 尝试: 贴纸→picked[0/4]:Komputer & Peralatan Pejabat(电脑办公"
},
"707826905566": {
"ok": true,
"cat": "picked[0/2]:Bagasi & Beg(箱包)/Beg Lelaki(男包)/Dompet(钱包) | 尝试: 卡包→no-result / 卡包→no-result / 卡套→no-result / 钱包→picked[0/2]:Bagasi & Beg(箱包)/Beg Lelaki(男包)/D"
},
"1027332215847": {
"ok": true,
"cat": "picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Rumah(家居收纳)/Kotak & Tong Penyimpanan(收纳盒) | 尝试: 收纳盒→picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Ruma"
},
"682195499823": {
"ok": true,
"cat": "picked[0/19]:Komputer & Peralatan Pejabat(电脑办公)/Alat Tulis & Bekalan Pejabat(办公文具用品)/Alatan Penulisan & Pembet | 尝试: 笔袋→no-result / 笔袋→no-result / 文具→picked[0/19]:Komputer & Peralatan Pejabat(电脑办"
},
"823560564174": {
"ok": true,
"cat": "picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fesyen Bayi(婴儿时尚配件)/Aksesori Rambut Bayi(婴儿发饰) | 尝试: 发圈→no-result / 发饰→picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fe"
},
"739147684759": {
"ok": true,
"cat": "picked[0/1]:Bayi & Bersalin(母婴用品)/Penjagaan & Kesihatan Bayi(婴儿护理与健康)/Lampin(尿布)/Aksesori Lampin Kain(布尿布配件)/P | 尝试: 纽扣→picked[0/1]:Bayi & Bersalin(母婴用品)/Penjagaan &"
},
"621300323670": {
"ok": true,
"cat": "picked[0/78]:Komputer & Peralatan Pejabat(电脑办公)/Peralatan Pejabat(办公设备)/Alat Ganti Peralatan Pejabat(办公设备零部件) | 尝试: 胶带座→no-result / 胶带座→no-result / 办公→picked[0/78]:Komputer & Peralatan Pejabat(电脑办"
},
"896469995553": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kemas & Aksesori Kostum(平价饰品)/Rantai Kunci(钥匙扣) | 尝试: 钥匙扣→picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kema"
},
"944470567338": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"787261097583": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"833753630925": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Cekak Rambut(发带发箍) | 尝试: 发箍→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"719391062058": {
"ok": true,
"cat": "picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Rumah(家居收纳)/Kotak & Tong Penyimpanan(收纳盒) | 尝试: 收纳盒→picked[0/3]:Bekalan Rumah(居家日用)/Pengatur Ruma"
},
"857781294719": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"863384503032": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"774494945453": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kemas & Aksesori Kostum(平价饰品)/Rantai Kunci(钥匙扣) | 尝试: 钥匙扣→picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kema"
},
"750271307455": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kemas & Aksesori Kostum(平价饰品)/Rantai Kunci(钥匙扣) | 尝试: 钥匙扣→picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kema"
},
"726442972676": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"775057912118": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kemas & Aksesori Kostum(平价饰品)/Rantai Kunci(钥匙扣) | 尝试: 钥匙扣→picked[0/1]:Aksesori Fesyen(时尚配件)/Barang Kema"
},
"893592264148": {
"ok": true,
"cat": "picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Rambut(发饰)/Klip Rambut & Pin Rambut(发夹发簪) | 尝试: 发夹→picked[0/1]:Aksesori Fesyen(时尚配件)/Aksesori Ra"
},
"607330049419": {
"ok": true,
"cat": "picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fesyen Bayi(婴儿时尚配件)/Aksesori Rambut Bayi(婴儿发饰) | 尝试: 发圈→no-result / 发饰→picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fe"
},
"854003999648": {
"ok": true,
"cat": "picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fesyen Bayi(婴儿时尚配件)/Aksesori Rambut Bayi(婴儿发饰) | 尝试: 发圈→no-result / 发饰→picked[1/6]:Bayi & Bersalin(母婴用品)/Aksesori Fe"
}
}
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
{"n":184,"items":[{"id":"184807703510005077","name":"Pure Natural Cotton Yarn for Baby Knitting - Soft 16-Ply","shop":5880976,"cat":951816,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510004697","name":"6.5cm Colorful Plastic Party Whistles for Kids, Blow Out Whistle Toys for Birthday Sports Games","shop":5880976,"cat":604390,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510004269","name":"High Elastic Wide Hair Ties Set of 5, Soft No-Break Hair Bands","shop":5880976,"cat":962696,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510004059","name":"50 Pcs Elastic Thin Hair Ties for Women Girls Daily Use","shop":5880976,"cat":962696,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510003789","name":"Cute Plaid Hair Clips & Bands for Kids, Bright Yellow Children's Hair Accessories","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510003519","name":"Stainless Steel Keychain with English Letter, Polished Metal Key Holder for Car Bag","shop":5880976,"cat":907656,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510003301","name":"Cute Cartoon Animal Mini Hair Clips for Bangs and Small Strands","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510003143","name":"Romantic Heart Keychain for Couples Silver Plated Valentine Gift","shop":5880976,"cat":907656,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510002979","name":"Stainless Steel Cat Keychain with Fish Charm Cute Bag Ornament","shop":5880976,"cat":907656,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510002811","name":"Elegant Red Rose Crab Hair Clip for Women Stylish Accessory","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510002613","name":"Cute Bunny Ear Hair Clips for Kids with Ribbon Bow, 2 Pcs","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510002419","name":"Clear Cosmetic Organizer with 6 Drawers for Desk Storage","shop":5880976,"cat":600621,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510002205","name":"Wide Plaid Fabric Headband for Women - Comfortable Hair Accessory","shop":5880976,"cat":605271,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510001565","name":"Transparent Crab Hair Clips with Hearts, Clear Acrylic Set","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510000821","name":"Korean Style Plaid Triangle Hair Clip with Decorative Button","shop":5880976,"cat":908552,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510000487","name":"Angel Wings & Stars Keychain Y2K Retro Bag Charm Cute Accessory","shop":5880976,"cat":907656,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703510000053","name":"Round Transparent Desktop Tape Dispenser with Smooth Cutter","shop":5880976,"cat":987656,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509999761","name":"Metallic Rectangular Coat Buttons with Leather Cover for Jackets","shop":5880976,"cat":1944336,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509999371","name":"Cute Heart Hair Ties Set, Soft Elastic Bands for Buns & Ponytails, Colorful Hair Accessories","shop":5880976,"cat":962696,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509998877","name":"96-Cell Brush Organizer Case for Watercolor & Gouache","shop":5880976,"cat":603002,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509998697","name":"Silver Matte Tin Photo Box with Clear Window for Cards","shop":5880976,"cat":600621,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509998203","name":"Ultra-Slim Card Holder Wallet for Men Women, Compact Cash & Card Pouch","shop":5880976,"cat":601430,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509997509","name":"Boho Style White PVC Laptop Stickers 10cm Round Decal","shop":5880976,"cat":988296,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509997369","name":"Cute Cartoon Cat Key Card Holder for Metro Office & Bag","shop":5880976,"cat":601430,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509996723","name":"Large Beige Fabric Hair Tie with Pearl for Buns and Low Ponytails","shop":5880976,"cat":962696,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509996281","name":"3D Glowing Butterfly Wall Stickers, Magnetic Light Up Decals","shop":5880976,"cat":988296,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509995631","name":"Matte PET Emoji Stickers for Goodnotes & Scrapbooking, 10 Sheets Waterproof","shop":5880976,"cat":988296,"catPath":"","img":1,"vars":0,"price":"","weight":""},{"id":"184807703509995245","Line truncated
+211
View File
@@ -0,0 +1,211 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── TikTok (AutoMavenShop_MY) 建品配方 ──
node tkbatch.mjs <oid> [catKeyword] [variantValue]
流程:选店铺 → 分类弹窗 → 标题/卖点/关键词/描述 → 变种属性 → SKU价+库存
→ 重量/尺寸 → 仓库 → 图片(外链) → 来源URL → 保存 → 回读 */
const OID = process.argv[2];
const CAT_KW = process.argv[3] || '';
const VAR_VAL = process.argv[4] || 'Default';
if (!OID) { console.log('用法: node tkbatch.mjs <oid> [catKeyword] [variantValue]'); process.exit(1); }
const IN = JSON.parse(fs.readFileSync('/tmp/tk_input.json', 'utf8'))[OID];
if (!IN) { console.log('无输入数据', OID); process.exit(1); }
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const DIMS = (() => { const W = Math.max(100, Math.round(IN.weight * 1.3));
return { L: Math.max(60, Math.min(400, W)), Wi: Math.max(40, Math.min(300, Math.round(W * 0.6))), H: Math.max(10, Math.min(200, Math.round(W * 0.2))) }; })();
// 0. 清理编辑页
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && /tiktok/i.test(x.url))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理 tiktok 页 ${k} 个`); await wait(1200); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/tiktokProduct/add' });
await wait(20000);
const cur = async () => String(await c.evalJS(`(()=>{const s=[...document.querySelectorAll('.ant-select')].filter(e=>e.offsetParent!==null)[0];
return s?((s.querySelector('.ant-select-selection-item')||{}).innerText||''):'';})()`));
// 1. 选店铺 AutoMavenShop_MY
async function pickShop() {
for (let attempt = 0; attempt < 3; attempt++) {
const pos = JSON.parse(String(await c.evalJS(`(()=>{const s=[...document.querySelectorAll('.ant-select')].filter(e=>e.offsetParent!==null)[0];
if(!s)return'[]'; s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if (!pos.length) return 'no-select';
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: pos[0], y: pos[1], button: 'left', clickCount: 1 });
await wait(1800);
await c.evalJS(`(()=>{const inp=document.querySelector('.ant-select-selection-search-input')||document.querySelector('.ant-select input');
if(!inp)return 0; inp.focus();
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,'AutoMaven');
inp.dispatchEvent(new Event('input',{bubbles:true})); return 1;})()`);
await wait(2200);
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null);
if(!dd.length)return'no-dd'; const d=dd[dd.length-1];
const hit=[...d.querySelectorAll('.ant-select-item-option')].find(o=>/AutoMavenShop_MY/i.test(o.innerText||''));
if(!hit)return'no-hit';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
await wait(6000);
const s = await cur();
if (/AutoMavenShop_MY/i.test(s)) return 'ok:' + s;
log(' 店铺重试', attempt + 1, r, JSON.stringify(s));
}
return 'fail';
}
// 2. 分类
async function pickCategory(kw) {
await c.evalJS(`(()=>{document.querySelectorAll('.ant-select-dropdown').forEach(d=>{if(d.offsetParent!==null)d.style.display='none';});
document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true})); document.body.click(); return 1;})()`);
await wait(1000);
const open = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*选择分类\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[0].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
if (open === 'nf') return 'no-btn';
await wait(3500);
const r = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const inp=m.querySelector('input[placeholder="搜索分类名称"]'); if(!inp)return'no-input';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(kw)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
const b=[...m.querySelectorAll('button')].find(x=>/^\\s*搜索\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'searched';})()`);
if (r !== 'searched') return r;
await wait(4000);
const pick = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const its=[...m.querySelectorAll('.search-result-item')];
if(!its.length)return'no-result';
const BAD=/二手|Barang Terpakai|Second|Used|Pre-?owned|Refurb/i;
const texts=its.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
let idx=texts.findIndex(t=>!BAD.test(t));
if(idx<0) idx=0;
const hit=its[idx];
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked['+idx+'/'+texts.length+']:'+texts[idx];})()`);
await wait(4500);
return pick;
}
// 2b. 多关键词回退 + 失败即中止
async function pickCategoryAny(kws) {
const tried = [];
for (const kw of kws) {
const r = String(await pickCategory(kw));
tried.push(kw + '\u2192' + r.slice(0, 45));
if (!/no-result|no-input|no-modal|no-btn/.test(r)) return { r, tried };
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(m){const x=m.querySelector('.ant-modal-close'); if(x&&x.offsetParent!==null)x.click();} return 1;})()`);
await wait(1500);
}
return { r: 'ALL-FAILED', tried };
}
// 3. 直填 store
const setStore = (js) => c.evalJS(js);
log('=== TikTok 建品 ===', OID, IN.title.slice(0, 50));
log('1) 店铺:', await pickShop());
const kw = CAT_KW || IN.cat_kw || IN.title.split(/[ ,]+/).slice(0, 2).join(' ');
const catRes = await pickCategoryAny([kw].concat(IN.cat_fb || []));
log('2) 分类:', SAN(String(catRes.r)).slice(0, 110), '| 尝试:', catRes.tried.join(' / '));
if (/ALL-FAILED|no-result|no-input|no-btn/.test(String(catRes.r))) { log('\u274c 分类未选中,跳过保存'); await c.shot('/tmp/tk_fail_' + OID + '.jpeg').catch(() => {}); process.exit(2); }
// 4. 文案
const desc = IN.bullets.map(b => '• ' + b).join('<br>') + '<br><br>Keywords: ' + (IN.keywords || '');
const wr = await setStore(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const f=p._s.get('tiktokGlobalAddProductStore').$state.formState;
f.productName=${JSON.stringify(IN.title)};
f.keyProductFeatures=${JSON.stringify(IN.bullets.slice(0, 5))};
f.searchTerms=${JSON.stringify((IN.keywords || '').split(',').map(s => s.trim()).filter(Boolean).slice(0, 10))};
f.description='<style>img{display: block}p{margin-block-start: 0; margin-block-end: 0}</style>'+${JSON.stringify(desc)};
return 'ok';})()`);
log('3) 文案:', wr, '| 标长', IN.title.length);
// 5. 变种属性
const vr = await c.evalJS(`(()=>{
const h=[...document.querySelectorAll('*')].filter(e=>e.offsetParent!==null&&/^\\s*变种属性\\s*$/.test((e.innerText||'').trim())).pop();
if(!h)return'no-sec'; let box=h;
for(let i=0;i<6&&box.parentElement;i++){ box=box.parentElement; if(box.querySelector('input[class*="!w-200"]'))break; }
const inp=box.querySelector('input[class*="!w-200"]'); if(!inp)return'no-inp';
inp.focus(); Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(inp,${JSON.stringify(VAR_VAL)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
inp.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true}));
const btn=[...box.querySelectorAll('button,a,span')].find(e=>/^\\s*(添加|Add)\\s*$/.test(e.innerText||''));
if(btn) for(const ev of ['mousedown','mouseup','click']) btn.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'ok';})()`);
log('4) 变种属性:', vr, VAR_VAL);
await wait(3500);
// 6. SKU 价格/库存
const SKU = 'AM-' + OID;
const sr = await c.evalJS(`(()=>{
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const tbl=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null).find(t=>[...t.querySelectorAll('th')].some(h=>/本地展示价/.test(h.innerText||'')));
if(!tbl)return'no-table';
const ths=[...tbl.querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
const done=[];
rows.forEach((tr,ri)=>{ [...tr.querySelectorAll('td')].forEach((td,ci)=>{ const name=ths[ci]||''; const ins=[...td.querySelectorAll('input')];
if(/^SKU/i.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],${JSON.stringify(SKU)}+(rows.length>1?('-'+ri):'')); done.push('sku'); }
if(/本地展示价/.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],${JSON.stringify(String(IN.price_myr))}); done.push('price'); }
if(/^库存/.test(name)&&ins[0]&&!ins[0].value){ setV(ins[0],'999'); done.push('stock'); } }); });
return 'rows'+rows.length+':'+[...new Set(done)].join(',');})()`);
log('5) SKU:', sr, '| MYR', IN.price_myr);
await wait(2000);
// 7. 重量 + 尺寸
const setItem = (rx, sel, val) => c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(rx)}).test(l.innerText||'');});
const el=it?it.querySelector(${JSON.stringify(sel)}):null; if(!el)return'nf';
const proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el, ${JSON.stringify(String(val))});
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); return 'ok';})()`);
log('6) 重量:', await setItem('重量', 'input:not([type=hidden])', IN.weight));
const dim = await c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(x=>{const l=x.querySelector('label'); return l&&/尺寸规格/.test(l.innerText||'');});
if(!it)return'nf'; const ins=[...it.querySelectorAll('input')];
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const L=ins.find(i=>i.placeholder==='长')||ins[0], W2=ins.find(i=>i.placeholder==='宽')||ins[1], H=ins.find(i=>i.placeholder==='高')||ins[2];
if(L)setV(L,${DIMS.L}); if(W2)setV(W2,${DIMS.Wi}); if(H)setV(H,${DIMS.H});
return 'ok:'+ins.length;})()`);
log('7) 尺寸:', dim, JSON.stringify(DIMS));
// 8. 图片
const ir = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const st=p._s.get('tiktokGlobalImgStore').$state;
const urls=${JSON.stringify(IN.imgs)};
st.formState.mainImage=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800,checked:true}));
return 'imgs:'+st.formState.mainImage.length;})()`);
log('8) 图片:', ir, '(共', IN.imgs.length, ')');
await wait(2500);
// 9. 来源URL
log('9) 来源URL:', await setItem('来源URL', 'input:not([type=hidden])', 'https://detail.1688.com/offer/' + OID + '.html'));
await wait(1200);
// 10. 保存
const before = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const b=p._s.get('tiktokGlobalAddBasicStore').$state;
return JSON.stringify({shopId:b.formState.shopId, cat:b.formState.categoryId, site:b.site});})()`);
log('10) 保存前状态:', before);
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('11) 保存点击:', sv);
await wait(9000);
const msg = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal')].filter(e=>e.offsetParent!==null)
.map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,160)).filter(Boolean);
return JSON.stringify({url:location.href, msgs:ms.slice(0,4)});})()`);
log('12) 结果:', SAN(String(msg)).slice(0, 400));
await c.shot('/tmp/tk_pilot.jpeg').catch(() => {});
process.exit(0);
+47
View File
@@ -0,0 +1,47 @@
6980309334003
6980309334010
6980309334027
6980309334034
6980309334041
6980309334058
6980309334065
6980309334072
6980309334089
6980309334096
6980309334102
6980309334119
6980309334126
6980309334133
6980309334140
6980309334157
6980309334164
6980309334171
6980309334188
6980309334195
6980309334201
6980309334218
6980309334225
6980309334232
6980309334249
6980309334256
6980309334263
6980309334270
6980309334287
6980309334294
6980309334300
6980309334317
6980309334324
6980309334331
6980309334348
6980309334355
6980309334362
6980309334379
6980309334386
6980309334393
6980309334409
6980309334416
6980309334423
6980309334430
6980309334447
6980309334454
6980309334461
+97
View File
@@ -0,0 +1,97 @@
{
"items": [
"* 店铺账号 [select] Snowmountain",
"* 新版产品分类 [select] 发夹、别针和夹子(Hair Barret",
" 是否为单品 [input] false",
" 店小秘分类 [-] ",
" 来源URL [input] ",
" [input] ",
"* Product Name (产品名称) [input] ",
"* SKU [input] ",
"* Product Identifiers (产品标识符) [select] UPC",
"* 产品属性 [ta] Gram",
"* Brand(品牌) [input] ",
" Small Parts Warning Code(小零件警告代码) [input] 2 - Choking hazard c",
" Net Content(净内容) [select] Gram",
" Multipack Quantity(多包装数量) [input] 1",
" Material(材料) [input] rhinestone",
" Label Image(标签图像) [ta] ",
" Label Image URL(标签图片URL) [ta] ",
" Label Image Contains(标签图像包含) [select] ",
" Is Prop 65 Warning Required(需要65号提案警告吗?) [select] No",
" Has Written Warranty(已书面保修) [select] No",
" Count Per Pack(每包计数) [input] 1",
" Condition(条件) [select] New(New)",
" Color Category(颜色类别) [input] ",
" Color(颜色) [ta] ",
" Assembled Product Width(组装产品宽度) [select] in",
" Assembled Product Depth(组装产品深度) [select] in",
" Age Group(年龄组) [input] Newborn",
" Country of Origin - Substantial Transformation(原产国-实质性转变) [input] ",
" Automate Pricing(自动定价) [ta] ",
" Minimum Seller Allowed Price(最低卖家允许价格) [input] ",
" Maximum Seller Allowed Price(卖方允许的最高价格) [input] ",
" Repricer Strategy(重新定价策略) [ta] ",
" External Product Identifier(外部产品标识符) [ta] ",
" External Product ID Type(外部产品ID类型) [select] ",
" External Product ID(外部产品ID) [ta] ",
" Product Package Dimensions and Weight(产品包装尺寸及重量) [input] ",
" Product Package Dimensions Depth (in)(产品包装尺寸深度(英寸)) [input] ",
" Product Package Weight (lbs)(产品包装重量(磅)) [input] ",
" Product Package Dimensions Height (in)(产品包装尺寸 高度(英寸)) [input] ",
" Product Package Dimensions Width (in)(产品包装尺寸 宽度(英寸)) [input] ",
" Site Start Date(网站开始日期) [input] ",
" Site End Date(网站结束日期) [input] ",
" State Restrictions(州限制) [ta] ",
" State Restrictions Reason(国家限制原因) [select] ",
" ZIP Codes(邮政编码) [ta] ",
" States(各州) [select] ",
" Academic Institution(学术机构) [ta] ",
" Character(性格) [input] ",
" Character Group(字符组) [input] ",
" Collection(收藏) [ta] ",
" Total Count(总计数) [input] ",
" Designer(设计师) [ta] ",
" Embellishment Type(装饰类型) [input] ",
" Closure Type(闭合类型) [input] Clip",
" Brand License(品牌授权) [input] ",
" Hair Barrette, Pin & Clip Type(发夹、别针和夹子类型) [input] Tiara Hairpin",
" Retail Packaging(零售包装) [input] ",
" Is Primary Variant(是主要变体) [input] ",
" Manufacturer Name(制造商名称) [ta] ",
" Manufacturer Part Number(制造商零件编号) [ta] ",
" Model Number(型号) [ta] ",
" Net Content Statement(净内容声明) [ta] ",
" Occasion(场合) [input] ",
" Pattern(图案) [input] ",
" Number of Pieces(件数) [input] ",
" Product Line(产品线) [input] ",
" California Prop 65 Warning Text(加州65号提案警告文本) [ta] ",
" Required Storage Condition(所需存储条件) [input] Ambient Storage",
" Season(季节) [input] Winter",
" Shape(形状) [ta] ",
" Size(尺寸) [ta] ",
" Size Descriptor(大小描述符) [input] ",
" Sports League(体育联盟) [input] ",
" Sports Team(运动队) [input] ",
" Style(风格) [input] ",
" Theme(主题) [ta] ",
" Third Party Accreditation Symbol on Product Package Code(产品包装代码上的第三方认可符号) [input] ",
" Warranty Text(保修文本) [ta] ",
" Warranty URL(保修网址) [input] ",
"* 价格 [input] ",
"* 库存 [input] ",
"* 运输重量 [input] ",
" MSRP [input] ",
"* 产品图片 [-] ",
"* 产品描述 [ta] ",
"* key feature [ta] ",
" Fulfillment Lag Time (Lag Time) [input] ",
" Must ship alone? (单独运输) [input] ",
" Ships in Original Packaging (原装运输) [input] ",
" Product is or Contains this Battery Type(电子元器件类型) [input] ",
" Product is or Contains a Chemical, Aerosol or Pesticide?(是否包含化学品) [input] ",
" Product is or Contains an Electronic Component?(是否包含电子元器件) [input] "
],
"tables": []
}
+154
View File
@@ -0,0 +1,154 @@
[
"眼镜盒(Eyeglass Cases)",
"缝纫别针(Sewing Pins)",
"文件文件夹(File Folders)",
"钱夹(Money Clips)",
"自行车瓶笼(Bicycle Bottle Cages)",
"肥皂盘和持有人(Soap Dishes & Holders)",
"时尚头带(Fashion Headbands)",
"鞋带(Shoelaces)",
"化妆包(Makeup Bags)",
"举重手套(Weight Lifting Gloves)",
"剪贴簿装饰(Scrapbooking Embellishments)",
"自行车后视镜(Bicycle Mirrors)",
"桌面管理器(Desktop Organizers)",
"行李标签(Luggage Tags)",
"汽车装饰内饰五金配件(Automotive Decorative Interior Hardware & Accessories)",
"零钱包(Coin Purses)",
"汽车组织者(Automotive Organizers)",
"牙刷套(Toothbrush Covers)",
"墙贴和贴纸(Wall Decals & Stickers)",
"钩环紧固件(Hook & Loop Fasteners)",
"窗膜(Window Film)",
"自行车制动器组(Bicycle Brake Sets)",
"标签(Labels)",
"黑头和白头去除工具(Blackhead & Whitehead Remover Tools)",
"无焰蜡烛(Flameless Candles)",
"礼品袋(Gift Bags)",
"跳绳(Jump Ropes)",
"冰箱储物箱、搁板和储物篮(Refrigerator Bins, Shelves & Baskets)",
"可重复使用的环保袋(Reusable Eco Bags)",
"首饰盒(Jewelry Boxes)",
"耳环(Earrings)",
"派对横幅(Party Banners)",
"应急准备工具包(Emergency Preparedness Kits)",
"手套和连指手套(Gloves & Mittens)",
"别针和胸针(Pins & Brooches)",
"急救包(First Aid Kits)",
"数学工具包(Math Tool Kits)",
"风铃(Wind Chimes)",
"护照持有人(Passport Holders)",
"铅笔盒(Pencil Boxes)",
"标尺(Rulers)",
"书签(Bookmarks)",
"浴室贴花(Bath Appliques)",
"花园雕像(Garden Statues)",
"浇水喷嘴(Watering Nozzles)",
"袜子(Socks)",
"隐形眼镜盒(Contact Lens Cases)",
"缝纫紧固件(Sewing Fasteners)",
"摩托车配件(Motorcycle Accessories)",
"汽车反光镜(Automotive Reflectors)",
"耳机盒(Headphone Cases)",
"洗漱用具包(Toiletry Kits & Bags)",
"手提包挂钩衣架(Handbag Hook Hangers)",
"铅笔(Pencils)",
"刷刷(Basting Brushes)",
"按摩器(Massagers)",
"篮子(Baskets)",
"荧光笔(Highlighters)",
"计算机机架和支架(Computer Racks & Mounts)",
"熨烫垫(Ironing Mats)",
"游泳袋(Swimming Gear Bags)",
"手袋(Handbags)",
"空白识别徽章(Blank Identification Badges)",
"新奇和堵嘴玩具(Novelty & Gag Toys)",
"葡萄酒手提袋、包和背包(Wine Totes, Bags & Backpacks)",
"隐形眼镜清洁系统(Contact Lens Cleaning Systems)",
"化妆刷和涂抹器(Makeup Brushes & Applicators)",
"发夹、别针和夹子(Hair Barrettes, Pins & Clips)",
"手机的皮肤(Skins for Cell Phones)",
"贴纸(Stickers)",
"自行车车架包(Bicycle Frame Bags)",
"标记(Markers)",
"卷发器(Hair Rollers)",
"自行车座杆夹具(Bicycle Seatpost Clamps)",
"墙纸(Wallpaper)",
"便当盒(Bento Boxes)",
"化妆组织者(Makeup Organizers)",
"硬件挂钩(Hardware Hooks)",
"储物箱和储物箱(Storage Chests & Boxes)",
"自行车铃铛和喇叭(Bicycle Bells & Horns)",
"汽车贴花和贴纸(Automotive Decals & Stickers)",
"酒塞和软木塞(Wine Stoppers & Corks)",
"腰包(Fanny Packs)",
"手提包整理包和插页(Handbag Organizers & Inserts)",
"药丸组织者(Pill Organizers)",
"运动装备包(Sports Equipment Bags)",
"浴帽(Shower Caps)",
"面部镜子(Face Mirrors)",
"钱包(Wallets)",
"钢笔(Pens)",
"鼻子和耳朵修剪器(Nose & Ear Trimmers)",
"卷发器(Hair Curling Irons)",
"钥匙扣(Keychains)",
"壁橱收纳盒(Closet Organizers)",
"项链吊坠(Necklace Pendants)",
"项链(Necklaces)",
"气球(Balloons)",
"漏斗和漏斗组(Funnels & Funnel Sets)",
"铅笔盒(Pencil Cases)",
"医用袋(Medical Bags)",
"键盘保护器(Keyboard Protectors)",
"鞋袋(Shoe Bags)",
"铅笔和钢笔橡皮擦(Pencil & Pen Erasers)",
"排水蛇(Drain Snakes)",
"学校和办公用纸(School & Office Paper)",
"牙刷架(Toothbrush Holders)",
"行李带(Luggage Straps)",
"包装组织者,立方体和文件夹(Packing Organizers, Cubes & Folders)",
"艺术画笔(Art Brushes)",
"眼影(Eye Shadow)",
"自行车灯(Bicycle Lights)",
"书挡(Bookends)",
"游戏附加组件(Game Add-On)",
"蜡笔(Crayons)",
"设备盖(Appliance Covers)",
"写作笔记本和素描本(Writing Notebooks & Sketch Books)",
"食品储存罐和容器(Food Storage Jars & Containers)",
"背包(Backpacks)",
"小雕像和小装饰品(Figurines & Knick-Knacks)",
"自行车拨链器(Bicycle Derailleurs)",
"手动卷笔刀(Manual Pencil Sharpeners)",
"花盆和花盆(Plant Pots & Planters)",
"公文包(Briefcases)",
"行李袋(Duffel Bags)",
"帽子(Hats)",
"面部清洁刷(Facial Cleansing Brushes)",
"头发刷子(Hair Brushes)",
"自行车曲柄(Bicycle Cranksets)",
"装饰品(Ornaments)",
"包包饰物(Bag Charms)",
"自行车工具(Bicycle Tools)",
"睫毛夹(Eyelash Curlers)",
"文具及文具套装(Stationery & Stationery Sets)",
"发梳(Hair Combs)",
"可重复使用的午餐袋和盒子(Reusable Lunch Bags & Boxes)",
"信使包和单肩包(Messenger & Shoulder Bags)",
"便条卡(Note Cards)",
"腕带(Wristbands)",
"铅笔握把(Pencil Grips)",
"车把胶带(Handlebar Tape)",
"绑腿(Gaiters)",
"摩托车零件(Motorcycle Parts)",
"明信片(Postcards)",
"牙签(Toothpicks)",
"自行车挡泥板(Bicycle Fenders)",
"名片夹(Business Card Holders)",
"其他化妆刷和工具(Other Makeup Brushes & Tools)",
"其他发型造型工具和产品(Other Hair Styling Tools & Products)",
"其他派对用品(Other Party Supplies)",
"其他手机配件(Other Cell Phone Accessories)",
"Other Bicycle Parts(Other Bicycle Parts)",
"其他旅行配件(Other Travel Accessories)"
]
+227
View File
@@ -0,0 +1,227 @@
{
"1027332215847": {
"cat": "picked:储物箱和储物箱(Storage",
"err": "仍报错的字段: []",
"ok": true
},
"547183997437": {
"cat": "picked:项链吊坠(Necklace",
"err": "仍报错的字段: []",
"ok": true
},
"548459586741": {
"cat": "picked:其他派对用品(Other",
"err": "仍报错的字段: []",
"ok": true
},
"591994902720": {
"cat": "picked:铅笔盒(Pencil",
"err": "仍报错的字段: []",
"ok": true
},
"610164857063": {
"cat": "picked:时尚头带(Fashion",
"err": "仍报错的字段: []",
"ok": true
},
"621300323670": {
"cat": "picked:桌面管理器(Desktop",
"err": "仍报错的字段: []",
"ok": true
},
"623011262210": {
"cat": "picked:墙贴和贴纸(Wall",
"err": "仍报错的字段: []",
"ok": true
},
"627879474686": {
"cat": "picked:袜子(Socks)",
"err": "仍报错的字段: []",
"ok": true
},
"639409307567": {
"cat": "picked:墙贴和贴纸(Wall",
"err": "仍报错的字段: []",
"ok": true
},
"641673262387": {
"cat": "picked:首饰盒(Jewelry",
"err": "仍报错的字段: []",
"ok": true
},
"657617635303": {
"cat": "picked:桌面管理器(Desktop",
"err": "仍报错的字段: []",
"ok": true
},
"661190598139": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"676236596821": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"682195499823": {
"cat": "picked:铅笔盒(Pencil",
"err": "仍报错的字段: []",
"ok": true
},
"685456757807": {
"cat": "picked:缝纫紧固件(Sewing",
"err": "仍报错的字段: []",
"ok": true
},
"694637537727": {
"cat": "picked:可重复使用的午餐袋和盒子(Reusable",
"err": "仍报错的字段: []",
"ok": true
},
"698782391248": {
"cat": "picked:贴纸(Stickers)",
"err": "仍报错的字段: []",
"ok": true
},
"703822717997": {
"cat": "picked:桌面管理器(Desktop",
"err": "仍报错的字段: []",
"ok": true
},
"707826905566": {
"cat": "picked:钱包(Wallets)",
"err": "仍报错的字段: []",
"ok": true
},
"708420302844": {
"cat": "picked:钥匙扣(Keychains)",
"err": "仍报错的字段: []",
"ok": true
},
"714492799386": {
"cat": "picked:化妆包(Makeup",
"err": "仍报错的字段: []",
"ok": true
},
"719391062058": {
"cat": "picked:化妆组织者(Makeup",
"err": "仍报错的字段: []",
"ok": true
},
"726442972676": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"735147938123": {
"cat": "picked:时尚头带(Fashion",
"err": "仍报错的字段: []",
"ok": true
},
"739147684759": {
"cat": "picked:缝纫紧固件(Sewing",
"err": "仍报错的字段: []",
"ok": true
},
"750271307455": {
"cat": "picked:钥匙扣(Keychains)",
"err": "仍报错的字段: []",
"ok": true
},
"774494945453": {
"cat": "picked:钥匙扣(Keychains)",
"err": "仍报错的字段: []",
"ok": true
},
"775057912118": {
"cat": "picked:钥匙扣(Keychains)",
"err": "仍报错的字段: []",
"ok": true
},
"787261097583": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"823560564174": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"823838532886": {
"cat": "picked:名片夹(Business",
"err": "仍报错的字段: []",
"ok": true
},
"833753630925": {
"cat": "picked:时尚头带(Fashion",
"err": "仍报错的字段: []",
"ok": true
},
"836512561982": {
"cat": "picked:化妆包(Makeup",
"err": "仍报错的字段: []",
"ok": true
},
"853414558425": {
"cat": "picked:其他手机配件(Other",
"err": "仍报错的字段: []",
"ok": true
},
"854003999648": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"857781294719": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"863384503032": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"863524134410": {
"cat": "picked:贴纸(Stickers)",
"err": "仍报错的字段: []",
"ok": true
},
"866537106657": {
"cat": "picked:缝纫紧固件(Sewing",
"err": "仍报错的字段: []",
"ok": true
},
"893592264148": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"896469995553": {
"cat": "picked:钥匙扣(Keychains)",
"err": "仍报错的字段: []",
"ok": true
},
"902440558099": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"944470567338": {
"cat": "picked:发夹、别针和夹子(Hair",
"err": "仍报错的字段: []",
"ok": true
},
"953869908708": {
"cat": "picked:墙贴和贴纸(Wall",
"err": "仍报错的字段: []",
"ok": true
},
"997450831819": {
"cat": "picked:贴纸(Stickers)",
"err": "仍报错的字段: []",
"ok": true
}
}
+48
View File
@@ -0,0 +1,48 @@
{
"997450831819": "184807701434436987",
"953869908708": "184807701434436937",
"944470567338": "184807701434436423",
"902440558099": "184807701434436399",
"896469995553": "184807701434436361",
"893592264148": "184807701434436319",
"866537106657": "184807701434436311",
"863524134410": "184807701434436289",
"863384503032": "184807701434436279",
"857781294719": "184807701434436257",
"854003999648": "184807701434436227",
"853414558425": "184807701434436183",
"836512561982": "184807701434436131",
"833753630925": "184807701434435981",
"823838532886": "184807701434435937",
"823560564174": "184807701434435823",
"787261097583": "184807701434435821",
"775057912118": "184807701434435805",
"774494945453": "184807701434435785",
"750271307455": "184807701434435757",
"739147684759": "184807701434435749",
"735147938123": "184807701434435731",
"726442972676": "184807701434435725",
"719391062058": "184807701434435723",
"714492799386": "184807701434435703",
"708420302844": "184807701434435695",
"707826905566": "184807701434435659",
"703822717997": "184807701434435649",
"698782391248": "184807701434435563",
"694637537727": "184807701434435549",
"685456757807": "184807701434435539",
"682195499823": "184807701434435519",
"676236596821": "184807701434435509",
"661190598139": "184807701434435491",
"657617635303": "184807701434435461",
"641673262387": "184807701434435433",
"639409307567": "184807701434435427",
"627879474686": "184807701434435419",
"623011262210": "184807701434435411",
"621300323670": "184807701434435409",
"610164857063": "184807701434435397",
"591994902720": "184807701434435375",
"548459586741": "184807701434435371",
"547183997437": "184807701434435369",
"1027332215847": "184807701434434765",
"607330049419": "184807701434434763"
}
File diff suppressed because it is too large. Load diff
+117
View File
@@ -0,0 +1,117 @@
{
"stores": [
"walmartAddStore",
"walmartBasicStore",
"walmartProductStore",
"walmartAttrStore",
"walmartImgStore",
"walmartDescStore",
"walmartPackStore",
"walmartSupplementStore",
"walmartAddVariationStore",
"walmartDxmInfoStore",
"walmartAddThemeStore",
"walmartPriceShippingStore"
],
"items": [
{
"lab": "店铺账号",
"req": true,
"kind": "select",
"val": "Snowmountain"
},
{
"lab": "新版产品分类",
"req": true,
"kind": "input",
"val": ""
},
{
"lab": "是否为单品",
"req": false,
"kind": "input",
"val": "false"
},
{
"lab": "店小秘分类",
"req": false,
"kind": "-",
"val": ""
},
{
"lab": "来源URL",
"req": false,
"kind": "input",
"val": ""
},
{
"lab": "产品属性",
"req": false,
"kind": "-",
"val": ""
},
{
"lab": "单位数量单价",
"req": false,
"kind": "input",
"val": ""
},
{
"lab": "库存",
"req": true,
"kind": "input",
"val": ""
},
{
"lab": "产品图片",
"req": true,
"kind": "-",
"val": ""
},
{
"lab": "产品描述",
"req": true,
"kind": "textarea",
"val": ""
},
{
"lab": "key feature",
"req": true,
"kind": "textarea",
"val": ""
}
],
"dump": {
"walmartAddStore": {
"sharedData.isTemplateQuoting": "false",
"sharedData.isVariationTemplateEditing": "false",
"sharedData.pageType": "add",
"attrsInfo.hasPpuQuantity": "false",
"defaultAttrsInfo.hasPpuQuantity": "false"
},
"walmartBasicStore": {
"formState.shopId": "7661631",
"formState.hasVariant": "false"
},
"walmartProductStore": {
"formState.productIdType": "UPC",
"dataState.skuChecked": "false",
"dataState.productIdChecked": "false"
},
"walmartAttrStore": {
"formState.isNoBrand": "false"
},
"walmartImgStore": {},
"walmartDescStore": {},
"walmartPackStore": {},
"walmartSupplementStore": {},
"walmartAddVariationStore": {
"fieldProductIdType": "UPC",
"baseState.skuChecked": "false",
"baseState.upcChecked": "false"
},
"walmartDxmInfoStore": {},
"walmartAddThemeStore": {},
"walmartPriceShippingStore": {}
}
}
+140
View File
@@ -0,0 +1,140 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── Walmart (Snowmountain, shopId 7661631) 建品配方 ──
node wmbatch.mjs <oid>
选店铺 → 选类目(索引) → Product Name/SKU/UPC(EAN)/Brand → 价格+库存+运输重量
→ 图片 → 描述+key feature → 保存 → 回读 */
const OID = process.argv[2];
if (!OID) { console.log('用法: node wmbatch.mjs <oid>'); process.exit(1); }
const IN = JSON.parse(fs.readFileSync('/tmp/wm_input.json', 'utf8'))[OID];
if (!IN) { console.log('无输入数据', OID); process.exit(1); }
const CATS = JSON.parse(fs.readFileSync('/tmp/wm_cats.json', 'utf8'));
const CAT = CATS[IN.cat_index];
const SHOP = 'Snowmountain';
const SKU = 'WM-' + OID;
const BRAND = process.env.WM_BRAND || 'Generic';
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && /walmart/i.test(x.url))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理 walmart 页 ${k} 个`); await wait(1200); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/walmartProduct/add' });
await wait(24000);
// 1. 店铺
const spos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: spos[0], y: spos[1], button: 'left', clickCount: 1 });
await wait(2500);
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>new RegExp('^\\\\s*${SHOP}\\\\s*$','i').test(o.innerText||''));
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
log('1) 店铺:', pk);
await wait(9000);
// 2. 类目
await c.evalJS(`document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));document.body.click();`);
await wait(1200);
const cpos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.innerText||'');});
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: cpos[0], y: cpos[1], button: 'left', clickCount: 1 });
await wait(3000);
let catRes = 'nf';
for (let i = 0; i < 70; i++) {
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const target=${JSON.stringify(CAT)};
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===target);
if(!hit)return'scroll';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
if (r === 'picked') { catRes = 'picked:' + CAT; break; }
if (r === 'no-dd') { catRes = 'no-dd'; break; }
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return 0; const rc=dd.querySelector('.rc-virtual-list-holder')||dd; rc.scrollTop=rc.scrollTop+340; return rc.scrollTop;})()`);
await wait(330);
}
log('2) 类目:', catRes);
if (catRes === 'nf' || catRes === 'no-dd') { log('❌ 类目未选中,中止'); process.exit(2); }
await wait(16000);
// 3. 全部字段直填 store
const wr = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const g=n=>p._s.get(n).$state;
const basic=g('walmartBasicStore').formState;
basic.hasVariant=false;
const pr=g('walmartProductStore').formState;
pr.productName=${JSON.stringify(IN.title)};
pr.productIdType='UPC';
pr.productId=${JSON.stringify(IN.ean)};
pr.sku=${JSON.stringify(SKU)};
const at=g('walmartAttrStore').formState;
at.brand=${JSON.stringify(BRAND)};
const ps=g('walmartPriceShippingStore').formState;
ps.price=${JSON.stringify(String(IN.price_usd))};
ps.inventory='999';
ps.shippingWeight=${JSON.stringify(String(IN.weight_lbs))};
const ds=g('walmartDescStore').formState;
ds.description=${JSON.stringify(IN.bullets.map(b => '• ' + b).join('<br>'))};
ds.keyFeatures=${JSON.stringify(IN.bullets.slice(0, 3).map(b => b.slice(0, 100)))};
const im=g('walmartImgStore').formState;
const urls=${JSON.stringify(IN.imgs)};
im.mainImage=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800}));
return JSON.stringify({name:pr.productName.length, upc:pr.productId, price:ps.price, w:ps.shippingWeight, imgs:urls.length, kf:ds.keyFeatures.length});})()`);
log('3) 字段:', wr);
await wait(2500);
// 3b. DOM 兜底:把关键文本字段再按真实事件写一遍(Brand/价格/库存/重量等 store 不绑 DOM)
const setByLabel = (rx, val) => c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(rx)}).test(l.innerText||'');});
if(!it)return'nf';
const el=it.querySelector('textarea')||it.querySelector('input:not([type=hidden])');
if(!el)return'no-el';
const proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el, ${JSON.stringify(String(val))});
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true}));
return 'ok';})()`);
const fills = [
['Product Name|产品名称', IN.title],
['^\\s*SKU', SKU],
['^\\s*Brand|品牌', BRAND],
['^\\s*价格', String(IN.price_usd)],
['^\\s*库存', '999'],
['运输重量', String(IN.weight_lbs)],
['产品描述', IN.bullets.map(b => '• ' + b).join('\n')],
['key feature', IN.bullets.slice(0, 3).join('\n')],
];
const fillRes = [];
for (const [rx, v] of fills) fillRes.push(rx.split('|')[0] + '=' + (await setByLabel(rx, v)));
log('3b) DOM兜底:', fillRes.join(' '));
await wait(2000);
// 4. 保存
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('4) 保存:', sv);
const seen = [];
for (let i = 0; i < 18; i++) {
await wait(700);
const m = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal-confirm')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean); return JSON.stringify(ms.slice(0,3));})()`);
JSON.parse(String(m)).forEach(x => seen.push(x));
}
log('5) 提示:', SAN([...new Set(seen)].join(' || ')).slice(0, 420) || '(无)');
const er = await c.evalJS(`(()=>{const e=[...document.querySelectorAll('.ant-form-item-has-error')].map(x=>{const l=x.querySelector('label'); return (l?l.innerText.trim():'?');});
return JSON.stringify([...new Set(e)].slice(0,8));})()`);
log('6) 仍报错的字段:', SAN(String(er)).slice(0, 200));
await c.shot('/tmp/wm_pilot.jpeg').catch(() => {});
process.exit(0);
+278
View File
@@ -0,0 +1,278 @@
{
"1027332215847": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 4",
"save": "保存: 成功",
"ok": true
},
"547183997437": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"548459586741": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"591994902720": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"607330049419": {
"brand": "brand 现值: Unbranded",
"imgs": "补后图片数: 4",
"save": "保存: 成功",
"ok": true
},
"610164857063": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"621300323670": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"623011262210": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"627879474686": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"639409307567": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"641673262387": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"657617635303": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"661190598139": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"676236596821": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"682195499823": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"685456757807": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"694637537727": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"698782391248": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"703822717997": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"707826905566": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"708420302844": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"714492799386": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"719391062058": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"726442972676": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"735147938123": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"739147684759": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"750271307455": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 5",
"save": "保存: 成功",
"ok": true
},
"774494945453": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"775057912118": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"787261097583": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"823560564174": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"823838532886": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"833753630925": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"836512561982": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"853414558425": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"854003999648": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"857781294719": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"863384503032": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"863524134410": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"866537106657": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"893592264148": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"896469995553": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"902440558099": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"944470567338": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"953869908708": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
},
"997450831819": {
"brand": "brand 现值: Generic",
"imgs": "补后图片数: 6",
"save": "保存: 成功",
"ok": true
}
}
+80
View File
@@ -0,0 +1,80 @@
import { attach } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* 给已存在的 Walmart 产品补:无品牌 + 网络图片 */
const PID = process.argv[2];
const OID = process.argv[3];
const IN = JSON.parse(fs.readFileSync('/tmp/wm_input.json','utf8'))[OID];
if(!PID||!IN){ console.log('用法: node wmfixone.mjs <pid> <oid>'); process.exit(1); }
const wait = ms => new Promise(z => setTimeout(z, ms));
const log=(...a)=>console.log(' ',...a);
const tl=await (await fetch('http://127.0.0.1:9223/json/list')).json();
for(const t of tl.filter(x=>x.type==='page'&&/walmart/i.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(()=>{});
await wait(1000);
const t=await (await import('/tmp/cdp3.mjs')).newTarget('about:blank');
const {CDP}=await import('/tmp/cdp3.mjs');
const c=await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate',{url:`https://www.dianxiaomi.com/web/walmartProduct/edit?id=${PID}`});
await wait(24000);
// 1. 无品牌
const br=await c.evalJS(`(()=>{const lab=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/Brand/.test(l.innerText||'');});
if(!lab)return'nf';
const inp=lab.querySelector('input[name=brand]');
if(inp&&inp.value&&inp.value!=='')return 'already:'+inp.value;
const cb=[...lab.querySelectorAll('.ant-checkbox-wrapper')].find(e=>/无品牌/.test(e.innerText||''));
if(!cb)return'no-cb';
for(const ev of ['mousedown','mouseup','click']) cb.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
const i2=cb.querySelector('input[type=checkbox]'); if(i2) i2.click();
return 'clicked';})()`);
log('无品牌:',br);
await wait(2000);
const bv=await c.evalJS(`(()=>{const lab=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/Brand/.test(l.innerText||'');});
const inp=lab?lab.querySelector('input[name=brand]'):null; return inp?inp.value:'?';})()`);
log('brand 现值:', String(bv));
// 2. 图片(若已有则跳过)
const cur=await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品图片/.test(l.innerText||'');});
const m=(it?it.innerText:'').match(/已经选用了\\s*(\\d+)\\s*张/); return m?Number(m[1]):-1;})()`);
log('已有图片数:', String(cur));
if (Number(cur) < 4) {
const pos=JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品图片/.test(l.innerText||'');});
const b=[...it.querySelectorAll('button')].find(e=>/选择图片/.test(e.innerText||''));
b.scrollIntoView({block:'center'}); const r=b.getBoundingClientRect();
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1]}); await wait(1500);
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0]+2,y:pos[1]+2}); await wait(2200);
const hit=JSON.parse(String(await c.evalJS(`(()=>{const d=[...document.querySelectorAll('.ant-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!d)return'[]'; const it=[...d.querySelectorAll('li,.ant-dropdown-menu-item')].find(e=>/网络图片/.test(e.innerText||''));
if(!it)return'[]'; const r=it.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
if(hit.length){ await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:hit[0],y:hit[1]}); await wait(300);
for(const type of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type,x:hit[0],y:hit[1],button:'left',clickCount:1}); }
await wait(4000);
const f=await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return'no-modal'; const ta=m.querySelector('textarea'); if(!ta)return'no-ta';
ta.focus(); Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value').set.call(ta, ${JSON.stringify((IN.imgs||[]).join('\n'))});
ta.dispatchEvent(new Event('input',{bubbles:true})); ta.dispatchEvent(new Event('change',{bubbles:true}));
return 'n='+ta.value.split('\\n').filter(Boolean).length;})()`);
log('填URL:', f);
await wait(1200);
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m)return 0; const b=[...m.querySelectorAll('button')].find(x=>/^\\s*添加\\s*$/.test(x.innerText||''));
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(15000);
}
const after=await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品图片/.test(l.innerText||'');});
const m=(it?it.innerText:'').match(/已经选用了\\s*(\\d+)\\s*张/); return m?Number(m[1]):-1;})()`);
log('补后图片数:', String(after));
// 3. 保存
await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
const el=b[b.length-1]; el.scrollIntoView({block:'center'});
for(const ev of ['mousedown','mouseup','click']) el.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
let ok=false;
for(let i=0;i<16;i++){ await wait(800);
const m=await c.evalJS(`(()=>[...document.querySelectorAll('.ant-modal,.ant-message-notice')].some(e=>e.offsetParent!==null&&/成功/.test(e.innerText||'')))()`);
if(m){ ok=true; break; } }
log('保存:', ok?'成功':'未见成功提示');
process.exit(ok?0:3);
+433
View File
@@ -0,0 +1,433 @@
{
"548459586741": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kw": "whistle",
"time": "2026-09-19T09:43:14.224Z"
},
"627879474686": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kw": "socks",
"time": "2026-09-19T09:49:34.908Z"
},
"735147938123": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"headband",
"hair"
],
"time": "2026-09-19T09:58:13.215Z"
},
"764423265986": {
"ok": false,
"msg": "[]",
"imgs": 6,
"kws": [
"thermometer",
"hygrometer"
],
"time": "2026-09-20T07:43:15.046Z"
},
"610164857063": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"headband",
"hair"
],
"time": "2026-09-19T10:02:44.735Z"
},
"676236596821": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:04:28.203Z"
},
"866537106657": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"button"
],
"time": "2026-09-19T10:06:11.668Z"
},
"863524134410": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:07:55.135Z"
},
"623011262210": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:09:38.601Z"
},
"714492799386": {
"ok": false,
"msg": "分类失败:makeup bag → noresult [modal未关/无cat]"
},
"997450831819": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:12:19.863Z"
},
"639409307567": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:14:03.296Z"
},
"698782391248": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:15:46.706Z"
},
"707826905566": {
"ok": false,
"msg": "[]",
"imgs": 6,
"kws": [
"card holder",
"wallet"
],
"time": "2026-09-20T07:53:22.225Z"
},
"1027332215847": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 4,
"kws": [
"box"
],
"time": "2026-09-19T10:18:27.975Z"
},
"682195499823": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"pencil case",
"brush"
],
"time": "2026-09-20T07:56:07.816Z"
},
"739147684759": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"button"
],
"time": "2026-09-19T10:21:09.254Z"
},
"944470567338": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:22:52.766Z"
},
"787261097583": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:24:36.330Z"
},
"833753630925": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"headband",
"hair"
],
"time": "2026-09-19T10:26:19.820Z"
},
"863384503032": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:28:03.320Z"
},
"774494945453": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"keychain"
],
"time": "2026-09-19T10:29:46.680Z"
},
"661190598139": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"hair",
"hairband"
],
"time": "2026-09-19T10:31:30.243Z"
},
"685456757807": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"yarn"
],
"time": "2026-09-19T10:33:13.720Z"
},
"708420302844": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"keychain"
],
"time": "2026-09-19T10:34:57.167Z"
},
"547183997437": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"necklace"
],
"time": "2026-09-19T10:36:40.675Z"
},
"657617635303": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:38:24.097Z"
},
"641673262387": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"earring"
],
"time": "2026-09-19T10:40:07.524Z"
},
"953869908708": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"sticker"
],
"time": "2026-09-19T10:41:50.993Z"
},
"703822717997": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T10:43:34.366Z"
},
"694637537727": {
"ok": false,
"msg": "分类失败:insulated bag → noresult [modal未关/无cat]"
},
"836512561982": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"box"
],
"time": "2026-09-19T10:46:15.578Z"
},
"591994902720": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"pencil case",
"pen bag"
],
"time": "2026-09-20T07:50:36.285Z"
},
"853414558425": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"keychain"
],
"time": "2026-09-19T10:48:56.820Z"
},
"902440558099": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"hair",
"hairband"
],
"time": "2026-09-19T10:50:40.305Z"
},
"823838532886": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"card"
],
"time": "2026-09-19T10:52:23.793Z"
},
"823560564174": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"hair",
"hairband"
],
"time": "2026-09-19T10:54:07.265Z"
},
"621300323670": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"tape"
],
"time": "2026-09-19T10:55:50.760Z"
},
"896469995553": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"keychain"
],
"time": "2026-09-19T10:57:34.189Z"
},
"719391062058": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"storage box",
"organizer"
],
"time": "2026-09-20T07:58:53.640Z"
},
"857781294719": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T11:00:15.538Z"
},
"750271307455": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 5,
"kws": [
"keychain"
],
"time": "2026-09-19T11:01:58.924Z"
},
"726442972676": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T11:03:42.512Z"
},
"775057912118": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"keychain"
],
"time": "2026-09-19T11:05:25.954Z"
},
"893592264148": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"clip",
"hair"
],
"time": "2026-09-19T11:07:09.457Z"
},
"607330049419": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"hair",
"hairband"
],
"time": "2026-09-19T11:08:53.317Z"
},
"854003999648": {
"ok": true,
"msg": "[\"消息提示 您的产品已保存到「 待发布」,请在待发布页完成发布! 创建新产品继续编辑\"]",
"imgs": 6,
"kws": [
"hair",
"hairband"
],
"time": "2026-09-19T11:10:36.759Z"
}
}
+1
View File
@@ -0,0 +1 @@
[{"oid":"548459586741","batch":"OK","found":2,"id":"184807708921479685","cat":"202213618","subj":"6.5cm Colorful Plastic Party Whistles fo"},{"oid":"661190598139","batch":"OK","found":1,"id":"184807708921552911","cat":"201223336","subj":"Retro Hair Tie with Crystal Pendant for "},{"oid":"764423265986","batch":"FAIL","found":0,"id":"","cat":"","subj":""},{"oid":"685456757807","batch":"OK","found":1,"id":"184807708921553779","cat":"200001531","subj":"Pure Natural Cotton Yarn for Baby Knitti"},{"oid":"735147938123","batch":"OK","found":1,"id":"184807708921513553","cat":"201303801","subj":"Wide Denim Retro Hair Band for Women Col"},{"oid":"627879474686","batch":"OK","found":1,"id":"184807708921502753","cat":"202220055","subj":"Cotton Cartoon Print High Socks for Girl"},{"oid":"610164857063","batch":"OK","found":1,"id":"184807708921526779","cat":"201303801","subj":"Breathable Sports Headband Non-Slip Runn"},{"oid":"676236596821","batch":"OK","found":1,"id":"184807708921530261","cat":"200352146","subj":"Cute Cartoon Baby Hair Clips Set, 3 Pcs "},{"oid":"708420302844","batch":"OK","found":1,"id":"184807708921554161","cat":"200387151","subj":"Brass Keychain with 12 Zodiac Signs & 5 "},{"oid":"866537106657","batch":"OK","found":1,"id":"184807708921535221","cat":"200127142","subj":"Butterfly Resin Buttons for Sweaters Coa"},{"oid":"547183997437","batch":"OK","found":1,"id":"184807708921555383","cat":"200001699","subj":"Cute Horse Pendant Necklace Set Silver J"},{"oid":"657617635303","batch":"OK","found":1,"id":"184807708921556577","cat":"200352146","subj":"Clear Magnetic Paperclip Box with Pastel"},{"oid":"641673262387","batch":"OK","found":1,"id":"184807708921557607","cat":"200001691","subj":"Organza Jewelry Pouch Bag with Drawstrin"},{"oid":"953869908708","batch":"OK","found":1,"id":"184807708921558109","cat":"200003295","subj":"Transparent Window Stickers Christmas Sn"},{"oid":"703822717997","batch":"OK","found":1,"id":"184807708921558801","cat":"200352146","subj":"Stainless Steel Spring Clip for Notebook"},{"oid":"863524134410","batch":"OK","found":1,"id":"184807708921536363","cat":"200003295","subj":"50pcs Pink Aesthetic Vinyl Stickers for "},{"oid":"694637537727","batch":"FAIL","found":0,"id":"","cat":"","subj":""},{"oid":"623011262210","batch":"OK","found":1,"id":"184807708921537457","cat":"200003295","subj":"Transparent Snowflake Window Stickers fo"},{"oid":"836512561982","batch":"OK","found":1,"id":"184807708921559613","cat":"37042004","subj":"Cute Plush Velvet Cosmetic Case with Bea"},{"oid":"714492799386","batch":"FAIL","found":0,"id":"","cat":"","subj":""},{"oid":"591994902720","batch":"OK","found":1,"id":"184807708922339527","cat":"211113","subj":"Cute Cat Pencil Case with Moving Ears an"},{"oid":"853414558425","batch":"OK","found":1,"id":"184807708921560431","cat":"200387151","subj":"Soft Silicone Heart Phone Bracelet Keych"},{"oid":"997450831819","batch":"OK","found":1,"id":"184807708921538449","cat":"200003295","subj":"Matte PET Emoji Stickers for Goodnotes &"},{"oid":"639409307567","batch":"OK","found":1,"id":"184807708921540367","cat":"200003295","subj":"3D Glowing Butterfly Wall Stickers, Magn"},{"oid":"902440558099","batch":"OK","found":1,"id":"184807708921561153","cat":"201223336","subj":"Large Beige Fabric Hair Tie with Pearl f"},{"oid":"823838532886","batch":"OK","found":1,"id":"184807708921562235","cat":"200364150","subj":"Cute Cartoon Cat Key Card Holder for Met"},{"oid":"698782391248","batch":"OK","found":1,"id":"184807708921542239","cat":"200003295","subj":"Boho Style White PVC Laptop Stickers 10c"},{"oid":"707826905566","batch":"FAIL","found":0,"id":"","cat":"","subj":""},{"oid":"1027332215847","batch":"OK","found":1,"id":"184807708921544281","cat":"37042004","subj":"Silver Matte Tin Photo Box with Clear Wi"},{"oid":"682195499823","batch":"OK","found":1,"id":"184807708922342261","cat":"211113","subj":"96-Cell Brush Organizer Case for Waterco"},{"oid":"823560564174","batch":"OK","found":1,"id":"184807708921564695","cat":"201223336","subj":"Cute Heart Hair Ties Set, Soft Elastic B"},{"oid":"739147684759","batch":"OK","found":1,"id":"184807708921545827","cat":"200127142","subj":"Metallic Rectangular Coat Buttons with L"},{"oid":"621300323670","batch":"OK","found":1,"id":"184807708921568715","cat":"40512","subj":"Round Transparent Desktop Tape Dispenser"},{"oid":"601964534137","batch":"-","found":0,"id":"","cat":"","subj":""},{"oid":"896469995553","batch":"OK","found":1,"id":"184807708921569227","cat":"200387151","subj":"Angel Wings & Stars Keychain Y2K Retro B"},{"oid":"944470567338","batch":"OK","found":1,"id":"184807708921546567","cat":"200352146","subj":"Korean Style Plaid Triangle Hair Clip wi"},{"oid":"787261097583","batch":"OK","found":1,"id":"184807708921548291","cat":"200352146","subj":"Transparent Crab Hair Clips with Hearts,"},{"oid":"833753630925","batch":"OK","found":1,"id":"184807708921549201","cat":"201303801","subj":"Wide Plaid Fabric Headband for Women - C"},{"oid":"719391062Line truncated
+256
View File
@@ -0,0 +1,256 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* 速卖通批量建品(全自动):分类走 UI 弹窗搜索,其余按已验证配方 */
const SHOP = 4024039;
const FREIGHT_NAME = process.argv[2] || 'MY beauty 30g+1美金';
const ONLY = process.argv[3];
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const EN = JSON.parse(fs.readFileSync('/tmp/en_all.json', 'utf8'));
const CLEAN = JSON.parse(fs.readFileSync('/tmp/wm_clean.json', 'utf8'));
const ALL = JSON.parse(fs.readFileSync('/tmp/all48.json', 'utf8'));
const KW_FB = {};
const DONE_F = '/tmp/smt_batch_done.json';
const DONE = fs.existsSync(DONE_F) ? JSON.parse(fs.readFileSync(DONE_F, 'utf8')) : {};
// 分类关键词:从英文标题里取最能定位品类的词
function catKeywords(oid) {
const t = (EN[oid] || {}).title || '';
const CAND = [
[/whistle/i, ['whistle']],
[/hair tie|scrunchie|hair ring|ponytail|elastic/i, ['hair', 'hairband']],
[/hair clip|barrette|hairpin|clip/i, ['clip', 'hair']],
[/headband|hair band/i, ['headband', 'hair']],
[/keychain|key ring|key holder/i, ['keychain']],
[/sticker|decal/i, ['sticker']],
[/thermometer|hygrometer/i, ['thermometer']],
[/button/i, ['button']],
[/yarn|knitting/i, ['yarn']],
[/sock/i, ['socks']],
[/necklace|pendant/i, ['necklace']],
[/bracelet/i, ['bracelet']],
[/earring/i, ['earring']],
[/card holder|card case|badge/i, ['card']],
[/organizer|storage/i, ['organizer']],
[/box|case|holder|container/i, ['box']],
[/bag|pouch/i, ['bag']],
[/comb|brush/i, ['comb']],
[/mirror/i, ['mirror']],
[/tape dispenser/i, ['tape']],
[/hair dryer/i, ['hair dryer']],
];
for (const [rx, ks] of CAND) if (rx.test(t)) return ks;
return [(t.split(/\s+/)[0] || 'gift')];
}
const oids = ONLY ? [ONLY] : Object.keys(ALL).filter(o => !DONE[o] && (CLEAN[o] || []).length >= 1);
log(`待建商品: ${oids.length} | 已有记录: ${Object.keys(DONE).length}`);
for (const oid of oids) {
const en = EN[oid], imgs = CLEAN[oid] || [];
let kws = catKeywords(oid);
if (!en || !imgs.length) { log(`⚠ ${oid} 缺文案或图片,跳过`); continue; }
log('');
log(`═══ ${oid} | 图${imgs.length} 张 | 关键词候选 ${JSON.stringify(kws)} ═══`);
// 清掉旧标签
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x => x.type === 'page' && /\/web\/smt\/add/.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {});
await wait(900);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await c.send('Network.enable');
let posted = false;
c.on(m => { if (m.method === 'Network.requestWillBeSent') { const r = m.params.request;
if (r.method === 'POST' && /\/api\/smtProduct\/add\.json/.test(r.url)) posted = true; } });
try {
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/smt/add' });
await wait(32000);
await c.evalJS(`(function(){
window.__f=function(rx,val,sel){ sel=sel||'input';
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; var lb=it.querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf(rx)<0) continue;
var el=it.querySelector(sel); if(!el) return 'nf';
var proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el,String(val));
['input','change','blur'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});
return 'ok'; }
return 'noitem'; };
window.__openSel=function(rx){
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; var lb=it.querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf(rx)<0) continue;
var sel=it.querySelector('.ant-select'); if(!sel) return 'nosel';
var tg=sel.querySelector('.ant-select-selector')||sel; tg.scrollIntoView({block:'center'});
['mousedown','mouseup','click'].forEach(function(ev){tg.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'opened'; }
return 'noitem'; };
window.__pick=function(txt){ var dds=[].slice.call(document.querySelectorAll('.ant-select-dropdown')).filter(function(e){return e.offsetParent!==null;});
if(!dds.length)return 'nodrop'; var os=[].slice.call(dds[dds.length-1].querySelectorAll('.ant-select-item-option')); if(!os.length)return 'noopt';
var o=txt? os.filter(function(x){return (x.innerText||'').trim()===txt;})[0] : os[0]; if(!o)o=os[0];
['mouseenter','mouseover','mousedown','mouseup','click'].forEach(function(ev){o.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'picked:'+(o.innerText||'').trim().slice(0,26); };
return 'ready';})()`);
// ① 店铺 + 模板
await c.evalJS(`(async function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
var b=p._s.get('smtAddBasicStore');
try{await b.updateShop(${SHOP});}catch(e){} await new Promise(z=>setTimeout(z,2500));
try{await b.updateShopIdFun(${SHOP});}catch(e){} await new Promise(z=>setTimeout(z,3000)); return 1;})()`, true);
const tplN = await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
return (p._s.get('smtAddBasicStore').$state.dataState.freightTemplateList||[]).length;})()`);
log(` ① 店铺 ok,运费模板 ${tplN} 个`);
// ② 分类(UI 弹窗,多关键词重试 + 校验关闭)
let catOk = false, catInfo = '';
if (process.env.SMT_KW) { kws = process.env.SMT_KW.split(',').filter(Boolean); log(' 关键词覆盖:', kws.join('/')); } else { kws = kws.concat(KW_FB[oid] || []); }
for (const kw of kws) {
await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;});
ms.forEach(function(m){var cb=m.querySelector('.ant-modal-close'); if(cb)cb.click();}); return 1;})()`);
await wait(1200);
await c.evalJS(`(function(){var bs=[].slice.call(document.querySelectorAll('button')).filter(function(e){return e.offsetParent!==null&&/选择分类/.test(e.innerText||'');});
if(bs.length){bs[0].scrollIntoView({block:'center'});['mousedown','mouseup','click'].forEach(function(ev){bs[0].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});} return 1;})()`);
await wait(4200);
const n0 = await c.evalJS(`(function(){return [].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}).length;})()`);
if (!n0) { catInfo = 'modal未开'; continue; }
await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}); if(!ms.length)return 0;
var m=ms[ms.length-1]; var el=m.querySelector('input[name="searchCategory"]'); if(!el)return 0;
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,${JSON.stringify(kw)});
['input','change'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});
var b=[].slice.call(m.querySelectorAll('button')).filter(function(x){return /搜索/.test(x.innerText||'');}); if(b.length)b[0].click(); return 1;})()`);
await wait(5500);
const picked = await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}); if(!ms.length)return 'nomodal';
var m=ms[ms.length-1]; var its=[].slice.call(m.querySelectorAll('.search-result-item'));
if(!its.length) return 'noresult';
var best=null,bs=-999;
var TITLE=${JSON.stringify((EN[oid]||{}).title||'')};
var BAD=/慈善|Благотвор|二手|Second-?hand|Used|Refurb|Sexual|情趣|Dewasa/i;
var KID=/Baby|Kids|Kanak|婴儿|儿童|Infant|Toddler/i;
var AUTO=/Automobile|Automotive|汽车|Motosikal/i;
var ISK=KID.test(TITLE), ISA=AUTO.test(TITLE);
var KW=${JSON.stringify(kw)}.toLowerCase();
its.forEach(function(it){ var tx=(it.innerText||'').replace(/\\s+/g,' ').trim(); var s=0;
if(BAD.test(tx)) { s-=200; }
if(!ISK && KID.test(tx)) s-=60;
if(!ISA && AUTO.test(tx)) s-=60;
var segs=tx.split('/'); var lastSeg=(segs[segs.length-1]||'').replace(/\(.*?\)/g,'').trim().toLowerCase();
if(KW && lastSeg.indexOf(KW)>=0) s+=40;
if(/小百货|配饰|饰品|发饰|家居|住宅|文具|玩具|运动|美容|健康/i.test(tx)) s+=12;
s -= Math.min(tx.length/80,5);
if(s>bs){bs=s;best=it;} });
if(!best) return 'noresult';
best.scrollIntoView({block:'center'});
['mousedown','mouseup','click'].forEach(function(ev){best.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'picked:'+(best.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60);})()`);
await wait(7000);
const left = await c.evalJS(`(function(){return [].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null && (e.innerText||'').indexOf('选择类目')>=0;}).length;})()`);
const cid = await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
return String(p._s.get('smtAddBasicStore').$state.formState.categoryId||'');})()`);
catInfo = kw + ' → ' + SAN(String(picked)).slice(0,60);
if (!left && cid) { catOk = true; break; }
catInfo += ' [modal未关/无cat]';
}
log(` ② 分类${catOk ? '✅' : '❌'}: ${catInfo.slice(0, 100)}`);
if (!catOk) { log(' 分类失败,跳过该商品'); DONE[oid] = { ok:false, msg:'分类失败:'+catInfo.slice(0,60) }; fs.writeFileSync(DONE_F, JSON.stringify(DONE,null,1)); c.close(); await wait(1000); continue; }
// ③ 文本 + 尺寸
await c.evalJS(`(function(){
window.__f('产品标题',${JSON.stringify(en.title)},'input:not([type=hidden])');
window.__f('零售价格','1.99'); window.__f('货值','0.50'); window.__f('库存数量','100');
window.__f('重量(kg)','0.02'); window.__f('包装后重量','0.02'); return 1;})()`);
await c.evalJS(`(function(){
var set=function(el,v){Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,v);
['input','change','blur'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});};
[['长','10'],['宽','5'],['高','3']].forEach(function(p){ var els=[].slice.call(document.querySelectorAll('input')).filter(function(e){return e.offsetParent!==null&&(e.placeholder||'')===p[0];});
if(els.length) set(els[els.length-1],p[1]); });
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var lb=its[i].querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf('包装后尺寸')<0) continue;
var ins=[].slice.call(its[i].querySelectorAll('input')); ['10','5','3'].forEach(function(v,k){ if(ins[k]) set(ins[k],v); }); }
return 1;})()`);
await wait(2000);
log(' ③ 文本+尺寸 ok');
// ④ 模板下拉
await c.evalJS(`window.__openSel('运费模板')`); await wait(3000);
let fr = await c.evalJS(`window.__pick(${JSON.stringify(FREIGHT_NAME)})`);
if (/noopt|nodrop/.test(String(fr))) fr = await c.evalJS(`window.__pick()`);
log(` ④ 运费模板: ${SAN(String(fr))}`);
await wait(2500);
await c.evalJS(`window.__openSel('服务模板')`); await wait(3000);
log(` 服务模板: ${SAN(String(await c.evalJS(`window.__pick()`)))}`);
await wait(2500);
await c.evalJS(`window.__openSel('是否原箱')`); await wait(2800);
log(` 是否原箱: ${SAN(String(await c.evalJS(`window.__pick('否')`)))}`);
await wait(2200);
// ⑤ 含税
await c.evalJS(`(function(){var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; if((it.innerText||'').indexOf('报价是否含关税')<0) continue;
var ws=[].slice.call(it.querySelectorAll('.ant-radio-wrapper'));
for(var j=0;j<ws.length;j++){ if((ws[j].innerText||'').indexOf('不含关税')>=0){ var inp=ws[j].querySelector('input'); if(inp&&!inp.checked)inp.click(); return 1; } } }
return 0;})()`);
await wait(1800);
// ⑥ 物流属性(直接赋值,不依赖 UI 点击)
await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
p._s.get('smtAddProductStore').$state.formState.specialProductTypeList=['1983471271:2025103']; return 1;})()`);
await wait(1500);
log(' ⑤ 含税+物流属性 ok');
// ⑦ 属性(含 check_box 归一化)
await c.evalJS(`(function(){
var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
var at=p._s.get('smtAddAttrStore'); var a=p._s.get('smtAddStore').$state.attrsInfo.attrsList||[];
var want={'品牌':'201512802','产地':'9441741844','化学品':'23399591357'};
var d=at.$state.dynamicFormData||{};
for(var i=0;i<a.length;i++){ var nm=a[i].nameZh||''; var key=String(a[i].arrtNameId);
for(var k in want){ if(nm.indexOf(k)>=0) d[key]='history-'+want[k]; } }
var ra=at.$state.renderAttrs||[];
for(var j=0;j<ra.length;j++){ var x=ra[j]; var kk=String(x.arrtNameId); var st=String(x.attributeShowTypeValue||'');
var needArr=(st==='check_box'||st==='multiSelect'||st==='selectPercent');
if(needArr && d[kk]!==undefined && !Array.isArray(d[kk])) d[kk]=[d[kk]];
if(!needArr && Array.isArray(d[kk])) d[kk]=d[kk][0]||''; }
at.$state.dynamicFormData=d; return 1;})()`);
await wait(1500);
// ⑧ 描述
await c.evalJS(`(function(){
try{ if(typeof CKEDITOR==='undefined'||!CKEDITOR.instances) return 0;
var n=Object.keys(CKEDITOR.instances)[0]; var ed=CKEDITOR.instances[n];
ed.setData(${JSON.stringify('<p>' + en.bullets.map(b => '&bull; ' + b).join('<br/>') + '</p><p><strong>Keywords:</strong> ' + en.keywords + '</p>')});
ed.fire('change'); ed.fire('blur'); ed.updateElement(); return 1; }catch(e){ return 0; }})()`);
await wait(2000);
// ⑨ 图片(直接赋值,外部 URL 可用)
const imgData = imgs.slice(0, 6).map((u, i) => ({ id: String(i + 1), url: u, name: 'img' + i + '.jpg', width: 800, height: 800, imgId: String(1000 + i), checked: false }));
await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
p._s.get('smtAddProductStore').$state.formState.imageList=${JSON.stringify(imgData)}; return 1;})()`);
await wait(3000);
log(` ⑥ 描述+图片(${imgData.length}张) ok`);
// ⑩ 保存
await c.evalJS(`(function(){var bs=[].slice.call(document.querySelectorAll('button')).filter(function(e){return e.offsetParent!==null&&/^\\s*保存\\s*$/.test((e.innerText||'').trim());});
if(bs.length) bs[0].click(); return 1;})()`);
await wait(17000);
const msg = String(await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-modal,.ant-message-notice')).filter(function(e){return e.offsetParent!==null;}).map(function(e){return (e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120);}))`));
const ok = /已保存到/.test(msg);
log(` ${ok ? '✅' : '❌'} 保存: ${SAN(msg).slice(0, 110)} | POST=${posted}`);
DONE[oid] = { ok, msg: SAN(msg).slice(0, 100), imgs: imgData.length, kws, time: new Date().toISOString() };
fs.writeFileSync(DONE_F, JSON.stringify(DONE, null, 1));
} catch (e) {
log(` ❌ 异常: ${String(e).slice(0, 90)}`);
DONE[oid] = { ok: false, err: String(e).slice(0, 90) };
fs.writeFileSync(DONE_F, JSON.stringify(DONE, null, 1));
}
try { c.close(); } catch (e) {}
await wait(1200);
}
log('');
log(`全部结束。成功 ${Object.values(DONE).filter(x => x.ok).length} / ${Object.keys(DONE).length}`);
+47
View File
@@ -0,0 +1,47 @@
# 店小秘编辑器「保存静默拦截」诊断法
## 症状
点「保存」→ **不发任何 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 省份)和类目相关的文件字段(速卖通说明书)都不在这个扫描范围内。
以后遇到「点了没反应」,第一步就该挂这个钩子。
+34
View File
@@ -0,0 +1,34 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/sheinProduct/add') || await attach('dianxiaomi.com');
console.log('url:', await c.evalJS('location.href'));
// 探测可用的消息库
const probe = await c.evalJS(`(()=>{const keys=['MYJ','Vue','antd','$message','ELEMENT'];
const found={};
keys.forEach(k=>{ try{ found[k]=typeof window[k]; }catch(e){ found[k]='err'; } });
return JSON.stringify(found);})()`);
console.log('全局对象:', String(probe));
// 埋钩子(尽量广)
const hook = await c.evalJS(`(()=>{window.__cap=[]; let n=0;
function wrap(obj,name,label){ try{ if(!obj||!obj[name])return; const o=obj[name];
obj[name]=function(){ try{ window.__cap.push(label+': '+Array.prototype.slice.call(arguments).map(function(a){return typeof a==='string'?a:JSON.stringify(a);}).join(' ').slice(0,200)); }catch(e){}
return o.apply(obj,arguments); }; n++; }catch(e){} }
try{ if(window.MYJ&&window.MYJ.message){ ['error','warning','info','success'].forEach(k=>wrap(window.MYJ.message,k,'MYJ.'+k)); } }catch(e){}
try{ if(window.antd&&window.antd.message){ ['error','warning','info','success'].forEach(k=>wrap(window.antd.message,k,'antd.'+k)); } }catch(e){}
try{ if(window.$message){ ['error','warning','info','success'].forEach(k=>wrap(window.$message,k,'$message.'+k)); } }catch(e){}
return 'wrapped '+n;})()`);
console.log('钩子:', String(hook));
// 点保存
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(9000);
const cap = await c.evalJS(`JSON.stringify(window.__cap||[])`);
console.log('★ 捕获:', String(cap).slice(0,600));
const errs = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-explain-error')).map(function(e){
var p=e.closest?e.closest('.ant-form-item'):null; var l=p?(p.querySelector('label')||{}).innerText||'':'';
return l.replace(/\\s+/g,' ').trim().slice(0,30)+' → '+(e.innerText||'').trim();}).filter(Boolean))`);
console.log('行内错误:', String(errs).slice(0,400));
const errItems = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-has-error')).map(function(x){var l=x.querySelector('label');return l?(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,36):'?';}))`);
console.log('出错字段:', String(errItems).slice(0,300));
process.exit(0);
+283
View File
@@ -0,0 +1,283 @@
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* 速卖通批量建品(全自动):分类走 UI 弹窗搜索,其余按已验证配方 */
const SHOP = 4024039;
const FREIGHT_NAME = process.argv[2] || 'MY beauty 30g+1美金';
const ONLY = process.argv[3];
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const EN = JSON.parse(fs.readFileSync('/tmp/en_all.json', 'utf8'));
const CLEAN = JSON.parse(fs.readFileSync('/tmp/wm_clean.json', 'utf8'));
const ALL = JSON.parse(fs.readFileSync('/tmp/all48.json', 'utf8'));
const KW_FB = {};
const DONE_F = '/tmp/smt_batch_done.json';
const DONE = fs.existsSync(DONE_F) ? JSON.parse(fs.readFileSync(DONE_F, 'utf8')) : {};
// 分类关键词:从英文标题里取最能定位品类的词
function catKeywords(oid) {
const t = (EN[oid] || {}).title || '';
const CAND = [
[/whistle/i, ['whistle']],
[/hair tie|scrunchie|hair ring|ponytail|elastic/i, ['hair', 'hairband']],
[/hair clip|barrette|hairpin|clip/i, ['clip', 'hair']],
[/headband|hair band/i, ['headband', 'hair']],
[/keychain|key ring|key holder/i, ['keychain']],
[/sticker|decal/i, ['sticker']],
[/thermometer|hygrometer/i, ['thermometer']],
[/button/i, ['button']],
[/yarn|knitting/i, ['yarn']],
[/sock/i, ['socks']],
[/necklace|pendant/i, ['necklace']],
[/bracelet/i, ['bracelet']],
[/earring/i, ['earring']],
[/card holder|card case|badge/i, ['card']],
[/organizer|storage/i, ['organizer']],
[/box|case|holder|container/i, ['box']],
[/bag|pouch/i, ['bag']],
[/comb|brush/i, ['comb']],
[/mirror/i, ['mirror']],
[/tape dispenser/i, ['tape']],
[/hair dryer/i, ['hair dryer']],
];
for (const [rx, ks] of CAND) if (rx.test(t)) return ks;
return [(t.split(/\s+/)[0] || 'gift')];
}
const oids = ONLY ? [ONLY] : Object.keys(ALL).filter(o => !DONE[o] && (CLEAN[o] || []).length >= 1);
log(`待建商品: ${oids.length} | 已有记录: ${Object.keys(DONE).length}`);
for (const oid of oids) {
const en = EN[oid], imgs = CLEAN[oid] || [];
let kws = catKeywords(oid);
if (!en || !imgs.length) { log(`⚠ ${oid} 缺文案或图片,跳过`); continue; }
log('');
log(`═══ ${oid} | 图${imgs.length} 张 | 关键词候选 ${JSON.stringify(kws)} ═══`);
// 清掉旧标签
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
for (const t of tl.filter(x => x.type === 'page' && /\/web\/smt\/add/.test(x.url))) await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {});
await wait(900);
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await c.send('Network.enable');
let posted = false;
c.on(m => { if (m.method === 'Network.requestWillBeSent') { const r = m.params.request;
if (r.method === 'POST' && /\/api\/smtProduct\/add\.json/.test(r.url)) posted = true; } });
try {
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/smt/add' });
await wait(32000);
await c.evalJS(`(function(){
window.__f=function(rx,val,sel){ sel=sel||'input';
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; var lb=it.querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf(rx)<0) continue;
var el=it.querySelector(sel); if(!el) return 'nf';
var proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el,String(val));
['input','change','blur'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});
return 'ok'; }
return 'noitem'; };
window.__openSel=function(rx){
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; var lb=it.querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf(rx)<0) continue;
var sel=it.querySelector('.ant-select'); if(!sel) return 'nosel';
var tg=sel.querySelector('.ant-select-selector')||sel; tg.scrollIntoView({block:'center'});
['mousedown','mouseup','click'].forEach(function(ev){tg.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'opened'; }
return 'noitem'; };
window.__pick=function(txt){ var dds=[].slice.call(document.querySelectorAll('.ant-select-dropdown')).filter(function(e){return e.offsetParent!==null;});
if(!dds.length)return 'nodrop'; var os=[].slice.call(dds[dds.length-1].querySelectorAll('.ant-select-item-option')); if(!os.length)return 'noopt';
var o=txt? os.filter(function(x){return (x.innerText||'').trim()===txt;})[0] : os[0]; if(!o)o=os[0];
['mouseenter','mouseover','mousedown','mouseup','click'].forEach(function(ev){o.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'picked:'+(o.innerText||'').trim().slice(0,26); };
return 'ready';})()`);
// ① 店铺 + 模板
await c.evalJS(`(async function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
var b=p._s.get('smtAddBasicStore');
try{await b.updateShop(${SHOP});}catch(e){} await new Promise(z=>setTimeout(z,2500));
try{await b.updateShopIdFun(${SHOP});}catch(e){} await new Promise(z=>setTimeout(z,3000)); return 1;})()`, true);
const tplN = await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
return (p._s.get('smtAddBasicStore').$state.dataState.freightTemplateList||[]).length;})()`);
log(` ① 店铺 ok,运费模板 ${tplN} 个`);
// ② 分类(UI 弹窗,多关键词重试 + 校验关闭)
let catOk = false, catInfo = '';
if (process.env.SMT_KW) { kws = process.env.SMT_KW.split(',').filter(Boolean); log(' 关键词覆盖:', kws.join('/')); } else { kws = kws.concat(KW_FB[oid] || []); }
for (const kw of kws) {
await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;});
ms.forEach(function(m){var cb=m.querySelector('.ant-modal-close'); if(cb)cb.click();}); return 1;})()`);
await wait(1200);
await c.evalJS(`(function(){var bs=[].slice.call(document.querySelectorAll('button')).filter(function(e){return e.offsetParent!==null&&/选择分类/.test(e.innerText||'');});
if(bs.length){bs[0].scrollIntoView({block:'center'});['mousedown','mouseup','click'].forEach(function(ev){bs[0].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});} return 1;})()`);
await wait(4200);
const n0 = await c.evalJS(`(function(){return [].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}).length;})()`);
if (!n0) { catInfo = 'modal未开'; continue; }
await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}); if(!ms.length)return 0;
var m=ms[ms.length-1]; var el=m.querySelector('input[name="searchCategory"]'); if(!el)return 0;
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,${JSON.stringify(kw)});
['input','change'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});
var b=[].slice.call(m.querySelectorAll('button')).filter(function(x){return /搜索/.test(x.innerText||'');}); if(b.length)b[0].click(); return 1;})()`);
await wait(5500);
const picked = await c.evalJS(`(function(){var ms=[].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null;}); if(!ms.length)return 'nomodal';
var m=ms[ms.length-1]; var its=[].slice.call(m.querySelectorAll('.search-result-item'));
if(!its.length) return 'noresult';
var best=null,bs=-999;
var TITLE=${JSON.stringify((EN[oid]||{}).title||'')};
var BAD=/慈善|Благотвор|二手|Second-?hand|Used|Refurb|Sexual|情趣|Dewasa/i;
var KID=/Baby|Kids|Kanak|婴儿|儿童|Infant|Toddler/i;
var AUTO=/Automobile|Automotive|汽车|Motosikal/i;
var ISK=KID.test(TITLE), ISA=AUTO.test(TITLE);
var KW=${JSON.stringify(kw)}.toLowerCase();
its.forEach(function(it){ var tx=(it.innerText||'').replace(/\\s+/g,' ').trim(); var s=0;
if(BAD.test(tx)) { s-=200; }
if(!ISK && KID.test(tx)) s-=60;
if(!ISA && AUTO.test(tx)) s-=60;
var segs=tx.split('/'); var lastSeg=(segs[segs.length-1]||'').replace(/\(.*?\)/g,'').trim().toLowerCase();
if(KW && lastSeg.indexOf(KW)>=0) s+=40;
if(/小百货|配饰|饰品|发饰|家居|住宅|文具|玩具|运动|美容|健康/i.test(tx)) s+=12;
s -= Math.min(tx.length/80,5);
if(s>bs){bs=s;best=it;} });
if(!best) return 'noresult';
best.scrollIntoView({block:'center'});
['mousedown','mouseup','click'].forEach(function(ev){best.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));});
return 'picked:'+(best.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60);})()`);
await wait(7000);
const left = await c.evalJS(`(function(){return [].slice.call(document.querySelectorAll('.ant-modal')).filter(function(e){return e.offsetParent!==null && (e.innerText||'').indexOf('选择类目')>=0;}).length;})()`);
const cid = await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
return String(p._s.get('smtAddBasicStore').$state.formState.categoryId||'');})()`);
catInfo = kw + ' → ' + SAN(String(picked)).slice(0,60);
if (!left && cid) { catOk = true; break; }
catInfo += ' [modal未关/无cat]';
}
log(` ② 分类${catOk ? '✅' : '❌'}: ${catInfo.slice(0, 100)}`);
if (!catOk) { log(' 分类失败,跳过该商品'); DONE[oid] = { ok:false, msg:'分类失败:'+catInfo.slice(0,60) }; fs.writeFileSync(DONE_F, JSON.stringify(DONE,null,1)); c.close(); await wait(1000); continue; }
// ③ 文本 + 尺寸
await c.evalJS(`(function(){
window.__f('产品标题',${JSON.stringify(en.title)},'input:not([type=hidden])');
window.__f('零售价格','1.99'); window.__f('货值','0.50'); window.__f('库存数量','100');
window.__f('重量(kg)','0.02'); window.__f('包装后重量','0.02'); return 1;})()`);
await c.evalJS(`(function(){
var set=function(el,v){Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,v);
['input','change','blur'].forEach(function(ev){el.dispatchEvent(new Event(ev,{bubbles:true}));});};
[['长','10'],['宽','5'],['高','3']].forEach(function(p){ var els=[].slice.call(document.querySelectorAll('input')).filter(function(e){return e.offsetParent!==null&&(e.placeholder||'')===p[0];});
if(els.length) set(els[els.length-1],p[1]); });
var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var lb=its[i].querySelector('label'); if(!lb) continue;
if((lb.innerText||'').indexOf('包装后尺寸')<0) continue;
var ins=[].slice.call(its[i].querySelectorAll('input')); ['10','5','3'].forEach(function(v,k){ if(ins[k]) set(ins[k],v); }); }
return 1;})()`);
await wait(2000);
log(' ③ 文本+尺寸 ok');
// ④ 模板下拉
await c.evalJS(`window.__openSel('运费模板')`); await wait(3000);
let fr = await c.evalJS(`window.__pick(${JSON.stringify(FREIGHT_NAME)})`);
if (/noopt|nodrop/.test(String(fr))) fr = await c.evalJS(`window.__pick()`);
log(` ④ 运费模板: ${SAN(String(fr))}`);
await wait(2500);
await c.evalJS(`window.__openSel('服务模板')`); await wait(3000);
log(` 服务模板: ${SAN(String(await c.evalJS(`window.__pick()`)))}`);
await wait(2500);
await c.evalJS(`window.__openSel('是否原箱')`); await wait(2800);
log(` 是否原箱: ${SAN(String(await c.evalJS(`window.__pick('否')`)))}`);
await wait(2200);
// ⑤ 含税
await c.evalJS(`(function(){var its=[].slice.call(document.querySelectorAll('.ant-form-item'));
for(var i=0;i<its.length;i++){ var it=its[i]; if((it.innerText||'').indexOf('报价是否含关税')<0) continue;
var ws=[].slice.call(it.querySelectorAll('.ant-radio-wrapper'));
for(var j=0;j<ws.length;j++){ if((ws[j].innerText||'').indexOf('不含关税')>=0){ var inp=ws[j].querySelector('input'); if(inp&&!inp.checked)inp.click(); return 1; } } }
return 0;})()`);
await wait(1800);
// ⑥ 物流属性(直接赋值,不依赖 UI 点击)
await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
p._s.get('smtAddProductStore').$state.formState.specialProductTypeList=['1983471271:2025103']; return 1;})()`);
await wait(1500);
log(' ⑤ 含税+物流属性 ok');
// ⑦ 属性(含 check_box 归一化)
await c.evalJS(`(function(){
var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
var at=p._s.get('smtAddAttrStore'); var a=p._s.get('smtAddStore').$state.attrsInfo.attrsList||[];
var want={'品牌':'201512802','产地':'9441741844','化学品':'23399591357'};
var d=at.$state.dynamicFormData||{};
for(var i=0;i<a.length;i++){ var nm=a[i].nameZh||''; var key=String(a[i].arrtNameId);
for(var k in want){ if(nm.indexOf(k)>=0) d[key]='history-'+want[k]; } }
var ra=at.$state.renderAttrs||[];
for(var j=0;j<ra.length;j++){ var x=ra[j]; var kk=String(x.arrtNameId); var st=String(x.attributeShowTypeValue||'');
var needArr=(st==='check_box'||st==='multiSelect'||st==='selectPercent');
if(needArr && d[kk]!==undefined && !Array.isArray(d[kk])) d[kk]=[d[kk]];
if(!needArr && Array.isArray(d[kk])) d[kk]=d[kk][0]||''; }
at.$state.dynamicFormData=d; return 1;})()`);
await wait(1500);
// ⑧ 描述
await c.evalJS(`(function(){
try{ if(typeof CKEDITOR==='undefined'||!CKEDITOR.instances) return 0;
var n=Object.keys(CKEDITOR.instances)[0]; var ed=CKEDITOR.instances[n];
ed.setData(${JSON.stringify('<p>' + en.bullets.map(b => '&bull; ' + b).join('<br/>') + '</p><p><strong>Keywords:</strong> ' + en.keywords + '</p>')});
ed.fire('change'); ed.fire('blur'); ed.updateElement(); return 1; }catch(e){ return 0; }})()`);
await wait(2000);
// ⑨ 图片(直接赋值,外部 URL 可用)
const imgData = imgs.slice(0, 6).map((u, i) => ({ id: String(i + 1), url: u, name: 'img' + i + '.jpg', width: 800, height: 800, imgId: String(1000 + i), checked: false }));
await c.evalJS(`(function(){var el=document.querySelector('#app');var p=el.__vue_app__.config.globalProperties.$pinia;
p._s.get('smtAddProductStore').$state.formState.imageList=${JSON.stringify(imgData)}; return 1;})()`);
await wait(3000);
log(` ⑥ 描述+图片(${imgData.length}张) ok`);
// ⑩ 保存(调试版:埋钩子)
const hook = await c.evalJS(`(function(){
window.__cap=[];
try{
var M = window.MYJ && window.MYJ.message;
if(M){ ['error','warning','info','success'].forEach(function(k){
var orig=M[k];
M[k]=function(){ try{ window.__cap.push(k+': '+Array.prototype.slice.call(arguments).map(function(a){return typeof a==='string'?a:JSON.stringify(a);}).join(' ').slice(0,200)); }catch(e){}
return orig.apply(M, arguments); };
}); return 'hooked MYJ.message'; }
return 'no MYJ';
}catch(e){ return 'ERR '+e.message; }})()`);
log(' 钩子:', hook);
const btns = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('button')).filter(function(e){return e.offsetParent!==null;}).map(function(b){
var r=b.getBoundingClientRect(); return {t:(b.innerText||'').replace(/\\s+/g,' ').trim().slice(0,14), y:Math.round(r.y), dis:b.disabled, cls:String(b.className||'').slice(0,34)};}).filter(function(x){return x.t;}))`);
log(' 按钮清单:', SAN(String(btns)).slice(0,420));
await c.evalJS(`(function(){var bs=[].slice.call(document.querySelectorAll('button')).filter(function(e){return e.offsetParent!==null&&/^\\s*保存\\s*$/.test((e.innerText||'').trim());});
if(bs.length) bs[0].click(); return 1;})()`);
await wait(9000);
const cap = await c.evalJS(`JSON.stringify(window.__cap||[])`);
log(' ★ 被捕获的错误消息:', SAN(String(cap)).slice(0,500));
const errs = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-explain-error')).map(function(e){
var t=(e.innerText||'').trim(); if(!t) return null;
var p=e.closest? e.closest('.ant-form-item') : null;
var lab=p? (p.querySelector('label')||{}).innerText||'' : '';
return lab.replace(/\\s+/g,' ').trim().slice(0,40)+' → '+t;}).filter(Boolean))`);
log(' 行内校验错误(带字段):', SAN(String(errs)).slice(0,400));
const hasErrItems = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-has-error')).map(function(x){
var l=x.querySelector('label'); return l?(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,44):'?';}))`);
log(' 出错字段列表:', SAN(String(hasErrItems)).slice(0,400));
await wait(8000);
const msg = String(await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-modal,.ant-message-notice')).filter(function(e){return e.offsetParent!==null;}).map(function(e){return (e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120);}))`));
const ok = /已保存到/.test(msg);
log(` ${ok ? '✅' : '❌'} 保存: ${SAN(msg).slice(0, 110)} | POST=${posted}`);
DONE[oid] = { ok, msg: SAN(msg).slice(0, 100), imgs: imgData.length, kws, time: new Date().toISOString() };
fs.writeFileSync(DONE_F, JSON.stringify(DONE, null, 1));
} catch (e) {
log(` ❌ 异常: ${String(e).slice(0, 90)}`);
DONE[oid] = { ok: false, err: String(e).slice(0, 90) };
fs.writeFileSync(DONE_F, JSON.stringify(DONE, null, 1));
}
try { c.close(); } catch (e) {}
await wait(1200);
}
log('');
log(`全部结束。成功 ${Object.values(DONE).filter(x => x.ok).length} / ${Object.keys(DONE).length}`);
+23
View File
@@ -0,0 +1,23 @@
import { attach } from '/tmp/cdp3.mjs';
const wait = ms => new Promise(z => setTimeout(z, ms));
const c = await attach('/web/temu/add') || await attach('dianxiaomi.com');
console.log('url:', await c.evalJS('location.href'));
const hook = await c.evalJS(`(()=>{window.__cap=[]; let n=0;
function wrap(obj,name,label){ try{ if(!obj||!obj[name])return; const o=obj[name];
obj[name]=function(){ try{ window.__cap.push(label+': '+Array.prototype.slice.call(arguments).map(function(a){return typeof a==='string'?a:JSON.stringify(a);}).join(' ').slice(0,200)); }catch(e){}
return o.apply(obj,arguments); }; n++; }catch(e){} }
try{ if(window.MYJ&&window.MYJ.message){ ['error','warning','info'].forEach(k=>wrap(window.MYJ.message,k,'MYJ.'+k)); } }catch(e){}
try{ if(window.antd&&window.antd.message){ ['error','warning','info'].forEach(k=>wrap(window.antd.message,k,'antd.'+k)); } }catch(e){}
return 'wrapped '+n;})()`);
console.log('钩子:', String(hook));
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
await c.evalJS(`(()=>{const x=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()));
if(x) for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
await wait(9000);
const cap = await c.evalJS(`JSON.stringify(window.__cap||[])`);
console.log('★ 捕获:', String(cap).slice(0,600));
const errItems = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-has-error')).map(function(x){var l=x.querySelector('label');return l?(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,36):'?';}))`);
console.log('出错字段:', String(errItems).slice(0,300));
const ex = await c.evalJS(`JSON.stringify([].slice.call(document.querySelectorAll('.ant-form-item-explain-error')).map(e=>{var p=e.closest?e.closest('.ant-form-item'):null;var l=p?(p.querySelector('label')||{}).innerText||'':'';return l.replace(/\\s+/g,' ').trim().slice(0,30)+' → '+(e.innerText||'').trim();}).filter(Boolean))`);
console.log('行内错误:', String(ex).slice(0,400));
process.exit(0);