diff --git a/NEXT-ROUND-PROMPT.md b/NEXT-ROUND-PROMPT.md index 9e75327..cbd6126 100644 --- a/NEXT-ROUND-PROMPT.md +++ b/NEXT-ROUND-PROMPT.md @@ -75,7 +75,7 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a | 平台 | add 路由 | 店铺控件 | 类目控件 | store 家族 | |---|---|---|---|---| | Joom | `/web/joomProduct/add` | **复选框组** 85 个(全部+84 店) | 可搜索 `.ant-select` 平铺列表 | `smtChoice*` + `joom*`(已跑通) | - | aliChoice | `/web/smtChoice/add` | 下拉(实测 2 家) | 弹窗 | `smtChoice*`:标题 `.subject`、描述要写 **CKEditor setData**、保存要**两遍**(第二遍前补「物流属性=普货」)——**已跑通**,`alcbatch.mjs` 批量中 | + | aliChoice | `/web/smtChoice/add` | 下拉(实测 2 家) | 弹窗 | `smtChoice*`:标题 `.subject`、描述要写 **CKEditor setData**、保存要**两遍**(第二遍前补「物流属性=普货」)——**已跑通**;类目要先 `alcdry.mjs` dry 扫一遍(避开强制「商品说明书」的类目,结果写 `alc_route.json`),再 `alcbatch.mjs` 批量。剩余卡点:`请选择SKU分类`、类目属性 `材质/尺寸` 选了不生效 | | Lazada | `/web/lazada/add` | 下拉 | 弹窗 | 选完店铺才注册 `lazada*` store(懒加载) | | fruugo | `/web/fruugoProduct/add` | 下拉 | 弹窗 | `fruugoAdd*`(Common/Basic/Dxm/Product/Variation/Image/Describe/Form) | diff --git a/alichoice建品/RECIPE.md b/alichoice建品/RECIPE.md index 597a7dc..c2f9e23 100644 --- a/alichoice建品/RECIPE.md +++ b/alichoice建品/RECIPE.md @@ -79,3 +79,20 @@ pinia._s.get('smtChoiceAddStore').$state.describeInfoData.detail = html; 把落到健康/医疗/化妆品等强资质类目的商品挑出来,再决定是换词还是跳过。 失败记录现在会带 `errItems`(例如 `商品说明书:请上传文件!`),见 `alc_done.json`。 + +--- + +## 第二次批量(带 dry 扫描出来的类目表)进展与新的失败类 + +`alcdry.mjs` 先扫类目(不保存),把每个商品**第一个不强制「商品说明书」**的品类词写进 +`alc_route.json`(47/47 都有可用类目;例:温度计「温度计」强制说明书 → 回退到「家用电器」`说明书=否`)。 +`alcbatch.mjs` 之后只读这张表,不再猜类目。 + +**结果**:`商品说明书` 类失败归零;但冒出两类**新的必填**(都是类目动态属性/SKU 级): + +| 现象(`errItems`) | 猜测与下一步 | +|---|---| +| `(无label):请选择SKU分类` | SKU 行上有个 **SKU分类** 下拉要选(Temu 的 SKU 表也有这一列)。下一步:定位它的落点(可能 `skuDataList[].skuClassification`)或走 UI 选第一项 | +| `材质(Material):` / `尺寸(Size):` | 类目动态属性里**把下拉当文本**的项,`fillRequiredSelects` 选了但值没生效(红色提示为空)→ 下一步 dump 这两项的控件类型(可能是可搜索 select 或 input) | + +成材率从 2/6 提升到 5/8,其余靠这两类补齐。 diff --git a/alichoice建品/alc_route.json b/alichoice建品/alc_route.json new file mode 100644 index 0000000..a9f3207 --- /dev/null +++ b/alichoice建品/alc_route.json @@ -0,0 +1,237 @@ +{ + "548459586741": { + "cat": "口哨", + "path": "运动及娱乐(Sports & Entertainment)/团队运动(Team Sports)/啦啦队用品及纪念品(Ch", + "needsManual": false + }, + "661190598139": { + "cat": "发饰", + "path": "服饰配饰(男/女/儿童配件,婴儿配饰发到婴儿服装)(Apparel Accessories)/发饰(New Headwe", + "needsManual": false + }, + "764423265986": { + "cat": "家用电器", + "path": "家用电器(Home Appliances)/家电零部件(Home Appliance Parts)/3合一早餐机零部件(", + "needsManual": false + }, + "685456757807": { + "cat": "毛线", + "path": "家居用品(Home & Garden)/手工艺品&缝纫用品(半成品)(Arts,Crafts & Sewing)/毛线编", + "needsManual": false + }, + "735147938123": { + "cat": "发箍", + "path": "接发与发套(Hair Extensions & Wigs)/亚洲假发(Hair For Asian)/亚洲化纤发(Syn", + "needsManual": false + }, + "627879474686": { + "cat": "袜子", + "path": "新奇特及特殊用途服装(Novelty & Special Use)/舞台表演服和舞蹈服(Stage & Dance We", + "needsManual": false + }, + "610164857063": { + "cat": "发带", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "676236596821": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "708420302844": { + "cat": "钥匙扣", + "path": "玩具(Toys & Hobbies)/填充动物/毛绒玩具(Stuffed Animals & Plush)/毛绒玩具钥匙", + "needsManual": false + }, + "866537106657": { + "cat": "纽扣", + "path": "家居用品(Home & Garden)/手工艺品&缝纫用品(半成品)(Arts,Crafts & Sewing)/DIY", + "needsManual": false + }, + "547183997437": { + "cat": "项链", + "path": "珠宝饰品及配件(Jewelry & Accessories)/精品珠宝(Fine Jewelry)/项链(Necklac", + "needsManual": false + }, + "657617635303": { + "cat": "收纳盒", + "path": "家居用品(Home & Garden)/家用储存收藏用具(Home Storage & Organization)/衣物", + "needsManual": false + }, + "641673262387": { + "cat": "收纳袋", + "path": "家居用品(Home & Garden)/家用储存收藏用具(Home Storage & Organization)/收纳", + "needsManual": false + }, + "953869908708": { + "cat": "贴纸", + "path": "消费电子(Consumer Electronics)/游戏及配附件(Games & Accessories)/贴纸(St", + "needsManual": false + }, + "703822717997": { + "cat": "文具", + "path": "图书和文化衍生用品(Books & Cultural Merchandise)/文化衍生品(办公文具)(Cultural", + "needsManual": false + }, + "863524134410": { + "cat": "贴纸", + "path": "消费电子(Consumer Electronics)/游戏及配附件(Games & Accessories)/贴纸(St", + "needsManual": false + }, + "694637537727": { + "cat": "收纳袋", + "path": "家居用品(Home & Garden)/家用储存收藏用具(Home Storage & Organization)/收纳", + "needsManual": false + }, + "623011262210": { + "cat": "贴纸", + "path": "消费电子(Consumer Electronics)/游戏及配附件(Games & Accessories)/贴纸(St", + "needsManual": false + }, + "836512561982": { + "cat": "化妆包", + "path": "箱包(Luggage & Bags)/特殊用途箱包(Special Purpose Bags)/化妆包(Cosmetic", + "needsManual": false + }, + "714492799386": { + "cat": "化妆包", + "path": "箱包(Luggage & Bags)/特殊用途箱包(Special Purpose Bags)/化妆包(Cosmetic", + "needsManual": false + }, + "591994902720": { + "cat": "笔袋", + "path": "办公、文化及教育用品(Office & School Supplies)/学生用品(School Supplies)/笔", + "needsManual": false + }, + "853414558425": { + "cat": "手机挂绳", + "path": "手机通讯配件(Phones & Telecommunications Accessories)/手机饰品(Mobile ", + "needsManual": false + }, + "997450831819": { + "cat": "贴纸", + "path": "消费电子(Consumer Electronics)/游戏及配附件(Games & Accessories)/贴纸(St", + "needsManual": false + }, + "639409307567": { + "cat": "墙贴", + "path": "家居用品(Home & Garden)/家庭装饰品(Home Decor)/家居装饰贴纸(Home Decoration", + "needsManual": false + }, + "902440558099": { + "cat": "发饰", + "path": "服饰配饰(男/女/儿童配件,婴儿配饰发到婴儿服装)(Apparel Accessories)/发饰(New Headwe", + "needsManual": false + }, + "823838532886": { + "cat": "卡套", + "path": "办公、文化及教育用品(Office & School Supplies)/笔记本、拍纸本等书写用品(Notebooks ", + "needsManual": false + }, + "698782391248": { + "cat": "贴纸", + "path": "消费电子(Consumer Electronics)/游戏及配附件(Games & Accessories)/贴纸(St", + "needsManual": false + }, + "707826905566": { + "cat": "卡包", + "path": "箱包(Luggage & Bags)/钱包&卡包(Wallets & Holders)/卡包(Card & ID Hol", + "needsManual": false + }, + "1027332215847": { + "cat": "收纳盒", + "path": "家居用品(Home & Garden)/家用储存收藏用具(Home Storage & Organization)/衣物", + "needsManual": false + }, + "682195499823": { + "cat": "笔袋", + "path": "办公、文化及教育用品(Office & School Supplies)/学生用品(School Supplies)/笔", + "needsManual": false + }, + "823560564174": { + "cat": "发饰", + "path": "服饰配饰(男/女/儿童配件,婴儿配饰发到婴儿服装)(Apparel Accessories)/发饰(New Headwe", + "needsManual": false + }, + "739147684759": { + "cat": "纽扣", + "path": "家居用品(Home & Garden)/手工艺品&缝纫用品(半成品)(Arts,Crafts & Sewing)/DIY", + "needsManual": false + }, + "621300323670": { + "cat": "胶带座", + "path": "办公、文化及教育用品(Office & School Supplies)/胶带、胶水、包装带等(Tapes, Adhes", + "needsManual": false + }, + "896469995553": { + "cat": "钥匙扣", + "path": "玩具(Toys & Hobbies)/填充动物/毛绒玩具(Stuffed Animals & Plush)/毛绒玩具钥匙", + "needsManual": false + }, + "944470567338": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "787261097583": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "833753630925": { + "cat": "发箍", + "path": "接发与发套(Hair Extensions & Wigs)/亚洲假发(Hair For Asian)/亚洲化纤发(Syn", + "needsManual": false + }, + "719391062058": { + "cat": "收纳盒", + "path": "家居用品(Home & Garden)/家用储存收藏用具(Home Storage & Organization)/衣物", + "needsManual": false + }, + "857781294719": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "863384503032": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "774494945453": { + "cat": "钥匙扣", + "path": "玩具(Toys & Hobbies)/填充动物/毛绒玩具(Stuffed Animals & Plush)/毛绒玩具钥匙", + "needsManual": false + }, + "750271307455": { + "cat": "钥匙扣", + "path": "玩具(Toys & Hobbies)/填充动物/毛绒玩具(Stuffed Animals & Plush)/毛绒玩具钥匙", + "needsManual": false + }, + "726442972676": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "775057912118": { + "cat": "钥匙扣", + "path": "玩具(Toys & Hobbies)/填充动物/毛绒玩具(Stuffed Animals & Plush)/毛绒玩具钥匙", + "needsManual": false + }, + "893592264148": { + "cat": "发夹", + "path": "接发与发套(Hair Extensions & Wigs)/美发沙龙工具及配件(Hair Tools & Accesso", + "needsManual": false + }, + "607330049419": { + "cat": "发饰", + "path": "服饰配饰(男/女/儿童配件,婴儿配饰发到婴儿服装)(Apparel Accessories)/发饰(New Headwe", + "needsManual": false + }, + "854003999648": { + "cat": "发饰", + "path": "服饰配饰(男/女/儿童配件,婴儿配饰发到婴儿服装)(Apparel Accessories)/发饰(New Headwe", + "needsManual": false + } +} \ No newline at end of file diff --git a/alichoice建品/alcbatch.mjs b/alichoice建品/alcbatch.mjs index 957915d..8a35271 100644 --- a/alichoice建品/alcbatch.mjs +++ b/alichoice建品/alcbatch.mjs @@ -24,6 +24,7 @@ const only = argv.filter(a => !a.startsWith('--')); let tasks = Object.keys(INPUT).filter(pid => (INPUT[pid].imgs || []).length > 0).map(pid => ({ pid })); if (only.length) tasks = tasks.filter(t => only.includes(t.pid)); else tasks = tasks.filter(t => !SKIP.has(t.pid)); let done = {}; try { done = JSON.parse(readFileSync(DONE_F, 'utf8')); } catch {} +let ROUTE = {}; try { ROUTE = JSON.parse(readFileSync(`${DIR}/alc_route.json`, 'utf8')); } catch {} const log = m => { const l = `[${new Date().toISOString().slice(11, 19)}] ${m}`; console.log(l); appendFileSync(LOG_F, l + '\n'); }; /** PC端描述:真实控件是 CKEditor(隐藏 textarea + 所见即所得),给编辑器实例 setData 才有效 */ @@ -109,15 +110,17 @@ for (const t of tasks) { await wait(6000); log(` 备货类型: ${await pickFirstOption(c, '备货类型')}`); await wait(3000); - // 类目:cat_kw 搜不到就依次试 cat_fb(tk_input 里给的后备品类词) - let cat = await selectCategory(c, it.cat_kw || '发夹', it.cat_kw); - if (!String(cat).startsWith('path:')) { - for (const fb of (it.cat_fb || []).slice(0, 3)) { - cat = await selectCategory(c, fb, fb); - if (String(cat).startsWith('path:')) { log(` 类目回退到「${fb}」`); break; } - } + // 类目:用 alcdry.mjs 扫出来的词(已确认该词落到的类目不强制「商品说明书」) + const rc = ROUTE[t.pid]; + if (!rc || !rc.cat) { + log(` ⏭ 跳过:没有不要求资质文件的类目(dry 扫描结论)`); + done[t.pid] = { ok: false, skipped: true, msg: '无可用类目:全部候选都强制商品说明书' }; + await closePage(c); c = null; + if (!dry) writeFileSync(DONE_F, JSON.stringify(done, null, 1)); + continue; } - log(` 类目: ${String(cat).slice(0, 110)}`); + const cat = await selectCategory(c, rc.cat, rc.cat); + log(` 类目: 「${rc.cat}」 ${String(cat).slice(0, 90)}`); if (!String(cat).startsWith('path:')) throw new Error('选类目失败:' + cat); await wait(6000); const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify((it.imgs || []).slice(0, 6))}; @@ -156,7 +159,7 @@ for (const t of tasks) { const red=[...x.querySelectorAll('.text-red-500')].map(e=>(e.innerText||'').trim().slice(0,30)).filter(Boolean); return t+':'+red.join('/');}).slice(0,6))`)); const ok = /已保存|保存成功|待发布/.test(mods); - done[t.pid] = ok ? { ok: true } : { ok: false, errItems: errItems.slice(0, 260), mods: mods.slice(0, 120), hook: String(cap).slice(-200) }; + done[t.pid] = ok ? { ok: true, cat: rc.cat, path: rc.path } : { ok: false, cat: rc.cat, errItems: errItems.slice(0, 260), mods: mods.slice(0, 120), hook: String(cap).slice(-200) }; log(ok ? ' ✓ 已保存到待发布' : ` ✗ 失败 ${mods.slice(0, 110)} | ${String(cap).slice(-170)}`); } catch (e) { done[t.pid] = { ok: false, msg: String(e).slice(0, 240) }; diff --git a/alichoice建品/alcdry.mjs b/alichoice建品/alcdry.mjs new file mode 100644 index 0000000..2c941c9 --- /dev/null +++ b/alichoice建品/alcdry.mjs @@ -0,0 +1,84 @@ +/** + * aliChoice 类目 dry 扫描(不保存) + * 对每个商品:cat_kw 起,逐次回退 cat_fb —— 选中后检查类目是否**强制商品说明书**, + * 取第一个不强制说明书的词;全都强制就标 needsManual。 + * 结果写 alc_route.json,供 alcbatch.mjs 直接使用(避免重跑时再猜类目)。 + */ +import { readFileSync, writeFileSync, appendFileSync } from 'fs'; +import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { selectCategory } from '/home/admin9webs/shein建品/sh_build.mjs'; + +const DIR = '/home/admin9webs/alichoice建品'; +const INPUT = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json', 'utf8')); +const OUT = `${DIR}/alc_route.json`; +const LOG = `${DIR}/alc_dry.log`; +const SKIP = new Set(['601964534137']); +const log = m => { const l = `[${new Date().toISOString().slice(11, 19)}] ${m}`; console.log(l); appendFileSync(LOG, l + '\n'); }; + +let route = {}; try { route = JSON.parse(readFileSync(OUT, 'utf8')); } catch {} + +async function pickFirstOption(c, labelRe) { + const p = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null) + .find(e=>new RegExp(${JSON.stringify(labelRe)}).test((e.querySelector('label')||{}).innerText||'')); + const s=f?f.querySelector('.ant-select-selector'):null; 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 (!p.length) return 'no-sel'; + await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: p[0], y: p[1], buttons: 0 }); await wait(250); + for (const t of ['mousePressed', 'mouseReleased']) + await c.send('Input.dispatchMouseEvent', { type: t, x: p[0], y: p[1], button: 'left', clickCount: 1, buttons: 1 }); + await wait(1800); + return c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop(); + if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null)[0]; if(!op) return 'no-op'; + const r=op.getBoundingClientRect(); + for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); + return 'picked:'+(op.innerText||'').trim().slice(0,14);})()`); +} + +/** 类目选中后,看表单里有没有「商品说明书」这类强制文件项 */ +async function needsManual(c) { + return c.evalJS(`(()=>{ + for (const e of document.querySelectorAll('.ant-form-item')) { + if (e.offsetParent === null) continue; + const t = (e.innerText || ''); + if (!/商品说明书|说明书/.test(t)) continue; + const lab = e.querySelector('label'); + const req = (lab && /required/.test(String(lab.className))) || e.querySelector('.attr-label.required') || /请上传文件/.test(t); + if (req) return 1; + } + return 0;})()`); +} + +const pids = Object.keys(INPUT).filter(p => (INPUT[p].imgs || []).length > 0).filter(p => !SKIP.has(p)); +log(`aliChoice 类目 dry 扫描:${pids.length} 条`); +for (const pid of pids) { + if (route[pid] && route[pid].cat) { log(`跳过 ${pid}(已有 ${route[pid].cat})`); continue; } + const it = INPUT[pid]; + let c; + try { + c = await newPage('https://www.dianxiaomi.com/web/smtChoice/add'); + await wait(11000); + await pickFirstOption(c, '店铺名称'); await wait(5500); + await pickFirstOption(c, '备货类型'); await wait(2500); + const cands = [it.cat_kw, ...(it.cat_fb || [])].filter(Boolean).slice(0, 6); + let chosen = null; + for (const kw of cands) { + const r = String(await selectCategory(c, kw, kw)); + await wait(3500); + if (!r.startsWith('path:')) { log(` ${pid} 「${kw}」无结果`); continue; } + const nm = await needsManual(c); + log(` ${pid} 「${kw}」 → ${r.slice(5, 60)} 说明书=${nm ? '强制' : '否'}`); + if (!nm) { chosen = { cat: kw, path: r.slice(5, 90), needsManual: false }; break; } + } + route[pid] = chosen || { cat: null, needsManual: true, tried: cands }; + writeFileSync(OUT, JSON.stringify(route, null, 1)); + } catch (e) { + log(` ${pid} 异常: ${String(e).slice(0, 120)}`); + route[pid] = { cat: null, error: String(e).slice(0, 120) }; + writeFileSync(OUT, JSON.stringify(route, null, 1)); + } finally { + if (c) await closePage(c); + await wait(1500); + } +} +const okN = Object.values(route).filter(v => v.cat).length; +log(`dry 扫描结束:可用类目 ${okN} / ${pids.length},需人工 ${Object.values(route).filter(v => v.needsManual).length}`);