138 lines
4.4 KiB
TypeScript
138 lines
4.4 KiB
TypeScript
/**
|
||
* 淘宝 / 天猫采集配置
|
||
*
|
||
* 选择器全部来自真实页面实测(2026-08-11,两个商品页各跑一轮反向探测):
|
||
* 天猫 detail.tmall.com/item.htm?id=960057430812
|
||
* 淘宝 item.taobao.com/item.htm?id=1060253247160
|
||
* 两站 DOM 完全一致(同一套前端),一份 profile 覆盖。
|
||
*
|
||
* 类名是 CSS Modules 的 `语义前缀--哈希` 形式,哈希每次构建都变,
|
||
* 所以一律用 `[class*="前缀--"]` 前缀匹配。
|
||
*
|
||
* 结尾那个 `--` 不能省——它把父容器和子元素区分开:
|
||
* `generalParamsInfoItem--` 不会误命中 `generalParamsInfoItemTitle--`。
|
||
*
|
||
* 实测证据见 docs/extension/selectors-taobao.md
|
||
*/
|
||
import type { SiteProfile } from './types';
|
||
|
||
export const profileTaobao: SiteProfile = {
|
||
id: 'taobao',
|
||
name: '淘宝/天猫',
|
||
|
||
urlPatterns: [
|
||
/^https:\/\/item\.taobao\.com\/item\.htm/,
|
||
/^https:\/\/detail\.tmall\.com\/item\.htm/,
|
||
],
|
||
|
||
extractItemId: (url) => url.match(/[?&]id=(\d+)/)?.[1] ?? null,
|
||
|
||
// 页面上没有 <h1>,别再拿它探活
|
||
readySelectors: [
|
||
'[class*="mainTitle--"]',
|
||
'[class*="picGallery--"]',
|
||
'#picGalleryEle',
|
||
],
|
||
readyTimeoutMs: 10_000,
|
||
|
||
// 阿里系 CDN 规则与 1688 相同
|
||
defaultSrcProps: ['data-lazyload-src', 'data-src', 'currentSrc', 'src'],
|
||
|
||
refererOrigin: 'https://www.taobao.com',
|
||
|
||
textRules: [
|
||
{
|
||
kind: 'title',
|
||
// mainTitle-- 是纯文本节点(探测里 imgs=0),最干净
|
||
// ItemTitle-- / MainTitle-- 是外层容器,带图标,作兜底
|
||
// 注意:属性选择器区分大小写,三个都得写
|
||
selectors: [
|
||
'[class*="mainTitle--"]',
|
||
'[class*="MainTitle--"]',
|
||
'[class*="ItemTitle--"]',
|
||
],
|
||
extract: 'first',
|
||
required: true,
|
||
},
|
||
{
|
||
kind: 'price',
|
||
// highlightPrice-- 是当前实际售价,两站一致
|
||
// priceWrap-- 是外层,会把"优惠前¥36.8"一起带进来,只作兜底
|
||
selectors: [
|
||
'[class*="highlightPrice--"]',
|
||
'[class*="priceWrap--"]',
|
||
],
|
||
extract: 'first',
|
||
},
|
||
{
|
||
kind: 'params',
|
||
// generalParamsInfoItem-- 每项含 Title(键) + SubTitle(值)
|
||
selectors: ['[class*="generalParamsInfoItem--"]'],
|
||
extract: 'table',
|
||
tableKeySelector: '[class*="ParamsInfoItemTitle--"]',
|
||
tableValueSelector: '[class*="ParamsInfoItemSubTitle--"]',
|
||
},
|
||
// desc 故意不采:详情容器 detailInfo-- 里混着用户评价、参数、图文详情,
|
||
// join 出来是一坨无法使用的字符串。1688/淘宝的中文文案对 Ozon 价值也低
|
||
// (见 docs/extension/1688-taobao-implementation.md 采集优先级)。
|
||
],
|
||
|
||
imageGroups: [
|
||
{
|
||
key: 'main',
|
||
name: '主图',
|
||
type: 'img',
|
||
// picGallery-- 内含大图 + 缩略图,同一张图的两种尺寸
|
||
// toOriginalUrl() 剥掉尺寸后缀后 dedupeKey 相同,会自动去重
|
||
selectors: [
|
||
'[class*="picGallery--"] img',
|
||
'#picGalleryEle img',
|
||
'[class*="thumbnailPic--"]',
|
||
],
|
||
// 不设 minWidth:缩略图 naturalWidth 只有 60 左右,
|
||
// 按 200 过滤会把主图全误杀(原图靠 toOriginalUrl 还原)
|
||
},
|
||
{
|
||
key: 'sku',
|
||
name: 'SKU图片',
|
||
type: 'img',
|
||
// ★ 与 1688 不同:淘宝 SKU 是真实 <img>,不是 CSS 背景图
|
||
// 探测证据:valueItem-- n=22 imgs=22(每项恰含一张 img)
|
||
// 所以这里不能用 srcProps: ['backgroundImage']
|
||
selectors: [
|
||
'[class*="valueItem--"]',
|
||
'[class*="valueItemImgWrap--"]',
|
||
],
|
||
nameSelectors: ['[class*="valueItemText--"]'],
|
||
minWidth: 20,
|
||
minHeight: 20,
|
||
},
|
||
{
|
||
key: 'detail',
|
||
name: '详情图',
|
||
type: 'img',
|
||
// 图文详情是懒加载的,需用户点开「图文详情」tab 或滚到底
|
||
selectors: [
|
||
'[class*="tabDetailWrap--"] img',
|
||
'[class*="detailInfo--"] img',
|
||
],
|
||
// detailInfo-- 同时包着「用户评价」区,买家晒单图能有 400-800px,
|
||
// 光靠 minWidth 滤不掉。这些图带水印、质量差,不能采
|
||
excludeWithin: [
|
||
'[class*="Comment--"]',
|
||
'[class*="comments--"]',
|
||
'[class*="userInfo--"]',
|
||
'[class*="rate"]',
|
||
],
|
||
minWidth: 300,
|
||
minHeight: 100,
|
||
},
|
||
{
|
||
key: 'video',
|
||
name: '视频',
|
||
type: 'video',
|
||
selectors: ['[class*="picGallery--"] video', 'video'],
|
||
},
|
||
],
|
||
};
|