Files
ozon-seller-kit/docs/extension/plan-revision.md
T
2026-08-11 17:09:23 +08:00

7.6 KiB
Raw Blame History

插件方案修正说明

对上一轮输出(profiles-ozon.ts / download-implementation.ts / IMPLEMENTATION_PLAN.md)的复核 最后更新:2026-08-11 上游:总体架构 · 契约

按你的三点反馈复核后,方案主体成立,但有 5 处需要改。R1 和 R2 是实质性问题,其余是准确性修正。


R1 · 保存目录:downloads API 做不到「选目录」🔴

上一轮说「完全采用 1688 的 downloads 方案」,这个结论对 1688 插件成立,对我们不成立

chrome.downloads.download()filename 只能是下载目录下的相对路径,不接受绝对路径,也不接受 ..。1688 插件够用是因为它只需要「按商品名建子目录」;而你的需求里有一条它没有:

用户可以选择采集数据保存的目录,这样同一商品不同平台采集的数据放在同一文件夹内

downloads API 下这意味着每次都落在 ~/Downloads/<商品名>/,用户无法指定别的位置,也无法可靠地"追加到上次那个文件夹"(只能靠商品名字符串撞对)。

改用 File System Access API

// 首次:用户选一次根目录(如 ~/Ozon商品库)
const rootHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
await idbSet('SH_ROOT_DIR', rootHandle);        // IndexedDB 可持久化存 handle

// 之后:无需再授权,直接建/进商品子目录
const root = await idbGet('SH_ROOT_DIR');
if (await root.queryPermission({ mode: 'readwrite' }) !== 'granted') {
  await root.requestPermission({ mode: 'readwrite' });   // 极少数情况需重新确认
}
const productDir = await root.getDirectoryHandle('儿童保温杯_316', { create: true });
const imagesDir  = await productDir.getDirectoryHandle('images', { create: true });
const mainDir    = await imagesDir.getDirectoryHandle('main', { create: true });

const fh = await mainDir.getFileHandle('main-001.jpg', { create: true });
const w  = await fh.createWritable();
await w.write(blob);
await w.close();

关键点:

  • handle 能存进 IndexedDB 并跨会话复用,不用每次弹框。这正好支撑"Ozon 采完切 1688 追加到同一文件夹"。
  • 只能在扩展页面上下文调用(side panel 可以,content script 不行)。采集在 content script,写盘在 side panel,正好符合现有分工。
  • 读回 sources.json 做去重(downloads API 只能写不能读,这是它第二个致命短板)。
  • 图片字节仍需 background 代理 fetch(绕 CORS / 防盗链),拿到 blob 再交给 side panel 写盘。

chrome.downloads 保留为降级路径:用户拒绝授权目录时,退回 ~/Downloads/<商品名>/


R2 · Ozon 选择器全部未经验证 🔴

上一轮 profiles-ozon.ts 里的选择器是我根据 Ozon 的通用 DOM 惯例推测的,没有在真实页面上跑过。其中:

选择器 可信度 说明
[data-widget="webProductHeading"] 🟡 Ozon 确实用 data-widget 标记区块,但具体名称需实测
[data-widget="webGallery"] 🟡 同上
.tsHeadline500Medium 🟡 Ozon 设计系统的 typography class,相对稳定
.k1p_27 .e5k_27 .c2h9_27 .h9o_27 .RA-a1 🔴 哈希类名,每次发版就变,等于无效

哈希类名写进配置是负资产——它给人"有兜底"的错觉,实际上一周后就失效。M2 第一步必须是在真实 Ozon 页面上实测,把哈希类名全部替换掉。

替代思路,按优先级:

  1. data-widget 属性Ozon 的区块标记,改版时相对稳定
  2. JSON-LD / __NUXT__ 之类的内嵌数据:见 R3
  3. 结构关系h1 在页面第一个 data-widget 里、图片在 <picture> 中等
  4. 哈希类名:只在实测确认当前有效时临时用,并标注"随时会失效"

