feat: 开发采集插件

This commit is contained in:
R524809
2026-08-11 17:09:23 +08:00
parent 6c4356de24
commit b27e42dc75
148 changed files with 6898 additions and 9691 deletions
+126
View File
@@ -0,0 +1,126 @@
# 淘宝/天猫选择器实测记录
> 实测日期:2026-08-11
> 页面:`detail.tmall.com/item.htm?id=960057430812`、`item.taobao.com/item.htm?id=1060253247160`
> 方法:反向扫描(dump 页面实际类名前缀,而非猜名字去查)
> 对应实现:`extension/src/profiles/taobao.ts`
---
## 1. 结论
**两站 DOM 完全一致**,同一套前端(`PageFramework--` / `tbpc-layout` / `keyInfo--` 骨架相同),一份 profile 覆盖淘宝与天猫。
类名形如 `mainTitle--HASH`,是 CSS Modules 产物:**语义前缀稳定,哈希后缀每次构建变**。因此选择器一律写 `[class*="前缀--"]`
结尾的 `--` 不能省——它把父容器和子元素区分开:`generalParamsInfoItem--` 不会误命中 `generalParamsInfoItemTitle--`
---
## 2. 确证的选择器
| 目标 | 选择器 | 实测证据 |
|---|---|---|
| 标题 | `[class*="mainTitle--"]` | n=2 imgs=0,纯文本节点。天猫「迷你特工队玩具X弗特…」淘宝「对插双刀流发光双刃剑…」 |
| 标题兜底 | `[class*="MainTitle--"]` `[class*="ItemTitle--"]` | 外层容器,天猫版带图标(imgs=2) |
| 价格 | `[class*="highlightPrice--"]` | 淘宝 `¥5.2`,天猫 `秒杀价¥27.72` |
| 价格兜底 | `[class*="priceWrap--"]` | 会带上「优惠前¥36.8」,故仅兜底 |
| 主图 | `[class*="picGallery--"] img` / `#picGalleryEle` | 天猫 imgs=6,淘宝 imgs=7 |
| 主图缩略 | `[class*="thumbnailPic--"]` | n=5/6 |
| SKU 容器 | `[class*="valueItem--"]` | **n=22 imgs=22**(天猫),每项恰含一张 img |
| SKU 规格名 | `[class*="valueItemText--"]` | 「特工x武器小【弗特】2种形态- 可变形」 |
| 参数项 | `[class*="generalParamsInfoItem--"]` | Title=「品牌」SubTitle=「劣狐狐(模玩)」 |
| 详情区 | `[class*="tabDetailWrap--"]` `[class*="detailInfo--"]` | 淘宝 imgs=7 |
---
## 3. 两个反直觉的点
### 3.1 淘宝 SKU 是真实 `<img>`,不是 CSS 背景图
**与 1688 相反。** 探测数据:
```
valueItem n=22 imgs=22 ← 容器,每项含 1 张 img
valueItemImgWrap n=22 imgs=22 ← 图片包裹层
valueItemImg n=22 imgs=0 ← img 元素本身(querySelectorAll('img') 查自己得 0
valueItemText n=22 imgs=0 ← 规格名文本
```
`imgs=0` 恰恰证明 `valueItemImg--` 就是 `<img>`。所以淘宝 profile **不能**用 `srcProps: ['backgroundImage']`1688 必须用)。
引擎为此补了一段兜底:选择器命中容器且未取到 URL 时,往下找一层 `querySelector('img')`(见 `collector/image.ts`)。
### 3.2 主图组不设 minWidth
`picGallery--` 里同时有大图和缩略图,缩略图 `naturalWidth` 只有 60 左右。按 `minWidth: 200` 过滤会把主图**全部误杀**。
不过滤是安全的,因为 `toOriginalUrl()` 会把两种尺寸都还原成同一个原图 URL,`dedupeKey` 相同即自动去重。
---
## 4. 两个失效的既有假设
### 4.1 页面上没有 `<h1>`
`document.querySelector('h1')` 返回 null。旧 profile 里的 `.tb-detail-hd h1``h1[data-spm]`、裸 `h1` 全部无效,`readySelectors` 也不能用 `h1` 探活。
### 4.2 没有 JSON-LD
两站 `script[type="application/ld+json"]` 都是**空数组**。
`plan-revision.md` R3 里"JSON-LD 比 DOM 选择器稳定一个数量级"的推测**对淘宝不成立**(对 Ozon 仍待验证)。淘宝只能走 DOM。
---
## 5. 待办
### 5.1 desc 暂不采集
`detailInfo--` 容器里混着用户评价、参数信息、图文详情三块,`extract: 'join'` 出来是无法使用的一坨。考虑到 1688/淘宝的中文文案对 Ozon 价值本就低(需重写),一期跳过。
若以后要采,需先定位「图文详情」那个 `tabDetailItem--` 的稳定标识。
### 5.2 详情图需要用户操作
图文详情是懒加载 + tab 切换。用户不点开「图文详情」tab 就采不到。`scan.ts` 已有 `stats.detail === 0` 的警告。
### 5.3 内嵌数据待评估
`window` 上有一批可能有用的键,但**当前架构读不到**——MV3 content script 默认跑在 isolated world,看不见页面 `window`。要读需 `world: 'MAIN'` 或注入 script 标签。
值得关注的:
```
__general_skupanel_cache_data ← 可能含完整 SKU 结构
__ICE_DATA_LOADER__ ← ICE 框架的数据层
__itempage_openapi
g_config
```
如果 `__general_skupanel_cache_data` 真含 SKU 数据,比 DOM 抓 22 个 `valueItem--` 可靠得多。二期评估。
---
## 6. 复测脚本
改版后重跑,对照本文档的证据列:
```js
(() => {
const KEY = /(title|name|main|pic|img|gallery|thumb|sku|value|desc|detail|param|price)/i;
const pfx = new Map();
document.querySelectorAll('*').forEach(el => {
if (typeof el.className !== 'string') return;
el.className.split(/\s+/).forEach(c => {
const m = c.match(/^([A-Za-z][A-Za-z0-9]*)--/);
if (!m || !KEY.test(m[1])) return;
const r = pfx.get(m[1]) ?? { prefix: m[1], n: 0, imgs: 0, sample: '' };
r.n++; r.imgs += el.querySelectorAll('img').length;
if (!r.sample) r.sample = (el.textContent || '').trim().slice(0, 40);
pfx.set(m[1], r);
});
});
console.table([...pfx.values()].sort((a, b) => b.n - a.n).slice(0, 40));
})();
```