更新 README + 模板导出新增面料弹性/沙特阿拉伯码识别
1) README 重写:双模式架构(热点采集/Pinterest 参考模式)、模特性别分组、生图尺寸配置、模板导出增强、一键打包脚本 2) template_export:识别「面料弹性」列填 spu.fabric;识别「尺码表-沙特阿拉伯码」列填 sku.size(均模糊匹配,检测到才填)
This commit is contained in:
@@ -1,23 +1,64 @@
|
|||||||
# POD 热点抓取 Agent(LangGraph 工程化版)
|
# POD 热点抓取 Agent(LangGraph 工程化版)
|
||||||
|
|
||||||
自动抓取海外各国(US / GB / JP / AU)实时热点 + 风格趋势,过滤侵权风险,产出可直接用于
|
自动抓取海外各国(US / GB / JP / AU / MX / DE / BR / SA / PL / ES / IT / CA)实时热点 + 风格趋势,
|
||||||
AI 生图的印花设计提示词。本版本用 **LangGraph** 重写,强调**工程化、节点兜底、可插拔**。
|
过滤侵权风险,产出可直接用于 AI 生图的印花设计提示词,并完成 **设计稿 → 三合一模特试穿 → OSS 图床 →
|
||||||
|
种草图 → 商品上传模板** 的完整 POD 生产链路。本版本用 **LangGraph** 重写,强调**工程化、节点兜底、可插拔**。
|
||||||
|
|
||||||
|
## 两种运行模式
|
||||||
|
|
||||||
|
| 模式 | 入口 | 数据来源 | 适用 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **热点采集模式** | UI「热点采集」 | Google Trends(trending/style/related) | 抓国家实时热点词,LLM 筛出可印花设计 |
|
||||||
|
| **Pinterest 参考模式** | UI「Pinterest 参考模式」 | Pinterest 爬图 + LLM 多模态分析 | 从真实图片提炼原创印花设计,侵权风险更低 |
|
||||||
|
|
||||||
## 架构:LangGraph 状态图
|
## 架构:LangGraph 状态图
|
||||||
|
|
||||||
|
### 模式 A:热点采集模式
|
||||||
|
|
||||||
```
|
```
|
||||||
START → seed → fetch → filter → score → screen → prompt_build → compose → END
|
START → seed → fetch → filter → score → screen → prompt_build → compose → product
|
||||||
|
→ oss_upload → seed_shot → template_export → END
|
||||||
```
|
```
|
||||||
|
|
||||||
| 节点 | 职责 | 可插拔点 / 兜底 |
|
| 节点 | 职责 | 可插拔点 / 兜底 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `seed` | 动态生成种子词(trending 派生 + 历史热点 + 月份/节日 → LLM) | 策略可插拔(`graph/seeds/`);LLM 失败回退静态种子 |
|
| `seed` | 动态生成种子词(trending 派生 + 历史热点 + 月份/节日 → LLM) | 策略可插拔(`graph/seeds/`);LLM 失败回退静态种子 |
|
||||||
| `fetch` | 抓取热点(多数据源汇总) | 数据源可插拔(`graph/sources/`);单源失败不影响其它源 |
|
| `fetch` | 抓取热点(Google Trends 多数据源汇总) | 数据源可插拔(`graph/sources/`);单源失败不影响其它源 |
|
||||||
| `filter` | 黑名单 + 真实人物 + 设计相关性三级过滤 | 各级独立 try,单级失败不影响其它级 |
|
| `filter` | 黑名单 + 真实人物 + 设计相关性三级过滤 | 各级独立 try,单级失败不影响其它级 |
|
||||||
| `score` | 归一化 + 跨源融合 + 综合分 | 纯逻辑,空数据返回空 |
|
| `score` | 归一化 + 跨源融合 + 综合分 | 纯逻辑,空数据返回空 |
|
||||||
| `screen` | LLM 合规筛查 + 结构化四要素 | LLM 后端可插拔(`graph/llms/`);调用失败降级 Mock |
|
| `screen` | LLM 合规筛查 + 结构化四要素 | LLM 后端可插拔(`graph/llms/`);调用失败降级 Mock |
|
||||||
| `prompt_build` | 用固定模板装配 image/wearable/composite 提示词 | 四要素缺失时动态推导兜底 |
|
| `prompt_build` | 用固定模板装配 image/wearable/composite 提示词 | 四要素缺失时动态推导兜底 |
|
||||||
| `compose` | 写 `output/<country>/` 产物;可选印图 | 图像后端可插拔(`graph/backends/`);无底图只导出 |
|
| `compose` | 生成纯印花设计稿(白底,可直接打印) | 图像后端可插拔(`graph/backends/`);无底图只导出 |
|
||||||
|
| `product` | SPU/颜色选品 → 底图 → 三图合成(图1模特+图2设计稿+图3底图) | 缺底图/模特图自动跳过 |
|
||||||
|
| `oss_upload` | 压缩(3:4 / ≥1340×1785 / <2MB)+ 上传阿里云 OSS | 可关闭(`oss.enabled=false`) |
|
||||||
|
| `seed_shot` | 从三合一主图按颜色选取种草图 + 上传 | 按 `seed_shot.count` 随机选取 |
|
||||||
|
| `template_export` | 把 SPU/SKU 数据填入商品上传模板 | 模板列名模糊匹配,多语言变体兼容 |
|
||||||
|
|
||||||
|
### 模式 B:Pinterest 参考模式
|
||||||
|
|
||||||
|
```
|
||||||
|
START → pinterest_init → pinterest_search → pinterest_scrape → pinterest_analyze
|
||||||
|
→ [pinterest_route 循环:简报不足 → 回 pinterest_search]
|
||||||
|
→ pinterest_finalize(排空简报池,后台并发生成 设计→三合一→OSS→种草图)
|
||||||
|
→ template_export → END
|
||||||
|
```
|
||||||
|
|
||||||
|
| 节点 | 职责 |
|
||||||
|
|---|---|
|
||||||
|
| `pinterest_init` | 创建简报池 + 并发生成流水线(`graph/pinterest_pipeline.py`) |
|
||||||
|
| `pinterest_search` | 按需生成搜索词(direct=种子词直接拼「 t-shirt design」;llm=LLM 按需生成,json_schema+防重复) |
|
||||||
|
| `pinterest_scrape` | 用 Playwright 爬取 Pinterest 图片(共享 Chrome 登录态,并发=1) |
|
||||||
|
| `pinterest_analyze` | 从图池取图 → 多并发 LLM 多模态分析 → 原创设计简报(侵权检测 + 原创化引导) |
|
||||||
|
| `pinterest_route` | 简报不足 → 图池有未消费图 → 继续分析;图池不足 → 新一轮搜索;轮次耗尽 → 结束 |
|
||||||
|
| `pinterest_finalize` | 排空简报池,后台线程池并发生成(设计→三合一→OSS→种草图),合并产品 |
|
||||||
|
|
||||||
|
**图池机制**:爬取的图片注册到 `image_pool.json`(路径+md5+搜索词);分析过的图片 md5 一律拉黑
|
||||||
|
(`used_images.json`,合适/不合适都拉黑);图池有未消费图片 → 直接分析(不搜索);不足 → 新一轮搜索。
|
||||||
|
每个简报带 `image_index`/`source_md5` 全局 id 校验,保证简报、设计稿、三合一套图对应同一张图。
|
||||||
|
|
||||||
|
**并发控制**(`config.yaml` 的 `pinterest:`):`analyze_concurrency`(分析线程数)、`analyze_batch`
|
||||||
|
(每轮取图数,0=自动补齐让 pipeline 队列满并发)、`max_designs`(每搜索词简报上限)、
|
||||||
|
`scrape_concurrency`(爬图并发,必须=1)、`err400_limit`(400 超限放弃当前种子词)。
|
||||||
|
|
||||||
**每个节点都用 `graph/validate.with_fallback` 包裹**:任何未预料异常都被捕获、记入
|
**每个节点都用 `graph/validate.with_fallback` 包裹**:任何未预料异常都被捕获、记入
|
||||||
`state['errors']`、返回最小更新,整图继续往下走,单点故障不中断流水线。
|
`state['errors']`、返回最小更新,整图继续往下走,单点故障不中断流水线。
|
||||||
@@ -26,9 +67,15 @@ START → seed → fetch → filter → score → screen → prompt_build → co
|
|||||||
|
|
||||||
```
|
```
|
||||||
pod_trend_agent/
|
pod_trend_agent/
|
||||||
├── cli.py # 命令行入口(替代旧 run/screen/compose)
|
├── cli.py # 命令行入口
|
||||||
├── config.yaml # 全局配置(数据源、权重、黑名单、LLM、模板、compose)
|
├── ui_app.py # 桌面 UI(Tkinter)
|
||||||
├── configs/countries/ # 各国专属数据源种子(US|GB|JP|AU.yaml)
|
├── config.yaml # 全局配置(数据源、权重、黑名单、LLM、模板、compose/product/oss/seed_shot)
|
||||||
|
├── configs/
|
||||||
|
│ ├── countries/ # 各国 Google Trends 种子词(US|GB|JP|AU|MX|DE|BR|SA|PL|ES|IT|CA.yaml)
|
||||||
|
│ ├── pinterest/ # 各国 Pinterest 种子词池(<CC>.yaml)
|
||||||
|
│ ├── model_features.yaml # 模特特征库(按性别分组 female/male,种草图用)
|
||||||
|
│ ├── seed_shot_templates.yaml # 种草图提示词模板
|
||||||
|
│ └── style_features.yaml
|
||||||
├── prompts/<country>/ # ★ 每个国家不同的提示词单独文件夹
|
├── prompts/<country>/ # ★ 每个国家不同的提示词单独文件夹
|
||||||
│ ├── system_prompt.md # 该国 LLM 系统提示(补充段,叠加到默认规则)
|
│ ├── system_prompt.md # 该国 LLM 系统提示(补充段,叠加到默认规则)
|
||||||
│ └── aesthetics.yaml # 审美 hint + 风格-配色 extra 规则 + 额外黑名单
|
│ └── aesthetics.yaml # 审美 hint + 风格-配色 extra 规则 + 额外黑名单
|
||||||
@@ -38,39 +85,86 @@ pod_trend_agent/
|
|||||||
│ ├── loader.py # 配置/提示词加载与合并
|
│ ├── loader.py # 配置/提示词加载与合并
|
||||||
│ ├── scoring.py # 归一化/融合/过滤(纯逻辑)
|
│ ├── scoring.py # 归一化/融合/过滤(纯逻辑)
|
||||||
│ ├── style_rules.py # 动态风格/配色推导
|
│ ├── style_rules.py # 动态风格/配色推导
|
||||||
│ ├── templates.py # 固定提示词模板(按国家区分,30×38cm 上限,文字敏感规则)
|
│ ├── templates.py # 固定提示词模板(按国家区分,文字敏感规则)
|
||||||
│ ├── classify.py # 热点分类
|
│ ├── classify.py # 热点分类
|
||||||
│ ├── sources/ # 数据源可插拔(google_trends / pinterest + 注册表)
|
│ ├── pinterest.py # Pinterest 图池/黑名单/压缩/MD5 去重
|
||||||
|
│ ├── pinterest_pipeline.py # 简报池 + 并发生成流水线(Pinterest 模式核心)
|
||||||
|
│ ├── product.py / product_batch.py # 三合一合成 / 批量
|
||||||
|
│ ├── seed_shot.py # 种草图选取 + 模特特征(性别分组)
|
||||||
|
│ ├── template_export.py # 商品上传模板导出(列名模糊匹配)
|
||||||
|
│ ├── oss_upload.py # 图片压缩 + 阿里云 OSS 上传
|
||||||
|
│ ├── sources/ # 数据源可插拔(google_trends + 注册表)
|
||||||
│ ├── llms/ # LLM 后端可插拔(mock / openai_compat + 注册表)
|
│ ├── llms/ # LLM 后端可插拔(mock / openai_compat + 注册表)
|
||||||
│ ├── backends/ # 图像后端可插拔(openai)
|
│ ├── backends/ # 图像后端可插拔(openai / mock)
|
||||||
│ ├── seeds/ # 种子词策略可插拔(static / dynamic + holidays 月份节日)
|
│ ├── seeds/ # 种子词策略可插拔(static / dynamic + holidays)
|
||||||
│ ├── nodes/ # 7 个流水线节点(含 seed)
|
│ ├── nodes/ # 流水线节点(seed/fetch/.../pinterest_*/product/oss/seed_shot/template_export)
|
||||||
│ └── agent.py # 构建并编译 StateGraph,run_country()
|
│ └── agent.py # 构建并编译 StateGraph(build_graph / build_pinterest_graph)
|
||||||
|
├── templates/
|
||||||
|
│ └── template_router.py # 商品上传模板路由(列名定位、数据写入)
|
||||||
|
├── scripts/
|
||||||
|
│ └── package.py # 一键打包脚本(PyInstaller → dist_vXX)
|
||||||
|
├── basemap/ # 平铺衣服底图(<款号>/<SKU.code>/)
|
||||||
|
├── material_library/ # 模特图(<品类>/)
|
||||||
|
├── db/spu_sku.db # SPU/SKU 数据库
|
||||||
|
├── pinterest_scraper/ # Playwright 爬虫 + Chrome 登录态
|
||||||
└── output/<country>/ # ★ 每个国家的产物独立文件夹
|
└── output/<country>/ # ★ 每个国家的产物独立文件夹
|
||||||
├── design_briefs.json # 含 国家/热点词/类别/风险/设计稿提示词/完整印图提示词
|
├── design_briefs.json/md # 设计简报(含 image_prompt / composite_prompt)
|
||||||
├── design_briefs.md
|
├── designs/ # 纯印花设计稿
|
||||||
├── composite_prompts.json/md # 封装提示词包(印到底图用)
|
├── product/ # 三合一合成图 + 已填写商品模板 <SKU>_已填写.xlsx
|
||||||
└── report.md # 各阶段统计 + 兜底错误记录
|
└── report.md # 各阶段统计 + 兜底错误记录
|
||||||
```
|
```
|
||||||
|
|
||||||
## 可插拔指南
|
## Pinterest 参考模式详解
|
||||||
|
|
||||||
- **加数据源**:在 `graph/sources/` 新建类继承 `DataSource` 实现 `fetch()`,在
|
1. **种子词池**:`configs/pinterest/<CC>.yaml` 存各国风格化种子词(含节日词),偏向印花设计。
|
||||||
`graph/sources/__init__.py` 的 `SOURCES` 登记;在 `config.yaml` 的 `sources:` 加入。
|
2. **按需搜索**:每次只生成 1 个搜索词(`search_terms_per_run=1`),自动追加「 t-shirt design」后缀
|
||||||
- **加 LLM 后端**:在 `graph/llms/` 新建类继承 `LLMBackend` 实现 `screen()`,在
|
(保证爬到的图是真实印花艺术,而非生活方式/风景图);爬取成功才标记已用,失败不拉黑。
|
||||||
`LLM_BACKENDS` 登记;`llm_screen.provider` 选择(openai/deepseek/qwen/moonshot 走 openai_compat)。
|
3. **爬图**:Playwright 打开 Pinterest 搜索页截图采集(共享 `.chrome_session` 登录态,并发强制=1)。
|
||||||
- **改种子词策略**:`seed_provider` 选 `static`(仅 yaml)/ `mock`(规则生成,零成本)/ `openai_compat`(真 LLM)。
|
4. **图池 + MD5 去重**:图片注册 `image_pool.json`;分析过的 md5 一律拉黑(`used_images.json`);
|
||||||
动态策略收集 trending 派生 + 上轮 safe 历史热点 + 月份/临近节日上下文,由 LLM 后端生成种子词,
|
设计稿生成后全局 MD5 去重(`.cache/global_design_md5.json`),跨国家生效。
|
||||||
与 yaml 静态种子合并后注入 `style.seeds` / `related.seed_keywords`(`config.yaml` 的 `seed_provider_cfg` 限量)。
|
5. **多模态分析**:大图先压缩(max 1024px / 1.5MB)再送 LLM,产出原创设计简报(motif/风格/配色/构图 +
|
||||||
- **加国家**:建 `configs/countries/<CODE>.yaml`(种子词)与 `prompts/<CODE>/`(提示词),
|
risk_level:safe/review/blocked)。blocked 拦截、review 加原创化魔改引导、safe 直接生成。
|
||||||
在 `config.yaml` 的 `countries:` 加入即可,无需改代码。
|
6. **并发生成**:简报推入 `PinterestPipeline`,后台线程池(`product.concurrency`,默认 5)逐条
|
||||||
- **加图像后端**:在 `graph/backends/` 实现 `ImageBackend`,`compose.backend` 配置后
|
生成 设计稿 → 三合一 → OSS 上传 → 种草图,边分析边生成,不等全部分析完。
|
||||||
用 `cli.py -c <国家> --base 底图.png` 印图。
|
|
||||||
|
## 模特性别分组
|
||||||
|
|
||||||
|
`configs/model_features.yaml` 的模特特征按性别分组(`female` / `male`)。种草图生成时读取商品上传
|
||||||
|
模板的「类目」表头值:
|
||||||
|
- 类目含「男」(如 `男士时尚>男装>男装T恤`)→ 固定从 **male** 组随机取模特
|
||||||
|
- 类目含「女」→ 固定从 **female** 组随机取模特
|
||||||
|
- 都不含 → 男女混合随机
|
||||||
|
|
||||||
|
## 生图尺寸(读 config,不硬编码)
|
||||||
|
|
||||||
|
| 图片类型 | 配置项 | 默认 |
|
||||||
|
|---|---|---|
|
||||||
|
| 纯印花设计稿 | `compose.design_size` | 1024x1024(1:1) |
|
||||||
|
| 三合一合成图 | `compose.size` | 1536x2048(3:4) |
|
||||||
|
| 种草图 | `seed_shot.size` | 1536x2048(3:4) |
|
||||||
|
|
||||||
|
## 商品上传模板导出
|
||||||
|
|
||||||
|
生成产品图后,从 db 读该 SPU/SKU 信息(材质/成分/图案/领型/面料/克重/印花类型/尺码表/边长/包装重量…),
|
||||||
|
经 `templates/template_router.py` 的路由函数填入商品上传模板,输出 `<SKU>_已填写.xlsx` 到
|
||||||
|
`output/<国家>/product/`。模板已自包含在项目 `templates/`(`config.product.template_path`,可改为自由上传)。
|
||||||
|
|
||||||
|
- 数据从模板第 6 行(数据区空行)开始填,SPU 与各尺码 SKU 紧邻;
|
||||||
|
- **列名模糊匹配**:模板列名按名称匹配(非固定行列),支持中/英/日变体;
|
||||||
|
- **胸围识别**:识别「基码表-胸围(cm)」「胸围全围(cm)」等含「胸围」的列,全部填 `sku.bust` 值;
|
||||||
|
- **申报价格**:模糊匹配所有含「申报价格」的列,统一按加价后价格填写(`price × (1+markup_percent%)`);
|
||||||
|
- **款式来源**:SPU 行「款式来源」统一填「现货款」;
|
||||||
|
- **商品产地**:国家简称映射正式名称(如「沙特」→「沙特阿拉伯」,`_COUNTRY_NAME_MAP` 可扩展);
|
||||||
|
- **详情图文**:由全部主图 + 种草图组成(不再拼接 img_url_2);
|
||||||
|
- 规格类型2 = `{size}*{color}`;币种=CNY;发货仓1~N 取模板顶头按「、」分隔,库存均 200;
|
||||||
|
- 模板文件被占用(打开中)时自动换名 `<SKU>_已填写_N.xlsx`,不中断导出;
|
||||||
|
- **多颜色 + 模式**:`sku_code` 支持逗号分隔多颜色;`spu_per_color`(true=每颜色一个 SPU 块 /
|
||||||
|
false=单 SPU 挂多颜色变体);CLI 用 `--single-spu` 切单 SPU 模式,UI 用「颜色多选 + 模板模式」。
|
||||||
|
|
||||||
## 用法
|
## 用法
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 跑全部国家(默认 US GB JP AU)
|
# 跑全部国家(热点采集模式,默认 US GB JP AU)
|
||||||
python cli.py
|
python cli.py
|
||||||
|
|
||||||
# 只跑英国,Mock 兜底 LLM
|
# 只跑英国,Mock 兜底 LLM
|
||||||
@@ -81,6 +175,13 @@ python cli.py -c US
|
|||||||
|
|
||||||
# 提供平铺衣服底图,把设计印上去(需 config.compose.backend + api_key)
|
# 提供平铺衣服底图,把设计印上去(需 config.compose.backend + api_key)
|
||||||
python cli.py -c US --base path/to/flatlay.png
|
python cli.py -c US --base path/to/flatlay.png
|
||||||
|
|
||||||
|
# 查看 db 可选款号 / 某款颜色
|
||||||
|
python cli.py --list-spus
|
||||||
|
python cli.py --list-colors DG004
|
||||||
|
|
||||||
|
# 指定款号+颜色跑流水线(product.backend=mock 占位 / openai 真生图需 compose.api_key)
|
||||||
|
python cli.py -c GB --spu DG004 --sku DG004-BL01
|
||||||
```
|
```
|
||||||
|
|
||||||
## 桌面 UI(Tkinter 轻量版)
|
## 桌面 UI(Tkinter 轻量版)
|
||||||
@@ -91,56 +192,40 @@ python ui_app.py
|
|||||||
|
|
||||||
# 无 GUI 自检(验证环境/核心链路,结果写入运行根 self_test_result.txt)
|
# 无 GUI 自检(验证环境/核心链路,结果写入运行根 self_test_result.txt)
|
||||||
python ui_app.py --self-test
|
python ui_app.py --self-test
|
||||||
|
|
||||||
# 打包 exe(单文件、无控制台;数据文件打进 _MEIPASS,output/.cache 写在 exe 旁)
|
|
||||||
.venv/Scripts/pyinstaller -F -w --name PODTrendAgent \
|
|
||||||
--add-data "config.yaml;." --add-data "configs;configs" --add-data "prompts;prompts" \
|
|
||||||
--hidden-import pytrends --hidden-import pytrends.request ui_app.py
|
|
||||||
# 产物:dist/PODTrendAgent.exe
|
|
||||||
```
|
```
|
||||||
|
|
||||||
UI 功能:国家多选、LLM 后端选择(mock/static/openai_compat...)、种子上限、**SPU/颜色选品**、运行(后台线程 + 实时日志)、
|
UI 功能:国家多选、**流程选择(热点采集 / Pinterest 参考模式)**、LLM 后端选择、种子上限、
|
||||||
结果表格(国家/热点/类别/风险/分数,双击看完整提示词)、打开产物目录。
|
**SPU/颜色选品**、运行(后台线程 + 实时日志)、结果表格(双击看完整提示词)、打开产物目录。
|
||||||
打包后首次运行自动在 exe 旁创建 `output/` 与 `.cache/`,并生成一份**可编辑的默认配置**
|
|
||||||
(`config.yaml` / `configs/` / `prompts/`);exe 运行时**优先读 exe 旁这份配置**(改了就生效),
|
|
||||||
exe 内置的作为兜底默认。想恢复默认:删掉 exe 旁对应文件即可。
|
|
||||||
|
|
||||||
## 产品图生成(product)
|
## 打包
|
||||||
|
|
||||||
热点提示词 → SPU/颜色选品 → basemap 底图 → 印花图 → 模特试穿合成图,产物在 `output/<country>/product/`。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看 db 可选款号 / 某款颜色
|
# 一键打包(自动取下一个版本号)
|
||||||
python cli.py --list-spus
|
python scripts/package.py
|
||||||
python cli.py --list-colors DG004
|
|
||||||
|
|
||||||
# 指定款号+颜色跑流水线(product.backend=mock 占位 / openai 真生图需 compose.api_key)
|
# 指定版本号
|
||||||
python cli.py -c GB --spu DG004 --sku DG004-BL01
|
python scripts/package.py 84
|
||||||
```
|
```
|
||||||
|
|
||||||
- 数据关系:`SPU.code`=款号,`SKU.code`=`款号-颜色编码`(如 DG004-BL01);底图在 `basemap/<款号>/<SKU.code>/`,模特图在 `material_library/<品类>/`。
|
产物:`dist_vXX/PODTrendAgent.exe`。打包流程:PyInstaller 构建(不跑自检)→ 同步数据目录
|
||||||
- 缺底图/模特图时对应步骤自动跳过并提示;`product.backend` 留空则仅存底图。
|
(config.yaml / configs / prompts / templates / basemap / material_library / db / output / logs)→
|
||||||
- **商品上传模板自动导出**:生成产品图后,从 db 读该 SPU/SKU 信息(材质/成分/图案/领型/面料/克重/印花类型/尺码表/边长/包装重量…),经 `templates/template_router.py` 的路由函数填入商品上传模板,
|
复制当前 Chrome 登录态到 exe 同目录(`pinterest_scraper/.chrome_session`)→ 同步上一个版本的 `.cache`。
|
||||||
输出 `<SKU>_已填写.xlsx` 到 `output/<国家>/product/`。模板已自包含在项目 `templates/`(`config.product.template_path`,可改为自由上传)。
|
exe 运行时优先读 exe 旁这份配置(改了就生效),exe 内置的作为兜底默认。
|
||||||
- 数据从模板第 6 行(数据区空行)开始填,SPU 与各尺码 SKU 紧邻;
|
|
||||||
- SKC货号:SPU 行=SPU.code、SKU 行=SKU.code;SKU货号暂不填;
|
|
||||||
- 规格类型2 = `{size}*{color}`;币种=CNY;发货仓1~N 取模板顶头按「、」分隔,库存均 200;
|
|
||||||
- 商品轮播图列名中/英/日变体均可模糊匹配;SPU 行轮播图1=生成首图,SKU 行轮播图2~5=db `img_url_2~5`(无则回退生成图);
|
|
||||||
- 模板文件被占用(打开中)时自动换名 `<SKU>_已填写_N.xlsx`,不中断导出;
|
|
||||||
- **多颜色 + 模式**:`sku_code` 支持逗号分隔多颜色(如 `DG015-VT01,DG015-DARK HEATHER`);`spu_per_color`(true=每颜色一个 SPU 块 / false=单 SPU 挂多颜色变体);CLI 用 `--single-spu` 切单 SPU 模式,UI 用「颜色多选 + 模板模式」。
|
|
||||||
- UI 打开时**默认加载勾选国家的热点缓存**(`output/<国家>/design_briefs.json`)直接展示,点「运行」刷新最新。
|
|
||||||
|
|
||||||
## 数据流要点
|
## 数据流要点
|
||||||
|
|
||||||
0. `seed`(动态种子词):收集 trending 派生(去噪 + 人名 + 风险护栏过滤)、上轮 safe 历史热点、
|
0. `seed`(动态种子词):收集 trending 派生(去噪 + 人名 + 风险护栏过滤)、上轮 safe 历史热点、
|
||||||
当前月份/临近节日,由 LLM 后端(mock 规则 / 真 LLM)生成动态种子词,与 yaml 静态种子合并注入抓取配置。
|
当前月份/临近节日,由 LLM 后端(mock 规则 / 真 LLM)生成动态种子词,与 yaml 静态种子合并注入抓取配置。
|
||||||
1. `fetch` 抓 `gt_trending`(国家 RSS 实时榜)+ `gt_style`/`gt_related`(风格/行业种子关联词,种子来自 seed 节点)。
|
1. `fetch` 抓 `gt_trending`(国家 RSS 实时榜)+ `gt_style`/`gt_related`(风格/行业种子关联词)。
|
||||||
2. `filter` 三级过滤:合规黑名单(全局 + 各国 `extra_blacklist`)→ 真实人物 → 泛新闻/科技/赛事词。
|
2. `filter` 三级过滤:合规黑名单(全局 + 各国 `extra_blacklist`)→ 真实人物 → 泛新闻/科技/赛事词。
|
||||||
3. `score` 按 (source,kind) 分组 min-max 归一化、跨源融合加权重。
|
3. `score` 按 (source,kind) 分组 min-max 归一化、跨源融合加权重。
|
||||||
4. `screen` LLM 合规筛查,产出 risk_level / 四要素;`keep_review=false` 时丢弃待复核。
|
4. `screen` LLM 合规筛查,产出 risk_level / 四要素;`keep_review=false` 时丢弃待复核。
|
||||||
5. `prompt_build` 用四要素 + 固定模板装配 image/wearable/composite 提示词(结构一致、可复用 img2img)。
|
5. `prompt_build` 用四要素 + 固定模板装配 image/wearable/composite 提示词(结构一致、可复用 img2img)。
|
||||||
6. `product`(可选)SPU/颜色选品 → basemap 底图 → 印花图 → 模特试穿合成(`output/<country>/product/`)。
|
6. `compose` 生成纯印花设计稿(白底,可直接打印),product 取第一张作为图2 复用。
|
||||||
7. `compose` 写各国 `output/<country>/`;给底图则调图像后端印图。
|
7. `product` SPU/颜色选品 → basemap 底图 → 三图合成(图1模特 + 图2设计稿 + 图3底图)→ 模板。
|
||||||
|
8. `oss_upload` 压缩(3:4 / ≥1340×1785 / <2MB)+ 上传阿里云 OSS,URL 写回 `*_url` 字段。
|
||||||
|
9. `seed_shot` 从三合一主图按颜色选取种草图(模特性别分组),上传 OSS,URL 写入模板「详情图文」。
|
||||||
|
10. `template_export` 把 SPU/SKU 数据填入商品上传模板,输出 `<SKU>_已填写.xlsx`。
|
||||||
|
|
||||||
> 合规红线(最重要):版权/商标/真实人物/敏感内容一律拦截或降级;二创/重绘仍有风险,
|
> 合规红线(最重要):版权/商标/真实人物/敏感内容一律拦截或降级;二创/重绘仍有风险,
|
||||||
> 拿不准进"待复核"。各国侵权盲区不同(如英国 WWE/游戏/乐队、日本动漫 IP),放在
|
> 拿不准进"待复核"。各国侵权盲区不同(如英国 WWE/游戏/乐队、日本动漫 IP),放在
|
||||||
|
|||||||
@@ -205,8 +205,10 @@ def _fill_design_fields(router, spu_code: str, oss_code: str, cn_title: str, en_
|
|||||||
|
|
||||||
|
|
||||||
def _build_spu_row(spu: Dict[str, Any], spu_code: str, origin_province: str,
|
def _build_spu_row(spu: Dict[str, Any], spu_code: str, origin_province: str,
|
||||||
color: Optional[str] = None) -> Dict[str, Any]:
|
color: Optional[str] = None,
|
||||||
"""构造一行 SPU(固定字段:SKC货号=code、风格=休闲、商品产地=经营站点;多颜色时用色值列区分)。"""
|
fabric_headers: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||||
|
"""构造一行 SPU(固定字段:SKC货号=code、风格=休闲、商品产地=经营站点;多颜色时用色值列区分)。
|
||||||
|
fabric 填「面料弹性」列(fabric_headers,如 SPU商品属性-面料弹性,检测到才填 spu.fabric)。"""
|
||||||
row: Dict[str, Any] = {
|
row: Dict[str, Any] = {
|
||||||
"基础信息-商品层级": "spu",
|
"基础信息-商品层级": "spu",
|
||||||
"SKC货号": spu_code, # code 路由为 SKC货号(用户要求)
|
"SKC货号": spu_code, # code 路由为 SKC货号(用户要求)
|
||||||
@@ -220,6 +222,10 @@ def _build_spu_row(spu: Dict[str, Any], spu_code: str, origin_province: str,
|
|||||||
v = spu.get(dbk)
|
v = spu.get(dbk)
|
||||||
if v not in (None, ""):
|
if v not in (None, ""):
|
||||||
row[header] = v
|
row[header] = v
|
||||||
|
fabric = spu.get("fabric")
|
||||||
|
if fabric not in (None, "") and fabric_headers:
|
||||||
|
for h in fabric_headers:
|
||||||
|
row[h] = fabric
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
@@ -235,14 +241,26 @@ def _find_bust_headers(router) -> List[str]:
|
|||||||
return hits or ["胸围全围(cm)"]
|
return hits or ["胸围全围(cm)"]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_fabric_headers(router) -> List[str]:
|
||||||
|
"""定位所有「面料弹性」列(SPU商品属性-面料弹性…模糊匹配);匹配到多个时全部填 spu.fabric。"""
|
||||||
|
return [str(k) for k in router.column_map if "面料弹性" in str(k)]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_sa_size_headers(router) -> List[str]:
|
||||||
|
"""定位所有「沙特阿拉伯码」列(尺码表-沙特阿拉伯码…模糊匹配);匹配到多个时全部填 sku.size。"""
|
||||||
|
return [str(k) for k in router.column_map if "沙特阿拉伯码" in str(k)]
|
||||||
|
|
||||||
|
|
||||||
def _build_sku_row(spu_code: str, sc: str, sk: Dict[str, Any], size: str, color: str,
|
def _build_sku_row(spu_code: str, sc: str, sk: Dict[str, Any], size: str, color: str,
|
||||||
warehouses: List[str], markup_percent: float = 0.0,
|
warehouses: List[str], markup_percent: float = 0.0,
|
||||||
multi: bool = True, price_header: str = "申报价格-日本站",
|
multi: bool = True, price_header: str = "申报价格-日本站",
|
||||||
bust_headers: Optional[List[str]] = None,
|
bust_headers: Optional[List[str]] = None,
|
||||||
price_headers: Optional[List[str]] = None) -> Dict[str, Any]:
|
price_headers: Optional[List[str]] = None,
|
||||||
|
sa_size_headers: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||||
"""构造一行 SKU(固定字段:SPU货号、SKC货号=sku.code、规格类型2、币种 CNY、发货仓1~N 及库存 200)。
|
"""构造一行 SKU(固定字段:SPU货号、SKC货号=sku.code、规格类型2、币种 CNY、发货仓1~N 及库存 200)。
|
||||||
价格(price_headers 列,如 申报价格-美国站/日本站,模糊匹配到多个时全部填)= SKU.price × (1+markup/100),
|
价格(price_headers 列,如 申报价格-美国站/日本站,模糊匹配到多个时全部填)= SKU.price × (1+markup/100),
|
||||||
预先填好。bust 填所有「胸围」列(bust_headers,如 基码表-胸围(cm)/胸围全围(cm),检测到才填)。
|
预先填好。bust 填所有「胸围」列(bust_headers,如 基码表-胸围(cm)/胸围全围(cm),检测到才填)。
|
||||||
|
size 填「尺码」列 + 所有「沙特阿拉伯码」列(sa_size_headers,如 尺码表-沙特阿拉伯码,检测到才填)。
|
||||||
规格类型2 统一填「尺码」两个字(不是 size 参数值)。"""
|
规格类型2 统一填「尺码」两个字(不是 size 参数值)。"""
|
||||||
row: Dict[str, Any] = {
|
row: Dict[str, Any] = {
|
||||||
"基础信息-商品层级": "sku",
|
"基础信息-商品层级": "sku",
|
||||||
@@ -256,6 +274,8 @@ def _build_sku_row(spu_code: str, sc: str, sk: Dict[str, Any], size: str, color:
|
|||||||
price_headers = [price_header]
|
price_headers = [price_header]
|
||||||
if bust_headers is None:
|
if bust_headers is None:
|
||||||
bust_headers = ["胸围全围(cm)"]
|
bust_headers = ["胸围全围(cm)"]
|
||||||
|
if sa_size_headers is None:
|
||||||
|
sa_size_headers = []
|
||||||
for j, w in enumerate(warehouses, start=1):
|
for j, w in enumerate(warehouses, start=1):
|
||||||
row[f"发货仓{j}"] = w
|
row[f"发货仓{j}"] = w
|
||||||
row[f"发货仓{j}库存"] = 200
|
row[f"发货仓{j}库存"] = 200
|
||||||
@@ -276,6 +296,13 @@ def _build_sku_row(spu_code: str, sc: str, sk: Dict[str, Any], size: str, color:
|
|||||||
for h in price_headers:
|
for h in price_headers:
|
||||||
row[h] = v
|
row[h] = v
|
||||||
continue
|
continue
|
||||||
|
if dbk == "size":
|
||||||
|
if v in (None, ""):
|
||||||
|
continue
|
||||||
|
row[header] = v
|
||||||
|
for h in sa_size_headers:
|
||||||
|
row[h] = v
|
||||||
|
continue
|
||||||
if v not in (None, ""):
|
if v not in (None, ""):
|
||||||
row[header] = v
|
row[header] = v
|
||||||
return row
|
return row
|
||||||
@@ -416,6 +443,8 @@ def _insert_product_block(
|
|||||||
composite_urls: Optional[List[Dict[str, Any]]] = None,
|
composite_urls: Optional[List[Dict[str, Any]]] = None,
|
||||||
seed_shot_urls: Optional[List[str]] = None,
|
seed_shot_urls: Optional[List[str]] = None,
|
||||||
bust_headers: Optional[List[str]] = None,
|
bust_headers: Optional[List[str]] = None,
|
||||||
|
fabric_headers: Optional[List[str]] = None,
|
||||||
|
sa_size_headers: Optional[List[str]] = None,
|
||||||
) -> List[int]:
|
) -> List[int]:
|
||||||
"""在已打开的 router 中插入一个产品的 SPU+SKU 块并填充设计字段,返回本块行号。
|
"""在已打开的 router 中插入一个产品的 SPU+SKU 块并填充设计字段,返回本块行号。
|
||||||
|
|
||||||
@@ -445,7 +474,7 @@ def _insert_product_block(
|
|||||||
# 单 SPU 多色:1 个 SPU 行(无色值,SPU 级信息由 _fill_design_fields 填充)
|
# 单 SPU 多色:1 个 SPU 行(无色值,SPU 级信息由 _fill_design_fields 填充)
|
||||||
# + 全部颜色尺码 SKU 行(色值在 SKU 行区分)
|
# + 全部颜色尺码 SKU 行(色值在 SKU 行区分)
|
||||||
block_rows.append(router.insert(
|
block_rows.append(router.insert(
|
||||||
_build_spu_row(spu, spu_code, origin_province),
|
_build_spu_row(spu, spu_code, origin_province, fabric_headers=fabric_headers),
|
||||||
match="exact",
|
match="exact",
|
||||||
))
|
))
|
||||||
for ci, (sc, skus) in enumerate(skus_by_color):
|
for ci, (sc, skus) in enumerate(skus_by_color):
|
||||||
@@ -458,7 +487,8 @@ def _insert_product_block(
|
|||||||
block_rows.append(router.insert(
|
block_rows.append(router.insert(
|
||||||
_build_sku_row(spu_code, sc, sk, size, color, warehouses,
|
_build_sku_row(spu_code, sc, sk, size, color, warehouses,
|
||||||
markup_percent=markup_percent, multi=True,
|
markup_percent=markup_percent, multi=True,
|
||||||
bust_headers=bust_headers, price_headers=price_headers),
|
bust_headers=bust_headers, price_headers=price_headers,
|
||||||
|
sa_size_headers=sa_size_headers),
|
||||||
spu_code=spu_code, match="exact",
|
spu_code=spu_code, match="exact",
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -474,7 +504,7 @@ def _insert_product_block(
|
|||||||
else:
|
else:
|
||||||
# 单 SPU + 多颜色变体:1 个 SPU 行(无色值)+ 所有颜色所有尺码 SKU 行(色值区分)
|
# 单 SPU + 多颜色变体:1 个 SPU 行(无色值)+ 所有颜色所有尺码 SKU 行(色值区分)
|
||||||
block_rows.append(router.insert(
|
block_rows.append(router.insert(
|
||||||
_build_spu_row(spu, spu_code, origin_province), match="exact"))
|
_build_spu_row(spu, spu_code, origin_province, fabric_headers=fabric_headers), match="exact"))
|
||||||
multi_variant = len(skus_by_color) > 1
|
multi_variant = len(skus_by_color) > 1
|
||||||
for ci, (sc, skus) in enumerate(skus_by_color):
|
for ci, (sc, skus) in enumerate(skus_by_color):
|
||||||
first = skus[0]
|
first = skus[0]
|
||||||
@@ -484,7 +514,8 @@ def _insert_product_block(
|
|||||||
block_rows.append(router.insert(
|
block_rows.append(router.insert(
|
||||||
_build_sku_row(spu_code, sc, sk, size, color, warehouses,
|
_build_sku_row(spu_code, sc, sk, size, color, warehouses,
|
||||||
markup_percent=markup_percent, multi=multi_variant,
|
markup_percent=markup_percent, multi=multi_variant,
|
||||||
bust_headers=bust_headers, price_headers=price_headers),
|
bust_headers=bust_headers, price_headers=price_headers,
|
||||||
|
sa_size_headers=sa_size_headers),
|
||||||
))
|
))
|
||||||
sku_imgs = [x for x in (images[1:] + images[:1]) if x][:4] if (ci == 0 and images) else []
|
sku_imgs = [x for x in (images[1:] + images[:1]) if x][:4] if (ci == 0 and images) else []
|
||||||
_fill_sku_carousel(router, spu_code, color, color_col, first, sku_imgs)
|
_fill_sku_carousel(router, spu_code, color, color_col, first, sku_imgs)
|
||||||
@@ -551,6 +582,8 @@ def export_product(
|
|||||||
origin_province, warehouses = _read_meta(router)
|
origin_province, warehouses = _read_meta(router)
|
||||||
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
|
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
|
||||||
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
|
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
|
||||||
|
fabric_headers = _find_fabric_headers(router) # 面料弹性列(SPU商品属性-面料弹性…检测到才填)
|
||||||
|
sa_size_headers = _find_sa_size_headers(router) # 沙特阿拉伯码列(尺码表-沙特阿拉伯码…检测到才填)
|
||||||
_insert_product_block(router, db_path, spu_code, sku_code,
|
_insert_product_block(router, db_path, spu_code, sku_code,
|
||||||
origin_province, warehouses, price_headers,
|
origin_province, warehouses, price_headers,
|
||||||
markup_percent=markup_percent, images=images,
|
markup_percent=markup_percent, images=images,
|
||||||
@@ -559,7 +592,9 @@ def export_product(
|
|||||||
ja_title=ja_title, es_title=es_title,
|
ja_title=ja_title, es_title=es_title,
|
||||||
composite_urls=composite_urls,
|
composite_urls=composite_urls,
|
||||||
seed_shot_urls=seed_shot_urls,
|
seed_shot_urls=seed_shot_urls,
|
||||||
bust_headers=bust_headers)
|
bust_headers=bust_headers,
|
||||||
|
fabric_headers=fabric_headers,
|
||||||
|
sa_size_headers=sa_size_headers)
|
||||||
out = router.save(out_path)
|
out = router.save(out_path)
|
||||||
return Path(out)
|
return Path(out)
|
||||||
finally:
|
finally:
|
||||||
@@ -591,6 +626,8 @@ def export_products(
|
|||||||
origin_province, warehouses = _read_meta(router)
|
origin_province, warehouses = _read_meta(router)
|
||||||
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
|
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
|
||||||
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
|
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
|
||||||
|
fabric_headers = _find_fabric_headers(router) # 面料弹性列(SPU商品属性-面料弹性…检测到才填)
|
||||||
|
sa_size_headers = _find_sa_size_headers(router) # 沙特阿拉伯码列(尺码表-沙特阿拉伯码…检测到才填)
|
||||||
for r in products:
|
for r in products:
|
||||||
try:
|
try:
|
||||||
_insert_product_block(
|
_insert_product_block(
|
||||||
@@ -608,6 +645,8 @@ def export_products(
|
|||||||
composite_urls=r.get("composite_urls"),
|
composite_urls=r.get("composite_urls"),
|
||||||
seed_shot_urls=r.get("seed_shot_urls"),
|
seed_shot_urls=r.get("seed_shot_urls"),
|
||||||
bust_headers=bust_headers,
|
bust_headers=bust_headers,
|
||||||
|
fabric_headers=fabric_headers,
|
||||||
|
sa_size_headers=sa_size_headers,
|
||||||
)
|
)
|
||||||
except Exception as e: # noqa: BLE001
|
except Exception as e: # noqa: BLE001
|
||||||
print(f"[template_export] 产品 {r.get('spu_code')} 写入失败,跳过: {e}")
|
print(f"[template_export] 产品 {r.get('spu_code')} 写入失败,跳过: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user