Files
dianxiaomi-crossborder-play…/alichoice建品/alcdry.mjs
T

85 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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}`);