feat: 插件开发 ozon 端主体完成

This commit is contained in:
Joey
2026-08-16 17:32:43 +08:00
parent 1591d5e35a
commit b57933e983
32 changed files with 1960 additions and 512 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* 1688 提取器离线验证 —— 用 reference/1688.html 快照跑真实提取逻辑。
*
* 用法:node scripts/verify-1688.mjs [快照路径]
* 依赖 esbuild 打包 TS 提取器(node_modules 里有)。
*/
import { readFileSync, readdirSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const htmlPath = process.argv[2] ?? '/Users/joey/sites/seller-store/ozon-seller-kit/reference/1688.html';
// 1. 打包提取器(纯函数无 DOM 依赖);pnpm 布局下 esbuild bin 可能不在 .bin,动态查找
function findEsbuild() {
const candidates = [join(root, 'node_modules/.bin/esbuild')];
try {
const pnpmDir = join(root, 'node_modules/.pnpm');
for (const d of readdirSync(pnpmDir)) {
if (d.startsWith('esbuild@')) {
candidates.push(join(pnpmDir, d, 'node_modules/esbuild/bin/esbuild'));
}
}
} catch { /* ignore */ }
candidates.push('esbuild'); // 全局兜底
for (const c of candidates) {
try { execSync(`${c} --version`, { stdio: 'pipe' }); return c; } catch { /* try next */ }
}
throw new Error('找不到可用的 esbuild');
}
const outFile = '/tmp/1688-state.bundle.mjs';
execSync(`${JSON.stringify(findEsbuild())} src/collector/1688-state.ts --bundle --format=esm --outfile=${outFile}`, { cwd: root });
const { extract1688State } = await import(`file://${outFile}`);
// 2. 从快照提取 script#3 并在 window 垫片里求值(还原 window.context
const html = readFileSync(htmlPath, 'utf8');
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)].map(m => m[1]);
const ctxScript = scripts.find(s => s.includes('window.context')) ?? scripts[3];
const windowShim = {};
new Function('window', 'document', 'location', ctxScript)(
windowShim, { querySelector: () => null }, { hostname: 'detail.1688.com' },
);
const context = windowShim.context;
if (!context) {
console.error('✗ window.context 求值失败');
process.exit(1);
}
console.log(`✓ window.context 就绪(keys: ${Object.keys(context).join(', ')}`);
// 3. 跑提取器并断言
const st = extract1688State(context);
const assert = (cond, msg) => { if (!cond) { console.error(`${msg}`); process.exit(1); } console.log(`${msg}`); };
assert(st !== null, '提取器返回非空');
assert(!!st.title && st.title.length >= 5, `标题: ${st.title}`);
assert(st.galleryImages.length >= 5, `主图 ${st.galleryImages.length}`);
assert(st.videos.length >= 1 && /\.mp4/.test(st.videos[0].url), `视频: ${st.videos[0]?.url?.slice(0, 60) ?? '无'}`);
assert(st.skus.length >= 3, `SKU ${st.skus.length} 个(含规格名/图)`);
assert(st.skus.every(s => /:/.test(s.name)), `SKU : ${st.skus.slice(0, 3).map(s => s.name).join(' | ')}`);
assert(!!st.price && /\d/.test(st.price), `价格区间: ${st.price}`);
assert(!!st.sales && /\d/.test(st.sales), `销量: ${st.sales}`);
assert(!!st.shop && st.shop.length >= 2, `店铺: ${st.shop}`);
const dimPair = st.params.find(p => p.key === '产品尺寸');
assert(!!dimPair && /\d+×\d+×\d+/.test(dimPair.value), `产品尺寸: ${dimPair?.value}`);
assert(st.params.some(p => p.key === '重量'), `重量: ${st.params.find(p => p.key === '重量')?.value}`);
assert(!!st.detailUrl?.startsWith('https://'), `detailUrl: ${st.detailUrl?.slice(0, 70)}`);
const priced = st.skus.filter(s => s.price);
assert(priced.length >= 1, `SKU 价格明细 ${priced.length} 条(如 ${priced[0]?.name} ${priced[0]?.price}`);
console.log('\n全部断言通过 ✅');