qrpa 1.0.13__py3-none-any.whl → 1.1.50__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ SHEIN商品数据模型
5
+ 使用SQLAlchemy定义SKC表、SKU表和详情表结构
6
+ """
7
+
8
+ from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, JSON, Date, DECIMAL, Index
9
+ from sqlalchemy.ext.declarative import declarative_base
10
+ from sqlalchemy.orm import sessionmaker
11
+ from datetime import datetime
12
+ import json
13
+ import os
14
+
15
+ # 创建基类
16
+ Base = declarative_base()
17
+
18
+ class SheinProductSkc(Base):
19
+ """
20
+ SHEIN商品SKC表
21
+ 存储SKC维度的商品信息
22
+ """
23
+ __tablename__ = 'shein_product_skc'
24
+
25
+ # 主键ID
26
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
27
+
28
+ # 原始数据中的ID作为skc_id
29
+ skc_id = Column(Integer, nullable=False, unique=True, comment='原始SKC ID')
30
+
31
+ # 店铺信息
32
+ store_username = Column(String(50), nullable=True, comment='店铺账号')
33
+ store_name = Column(String(100), nullable=True, comment='店铺名称')
34
+ store_manager = Column(String(50), nullable=True, comment='店长')
35
+
36
+ # 商品基本信息
37
+ pic_url = Column(Text, nullable=True, comment='SKC主图')
38
+ supplier_code = Column(String(100), nullable=True, comment='商家SKC')
39
+ skc = Column(String(50), nullable=True, comment='平台SKC')
40
+ spu = Column(String(50), nullable=True, comment='SPU编码')
41
+ category_name = Column(String(100), nullable=True, comment='叶子类目')
42
+
43
+ # 上架信息
44
+ shelf_days = Column(Integer, nullable=True, comment='上架天数')
45
+ shelf_date = Column(Date, nullable=True, comment='上架日期')
46
+
47
+ # 商品标签和状态
48
+ goods_label_list = Column(JSON, nullable=True, comment='商品标签列表')
49
+ supply_status = Column(String(50), nullable=True, comment='供货状态')
50
+ shelf_status = Column(Integer, nullable=True, comment='上架状态')
51
+ goods_level = Column(String(50), nullable=True, comment='商品等级')
52
+
53
+ # 其他字段
54
+ group_flag = Column(Integer, nullable=True, comment='组合标志')
55
+ goods_level_can_order_flag = Column(Integer, nullable=True, comment='商品等级可下单标志')
56
+ stock_standard = Column(Integer, nullable=True, comment='备货标准')
57
+ stock_warn_status = Column(Integer, nullable=True, comment='库存预警状态')
58
+
59
+ # 销量汇总
60
+ c7d_sale_cnt_sum = Column(Integer, nullable=True, comment='7日销量汇总')
61
+ shein_sale_by_inventory = Column(Integer, nullable=True, comment='希音库存销售')
62
+
63
+ # 时间戳
64
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
65
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
66
+
67
+ # 用户备注字段(供后续web界面使用)
68
+ user_notes = Column(Text, nullable=True, comment='用户备注')
69
+
70
+ # 定义索引
71
+ __table_args__ = (
72
+ Index('ix_skc_id', 'skc_id'),
73
+ Index('ix_skc', 'skc'),
74
+ Index('ix_spu', 'spu'),
75
+ Index('ix_supplier_code', 'supplier_code'),
76
+ Index('ix_store_username', 'store_username'),
77
+ )
78
+
79
+ def __repr__(self):
80
+ return f"<SheinProductSkc(id={self.id}, skc='{self.skc}', spu='{self.spu}')>"
81
+
82
+ class SheinProductSku(Base):
83
+ """
84
+ SHEIN商品SKU表
85
+ 存储SKU维度的商品信息
86
+ """
87
+ __tablename__ = 'shein_product_sku'
88
+
89
+ # 主键ID
90
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
91
+
92
+ # 原始数据中的ID作为sku_id
93
+ sku_id = Column(Integer, nullable=False, unique=True, comment='原始SKU ID')
94
+
95
+ # 关联SKC本地主键ID
96
+ local_skc_id = Column(Integer, nullable=False, comment='关联的SKC本地主键ID')
97
+
98
+ # SKU基本信息
99
+ sort_value = Column(Integer, nullable=True, comment='排序值')
100
+ sku_code = Column(String(50), nullable=True, comment='SKU编码')
101
+ attr = Column(String(100), nullable=True, comment='SKU属性')
102
+ supplier_sku = Column(String(100), nullable=True, comment='供应商SKU')
103
+
104
+ # 销量数据
105
+ order_cnt = Column(Integer, nullable=True, comment='今日订单数')
106
+ total_sale_volume = Column(Integer, nullable=True, comment='今日总销量')
107
+ c7d_sale_cnt = Column(Integer, nullable=True, comment='7日销量')
108
+ c30d_sale_cnt = Column(Integer, nullable=True, comment='30日销量')
109
+ old_c7d_sale_cnt = Column(Integer, nullable=True, comment='更新前的7日销量')
110
+ old_c30d_sale_cnt = Column(Integer, nullable=True, comment='更新前的30日销量')
111
+ sale_cnt_updated_at = Column(DateTime, nullable=True, comment='销量更新时间')
112
+ old_sale_cnt_updated_at = Column(DateTime, nullable=True, comment='旧销量更新时间')
113
+
114
+ # 价格成本信息
115
+ price = Column(DECIMAL(10, 2), nullable=True, comment='价格')
116
+ erp_cost_price = Column(DECIMAL(10, 2), nullable=True, comment='ERP成本价')
117
+ erp_supplier_name = Column(String(100), nullable=True, comment='ERP默认供货商')
118
+ erp_stock = Column(Integer, nullable=True, comment='ERP库存')
119
+
120
+ # 时间戳
121
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
122
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
123
+
124
+ # 定义索引
125
+ __table_args__ = (
126
+ Index('ix_sku_id', 'sku_id'),
127
+ Index('ix_local_skc_id', 'local_skc_id'),
128
+ Index('ix_sku_code', 'sku_code'),
129
+ Index('ix_supplier_sku', 'supplier_sku'),
130
+ )
131
+
132
+ def __repr__(self):
133
+ return f"<SheinProductSku(id={self.id}, sku_code='{self.sku_code}', attr='{self.attr}')>"
134
+
135
+ class SheinProductDetail(Base):
136
+ """
137
+ SHEIN商品详情信息表
138
+ 以KV形式保存SPU维度的JSON数据
139
+ """
140
+ __tablename__ = 'shein_product_detail'
141
+
142
+ # 主键ID
143
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
144
+
145
+ # SPU编码
146
+ spu = Column(String(50), nullable=False, comment='SPU编码')
147
+
148
+ # 字段名称
149
+ field_name = Column(String(100), nullable=False, comment='字段名称')
150
+
151
+ # JSON数据
152
+ json_data = Column(JSON, nullable=True, comment='JSON数据')
153
+
154
+ # 时间戳
155
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
156
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
157
+
158
+ # 定义索引
159
+ __table_args__ = (
160
+ Index('ix_spu', 'spu'),
161
+ Index('ix_field_name', 'field_name'),
162
+ Index('ix_spu_field', 'spu', 'field_name'),
163
+ )
164
+
165
+ def __repr__(self):
166
+ return f"<SheinProductDetail(id={self.id}, spu='{self.spu}', field_name='{self.field_name}')>"
167
+
168
+ class SheinProductManager:
169
+ """
170
+ SHEIN商品数据管理器
171
+ 提供数据库操作相关方法
172
+ """
173
+
174
+ def __init__(self, database_url):
175
+ """
176
+ 初始化数据库连接
177
+
178
+ Args:
179
+ database_url (str): 数据库连接URL,例如:
180
+ mysql+pymysql://username:password@localhost:3306/database_name
181
+ """
182
+ self.engine = create_engine(database_url, echo=False)
183
+ self.Session = sessionmaker(bind=self.engine)
184
+
185
+ def create_tables(self):
186
+ """
187
+ 创建数据表
188
+ """
189
+ Base.metadata.create_all(self.engine)
190
+ print("数据表创建成功!")
191
+
192
+ def drop_tables(self):
193
+ """
194
+ 删除数据表
195
+ """
196
+ Base.metadata.drop_all(self.engine)
197
+ print("数据表删除成功!")
198
+
199
+ def _parse_date(self, date_str):
200
+ """
201
+ 解析日期字符串
202
+ """
203
+ if not date_str:
204
+ return None
205
+ try:
206
+ return datetime.strptime(date_str, '%Y-%m-%d').date()
207
+ except:
208
+ return None
209
+
210
+ def _extract_goods_labels(self, goods_label_list):
211
+ """
212
+ 提取商品标签名称列表
213
+ """
214
+ if not goods_label_list:
215
+ return []
216
+ return [label.get('name', '') for label in goods_label_list if isinstance(label, dict)]
217
+
218
+ def upsert_product_data(self, data_list):
219
+ """
220
+ 从JSON数据中执行upsert操作(插入或更新)
221
+
222
+ Args:
223
+ store_username (str): 店铺账号
224
+ data_list (list): 商品数据列表
225
+ """
226
+ session = self.Session()
227
+ try:
228
+ for data in data_list:
229
+
230
+ # 处理SKC数据
231
+ skc_record = self._upsert_skc_data(session, data)
232
+
233
+ # 处理SKU数据
234
+ for sku_data in data.get('skuList', []):
235
+ sku_data['local_skc_id'] = skc_record.id
236
+ self._upsert_sku_data(session, sku_data)
237
+
238
+ session.commit()
239
+ print(f"成功处理 {len(data_list)} 条商品数据")
240
+
241
+ except Exception as e:
242
+ session.rollback()
243
+ print(f"处理数据失败: {e}")
244
+ raise
245
+ finally:
246
+ session.close()
247
+
248
+ def _upsert_skc_data(self, session, data):
249
+ """
250
+ 插入或更新SKC数据
251
+ 返回SKC记录对象
252
+ """
253
+ skc_id = data.get('id')
254
+ existing_skc = session.query(SheinProductSkc).filter_by(skc_id=skc_id).first()
255
+
256
+ if existing_skc:
257
+ # 更新现有记录(保留用户备注)
258
+ existing_skc.store_username = data.get('store_username')
259
+ existing_skc.store_name = data.get('store_name')
260
+ existing_skc.store_manager = data.get('store_manager')
261
+ existing_skc.pic_url = data.get('picUrl')
262
+ existing_skc.supplier_code = data.get('supplierCode')
263
+ existing_skc.skc = data.get('skc')
264
+ existing_skc.spu = data.get('spu')
265
+ existing_skc.category_name = data.get('categoryName')
266
+ existing_skc.shelf_days = data.get('shelfDays')
267
+ existing_skc.shelf_date = self._parse_date(data.get('shelfDate'))
268
+ existing_skc.goods_label_list = self._extract_goods_labels(data.get('goodsLabelList'))
269
+ existing_skc.supply_status = data.get('supplyStatus', {}).get('name') if data.get('supplyStatus') else None
270
+ existing_skc.shelf_status = data.get('shelfStatus', {}).get('value') if data.get('shelfStatus') else None
271
+ existing_skc.goods_level = data.get('goodsLevel', {}).get('name') if data.get('goodsLevel') else None
272
+ existing_skc.group_flag = data.get('groupFlag')
273
+ existing_skc.goods_level_can_order_flag = 1 if data.get('goodsLevelCanOrderFlag') else 0
274
+ existing_skc.stock_standard = data.get('stockStandard', {}).get('value') if data.get('stockStandard') else None
275
+ existing_skc.stock_warn_status = data.get('stockWarnStatus', {}).get('value') if data.get('stockWarnStatus') else None
276
+ existing_skc.c7d_sale_cnt_sum = data.get('c7dSaleCntSum')
277
+ existing_skc.shein_sale_by_inventory = data.get('sheinSaleByInventory')
278
+ existing_skc.updated_at = datetime.now()
279
+ return existing_skc
280
+ else:
281
+ # 插入新记录
282
+ new_skc = SheinProductSkc(
283
+ skc_id=skc_id,
284
+ store_username=data.get('store_username'),
285
+ store_name=data.get('store_name'),
286
+ store_manager=data.get('store_manager'),
287
+ pic_url=data.get('picUrl'),
288
+ supplier_code=data.get('supplierCode'),
289
+ skc=data.get('skc'),
290
+ spu=data.get('spu'),
291
+ category_name=data.get('categoryName'),
292
+ shelf_days=data.get('shelfDays'),
293
+ shelf_date=self._parse_date(data.get('shelfDate')),
294
+ goods_label_list=self._extract_goods_labels(data.get('goodsLabelList')),
295
+ supply_status=data.get('supplyStatus', {}).get('name') if data.get('supplyStatus') else None,
296
+ shelf_status=data.get('shelfStatus', {}).get('value') if data.get('shelfStatus') else None,
297
+ goods_level=data.get('goodsLevel', {}).get('name') if data.get('goodsLevel') else None,
298
+ group_flag=data.get('groupFlag'),
299
+ goods_level_can_order_flag=1 if data.get('goodsLevelCanOrderFlag') else 0,
300
+ stock_standard=data.get('stockStandard', {}).get('value') if data.get('stockStandard') else None,
301
+ stock_warn_status=data.get('stockWarnStatus', {}).get('value') if data.get('stockWarnStatus') else None,
302
+ c7d_sale_cnt_sum=data.get('c7dSaleCntSum'),
303
+ shein_sale_by_inventory=data.get('sheinSaleByInventory')
304
+ )
305
+ session.add(new_skc)
306
+ session.flush() # 获取主键ID
307
+ return new_skc
308
+
309
+ def _upsert_sku_data(self, session, sku_data):
310
+ """
311
+ 插入或更新SKU数据
312
+ """
313
+ sku_id = sku_data.get('id')
314
+ existing_sku = session.query(SheinProductSku).filter_by(sku_id=sku_id).first()
315
+
316
+ if existing_sku:
317
+ # 更新现有记录(保留用户备注)
318
+ existing_sku.local_skc_id = sku_data.get('local_skc_id')
319
+ existing_sku.sort_value = sku_data.get('sortValue')
320
+ existing_sku.sku_code = sku_data.get('skuCode')
321
+ existing_sku.attr = sku_data.get('attr')
322
+ existing_sku.supplier_sku = sku_data.get('supplierSku')
323
+ existing_sku.order_cnt = sku_data.get('orderCnt')
324
+ existing_sku.total_sale_volume = sku_data.get('totalSaleVolume')
325
+ # 保存旧的销量数据和更新时间
326
+ existing_sku.old_c7d_sale_cnt = existing_sku.c7d_sale_cnt
327
+ existing_sku.old_c30d_sale_cnt = existing_sku.c30d_sale_cnt
328
+ existing_sku.old_sale_cnt_updated_at = existing_sku.sale_cnt_updated_at
329
+ # 更新新的销量数据和更新时间
330
+ existing_sku.c7d_sale_cnt = sku_data.get('c7dSaleCnt')
331
+ existing_sku.c30d_sale_cnt = sku_data.get('c30dSaleCnt')
332
+ existing_sku.sale_cnt_updated_at = datetime.now()
333
+ existing_sku.price = sku_data.get('price')
334
+ existing_sku.erp_cost_price = sku_data.get('erp_cost_price')
335
+ existing_sku.erp_supplier_name = sku_data.get('erp_supplier_name')
336
+ existing_sku.erp_stock = sku_data.get('erp_stock')
337
+ existing_sku.updated_at = datetime.now()
338
+ else:
339
+ # 插入新记录
340
+ new_sku = SheinProductSku(
341
+ sku_id=sku_id,
342
+ local_skc_id=sku_data.get('local_skc_id'),
343
+ sort_value=sku_data.get('sortValue'),
344
+ sku_code=sku_data.get('skuCode'),
345
+ attr=sku_data.get('attr'),
346
+ supplier_sku=sku_data.get('supplierSku'),
347
+ order_cnt=sku_data.get('orderCnt'),
348
+ total_sale_volume=sku_data.get('totalSaleVolume'),
349
+ c7d_sale_cnt=sku_data.get('c7dSaleCnt'),
350
+ c30d_sale_cnt=sku_data.get('c30dSaleCnt'),
351
+ sale_cnt_updated_at=datetime.now(),
352
+ price=sku_data.get('price'),
353
+ erp_cost_price=sku_data.get('erp_cost_price'),
354
+ erp_supplier_name=sku_data.get('erp_supplier_name')
355
+ )
356
+ session.add(new_sku)
357
+
358
+ def upsert_product_detail(self, spu, field_name, json_data):
359
+ """
360
+ 插入或更新商品详情数据
361
+
362
+ Args:
363
+ spu (str): SPU编码
364
+ field_name (str): 字段名称
365
+ json_data (dict): JSON数据
366
+ """
367
+ session = self.Session()
368
+ try:
369
+ existing_detail = session.query(SheinProductDetail).filter_by(
370
+ spu=spu, field_name=field_name
371
+ ).first()
372
+
373
+ if existing_detail:
374
+ # 更新现有记录
375
+ existing_detail.json_data = json_data
376
+ existing_detail.updated_at = datetime.now()
377
+ else:
378
+ # 插入新记录
379
+ new_detail = SheinProductDetail(
380
+ spu=spu,
381
+ field_name=field_name,
382
+ json_data=json_data
383
+ )
384
+ session.add(new_detail)
385
+
386
+ session.commit()
387
+ print(f"成功处理详情数据: spu={spu}, field_name={field_name}")
388
+
389
+ except Exception as e:
390
+ session.rollback()
391
+ print(f"处理详情数据失败: {e}")
392
+ raise
393
+ finally:
394
+ session.close()
395
+
396
+ def get_skc_products(self, limit=None, offset=None):
397
+ """
398
+ 查询SKC商品列表
399
+
400
+ Args:
401
+ limit (int): 限制返回数量
402
+ offset (int): 偏移量
403
+
404
+ Returns:
405
+ list: SKC商品列表
406
+ """
407
+ session = self.Session()
408
+ try:
409
+ query = session.query(SheinProductSkc)
410
+ if offset:
411
+ query = query.offset(offset)
412
+ if limit:
413
+ query = query.limit(limit)
414
+ return query.all()
415
+ finally:
416
+ session.close()
417
+
418
+ def search_products(self, **kwargs):
419
+ """
420
+ 根据条件搜索商品
421
+
422
+ Args:
423
+ **kwargs: 搜索条件,如skc, spu, store_username等
424
+
425
+ Returns:
426
+ list: 符合条件的商品列表
427
+ """
428
+ session = self.Session()
429
+ try:
430
+ query = session.query(SheinProductSkc)
431
+
432
+ # 根据传入的条件进行过滤
433
+ for key, value in kwargs.items():
434
+ if hasattr(SheinProductSkc, key) and value is not None:
435
+ if isinstance(value, str) and key in ['skc', 'spu', 'supplier_code', 'category_name']:
436
+ # 字符串字段支持模糊搜索
437
+ query = query.filter(getattr(SheinProductSkc, key).like(f'%{value}%'))
438
+ else:
439
+ query = query.filter(getattr(SheinProductSkc, key) == value)
440
+
441
+ return query.all()
442
+ finally:
443
+ session.close()
444
+
445
+ def example_usage():
446
+ """
447
+ 使用示例
448
+ """
449
+ # 数据库连接URL(请根据实际情况修改)
450
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
451
+
452
+ # 创建管理器实例
453
+ manager = SheinProductManager(database_url)
454
+
455
+ # 创建数据表
456
+ manager.create_tables()
457
+
458
+ # 从JSON文件导入数据
459
+ json_file = "skc_list_GS4355889.json"
460
+
461
+ # 读取JSON文件
462
+ with open(json_file, 'r', encoding='utf-8') as f:
463
+ data_dict = json.load(f)
464
+ for store_username, data_list in data_dict.items():
465
+ manager.upsert_product_data(data_list)
466
+
467
+ # 查询示例
468
+ products = manager.get_skc_products(limit=10)
469
+ for product in products:
470
+ print(f"SKC: {product.skc}, SPU: {product.spu}, 类目: {product.category_name}")
471
+
472
+ # 搜索示例
473
+ search_results = manager.search_products(store_username="GS4355889", shelf_status=1)
474
+ print(f"已上架商品数量: {len(search_results)}")
475
+
476
+ # 详情数据示例
477
+ # manager.upsert_product_detail("i250319906447", "attribute_template", {"template_id": "t23082580827"})
478
+
479
+ def example_usage2():
480
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
481
+ manager = SheinProductManager(database_url)
482
+ manager.create_tables()
483
+ # 从JSON文件导入数据
484
+ json_file = "attribute_template_t23082580827.json"
485
+ # 读取JSON文件
486
+ with open(json_file, 'r', encoding='utf-8') as f:
487
+ data_dict = json.load(f)
488
+ manager.upsert_product_detail("t23082580827", "attribute_template", data_dict)
489
+
490
+ if __name__ == "__main__":
491
+ pass
492
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
493
+ manager = SheinProductManager(database_url)
494
+ manager.create_tables()
495
+ # example_usage()