v110-v112 自定义模式完善 + 模板导出增强 + 多模态兼容优化

- 自定义模式:分析模型输出 delta 唯一改动指令,生图模板 custom_image_prompt.md({delta} 占位符),不再使用负向提示词;generate_design 按 custom_mode 分支,Pinterest 模式保留原创化指令,两模式互不影响
- 多模态分析 response_format 三级回退(json_schema → json_object → none),兼容 DeepSeek
- 模板导出:details 扩展列(细节1/2/3)、target_audience 扩展列(适用人群1)、固定值风格1=休闲/风格2=运动
- 童装特征库更新 + 标题模板外部化 + 图源映射增强
This commit is contained in:
2026-09-03 18:28:39 +08:00
parent d68cc3b9e3
commit 5ab5cf6586
40 changed files with 1967 additions and 149 deletions
+22 -6
View File
@@ -98,13 +98,17 @@ START → pinterest_init → pinterest_custom_load → pinterest_analyze
保证用户每次重新上传/选择的所有有效图片都会被重新多模态分析;分析批上限按「选品清单总数」截断,
避免对多余图片浪费配额。`pinterest_custom_route` 只在 wait / analyze / done 间流转,从不 search。
**自定义模式提示词独立**
**自定义模式提示词独立(与 Pinterest 模式完全隔离,互不影响)**
- **多模态分析提示词**`prompts/custom_analyze_system.md` / `prompts/custom_analyze_user.md`
(与 Pinterest 模式的 `pinterest_analyze_*` 完全独立,互不影响);
- **生图提示词模板**`config.yaml``custom.prompt_pieces``image_prompt_template` /
`negative_prompt`)可编辑——以参考图片的整体氛围/主题/时代/风格类型为灵感,**明确禁止复制或模仿**
参考图片的印刷 artwork、角色、布局、文字及服装/模特等元素,仅基于观察到的主题/技术/调色板/布局
生成新设计;留空回退代码内置 `CUSTOM_IMAGE_PROMPT_TEMPLATE` / `CUSTOM_NEGATIVE_PROMPT`
(与 Pinterest 模式的 `pinterest_analyze_*` 完全独立)——分析模型只输出**唯一改动指令**
`{"delta": "<一句话>"}`(同主题换姿态 / 换主体 / 换文字 / 改构图,优先保最小改动,绝不复制原图);
- **生图提示词模板**`prompts/custom_image_prompt.md`(优先,可编辑)→
`config.custom.prompt_pieces.image_prompt_template` → 代码内置默认。模板含 `{delta}` 占位符,
运行时用分析产出的 delta 替换——以参考图印花为基底,只应用 delta 这一处改动,其余
(布局 / 元素位置 / 配色 / 阴影 / 纹理 / 线条 / 文字排版)保持不变;
- **不再使用负向提示词**:约束已全部内置进生图模板,`composite_negative` 置空;
- **模式隔离**`generate_design``custom_mode` 分支——Pinterest 模式保留「原创化指令」
(防止复制原图),自定义模式不追加(模板已内置防复制约束),两个模式的生图行为互不影响。
## 目录结构
@@ -127,6 +131,7 @@ pod_trend_agent/
├── prompts/pinterest_analyze_user.md # 多模态分析用户提示词,可配置
├── prompts/custom_analyze_system.md # 自定义模式多模态分析系统提示词(独立于 Pinterest)
├── prompts/custom_analyze_user.md # 自定义模式多模态分析用户提示词(独立于 Pinterest)
├── prompts/custom_image_prompt.md # ★ 自定义模式生图提示词模板({delta} 占位符,可编辑)
├── prompts/title_prompt_1.md # ★ 标题生成提示词(英语市场 US/GB/AU/MX
├── prompts/title_prompt_2.md # ★ 标题生成提示词(日本市场 JP)
├── prompts/title_prompt_3.md # ★ 标题生成提示词(西班牙市场 ES)
@@ -209,6 +214,12 @@ Pinterest 参考模式与自定义图片模式的多模态分析提示词已**
`image_prompt` 二段式结构 / `suitable_for_print` 判定标准等约束(多模态分析不再产出
`negative_prompt`,生图负向约束由固定 `print_suffix` 承担)。
**多模态分析 response_format 三级回退**`openai_compat_backend.analyze_pinterest_images`):
1. `json_schema`OpenAI 原生 strict 输出)→ 2. `json_object`(部分兼容厂商支持,但要求提示词含
"json",自动追加 "Return your answer as a JSON object.",兼容 DeepSeek)→ 3. 无 `response_format`
(依赖 `_extract_json` 兜底解析)。DeepSeek 等厂商直接 json_schema 会 400
"This response_format type is unavailable now",三级回退保证分析不中断。
## 标题生成提示词(可配置)
标题生成提示词已**外部化为独立文件**,无需改代码即可调优:
@@ -305,6 +316,11 @@ config.yaml。模板内 `{year}`/`{month}`/`{season}` 占位符运行时自动
- **SPU 商品属性字段映射**`placket_type→门襟类型``sleeve_type→袖型``sleeve_length_type→袖长`
`breast_pad→胸垫``layout→版型``silhouette→廓形``length_type→长度``belt→腰带`
`occasion→场合``scene→场景``hemline_shape→下摆形状`,各字段独立不覆盖;
- **details 扩展列**:精确匹配「细节1 / 细节2 / 细节3」列(`_find_details_headers`),匹配到即全部填
`spu.details` 值(同一详情写多个扩展列);
- **target_audience 扩展列**:精确匹配「适用人群1」列(`_find_target_audience_headers`),匹配到即填
`target_audience` 映射后的值(童装按性别映射,见下);
- **固定值风格列**:匹配到「风格1」列统一填「休闲」、匹配到「风格2」列统一填「运动」;
- **季节映射(女装)**`spu.season` 的「四季」→「ALL/全球/所有」(`config.yaml``season_map.female` 可配置);
- **印花图案映射(女装)**`spu.pattern` 为「印花」时 →「卡通」(`pattern_map` 可配置);
- **适用人群映射(童装)**`spu.target_audience` 按童装性别映射(`target_audience_map.kids` 可配置),
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""聚焦验证:自定义模式分析是否产出 delta + prompt_node 是否走自定义分支。"""
import sys
import time
from pathlib import Path
ROOT = Path(r"C:\Users\Admin\Desktop\test模版\design_agent\pod_trend_agent")
sys.path.insert(0, str(ROOT))
import yaml
cfg = yaml.safe_load((ROOT / "config.yaml").read_text(encoding="utf-8")) or {}
cfg["pinterest"]["mode"] = "custom"
cfg["pinterest"]["custom_image_dir"] = r"C:\Users\Admin\Desktop\新建文件夹\新建文件夹"
cfg["product"]["spu_tasks"] = [{"spu": "SATU001", "skus": "SATU001-WH01"}]
cfg["product"]["template_path"] = r"C:\Users\Admin\Desktop\SatVoy Shop--沙特女士白色纯棉T模板.xlsx"
from graph.loader import build_country_config
from graph.nodes.pinterest_custom_load_node import pinterest_custom_load_node
from graph.nodes.pinterest_analyze_node import pinterest_analyze_node
out_root = ROOT / "output_test_analyze"
out_root.mkdir(parents=True, exist_ok=True)
cc = build_country_config(cfg, "SA", ROOT)
ts = time.strftime("%Y%m%d_%H%M%S")
output_dir = out_root / "output" / "SA" / ts
output_dir.mkdir(parents=True, exist_ok=True)
state = {
"country": "SA",
"config": cfg,
"country_config": cc,
"prompts_dir": str(ROOT / "prompts" / "SA"),
"cache_dir": str(out_root / "output" / "SA"),
"output_dir": str(output_dir),
"briefs": [],
"composite": [],
"designs": [],
"errors": [],
"stats": {},
"task_timestamp": ts,
"oss_seq": 0,
"pinterest_target": 1,
"pinterest_rounds": 0,
"pinterest_attempted": [],
"pinterest_images": {},
"pinterest_briefs": [],
"pinterest_login": {},
"pinterest_pipeline": None,
}
print("=== 1) custom_load ===")
r1 = pinterest_custom_load_node(state)
print("custom_mode =", r1.get("custom_mode"), "| loaded =", (r1.get("stats") or {}).get("pinterest_custom", {}).get("loaded"))
state.update(r1)
print("\n=== 2) analyze ===")
r2 = pinterest_analyze_node(state)
briefs = r2.get("briefs") or []
print(f"简报 {len(briefs)} 条:")
for b in briefs:
print(f" topic={b.get('topic')}")
print(f" brief_id={b.get('brief_id')}")
print(f" delta={str(b.get('delta'))[:100]}")
print(f" image_prompt前80={str(b.get('image_prompt'))[:80]}")
print(f" ref_images={b.get('ref_images')}")
print(f" composite_negative={repr(b.get('composite_negative'))}")
print(f" 走自定义分支(含delta替换)={('{delta}' not in str(b.get('image_prompt'))) and bool(b.get('delta'))}")
print("\n=== 结论 ===")
if briefs:
b = briefs[0]
has_delta = bool(str(b.get("delta") or "").strip())
used_custom_tpl = "custom" in str(b.get("image_prompt") or "").lower() or has_delta
print(f"delta 产出: {has_delta}")
print(f"image_prompt 含 delta 替换: {has_delta and '{delta}' not in str(b.get('image_prompt'))}")
print(f"composite_negative 为空: {not str(b.get('composite_negative') or '')}")
else:
print("无简报产出(分析失败)")
+64
View File
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""自定义模式实测:验证参考图是否上传 /images/edits。禁用 OSS/种草图减少副作用。"""
import sys
import time
from pathlib import Path
ROOT = Path(r"C:\Users\Admin\Desktop\test模版\design_agent\pod_trend_agent")
sys.path.insert(0, str(ROOT))
import yaml
from graph.agent import run_pinterest_ref
cfg = yaml.safe_load((ROOT / "config.yaml").read_text(encoding="utf-8")) or {}
# —— 自定义模式配置 ——
cfg["pinterest"]["mode"] = "custom"
cfg["pinterest"]["custom_image_dir"] = r"C:\Users\Admin\Desktop\新建文件夹\新建文件夹"
cfg["pinterest"]["provider"] = "openai"
cfg["product"]["spu_tasks"] = [{"spu": "SATU001", "skus": "SATU001-WH01"}]
cfg["product"]["template_path"] = r"C:\Users\Admin\Desktop\SatVoy Shop--沙特女士白色纯棉T模板.xlsx"
cfg["product"]["backend"] = "openai"
cfg["compose"]["backend"] = "openai"
cfg["oss"]["enabled"] = False
cfg["seed_shot"]["enabled"] = False
# 独立输出目录,避免污染现有 output
out_root = ROOT / "output_test_custom"
out_root.mkdir(parents=True, exist_ok=True)
print("=" * 70)
print("开始自定义模式实测(SA / SATU001-WH01 / 参考图上传验证)")
print("=" * 70)
t0 = time.time()
try:
res = run_pinterest_ref(
"SA", cfg, ROOT, output_root=out_root,
custom_image_dir=r"C:\Users\Admin\Desktop\新建文件夹\新建文件夹",
)
except Exception as e:
import traceback
traceback.print_exc()
sys.exit(1)
print("\n" + "=" * 70)
print(f"完成,耗时 {time.time() - t0:.0f}s")
print("=" * 70)
errs = res.get("errors") or []
print(f"错误 {len(errs)} 条:")
for e in errs:
print(f" - [{e.get('node')}] {e.get('type')}: {e.get('message')}")
briefs = res.get("briefs") or []
print(f"简报 {len(briefs)} 条:")
for b in briefs:
print(f" topic={b.get('topic')} | brief_id={b.get('brief_id')} | "
f"ref_images={b.get('ref_images')} | delta={str(b.get('delta'))[:60]}")
designs = res.get("designs") or []
print(f"设计稿 {len(designs)} 个:")
for d in designs:
print(f" topic={d.get('topic')} | path={d.get('path')}")
products = res.get("product") or []
print(f"产品 {len(products)} 个:")
for p in products:
print(f" img_code={p.get('img_code')} | composite={p.get('composite_path')}")
+10 -11
View File
@@ -86,8 +86,8 @@ pinterest:
# 自定义模式(pinterest.mode=custom)专属配置:多模态提示词与生图固定段独立于 Pinterest 参考模式。
custom:
# 自定义模式生图提示词的固定段(可编辑;留空=回退代码内置默认)。
# 自定义模式生图正向提示词 = image_prompt_template{image_prompt} 替换为分析模型产出的新设计描述);
# 负向 = negative_promptcompose 生图时作为负向参数传给图像后端)。
# 自定义模式生图正向提示词 = image_prompt_template{image_prompt} 替换为分析模型产出的新设计描述
# {delta} 替换为分析模型产出的唯一改动指令);不再使用负向提示词(约束已内置进模板)。
# 凡 review 简报额外追加 review_rebrand_hint。
prompt_pieces:
image_prompt_template: >-
@@ -100,10 +100,6 @@ custom:
on the print area, identify its subject, technique, palette and layout,
and base the new design on THOSE observed traits. Ignore the model,
background and photo quality. Then: {image_prompt}
negative_prompt: >-
copy of reference artwork, lookalike of the bestseller print, characters,
mascots, likenesses, logos, trademarks, watermark, photorealistic shirt,
apparel, product mockup, model, garment, hanger, busy background
print_suffix: >-
standalone pure print design on a pure white background, the print artwork is SMALL and
CENTERED with clearly larger white margins around it, print area between about 15x18 cm and
@@ -179,11 +175,14 @@ season_map:
female:
四季: ALL/全球/所有
# 印花图案值映射表(女装模板专用)key=db pattern 值,value=模板下拉框精确选项。
# 男装模板保留 db 原值(不查此表,不在此配置)。缺失/留空回退内置默认(印花→卡通)
# 印花图案值映射表:key=db pattern 值,value=模板下拉框精确选项。
# female(女装)与 girl_kids(女童)模板遇到「印花」→「卡通」;男装/男童模板保留 db 原值
# 缺失/留空回退内置默认(印花→卡通)。
pattern_map:
female:
印花: 卡通
girl_kids:
印花: 卡通
# 适用人群值映射表(童装模板专用):key=db target_audience 值,value=模板下拉框精确选项。
# 女童模板遇到「男童」→「女童」;男童模板遇到「女童」→「男童」(内置默认,可在此覆盖/新增)。
@@ -242,9 +241,9 @@ query_noise:
llm_screen:
enabled: true
provider: openai # openai(真 LLM,需 key/ mock(无 key 启发式兜底)
api_key: "ark-607f7194-f253-4c55-a421-237317d7683e-2bf08" # 留空则自动读取环境变量 LLM_API_KEY / OPENAI_API_KEY(推荐,避免密钥入库)
base_url: "https://ark.cn-beijing.volces.com/api/v3" # 自定义网关地址,也可用环境变量 LLM_BASE_URL 覆盖
model: "doubao-seed-2-0-lite-260215" # 自定义模型名(任意 OpenAI 兼容模型)
api_key: "sk-93d4c647a8f44d6a95ff96e32f15776b" # 留空则自动读取环境变量 LLM_API_KEY / OPENAI_API_KEY(推荐,避免密钥入库)
base_url: "https://api.deepseek.com" # 自定义网关地址,也可用环境变量 LLM_BASE_URL 覆盖
model: "deepseek-v4-flash-vision-exp" # 自定义模型名(任意 OpenAI 兼容模型)
temperature: 0.6
max_topics_per_call: 12
min_score: 0.0
+16 -10
View File
@@ -45,54 +45,60 @@ kids_features:
# ================= 场景特征(完整约束版,男童/女童) =================
# ================= 场景特征(完整约束版,男童/女童) =================
# ================= 场景特征(安全过滤友好版,男童/女童) =================
# 改写说明:删除"性感化/成人化/裸"等反向触发词;"儿童"降频改为"孩子/小模特"
# 删除申辩式语句;姿态约束全部改为正向描述。约束效果与原版一致。
# ================= 场景特征(安全过滤友好版,男童/女童) =================
# 改写说明:删除"性感化/成人化/裸"等反向触发词;"儿童"降频改为"孩子/小模特"
# 删除申辩式语句;姿态约束全部改为正向描述。约束效果与原版一致。
scene_features:
boy_kids:
# ---------- 棚拍主图区 ----------
- name: "纯白背景正面站姿"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止画面下沿出现裸腿,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站姿自然挺拔,双手自然垂放于身体两侧或轻衣服下摆两侧,绝对不可抬手遮挡印花。【稳定性约束】儿童站在纯白无缝背景纸前,表情自然开心,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的质感。采用电商标准高亮柔光,光线均匀无硬阴影,衣服受光均匀色彩还原准确。画面比例为3:4。商业白底图摄影,50mm镜头,f/2.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,五官自然不刻意强调,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止画面下沿出现光脚特写,禁止改变抽绳、帽绳、下摆罗纹、袖口罗纹等服装结构部件的原始颜色,禁止将印花图案或印花颜色延伸绘制到抽绳、帽子、罗纹等结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】站姿自然挺拔,肩部放松,双手自然垂放于身体两侧或轻轻抓住衣服下摆两侧,不可抬手遮挡胸口印花。【稳定性约束】小模特站在纯白无缝背景纸前,表情自然开心,整体呈现[服装风格]的调性,完整展示童装正面印花图案与[材质]面料的质感。采用电商标准高亮柔光,光线均匀分布无硬阴影。画面比例为3:4。商业白底图摄影,50mm镜头,f/2.8光圈,8k分辨率。
- name: "奶油黄彩底棚拍"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站姿自然,单手轻握一个纯色小皮球收在腰侧画面下沿,球体不遮挡衣服,绝对不可抬手遮挡印花。【稳定性约束】儿童站在低饱和奶油黄色无缝背景纸前,笑容灿烂自然,整体呈现[服装风格]的调性,突出印花的童趣感与[材质]面料质感。采用高亮柔光箱均匀打光,背景与服装色彩柔和呼应不冲突,衣服受光均匀。画面比例为3:4。糖果色商业棚拍,50mm镜头,f/2.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】站姿自然,单手轻握一个纯色小皮球收在腰侧画面下沿,球体不遮挡衣服,不可抬手遮挡印花。【稳定性约束】小模特站在低饱和奶油黄色无缝背景纸前,笑容灿烂自然,整体呈现[服装风格]的调性,突出童装正面印花的童趣感与[材质]面料质感。采用高亮柔光箱均匀打光,背景与服装色彩柔和呼应不冲突。画面比例为3:4。糖果色商业棚拍,50mm镜头,f/2.8光圈,8k分辨率。
# ---------- 户外玩耍区 ----------
- name: "公园游乐场站姿"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与运动鞋。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止游乐设施伸入前景遮挡衣服,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站在阳光明媚的公园草坪的彩色滑梯旁,双手自然垂放或轻扶滑梯扶手位于画面下沿,笑容灿烂,绝对不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用上午明亮的漫射自然光,衣服受光均匀色彩准确,背景为高度虚化的彩色滑梯与游乐设施,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的质感,画面充满童趣活力。画面比例为3:4。清透童趣摄影,50mm镜头,f/1.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与运动鞋。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止游乐设施伸入前景遮挡衣服,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特站在阳光明媚的公园草坪的彩色滑梯旁,双手自然垂放或轻扶滑梯扶手位于画面下沿,笑容灿烂,不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用上午明亮的漫射自然光,衣服受光均匀色彩准确,背景为高度虚化的彩色滑梯与游乐设施,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的质感,画面充满童趣活力。画面比例为3:4。清透童趣摄影,50mm镜头,f/1.8光圈,8k分辨率。
- name: "海边沙滩站姿"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与凉鞋。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止沙子沾染弄脏衣服影响印花清晰度,禁止海水打湿变色衣服,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站在细软的沙滩上,双手自然垂放于身体两侧,头微转向镜头笑容明朗,绝对不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用正午强烈的顺光,衣服过曝得到良好控制色彩鲜明通透,背景为高度虚化的蔚蓝海水与远处海平线,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈质感。画面比例为3:4。夏日清透色彩,高饱和度,50mm镜头,f/2.0光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与凉鞋。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止沙子沾染弄脏衣服影响印花清晰度,禁止海水打湿变色衣服,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特站在细软的沙滩上,双手自然垂放于身体两侧,头微转向镜头笑容明朗,不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用正午强烈的顺光,衣服过曝得到良好控制色彩鲜明通透,背景为高度虚化的蔚蓝海水与远处海平线,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈质感。画面比例为3:4。夏日清透色彩,高饱和度,50mm镜头,f/2.0光圈,8k分辨率。
# ---------- 居家生活区 ----------
- name: "儿童房积木桌玩耍"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色长裤。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止玩具与桌沿遮挡衣服主体,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童坐在明亮的儿童房积木桌旁,低头搭积木后抬头看向镜头露出笑容,双手举起的积木收在桌面高度的画面下沿不遮挡衣服,绝对不可将玩具举至胸口。【稳定性约束】背景必须保持干净明亮。采用明亮室内自然窗光与暖色灯带结合,衣服受光均匀,背景为高度虚化的温馨儿童房与彩色积木,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的舒适质感。画面比例为3:4。日系胶片质感,柯达Portra 400色彩预设,50mm镜头,f/1.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色长裤。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止玩具与桌沿遮挡衣服主体,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特坐在明亮的儿童房积木桌旁,低头搭积木后抬头看向镜头露出笑容,双手举起的积木收在桌面高度的画面下沿不遮挡衣服,不可将玩具举至胸口。【稳定性约束】背景必须保持干净明亮。采用明亮室内自然窗光与暖色灯带结合,衣服受光均匀,背景为高度虚化的温馨儿童房与彩色积木,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的舒适质感。画面比例为3:4。日系胶片质感,柯达Portra 400色彩预设,50mm镜头,f/1.8光圈,8k分辨率。
girl_kids:
# ---------- 棚拍主图区 ----------
- name: "纯白背景正面站姿"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止画面下沿出现裸腿,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站姿自然挺拔,双手自然垂放于身体两侧或轻衣服下摆两侧,绝对不可抬手遮挡印花。【稳定性约束】儿童站在纯白无缝背景纸前,表情自然甜美,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的质感。采用电商标准高亮柔光,光线均匀无硬阴影,衣服受光均匀色彩还原准确。画面比例为3:4。商业白底图摄影,50mm镜头,f/2.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,五官自然不刻意强调,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止画面下沿出现光脚特写,禁止改变抽绳、帽绳、下摆罗纹、袖口罗纹等服装结构部件的原始颜色,禁止将印花图案或印花颜色延伸绘制到抽绳、帽子、罗纹等结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】站姿自然挺拔,肩部放松,双手自然垂放于身体两侧或轻轻抓住衣服下摆两侧,不可抬手遮挡胸口印花。【稳定性约束】小模特站在纯白无缝背景纸前,表情自然甜美,整体呈现[服装风格]的调性,完整展示童装正面印花图案与[材质]面料的质感。采用电商标准高亮柔光,光线均匀分布无硬阴影。画面比例为3:4。商业白底图摄影,50mm镜头,f/2.8光圈,8k分辨率。
- name: "奶油粉彩底棚拍"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站姿自然,单手轻轻抱一只浅色小毛绒玩具收在腰腹高度的画面下沿,玩具不遮挡衣服,绝对不可抬手遮挡印花。【稳定性约束】儿童站在低饱和奶油粉色无缝背景纸前,笑容甜美自然,整体呈现[服装风格]的调性,突出印花的童趣感与[材质]面料质感。采用高亮柔光箱均匀打光,背景与服装色彩柔和呼应不冲突,衣服受光均匀。画面比例为3:4。糖果色商业棚拍,50mm镜头,f/2.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特下半身必须穿着基础色长裤或短裤并搭配鞋子,下装即使位于画面下沿之外也必须真实穿着。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】站姿自然,单手轻轻抱一只浅色小毛绒玩具收在腰腹高度的画面下沿,玩具不遮挡衣服,不可抬手遮挡印花。【稳定性约束】小模特站在低饱和奶油粉色无缝背景纸前,笑容甜美自然,整体呈现[服装风格]的调性,突出童装正面印花的童趣感与[材质]面料质感。采用高亮柔光箱均匀打光,背景与服装色彩柔和呼应不冲突。画面比例为3:4。糖果色商业棚拍,50mm镜头,f/2.8光圈,8k分辨率。
# ---------- 户外玩耍区 ----------
- name: "公园草坪花朵场景"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与凉鞋。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止花朵枝叶伸入前景遮挡衣服,禁止头发垂落遮挡印花区域,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童站在阳光明媚的公园草坪上,身旁有秋千虚化背景,双手自然垂放,头微歪露出甜美的笑容,绝对不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用上午明亮的漫射自然光,衣服受光均匀色彩准确,背景为大面积虚化的绿色草坪与五彩花朵光斑,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈质感,画面梦幻通透。画面比例为3:4。清透氧气感色彩,85mm镜头,f/1.8光圈,清新高调摄影,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色短裤与凉鞋。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止花朵枝叶伸入前景遮挡衣服,禁止头发垂落遮挡印花区域,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特站在阳光明媚的公园草坪上,身旁有秋千虚化背景,双手自然垂放,头微歪露出甜美的笑容,不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用上午明亮的漫射自然光,衣服受光均匀色彩准确,背景为大面积虚化的绿色草坪与五彩花朵光斑,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈质感,画面梦幻通透。画面比例为3:4。清透氧气感色彩,85mm镜头,f/1.8光圈,清新高调摄影,8k分辨率。
- name: "泡泡机欢乐场景"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色下装与鞋子。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止泡泡大面积覆盖衣服表面影响印花清晰度,泡泡只点缀衣服周围的空气区域,禁止头发甩动遮挡印花区域,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童在户外追逐飘浮的肥皂泡,双手自然张开于身体两侧小幅挥舞,表情开心大笑,绝对不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用下午明亮的自然光,衣服受光均匀,背景为高度虚化的公园草坪与漂浮的七彩泡泡,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈透气质感,画面梦幻通透。画面比例为3:4。清透童趣摄影,50mm镜头,f/1.8光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色下装与鞋子。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止泡泡大面积覆盖衣服表面影响印花清晰度,泡泡只点缀衣服周围的空气区域,禁止头发甩动遮挡印花区域,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特在户外草坪追逐飘浮的肥皂泡,双手自然张开于身体两侧小幅挥舞,表情开心大笑,不可抬手遮挡印花。【稳定性约束】背景必须保持干净明亮。采用下午明亮的自然光,衣服受光均匀,背景为高度虚化的公园草坪与漂浮的七彩泡泡,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的轻盈透气质感,画面梦幻通透。画面比例为3:4。清透童趣摄影,50mm镜头,f/1.8光圈,8k分辨率。
# ---------- 居家生活区 ----------
- name: "飘窗边阅读场景"
prompt: |
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色长裤。【儿童保护约束】模特为一名健康的儿童模特,呈现日常自然的儿童形象,仅用于服装展示;自然素颜状态,禁止任何成人化妆造;姿态符合儿童天性,禁止成人化姿势与性感化表达。【构图约束】取景自头顶上方少许留白至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止不穿下装,禁止绘本遮挡衣服正面与印花,绘本只放在儿童腿上腰腹高度的画面下沿,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】儿童坐在明亮的飘窗边看绘本,抬头看向镜头露出甜美的笑容,双手扶绘本收在大腿高度的画面下沿,绝对不可将绘本举至胸口。【稳定性约束】背景必须保持干净明亮。室内采用柔和的窗光侧顺光保证衣服受光均匀,背景为高度虚化的温馨飘窗与窗纱光晕,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的柔软亲肤质感。画面比例为3:4。日系胶片质感,柯达Portra 400色彩预设,50mm镜头,f/2.0光圈,8k分辨率。
【最高优先级约束】请严格保留参考图1中童装的完整外观,包括其原有的颜色、印花图案、版型款式、材质纹理与缝线细节,绝对禁止对衣服本身进行任何形式的修改、重新设计、变色或改变图案。仅提取这件[商品名称],将其穿在一位[模特特征]的孩子身上,模特仅穿这一件上衣,绝对禁止在外面叠加任何外套或夹克;同时模特穿着基础色长裤。【构图约束】服装主体构图:取景范围自头顶上方少许留白起,至衣服下摆下方一掌宽处,这件[商品名称]的领口、肩线、双袖、下摆与正面印花必须100%完整入画且无遮挡;童装在画面中占比不低于55%,头部占比不超过画面高度的三分之一,画面的绝对视觉中心是胸前印花;禁止面部特写构图,禁止裁切衣服任何部位。【负面约束】禁止改变衣服颜色,禁止遮挡材质纹理,禁止遮挡或破坏印花图案完整性,禁止双手交叉抱胸,禁止手部遮挡胸口,禁止绘本遮挡衣服正面与印花,绘本只放在腿上腰腹高度的画面下沿,禁止改变抽绳、帽绳、罗纹等服装结构部件的原始颜色,禁止将印花颜色延伸绘制到结构部件上,所有结构部件必须严格保持参考图中的原始同色状态。【动作硬约束】小模特坐在明亮的飘窗边看绘本,抬头看向镜头露出甜美的笑容,双手扶绘本收在大腿高度的画面下沿,不可将绘本举至胸口。【稳定性约束】背景必须保持干净明亮。室内采用柔和的窗光侧顺光保证衣服受光均匀,背景为高度虚化的温馨飘窗与窗纱光晕,整体呈现[服装风格]的调性,完整展示正面印花图案与[材质]面料的柔软亲肤质感。画面比例为3:4。日系胶片质感,柯达Portra 400色彩预设,50mm镜头,f/2.0光圈,8k分辨率。
# ================= 服装风格(男童/女童) =================
style_features:
+95
View File
@@ -0,0 +1,95 @@
# 2026-09-03 工作日志
## 新增:Inkreach 商城款号爬取脚本 `crawl_style_codes.py`
- 需求:按【国家 + 品类】爬取款号(如 DG505 / JPTM001 / PLTK005),去重输出。
- 接口探查结论(重要,见 MEMORY.md 已固化):后端走 **mapi.sdspod.com**`api.sdspod.com` 在本机 DNS 解析不到,勿用)。
- 端点:`/products/pageOptionGroup`(国家列表 14 国)、`/products/page`(商品分页)、`/categories`(品类树 26 顶层)。
- 坑:`overseasArea`(国家)与 `id`(品类)筛选参数**实测不生效**(各国 totalCount 恒为 536)→ 改为全量分页拉取 + 本地聚合。
- 商品 `categories` 标签顺序固定:`[国家/工厂标签, 品类, "款号 品名"]`(如 `美国工厂直发 | 男士T恤 | DG505 180g男士纯棉长袖`),这是国家/品类/款号最可靠的来源。
- 款号提取三个坑及修复:
1. `SFB`(墨西哥运动短裤)是**纯字母无数字**款号 → 改为优先用 categories 的「款号 品名」标签识别,不再强制要求含数字。
2. 「中东本地工厂直发」属沙特,但正式国名是「沙特阿拉伯」→ 加 `COUNTRY_ALIASES` 别名表(中东→SA、国内工厂→CN)。
3. 尺码串(S/M/XL/2XL/3XL)会被旧规则误当款号 → 加 `SIZE_WORDS` 黑名单(US 因此由 60 校正为 56)。
- CN(国内工厂)4 条商品 name 无国家前缀、且本身无款号 → 用 `shipmentArea` 兜底归国家,款号留空。
- 结果:在架商品 540(海外 536 + 国内 4)→ **13 国 / 65 个品类组合 / 150 条;全局去重款号 149 个**
- 产物:`style_codes_by_country.json`(按国家→品类→款号)、`style_codes_by_country.csv`country_code,country,category,style_codeutf-8-sig)。
## 数据维护(同日早些时候)
- SPU 表新增 `kids_type``kids_age` 两字段(TEXT,默认 NULL 空),`create_db.py` 已同步;SPU 现 39 字段。备份 `spu_sku.db.bak_spu_kids`
- SKU:按 `PLTK005-WH01` 复制生成 6 行 `PLTK005-BL01`6Y/7Y/8Y/9Y/10Y/11-12Y),code 与 color=黑色 覆盖,其余字段照抄;PLTK005 SKU 由 12 → 18。备份 `spu_sku.db.bak_pltk005_bl01`
- SKU `color` 规范化(单字 → 「X色」):
- 纯「黑」256 + 纯「白」149 = 405 行(备份 `spu_sku.db.bak_color_fix`)。
- 其余单字 杏68/棕8/灰136/粉23/紫32/红76/绿45/蓝30/黄3 = 421 行(备份 `spu_sku.db.bak_color_single`)。
- 未动:多字与组合色(`黑色 白色``白色 蓝色 红色 皇家蓝``黑胡椒色``象牙白``米白` 等)。
## 品类白名单过滤(同日追加)
- 需求:只保留 T恤/卫衣/长袖/背心/夹克,删除下装与家居类,**对应款号一并删除**。
- 实现:`crawl_style_codes.py` 加常量 `KEEP_CATEGORY_KEYWORDS = ("T恤","卫衣","长袖","背心","夹克")`,在按品类聚合前过滤(品类名含任一关键词才保留);**置空该常量即恢复全量输出**。
- 剔除 80 条商品,涉及品类:下装(短裤 & 长裤)20、男士短裤 16、女士短裤 14、家居配饰 10、内衣 6、家居 5、桌面用品 2、配饰 2、男士长裤 2、童装短裤 2、印花烫贴 1。
- 结果:13 国 / 10 个品类(女士T恤/女士卫衣/女士背心/男士T恤/男士T恤·GBTM011长袖T/男士卫衣/男士夹克/男士背心/童装T恤/童装卫衣)/ 116 条;**全局去重款号 149 → 115**。
- 注:此过滤只作用于爬取产物(json/csv),未改动 SQLite 数据库。
## 按爬取清单清洗数据库(删除非清单款号)
- 备份:`spu_sku.db.bak_before_style_filter`(删前状态 SPU 143 / SKU 2864)。
- 规则:以 `style_codes_by_country.json` 的 115 个款号为准,`spu.code`UPPER+TRIM)不在清单内的 SPU 及其 SKU 全部删除。
- 结果:删除 **37 个 SPU / 476 条 SKU**;剩余 **SPU 106 / SKU 2388**;孤儿 SKU = 0。
- 被删主力:US 的 DG201/202/204/205/206/210(下装)、JSD001~007(短裤)、JSA002/003/007/010(内衣·短裤)、USIU002/005/012(家居)、YSM01/02(配饰);JP 的 JPIU014/020/021(家居配饰)、JPPM011/JPSM018(下装)、JPJF005PL 的 PLHM011/PLPM014/PLSU025(下装);GB 的 GBIU009/014/015DE 的 DEPM004MX 的 SFB。
- 清单有但库里未入库的 9 个款号(本次未补,后续可从 PG 同步):BRTF004、DG505、GBHK007、GBTK012、GBTM010、GBTM011、JPPX012、JPPX013、PLTM031。
- 审计文件:`delete_non_keep_codes.sql`(含完整 115 款号 IN 清单,可复核/重放)。
## 童装T恤款号字段批量更新
- 依据:`style_codes_by_country.csv/json` 中品类含「童装T恤」的款号共 19 个,库中命中 **18 个**GBTK012 尚未入库)。
- 更新 4 个字段:`target_audience='男童'``season='ALL/全球/所有'``kids_type='上衣'``kids_age='12岁及以下'`,影响 18 行。
- 覆盖国家:US(5000B/DG012/DG014/DG301/DG503/JSA004)、PL(PLHM012/PLHM013/PLTK005/PLTK015/PLTK016)、GB(GBTK005/GBTK008)、JP(JPTK015)、MX(METN001)、SA(SATK004)、ES(ESTK005)、IT(ITTK002);清单国家与库内 country 全部一致。
- 备份:`spu_sku.db.bak_kids_tshirt`;审计 SQL`kids_tshirt_update.sql`
- 备注:GBHM002/GBHM003 的 season 早就是 'ALL/全球/所有'(历史值,非本次改动);**童装卫衣**品类(清单 5 个 / 库中 4 个)本次未改,待用户决定是否同样套用童装字段。
## 男士卫衣款号字段批量更新
- 依据:清单中品类含「男士卫衣」的款号共 21 个,库中命中 **19 个**JPPX012/JPPX013 尚未入库);国家一致性全部 OK。
- 更新 5 个字段:`details='抽绳'``collar_style='连帽'``target_audience='男士'``season='秋/冬'``sleeve_length_type='长袖'`,影响 19 行。
- 覆盖:US(DF009/DF010/JSB001/JSB002/JSB003)、PL(PLHM002/PLHM003)、GB(GBHM002/GBHM003)、JP(JPHM009/JPHM010)、KR(KRHM002/KRHM003)、ES(ESHM003/ESHM004)、MX(HM01)、CA(CAHM002)、DE(DEHM003)、SA(SAHM003)。
- 备份:`spu_sku.db.bak_men_hoodie`;审计 SQL`men_hoodie_update.sql`
- 备注:GBHM002/GBHM003 原 season='ALL/全球/所有'(历史值)已被本次覆盖为 '秋/冬';当前全表 season 分布 = 秋/冬 19 + ALL/全球/所有 18(童装T恤)+ 四季 69。
## 品类→字段批量套用模板(可复用)
- 数据源固定:`style_codes_by_country.json`(按 国家→品类→款号 组织),脚本 `crawl_style_codes.py` 可重跑刷新。
- 标准流程:备份 → 从 json 取「品类含关键词」的款号 → 与 spu.code(UPPER+TRIM) 比对(列出命中/缺失/国家是否一致)→ 事务 UPDATE → 验证明细与全表分布 → 审计 SQL 落盘。
- 已套用:童装T恤(18 个,男童/ALL全球/上衣/12岁及以下)、男士卫衣(19 个,抽绳/连帽/男士/秋·冬/长袖)。
## 新增袖长列 sleeve_length(用户选「加一列」方案)
- 背景:商城无袖长分类字段(见 MEMORY.md 三套分类体系),长袖信息只写在商品 name 里,故按分类筛不出长袖。
- 实现:`crawl_style_codes.py` 新增 `guess_sleeve_length(name, category)`,判定优先级:
1. name 含「无袖/背心/吊带」或品类含「背心」→ 无袖
2. name 含「长袖」→ 长袖
3. name 含「短袖」→ 短袖
4. 品类含「卫衣/夹克」→ 长袖(推断)
5. 其余普通 T 恤 → 短袖(默认)
- 产出:CSV 由 4 列扩为 5 列(country_code, country, category, style_code, **sleeve_length**);JSON 每个国家下新增 `sleeve_length``sleeve_source`(判定来源,便于审计修正)。
- 结果:长袖 37(name 标注 9 + 品类推断 28)、短袖 69(name 标注 6 + 默认 63)、无袖 9,合计 115 个款号全覆盖、无空值。
- 注意:63 个「默认短袖」与 28 个「卫衣/夹克推断长袖」属推断值,如需人工校正以 `sleeve_source` 列筛选即可。
## 袖长回填 SPU.sleeve_length_type(区分长袖)
- 用户需求:「先帮我把长袖给区分出来」→ 把清单 sleeve_length 回填到 SPU 表,让库里能直接筛出长袖。
- 执行:以 `style_codes_by_country.csv` 的 sleeve_length 为准回填全部 106 个款号;**更新 20 行**
- 12 个 **短袖→长袖**(藏在 T恤/卫衣/夹克里):DG501/DG502/DG503/DG504/GBHK006/JPTM006/KRTM004/PLHF029/PLHM009/PLHM010/PLJM030/PLTF026
- 8 个 **短袖→无袖**(背心款):AUTM003/JPTM008/JSA009/TBB001/VS001/VS002/VS003/VS004
- 更新后分布:**长袖 31 / 短袖 66 / 无袖 9**=106)。清单长袖 37 6 个未入库长袖款(DG505/GBTM011/GBTK012/JPPX012/JPPX013 等)= 31。
- 备份:`spu_sku.db.bak_sleeve_backfill`;审计 SQL`sleeve_backfill.sql`106 条逐款 UPDATE)。
## 长袖T恤细化(品类含 T恤 且 长袖 → season=秋/冬)
- 规则:清单 category 含「T恤」且 sleeve_length='长袖' 的款号 → SPU `sleeve_length_type='长袖'``season='秋/冬'`
- 清单共 8 款(DG502/DG503/DG505/GBTK012/GBTM011/JPTM006/KRTM004/PLTF026),库中命中 **5 个**DG505/GBTK012/GBTM011 未入库)。
- sleeve_length_type 上一步回填时已为长袖;本次主要把 season 改「秋/冬」;注意 DG503(童装T恤)season 由「ALL/全球/所有」被覆盖为「秋/冬」(用户指定)。
- 全表 season 分布更新为:秋/冬 24(男士卫衣 19 + 长袖T恤 5/ ALL/全球/所有 17 / 四季 65。
- 备份:`spu_sku.db.bak_tee_long`;审计 SQL`tee_long_update.sql`
## 童装款 SKU size_type 统一为「欧美尺码年龄码」
- 范围:清单 category 含「童装」的款号 = 童装T恤 19 + 童装卫衣 5 = 24 个;库中命中 **22 个**GBHK007/GBTK012 未入库)。
- 更新:22 款对应 **508 条 SKU**`size_type='欧美尺码年龄码'`
- 注意:① JPTK015(JP) 的 size_group 仍为「亚洲尺码」、size_type 现在是「欧美尺码年龄码」——组合不一致,待用户确认是否妥当;② 部分童装款实际 size 是 XS-XL5000B/METN001/SATK004/DG012 等)或 90~170 体码(DG014/DG503/DG504/JSA004/JPTK015),本次仅统一 size_type 文案、未改 size。
- 备份:`spu_sku.db.bak_kids_sizetype`;审计 SQL`kids_size_type_update.sql`
## 背心款 sleeve_length_type 幂等确认
- 范围:清单品类含「背心」共 9 款(男士背心 AUTM003/JPTM002/JPTM008/TBB001/VS001/VS003;女士背心 JSA009/VS002/VS004),库中全部命中。
- 结果:9 款在「袖长回填」那步已统一为「无袖」,本轮幂等 UPDATE 确认状态正确(值无实际变化)。
- 备份:`spu_sku.db.bak_vest`;审计 SQL`vest_update.sql`(可复用)。
+17
View File
@@ -23,3 +23,20 @@
- 部分品类 colors.code 为脏数据(如 'METB001-S-3XL黑色'、'5000B-黑色'、'SFB-黑色')→ 兜底补全
- 部分品类无 product_extra(如 SFB/5000B)→ 尺码走 size_chart 回退,包装/重量留空
- 家居类尺码格式不一致('30*40/76.2*101.6' vs '30*40')、均码叫法不同(Onesize vs 均码)→ 已加归一化兜底
## Inkreach 商城公开 API(供 crawl_style_codes.py 复用)
- 域名:`https://mapi.sdspod.com`**坑:前端 JS 里写的 `api.sdspod.com` 在本机 DNS 解析不到,不可用**)
- 端点(无需鉴权,带 Referer: https://www.inkreach.cn/ 即可):
- `/products/pageOptionGroup` → shipmentCountryList14 国(US/PL/GB/JP/MX/SA/CA/ES/AU/DE/KR/BR/IT/CN,含各国商品数)
- `/products/page` → 商品分页,参数 `shipmentArea=overseas|CN``page``size`size 可到 100
- `/categories` → 品类树,26 个顶层(含「美国/欧洲/日本…本地直发」这类国家维度)
- **大坑**`overseasArea`(国家)与 `id`(品类)筛选参数实测**不生效**(各国 totalCount 恒等)→ 只能全量分页拉取后本地聚合。
- 商品结构:`categories` 标签顺序固定 = `[国家/工厂标签, 品类, "款号 品名"]`,例:`美国工厂直发 | 男士T恤 | DG505 180g男士纯棉长袖`——国家/品类/款号最可靠的来源。
- 款号提取:优先 categories 的「款号 品名」标签(能覆盖纯字母款号如 `SFB`),其次 name 的 `-` 分段;必须用 `SIZE_WORDS` 排除尺码串(S/M/XL/2XL/3XL 会被误判)。
- 国家别名:中东→SA(沙特,站点文案非「沙特阿拉伯」)、国内工厂→CN;CN 商品 name 无国家前缀,用 `shipmentArea` 兜底。
- 规模参考:在架商品 540(海外 536 + 国内 4)→ 13 国、**149 个去重款号**(CN 4 条本身无款号)。
- **三套分类体系(大坑,已查证)**:
1. `/categories` 品类树:625 个节点,含「长袖T恤」等 14 个长袖节点,但**没有任何商品挂在上面**(商品 category_id 全都不在这棵树里)。
2. 商品 `category_id`:取值在 8186~8741 区间,**不属于** `/categories` 树(树节点 id 是 1295~8659 另一套),无法互相映射。
3. 商品 `categories` 标签(脚本用的就是它):`[国家/工厂, 人群款式, "款号 品名"]`,按国家+人群款式组织。
- **「长袖/短袖」不是分类,只写在商品 name 里**:全站 name 含"长袖"的商品 30 条 → 9 个款号(DG502/DG503/DG505/GBTK012/GBTM011/JPTM006/KRTM004/PLHF029/PLTF026),它们被并入 男士T恤/女士T恤/童装T恤/女士卫衣 品类。要按长袖筛选只能从 name 判断。
+350
View File
@@ -0,0 +1,350 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
按【国家 + 品类】爬取 Inkreach 商城商品款号(如 DG505、JPTM001、PLTK005),去重后输出。
数据源(均为公开只读接口):
- 国家列表 : GET {BASE}/products/pageOptionGroup -> shipmentCountryList (code / 中文名 / 商品数)
- 品类树 : GET {BASE}/categories -> 品类层级(辅助,商品自带品类标签优先)
- 商品列表 : GET {BASE}/products/page -> 分页商品(shipmentArea=overseas / CN
说明:
- 服务端 overseasArea / id 筛选参数实测不生效,因此采用「全量分页拉取 + 本地按国家/品类聚合」。
- 款号从商品 name 中提取:name 形如 "美国(不包邮)180g男士纯棉长袖-DG505-单面印花-美西洛杉矶一仓"
取被 "-" 分隔、且满足「纯大写字母+数字、长度 3~8、同时含字母和数字」的段。
- 国家由商品 categories[0](如"美国工厂直发"/"巴西本地工厂直发")与 name 前缀匹配中文国家名得到。
品类白名单:
- 只输出品类名含 T恤 / 卫衣 / 长袖 / 背心 / 夹克 的款号(见 KEEP_CATEGORY_KEYWORDS);
下装(短裤/长裤)、家居、家居配饰、内衣、配饰、印花烫贴、桌面用品等品类连同其款号一并剔除。
- 恢复全量:把 KEEP_CATEGORY_KEYWORDS 改成空元组 () 重跑即可。
输出(与脚本同目录):
- style_codes_by_country.json {国家代码: {country, categories: {品类: [款号...]}}}
- style_codes_by_country.csv 国家代码,国家,品类,款号
"""
import csv
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from collections import defaultdict
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
BASE = "https://mapi.sdspod.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Accept": "application/json",
"Referer": "https://www.inkreach.cn/",
}
PAGE_SIZE = 100 # 实测服务端允许的最大分页大小
REQUEST_DELAY = 0.3 # 请求间隔(秒),礼貌爬取
TIMEOUT = 30
RETRIES = 3
OUT_JSON = "style_codes_by_country.json"
OUT_CSV = "style_codes_by_country.csv"
# 品类白名单:品类名包含任一关键词才保留,其余(下装/短裤/长裤/家居/家居配饰/内衣/
# 配饰/印花烫贴/桌面用品 等)连同其款号一并剔除。想恢复全量,把这里改成空元组即可。
KEEP_CATEGORY_KEYWORDS = ("T恤", "卫衣", "长袖", "背心", "夹克")
# ---------------------------------------------------------------- 基础请求
def http_get_json(path, params=None):
"""GET 请求,返回 dict;失败自动重试。"""
url = BASE + path
if params:
url += "?" + urllib.parse.urlencode(params)
last_err = None
for attempt in range(1, RETRIES + 1):
try:
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as e: # noqa: BLE001
last_err = e
if attempt < RETRIES:
time.sleep(1.0 * attempt)
raise RuntimeError(f"请求失败 {url} -> {last_err}")
# ---------------------------------------------------------------- 数据抓取
def fetch_countries():
"""返回 [(countryCode, 中文名, 商品数), ...],按名称长度降序(便于长名优先匹配)。"""
d = http_get_json("/products/pageOptionGroup")
lst = d.get("shipmentCountryList") or []
out = [(c.get("countryCode") or "", c.get("country") or "", c.get("num") or 0) for c in lst]
out.sort(key=lambda x: -len(x[1]))
return out
def fetch_all_products():
"""分页拉取全部商品(海外 + 中国两个发货区域),按 id 去重。"""
products, seen = [], set()
for area in ("overseas", "CN"):
page, got = 1, 0
while True:
d = http_get_json("/products/page", {"shipmentArea": area, "page": page, "size": PAGE_SIZE})
items = d.get("items") or []
if not items:
break
for it in items:
pid = it.get("id")
if pid in seen:
continue
seen.add(pid)
it["_shipmentArea"] = area
products.append(it)
total = int(d.get("totalCount") or 0)
got += len(items)
print(f" [{area}] page={page:<3} +{len(items):<4} 本区累计 {got}/{total}")
if got >= total or len(items) < PAGE_SIZE:
break
page += 1
time.sleep(REQUEST_DELAY)
return products
# ---------------------------------------------------------------- 解析规则
CODE_SEG = re.compile(r"^[A-Z0-9]{2,8}$")
CODE_LABEL = re.compile(r"^([A-Z0-9]{2,8})\s+") # "DG505 180g男士纯棉长袖"
CODE_INLINE = re.compile(r"\b([A-Z]{1,5}\d{2,6}[A-Z]?|\d{2,5}[A-Z]{1,3})\b")
# 尺码词:避免把 S / M / XL / 3XL 之类误判成款号
SIZE_WORDS = {"S", "M", "L", "XL", "XXL", "XXXL", "XXXXL", "XXXXXL",
"XS", "XXS", "2XL", "3XL", "4XL", "5XL", "6XL", "SML", "ML", "OS"}
# 国家别名:站点文案与 pageOptionGroup 的正式国名不一致时的映射
COUNTRY_ALIASES = {
"中东": "SA", # "中东本地工厂直发" -> 沙特阿拉伯(SA)
"国内工厂": "CN", # "国内工厂" -> 中国(CN)
}
def is_valid_code(s):
"""款号合法性:2~8 位大写字母/数字,含字母,且不是尺码词。"""
s = (s or "").upper()
if s in SIZE_WORDS:
return False
if not CODE_SEG.match(s):
return False
return bool(re.search(r"[A-Z]", s)) # 必须含字母,排除 "180" 之类纯数字
def extract_style_code(name, cats):
"""提取款号:优先 categories 的『款号 品名』标签,其次 name 的 '-' 分段,最后全局正则。"""
# 1) categories 里的 "DG505 180g男士纯棉长袖" / "SFB 运动短裤"(最可靠,纯字母款号也能拿到)
for c in cats:
m = CODE_LABEL.match(c.strip())
if m and is_valid_code(m.group(1)):
return m.group(1).upper()
# 2) name 按 '-' 分段
for seg in (name or "").split("-"):
s = seg.strip().upper()
if is_valid_code(s):
return s
# 3) 兜底:全局正则
m = CODE_INLINE.search(name or "")
if m and is_valid_code(m.group(1)):
return m.group(1).upper()
return None
def match_country(text, head, countries, area=None):
"""匹配国家:别名 -> name 前段 -> 全文 -> shipmentArea 兜底。"""
cn_by_code = {code: cn for code, cn, _ in countries}
for alias, code in COUNTRY_ALIASES.items():
if alias in head or alias in text:
return code, cn_by_code.get(code, alias)
for scope in (head, text):
for code, cn, _ in countries:
if cn and cn in scope:
return code, cn
if area == "CN":
return "CN", cn_by_code.get("CN", "中国")
return "UNKNOWN", "未识别"
def pick_category(cats, cn_name, style_code):
"""品类 = categories 中去掉『国家/工厂标签』与『款号+品名标签』后剩下的部分。"""
rest = []
for c in cats:
c_stripped = c.strip()
if not c_stripped:
continue
if cn_name and cn_name in c_stripped: # 国家标签,如"美国工厂直发"
continue
if "直发" in c_stripped or "工厂" in c_stripped:
continue
head = c_stripped.split(" ")[0]
if style_code and head.upper() == style_code.upper(): # "DG505 180g男士纯棉长袖"
continue
rest.append(c_stripped)
return " / ".join(rest) if rest else "未分类"
def guess_sleeve_length(name, category):
"""推断袖长,返回 (袖长, 来源)。
商城没有袖长分类字段,只能从商品名/品类推断,来源用于审计:
- 商品名明确写「长袖/短袖/无袖」 -> 来源 "name"
- 品类是背心/吊带 -> 无袖,来源 "品类"
- 品类是卫衣/夹克 -> 长袖,来源 "品类推断"(卫衣、夹克默认长袖)
- 其余(普通 T 恤,名里没写) -> 短袖,来源 "默认(T恤)"
"""
n, c = name or "", category or ""
if "无袖" in n or "背心" in n or "吊带" in n or "背心" in c:
return "无袖", "品类" if ("背心" in c and "背心" not in n and "无袖" not in n) else "name"
if "长袖" in n:
return "长袖", "name"
if "短袖" in n:
return "短袖", "name"
if "卫衣" in c or "夹克" in c:
return "长袖", "品类推断"
return "短袖", "默认(T恤)"
# ---------------------------------------------------------------- 主流程
def main():
print("=" * 66)
print("Inkreach 款号爬取:按国家 + 品类,去重")
print("=" * 66)
print("\n[1/4] 获取国家列表 ...")
countries = fetch_countries()
for code, cn, num in countries:
print(f" {code:<8} {cn:<10} {num:>4} 个商品")
print(f"{len(countries)} 个国家/地区")
print("\n[2/4] 获取品类树 ...")
try:
cat_tree = http_get_json("/categories").get("items") or []
leaf = sum(len(c.get("subcategory") or []) for c in cat_tree)
print(f" 顶层品类 {len(cat_tree)} 个,二级品类 {leaf} 个(商品自带品类标签优先,此树仅参考)")
except Exception as e: # noqa: BLE001
print(f" 品类树获取失败(不影响主流程): {e}")
print("\n[3/4] 分页拉取全部商品 ...")
products = fetch_all_products()
print(f" 商品总数(去重后): {len(products)}")
print("\n[4/4] 解析并按 国家 / 品类 聚合款号 ...")
# country_code -> category -> {style_code}
data = dict()
country_cn = {}
no_code = []
dropped = defaultdict(int) # 被品类白名单剔除的品类 -> 商品条数
sleeve_map = {} # 款号 -> (袖长, 来源)
for it in products:
name = it.get("name") or ""
cats = [c.get("name", "") for c in (it.get("categories") or [])]
text = " ".join([name] + cats)
head = name[:12] # 国家一般写在 name 最前面
code, cn = match_country(text, head, countries, it.get("_shipmentArea"))
style = extract_style_code(name, cats)
if not style:
no_code.append(name)
continue
style = style.upper()
category = pick_category(cats, cn, style)
# 品类白名单过滤:只保留 T恤/卫衣/长袖/背心/夹克,其余品类及其款号一并剔除
if KEEP_CATEGORY_KEYWORDS and not any(k in category for k in KEEP_CATEGORY_KEYWORDS):
dropped[category] += 1
continue
country_cn.setdefault(code, cn)
data.setdefault(code, {}).setdefault(category, set()).add(style)
if style not in sleeve_map:
sleeve_map[style] = guess_sleeve_length(name, category)
if no_code:
print(f"{len(no_code)} 条商品未提取到款号(见控制台样例):")
for n in no_code[:5]:
print(f" - {n}")
if dropped:
print(f" 已按品类白名单剔除 {sum(dropped.values())} 条商品,涉及品类:")
for c, n in sorted(dropped.items(), key=lambda x: -x[1]):
print(f" - {c}{n} 条)")
# ---------------- 汇总输出 ----------------
print("\n" + "=" * 66)
print("汇总:各国品类数 / 款号数(去重)")
print("=" * 66)
print(f"{'国家':<12}{'代码':<8}{'品类数':>8}{'款号数':>10}")
print("-" * 66)
total_cats = total_codes = 0
all_codes = set()
for code in sorted(data, key=lambda c: -sum(len(v) for v in data[c].values())):
cats_map = data[code]
n_codes = sum(len(v) for v in cats_map.values())
total_cats += len(cats_map)
total_codes += n_codes
for v in cats_map.values():
all_codes |= v
print(f"{country_cn.get(code,''):<12}{code:<8}{len(cats_map):>8}{n_codes:>10}")
print("-" * 66)
print(f"{'合计':<20}{total_cats:>8}{total_codes:>10} (全局去重款号 {len(all_codes)} 个)")
# ---------------- 袖长分布 ----------------
if sleeve_map:
print("\n=== 袖长分布(按款号去重)===")
sl_cnt, src_cnt = defaultdict(int), defaultdict(int)
for v, s in sleeve_map.values():
sl_cnt[v] += 1
src_cnt[s] += 1
for k, v in sorted(sl_cnt.items(), key=lambda x: -x[1]):
print(f" {k:<6} {v:>4} 个款号")
print(" 判定来源:", dict(sorted(src_cnt.items(), key=lambda x: -x[1])))
# ---------------- 写 JSON ----------------
out = {}
for code, cats_map in data.items():
codes_here = sorted({c for v in cats_map.values() for c in v})
out[code] = {
"country": country_cn.get(code, ""),
"categories": {c: sorted(v) for c, v in sorted(cats_map.items())},
"sleeve_length": {c: sleeve_map.get(c, ("", ""))[0] for c in codes_here},
"sleeve_source": {c: sleeve_map.get(c, ("", ""))[1] for c in codes_here},
}
with open(OUT_JSON, "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False, indent=2)
print(f"\n✅ 已写出 {OUT_JSON}")
# ---------------- 写 CSV ----------------
rows = []
for code in sorted(out):
for cat, codes in out[code]["categories"].items():
for c in codes:
rows.append([code, country_cn.get(code, ""), cat, c,
sleeve_map.get(c, ("", ""))[0]])
with open(OUT_CSV, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["country_code", "country", "category", "style_code", "sleeve_length"])
w.writerows(rows)
print(f"✅ 已写出 {OUT_CSV}{len(rows)} 行)")
# ---------------- 样例预览 ----------------
print("\n样例预览(每个国家取 1 个品类,最多 8 个款号):")
for code in list(sorted(out))[:6]:
cats_map = out[code]["categories"]
if not cats_map:
continue
cat0 = sorted(cats_map)[0]
codes = cats_map[cat0][:8]
print(f" [{code}] {country_cn.get(code,'')} / {cat0}: {', '.join(codes)}"
+ (" ..." if len(cats_map[cat0]) > 8 else ""))
if __name__ == "__main__":
main()
+18
View File
@@ -0,0 +1,18 @@
-- 以爬取清单(剔除后 115 个款号)为准,
-- 删除 spu.code 不在清单内的 SPU 及其对应 SKU。
-- 执行顺序:先删子表 sku,再删主表 spu。
-- 1) 删除非清单款号对应的 SKU
DELETE FROM sku
WHERE spu_id IN (
SELECT id FROM spu
WHERE UPPER(TRIM(code)) NOT IN ('5000B', 'AUTM001', 'AUTM002', 'AUTM003', 'BRTF004', 'BRTF007', 'BRTM001', 'BRTM002', 'C1717', 'CAHM002', 'CATM001', 'CATM006', 'CATM007', 'DEHM003', 'DETM001', 'DETM002', 'DF009', 'DF010', 'DG001', 'DG004', 'DG012', 'DG013', 'DG014', 'DG015', 'DG100', 'DG101', 'DG102', 'DG110', 'DG120', 'DG150', 'DG301', 'DG501', 'DG502', 'DG503', 'DG504', 'DG505', 'DG601', 'ESHM003', 'ESHM004', 'ESTK005', 'ESTM002', 'GBHK006', 'GBHK007', 'GBHM002', 'GBHM003', 'GBTF004', 'GBTK005', 'GBTK008', 'GBTK012', 'GBTM001', 'GBTM010', 'GBTM011', 'GBTM013', 'HM01', 'ITFM005', 'ITTK002', 'ITTM001', 'JPHM009', 'JPHM010', 'JPPX012', 'JPPX013', 'JPTK015', 'JPTM001', 'JPTM002', 'JPTM004', 'JPTM006', 'JPTM007', 'JPTM008', 'JSA004', 'JSA006', 'JSA008', 'JSA009', 'JSA011', 'JSB001', 'JSB002', 'JSB003', 'JSE008', 'JSS001', 'KRHM002', 'KRHM003', 'KRTM001', 'KRTM004', 'MESXT001', 'METB001', 'METN001', 'METP001', 'PET001', 'PLHF029', 'PLHM002', 'PLHM003', 'PLHM009', 'PLHM010', 'PLHM012', 'PLHM013', 'PLJM030', 'PLTF004', 'PLTF026', 'PLTF027', 'PLTF028', 'PLTK005', 'PLTK015', 'PLTK016', 'PLTM001', 'PLTM006', 'PLTM008', 'PLTM024', 'PLTM031', 'SAHM003', 'SATK004', 'SATU001', 'TBB001', 'VS001', 'VS002', 'VS003', 'VS004')
);
-- 2) 删除非清单款号的 SPU
DELETE FROM spu
WHERE UPPER(TRIM(code)) NOT IN ('5000B', 'AUTM001', 'AUTM002', 'AUTM003', 'BRTF004', 'BRTF007', 'BRTM001', 'BRTM002', 'C1717', 'CAHM002', 'CATM001', 'CATM006', 'CATM007', 'DEHM003', 'DETM001', 'DETM002', 'DF009', 'DF010', 'DG001', 'DG004', 'DG012', 'DG013', 'DG014', 'DG015', 'DG100', 'DG101', 'DG102', 'DG110', 'DG120', 'DG150', 'DG301', 'DG501', 'DG502', 'DG503', 'DG504', 'DG505', 'DG601', 'ESHM003', 'ESHM004', 'ESTK005', 'ESTM002', 'GBHK006', 'GBHK007', 'GBHM002', 'GBHM003', 'GBTF004', 'GBTK005', 'GBTK008', 'GBTK012', 'GBTM001', 'GBTM010', 'GBTM011', 'GBTM013', 'HM01', 'ITFM005', 'ITTK002', 'ITTM001', 'JPHM009', 'JPHM010', 'JPPX012', 'JPPX013', 'JPTK015', 'JPTM001', 'JPTM002', 'JPTM004', 'JPTM006', 'JPTM007', 'JPTM008', 'JSA004', 'JSA006', 'JSA008', 'JSA009', 'JSA011', 'JSB001', 'JSB002', 'JSB003', 'JSE008', 'JSS001', 'KRHM002', 'KRHM003', 'KRTM001', 'KRTM004', 'MESXT001', 'METB001', 'METN001', 'METP001', 'PET001', 'PLHF029', 'PLHM002', 'PLHM003', 'PLHM009', 'PLHM010', 'PLHM012', 'PLHM013', 'PLJM030', 'PLTF004', 'PLTF026', 'PLTF027', 'PLTF028', 'PLTK005', 'PLTK015', 'PLTK016', 'PLTM001', 'PLTM006', 'PLTM008', 'PLTM024', 'PLTM031', 'SAHM003', 'SATK004', 'SATU001', 'TBB001', 'VS001', 'VS002', 'VS003', 'VS004');
-- 校验:应返回 0
SELECT COUNT(*) AS orphan_sku FROM sku
WHERE spu_id NOT IN (SELECT id FROM spu);
+10
View File
@@ -0,0 +1,10 @@
-- 童装款号(22 个)对应 SKU 的 size_type 统一改为『欧美尺码年龄码』
UPDATE sku
SET size_type = '欧美尺码年龄码'
WHERE spu_id IN (SELECT id FROM spu WHERE UPPER(TRIM(code)) IN ('5000B', 'DG012', 'DG014', 'DG301', 'DG503', 'DG504', 'ESTK005', 'GBHK006', 'GBTK005', 'GBTK008', 'ITTK002', 'JPTK015', 'JSA004', 'METN001', 'PLHM009', 'PLHM010', 'PLHM012', 'PLHM013', 'PLTK005', 'PLTK015', 'PLTK016', 'SATK004'));
-- 校验
SELECT s.code, s.country, k.size_type, COUNT(*) AS n
FROM sku k JOIN spu s ON k.spu_id = s.id
WHERE UPPER(TRIM(s.code)) IN ('5000B', 'DG012', 'DG014', 'DG301', 'DG503', 'DG504', 'ESTK005', 'GBHK006', 'GBTK005', 'GBTK008', 'ITTK002', 'JPTK015', 'JSA004', 'METN001', 'PLHM009', 'PLHM010', 'PLHM012', 'PLHM013', 'PLTK005', 'PLTK015', 'PLTK016', 'SATK004')
GROUP BY s.code, s.country, k.size_type;
+15
View File
@@ -0,0 +1,15 @@
-- 以 style_codes_by_country.csv 中『童装T恤』品类的 19 个款号为准,
-- 更新对应 SPU 的 target_audience / season / kids_type / kids_age。
UPDATE spu
SET target_audience = '男童',
season = 'ALL/全球/所有',
kids_type = '上衣',
kids_age = '12岁及以下'
WHERE UPPER(TRIM(code)) IN ('5000B', 'DG012', 'DG014', 'DG301', 'DG503', 'ESTK005', 'GBTK005', 'GBTK008', 'GBTK012', 'ITTK002', 'JPTK015', 'JSA004', 'METN001', 'PLHM012', 'PLHM013', 'PLTK005', 'PLTK015', 'PLTK016', 'SATK004');
-- 校验(应返回被更新行数与取值分布)
SELECT target_audience, season, kids_type, kids_age, COUNT(*)
FROM spu
WHERE UPPER(TRIM(code)) IN ('5000B', 'DG012', 'DG014', 'DG301', 'DG503', 'ESTK005', 'GBTK005', 'GBTK008', 'GBTK012', 'ITTK002', 'JPTK015', 'JSA004', 'METN001', 'PLHM012', 'PLHM013', 'PLTK005', 'PLTK015', 'PLTK016', 'SATK004')
GROUP BY target_audience, season, kids_type, kids_age;
+16
View File
@@ -0,0 +1,16 @@
-- 以 style_codes_by_country.csv 中『男士卫衣』品类的 21 个款号为准,
-- 更新对应 SPU 的 details / collar_style / target_audience / season / sleeve_length_type。
UPDATE spu
SET details = '抽绳',
collar_style = '连帽',
target_audience = '男士',
season = '秋/冬',
sleeve_length_type = '长袖'
WHERE UPPER(TRIM(code)) IN ('CAHM002', 'DEHM003', 'DF009', 'DF010', 'ESHM003', 'ESHM004', 'GBHM002', 'GBHM003', 'HM01', 'JPHM009', 'JPHM010', 'JPPX012', 'JPPX013', 'JSB001', 'JSB002', 'JSB003', 'KRHM002', 'KRHM003', 'PLHM002', 'PLHM003', 'SAHM003');
-- 校验
SELECT details, collar_style, target_audience, season, sleeve_length_type, COUNT(*)
FROM spu
WHERE UPPER(TRIM(code)) IN ('CAHM002', 'DEHM003', 'DF009', 'DF010', 'ESHM003', 'ESHM004', 'GBHM002', 'GBHM003', 'HM01', 'JPHM009', 'JPHM010', 'JPPX012', 'JPPX013', 'JSB001', 'JSB002', 'JSB003', 'KRHM002', 'KRHM003', 'PLHM002', 'PLHM003', 'SAHM003')
GROUP BY details, collar_style, target_audience, season, sleeve_length_type;
+109
View File
@@ -0,0 +1,109 @@
-- 以 style_codes_by_country.csv 的 sleeve_length 为准,回填 SPU.sleeve_length_type
-- 逐个款号 UPDATE(防误伤)
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = '5000B';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'AUTM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'AUTM002';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'AUTM003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'BRTF007';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'BRTM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'BRTM002';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'C1717';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'CAHM002';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'CATM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'CATM006';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'CATM007';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DEHM003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DETM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DETM002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DF009';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DF010';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG012';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG013';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG014';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG015';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG100';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG101';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG102';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG110';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG120';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG150';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG301';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DG501';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DG502';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DG503';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'DG504';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'DG601';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'ESHM003';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'ESHM004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'ESTK005';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'ESTM002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'GBHK006';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'GBHM002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'GBHM003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'GBTF004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'GBTK005';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'GBTK008';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'GBTM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'GBTM013';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'HM01';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'ITFM005';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'ITTK002';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'ITTM001';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JPHM009';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JPHM010';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JPTK015';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JPTM001';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'JPTM002';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JPTM004';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JPTM006';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JPTM007';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'JPTM008';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSA004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSA006';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSA008';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'JSA009';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSA011';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JSB001';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JSB002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'JSB003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSE008';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'JSS001';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'KRHM002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'KRHM003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'KRTM001';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'KRTM004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'MESXT001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'METB001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'METN001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'METP001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PET001';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLHF029';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLHM002';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLHM003';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLHM009';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLHM010';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLHM012';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLHM013';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLJM030';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTF004';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'PLTF026';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTF027';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTF028';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTK005';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTK015';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTK016';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTM001';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTM006';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTM008';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'PLTM024';
UPDATE spu SET sleeve_length_type = '长袖' WHERE UPPER(TRIM(code)) = 'SAHM003';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'SATK004';
UPDATE spu SET sleeve_length_type = '短袖' WHERE UPPER(TRIM(code)) = 'SATU001';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'TBB001';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'VS001';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'VS002';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'VS003';
UPDATE spu SET sleeve_length_type = '无袖' WHERE UPPER(TRIM(code)) = 'VS004';
BIN
View File
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
country_code,country,category,style_code,sleeve_length
AU,澳大利亚,男士T恤,AUTM001,短袖
AU,澳大利亚,男士T恤,AUTM002,短袖
AU,澳大利亚,男士背心,AUTM003,无袖
BR,巴西,女士T恤,BRTF004,短袖
BR,巴西,女士T恤,BRTF007,短袖
BR,巴西,男士T恤,BRTM001,短袖
BR,巴西,男士T恤,BRTM002,短袖
CA,加拿大,男士T恤,CATM001,短袖
CA,加拿大,男士T恤,CATM006,短袖
CA,加拿大,男士T恤,CATM007,短袖
CA,加拿大,男士卫衣,CAHM002,长袖
DE,德国,男士T恤,DETM001,短袖
DE,德国,男士T恤,DETM002,短袖
DE,德国,男士卫衣,DEHM003,长袖
ES,西班牙,男士T恤,ESTM002,短袖
ES,西班牙,男士卫衣,ESHM003,长袖
ES,西班牙,男士卫衣,ESHM004,长袖
ES,西班牙,童装T恤,ESTK005,短袖
GB,英国,女士T恤,GBTF004,短袖
GB,英国,男士T恤,GBTM001,短袖
GB,英国,男士T恤,GBTM010,短袖
GB,英国,男士T恤,GBTM013,短袖
GB,英国,男士T恤 / GBTM011长袖T,GBTM011,长袖
GB,英国,男士卫衣,GBHM002,长袖
GB,英国,男士卫衣,GBHM003,长袖
GB,英国,童装T恤,GBTK005,短袖
GB,英国,童装T恤,GBTK008,短袖
GB,英国,童装T恤,GBTK012,长袖
GB,英国,童装卫衣,GBHK006,长袖
GB,英国,童装卫衣,GBHK007,长袖
IT,意大利,女士T恤,ITFM005,短袖
IT,意大利,男士T恤,ITTM001,短袖
IT,意大利,童装T恤,ITTK002,短袖
JP,日本,男士T恤,JPTM001,短袖
JP,日本,男士T恤,JPTM004,短袖
JP,日本,男士T恤,JPTM006,长袖
JP,日本,男士T恤,JPTM007,短袖
JP,日本,男士卫衣,JPHM009,长袖
JP,日本,男士卫衣,JPHM010,长袖
JP,日本,男士卫衣,JPPX012,长袖
JP,日本,男士卫衣,JPPX013,长袖
JP,日本,男士背心,JPTM002,无袖
JP,日本,男士背心,JPTM008,无袖
JP,日本,童装T恤,JPTK015,短袖
KR,韩国,男士T恤,KRTM001,短袖
KR,韩国,男士T恤,KRTM004,长袖
KR,韩国,男士卫衣,KRHM002,长袖
KR,韩国,男士卫衣,KRHM003,长袖
MX,墨西哥,女士T恤,METP001,短袖
MX,墨西哥,男士T恤,MESXT001,短袖
MX,墨西哥,男士T恤,METB001,短袖
MX,墨西哥,男士T恤,PET001,短袖
MX,墨西哥,男士卫衣,HM01,长袖
MX,墨西哥,男士背心,TBB001,无袖
MX,墨西哥,童装T恤,METN001,短袖
PL,波兰,女士T恤,PLTF004,短袖
PL,波兰,女士T恤,PLTF026,长袖
PL,波兰,女士T恤,PLTF027,短袖
PL,波兰,女士T恤,PLTF028,短袖
PL,波兰,女士卫衣,PLHF029,长袖
PL,波兰,男士T恤,PLTM001,短袖
PL,波兰,男士T恤,PLTM006,短袖
PL,波兰,男士T恤,PLTM008,短袖
PL,波兰,男士T恤,PLTM024,短袖
PL,波兰,男士T恤,PLTM031,短袖
PL,波兰,男士卫衣,PLHM002,长袖
PL,波兰,男士卫衣,PLHM003,长袖
PL,波兰,男士夹克,PLJM030,长袖
PL,波兰,童装T恤,PLHM012,短袖
PL,波兰,童装T恤,PLHM013,短袖
PL,波兰,童装T恤,PLTK005,短袖
PL,波兰,童装T恤,PLTK015,短袖
PL,波兰,童装T恤,PLTK016,短袖
PL,波兰,童装卫衣,PLHM009,长袖
PL,波兰,童装卫衣,PLHM010,长袖
SA,沙特阿拉伯,女士T恤,SATU001,短袖
SA,沙特阿拉伯,男士T恤,SATU001,短袖
SA,沙特阿拉伯,男士卫衣,SAHM003,长袖
SA,沙特阿拉伯,童装T恤,SATK004,短袖
US,美国,女士T恤,DG100,短袖
US,美国,女士T恤,DG101,短袖
US,美国,女士T恤,DG102,短袖
US,美国,女士T恤,DG110,短袖
US,美国,女士T恤,DG120,短袖
US,美国,女士T恤,DG150,短袖
US,美国,女士T恤,DG502,长袖
US,美国,女士T恤,DG601,短袖
US,美国,女士T恤,JSA008,短袖
US,美国,女士T恤,JSA011,短袖
US,美国,女士T恤,JSE008,短袖
US,美国,女士卫衣,DG501,长袖
US,美国,女士背心,JSA009,无袖
US,美国,女士背心,VS002,无袖
US,美国,女士背心,VS004,无袖
US,美国,男士T恤,C1717,短袖
US,美国,男士T恤,DG001,短袖
US,美国,男士T恤,DG004,短袖
US,美国,男士T恤,DG013,短袖
US,美国,男士T恤,DG015,短袖
US,美国,男士T恤,DG505,长袖
US,美国,男士T恤,JSA006,短袖
US,美国,男士T恤,JSS001,短袖
US,美国,男士卫衣,DF009,长袖
US,美国,男士卫衣,DF010,长袖
US,美国,男士卫衣,JSB001,长袖
US,美国,男士卫衣,JSB002,长袖
US,美国,男士卫衣,JSB003,长袖
US,美国,男士背心,VS001,无袖
US,美国,男士背心,VS003,无袖
US,美国,童装T恤,5000B,短袖
US,美国,童装T恤,DG012,短袖
US,美国,童装T恤,DG014,短袖
US,美国,童装T恤,DG301,短袖
US,美国,童装T恤,DG503,长袖
US,美国,童装T恤,JSA004,短袖
US,美国,童装卫衣,DG504,长袖
1 country_code country category style_code sleeve_length
2 AU 澳大利亚 男士T恤 AUTM001 短袖
3 AU 澳大利亚 男士T恤 AUTM002 短袖
4 AU 澳大利亚 男士背心 AUTM003 无袖
5 BR 巴西 女士T恤 BRTF004 短袖
6 BR 巴西 女士T恤 BRTF007 短袖
7 BR 巴西 男士T恤 BRTM001 短袖
8 BR 巴西 男士T恤 BRTM002 短袖
9 CA 加拿大 男士T恤 CATM001 短袖
10 CA 加拿大 男士T恤 CATM006 短袖
11 CA 加拿大 男士T恤 CATM007 短袖
12 CA 加拿大 男士卫衣 CAHM002 长袖
13 DE 德国 男士T恤 DETM001 短袖
14 DE 德国 男士T恤 DETM002 短袖
15 DE 德国 男士卫衣 DEHM003 长袖
16 ES 西班牙 男士T恤 ESTM002 短袖
17 ES 西班牙 男士卫衣 ESHM003 长袖
18 ES 西班牙 男士卫衣 ESHM004 长袖
19 ES 西班牙 童装T恤 ESTK005 短袖
20 GB 英国 女士T恤 GBTF004 短袖
21 GB 英国 男士T恤 GBTM001 短袖
22 GB 英国 男士T恤 GBTM010 短袖
23 GB 英国 男士T恤 GBTM013 短袖
24 GB 英国 男士T恤 / GBTM011长袖T GBTM011 长袖
25 GB 英国 男士卫衣 GBHM002 长袖
26 GB 英国 男士卫衣 GBHM003 长袖
27 GB 英国 童装T恤 GBTK005 短袖
28 GB 英国 童装T恤 GBTK008 短袖
29 GB 英国 童装T恤 GBTK012 长袖
30 GB 英国 童装卫衣 GBHK006 长袖
31 GB 英国 童装卫衣 GBHK007 长袖
32 IT 意大利 女士T恤 ITFM005 短袖
33 IT 意大利 男士T恤 ITTM001 短袖
34 IT 意大利 童装T恤 ITTK002 短袖
35 JP 日本 男士T恤 JPTM001 短袖
36 JP 日本 男士T恤 JPTM004 短袖
37 JP 日本 男士T恤 JPTM006 长袖
38 JP 日本 男士T恤 JPTM007 短袖
39 JP 日本 男士卫衣 JPHM009 长袖
40 JP 日本 男士卫衣 JPHM010 长袖
41 JP 日本 男士卫衣 JPPX012 长袖
42 JP 日本 男士卫衣 JPPX013 长袖
43 JP 日本 男士背心 JPTM002 无袖
44 JP 日本 男士背心 JPTM008 无袖
45 JP 日本 童装T恤 JPTK015 短袖
46 KR 韩国 男士T恤 KRTM001 短袖
47 KR 韩国 男士T恤 KRTM004 长袖
48 KR 韩国 男士卫衣 KRHM002 长袖
49 KR 韩国 男士卫衣 KRHM003 长袖
50 MX 墨西哥 女士T恤 METP001 短袖
51 MX 墨西哥 男士T恤 MESXT001 短袖
52 MX 墨西哥 男士T恤 METB001 短袖
53 MX 墨西哥 男士T恤 PET001 短袖
54 MX 墨西哥 男士卫衣 HM01 长袖
55 MX 墨西哥 男士背心 TBB001 无袖
56 MX 墨西哥 童装T恤 METN001 短袖
57 PL 波兰 女士T恤 PLTF004 短袖
58 PL 波兰 女士T恤 PLTF026 长袖
59 PL 波兰 女士T恤 PLTF027 短袖
60 PL 波兰 女士T恤 PLTF028 短袖
61 PL 波兰 女士卫衣 PLHF029 长袖
62 PL 波兰 男士T恤 PLTM001 短袖
63 PL 波兰 男士T恤 PLTM006 短袖
64 PL 波兰 男士T恤 PLTM008 短袖
65 PL 波兰 男士T恤 PLTM024 短袖
66 PL 波兰 男士T恤 PLTM031 短袖
67 PL 波兰 男士卫衣 PLHM002 长袖
68 PL 波兰 男士卫衣 PLHM003 长袖
69 PL 波兰 男士夹克 PLJM030 长袖
70 PL 波兰 童装T恤 PLHM012 短袖
71 PL 波兰 童装T恤 PLHM013 短袖
72 PL 波兰 童装T恤 PLTK005 短袖
73 PL 波兰 童装T恤 PLTK015 短袖
74 PL 波兰 童装T恤 PLTK016 短袖
75 PL 波兰 童装卫衣 PLHM009 长袖
76 PL 波兰 童装卫衣 PLHM010 长袖
77 SA 沙特阿拉伯 女士T恤 SATU001 短袖
78 SA 沙特阿拉伯 男士T恤 SATU001 短袖
79 SA 沙特阿拉伯 男士卫衣 SAHM003 长袖
80 SA 沙特阿拉伯 童装T恤 SATK004 短袖
81 US 美国 女士T恤 DG100 短袖
82 US 美国 女士T恤 DG101 短袖
83 US 美国 女士T恤 DG102 短袖
84 US 美国 女士T恤 DG110 短袖
85 US 美国 女士T恤 DG120 短袖
86 US 美国 女士T恤 DG150 短袖
87 US 美国 女士T恤 DG502 长袖
88 US 美国 女士T恤 DG601 短袖
89 US 美国 女士T恤 JSA008 短袖
90 US 美国 女士T恤 JSA011 短袖
91 US 美国 女士T恤 JSE008 短袖
92 US 美国 女士卫衣 DG501 长袖
93 US 美国 女士背心 JSA009 无袖
94 US 美国 女士背心 VS002 无袖
95 US 美国 女士背心 VS004 无袖
96 US 美国 男士T恤 C1717 短袖
97 US 美国 男士T恤 DG001 短袖
98 US 美国 男士T恤 DG004 短袖
99 US 美国 男士T恤 DG013 短袖
100 US 美国 男士T恤 DG015 短袖
101 US 美国 男士T恤 DG505 长袖
102 US 美国 男士T恤 JSA006 短袖
103 US 美国 男士T恤 JSS001 短袖
104 US 美国 男士卫衣 DF009 长袖
105 US 美国 男士卫衣 DF010 长袖
106 US 美国 男士卫衣 JSB001 长袖
107 US 美国 男士卫衣 JSB002 长袖
108 US 美国 男士卫衣 JSB003 长袖
109 US 美国 男士背心 VS001 无袖
110 US 美国 男士背心 VS003 无袖
111 US 美国 童装T恤 5000B 短袖
112 US 美国 童装T恤 DG012 短袖
113 US 美国 童装T恤 DG014 短袖
114 US 美国 童装T恤 DG301 短袖
115 US 美国 童装T恤 DG503 长袖
116 US 美国 童装T恤 JSA004 短袖
117 US 美国 童装卫衣 DG504 长袖
+565
View File
@@ -0,0 +1,565 @@
{
"US": {
"country": "美国",
"categories": {
"女士T恤": [
"DG100",
"DG101",
"DG102",
"DG110",
"DG120",
"DG150",
"DG502",
"DG601",
"JSA008",
"JSA011",
"JSE008"
],
"女士卫衣": [
"DG501"
],
"女士背心": [
"JSA009",
"VS002",
"VS004"
],
"男士T恤": [
"C1717",
"DG001",
"DG004",
"DG013",
"DG015",
"DG505",
"JSA006",
"JSS001"
],
"男士卫衣": [
"DF009",
"DF010",
"JSB001",
"JSB002",
"JSB003"
],
"男士背心": [
"VS001",
"VS003"
],
"童装T恤": [
"5000B",
"DG012",
"DG014",
"DG301",
"DG503",
"JSA004"
],
"童装卫衣": [
"DG504"
]
},
"sleeve_length": {
"5000B": "短袖",
"C1717": "短袖",
"DF009": "长袖",
"DF010": "长袖",
"DG001": "短袖",
"DG004": "短袖",
"DG012": "短袖",
"DG013": "短袖",
"DG014": "短袖",
"DG015": "短袖",
"DG100": "短袖",
"DG101": "短袖",
"DG102": "短袖",
"DG110": "短袖",
"DG120": "短袖",
"DG150": "短袖",
"DG301": "短袖",
"DG501": "长袖",
"DG502": "长袖",
"DG503": "长袖",
"DG504": "长袖",
"DG505": "长袖",
"DG601": "短袖",
"JSA004": "短袖",
"JSA006": "短袖",
"JSA008": "短袖",
"JSA009": "无袖",
"JSA011": "短袖",
"JSB001": "长袖",
"JSB002": "长袖",
"JSB003": "长袖",
"JSE008": "短袖",
"JSS001": "短袖",
"VS001": "无袖",
"VS002": "无袖",
"VS003": "无袖",
"VS004": "无袖"
},
"sleeve_source": {
"5000B": "默认(T恤)",
"C1717": "默认(T恤)",
"DF009": "品类推断",
"DF010": "品类推断",
"DG001": "默认(T恤)",
"DG004": "默认(T恤)",
"DG012": "默认(T恤)",
"DG013": "默认(T恤)",
"DG014": "默认(T恤)",
"DG015": "默认(T恤)",
"DG100": "默认(T恤)",
"DG101": "默认(T恤)",
"DG102": "默认(T恤)",
"DG110": "默认(T恤)",
"DG120": "默认(T恤)",
"DG150": "默认(T恤)",
"DG301": "默认(T恤)",
"DG501": "品类推断",
"DG502": "name",
"DG503": "name",
"DG504": "品类推断",
"DG505": "name",
"DG601": "默认(T恤)",
"JSA004": "默认(T恤)",
"JSA006": "默认(T恤)",
"JSA008": "默认(T恤)",
"JSA009": "name",
"JSA011": "默认(T恤)",
"JSB001": "品类推断",
"JSB002": "品类推断",
"JSB003": "品类推断",
"JSE008": "默认(T恤)",
"JSS001": "默认(T恤)",
"VS001": "name",
"VS002": "name",
"VS003": "name",
"VS004": "name"
}
},
"BR": {
"country": "巴西",
"categories": {
"女士T恤": [
"BRTF004",
"BRTF007"
],
"男士T恤": [
"BRTM001",
"BRTM002"
]
},
"sleeve_length": {
"BRTF004": "短袖",
"BRTF007": "短袖",
"BRTM001": "短袖",
"BRTM002": "短袖"
},
"sleeve_source": {
"BRTF004": "默认(T恤)",
"BRTF007": "默认(T恤)",
"BRTM001": "默认(T恤)",
"BRTM002": "默认(T恤)"
}
},
"AU": {
"country": "澳大利亚",
"categories": {
"男士T恤": [
"AUTM001",
"AUTM002"
],
"男士背心": [
"AUTM003"
]
},
"sleeve_length": {
"AUTM001": "短袖",
"AUTM002": "短袖",
"AUTM003": "无袖"
},
"sleeve_source": {
"AUTM001": "默认(T恤)",
"AUTM002": "默认(T恤)",
"AUTM003": "name"
}
},
"PL": {
"country": "波兰",
"categories": {
"女士T恤": [
"PLTF004",
"PLTF026",
"PLTF027",
"PLTF028"
],
"女士卫衣": [
"PLHF029"
],
"男士T恤": [
"PLTM001",
"PLTM006",
"PLTM008",
"PLTM024",
"PLTM031"
],
"男士卫衣": [
"PLHM002",
"PLHM003"
],
"男士夹克": [
"PLJM030"
],
"童装T恤": [
"PLHM012",
"PLHM013",
"PLTK005",
"PLTK015",
"PLTK016"
],
"童装卫衣": [
"PLHM009",
"PLHM010"
]
},
"sleeve_length": {
"PLHF029": "长袖",
"PLHM002": "长袖",
"PLHM003": "长袖",
"PLHM009": "长袖",
"PLHM010": "长袖",
"PLHM012": "短袖",
"PLHM013": "短袖",
"PLJM030": "长袖",
"PLTF004": "短袖",
"PLTF026": "长袖",
"PLTF027": "短袖",
"PLTF028": "短袖",
"PLTK005": "短袖",
"PLTK015": "短袖",
"PLTK016": "短袖",
"PLTM001": "短袖",
"PLTM006": "短袖",
"PLTM008": "短袖",
"PLTM024": "短袖",
"PLTM031": "短袖"
},
"sleeve_source": {
"PLHF029": "name",
"PLHM002": "品类推断",
"PLHM003": "品类推断",
"PLHM009": "品类推断",
"PLHM010": "品类推断",
"PLHM012": "默认(T恤)",
"PLHM013": "name",
"PLJM030": "品类推断",
"PLTF004": "默认(T恤)",
"PLTF026": "name",
"PLTF027": "默认(T恤)",
"PLTF028": "默认(T恤)",
"PLTK005": "默认(T恤)",
"PLTK015": "默认(T恤)",
"PLTK016": "name",
"PLTM001": "默认(T恤)",
"PLTM006": "默认(T恤)",
"PLTM008": "name",
"PLTM024": "默认(T恤)",
"PLTM031": "默认(T恤)"
}
},
"KR": {
"country": "韩国",
"categories": {
"男士T恤": [
"KRTM001",
"KRTM004"
],
"男士卫衣": [
"KRHM002",
"KRHM003"
]
},
"sleeve_length": {
"KRHM002": "长袖",
"KRHM003": "长袖",
"KRTM001": "短袖",
"KRTM004": "长袖"
},
"sleeve_source": {
"KRHM002": "品类推断",
"KRHM003": "品类推断",
"KRTM001": "默认(T恤)",
"KRTM004": "name"
}
},
"SA": {
"country": "沙特阿拉伯",
"categories": {
"女士T恤": [
"SATU001"
],
"男士T恤": [
"SATU001"
],
"男士卫衣": [
"SAHM003"
],
"童装T恤": [
"SATK004"
]
},
"sleeve_length": {
"SAHM003": "长袖",
"SATK004": "短袖",
"SATU001": "短袖"
},
"sleeve_source": {
"SAHM003": "品类推断",
"SATK004": "默认(T恤)",
"SATU001": "默认(T恤)"
}
},
"MX": {
"country": "墨西哥",
"categories": {
"女士T恤": [
"METP001"
],
"男士T恤": [
"MESXT001",
"METB001",
"PET001"
],
"男士卫衣": [
"HM01"
],
"男士背心": [
"TBB001"
],
"童装T恤": [
"METN001"
]
},
"sleeve_length": {
"HM01": "长袖",
"MESXT001": "短袖",
"METB001": "短袖",
"METN001": "短袖",
"METP001": "短袖",
"PET001": "短袖",
"TBB001": "无袖"
},
"sleeve_source": {
"HM01": "品类推断",
"MESXT001": "默认(T恤)",
"METB001": "默认(T恤)",
"METN001": "默认(T恤)",
"METP001": "默认(T恤)",
"PET001": "默认(T恤)",
"TBB001": "name"
}
},
"IT": {
"country": "意大利",
"categories": {
"女士T恤": [
"ITFM005"
],
"男士T恤": [
"ITTM001"
],
"童装T恤": [
"ITTK002"
]
},
"sleeve_length": {
"ITFM005": "短袖",
"ITTK002": "短袖",
"ITTM001": "短袖"
},
"sleeve_source": {
"ITFM005": "name",
"ITTK002": "name",
"ITTM001": "默认(T恤)"
}
},
"GB": {
"country": "英国",
"categories": {
"女士T恤": [
"GBTF004"
],
"男士T恤": [
"GBTM001",
"GBTM010",
"GBTM013"
],
"男士T恤 / GBTM011长袖T": [
"GBTM011"
],
"男士卫衣": [
"GBHM002",
"GBHM003"
],
"童装T恤": [
"GBTK005",
"GBTK008",
"GBTK012"
],
"童装卫衣": [
"GBHK006",
"GBHK007"
]
},
"sleeve_length": {
"GBHK006": "长袖",
"GBHK007": "长袖",
"GBHM002": "长袖",
"GBHM003": "长袖",
"GBTF004": "短袖",
"GBTK005": "短袖",
"GBTK008": "短袖",
"GBTK012": "长袖",
"GBTM001": "短袖",
"GBTM010": "短袖",
"GBTM011": "长袖",
"GBTM013": "短袖"
},
"sleeve_source": {
"GBHK006": "品类推断",
"GBHK007": "品类推断",
"GBHM002": "品类推断",
"GBHM003": "品类推断",
"GBTF004": "默认(T恤)",
"GBTK005": "默认(T恤)",
"GBTK008": "name",
"GBTK012": "name",
"GBTM001": "默认(T恤)",
"GBTM010": "默认(T恤)",
"GBTM011": "name",
"GBTM013": "默认(T恤)"
}
},
"ES": {
"country": "西班牙",
"categories": {
"男士T恤": [
"ESTM002"
],
"男士卫衣": [
"ESHM003",
"ESHM004"
],
"童装T恤": [
"ESTK005"
]
},
"sleeve_length": {
"ESHM003": "长袖",
"ESHM004": "长袖",
"ESTK005": "短袖",
"ESTM002": "短袖"
},
"sleeve_source": {
"ESHM003": "品类推断",
"ESHM004": "品类推断",
"ESTK005": "默认(T恤)",
"ESTM002": "默认(T恤)"
}
},
"CA": {
"country": "加拿大",
"categories": {
"男士T恤": [
"CATM001",
"CATM006",
"CATM007"
],
"男士卫衣": [
"CAHM002"
]
},
"sleeve_length": {
"CAHM002": "长袖",
"CATM001": "短袖",
"CATM006": "短袖",
"CATM007": "短袖"
},
"sleeve_source": {
"CAHM002": "品类推断",
"CATM001": "默认(T恤)",
"CATM006": "默认(T恤)",
"CATM007": "默认(T恤)"
}
},
"JP": {
"country": "日本",
"categories": {
"男士T恤": [
"JPTM001",
"JPTM004",
"JPTM006",
"JPTM007"
],
"男士卫衣": [
"JPHM009",
"JPHM010",
"JPPX012",
"JPPX013"
],
"男士背心": [
"JPTM002",
"JPTM008"
],
"童装T恤": [
"JPTK015"
]
},
"sleeve_length": {
"JPHM009": "长袖",
"JPHM010": "长袖",
"JPPX012": "长袖",
"JPPX013": "长袖",
"JPTK015": "短袖",
"JPTM001": "短袖",
"JPTM002": "无袖",
"JPTM004": "短袖",
"JPTM006": "长袖",
"JPTM007": "短袖",
"JPTM008": "无袖"
},
"sleeve_source": {
"JPHM009": "品类推断",
"JPHM010": "品类推断",
"JPPX012": "品类推断",
"JPPX013": "品类推断",
"JPTK015": "默认(T恤)",
"JPTM001": "默认(T恤)",
"JPTM002": "name",
"JPTM004": "默认(T恤)",
"JPTM006": "name",
"JPTM007": "默认(T恤)",
"JPTM008": "name"
}
},
"DE": {
"country": "德国",
"categories": {
"男士T恤": [
"DETM001",
"DETM002"
],
"男士卫衣": [
"DEHM003"
]
},
"sleeve_length": {
"DEHM003": "长袖",
"DETM001": "短袖",
"DETM002": "短袖"
},
"sleeve_source": {
"DEHM003": "品类推断",
"DETM001": "默认(T恤)",
"DETM002": "默认(T恤)"
}
}
}
+10
View File
@@ -0,0 +1,10 @@
-- 清单『品类含 T恤 且 长袖』的 5 个款号:
-- sleeve_length_type='长袖'season='秋/冬'
UPDATE spu
SET sleeve_length_type = '长袖',
season = '秋/冬'
WHERE UPPER(TRIM(code)) IN ('DG502', 'DG503', 'JPTM006', 'KRTM004', 'PLTF026');
-- 校验
SELECT code, sleeve_length_type, season FROM spu
WHERE UPPER(TRIM(code)) IN ('DG502', 'DG503', 'JPTM006', 'KRTM004', 'PLTF026');
+7
View File
@@ -0,0 +1,7 @@
-- 背心款号(9 个)sleeve_length_type 统一为『无袖』
UPDATE spu SET sleeve_length_type = '无袖'
WHERE UPPER(TRIM(code)) IN ('AUTM003', 'JPTM002', 'JPTM008', 'JSA009', 'TBB001', 'VS001', 'VS002', 'VS003', 'VS004');
-- 校验
SELECT code, country, sleeve_length_type FROM spu
WHERE UPPER(TRIM(code)) IN ('AUTM003', 'JPTM002', 'JPTM008', 'JSA009', 'TBB001', 'VS001', 'VS002', 'VS003', 'VS004');
+2
View File
@@ -188,6 +188,8 @@ class MockBackend:
"image_prompt": (f"{motif}, {art_style}, {palette}, {composition}, "
f"original {art_style} t-shirt print design, "
f"no brand logo, no trademark, no character, no watermark"),
# 自定义模式 mock 兜底:给一个具体改动指令(模板 {delta} 占位符替换用)
"delta": "change the main subject to face the opposite direction",
# 生图参考:每条简报对应其来源爬取图(mock 按图逐张产出简报,顺序一一对应)
"ref_images": [str(paths[i])] if i < len(paths) else [],
"source": "pinterest",
+65 -22
View File
@@ -10,7 +10,7 @@ import os
import re
import time
from pathlib import Path
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
import requests
import yaml
@@ -197,20 +197,38 @@ def _read_title_prompt(tpl_no: str) -> str:
return ""
_TITLE_ROUTE_CACHE: Optional[Dict[str, str]] = None
_TITLE_ROUTE_CACHE_FILE: str = ""
def _load_title_route() -> Dict[str, str]:
"""从 config.yaml 顶层 title_templates.route 读取国家→模板编号路由;缺失/留空回退内置默认。"""
"""从 config.yaml 顶层 title_templates.route 读取国家→模板编号路由;缺失/留空回退内置默认。
路由在运行期不变,模块级缓存避免并发多产品时每次重读 config.yaml(mtime 变化会刷新)。
"""
global _TITLE_ROUTE_CACHE, _TITLE_ROUTE_CACHE_FILE
cfg_file = ""
for base in (runtime_root(), project_root()):
p = base / "config.yaml"
if not p.exists():
continue
if p.exists():
cfg_file = str(p)
break
if cfg_file == _TITLE_ROUTE_CACHE_FILE and _TITLE_ROUTE_CACHE is not None:
return dict(_TITLE_ROUTE_CACHE)
route: Dict[str, str] = {}
try:
data = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
route = (data.get("title_templates") or {}).get("route") or {}
if isinstance(route, dict) and route:
return {str(k): str(v) for k, v in route.items()}
data = yaml.safe_load(open(cfg_file, encoding="utf-8")) or {}
r = (data.get("title_templates") or {}).get("route") or {}
if isinstance(r, dict) and r:
route = {str(k): str(v) for k, v in r.items()}
except Exception as e: # noqa: BLE001
print(f"[titles] 读取 config.yaml title_templates.route 失败: {e}")
return dict(_TITLE_ROUTE_FALLBACK)
if not route:
route = dict(_TITLE_ROUTE_FALLBACK)
cfg_file = ""
_TITLE_ROUTE_CACHE = dict(route)
_TITLE_ROUTE_CACHE_FILE = cfg_file
return dict(route)
def _inject_now(prompt: str) -> str:
@@ -225,12 +243,17 @@ def _inject_now(prompt: str) -> str:
.replace("{season}", season))
def resolve_title_prompt(country: str = "") -> str:
"""按国家解析标题生成提示词(自动注入当前时间变量);未知国家/留空回退模板 1。"""
def resolve_title_prompt(country: str = "", category_path: str = "") -> str:
"""按国家解析标题生成提示词(自动注入当前时间 + 类目路径变量);未知国家/留空回退模板 1。
category_path:商品上传模版「类目」完整路径(如
服装、鞋靴和珠宝饰品>女童时尚>女童服装>女童上衣、T恤、衬衫>女童T恤),
模板含 {category_path} 占位符时替换;未提供/为空则替换为空串。"""
route = _load_title_route()
tpl_no = route.get(country or "", "1")
prompt = _read_title_prompt(tpl_no) or _TITLE_TEMPLATES_FALLBACK.get(tpl_no, _TITLE_TEMPLATES_FALLBACK["1"])
return _inject_now(prompt)
prompt = _inject_now(prompt)
return prompt.replace("{category_path}", str(category_path or ""))
def build_seed_user_prompt(context: Dict[str, Any]) -> str:
@@ -409,6 +432,7 @@ PINTEREST_ANALYZE_SCHEMA = {
"properties": {
"suitable_for_print": {"type": "boolean"},
"image_prompt": {"type": "string"},
"delta": {"type": "string"},
},
"required": ["suitable_for_print", "image_prompt"],
"additionalProperties": False,
@@ -777,32 +801,43 @@ class OpenAICompatBackend(LLMBackend):
{"type": "text", "text": user_prompt},
]
user_content += [{"type": "image_url", "image_url": {"url": u}} for u in data_uris]
payload = {
base_payload = {
"model": model,
"messages": [
{"role": "system", "content": sys_prompt},
{"role": "user", "content": user_content},
],
"temperature": 0.5,
"response_format": {
}
# 兼容厂商差异的尝试链:
# 1) json_schemaOpenAI 原生 strict 输出)
# 2) json_object(部分兼容厂商支持,但要求提示词含 "json",如 DeepSeek → 追加显式指令)
# 3) 无 response_format(依赖 _extract_json 兜底解析)
last_err: Optional[Exception] = None
for variant in ("json_schema", "json_object", "none"):
payload = dict(base_payload)
if variant == "json_schema":
payload["response_format"] = {
"type": "json_schema",
"json_schema": {
"name": PINTEREST_ANALYZE_SCHEMA["name"],
"strict": True,
"schema": PINTEREST_ANALYZE_SCHEMA["schema"],
},
},
}
elif variant == "json_object":
payload["response_format"] = {"type": "json_object"}
payload["messages"][1]["content"] = [
{"type": "text", "text": user_prompt + "\nReturn your answer as a JSON object."},
] + [{"type": "image_url", "image_url": {"url": u}} for u in data_uris]
try:
resp = requests.post(url, json=payload, headers=headers, timeout=180, proxies=NO_PROXY)
resp.raise_for_status()
return str(resp.json()["choices"][0]["message"].get("content") or "")
except Exception as e: # noqa: BLE001 兼容厂商不支持 json_schema
except Exception as e: # noqa: BLE001
last_err = e
_notify_400(e)
payload["response_format"] = {"type": "json_object"}
resp = requests.post(url, json=payload, headers=headers, timeout=180, proxies=NO_PROXY)
resp.raise_for_status()
return str(resp.json()["choices"][0]["message"].get("content") or "")
raise last_err
# 图片输入失败/无有效图片 → 直接放弃该产品(不降级纯文本),由节点跳过后续流程
if not data_uris:
@@ -826,6 +861,10 @@ class OpenAICompatBackend(LLMBackend):
designs_raw = parsed
else:
designs_raw = []
# 自定义模式:分析模型按 custom_analyze_system.md 直接返回 {"delta": "..."}(无 designs 包装)
# → 包装成单条简报;suitable_for_print 缺省 True(下游 _brief_suitable 默认放行)
if not designs_raw and custom_mode and isinstance(parsed, dict) and str(parsed.get("delta") or "").strip():
designs_raw = [parsed]
for i, d in enumerate(designs_raw):
if not isinstance(d, dict):
continue
@@ -833,17 +872,21 @@ class OpenAICompatBackend(LLMBackend):
"topic": term,
"suitable_for_print": bool(d.get("suitable_for_print", True)),
"image_prompt": str(d.get("image_prompt", "")).strip(),
# 自定义模式分析模型产出的「唯一改动」指令,注入生图模板 {delta} 占位符
"delta": str(d.get("delta", "")).strip(),
# 生图参考:每条简报对应其来源爬取图(LLM 按图逐张产出简报,顺序一一对应)
"ref_images": [str(image_paths[i])] if i < len(image_paths) else [],
"source": "pinterest",
})
return designs
def generate_title(self, image_path: str, system_prompt: str = "", country: str = "") -> Dict[str, Any]:
def generate_title(self, image_path: str, system_prompt: str = "", country: str = "",
category_path: str = "") -> Dict[str, Any]:
"""多模态:分析服装图片,生成商品标题(按国家路由模板)。
系统提示词:显式传入优先;否则按 country 经 config.yaml title_templates.route 路由到
prompts/title_prompt_<编号>.md 对应模板(缺失回退内置默认)。
category_path:模版「类目」完整路径,注入模板 {category_path} 占位符(可为空)。
模板 1US/GB/AU/MX)返回 {"en_title","cn_title"}
模板 2(JP)额外返回 {"ja_title"}
模板 3ES)返回 {"es_title","cn_title"}。
@@ -874,7 +917,7 @@ class OpenAICompatBackend(LLMBackend):
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt or resolve_title_prompt(country)},
{"role": "system", "content": system_prompt or resolve_title_prompt(country, category_path)},
{"role": "user", "content": [
{"type": "text", "text": "请分析这张服装图片,按规则输出标题,结果以 JSON 格式返回。"},
{"type": "image_url", "image_url": {"url": data_uri}},
+13 -3
View File
@@ -125,7 +125,8 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
seed: Optional[int] = None,
on_400=None,
on_503=None,
size: str = "1024x1024") -> Optional[str]:
size: str = "1024x1024",
custom_mode: bool = False) -> Optional[str]:
"""生成单张纯印花设计稿(图2)。返回设计稿路径;失败 / 全局 MD5 重复返回 None。
out_stem: 输出文件名主干(不含扩展名),最终文件 = {out_stem}_design.png。
@@ -146,7 +147,15 @@ def generate_design(ib, brief: Dict[str, Any], design_dir: Path, out_stem: str,
ref_images = [str(p) for p in (brief.get("ref_images") or []) if str(p)]
if ref_images and hasattr(ib, "print"):
try:
# 图生图:以爬取图为参考,按分析简报生成原创设计(不复制原图)
if custom_mode:
# 自定义模式:按 image_prompt 图生图(模板已内置防复制/防服装约束,不追加原创化指令)
out_path = ib.print(
img_prompt, ref_images[0], out_path,
brief.get("composite_negative", ""),
extra_images=ref_images[1:] or None,
size=size, seed=seed) # 设计稿尺寸按 config compose.design_size
else:
# Pinterest 模式:追加原创化指令(防止复制原图,保持原有行为)
ref_prompt = img_prompt + (
" Create an ORIGINAL, non-copying flat print design inspired ONLY by "
"the reference image's style and mood. Do NOT reproduce the reference "
@@ -255,7 +264,8 @@ def compose_node(state: Dict[str, Any]) -> Dict[str, Any]:
"""单张设计稿生成(并发线程内调用,每设计一线程)。"""
out_path = generate_design(ib, b, design_dir, f"{country}_{i:02d}",
_safe_errors, seed=_seed,
size=compose_cfg.get("design_size", "1024x1024"))
size=compose_cfg.get("design_size", "1024x1024"),
custom_mode=bool(state.get("custom_mode")))
if out_path is None:
return i, b, None, None
return i, b, out_path, None
+3 -3
View File
@@ -63,9 +63,9 @@ def oss_upload_node(state: Dict[str, Any]) -> Dict[str, Any]:
print(f"[oss] 货号计数已达上限 999,停止上传后续图片({src}")
break
try:
code = f"{prefix}{seq:03d}" # 货号:前缀 + 3 位计数(000 起)
code = str(r.get("img_code") or "") or f"{prefix}{seq:03d}" # 货号:优先用 product 生成的 img_code,缺省自增
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(country, ts, code, _gen_rand4())
key = build_oss_key(country, ts, code, _gen_rand4(), local=compressed)
url = upload_to_oss(oss_cfg, compressed, key)
if url:
r[f"{kind}_url"] = url
@@ -91,7 +91,7 @@ def oss_upload_node(state: Dict[str, Any]) -> Dict[str, Any]:
try:
code = f"{prefix}{seq:03d}"
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(country, ts, code, _gen_rand4())
key = build_oss_key(country, ts, code, _gen_rand4(), local=compressed)
url = upload_to_oss(oss_cfg, compressed, key)
if url:
cc["url"] = url
+1
View File
@@ -75,6 +75,7 @@ def _enrich_briefs(raw_briefs: List[Dict[str, Any]], country: str,
"design_category": classify(term),
"concept": f"围绕「{term}」的原创印花设计",
"image_prompt": str(b.get("image_prompt") or "").strip(),
"delta": str(b.get("delta") or "").strip(),
"ref_images": [str(p) for p in (b.get("ref_images") or []) if str(p)],
"source_md5": str(b.get("source_md5") or "").strip().lower(),
"brief_id": str(b.get("brief_id") or "").strip(),
+14 -2
View File
@@ -195,7 +195,7 @@ def _process_spu(
db_path, basemap_root, material_root, category, prod_dir, brief, ib,
spu, sku_code, pcfg, errors, shared_design=None, title_backend=None, country="",
img_code="", model_img=None, design_size="1024x1024", compose_size="1536x2048",
on_503=None, model_kind="model",
on_503=None, model_kind="model", category_path="",
) -> Optional[Dict[str, Any]]:
"""处理单个款号:选色 → 底图 → 设计稿 → (mark==1) 模特 → 合成 → 模板导出。
shared_design: compose 节点生成的纯印花设计稿路径(图2);为 None 时回退本节点 generate。
@@ -434,7 +434,7 @@ def _process_spu(
title_img = (result.get("composite_path") or result.get("printed_path")
or result.get("design_path"))
if title_img:
t = title_backend.generate_title(title_img, country=country)
t = title_backend.generate_title(title_img, country=country, category_path=category_path)
if t.get("en_title") or t.get("cn_title") or t.get("ja_title") or t.get("es_title"):
result["en_title"] = t.get("en_title", "")
result["cn_title"] = t.get("cn_title", "")
@@ -482,6 +482,17 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
spu_tasks = pcfg.get("spu_tasks") or []
spu_count = int(pcfg.get("spu_count") or 0)
# 标题生成用:模版「类目」完整路径(如 服装、鞋靴和珠宝饰品>女童时尚>…>女童T恤),
# 注入标题提示词 {category_path} 占位符;模板未配置/读取失败为空串
category_path = ""
try:
_tp = str(pcfg.get("template_path") or "").strip()
if _tp:
from graph.seed_shot import read_template_category
category_path = read_template_category(_tp)
except Exception as _e: # noqa: BLE001
print(f"[product] 读取模版类目路径失败(标题 category_path 留空): {_e}")
# 1) 选简报(优先 safe
safe = [b for b in briefs if b.get("risk_level") == "safe"] or briefs
if not safe:
@@ -634,6 +645,7 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
country, img_code=img_code,
model_img=_src.get("img"), # 按任务序号取独立随机图源
model_kind=_src.get("kind", "model"),
category_path=category_path,
design_size=str((config.get("compose") or {}).get("design_size") or "1024x1024"),
compose_size=str((config.get("compose") or {}).get("size") or "1536x2048"))
if r:
+33 -18
View File
@@ -39,9 +39,9 @@ REVIEW_REBRAND_HINT = (
"a generic, non-infringing homage in the same mood, clearly distinct from the original."
)
# —— 自定义模式(custom)生图模板:固定前缀 + 分析模型 image_prompt + 固定负向 ——
# 自定义模式分析模型产出的是「新设计描述」,生图时套用这套固定模板(含防复制/防服装约束),
# 负向用固定文本(写入 composite_negativecompose 生图时作为负向参数传给图像后端)
# —— 自定义模式(custom)生图模板:固定前缀 + 分析模型 image_prompt + delta 改动指令 ——
# 自定义模式分析模型产出的是「新设计描述 + 唯一改动指令 delta」,生图时套用这套固定模板
# (含防复制/防服装约束),不再使用负向提示词——约束已全部内置进模板
CUSTOM_IMAGE_PROMPT_TEMPLATE = (
"Use the attached bestseller product photo only as loose inspiration for "
"overall mood, theme, era and style genre — do NOT reproduce, trace, "
@@ -53,11 +53,6 @@ CUSTOM_IMAGE_PROMPT_TEMPLATE = (
"base the new design on THOSE observed traits. Ignore the model, background "
"and photo quality. Then: {image_prompt}"
)
CUSTOM_NEGATIVE_PROMPT = (
"copy of reference artwork, lookalike of the bestseller print, characters, "
"mascots, likenesses, logos, trademarks, watermark, photorealistic shirt, "
"apparel, product mockup, model, garment, hanger, busy background"
)
# 图像生成策略敏感词 → 安全等效描述(生成设计稿前清洗 motif,
# 避免 gpt-image 等内容策略频繁拦截导致"生图限制多")
@@ -84,6 +79,18 @@ def _safe_motif(motif: str) -> str:
return motif
def _read_custom_prompt_md(filename: str) -> str:
"""读取 prompts/<filename>(自定义模式生图模板),优先运行根 exe 旁、回退数据根;无/空返回空串。"""
from graph.paths import project_root, runtime_root
for base in (runtime_root(), project_root()):
p = base / "prompts" / filename
if p.exists():
t = p.read_text(encoding="utf-8").strip()
if t:
return t
return ""
@with_fallback("prompt_build")
def prompt_node(state: Dict[str, Any]) -> Dict[str, Any]:
screened: List[Dict[str, Any]] = state.get("screened") or []
@@ -101,8 +108,11 @@ def prompt_node(state: Dict[str, Any]) -> Dict[str, Any]:
print_suffix = (pp.get("print_suffix") or "").strip() or PINTEREST_PRINT_SUFFIX
spelling_rule = (pp.get("spelling_rule") or "").strip() or SPELLING_RULE
review_rebrand_hint = (pp.get("review_rebrand_hint") or "").strip() or REVIEW_REBRAND_HINT
custom_ip_tpl = (pp.get("image_prompt_template") or "").strip() or CUSTOM_IMAGE_PROMPT_TEMPLATE
custom_neg = (pp.get("negative_prompt") or "").strip() or CUSTOM_NEGATIVE_PROMPT
# 自定义模式生图模板优先读 prompts/custom_image_prompt.md(可编辑),
# 其次 config.custom.prompt_pieces,最后回退代码内置默认。
# 自定义模式不再使用负向提示词(约束已内置进模板),不再读取 custom_negative_prompt.md。
custom_ip_tpl = _read_custom_prompt_md("custom_image_prompt.md") \
or (pp.get("image_prompt_template") or "").strip() or CUSTOM_IMAGE_PROMPT_TEMPLATE
briefs: List[Dict[str, Any]] = []
for r in screened:
@@ -122,15 +132,20 @@ def prompt_node(state: Dict[str, Any]) -> Dict[str, Any]:
prompts = assemble_prompts(motif, art_style, palette, composition, tpls, country)
llm_ip = (r.get("image_prompt") or "").strip()
if r.get("source") == "pinterest" and llm_ip:
delta = (r.get("delta") or "").strip()
if r.get("source") == "pinterest":
if bool(state.get("custom_mode")):
# —— 自定义模式:固定模板(前缀 + 分析 image_prompt),负向用固定文本 ——
# 模板含防复制/防服装约束,不再追加 print_suffix;负向写入 composite_negative
# compose 生图时作为负向参数传给图像后端。
prompts["image_prompt"] = custom_ip_tpl.replace("{image_prompt}", llm_ip)
prompts["composite_negative"] = custom_neg
print(f"[prompt] 自定义模式按固定模板拼 image_prompt(含固定负向): 「{r['topic']}")
else:
# —— 自定义模式:固定模板(前缀 + 分析 image_prompt + delta 改动指令),负向留空 ——
# 新分析模板只产出 delta(无 image_prompt),生图模板也只引用 {delta};
# 只要 delta 或 image_prompt 任一存在即可装配,不再使用负向提示词
# (约束已全部内置进模板),composite_negative 置空避免后端追加 Negative 段。
if llm_ip or delta:
prompts["image_prompt"] = (
custom_ip_tpl.replace("{delta}", delta).replace("{image_prompt}", llm_ip)
)
prompts["composite_negative"] = ""
print(f"[prompt] 自定义模式按固定模板拼 image_prompt(含 delta 改动指令,无负向): 「{r['topic']}")
elif llm_ip:
# —— Pinterest 参考模式:跳过四要素模板,按 3 段结构拼 image_prompt ——
# ① image_prompt(分析模型产出) + 固定输出形态后缀 PINTEREST_PRINT_SUFFIX
# ② 拼写锁定句 SPELLING_RULE(仅当 LLM image_prompt 已含引号文字段时)
+5 -3
View File
@@ -164,7 +164,7 @@ def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
try:
compressed = compress_for_oss(pth, str(Path(pth).with_suffix(".oss.jpg")))
url = upload_to_oss(oss_cfg, compressed,
build_oss_key(country, ts, code, _gen_rand4()))
build_oss_key(country, ts, code, _gen_rand4(), local=compressed))
if url:
urls.append(url)
r["seed_shot_urls"] = urls
@@ -175,8 +175,10 @@ def seed_shot_node(state: Dict[str, Any]) -> Dict[str, Any]:
return {"spu_code": r.get("spu_code"), "sku_code": r.get("sku_code"),
"paths": paths, "urls": urls}
# 并发:每个产品一个独立线程默认);config.seed_shot.concurrency 可覆盖
seed_concurrency = int((config.get("seed_shot") or {}).get("concurrency") or 0) or len(products) or 1
# 并发:每个产品一个独立线程默认上限 5(与 product 一致,避免多产品压垮图像网关),
# config.seed_shot.concurrency 可显式覆盖(含 0/留空→默认 5)
seed_concurrency = int((config.get("seed_shot") or {}).get("concurrency") or 0) or 5
seed_concurrency = min(seed_concurrency, len(products)) if products else 1
if len(products) > 1:
print(f"[seed_shot] 并发 {seed_concurrency} 生成种草图({len(products)} 个产品)")
with concurrent.futures.ThreadPoolExecutor(max_workers=seed_concurrency) as _ex:
+3 -1
View File
@@ -101,7 +101,8 @@ def template_export_node(state: Dict[str, Any]) -> Dict[str, Any]:
from graph.template_export import (export_products, _resolve_component_map,
_resolve_season_map, _resolve_pattern_map,
_resolve_target_audience_map, _resolve_kids_type_map)
_resolve_target_audience_map, _resolve_kids_type_map,
_resolve_kids_pattern_map)
tdir = (pcfg.get("template_dir") or "").strip() or str(Path(tp).parent)
prod_dir = output_dir / "product"
prod_dir.mkdir(parents=True, exist_ok=True)
@@ -159,6 +160,7 @@ def template_export_node(state: Dict[str, Any]) -> Dict[str, Any]:
pattern_map=_resolve_pattern_map(config),
target_audience_map=_resolve_target_audience_map(config),
kids_type_map=_resolve_kids_type_map(config),
kids_pattern_map=_resolve_kids_pattern_map(config),
)
for r in products:
if (r.get("composite_path") or r.get("printed_path")) and (r.get("en_title") or "").strip():
+10 -2
View File
@@ -84,7 +84,15 @@ def upload_to_oss(cfg: dict, local_path: str, object_key: str) -> Optional[str]:
return None
def build_oss_key(country: str, timestamp: str, code: str, rand4: str, ext: str = ".jpg") -> str:
"""对象 key{国家}/{时间戳}/{货号}_{4位随机}.jpg(如 GB/20260820150945/DG000_Ab3x.jpg)。"""
def build_oss_key(country: str, timestamp: str, code: str, rand4: str,
ext: str = "", local: Optional[str] = None) -> str:
"""对象 key{国家}/{时间戳}/{货号}_{4位随机}.jpg(如 GB/20260820150945/DG000_Ab3x.jpg)。
ext 未显式指定时,优先从 local(实际待上传文件)的真实后缀推导——
文件可能是 png/jpg 而非固定 jpg;local 也未提供时回退 ".jpg"
"""
if not ext.strip() and local:
ext = Path(local).suffix if Path(local).suffix else ".jpg"
ext = ext.strip() or ".jpg"
safe = lambda s: "".join(c for c in (s or "") if c.isalnum() or c in "-_").strip()
return f"{safe(country)}/{safe(timestamp)}/{safe(code)}_{safe(rand4)}{ext}"
+3
View File
@@ -189,6 +189,9 @@ def list_valid_images(folder: Any) -> List[Path]:
return []
out: List[Path] = []
for f in sorted(d.rglob("*")):
# 跳过隐藏/缓存子目录(如 .compressed 压缩缓存),避免把压缩产物当有效图片
if any(part.startswith(".") for part in f.relative_to(d).parts[:-1]):
continue
if f.is_file() and f.suffix.lower() in _IMG_EXTS:
out.append(f)
return out
+37 -13
View File
@@ -20,6 +20,7 @@ from pathlib import Path
from typing import Any, Dict, List, Optional
from graph.paths import project_root, runtime_root
from graph.validate import ThreadSafeErrors
class PinterestPipeline:
@@ -83,6 +84,12 @@ class PinterestPipeline:
self._country_config = state.get("country_config") or {}
# 补充重试次数:设计生成失败/侵权时,从图池取新图重新分析的最多尝试次数
self._supply_attempts = int((self.config.get("pinterest") or {}).get("supply_attempts", 3))
# 自定义模式标志:pinterest_init 先于 pinterest_custom_load 执行(state.custom_mode 尚未置位),
# 故从 config 判断(mode=custom 或 custom_image_dir 非空),供 generate_design 决定是否追加原创化指令
_pcfg = self.config.get("pinterest") or {}
self.custom_mode = bool(state.get("custom_mode")) \
or (str(_pcfg.get("mode") or "").strip().lower() == "custom") \
or bool(str(_pcfg.get("custom_image_dir") or "").strip())
# 400 计数(per 种子词):多模态 + 生图模型合计,超限放弃当前种子词
self._err400_lock = threading.Lock()
@@ -470,6 +477,9 @@ class PinterestPipeline:
# 单条简报完整链路:设计 → 三合一 → OSS → 种草图
# ------------------------------------------------------------------ #
def _process_one(self, brief: Dict[str, Any], idx: int) -> None:
# 每个 worker 用独立线程安全错误收集器,后台调用链(generate_design/_process_spu
# 的 append 与 finish() 的 list(self._errors) 读取不再互相竞态,结束时统一合并。
errs = ThreadSafeErrors()
try:
if self.is_fatal_503_aborted():
return
@@ -480,7 +490,7 @@ class PinterestPipeline:
spu, skus = self._worklist[idx]
img_code = f"{self._prefix}{idx:03d}"
# 1) 生成设计(compose)——直接按货号命名 designs/{img_code}_design.png
design_path = self._gen_design(brief, img_code)
design_path = self._gen_design(brief, img_code, errs)
if self.is_fatal_503_aborted():
return
if self.is_400_aborted():
@@ -496,7 +506,7 @@ class PinterestPipeline:
if new_brief is None:
return
brief = new_brief
design_path = self._gen_design(brief, img_code)
design_path = self._gen_design(brief, img_code, errs)
if self.is_fatal_503_aborted():
return
if design_path:
@@ -505,7 +515,7 @@ class PinterestPipeline:
return
brief["design_path"] = design_path
# 2) 三合一(product)——同一货号;task_idx=本次简报序号,模特按任务独立随机
prod = self._process_spu(brief, spu, skus, img_code, design_path, task_idx=idx)
prod = self._process_spu(brief, spu, skus, img_code, design_path, task_idx=idx, errors=errs)
if self.is_fatal_503_aborted():
return
if not prod:
@@ -532,6 +542,9 @@ class PinterestPipeline:
"message": f"简报 {idx} 处理失败: {e}", "trace": ""})
print(f"[pinterest_pipeline] 简报 {idx} 处理失败: {e}")
finally:
# 合并本 worker 收集的错误到共享 _errors(持锁,供 finish() 安全读取)
with self._errors_lock:
self._errors.extend(list(errs))
with self._cond:
self._in_flight = max(0, self._in_flight - 1)
self._cond.notify_all()
@@ -571,7 +584,7 @@ class PinterestPipeline:
print(f"[pinterest_pipeline] 读回落盘产品失败: {e}")
return out
def _gen_design(self, brief: Dict[str, Any], img_code: str) -> Optional[str]:
def _gen_design(self, brief: Dict[str, Any], img_code: str, errors: ThreadSafeErrors) -> Optional[str]:
if self._ib is None:
return None
try:
@@ -587,16 +600,16 @@ class PinterestPipeline:
self.record_503()
self.abort_unfinished()
return generate_design(self._ib, brief, design_dir, img_code, self._errors,
return generate_design(self._ib, brief, design_dir, img_code, errors,
on_400=_on_400, on_503=_on_503,
size=str((self.config.get("compose") or {}).get("design_size") or "1024x1024"))
size=str((self.config.get("compose") or {}).get("design_size") or "1024x1024"),
custom_mode=self.custom_mode)
except Exception as e: # noqa: BLE001
if self.is_fatal_503(e):
self.record_503()
self.abort_unfinished()
return None
with self._errors_lock:
self._errors.append({"node": "compose", "type": type(e).__name__,
errors.append({"node": "compose", "type": type(e).__name__,
"message": f"设计稿生成失败 {brief.get('topic', '')}: {e}",
"trace": ""})
return None
@@ -677,18 +690,29 @@ class PinterestPipeline:
"model_kind": src.get("kind", "model")}
def _process_spu(self, brief: Dict[str, Any], spu, skus: str, img_code: str,
design_path: str, task_idx: int = 0) -> Optional[Dict[str, Any]]:
design_path: str, task_idx: int = 0, errors: Optional[ThreadSafeErrors] = None) -> Optional[Dict[str, Any]]:
from graph.nodes.product_node import _process_spu as _ps
prod_dir = self.output_dir / "product"
prod_dir.mkdir(parents=True, exist_ok=True)
# 设计稿已按货号命名(designs/{img_code}_design.png),直接复用,无需再拷贝
brief = dict(brief)
brief["design_path"] = design_path
# 标题生成用:模版「类目」完整路径(注入标题提示词 {category_path}),懒加载缓存一次
if not hasattr(self, "_category_path"):
self._category_path = ""
try:
_tp = str((self.config.get("product") or {}).get("template_path") or "").strip()
if _tp:
from graph.seed_shot import read_template_category
self._category_path = read_template_category(_tp)
except Exception as _e: # noqa: BLE001
print(f"[pinterest_pipeline] 读取模版类目路径失败(标题 category_path 留空): {_e}")
r = _ps(self._db_path, self._basemap_root, self._material_root, self._category,
prod_dir, brief, self._ib, spu, skus, self.config.get("product") or {},
self._errors, design_path, self._title_backend, self.country,
errors, design_path, self._title_backend, self.country,
img_code=img_code,
on_503=lambda: (self.record_503(), self.abort_unfinished()),
category_path=self._category_path,
**self._model_source(task_idx, spu))
if r:
r["img_code"] = img_code
@@ -710,7 +734,7 @@ class PinterestPipeline:
continue
try:
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4())
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4(), local=compressed)
url = upload_to_oss(self._oss_cfg, compressed, key)
if url:
r[f"{kind}_url"] = url
@@ -728,7 +752,7 @@ class PinterestPipeline:
continue
try:
compressed = compress_for_oss(src, str(Path(src).with_suffix(".oss.jpg")))
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4())
key = build_oss_key(self.country, self.task_timestamp, base_code, _gen_rand4(), local=compressed)
url = upload_to_oss(self._oss_cfg, compressed, key)
if url:
cc["url"] = url
@@ -796,7 +820,7 @@ class PinterestPipeline:
compressed = compress_for_oss(pth, str(Path(pth).with_suffix(".oss.jpg")))
url = upload_to_oss(self._oss_cfg, compressed,
build_oss_key(self.country, self.task_timestamp,
code, _gen_rand4()))
code, _gen_rand4(), local=compressed))
if url:
urls.append(url)
r["seed_shot_urls"] = urls
+1
View File
@@ -30,6 +30,7 @@ class AgentState(TypedDict, total=False):
seed_words: Dict[str, Any] # seed 产出:动态种子词(含 llm_style_seeds / llm_related_seeds
# —— Pinterest 参考模式(独立于 Google Trends 采集链路)——
custom_mode: bool # 自定义模式标志(pinterest_custom_load 置 TrueLangGraph 仅合并声明键,必须显式声明否则丢失)
pinterest_search_terms: List[str] # pinterest_search 产出:LLM 生成的搜索词
pinterest_images: Dict[str, List[str]] # pinterest_scrape 产出:搜索词 → 爬取图片路径列表
pinterest_briefs: List[Dict[str, Any]] # pinterest_analyze 产出:LLM 分析图片的原始设计简报
+91 -2
View File
@@ -108,6 +108,48 @@ def _map_pattern(value: Any, gender: Optional[str], pattern_map: Optional[Dict[s
return mapping.get(v, value)
# 女童图案值字典映射:db pattern 值 → 女童模板下拉框选项(男童保留原值)。
# 女童模板遇到 pattern="印花" 时映射为「卡通」;该字典为「内置默认」,
# 运行时可被 config.yaml 顶层 pattern_map.girl_kids 覆盖(缺失/留空回退这里)。
_PATTERN_KIDS_MAP = {
"girl_kids": {"印花": "卡通"},
}
def _resolve_kids_pattern_map(config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""从 config 读取可配置女童图案映射(config.pattern_map.girl_kids),缺失/留空回退内置默认。
仅作用于 spu.pattern 字段(gender=girl_kids 时映射;男童/成人/None 保留原值)。
"""
if not config:
return {g: dict(m) for g, m in _PATTERN_KIDS_MAP.items()}
kids = None
try:
pm = config.get("pattern_map") or {}
kids = pm if isinstance(pm, dict) else {}
except Exception: # noqa: BLE001
kids = {}
if not isinstance(kids, dict) or not kids:
return {g: dict(m) for g, m in _PATTERN_KIDS_MAP.items()}
merged = {g: dict(m) for g, m in _PATTERN_KIDS_MAP.items()}
for g, m in kids.items():
if isinstance(m, dict):
merged.setdefault(str(g), {}).update(
{str(k): v for k, v in m.items() if v not in (None, "")})
return merged
def _map_kids_pattern(value: Any, gender: Optional[str],
kids_pattern_map: Optional[Dict[str, Any]] = None) -> Any:
"""图案值按童装性别映射(仅 spu.pattern 字段):gender 为 girl_kids 时
查女童图案映射表(默认内置、可由 config 覆盖,找不到保留原值),如「印花」→「卡通」。
男童/成人保留原值。"""
if gender != "girl_kids" or value is None:
return value
mapping = kids_pattern_map if kids_pattern_map is not None else _PATTERN_KIDS_MAP
return mapping.get("girl_kids", {}).get(str(value).strip(), value)
def _resolve_season_map(config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""从 config 读取可配置季节映射(config.season_map.female),缺失/留空回退内置默认。
@@ -236,8 +278,9 @@ def _size_rank(size) -> tuple:
return (9, 0, "")
if s in ("ONESIZE", "ONE SIZE", "FREESIZE", "FREE SIZE", "均码"):
return (8, 0, s)
if s == "XS":
return (2, 0, s)
m = re.fullmatch(r"(X+)S", s) # XS/XXS/XXXS(童装常见 XXS
if m:
return (2, -len(m.group(1)), s)
if s in ("S", "M", "L"):
return (2, {"S": 1, "M": 2, "L": 3}[s], s)
m = re.fullmatch(r"(X+)(L)", s) # XL/XXL/XXXL/XXXXL/XXXXXL
@@ -409,12 +452,15 @@ 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,
color: Optional[str] = None,
fabric_headers: Optional[List[str]] = None,
details_headers: Optional[List[str]] = None,
target_audience_headers: Optional[List[str]] = None,
gender: Optional[str] = None,
component_map: Optional[Dict[str, Any]] = None,
season_map: Optional[Dict[str, Any]] = None,
pattern_map: Optional[Dict[str, Any]] = None,
target_audience_map: Optional[Dict[str, Any]] = None,
kids_type_map: Optional[Dict[str, Any]] = None,
kids_pattern_map: Optional[Dict[str, Any]] = None,
oss_code: str = "",
model_info: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
"""构造一行 SPU(固定字段:SKC货号=当前生成货号、风格=休闲、商品产地=中国大陆、产地省份=广东省;多颜色时用色值区分)。
@@ -431,6 +477,8 @@ def _build_spu_row(spu: Dict[str, Any], spu_code: str,
"基础信息-商品层级": "spu",
"SKC货号": oss_code or spu_code, # SKC货号 = 当前生成货号 oss_code(无则回退 spu_code
"风格": "休闲", # style 路由为"休闲"(用户要求)
"风格1": "休闲", # 新增:匹配到「风格1」列填休闲
"风格2": "运动", # 新增:匹配到「风格2」列填运动
"商品产地": "中国大陆", # 所有国家统一「中国大陆」,不读经营站点/不做字典匹配(用户要求)
"产地省份": "广东省", # 新增:精确匹配「产地省份」列,统一填「广东省」(用户要求)
"款式来源": "现货款", # SPU商品属性-款式来源 统一填「现货款」(用户要求)
@@ -445,6 +493,7 @@ def _build_spu_row(spu: Dict[str, Any], spu_code: str,
v = _map_season(v, gender, season_map)
elif dbk == "pattern":
v = _map_pattern(v, gender, pattern_map)
v = _map_kids_pattern(v, gender, kids_pattern_map) # 女童:印花→卡通(男童/成人保留)
elif dbk == "target_audience":
v = _map_target_audience(v, gender, target_audience_map)
if v not in (None, ""):
@@ -453,6 +502,15 @@ def _build_spu_row(spu: Dict[str, Any], spu_code: str,
if fabric not in (None, "") and fabric_headers:
for h in fabric_headers:
row[h] = fabric
details = spu.get("details")
if details not in (None, "") and details_headers:
for h in details_headers:
row[h] = details
ta = spu.get("target_audience")
if ta not in (None, "") and target_audience_headers:
ta = _map_target_audience(ta, gender, target_audience_map)
for h in target_audience_headers:
row[h] = ta
if model_info:
if model_info.get("model"):
row["试穿模特"] = model_info["model"]
@@ -489,6 +547,16 @@ def _find_fabric_headers(router) -> List[str]:
return [str(k) for k in router.column_map if "面料弹性" in str(k)]
def _find_details_headers(router) -> List[str]:
"""定位「细节」扩展列(细节1/细节2/细节3 精确列名匹配);匹配到多个时全部填 spu.details。"""
return [str(k) for k in router.column_map if str(k).strip() in ("细节1", "细节2", "细节3")]
def _find_target_audience_headers(router) -> List[str]:
"""定位「适用人群」扩展列(适用人群1 精确列名匹配);匹配到则填 target_audience 映射后的值。"""
return [str(k) for k in router.column_map if str(k).strip() in ("适用人群1",)]
def _find_sa_size_headers(router) -> List[str]:
"""定位所有「沙特阿拉伯码」列(尺码表-沙特阿拉伯码…模糊匹配);匹配到多个时全部填 sku.size。"""
return [str(k) for k in router.column_map if "沙特阿拉伯码" in str(k)]
@@ -919,6 +987,8 @@ def _insert_product_block(
seed_shot_urls: Optional[List[str]] = None,
bust_headers: Optional[List[str]] = None,
fabric_headers: Optional[List[str]] = None,
details_headers: Optional[List[str]] = None,
target_audience_headers: Optional[List[str]] = None,
sa_size_headers: Optional[List[str]] = None,
gender: Optional[str] = None,
component_map: Optional[Dict[str, Any]] = None,
@@ -926,6 +996,7 @@ def _insert_product_block(
pattern_map: Optional[Dict[str, Any]] = None,
target_audience_map: Optional[Dict[str, Any]] = None,
kids_type_map: Optional[Dict[str, Any]] = None,
kids_pattern_map: Optional[Dict[str, Any]] = None,
suggested_price_ratio: float = 0.0,
suggested_price_headers: Optional[List[str]] = None,
suggested_unit_headers: Optional[List[str]] = None,
@@ -965,10 +1036,13 @@ def _insert_product_block(
# + 全部颜色尺码 SKU 行(色值在 SKU 行区分)
block_rows.append(router.insert(
_build_spu_row(spu, spu_code, fabric_headers=fabric_headers,
details_headers=details_headers,
target_audience_headers=target_audience_headers,
gender=gender, component_map=component_map,
season_map=season_map, pattern_map=pattern_map,
target_audience_map=target_audience_map,
kids_type_map=kids_type_map,
kids_pattern_map=kids_pattern_map,
oss_code=oss_code, model_info=model_info),
match="exact",
))
@@ -1008,10 +1082,13 @@ def _insert_product_block(
# 单 SPU + 多颜色变体:1 个 SPU 行(无色值)+ 所有颜色所有尺码 SKU 行(色值区分)
block_rows.append(router.insert(
_build_spu_row(spu, spu_code, fabric_headers=fabric_headers,
details_headers=details_headers,
target_audience_headers=target_audience_headers,
gender=gender, component_map=component_map,
season_map=season_map, pattern_map=pattern_map,
target_audience_map=target_audience_map,
kids_type_map=kids_type_map,
kids_pattern_map=kids_pattern_map,
oss_code=oss_code, model_info=model_info), match="exact"))
multi_variant = len(skus_by_color) > 1
for ci, (sc, skus) in enumerate(skus_by_color):
@@ -1077,6 +1154,7 @@ def export_product(
pattern_map: Optional[Dict[str, Any]] = None,
target_audience_map: Optional[Dict[str, Any]] = None,
kids_type_map: Optional[Dict[str, Any]] = None,
kids_pattern_map: Optional[Dict[str, Any]] = None,
) -> Path:
"""生成商品上传"已填写"模板(支持多产品合并到同一文件)。
@@ -1106,6 +1184,8 @@ def export_product(
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
fabric_headers = _find_fabric_headers(router) # 面料弹性列(SPU商品属性-面料弹性…检测到才填)
details_headers = _find_details_headers(router) # 细节扩展列(细节1/细节2/细节3…精确匹配)
target_audience_headers = _find_target_audience_headers(router) # 适用人群扩展列(适用人群1…精确匹配)
sa_size_headers = _find_sa_size_headers(router) # 沙特阿拉伯码列(尺码表-沙特阿拉伯码…检测到才填)
gender = _template_gender(template_path) # 类目含「男」→male /「女」→female(成分值映射用)
suggested_price_headers = _find_suggested_price_headers(router) # 建议售价列(不含单位)
@@ -1124,6 +1204,8 @@ def export_product(
seed_shot_urls=seed_shot_urls,
bust_headers=bust_headers,
fabric_headers=fabric_headers,
details_headers=details_headers,
target_audience_headers=target_audience_headers,
sa_size_headers=sa_size_headers,
gender=gender,
component_map=component_map,
@@ -1131,6 +1213,7 @@ def export_product(
pattern_map=pattern_map,
target_audience_map=target_audience_map,
kids_type_map=kids_type_map,
kids_pattern_map=kids_pattern_map,
suggested_price_ratio=suggested_price_ratio,
suggested_price_headers=suggested_price_headers,
@@ -1160,6 +1243,7 @@ def export_products(
pattern_map: Optional[Dict[str, Any]] = None,
target_audience_map: Optional[Dict[str, Any]] = None,
kids_type_map: Optional[Dict[str, Any]] = None,
kids_pattern_map: Optional[Dict[str, Any]] = None,
) -> Path:
"""批量合并导出:所有产品一次性写入同一模板,只打开/保存一次。
@@ -1176,6 +1260,8 @@ def export_products(
price_headers = _find_price_headers(router) # 申报价格列(美站/日站/英站…模糊匹配,多个全填)
bust_headers = _find_bust_headers(router) # 胸围列(基码表-胸围(cm)/胸围全围(cm)…多个全填)
fabric_headers = _find_fabric_headers(router) # 面料弹性列(SPU商品属性-面料弹性…检测到才填)
details_headers = _find_details_headers(router) # 细节扩展列(细节1/细节2/细节3…精确匹配)
target_audience_headers = _find_target_audience_headers(router) # 适用人群扩展列(适用人群1…精确匹配)
sa_size_headers = _find_sa_size_headers(router) # 沙特阿拉伯码列(尺码表-沙特阿拉伯码…检测到才填)
gender = _template_gender(template_path) # 类目含「男」→male /「女」→female(成分值映射用)
suggested_price_headers = _find_suggested_price_headers(router) # 建议售价列(不含单位)
@@ -1202,6 +1288,8 @@ def export_products(
seed_shot_urls=r.get("seed_shot_urls"),
bust_headers=bust_headers,
fabric_headers=fabric_headers,
details_headers=details_headers,
target_audience_headers=target_audience_headers,
sa_size_headers=sa_size_headers,
gender=gender,
component_map=component_map,
@@ -1209,6 +1297,7 @@ def export_products(
pattern_map=pattern_map,
target_audience_map=target_audience_map,
kids_type_map=kids_type_map,
kids_pattern_map=kids_pattern_map,
suggested_price_ratio=suggested_price_ratio,
suggested_price_headers=suggested_price_headers,
+14 -16
View File
@@ -1,19 +1,17 @@
You are a POD T-shirt design analyst working in CUSTOM-REFERENCE mode. You are given ONE existing bestseller product photo — an on-model shot or a flat-lay shot of a printed garment. Your job:
1. Locate the printed artwork on the garment and judge whether that PRINT can inspire a new T-shirt design (ignore the shirt itself, the model, the background, watermarks and photo quality — judge only the printed artwork).
2. Write an ORIGINAL design brief that keeps the bestseller's general vibe (theme, style genre, era, mood) but is a clearly different, non-infringing work.
You are a POD design analyst. Look at the printed artwork in this image and decide the
SMALLEST change that makes it a new artwork rather than a copy.
RULES:
0. REFERENCE HANDLING — the attached photo is a product mockup of an existing bestseller, not an artwork file. Use it ONLY to identify the printed artwork and distill its appeal into generic style keywords (retro, y2k, minimal, grunge, boho, kawaii, western, cottagecore, vintage cartoon...). Never reproduce, trace, rearrange, recolor or closely imitate the print, its layout, characters or text.
1. SAME SUBJECT FAMILY, NEW EXECUTION — the new design must belong to the same visual family as the bestseller print so a buyer would instantly see they are "same style, different shirt". LOCK these elements to the bestseller's: (a) general subject category (e.g., human/animal figure silhouette, celestial object, floral, vehicle, typography-led...), (b) art technique (e.g., grunge ink silhouette, clean line art, watercolor, distressed collage...), (c) palette (monochrome / muted / neon...). VARY these: the exact subject, the pose, the composition and layout, and any decorative framing. The result must be clearly original — no traced or rearranged reuse of the reference artwork — while staying recognizably in the same genre.
2. IP SCREENING — if the bestseller print contains brand logos, trademarks, slogans, mascots, copyrighted characters (including stylized or silhouette versions), real people/celebrities, movie/game/anime/band IP, or lyrics, do NOT imitate them; swap in fully generic equivalents (e.g., an unnamed cartoon animal instead of a recognizable mascot). Also avoid politics, religion, violence, sexual content, alcohol, drugs, gambling, flags, death/occult themes.
3. FORM — ONE clear central subject with strong graphic composition; print-ready standalone artwork. ANY colors are fine — rich palettes, gradients and detailed shading are all acceptable. Because the reference is a product photo, explicitly exclude the garment and the photo itself: never describe shirts, models, hangers, scenes or backgrounds. Photographic elements in the print may be re-rendered as detailed full-color illustrations, retro badges or vintage stickers.
4. TEXT — short ORIGINAL English wording (1-6 words) allowed; wrap exact words in double quotes and demand exact spelling; integrate into composition. Never reuse, translate or near-duplicate the bestseller print's wording; no brand/band/movie names or famous slogans. When unsure, omit.
Choose ONE delta, in this order of preference:
1. same subject, new pose/angle/action
2. if the subject is a distinctive character: swap to a close relative in the same category
3. if the wording is a famous slogan/lyrics: change only the wording
4. if the print is typography-led or scenery-led: alter ONE compositional element
suitable_for_print: judge ONLY the printed artwork extracted from the bestseller photo. DEFAULT TRUE for graphics, illustrations, badges, vector art, typography prints, or prints visible on mockups. FALSE only for: blank garments with no artwork, prints that are indiscernible due to watermark, blur or very low quality, pure subjectless photo scenery, memes/screenshots/collages with no usable motif. Even when FALSE, still fill all fields so downstream never breaks.
Output ONLY one sentence, fully concrete — exact facing direction, exact limb/wing/head
positions, or exact new words. Never write vague phrases like "a new pose" or "more dynamic".
Do not mention layout, palette, texture or typography — those stay unchanged.
If the subject contains a brand mascot / copyrighted character / celebrity / logo, that
replacement IS the delta (generic equivalent, same style, same position).
image_prompt = two parts:
1) mandatory opener: "First, carefully examine the printed artwork on the garment in the attached photo: zoom in mentally on the print area, identify its subject, technique, palette and layout, and base the new design on THOSE observed traits. Ignore the model, background and photo quality. Then: use the attached bestseller product photo only as loose inspiration for overall mood, theme, era and style genre — do NOT reproduce, trace, rearrange, recolor or closely imitate its printed artwork, characters, layout or text, and do NOT render a shirt, garment, model, hanger, photo scene, product mockup or background of any kind."
2) the new design: [same subject family as observed] + [same technique and palette family] + [changed exact subject, pose, composition and framing] + [mood], plus quoted original text if used. NEVER mention shirts, apparel, models, scenes, sizes, backgrounds or watermarks — placement is handled externally. Before output, verify the new design shares the locked elements (subject category, technique, palette) with the observed print; if not, revise it.
OUTPUT — ONLY valid JSON, no fences:
{"designs":[{"suitable_for_print":<bool>,"image_prompt":"<str>"}]}
Output format — no fences, no explanation:
{"delta": "<one sentence>"}
+1 -1
View File
@@ -1 +1 @@
Analyze the attached bestseller product photo, locate its printed artwork, and produce one ORIGINAL T-shirt print design brief that keeps its general vibe without copying its printed artwork.
+7
View File
@@ -0,0 +1,7 @@
The attached photo shows a printed garment. Work only with the printed artwork: mentally
flatten it (ignore fabric folds, wrinkles, perspective and lighting). Apply exactly one
change to the artwork: {delta}
Everything else stays exactly as it is: layout, element positions, palette, shading,
textures, line style, wording, typography. Do not redesign, restyle, add or remove
anything else. Never render the shirt, garment, model or photo scene. Output the finished
artwork only — flat, standalone, print-ready, centered on a plain white background.
+26 -10
View File
@@ -1,20 +1,36 @@
Role
你是一位资深的跨境服装运营专家,精通 Temu 平台的英语电商 SEO 标题逻辑。你的任务是通过分析服装图片,生成高权重的英语-中文商品标题。
你是一位资深的跨境服装运营专家,精通 Temu 平台的英语电商 SEO 标题逻辑。你的任务是通过分析服装图片及给定的类目路径,生成高权重的英语-中文商品标题。
Task
请深度分析图片中的服装特征(品类、风格、材质、剪裁、细节、受众),生成符合 Temu 搜索逻辑的中英双语标题。
请深度分析图片中的服装特征(品类、风格、材质、剪裁、细节),并结合注入的类目路径,生成符合 Temu 搜索逻辑的中英双语标题。
当前时间(标题须贴合当下,季节/年份词以此为准)
Current time: {year}-{month}{season}),标题中的年份/季节等时效词必须使用以上时间(如 Fall {year} / {year}秋季新款)。
Analysis Focus (视觉分析重点)
品类识别:准确判断英语核心词(如 Dress, Blouse, Sweatshirt)和中文核心词(如 连衣裙, 卫衣)。
Current time
当前时间:{year}-{month}{season}),标题中的年份/季节等时效词必须使用以上时间(如 Fall {year} / {year}秋季新款)。
Current Category
当前类目路径:{category_path}
Analysis Focus (视觉与类目分析重点)
受众与品类解析:解析注入的类目路径,准确提取性别与受众人群(如 Womens, Mens, Boys, Girls / 女装, 男装, 男童, 女童)以及最底层的品类核心词(如 T-Shirt, Dress)。标题中的受众人群词必须与类目节点严格匹配。如果是女装/女童类目,绝对禁止出现任何男性相关字眼(如 Men’s, Boys / 男装, 男童);反之亦然。
风格定位:判断风格流派(如 Boho, Vintage, Minimalist / 法式, 复古, 极简)。
设计细节:提取领型、袖型、裙长、印花、纽扣等(如 V-neck, Puff Sleeve / V领, 泡泡袖)。
材质推断:根据视觉特征推断面料(如 Ribbed Knit, Cotton Linen / 针织, 棉麻)。
适用场景:推断穿着场景(如 Beach, Office, Party, Vacation, Going Out / 度假, 通勤, 约会)。
Constraints (生成规则)
English Title: 遵循 Temu 平台风格,标题长度尽量写满 100-150 字符(上限不超过150)。核心品类词前置,依次叠加:核心词 → 风格词 → 属性细节(领型/袖型/长度/印花)→ 材质 → 版型 → 季节 → 场景(Casual/Office/Party/Beach/Date Night/Going Out)→ 人群(Women/Ladies)。多关键词并列堆叠,用逗号/空格分隔,不追求句子通顺,追求搜索覆盖率。
Chinese Title: 遵循淘宝/1688风格,长度 30-60 个汉字,关键词权重递减,结构为:季节年份 + 人群 + 风格 + 核心品类 + 材质 + 设计细节(领型/袖型/版型/元素)+ 场景卖点 + 风格流派词堆叠(如 复古/法式/慵懒风/多巴胺)。
English Title (绝对禁止包含任何中文字符,必须为纯英文):
遵循 Temu 平台风格,标题长度 100-150 字符(上限不超过150),按「属性分段式」结构生成,每段用半角逗号分隔,段内信息紧凑:
第一段:人群(取自类目) + 材质 + 核心属性 + 品类核心词(取自类目)(如 Womens 100% Cotton Short Sleeve Crewneck T-Shirt
第二段:主题/印花/设计亮点(如 Graphic Print, Floral Embroidered
第三段:面料体感 + 版型 + 品类同义词(如 Soft Breathable Loose Fit Tee
第四段:场景/用途收尾(如 Ideal For Summer Daily Wear
参考范例:*Womens 100% Cotton Short Sleeve Crewneck TShirt, Floral Embroidered, Soft Breathable Loose Fit Tee, Ideal For Summer Daily Wear*
核心词前置、同义词复用(如 Dress/Jumper、Tee/T-Shirt)、不追求句子通顺,追求搜索覆盖率。
【严禁红线 1】:en_title 字段的值中绝对不能出现任何中文字符,只能包含英文字母、数字、空格和标准英文半角标点符号。
【严禁红线 2】:受众人群词必须严格符合 Current Category。如果是女装/女童类目,严禁出现 Men/Boy 等男性词汇;如果是男装/男童类目,严禁出现 Women/Girl 等女性词汇。
Chinese Title:
遵循淘宝/1688风格,长度 30-60 个汉字,关键词权重递减,结构为:季节年份 + 人群(取自类目) + 风格 + 核心品类(取自类目) + 材质 + 设计细节(领型/袖型/版型/元素)+ 场景卖点 + 风格流派词堆叠(如 复古/法式/慵懒风/多巴胺)。
Output
必须严格返回 JSON 格式,不要包含 Markdown 代码块标记,格式如下:
{“en_title”: “Title in English”, “cn_title”: “中文标题”}
必须严格返回 JSON 格式,不要包含 Markdown 代码块标记。en_title 对应的值必须且只能包含纯英文,cn_title 对应的值包含中文,格式如下:
{“en_title”: “Pure English Title Here Without Any Chinese Characters”, “cn_title”: “这里填写中文标题”}
+3
View File
@@ -7,6 +7,9 @@
# 当前时间(标题须贴合当下,季节/年份词以此为准)
- **現在の時刻**: {year}-{month}{season}),标题中的年份/季节等时效词必须使用以上时间。
# Current Category
- **現在のカテゴリパス**: {category_path}
# Analysis Focus (视觉分析重点)
- **品类识别**:准确判断英语核心词(如 Dress, Blouse, Sweatshirt)、中文核心词(如 连衣裙, 卫衣)和日语核心词(如 ワンピース, ブラウス, スウェット)。
- **风格定位**:判断风格流派(如 フェミニン, ヴィンテージ, ミニマル / 法式, 复古, 极简 / フェミニン, レトロ, シンプル)。
+3
View File
@@ -7,6 +7,9 @@
# 当前时间(标题须贴合当下,季节/年份词以此为准)
- **Current time**: {year}-{month}{season}),标题中的年份/季节等时效词必须使用以上时间。
# Current Category
- **Ruta de categoría actual**: {category_path}
# Analysis Focus (视觉分析重点)
- 品类识别:准确判断西班牙语核心词(如 Vestido, Blusa, Sudadera)和中文核心词(如 连衣裙, 卫衣)。
- 风格定位:判断风格流派(如 Boho, Vintage, Minimalista / 法式, 复古, 极简)。
+1
View File
@@ -1021,6 +1021,7 @@ class App(tk.Tk):
"ES": ("西班牙站", "西班牙"),
"IT": ("意大利站", "意大利"),
"CA": ("加拿大站", "加拿大"),
"KR": ("韩国站", "韩国"),
}
allowed = country_sites.get(str(self.country_var.get()).upper(), ())
site_name = ""
+85
View File
@@ -0,0 +1,85 @@
# 字段映射表汇总
本文档汇总「商品上传模板导出」中所有按性别/类目生效的字段映射表。
来源:`graph/template_export.py`(内置默认)+ `config.yaml`(可覆盖/新增,优先)。
密钥约定:`→` 左为 db(数据库中)值,右为模板下拉框精确选项。
---
## 1. 成分映射 component_map
| 作用范围 | db 值 | 模板值 |
|---|---|---|
| 女装 `female` | 棉 | 棉Cotton |
| 女装 `female` | 聚酯纤维 / 涤纶 / 再生聚酯纤维 / 聚酯纤维(涤纶) | 聚酯纤维(涤纶)Polyester |
| 女装 `female` | 氨纶 | 氨纶Elastane |
| 女装 `female` | 锦纶 | 锦纶(尼龙)Polyamide |
- 映射键:`component_1 / component_2 / component_3` 三个字段
- config 段:`component_map.female`
- 男装/童装/无成分:保留 db 原值(不查此表;完整配置以 `config.yaml` 为准)
---
## 2. 季节映射 season_map
| 作用范围 | db 值 | 模板值 |
|---|---|---|
| 女装 `female` | 四季 | ALL/全球/所有 |
- 映射键:`season` 字段
- config 段:`season_map.female`
- 男装保留 db 原值
---
## 3. 印花图案映射 pattern_map
| 作用范围 | db 值 | 模板值 |
|---|---|---|
| 女装 `female` | 印花 | 卡通 |
| 女童 `girl_kids` | 印花 | 卡通 |
- 映射键:`pattern` 字段
- config 段:`pattern_map.female``pattern_map.girl_kids`
- 男装 / **男童** 保留 db 原值(男童不映射)
---
## 4. 适用人群映射 target_audience_map
| 作用范围 | db 值 | 模板值 |
|---|---|---|
| 女童 `girl_kids` | 男童 | 女童 |
| 男童 `boy_kids` | 女童 | 男童 |
- 童装模板专用:女童模板遇到「男童」→「女童」;男童模板遇到「女童」→「男童」
- config 段:`target_audience_map.kids.girl_kids``target_audience_map.kids.boy_kids`
- 成人男/女装保留 db 原值(不查此表)
---
## 5. 类型映射(SPU商品属性-类型)kids_type_map
| 作用范围 | db 值 | 模板值 |
|---|---|---|
| 女童 `girl_kids` | 上衣 | 针织上衣 |
- 映射键:`kids_type``SPU商品属性-类型`
- config 段:`kids_type_map.girl_kids`
- **只有女童**需要映射结果;男童保留 db 原值
---
## 6. 适用年龄段(童装)
- 映射:`kids_age``适用年龄段`(仅童装模板填,原样透传,不走映射表)
---
## 配置覆盖说明
所有映射表均遵循同一加载逻辑:**config.yaml 有对应段则「合并覆盖」内置默认,缺失/留空则回退内置默认**。
内置默认常量(`_COMPONENT_FEMALE_MAP` / `_SEASON_FEMALE_MAP` / `_PATTERN_FEMALE_MAP` /
`_PATTERN_KIDS_MAP` / `_TARGET_AUDIENCE_KIDS_MAP` / `_KIDS_TYPE_GIRL_MAP`)位于
`graph/template_export.py`