272 lines
6.4 KiB
TypeScript
272 lines
6.4 KiB
TypeScript
/**
|
|
* Ozon商品页采集配置
|
|
*
|
|
* 设计原则:
|
|
* 1. 人工控制触发,不做复杂等待
|
|
* 2. 多套选择器并存,应对Ozon的A/B测试
|
|
* 3. 优先采集跟卖必需的字段
|
|
*/
|
|
|
|
import type { SiteProfile } from './types';
|
|
|
|
export const profileOzon: SiteProfile = {
|
|
id: 'ozon',
|
|
name: 'Ozon',
|
|
|
|
// URL匹配
|
|
urlPatterns: [
|
|
/^https:\/\/www\.ozon\.ru\/product\//,
|
|
/^https:\/\/www\.ozon\.ru\/context\/detail\/id\//
|
|
],
|
|
|
|
// 提取商品ID
|
|
extractItemId: (url) => {
|
|
// Ozon URL格式: https://www.ozon.ru/product/name-123456789/
|
|
const match = url.match(/\/product\/[^\/]+-(\d+)/);
|
|
return match?.[1] ?? null;
|
|
},
|
|
|
|
// 简单的就绪检测 - 只要关键元素存在即可
|
|
readySelectors: [
|
|
'[data-widget="webProductHeading"]', // 标题区
|
|
'[data-widget="webGallery"]' // 图片画廊
|
|
],
|
|
readyTimeoutMs: 5_000, // 快速失败,不等太久
|
|
|
|
// 图片来源属性优先级
|
|
defaultSrcProps: ['data-src', 'currentSrc', 'src'],
|
|
|
|
refererOrigin: 'https://www.ozon.ru',
|
|
|
|
// ==========================================
|
|
// 文本素材规则
|
|
// ==========================================
|
|
textRules: [
|
|
// 1. 标题 (必需)
|
|
{
|
|
kind: 'title',
|
|
selectors: [
|
|
'[data-widget="webProductHeading"] h1',
|
|
'.tsHeadline500Medium',
|
|
'h1[itemprop="name"]'
|
|
],
|
|
extract: 'first',
|
|
required: true
|
|
},
|
|
|
|
// 2. 价格
|
|
{
|
|
kind: 'price',
|
|
selectors: [
|
|
'[data-widget="webPrice"] span[class*="tsBodyControl500"]',
|
|
'[data-widget="webPrice"] span',
|
|
'.c2h9_27 span', // 可能的备用类名
|
|
'span[itemprop="price"]'
|
|
],
|
|
extract: 'first'
|
|
},
|
|
|
|
// 3. 参数表 (特性)
|
|
{
|
|
kind: 'params',
|
|
selectors: [
|
|
'[data-widget="webCharacteristics"] dl',
|
|
'[data-widget="webDetailedCharacteristics"] dl',
|
|
'.k1p_27 dl'
|
|
],
|
|
extract: 'table',
|
|
tableKeySelector: 'dt',
|
|
tableValueSelector: 'dd'
|
|
},
|
|
|
|
// 4. 简介/卖点
|
|
{
|
|
kind: 'selling_point',
|
|
selectors: [
|
|
'[data-widget="webFeatures"]',
|
|
'[data-widget="webAO"]',
|
|
'.h9o_27' // About this item
|
|
],
|
|
extract: 'join'
|
|
},
|
|
|
|
// 5. 详细描述
|
|
{
|
|
kind: 'desc',
|
|
selectors: [
|
|
'[data-widget="webDescription"]',
|
|
'[data-widget="webRichContent"]',
|
|
'.RA-a1'
|
|
],
|
|
extract: 'join'
|
|
}
|
|
],
|
|
|
|
// ==========================================
|
|
// 图片素材规则
|
|
// ==========================================
|
|
imageGroups: [
|
|
// 主图画廊
|
|
{
|
|
key: 'main',
|
|
name: '主图',
|
|
type: 'img',
|
|
selectors: [
|
|
'[data-widget="webGallery"] img[class*="Image"]',
|
|
'[data-widget="webGallery"] source', // picture元素
|
|
'[data-widget="webPhotoGallery"] img',
|
|
'.b013-a img' // 旧版选择器
|
|
],
|
|
minWidth: 200,
|
|
minHeight: 200
|
|
},
|
|
|
|
// SKU变体图 (颜色/尺寸)
|
|
{
|
|
key: 'sku',
|
|
name: 'SKU图片',
|
|
type: 'img',
|
|
selectors: [
|
|
'[data-widget="webDetailSKU"] button img',
|
|
'[data-widget="webVariants"] img',
|
|
'[data-widget="webSku"] img',
|
|
'.k3r_27 img' // SKU容器
|
|
],
|
|
// SKU规格名提取
|
|
nameSelectors: [
|
|
'span[class*="Value"]',
|
|
'span[class*="Text"]',
|
|
'.tsBodyControl400Small'
|
|
],
|
|
minWidth: 20,
|
|
minHeight: 20
|
|
},
|
|
|
|
// 详情图 (描述中的图片)
|
|
{
|
|
key: 'detail',
|
|
name: '详情图',
|
|
type: 'img',
|
|
selectors: [
|
|
'[data-widget="webDescription"] img',
|
|
'[data-widget="webRichContent"] img',
|
|
'[data-widget="webFeatures"] img',
|
|
'.RA-a1 img'
|
|
],
|
|
minWidth: 300,
|
|
minHeight: 100
|
|
},
|
|
|
|
// 视频 (如果有)
|
|
{
|
|
key: 'video',
|
|
name: '视频',
|
|
type: 'video',
|
|
selectors: [
|
|
'[data-widget="webGallery"] video',
|
|
'[data-widget="webVideo"] video',
|
|
'video[class*="Video"]'
|
|
]
|
|
}
|
|
],
|
|
|
|
// ==========================================
|
|
// 图片URL处理规则
|
|
// ==========================================
|
|
originalUrlRules: [
|
|
{
|
|
// Ozon CDN缩略图处理
|
|
// 例: /wc200/xxx.jpg → /wc1200/xxx.jpg (获取更高分辨率)
|
|
match: /\/wc\d+\//,
|
|
replace: '/wc1200/'
|
|
},
|
|
{
|
|
// 或者移除尺寸参数
|
|
// 例: image.jpg?width=200 → image.jpg
|
|
match: /\?(width|height|size|quality)=[^&]+&?/g,
|
|
replace: ''
|
|
}
|
|
]
|
|
};
|
|
|
|
// ==========================================
|
|
// Ozon特殊处理函数
|
|
// ==========================================
|
|
|
|
/**
|
|
* Ozon页面额外的数据提取
|
|
* (可选) 从页面的JSON-LD结构化数据中提取
|
|
*/
|
|
export function extractOzonStructuredData(): {
|
|
brand?: string;
|
|
sku?: string;
|
|
availability?: string;
|
|
} | null {
|
|
try {
|
|
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
for (const script of scripts) {
|
|
const data = JSON.parse(script.textContent || '{}');
|
|
if (data['@type'] === 'Product') {
|
|
return {
|
|
brand: data.brand?.name,
|
|
sku: data.sku,
|
|
availability: data.offers?.availability
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to extract structured data:', e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 检测Ozon页面是否已就绪
|
|
* (简化版 - 只检查关键元素存在)
|
|
*/
|
|
export function isOzonPageReady(): {
|
|
ready: boolean;
|
|
missing: string[];
|
|
} {
|
|
const requiredElements = [
|
|
{ selector: '[data-widget="webProductHeading"]', name: '标题' },
|
|
{ selector: '[data-widget="webGallery"]', name: '图片画廊' }
|
|
];
|
|
|
|
const missing: string[] = [];
|
|
|
|
for (const elem of requiredElements) {
|
|
if (!document.querySelector(elem.selector)) {
|
|
missing.push(elem.name);
|
|
}
|
|
}
|
|
|
|
return {
|
|
ready: missing.length === 0,
|
|
missing
|
|
};
|
|
}
|
|
|
|
// ==========================================
|
|
// 使用示例 (在content script中)
|
|
// ==========================================
|
|
/*
|
|
import { profileOzon, isOzonPageReady } from './profiles/ozon';
|
|
|
|
// 用户点击"采集"按钮时
|
|
async function handleCollect() {
|
|
// 1. 快速检查
|
|
const { ready, missing } = isOzonPageReady();
|
|
if (!ready) {
|
|
alert(`页面未完全加载,缺少: ${missing.join(', ')}\n请稍候再试`);
|
|
return;
|
|
}
|
|
|
|
// 2. 执行采集
|
|
const result = await scanCurrentPage(); // 使用通用采集引擎
|
|
|
|
// 3. 显示结果
|
|
console.log('采集完成:', result);
|
|
}
|
|
*/
|