R3 · 内嵌 JSON 可能比 DOM 选择器更靠得住 🟡

2026-08-11 更新:这条对淘宝/天猫已证伪。 两站实测 script[type="application/ld+json"] 都是空数组,只能走 DOM(详见 selectors-taobao.md §4.2)。 下面的推理对 Ozon 仍待验证——Ozon 是 SSR 电商站,带 JSON-LD 的概率仍然不低。

另外淘宝 window 上有 __general_skupanel_cache_data 等键可能含结构化数据, 但 MV3 content script 默认在 isolated world,读不到页面 window,需 world: 'MAIN'。二期评估。

上一轮把接口抓取评估为"MV3 下 webRequest 读不到响应体,建议以 DOM 为主"——这个结论对网络层拦截是对的,但漏了第三条路。

Ozon 是 SSR + 水合,页面 HTML 里通常带完整的商品数据(application/ld+json、或挂在 window 上的 state)。这是同步可读、无需拦截网络的:

// 路径 A:JSON-LD(标准化,最稳)
document.querySelectorAll('script[type="application/ld+json"]')
// → { "@type": "Product", name, sku, brand, offers: { price, priceCurrency }, image[] }

// 路径 B:内嵌 state(字段全,但结构随版本变)
// 实测时在 Console 里翻 window 上的候选键

若实测发现 Ozon 的 JSON-LD 里就有标题、价格、品牌、图片列表,那主路径应该是解析 JSON-LD,DOM 选择器降级为兜底——JSON-LD 有 schema.org 标准约束,比哈希类名稳定一个数量级。

M2 的实测任务因此扩为两条:DOM 选择器 + 内嵌 JSON,看哪条覆盖率高。


R4 · product.json 生成逻辑要移出插件 🟡

上一轮 download-implementation.ts 里的 buildOzonProductJson() 试图填 attributes[].id,还标了 // 需要查询Ozon类目属性字典。这块插件做不了也不该做:属性 id 依赖类目,类目在工作台才定。

按契约(product-json.md §4):

插件      → 写 _raw.params(原始 kv),attributes 留空数组
工作台    → 定类目 → 拉字典 → 映射 attributes

另外两处要改:

  • offer_id 上一轮注释成"需要用户填写",应明确采集阶段恒为空字符串。跟卖场景下沿用竞品货号是错的。
  • images 上一轮直接填了采集到的源站 URL。应填本地相对路径_imagesimages 字段留空——Ozon 要的是我们自己图床的公网 URL,源站 URL 提交上去等于盗链且随时失效。

R5 · 就绪检测保留人工控制,但补一条提示 🟢

你的判断对,人工触发能绕开绝大部分动态渲染问题,一期不做 MutationObserver。

只补一点:Ozon 详情图是滚动懒加载的,用户不滚到底部时详情图根本不在 DOM 里。所以侧边栏在检测到 detail 组为 0 张时,要提示:

主图 6 · SKU 4 · 详情 0
⚠️ 详情图为 0,请滚动到页面底部让图片加载后重新采集

比静默采到 0 张要好。这不算"智能等待",只是把结果如实告诉用户。


修正后的 M1M4

里程碑 内容 关键改动
M1 product.json 契约 + TS 类型 新增,先定契约
M2 Ozon 实测:选择器 + 内嵌 JSON 双路径调研 R2/R3这一步必须在真实页面上做,是整个插件的地基
M3 采集引擎 + 侧边栏表单 + 图片分组勾选
M4 File System Access 写商品文件夹 R1,替换 downloads 方案
M5 1688 profile + 读 sources.json 去重追加

M2 需要你提供 3–5 个不同类目的 Ozon 商品页链接(最好含一个有 SKU 变体的、一个详情图很多的)。没有真实页面,选择器配置只能停在推测。