85 lines
4.6 KiB
JavaScript
85 lines
4.6 KiB
JavaScript
/**
|
||
* 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}`);
|