Files
dianxiaomi-crossborder-play…/速卖通建品/smtbatch.mjs
T
admin9webs f5720ad613 店小秘多平台铺货配方:7 平台建品脚本 + 静默拦截诊断法 + 项目状态
- TikTok/Shopee/速卖通/Walmart 各 46-47 条已在待发布
- Ozon 47 条已建、改价 40/47
- SHEIN 配方跑通第一条;Temu 差「预览图」一步
- 核心产出:MYJ.message.error 钩子诊断法(解开了三个平台的保存静默拦截)
- 含下一轮提示词 NEXT-ROUND-PROMPT.md
2026-09-20 17:29:38 +08:00

257 lines
16 KiB
JavaScript

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}`);