POD 趋势感知 Agent:缓存热点模式 + 三图合成 + 热点去重/风格去重 + review 兜底
- 缓存热点批量流程(有采集缓存不触发 Google) - 简报不足直接从采集缓存生成(轻量补齐) - 三图合成(模特/印花/底图)+ 底图压缩 <2MB - 热点去重→风格去重自动切换 + 不适合类目 review 兜底 - 透明背景(background=transparent)+ 提示词清洗(敏感词/背景描述) - 任务前 basemap 校验 + 模板国家校验 + 模特任务级分配
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# 2026-08-19 工作日志
|
||||
|
||||
## 创建 SQLite 商品数据库
|
||||
- 在 `db/` 目录下创建 `spu_sku.db`,含 SPU、SKU 两张表。
|
||||
- SPU 表 25 个字段:id(主键,自增)、code、material、component_1~3、component_proportion_1~3、pattern、details、collar_style、style、care_Instructions、fabric、target_audience、season、is_transparent、layout、weaving_method、printing_type、fabric_texture_1、fabric_weight_1、fabric_weight_unit_1、lining_texture。
|
||||
- SKU 表 9 个字段:id(主键,自增)、spu_id(外键→SPU.id,含索引 idx_sku_spu_id)、code、color、size、price(REAL)、stock(INTEGER 默认0)、image_url、status(默认 'active')。
|
||||
- 建库脚本保留在 `db/create_db.py`,可修改字段后重跑重建。
|
||||
- SKU 字段为按电商惯例补充设计,用户未指定,待确认是否调整。
|
||||
|
||||
## 更新 SKU 表结构(用户指定字段)
|
||||
- 用户提供完整 SKU 字段定义,重建 SKU 表,共 19 个字段:
|
||||
id(主键,自增)、spu_id(外键→SPU.id)、code、price(REAL)、color、size、size_group、size_type、shoulder_width、bust、clothing_length、sleeve_length、longest_side、secondary_long_side、shortest_side、img_url_2~img_url_5。
|
||||
- 尺寸类字段(shoulder_width/bust/clothing_length 等)用 TEXT 存储(兼容区间值/单位),如需要数值计算可改 REAL。
|
||||
- 注意:删除 db 文件会被沙箱安全机制拦截,create_db.py 已改为 DROP TABLE 方式重建,不再删除文件。
|
||||
|
||||
## 从 PG (inkreach) 同步美国商品数据
|
||||
- PG 连接:localhost:5432, user=postgres, pwd=inkreach, db=inkreach(psycopg2 装在 venv `C:\Users\Admin\.workbuddy\binaries\python\envs\default`)。
|
||||
- 数据规律:**plates.id IN (2,3,4,5) = 美国男装/女装/童装/家居配饰**;SPU = categories(code 去重 59 个);SKU = prices(web_product_id 粒度 218 条);成分=composition,洗涤/英文名/质地/印花工艺=product_extra,图片=color_detail_images,尺码范围=colors.size_range。
|
||||
- 映射:SPU.code=品类code, material/fabric=品类fabric, component_1~3=composition.comp1~3, style=sub_category, details=english_name, care_Instructions=washing_instructions, target_audience=plates.display_name, printing_type=design_explanation(烫画/热转印), fabric_texture_1=texture, fabric_weight_1=从名称正则提取克重。
|
||||
- SKU: code=sku, price=price, size_group=colors.size_range, size_type=按尺码范围推断(字母码/儿童码/婴童码/均码/家居尺寸), img_url_2~5=该产品 color_detail_images 第2~5张。
|
||||
- **留空字段(PG 无对应数据)**:pattern、collar_style、season、is_transparent、layout、weaving_method、lining_texture;SKU 的 color(web_sku 与 colors 名无法可靠映射,75 品类中 21 个不一致)、size、shoulder_width、bust、clothing_length、sleeve_length、longest_side、secondary_long_side、shortest_side(尺寸为品类级多值)。
|
||||
- 结果:SPU 59 条(5000B 无价格故无 SKU)、SKU 218 条,字段未改动。
|
||||
- 同步脚本 `db/sync_from_pg.py` 可重跑(先 DELETE 清空再插入)。
|
||||
|
||||
## SKU 改为颜色×尺码粒度(v2)
|
||||
- 用户要求:SKU.code 用颜色映射编码(如 DG004-AP01 = colors.code)、color 填颜色名、每个尺码独立一个 SKU、包装规格从原库解析。
|
||||
- 新数据源(product_extra 表的 JSON 字段):
|
||||
- `product_size` JSON:尺码表(表头动态列:肩宽/胸围/衣长/袖长/腰围等,第一列=尺码)
|
||||
- `packaging_spec` JSON:包装规格("包装尺寸(cm)"列 = 长*宽*高,拆分为最长边/次长边/最短边;另有 in/体积/重量列)
|
||||
- SKU 生成:每个 SPU = colors(按主 category_id) × product_size 尺码表 → 笛卡尔积。
|
||||
- code=colors.code(颜色映射编码),color=colors.name,size=尺码,size_group=colors.size_range,size_type=推断
|
||||
- price=该品类 prices 首条价(颜色尺码粒度无独立价);img_url_2~5=品类首颜色 web_sku 图
|
||||
- 5000B 无 product_extra → 尺码回退 size_chart;无 prices → price 留空
|
||||
- **坑**:5000B(12色)/DG502(1色) 的 colors.code 是坏数据(存了颜色名/品类码)→ 脚本兜底生成 f"{品类code}-{颜色名}"。
|
||||
- 结果:SPU 59 / SKU 1400(254 个唯一颜色编码)。填充率:code/color/size/size_group/size_type 100%,price 1340/1400,包装规格 1326/1400,尺寸字段 75~88%。
|
||||
|
||||
## SKU 尺码字段固定值 + SPU 增加国家字段
|
||||
- 用户要求:SKU.size_group 全部='尺码',SKU.size_type 全部='欧美尺码常规'(不再用推断逻辑)。
|
||||
- SPU 表新增 `country` 列(TEXT),全部 59 条 SPU 设为 'US'。
|
||||
- 已同步更新 `create_db.py`(SPU 表加 country 列)和 `sync_from_pg.py`(SPU 插入含 country='US',SKU 用固定 sgroup/stype),重跑验证通过:SPU 59 / SKU 1400 / country 全 US / size_group、size_type 各 1 个唯一值。
|
||||
|
||||
## SKU 增加 package_weight(包装重量)
|
||||
- SKU 表新增 `package_weight` 列,来源 = `product_extra.packaging_spec` JSON 的"含包装重量(g)"列(单位:克),新增 `parse_weight_json()` 解析函数。
|
||||
- 匹配兜底(重要):
|
||||
1. 精确匹配尺码 → 失败则取 '/' 前部分(家居类 '30*40/76.2*101.6' vs '30*40')
|
||||
2. 均码同义词兜底('Onesize'/'OneSize'/'均码' 互相匹配,YSM02 案例)
|
||||
- 结果:填充 1340/1400(95.7%),仅 5000B 无数据(PG 无 packaging_spec,合理留空)。重量范围 16~847 g。
|
||||
- create_db.py 的 SKU 定义同步加了 package_weight 列。
|
||||
@@ -0,0 +1,64 @@
|
||||
# 2026-08-20 工作日志
|
||||
|
||||
## SPU 增加 mark 字段 + 批量替换固定值
|
||||
- SPU 表新增 `mark` 列(TEXT),全部 59 条 SPU 设为 '1'。
|
||||
- 按用户要求用 SQL 批量替换 SPU 15 个字段为统一模板值:
|
||||
pattern=印花、details=无、collar_style=圆领、style=休闲、care_Instructions=数码印花类可机洗且不可干洗、fabric=微弹、target_audience=成人、season=四季、is_transparent=否、layout=H、weaving_method=针织(含钩织、毛织面料)、printing_type=定位印花、fabric_texture_1=光面、fabric_weight_unit_1=g/㎡、lining_texture=无里料/无内衬。
|
||||
- **注意**:这些字段不再随 PG 数据变化(原 details=english_name、fabric=品类面料、target_audience=美国男装等已被覆盖),sync_from_pg.py 的 SPU 插入已改为 fixed 模板值 + mark='1'。
|
||||
- 保留的 PG 映射字段:material(20 个唯一值)、component_1~3/比例、fabric_weight_1(克重,如 180/207/270)。
|
||||
- create_db.py 的 SPU 定义同步加了 mark 列。重跑验证:SPU 59 / SKU 1400,16 个固定值字段各仅 1 个唯一值。
|
||||
- 踩坑:SPU INSERT 的占位符一度写成 27 个 ? 对 26 列(报 27 values for 26 columns),已修正。
|
||||
|
||||
## component_proportion 字段转百分比
|
||||
- SPU 的 component_proportion_1/2/3 由数值(100、100.0、92.0…)改为百分比形式(100%、92%…),空值保持不变。
|
||||
- SQL:`UPDATE SPU SET component_proportion_x = CAST(CAST(component_proportion_x AS REAL) AS INTEGER) || '%' WHERE ... IS NOT NULL AND <> ''`(当前值全为整数小数,无精度损失)。
|
||||
- sync_from_pg.py 新增 `format_pct()` 函数(整数值去 .0 加 %,带小数保留,非数字原样),SPU 插入时对 p1/p2/p3 应用。重跑验证:p2 空 33、p3 空 51 保持,格式全部正确。
|
||||
|
||||
## SPU layout 字段改为"常规"
|
||||
- SPU.layout 由 'H' 全部替换为 '常规'(59 条),sync_from_pg.py 的 fixed 模板值同步更新。
|
||||
|
||||
## 同步墨西哥货盘数据(替换美国数据)
|
||||
- PG `plates` 表:`墨西哥` = plate_id 6(共 17 国,美国 2/3/4/5、日本 7、韩国 8、沙特 9、巴西 10、英国 11、加拿大 12、波兰 13、西班牙 14、德国 15、澳洲 16、意大利 17)。
|
||||
- 改造 sync_from_pg.py:顶部常量 `PLATE_IDS=(6,)` + `COUNTRY="MX"` 可切换国家;SPU 插入 country 用 COUNTRY;SKU 颜色增加 `clean_color_name()`(去尺码前缀如 'S-3XL黑色'→'黑色')与 `is_clean_code()`(code 须 '品类code-非中文后缀')兜底。
|
||||
- 结果:**SPU 8 / SKU 123**(颜色×尺码粒度),country 全 MX。8 个品类:HM01、MESXT001、METB001、METN001、METP001、PET001、SFB、TBB001。
|
||||
- 脏数据兜底:墨西哥 colors 有脏 code('METB001-S-3XL黑色'、'SFB-黑色'),清洗后 code 为 'METB001-黑色'/'SFB-黑色' 等(源库只有中文名、无拉丁编码,故 code 含中文属必然)。
|
||||
- 留空:仅 **SFB**(运动短裤)12 条 SKU 缺 price/package_weight/img_url,因 PG 中 SFB 无 prices 记录、无 product_extra(无 packaging JSON)、无 color_detail_images。SFB 尺码走 size_chart 回退(2 色×6 码)。
|
||||
- 注:本步将数据库内容由美国(US)整体替换为墨西哥(MX)——这是**错误的**,用户要求的是保留 US 再追加 MX。
|
||||
|
||||
## 修正:US + MX 双国累积同步(保留多国数据)
|
||||
- sync_from_pg.py 重构为 `sync(plate_ids, country, clear_first)`:
|
||||
- `clear_first=True` 先 DELETE 整库再插入(重置/单国全量);`clear_first=False` 仅追加,跳过已存在的 SPU code 与 SKU 组合 (spu_id, code, size)。
|
||||
- `__main__` 顺序执行:`sync((2,3,4,5),"US",clear_first=True)` 再 `sync((6,),"MX",clear_first=False)`。
|
||||
- 结果:**SPU 67(US 59 + MX 8)/ SKU 1523(US 1400 + MX 123)**,country 分布 `[('MX',8),('US',59)]`,全部保留。
|
||||
- 缺失 price/package_weight/img 共 72 条 = 5000B(US,60 条,源库无价格/包装/图)+ SFB(MX,12 条,源库无 prices/product_extra/图),属正常留空。
|
||||
- 顶部注释已列出全部 17 国 plate_id 映射,后续同步任意国家(如 JP=7/KR=8/GB=11/DE=15)追加进脚本的 `__main__` 调用即可,不会覆盖已有数据。
|
||||
|
||||
## 追加同步日本(JP)与英国(GB)
|
||||
- JP = plate_id 7(15 品类,1 个缺 product_extra 走 size_chart 回退);GB = plate_id 11(15 品类,其中 4 个 code=NULL 被自动过滤,实际 11 有效品类,均有 product_extra)。
|
||||
- `__main__` 追加 `sync((7,), "JP", False)` 与 `sync((11,), "GB", False)`,整库重置顺序:US(全量) → MX → JP → GB(追加)。
|
||||
- 结果:**SPU 93(US59/MX8/JP15/GB11)/ SKU 1997(US1400/MX123/JP216/GB258)**,四国数据全部保留。
|
||||
- JP 字段填充:color/size 216/216,price 204/216,package_weight 200/216,img 204/216(缺项来自源库无价格/包装/图的品类)。GB:color/size/price/img 全填,package_weight 238/258,shoulder_width 236/258(裤子/裙无肩宽属正常)。
|
||||
- 颜色名正常(日文/英文均识别),尺码体系 JP=XS~L、GB=S~XL,均按颜色×尺码展开。
|
||||
|
||||
## SKU code 命名规则改为「款号-颜色的英文名」(用户要求)
|
||||
- 问题:用户反馈「看不到日本 SKU」——实际 JP 数据齐全(216 条),根因是 JP `colors` 源数据存在脏 code(中文颜色名+尺码区间拼进 code,甚至整组颜色被拼成一条),旧兜底把它们变成 `JPHM009-灰色(S~XXL)` 这类难看 code,看起来像坏数据。
|
||||
- 新规则(`sync_from_pg.py`):SKU code 规范(品类code-纯字母数字后缀,如 `JPHM009-BL01`/`JPTM007-ESPRESSO`)直接用;**缺失/脏 code → 款号(品类code)-颜色的英文名**。
|
||||
- 英文名来源:PG `color_detail_images.color_name_web`(按 color_id 取,如 Black/White/Navy/Apricot);该表无值则用 `ZH_TO_EN` 中文→英文字典兜底;多色拼接脏名取首个可识别颜色英文名(`first_color_en()`)。
|
||||
- `is_clean_code()` 收窄:后缀须为纯字母数字,含中文/`~`/`()` 一律判为脏(修复纯尺码区间假颜色 `JPHM009-(S~XXXL)` 被误判规范的问题);纯尺码行 code 仅保留款号,color=NULL。
|
||||
- `clean_color_name()` 增强:额外去掉结尾尺码区间括号(如 `灰色(S~XXL)`→`灰色`)。
|
||||
- 全量重跑(先备份 spu_sku.db.bak):SPU/SKU 总数不变(93/1997),JP 残留中文/`~` 的 code 由多条降为 **0**。
|
||||
- **已知源数据坑**:`JPTM001` 的某条 `colors` 记录把 5 个颜色(白/杏/藏青/灰/粉)拼进同一 code 字段,脚本只能取首个颜色英文名兜底为 `JPTM001-White`,无法还原成 5 条独立颜色 SKU——需在 PG 侧修源。
|
||||
- 结论:加其他国家只需在 `__main__` 追加 `sync((plate_id,), "国家码", False)`,命名规则自动生效。
|
||||
|
||||
## 清洗 JPTM001 脏 colors + 重新同步(用户要求"先清洗再导入sku")
|
||||
- PG 侧:`colors` 表 id=357 把 5 个颜色(白/杏/藏青/灰/粉)拼进同一条 code(源数据损坏);其 `color_detail_images` 本就有 5 个独立颜色(White/Apricot/Deep Navy Blue/Gray/Pink,各 9 图)但 `color_id` 全为 NULL——脏记录是个未被图片引用的孤儿。
|
||||
- 清洗(事务提交):DELETE id=357;INSERT 5 条规范 `colors`(code=JPTM001-WH01/AP01/BE01/GR02/PK01,name 中文,size_range=XS-XXXL);UPDATE `color_detail_images.color_id` 指回新 id(白/杏/藏青/灰/粉 各 9 行 + 黑 9 行全关联成功)。
|
||||
- 颜色 code 缩写约定:白=WH01、杏=AP01、藏青=BE01、灰=GR02、粉=PK01(粉色无先例新编 PK01;藏青沿用 JPTM006 的 BE01)。
|
||||
- 重跑 `sync_from_pg.py` 全量:JPTM001 SKU 由 2 色(14 条)→6 色(42 条);全局 SKU 1997→2025(+28),SPU 93 不变。脏 `JPTM001-White` 长串记录已清除。
|
||||
- 注意:四国(US/MX/JP/GB)还存在其它"多色拼接"脏 colors 记录(一条 code 含 ≥2 个颜色,如 DG102/DG120/DG205/DG701/VS002/JSA004/GBHM002/GBTF004 等)。JPTM001 已修,其余待用户决定是否同法清洗(每条需先核 color_detail_images 是否可按独立颜色拆分)。
|
||||
|
||||
## SKU 尺码字段按国家区分(仅 JP=亚洲,其余=欧美)
|
||||
- 用户最初要求 US/JP 改为亚洲尺码,后纠正:**US 不改**,仅 JP 走亚洲尺码。
|
||||
- 最终状态:`size_group`/`size_type` —— JP=亚洲尺码/亚洲尺码亚洲常规(244),US/GB/MX=尺码/欧美尺码常规(1781)。全表 SKU 仍 2025。
|
||||
- 回滚备份:spu_sku.db.bak_usjp(US+JP 都改错的版本)、spu_sku.db.bak_revert_us(改回 US 后的版本)。
|
||||
- 规律:尺码体系与国家挂钩——**仅 JP=亚洲尺码亚洲常规**,US/GB/MX=欧美尺码常规。后续加国家时按此约定(亚洲市场如 JP/KR 走亚洲,欧美市场 US/GB/MX/DE 走欧美),或写进 sync_from_pg.py 按 country 映射自动填。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 项目长期记忆:Inkreach POD 数据库同步
|
||||
|
||||
## PG 库连接
|
||||
- host=localhost, port=5432, user=postgres, password=inkreach, dbname=inkreach
|
||||
- Python 驱动 psycopg2 装在隔离 venv:`C:/Users/Admin/.workbuddy/binaries/python/envs/default/Scripts/python.exe`
|
||||
|
||||
## 国家货盘映射(plates 表 id → 国家)
|
||||
- 美国 2/3/4/5、墨西哥 6、日本 7、韩国 8、沙特 9、巴西 10、英国 11、加拿大 12、波兰 13、西班牙 14、德国 15、澳洲 16、意大利 17
|
||||
|
||||
## SPU/SKU 表结构(SQLite spu_sku.db,字段不可改)
|
||||
- SPU(25+ 业务字段 + country + mark):code/material/component_1~3/比例/pattern/details/collar_style/style/care_Instructions/fabric/target_audience/season/is_transparent/layout/weaving_method/printing_type/fabric_texture_1/fabric_weight_1/fabric_weight_unit_1/lining_texture/country/mark
|
||||
- SPU 固定模板值(不随 PG 变):pattern=印花、details=无、collar_style=圆领、style=休闲、care_Instructions=数码印花类可机洗且不可干洗、fabric=微弹、target_audience=成人、season=四季、is_transparent=否、layout=常规、weaving_method=针织(含钩织、毛织面料)、printing_type=定位印花、fabric_texture_1=光面、fabric_weight_unit_1=g/㎡、lining_texture=无里料/无内衬、mark=1
|
||||
- SKU(19 字段):spu_id/code/price/color/size/size_group/size_type/shoulder_width/bust/clothing_length/sleeve_length/longest_side/secondary_long_side/shortest_side/package_weight/img_url_2~5
|
||||
|
||||
## 同步脚本 sync_from_pg.py 规律
|
||||
- SPU = categories(按 code 去重);SKU = colors × product_size/ size_chart 尺码(颜色×尺码粒度)
|
||||
- 业务模板固定值写入 SPU,PG 仅提供 material、component、fabric_weight(从 name 提取克重)、country
|
||||
- SKU code = colors.code(脏数据兜底为 品类code-颜色名);color = 清洗后颜色名;price = 该品类 prices 首条;包装尺寸/重量 = product_extra.packaging_spec JSON;尺码表 = product_extra.product_size JSON(缺则回退 size_chart)
|
||||
- 切换国家:改顶部 `PLATE_IDS` 与 `COUNTRY` 后重跑(会 DELETE 两表再插入)
|
||||
- 脏颜色 code 处理:clean_color_name() 去尺码前缀;is_clean_code() 判断规范,不规范则 品类code-颜色名 兜底(源库无拉丁编码时 code 含中文属必然)
|
||||
|
||||
## 已知数据坑
|
||||
- 部分品类 colors.code 为脏数据(如 'METB001-S-3XL黑色'、'5000B-黑色'、'SFB-黑色')→ 兜底补全
|
||||
- 部分品类无 product_extra(如 SFB/5000B)→ 尺码走 size_chart 回退,包装/重量留空
|
||||
- 家居类尺码格式不一致('30*40/76.2*101.6' vs '30*40')、均码叫法不同(Onesize vs 均码)→ 已加归一化兜底
|
||||
@@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sqlite3
|
||||
|
||||
db_path = r"C:\Users\Admin\Desktop\test模版\design_agent\pod_trend_agent\db\spu_sku.db"
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
# 若表已存在则先删除(重建表结构)
|
||||
cur.execute("DROP TABLE IF EXISTS SKU")
|
||||
cur.execute("DROP TABLE IF EXISTS SPU")
|
||||
|
||||
# ---------- SPU 表 ----------
|
||||
cur.execute("""
|
||||
CREATE TABLE SPU (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT,
|
||||
material TEXT,
|
||||
component_1 TEXT,
|
||||
component_2 TEXT,
|
||||
component_3 TEXT,
|
||||
component_proportion_1 TEXT,
|
||||
component_proportion_2 TEXT,
|
||||
component_proportion_3 TEXT,
|
||||
pattern TEXT,
|
||||
details TEXT,
|
||||
collar_style TEXT,
|
||||
style TEXT,
|
||||
care_Instructions TEXT,
|
||||
fabric TEXT,
|
||||
target_audience TEXT,
|
||||
season TEXT,
|
||||
is_transparent TEXT,
|
||||
layout TEXT,
|
||||
weaving_method TEXT,
|
||||
printing_type TEXT,
|
||||
fabric_texture_1 TEXT,
|
||||
fabric_weight_1 TEXT,
|
||||
fabric_weight_unit_1 TEXT,
|
||||
lining_texture TEXT,
|
||||
country TEXT,
|
||||
mark TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
# ---------- SKU 表 ----------
|
||||
cur.execute("""
|
||||
CREATE TABLE SKU (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
spu_id INTEGER NOT NULL,
|
||||
code TEXT,
|
||||
price REAL,
|
||||
color TEXT,
|
||||
size TEXT,
|
||||
size_group TEXT,
|
||||
size_type TEXT,
|
||||
shoulder_width TEXT,
|
||||
bust TEXT,
|
||||
clothing_length TEXT,
|
||||
sleeve_length TEXT,
|
||||
longest_side TEXT,
|
||||
secondary_long_side TEXT,
|
||||
shortest_side TEXT,
|
||||
package_weight TEXT,
|
||||
img_url_2 TEXT,
|
||||
img_url_3 TEXT,
|
||||
img_url_4 TEXT,
|
||||
img_url_5 TEXT,
|
||||
FOREIGN KEY (spu_id) REFERENCES SPU(id)
|
||||
)
|
||||
""")
|
||||
|
||||
# 索引:SKU 按 spu_id 快速查询
|
||||
cur.execute("CREATE INDEX idx_sku_spu_id ON SKU(spu_id)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# ---------- 验证 ----------
|
||||
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||
tables = cur.fetchall()
|
||||
print("数据库文件:", db_path)
|
||||
print("表列表:", [t[0] for t in tables])
|
||||
|
||||
cur.execute("PRAGMA table_info(SPU)")
|
||||
spu_cols = cur.fetchall()
|
||||
print("\nSPU 字段 ({}个):".format(len(spu_cols)))
|
||||
for row in spu_cols:
|
||||
print(" ", row[1], "|", row[2], "| 主键" if row[5] else "")
|
||||
|
||||
cur.execute("PRAGMA table_info(SKU)")
|
||||
sku_cols = cur.fetchall()
|
||||
print("\nSKU 字段 ({}个):".format(len(sku_cols)))
|
||||
for row in sku_cols:
|
||||
print(" ", row[1], "|", row[2], "| 主键" if row[5] else "")
|
||||
|
||||
conn.close()
|
||||
print("\n创建完成")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,537 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
从 inkreach PostgreSQL 同步指定国家货盘商品数据到 SQLite (spu_sku.db)
|
||||
|
||||
支持多国累积同步(保留已存在数据):
|
||||
- sync(plate_ids, country, clear_first)
|
||||
clear_first=True -> 先 DELETE 整库再插入(重置该次目标)
|
||||
clear_first=False -> 仅追加,跳过已存在的 SPU code 与 SKU 组合
|
||||
|
||||
国家货盘 plate_id 映射:
|
||||
- 美国 : (2, 3, 4, 5) country="US"
|
||||
- 墨西哥: (6,) country="MX"
|
||||
- 日本 : (7,) country="JP"
|
||||
- 韩国 : (8,) country="KR"
|
||||
- 沙特 : (9,) country="SA"
|
||||
- 巴西 : (10,) country="BR"
|
||||
- 英国 : (11,) country="GB"
|
||||
- 加拿大: (12,) country="CA"
|
||||
- 波兰 : (13,) country="PL"
|
||||
- 西班牙: (14,) country="ES"
|
||||
- 德国 : (15,) country="DE"
|
||||
- 澳洲 : (16,) country="AU"
|
||||
- 意大利: (17,) country="IT"
|
||||
|
||||
SPU = categories(code 去重)
|
||||
SKU = 颜色 × 尺码 粒度:
|
||||
- code = colors.code 颜色映射编码(脏数据兜底为 款号-颜色的英文名,英文名取自 color_detail_images.color_name_web)
|
||||
- color = colors.name 颜色名
|
||||
- size = product_extra.product_size JSON 的尺码列(缺 JSON 回退 size_chart)
|
||||
- 肩宽/胸围/衣长/袖长 = product_size JSON 对应列(动态识别表头)
|
||||
- 最长边/次长边/最短边 = packaging_spec JSON "包装尺寸(cm)" 列 长*宽*高 拆分排序
|
||||
- package_weight = packaging_spec JSON "含包装重量(g)" 列(单位: 克)
|
||||
- price = 该品类 prices 首条价格(颜色尺码粒度无独立价格)
|
||||
- img_url_2~5 = 该品类首颜色 web_sku 的图(seq 2~5)
|
||||
"""
|
||||
import sqlite3
|
||||
import psycopg2
|
||||
import re
|
||||
import json
|
||||
|
||||
PG = dict(host="localhost", port=5432, user="postgres", password="inkreach", dbname="inkreach")
|
||||
SQLITE = r"C:\Users\Admin\Desktop\test模版\design_agent\pod_trend_agent\db\spu_sku.db"
|
||||
|
||||
|
||||
def clean(v):
|
||||
if v is None:
|
||||
return None
|
||||
s = str(v).strip()
|
||||
return s if s else None
|
||||
|
||||
|
||||
def format_pct(v):
|
||||
"""成分比例转百分比形式:'100'/'100.0' -> '100%',空值保持 None"""
|
||||
s = clean(v)
|
||||
if s is None:
|
||||
return None
|
||||
try:
|
||||
num = float(s)
|
||||
if num == int(num):
|
||||
return f"{int(num)}%"
|
||||
return f"{num}%"
|
||||
except ValueError:
|
||||
return s
|
||||
|
||||
|
||||
# 中文颜色名 -> 英文(color_name_web 缺失时的兜底)
|
||||
ZH_TO_EN = {
|
||||
"黑": "Black", "黑色": "Black",
|
||||
"白": "White", "白色": "White",
|
||||
"灰": "Gray", "灰色": "Gray",
|
||||
"杏": "Apricot", "杏色": "Apricot",
|
||||
"咖色": "Coffee", "咖": "Coffee",
|
||||
"藏青": "Navy", "藏青色": "Navy",
|
||||
"蓝": "Blue", "蓝色": "Blue", "海蓝": "Blue", "海蓝色": "Ocean Blue",
|
||||
"翠绿": "Green", "翠绿色": "Green", "绿": "Green", "绿色": "Green",
|
||||
"紫": "Purple", "紫色": "Purple",
|
||||
"玫红": "Rose", "玫红色": "Rose", "粉": "Pink", "粉色": "Pink", "粉红": "Pink",
|
||||
"红": "Red", "红色": "Red",
|
||||
"黄": "Yellow", "黄色": "Yellow",
|
||||
"金": "Gold", "金色": "Gold",
|
||||
"银": "Silver", "银色": "Silver",
|
||||
"棕": "Brown", "棕色": "Brown",
|
||||
"米": "Beige", "米色": "Beige", "卡其": "Khaki", "卡其色": "Khaki",
|
||||
"橙": "Orange", "橙色": "Orange",
|
||||
}
|
||||
|
||||
|
||||
def clean_color_name(name):
|
||||
"""清洗颜色名:
|
||||
'S-3XL黑色' -> '黑色'(去开头尺码前缀)
|
||||
'灰色(S~XXL)' -> '灰色'(去结尾尺码区间括号)
|
||||
'(M~3XL)' -> None(纯尺码无颜色)
|
||||
保留中文颜色核心部分。"""
|
||||
s = clean(name)
|
||||
if not s:
|
||||
return None
|
||||
# 开头若全是非中文(尺码前缀如 S-3XL / S(4-5)y),取其后中文部分
|
||||
m = re.match(r"^[^一-鿿]+([一-鿿].*)$", s)
|
||||
if m:
|
||||
s = m.group(1).strip()
|
||||
# 去掉结尾的尺码区间括号,如 (S~XXL)/(M~5XL)/(XS~3XL)
|
||||
s = re.sub(r"[((][^一-鿿]*[))]$", "", s).strip()
|
||||
# 去掉残留的纯尺码/空白字符
|
||||
s = s.strip(" ()()~ ")
|
||||
return s if s else None
|
||||
|
||||
|
||||
def is_clean_code(ccode, catcode):
|
||||
"""判断颜色 code 是否规范:必须以 品类code- 开头,
|
||||
且后缀为纯字母数字(如 BL01 / ESPRESSO),不含中文、~ 或括号。
|
||||
纯尺码区间(如 JPHM009-(S~XXXL))与多色拼接脏数据会被判为非规范。"""
|
||||
if not ccode or not catcode:
|
||||
return False
|
||||
prefix = catcode + "-"
|
||||
if not ccode.startswith(prefix):
|
||||
return False
|
||||
suffix = ccode[len(prefix):]
|
||||
if not suffix or re.search(r"[一-鿿~(())]", suffix):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def first_color_en(name):
|
||||
"""从可能含多色的脏名称中取首个可识别颜色的英文名,否则原样返回。"""
|
||||
if not name:
|
||||
return None
|
||||
for zh, en in ZH_TO_EN.items():
|
||||
if zh in name:
|
||||
return en
|
||||
return name
|
||||
|
||||
|
||||
def extract_weight(name):
|
||||
if not name:
|
||||
return None, None
|
||||
m = re.search(r"(\d+(?:\.\d+)?)\s*(?:G|g|克)", name)
|
||||
if m:
|
||||
return m.group(1), "G"
|
||||
return None, None
|
||||
|
||||
|
||||
def parse_size_json(raw):
|
||||
"""product_size JSON -> [{size, shoulder, bust, length, sleeve}...]"""
|
||||
if not raw:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return []
|
||||
if not isinstance(data, list) or len(data) < 2:
|
||||
return []
|
||||
header = [c.get("content", "") for c in data[0]]
|
||||
size_i = next((i for i, h in enumerate(header) if "尺码" in h), None)
|
||||
sh_i = next((i for i, h in enumerate(header) if "肩宽" in h), None)
|
||||
bu_i = next((i for i, h in enumerate(header) if "胸围" in h), None)
|
||||
le_i = next((i for i, h in enumerate(header) if "衣长" in h), None)
|
||||
sl_i = next((i for i, h in enumerate(header) if "袖长" in h), None)
|
||||
if size_i is None:
|
||||
return []
|
||||
rows = []
|
||||
for r in data[1:]:
|
||||
if not isinstance(r, list):
|
||||
continue
|
||||
def cell(i):
|
||||
if i is None or i >= len(r):
|
||||
return None
|
||||
return clean(r[i].get("content", "") if isinstance(r[i], dict) else r[i])
|
||||
size = cell(size_i)
|
||||
if not size or size == "尺码":
|
||||
continue
|
||||
rows.append(dict(size=size, shoulder=cell(sh_i), bust=cell(bu_i),
|
||||
length=cell(le_i), sleeve=cell(sl_i)))
|
||||
return rows
|
||||
|
||||
|
||||
def parse_pkg_json(raw):
|
||||
"""packaging_spec JSON -> {size: (longest, second, shortest)} 按 cm 列拆分排序"""
|
||||
if not raw:
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return {}
|
||||
if not isinstance(data, list) or len(data) < 2:
|
||||
return {}
|
||||
header = data[0]
|
||||
size_i = next((i for i, c in enumerate(header) if "尺码" in c.get("content", "")), None)
|
||||
cm_i = next((i for i, c in enumerate(header)
|
||||
if "包装尺寸" in c.get("content", "") and "cm" in c.get("content", "")), None)
|
||||
if size_i is None or cm_i is None:
|
||||
return {}
|
||||
out = {}
|
||||
for r in data[1:]:
|
||||
if not isinstance(r, list) or cm_i >= len(r):
|
||||
continue
|
||||
size = clean(r[size_i].get("content", "")) if isinstance(r[size_i], dict) else clean(r[size_i])
|
||||
val = clean(r[cm_i].get("content", "")) if isinstance(r[cm_i], dict) else clean(r[cm_i])
|
||||
if not size or not val:
|
||||
continue
|
||||
parts = re.findall(r"\d+(?:\.\d+)?", val)
|
||||
if len(parts) >= 3:
|
||||
nums = sorted((float(p) for p in parts[:3]), reverse=True)
|
||||
out[size] = (nums[0], nums[1], nums[2])
|
||||
return out
|
||||
|
||||
|
||||
def parse_weight_json(raw):
|
||||
"""packaging_spec JSON -> {size: 含包装重量(g)},取"含包装重量(g)"列"""
|
||||
if not raw:
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return {}
|
||||
if not isinstance(data, list) or len(data) < 2:
|
||||
return {}
|
||||
header = data[0]
|
||||
size_i = next((i for i, c in enumerate(header) if "尺码" in c.get("content", "")), None)
|
||||
w_i = next((i for i, c in enumerate(header)
|
||||
if "重量" in c.get("content", "") and "(g)" in c.get("content", "")), None)
|
||||
if size_i is None or w_i is None:
|
||||
return {}
|
||||
out = {}
|
||||
for r in data[1:]:
|
||||
if not isinstance(r, list) or w_i >= len(r):
|
||||
continue
|
||||
size = clean(r[size_i].get("content", "")) if isinstance(r[size_i], dict) else clean(r[size_i])
|
||||
val = clean(r[w_i].get("content", "")) if isinstance(r[w_i], dict) else clean(r[w_i])
|
||||
if not size or not val:
|
||||
continue
|
||||
out[size] = val
|
||||
return out
|
||||
|
||||
|
||||
def sync(plate_ids, country, clear_first=True):
|
||||
print(f"\n==== 同步 {country} (plate_ids={plate_ids}, clear_first={clear_first}) ====")
|
||||
pg = psycopg2.connect(**PG)
|
||||
pg.autocommit = True
|
||||
pc = pg.cursor()
|
||||
|
||||
# ---------- SPU 数据 ----------
|
||||
pc.execute("""
|
||||
SELECT c.id, c.plate_id, c.code, c.name, c.sub_category, c.fabric, c.composition, p.display_name
|
||||
FROM categories c
|
||||
LEFT JOIN plates p ON c.plate_id = p.id
|
||||
WHERE c.plate_id IN %s AND c.code IS NOT NULL AND c.code <> ''
|
||||
ORDER BY c.plate_id, c.id
|
||||
""", (plate_ids,))
|
||||
cat_rows = pc.fetchall()
|
||||
|
||||
spu_map = {}
|
||||
for cid, plate_id, code, name, sub, fabric, comp, display in cat_rows:
|
||||
if code in spu_map:
|
||||
prev = spu_map[code]
|
||||
prev_score = (0 if prev["sub"] == "组合款" else 1, -prev["plate_id"], -prev["cid"])
|
||||
new_score = (0 if sub == "组合款" else 1, -plate_id, -cid)
|
||||
if new_score > prev_score:
|
||||
spu_map[code] = dict(cid=cid, plate_id=plate_id, name=name, sub=sub,
|
||||
fabric=fabric, comp_raw=comp, display=display)
|
||||
else:
|
||||
spu_map[code] = dict(cid=cid, plate_id=plate_id, name=name, sub=sub,
|
||||
fabric=fabric, comp_raw=comp, display=display)
|
||||
|
||||
pc.execute("""
|
||||
SELECT category_id, fabric, comp1, comp1_pct, comp2, comp2_pct, comp3, comp3_pct
|
||||
FROM composition
|
||||
""")
|
||||
comp_by_cat = {}
|
||||
for cid, fabric, c1, p1, c2, p2, c3, p3 in pc.fetchall():
|
||||
comp_by_cat[cid] = dict(c1=c1, p1=p1, c2=c2, p2=p2, c3=c3, p3=p3)
|
||||
|
||||
pc.execute("""
|
||||
SELECT code, english_name, washing_instructions, design_explanation, texture,
|
||||
product_size, packaging_spec
|
||||
FROM product_extra
|
||||
""")
|
||||
extra_by_code = {}
|
||||
for code, en, wash, design, texture, psize, pkg in pc.fetchall():
|
||||
extra_by_code[code] = dict(en=en, wash=wash, design=design, texture=texture,
|
||||
psize=psize, pkg=pkg)
|
||||
|
||||
# ---------- SKU 数据 ----------
|
||||
# 颜色
|
||||
pc.execute("""
|
||||
SELECT id, category_id, name, code, size_range
|
||||
FROM colors
|
||||
WHERE category_id IN (SELECT id FROM categories WHERE plate_id IN %s)
|
||||
ORDER BY category_id, seq
|
||||
""", (plate_ids,))
|
||||
colors_by_cat = {}
|
||||
for col_id, cid, name, code, sr in pc.fetchall():
|
||||
colors_by_cat.setdefault(cid, []).append(dict(id=col_id, name=name, code=code, sr=sr))
|
||||
|
||||
# 颜色英文名(color_detail_images.color_name_web,按 color_id)
|
||||
pc.execute("""
|
||||
SELECT DISTINCT color_id, color_name_web
|
||||
FROM color_detail_images
|
||||
WHERE color_id IN (
|
||||
SELECT id FROM colors
|
||||
WHERE category_id IN (SELECT id FROM categories WHERE plate_id IN %s)
|
||||
)
|
||||
""", (plate_ids,))
|
||||
color_en_by_id = {}
|
||||
for col_id, en in pc.fetchall():
|
||||
en = clean(en)
|
||||
if en:
|
||||
color_en_by_id[col_id] = en
|
||||
|
||||
# size_chart 回退
|
||||
pc.execute(f"""
|
||||
SELECT category_id, size, shoulder, bust, length, sleeve
|
||||
FROM size_chart
|
||||
WHERE category_id IN (SELECT id FROM categories WHERE plate_id IN %s)
|
||||
ORDER BY category_id, seq
|
||||
""", (plate_ids,))
|
||||
sizechart_by_cat = {}
|
||||
for cid, size, sh, bu, le, sl in pc.fetchall():
|
||||
sizechart_by_cat.setdefault(cid, []).append(dict(size=size, shoulder=sh, bust=bu,
|
||||
length=le, sleeve=sl))
|
||||
|
||||
# prices(首条价格)
|
||||
pc.execute("""
|
||||
SELECT code, price FROM prices
|
||||
WHERE code IN (SELECT DISTINCT code FROM categories WHERE plate_id IN %s)
|
||||
ORDER BY code, web_product_id
|
||||
""", (plate_ids,))
|
||||
price_by_code = {}
|
||||
for code, price in pc.fetchall():
|
||||
if code not in price_by_code:
|
||||
price_by_code[code] = price
|
||||
|
||||
# 图片(按 code,取首个 web_sku 的图)
|
||||
pc.execute("""
|
||||
SELECT cd.code, cd.web_sku, cd.seq, cd.image_url
|
||||
FROM color_detail_images cd
|
||||
WHERE cd.code IN (SELECT DISTINCT code FROM categories WHERE plate_id IN %s)
|
||||
ORDER BY cd.code, cd.web_sku, cd.seq
|
||||
""", (plate_ids,))
|
||||
imgs_by_code = {}
|
||||
for code, wsku, seq, url in pc.fetchall():
|
||||
imgs_by_code.setdefault(code, []).append((wsku, seq, url))
|
||||
|
||||
pg.close()
|
||||
|
||||
# ---------- 写入 SQLite ----------
|
||||
db = sqlite3.connect(SQLITE)
|
||||
cur = db.cursor()
|
||||
|
||||
if clear_first:
|
||||
cur.execute("DELETE FROM SKU")
|
||||
cur.execute("DELETE FROM SPU")
|
||||
db.commit()
|
||||
print("[clear] 已清空 SPU/SKU 旧数据")
|
||||
else:
|
||||
print("[append] 保留现有数据,仅追加新国家")
|
||||
|
||||
# append 模式:收集已存在的 SPU code 与 SKU 组合,避免重复插入
|
||||
existing_spu_codes = set()
|
||||
existing_sku_keys = set()
|
||||
if not clear_first:
|
||||
existing_spu_codes = {r[0] for r in cur.execute("SELECT code FROM SPU")}
|
||||
existing_sku_keys = {(r[0], r[1], r[2])
|
||||
for r in cur.execute("SELECT spu_id, code, size FROM SKU")}
|
||||
print(f"[append] 已有 SPU {len(existing_spu_codes)} 条, SKU {len(existing_sku_keys)} 条")
|
||||
|
||||
# SPU
|
||||
spu_id_by_code = {}
|
||||
n_spu = 0
|
||||
n_spu_skip = 0
|
||||
for code, s in sorted(spu_map.items()):
|
||||
# append 模式:若该 SPU code 已存在则跳过(保留首次写入的数据)
|
||||
if code in existing_spu_codes:
|
||||
spu_id = cur.execute("SELECT id FROM SPU WHERE code=?", (code,)).fetchone()[0]
|
||||
spu_id_by_code[code] = spu_id
|
||||
n_spu_skip += 1
|
||||
continue
|
||||
comp = comp_by_cat.get(s["cid"], {}) or {}
|
||||
weight, wunit = extract_weight(s["name"])
|
||||
# 业务模板固定值(按用户要求,不随 PG 数据变化)
|
||||
fixed = dict(
|
||||
pattern="印花", details="无", collar_style="圆领", style="休闲",
|
||||
care_Instructions="数码印花类可机洗且不可干洗", fabric="微弹",
|
||||
target_audience="成人", season="四季", is_transparent="否", layout="常规",
|
||||
weaving_method="针织(含钩织、毛织面料)", printing_type="定位印花",
|
||||
fabric_texture_1="光面", fabric_weight_unit_1="g/㎡",
|
||||
lining_texture="无里料/无内衬", mark="1",
|
||||
)
|
||||
cur.execute("""
|
||||
INSERT INTO SPU (
|
||||
code, material, component_1, component_2, component_3,
|
||||
component_proportion_1, component_proportion_2, component_proportion_3,
|
||||
pattern, details, collar_style, style, care_Instructions, fabric,
|
||||
target_audience, season, is_transparent, layout, weaving_method,
|
||||
printing_type, fabric_texture_1, fabric_weight_1, fabric_weight_unit_1,
|
||||
lining_texture, country, mark
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
code, clean(s["fabric"]),
|
||||
clean(comp.get("c1")), clean(comp.get("c2")), clean(comp.get("c3")),
|
||||
format_pct(comp.get("p1")), format_pct(comp.get("p2")), format_pct(comp.get("p3")),
|
||||
fixed["pattern"], fixed["details"], fixed["collar_style"], fixed["style"],
|
||||
fixed["care_Instructions"], fixed["fabric"], fixed["target_audience"],
|
||||
fixed["season"], fixed["is_transparent"], fixed["layout"], fixed["weaving_method"],
|
||||
fixed["printing_type"], fixed["fabric_texture_1"], weight, fixed["fabric_weight_unit_1"],
|
||||
fixed["lining_texture"],
|
||||
country, fixed["mark"],
|
||||
))
|
||||
spu_id_by_code[code] = cur.lastrowid
|
||||
n_spu += 1
|
||||
|
||||
# SKU(颜色 × 尺码)
|
||||
n_sku = 0
|
||||
n_sku_skip = 0
|
||||
detail = dict(no_color=0, no_size=0, no_pkg=0, no_img=0, color_sku=0)
|
||||
for code, s in sorted(spu_map.items()):
|
||||
spu_id = spu_id_by_code[code]
|
||||
cat_id = s["cid"]
|
||||
extra = extra_by_code.get(code, {})
|
||||
|
||||
# 颜色列表
|
||||
color_list = colors_by_cat.get(cat_id, [])
|
||||
if not color_list:
|
||||
color_list = [dict(name=None, code=code, sr=None)] # 兜底
|
||||
detail["no_color"] += 1
|
||||
# 品类尺码范围(取首个非空)
|
||||
sr = next((c["sr"] for c in color_list if c["sr"]), None)
|
||||
if sr is None:
|
||||
sr = None
|
||||
|
||||
# 尺码表:product_size JSON 优先,回退 size_chart
|
||||
size_rows = parse_size_json(extra.get("psize"))
|
||||
if not size_rows:
|
||||
size_rows = sizechart_by_cat.get(cat_id, [])
|
||||
if not size_rows:
|
||||
detail["no_size"] += 1
|
||||
pkg_map = parse_pkg_json(extra.get("pkg"))
|
||||
weight_map = parse_weight_json(extra.get("pkg"))
|
||||
# 归一化兜底:尺码列可能带英寸后缀(如 '30*40/76.2*101.6' vs '30*40')
|
||||
pkg_map_norm = {k.split("/")[0]: v for k, v in pkg_map.items()}
|
||||
weight_map_norm = {k.split("/")[0]: v for k, v in weight_map.items()}
|
||||
|
||||
def lookup_pkg(size):
|
||||
v = pkg_map.get(size) or pkg_map_norm.get(size.split("/")[0])
|
||||
if v is None and ("one" in size.lower() or "均码" in size):
|
||||
v = pkg_map.get("均码") or pkg_map.get("Onesize") or pkg_map.get("OneSize")
|
||||
return v
|
||||
|
||||
def lookup_weight(size):
|
||||
v = weight_map.get(size) or weight_map_norm.get(size.split("/")[0])
|
||||
if v is None and ("one" in size.lower() or "均码" in size):
|
||||
v = weight_map.get("均码") or weight_map.get("Onesize") or weight_map.get("OneSize")
|
||||
return v
|
||||
|
||||
# 图片
|
||||
imgs = imgs_by_code.get(code, [])
|
||||
if imgs:
|
||||
first_sku = imgs[0][0]
|
||||
urls = [u for ws, _, u in imgs if ws == first_sku]
|
||||
else:
|
||||
urls = []
|
||||
if not urls:
|
||||
detail["no_img"] += 1
|
||||
img2 = urls[1] if len(urls) > 1 else None
|
||||
img3 = urls[2] if len(urls) > 2 else None
|
||||
img4 = urls[3] if len(urls) > 3 else None
|
||||
img5 = urls[4] if len(urls) > 4 else None
|
||||
|
||||
price = price_by_code.get(code)
|
||||
# 按用户要求:size_group 固定为"尺码",size_type 固定为"欧美尺码常规"
|
||||
sgroup = "尺码"
|
||||
stype = "欧美尺码常规"
|
||||
|
||||
for col in color_list:
|
||||
raw_code = col["code"]
|
||||
raw_name = col["name"]
|
||||
col_id = col["id"]
|
||||
cname = clean_color_name(raw_name) # 清洗颜色名(去尺码前缀/区间)
|
||||
# 颜色英文名:优先 PG color_name_web,否则用中文名查兜底字典/取首个颜色
|
||||
en = None
|
||||
if cname:
|
||||
en = color_en_by_id.get(col_id) or first_color_en(cname)
|
||||
# 命名规则:
|
||||
# 规范 code(品类code-英文/缩写,无中文,如 JPHM009-BL01 / JPTM007-ESPRESSO)
|
||||
# -> 直接使用
|
||||
# 缺失/脏 code(含中文或尺码区间)-> 款号(品类code)-颜色的英文名
|
||||
if not is_clean_code(raw_code, code):
|
||||
ccode = f"{code}-{en}" if en else code
|
||||
else:
|
||||
ccode = raw_code
|
||||
for srow in size_rows:
|
||||
size = clean(srow["size"])
|
||||
if not size:
|
||||
continue
|
||||
# append 模式:跳过已存在的 SKU 组合
|
||||
if (spu_id, ccode, size) in existing_sku_keys:
|
||||
n_sku_skip += 1
|
||||
continue
|
||||
pkg = lookup_pkg(size)
|
||||
pkg_weight = lookup_weight(size)
|
||||
cur.execute("""
|
||||
INSERT INTO SKU (
|
||||
spu_id, code, price, color, size, size_group, size_type,
|
||||
shoulder_width, bust, clothing_length, sleeve_length,
|
||||
longest_side, secondary_long_side, shortest_side,
|
||||
package_weight, img_url_2, img_url_3, img_url_4, img_url_5
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
spu_id, ccode, price, cname, size, sgroup, stype,
|
||||
clean(srow.get("shoulder")), clean(srow.get("bust")),
|
||||
clean(srow.get("length")), clean(srow.get("sleeve")),
|
||||
str(pkg[0]) if pkg else None,
|
||||
str(pkg[1]) if pkg else None,
|
||||
str(pkg[2]) if pkg else None,
|
||||
pkg_weight,
|
||||
img2, img3, img4, img5,
|
||||
))
|
||||
n_sku += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
# ---------- 验证 ----------
|
||||
print(f" 本次新增 SPU: {n_spu} (跳过已存在 {n_spu_skip})")
|
||||
print(f" 本次新增 SKU: {n_sku} (跳过已存在 {n_sku_skip}) (颜色×尺码展开)")
|
||||
print(f" 无颜色品类: {detail['no_color']}, 无尺码品类: {detail['no_size']}, 无图品类: {detail['no_img']}")
|
||||
print(f" 库内 SPU 总数: {cur.execute('SELECT COUNT(*) FROM SPU').fetchone()[0]}")
|
||||
print(f" 库内 SKU 总数: {cur.execute('SELECT COUNT(*) FROM SKU').fetchone()[0]}")
|
||||
print(f" country 分布: {cur.execute('SELECT country, COUNT(*) FROM SPU GROUP BY country').fetchall()}")
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 顺序执行:先全量同步美国(重置整库),再依次追加其他国家(保留已有数据)
|
||||
sync((2, 3, 4, 5), "US", clear_first=True)
|
||||
sync((6,), "MX", clear_first=False)
|
||||
sync((7,), "JP", clear_first=False)
|
||||
sync((11,), "GB", clear_first=False)
|
||||
print("\n✅ 同步完成:SPU/SKU 已包含 US + MX + JP + GB 数据")
|
||||
Reference in New Issue
Block a user