qrpa 1.0.65__py3-none-any.whl → 1.0.68__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.

Potentially problematic release.


This version of qrpa might be problematic. Click here for more details.

@@ -0,0 +1,476 @@
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
+ # 定义索引
68
+ __table_args__ = (
69
+ Index('ix_skc_id', 'skc_id'),
70
+ Index('ix_skc', 'skc'),
71
+ Index('ix_spu', 'spu'),
72
+ Index('ix_supplier_code', 'supplier_code'),
73
+ Index('ix_store_username', 'store_username'),
74
+ )
75
+
76
+ def __repr__(self):
77
+ return f"<SheinProductSkc(id={self.id}, skc='{self.skc}', spu='{self.spu}')>"
78
+
79
+ class SheinProductSku(Base):
80
+ """
81
+ SHEIN商品SKU表
82
+ 存储SKU维度的商品信息
83
+ """
84
+ __tablename__ = 'shein_product_sku'
85
+
86
+ # 主键ID
87
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
88
+
89
+ # 原始数据中的ID作为sku_id
90
+ sku_id = Column(Integer, nullable=False, unique=True, comment='原始SKU ID')
91
+
92
+ # 关联SKC本地主键ID
93
+ local_skc_id = Column(Integer, nullable=False, comment='关联的SKC本地主键ID')
94
+
95
+ # SKU基本信息
96
+ sort_value = Column(Integer, nullable=True, comment='排序值')
97
+ sku_code = Column(String(50), nullable=True, comment='SKU编码')
98
+ attr = Column(String(100), nullable=True, comment='SKU属性')
99
+ supplier_sku = Column(String(100), nullable=True, comment='供应商SKU')
100
+
101
+ # 销量数据
102
+ order_cnt = Column(Integer, nullable=True, comment='今日订单数')
103
+ total_sale_volume = Column(Integer, nullable=True, comment='今日总销量')
104
+ c7d_sale_cnt = Column(Integer, nullable=True, comment='7日销量')
105
+ c30d_sale_cnt = Column(Integer, nullable=True, comment='30日销量')
106
+
107
+ # 价格成本信息
108
+ price = Column(DECIMAL(10, 2), nullable=True, comment='价格')
109
+ erp_cost_price = Column(DECIMAL(10, 2), nullable=True, comment='ERP成本价')
110
+ erp_supplier_name = Column(String(100), nullable=True, comment='ERP默认供货商')
111
+
112
+ # 时间戳
113
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
114
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
115
+
116
+ # 定义索引
117
+ __table_args__ = (
118
+ Index('ix_sku_id', 'sku_id'),
119
+ Index('ix_local_skc_id', 'local_skc_id'),
120
+ Index('ix_sku_code', 'sku_code'),
121
+ Index('ix_supplier_sku', 'supplier_sku'),
122
+ )
123
+
124
+ def __repr__(self):
125
+ return f"<SheinProductSku(id={self.id}, sku_code='{self.sku_code}', attr='{self.attr}')>"
126
+
127
+ class SheinProductDetail(Base):
128
+ """
129
+ SHEIN商品详情信息表
130
+ 以KV形式保存SPU维度的JSON数据
131
+ """
132
+ __tablename__ = 'shein_product_detail'
133
+
134
+ # 主键ID
135
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
136
+
137
+ # SPU编码
138
+ spu = Column(String(50), nullable=False, comment='SPU编码')
139
+
140
+ # 字段名称
141
+ field_name = Column(String(100), nullable=False, comment='字段名称')
142
+
143
+ # JSON数据
144
+ json_data = Column(JSON, nullable=True, comment='JSON数据')
145
+
146
+ # 时间戳
147
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
148
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
149
+
150
+ # 定义索引
151
+ __table_args__ = (
152
+ Index('ix_spu', 'spu'),
153
+ Index('ix_field_name', 'field_name'),
154
+ Index('ix_spu_field', 'spu', 'field_name'),
155
+ )
156
+
157
+ def __repr__(self):
158
+ return f"<SheinProductDetail(id={self.id}, spu='{self.spu}', field_name='{self.field_name}')>"
159
+
160
+ class SheinProductManager:
161
+ """
162
+ SHEIN商品数据管理器
163
+ 提供数据库操作相关方法
164
+ """
165
+
166
+ def __init__(self, database_url):
167
+ """
168
+ 初始化数据库连接
169
+
170
+ Args:
171
+ database_url (str): 数据库连接URL,例如:
172
+ mysql+pymysql://username:password@localhost:3306/database_name
173
+ """
174
+ self.engine = create_engine(database_url, echo=False)
175
+ self.Session = sessionmaker(bind=self.engine)
176
+
177
+ def create_tables(self):
178
+ """
179
+ 创建数据表
180
+ """
181
+ Base.metadata.create_all(self.engine)
182
+ print("数据表创建成功!")
183
+
184
+ def drop_tables(self):
185
+ """
186
+ 删除数据表
187
+ """
188
+ Base.metadata.drop_all(self.engine)
189
+ print("数据表删除成功!")
190
+
191
+ def _parse_date(self, date_str):
192
+ """
193
+ 解析日期字符串
194
+ """
195
+ if not date_str:
196
+ return None
197
+ try:
198
+ return datetime.strptime(date_str, '%Y-%m-%d').date()
199
+ except:
200
+ return None
201
+
202
+ def _extract_goods_labels(self, goods_label_list):
203
+ """
204
+ 提取商品标签名称列表
205
+ """
206
+ if not goods_label_list:
207
+ return []
208
+ return [label.get('name', '') for label in goods_label_list if isinstance(label, dict)]
209
+
210
+ def upsert_product_data(self, data_list):
211
+ """
212
+ 从JSON数据中执行upsert操作(插入或更新)
213
+
214
+ Args:
215
+ store_username (str): 店铺账号
216
+ data_list (list): 商品数据列表
217
+ """
218
+ session = self.Session()
219
+ try:
220
+ for data in data_list:
221
+
222
+ # 处理SKC数据
223
+ skc_record = self._upsert_skc_data(session, data)
224
+
225
+ # 处理SKU数据
226
+ for sku_data in data.get('skuList', []):
227
+ sku_data['local_skc_id'] = skc_record.id
228
+ self._upsert_sku_data(session, sku_data)
229
+
230
+ session.commit()
231
+ print(f"成功处理 {len(data_list)} 条商品数据")
232
+
233
+ except Exception as e:
234
+ session.rollback()
235
+ print(f"处理数据失败: {e}")
236
+ raise
237
+ finally:
238
+ session.close()
239
+
240
+ def _upsert_skc_data(self, session, data):
241
+ """
242
+ 插入或更新SKC数据
243
+ 返回SKC记录对象
244
+ """
245
+ skc_id = data.get('id')
246
+ existing_skc = session.query(SheinProductSkc).filter_by(skc_id=skc_id).first()
247
+
248
+ if existing_skc:
249
+ # 更新现有记录(保留用户备注)
250
+ existing_skc.store_username = data.get('store_username')
251
+ existing_skc.store_name = data.get('store_name')
252
+ existing_skc.store_manager = data.get('store_manager')
253
+ existing_skc.pic_url = data.get('picUrl')
254
+ existing_skc.supplier_code = data.get('supplierCode')
255
+ existing_skc.skc = data.get('skc')
256
+ existing_skc.spu = data.get('spu')
257
+ existing_skc.category_name = data.get('categoryName')
258
+ existing_skc.shelf_days = data.get('shelfDays')
259
+ existing_skc.shelf_date = self._parse_date(data.get('shelfDate'))
260
+ existing_skc.goods_label_list = self._extract_goods_labels(data.get('goodsLabelList'))
261
+ existing_skc.supply_status = data.get('supplyStatus', {}).get('name') if data.get('supplyStatus') else None
262
+ existing_skc.shelf_status = data.get('shelfStatus', {}).get('value') if data.get('shelfStatus') else None
263
+ existing_skc.goods_level = data.get('goodsLevel', {}).get('name') if data.get('goodsLevel') else None
264
+ existing_skc.group_flag = data.get('groupFlag')
265
+ existing_skc.goods_level_can_order_flag = 1 if data.get('goodsLevelCanOrderFlag') else 0
266
+ existing_skc.stock_standard = data.get('stockStandard', {}).get('value') if data.get('stockStandard') else None
267
+ existing_skc.stock_warn_status = data.get('stockWarnStatus', {}).get('value') if data.get('stockWarnStatus') else None
268
+ existing_skc.c7d_sale_cnt_sum = data.get('c7dSaleCntSum')
269
+ existing_skc.shein_sale_by_inventory = data.get('sheinSaleByInventory')
270
+ existing_skc.updated_at = datetime.now()
271
+ return existing_skc
272
+ else:
273
+ # 插入新记录
274
+ new_skc = SheinProductSkc(
275
+ skc_id=skc_id,
276
+ store_username=data.get('store_username'),
277
+ store_name=data.get('store_name'),
278
+ store_manager=data.get('store_manager'),
279
+ pic_url=data.get('picUrl'),
280
+ supplier_code=data.get('supplierCode'),
281
+ skc=data.get('skc'),
282
+ spu=data.get('spu'),
283
+ category_name=data.get('categoryName'),
284
+ shelf_days=data.get('shelfDays'),
285
+ shelf_date=self._parse_date(data.get('shelfDate')),
286
+ goods_label_list=self._extract_goods_labels(data.get('goodsLabelList')),
287
+ supply_status=data.get('supplyStatus', {}).get('name') if data.get('supplyStatus') else None,
288
+ shelf_status=data.get('shelfStatus', {}).get('value') if data.get('shelfStatus') else None,
289
+ goods_level=data.get('goodsLevel', {}).get('name') if data.get('goodsLevel') else None,
290
+ group_flag=data.get('groupFlag'),
291
+ goods_level_can_order_flag=1 if data.get('goodsLevelCanOrderFlag') else 0,
292
+ stock_standard=data.get('stockStandard', {}).get('value') if data.get('stockStandard') else None,
293
+ stock_warn_status=data.get('stockWarnStatus', {}).get('value') if data.get('stockWarnStatus') else None,
294
+ c7d_sale_cnt_sum=data.get('c7dSaleCntSum'),
295
+ shein_sale_by_inventory=data.get('sheinSaleByInventory')
296
+ )
297
+ session.add(new_skc)
298
+ session.flush() # 获取主键ID
299
+ return new_skc
300
+
301
+ def _upsert_sku_data(self, session, sku_data):
302
+ """
303
+ 插入或更新SKU数据
304
+ """
305
+ sku_id = sku_data.get('id')
306
+ existing_sku = session.query(SheinProductSku).filter_by(sku_id=sku_id).first()
307
+
308
+ if existing_sku:
309
+ # 更新现有记录(保留用户备注)
310
+ existing_sku.local_skc_id = sku_data.get('local_skc_id')
311
+ existing_sku.sort_value = sku_data.get('sortValue')
312
+ existing_sku.sku_code = sku_data.get('skuCode')
313
+ existing_sku.attr = sku_data.get('attr')
314
+ existing_sku.supplier_sku = sku_data.get('supplierSku')
315
+ existing_sku.order_cnt = sku_data.get('orderCnt')
316
+ existing_sku.total_sale_volume = sku_data.get('totalSaleVolume')
317
+ existing_sku.c7d_sale_cnt = sku_data.get('c7dSaleCnt')
318
+ existing_sku.c30d_sale_cnt = sku_data.get('c30dSaleCnt')
319
+ existing_sku.price = sku_data.get('price')
320
+ existing_sku.erp_cost_price = sku_data.get('erp_cost_price')
321
+ existing_sku.erp_supplier_name = sku_data.get('erp_supplier_name')
322
+ existing_sku.updated_at = datetime.now()
323
+ else:
324
+ # 插入新记录
325
+ new_sku = SheinProductSku(
326
+ sku_id=sku_id,
327
+ local_skc_id=sku_data.get('local_skc_id'),
328
+ sort_value=sku_data.get('sortValue'),
329
+ sku_code=sku_data.get('skuCode'),
330
+ attr=sku_data.get('attr'),
331
+ supplier_sku=sku_data.get('supplierSku'),
332
+ order_cnt=sku_data.get('orderCnt'),
333
+ total_sale_volume=sku_data.get('totalSaleVolume'),
334
+ c7d_sale_cnt=sku_data.get('c7dSaleCnt'),
335
+ c30d_sale_cnt=sku_data.get('c30dSaleCnt'),
336
+ price=sku_data.get('price'),
337
+ erp_cost_price=sku_data.get('erp_cost_price'),
338
+ erp_supplier_name=sku_data.get('erp_supplier_name')
339
+ )
340
+ session.add(new_sku)
341
+
342
+ def upsert_product_detail(self, spu, field_name, json_data):
343
+ """
344
+ 插入或更新商品详情数据
345
+
346
+ Args:
347
+ spu (str): SPU编码
348
+ field_name (str): 字段名称
349
+ json_data (dict): JSON数据
350
+ """
351
+ session = self.Session()
352
+ try:
353
+ existing_detail = session.query(SheinProductDetail).filter_by(
354
+ spu=spu, field_name=field_name
355
+ ).first()
356
+
357
+ if existing_detail:
358
+ # 更新现有记录
359
+ existing_detail.json_data = json_data
360
+ existing_detail.updated_at = datetime.now()
361
+ else:
362
+ # 插入新记录
363
+ new_detail = SheinProductDetail(
364
+ spu=spu,
365
+ field_name=field_name,
366
+ json_data=json_data
367
+ )
368
+ session.add(new_detail)
369
+
370
+ session.commit()
371
+ print(f"成功处理详情数据: spu={spu}, field_name={field_name}")
372
+
373
+ except Exception as e:
374
+ session.rollback()
375
+ print(f"处理详情数据失败: {e}")
376
+ raise
377
+ finally:
378
+ session.close()
379
+
380
+ def get_skc_products(self, limit=None, offset=None):
381
+ """
382
+ 查询SKC商品列表
383
+
384
+ Args:
385
+ limit (int): 限制返回数量
386
+ offset (int): 偏移量
387
+
388
+ Returns:
389
+ list: SKC商品列表
390
+ """
391
+ session = self.Session()
392
+ try:
393
+ query = session.query(SheinProductSkc)
394
+ if offset:
395
+ query = query.offset(offset)
396
+ if limit:
397
+ query = query.limit(limit)
398
+ return query.all()
399
+ finally:
400
+ session.close()
401
+
402
+ def search_products(self, **kwargs):
403
+ """
404
+ 根据条件搜索商品
405
+
406
+ Args:
407
+ **kwargs: 搜索条件,如skc, spu, store_username等
408
+
409
+ Returns:
410
+ list: 符合条件的商品列表
411
+ """
412
+ session = self.Session()
413
+ try:
414
+ query = session.query(SheinProductSkc)
415
+
416
+ # 根据传入的条件进行过滤
417
+ for key, value in kwargs.items():
418
+ if hasattr(SheinProductSkc, key) and value is not None:
419
+ if isinstance(value, str) and key in ['skc', 'spu', 'supplier_code', 'category_name']:
420
+ # 字符串字段支持模糊搜索
421
+ query = query.filter(getattr(SheinProductSkc, key).like(f'%{value}%'))
422
+ else:
423
+ query = query.filter(getattr(SheinProductSkc, key) == value)
424
+
425
+ return query.all()
426
+ finally:
427
+ session.close()
428
+
429
+ def example_usage():
430
+ """
431
+ 使用示例
432
+ """
433
+ # 数据库连接URL(请根据实际情况修改)
434
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
435
+
436
+ # 创建管理器实例
437
+ manager = SheinProductManager(database_url)
438
+
439
+ # 创建数据表
440
+ manager.create_tables()
441
+
442
+ # 从JSON文件导入数据
443
+ json_file = "skc_list_GS4355889.json"
444
+
445
+ # 读取JSON文件
446
+ with open(json_file, 'r', encoding='utf-8') as f:
447
+ data_dict = json.load(f)
448
+ for store_username, data_list in data_dict.items():
449
+ manager.upsert_product_data(data_list)
450
+
451
+ # 查询示例
452
+ products = manager.get_skc_products(limit=10)
453
+ for product in products:
454
+ print(f"SKC: {product.skc}, SPU: {product.spu}, 类目: {product.category_name}")
455
+
456
+ # 搜索示例
457
+ search_results = manager.search_products(store_username="GS4355889", shelf_status=1)
458
+ print(f"已上架商品数量: {len(search_results)}")
459
+
460
+ # 详情数据示例
461
+ # manager.upsert_product_detail("i250319906447", "attribute_template", {"template_id": "t23082580827"})
462
+
463
+ def example_usage2():
464
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
465
+ manager = SheinProductManager(database_url)
466
+ manager.create_tables()
467
+ # 从JSON文件导入数据
468
+ json_file = "attribute_template_t23082580827.json"
469
+ # 读取JSON文件
470
+ with open(json_file, 'r', encoding='utf-8') as f:
471
+ data_dict = json.load(f)
472
+ manager.upsert_product_detail("t23082580827", "attribute_template", data_dict)
473
+
474
+ if __name__ == "__main__":
475
+ pass
476
+ # example_usage()
qrpa/shein_excel.py CHANGED
@@ -281,11 +281,11 @@ class SheinExcel:
281
281
  cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
282
282
  write_dict_to_file(cache_file_excel, excel_data)
283
283
 
284
- sheet_name = '希音退货列表'
285
- batch_excel_operations(self.config.excel_return_list, [
286
- (sheet_name, 'write', excel_data, ['W', 'Z']),
287
- (sheet_name, 'format', self.format_return_list)
288
- ])
284
+ # sheet_name = '希音退货列表'
285
+ # batch_excel_operations(self.config.excel_return_list, [
286
+ # (sheet_name, 'write', excel_data, ['W', 'Z']),
287
+ # (sheet_name, 'format', self.format_return_list)
288
+ # ])
289
289
 
290
290
  excel_data = [header]
291
291
  cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
@@ -381,7 +381,8 @@ class SheinExcel:
381
381
 
382
382
  batch_excel_operations(self.config.excel_return_list, [
383
383
  (sheet_name, 'write', excel_data, ['W', 'Z']),
384
- (sheet_name, 'format', self.format_return_list)
384
+ (sheet_name, 'format', self.format_return_list),
385
+ ('Sheet1', 'delete')
385
386
  ])
386
387
 
387
388
  def format_return_list(self, sheet):
qrpa/shein_lib.py CHANGED
@@ -48,7 +48,7 @@ class SheinLib:
48
48
 
49
49
  if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
50
50
  if 'https://sso.geiwohuo.com/#/home' not in web_page.url:
51
- log("鉴权确定按钮可见 点击'确定'按钮", self.store_username, self.store_name)
51
+ log("鉴权确定按钮可见 点击'确定'按钮", web_page.title(), web_page.url, self.store_username, self.store_name)
52
52
  web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
53
53
  web_page.wait_for_load_state("load")
54
54
  web_page.wait_for_timeout(1000)
@@ -91,6 +91,10 @@ class SheinLib:
91
91
  return
92
92
 
93
93
  log('商家后台不可见', web_page.title(), web_page.url, self.store_username, self.store_name)
94
+ if 'https://sso.geiwohuo.com/#/home' in web_page.url:
95
+ web_page.wait_for_timeout(3000)
96
+ web_page.reload()
97
+ web_page.wait_for_load_state("load")
94
98
 
95
99
  while r'=/CN' in web_page.url:
96
100
  web_page.goto('https://sso.geiwohuo.com')
@@ -105,7 +109,6 @@ class SheinLib:
105
109
  log('再延时5秒', self.store_username, self.store_name)
106
110
  web_page.wait_for_timeout(5000)
107
111
 
108
- web_page.wait_for_load_state("load")
109
112
  web_page.wait_for_timeout(1000)
110
113
 
111
114
  if 'https://sso.geiwohuo.com/#/home' in web_page.url:
@@ -231,10 +234,11 @@ class SheinLib:
231
234
 
232
235
  url = f"https://sso.geiwohuo.com/pfmp/returnOrder/page"
233
236
  payload = {
234
- "addTimeStart": f"{start_date} 00:00:00",
235
- "addTimeEnd" : f"{end_date} 23:59:59",
236
- "page" : page_num,
237
- "perPage" : page_size
237
+ "returnOrderType": 1, # 只查询退货
238
+ "addTimeStart" : f"{start_date} 00:00:00",
239
+ "addTimeEnd" : f"{end_date} 23:59:59",
240
+ "page" : page_num,
241
+ "perPage" : page_size
238
242
  }
239
243
  response_text = fetch(self.web_page, url, payload)
240
244
  error_code = response_text.get('code')
@@ -256,30 +260,31 @@ class SheinLib:
256
260
  today_list_item = []
257
261
  # 过滤 退货出库时间 是昨天的
258
262
  for item in list_item:
259
- has_valid_package = item.get('hasPackage') == 1
260
- is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
261
263
  returnOrderId = item['id']
262
- item['return_box_detail'] = []
263
- item['qc_report_url'] = ''
264
- item['report_url'] = ''
265
264
  item['store_username'] = self.store_username
266
265
  item['store_name'] = self.store_name
267
266
  item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
268
- if has_valid_package:
269
- return_box_detail = self.get_return_order_box_detail(returnOrderId)
270
- if len(return_box_detail) > 0:
271
267
 
272
- if int(item['returnScrapType']) == 1:
273
- purchaseCode = item['sellerOrderNo']
274
- delivery_code = item['sellerDeliveryNo']
275
- item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
268
+ item['qc_report_url'] = ''
269
+ if int(item['returnScrapType']) == 1:
270
+ purchaseCode = item['sellerOrderNo']
271
+ delivery_code = item['sellerDeliveryNo']
272
+ item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
273
+
274
+ item['report_url'] = ''
275
+ if int(item['returnScrapType']) == 2:
276
+ item['report_url'] = self.get_inspect_report_url(returnOrderId)
276
277
 
277
- if int(item['returnScrapType']) == 2:
278
- item['report_url'] = self.get_inspect_report_url(returnOrderId)
278
+ item['return_box_detail'] = []
279
279
 
280
+ all_list_item.append(item)
281
+ has_valid_package = item.get('hasPackage') == 1
282
+ if has_valid_package:
283
+ return_box_detail = self.get_return_order_box_detail(returnOrderId)
284
+ if len(return_box_detail) > 0:
280
285
  item['return_box_detail'] = return_box_detail
281
286
 
282
- all_list_item.append(item)
287
+ is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
283
288
  if is_valid_yesterday:
284
289
  today_list_item.append(item)
285
290
 
@@ -631,17 +636,102 @@ class SheinLib:
631
636
  log(f'正在获取 {self.store_name} 最近一个月出库金额: {last_item["totalCustomerAmount"]}')
632
637
  return last_item['totalCustomerAmount']
633
638
 
639
+ def get_product_attr(self, spu, attr_name):
640
+ try:
641
+ product_detail = self.get_product_detail(spu)
642
+ product_type_id = product_detail.get('product_type_id')
643
+ category_id = product_detail.get('category_id')
644
+
645
+ if not product_type_id or not category_id:
646
+ return None # 或者根据需要返回一个默认值
647
+
648
+ attribute_template = self.get_attribute_templates(spu, category_id, [product_type_id])
649
+ attr_info = attribute_template.get('attribute_infos', [])
650
+
651
+ # 查找材质属性映射,防止没有匹配项
652
+ attr_item = next((item for item in attr_info if item.get('attribute_name') == attr_name), None)
653
+ if not attr_item:
654
+ return None # 或者返回一个默认值
655
+
656
+ attr_id = attr_item.get('attribute_id')
657
+
658
+ # 拿到产品材质的属性值ID
659
+ product_attribute_list = product_detail.get('product_attribute_list', [])
660
+ attribute_value_id = next((item['attribute_value_id'] for item in product_attribute_list if item.get('attribute_id') == attr_id), None)
661
+ if not attribute_value_id:
662
+ return None # 或者返回一个默认值
663
+
664
+ # 获取属性值名称
665
+ attr_value = next((item['attribute_value'] for item in attr_item.get('attribute_value_info_list', []) if item.get('attribute_value_id') == attribute_value_id), None)
666
+ return attr_value # 返回找到的属性值
667
+ except Exception as e:
668
+ log(f"Error occurred: {e}")
669
+ send_exception()
670
+ return None # 或者返回一个默认值
671
+
672
+ def get_attribute_templates(self, spu_name, category_id, product_type_id_list):
673
+ log(f'正在获取 {spu_name} 商品属性模板')
674
+
675
+ if not isinstance(product_type_id_list, list):
676
+ raise '参数错误: product_type_id_list 需要是列表'
677
+
678
+ cache_file = f'{self.config.auto_dir}/shein/attribute/attribute_template_{spu_name}.json'
679
+ attr_list = read_dict_from_file(cache_file, 3600 * 24 * 7)
680
+ if len(attr_list) > 0:
681
+ return attr_list
682
+
683
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/basic/query_attribute_templates"
684
+ payload = {
685
+ "category_id" : category_id,
686
+ "for_update" : True,
687
+ "product_type_id_list": product_type_id_list,
688
+ "spu_name" : spu_name
689
+ }
690
+ response_text = fetch(self.web_page, url, payload)
691
+ error_code = response_text.get('code')
692
+ if str(error_code) != '0':
693
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
694
+ info = response_text.get('info')
695
+
696
+ data = info.get('data')[0]
697
+ write_dict_to_file(cache_file, data)
698
+ return data
699
+
700
+ def get_product_detail(self, spu_name):
701
+ cache_file = f'{self.config.auto_dir}/shein/product_detail/product_detail_{spu_name}.json'
702
+ info = read_dict_from_file(cache_file, 3600 * 24 * 7)
703
+ if len(info) > 0:
704
+ return info
705
+
706
+ log(f'正在获取 {spu_name} 商品详情')
707
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/get_product_detail"
708
+ payload = {
709
+ "spu_name": spu_name
710
+ }
711
+ response_text = fetch(self.web_page, url, payload)
712
+ error_code = response_text.get('code')
713
+ if str(error_code) != '0':
714
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
715
+ info = response_text.get('info')
716
+ write_dict_to_file(cache_file, info)
717
+ return info
718
+
634
719
  # 存储商品库
635
720
  def store_product_info(self):
636
721
  # todo 商品详情 属性 规格 图片 重量 与 尺寸
637
722
  skc_list = self.get_bak_base_info()
638
723
  cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
639
724
  dict_sku = read_dict_from_file(cache_file)
725
+ dict_product_detail = []
640
726
  for skc_item in skc_list:
641
727
  skc_item['store_username'] = self.store_username
642
728
  skc_item['store_name'] = self.store_name
643
729
  skc_item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
644
730
  spu = skc_item['spu']
731
+ if spu not in dict_product_detail:
732
+ dict_product_detail.append(spu)
733
+ material = self.get_product_attr(spu, '材质')
734
+ log(material) # 这一步是为了获取 spu 详情和属性
645
735
 
646
736
  # 倒序遍历 skuList,安全删除
647
737
  for i in range(len(skc_item['skuList']) - 1, -1, -1):
@@ -657,6 +747,12 @@ class SheinLib:
657
747
  cache_file = f'{self.config.auto_dir}/shein/product/skc_list_{self.store_username}.json'
658
748
  write_dict_to_file_ex(cache_file, {self.store_username: skc_list}, [self.store_username])
659
749
 
750
+ skc_file = f'{self.config.auto_dir}/shein/product/skc_list_file.json'
751
+ write_dict_to_file_ex(skc_file, {self.store_username: cache_file}, [self.store_username])
752
+
753
+ detail_file = f'{self.config.auto_dir}/shein/product/product_detail_file.json'
754
+ write_dict_to_file_ex(detail_file, {self.store_username: dict_product_detail}, [self.store_username])
755
+
660
756
  # 获取备货信息列表
661
757
  def get_bak_base_info(self):
662
758
  log(f'获取备货信息列表 {self.store_name} {self.store_username}')
qrpa/shein_mysql.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import json
2
2
 
3
3
  from .mysql_module.shein_return_order_model import SheinReturnOrderManager
4
+ from .mysql_module.shein_product_model import SheinProductManager
4
5
  from .fun_base import log
5
6
 
6
7
  class SheinMysql:
@@ -17,4 +18,37 @@ class SheinMysql:
17
18
  with open(json_file, 'r', encoding='utf-8') as f:
18
19
  dict = json.load(f)
19
20
  for store_username, data_list in dict.items():
20
- manager.upsert_return_order_data(store_username, data_list)
21
+ manager.upsert_return_order_data(store_username, data_list)
22
+
23
+ def upsert_shein_product(self, json_file):
24
+ log(f'当前使用的数据库: {self.config.db.database_url}')
25
+ # 创建管理器实例
26
+ manager = SheinProductManager(self.config.db.database_url)
27
+ # 创建数据表
28
+ manager.create_tables()
29
+ with open(json_file, 'r', encoding='utf-8') as f:
30
+ file_list = json.load(f)
31
+ for store_username, store_skc_list_file in file_list.items():
32
+ with open(store_skc_list_file, 'r', encoding='utf-8') as f:
33
+ dict_store_skc_list = json.load(f)
34
+ for store_username, data_list in dict_store_skc_list.items():
35
+ manager.upsert_product_data(data_list)
36
+
37
+ def upsert_shein_product_info(self, json_file):
38
+ log(f'当前使用的数据库: {self.config.db.database_url}')
39
+ # 创建管理器实例
40
+ manager = SheinProductManager(self.config.db.database_url)
41
+ # 创建数据表
42
+ manager.create_tables()
43
+ with open(json_file, 'r', encoding='utf-8') as f:
44
+ file_list = json.load(f)
45
+ for store_username, store_spu_list in file_list.items():
46
+ for spu in store_spu_list:
47
+ product_detail_file = f'{self.config.auto_dir}/shein/product_detail/product_detail_{spu}.json'
48
+ attribute_file = f'{self.config.auto_dir}/shein/attribute/attribute_template_{spu}.json'
49
+ with open(product_detail_file, 'r', encoding='utf-8') as f:
50
+ data_list = json.load(f)
51
+ manager.upsert_product_detail(spu, 'product_detail', data_list)
52
+ with open(attribute_file, 'r', encoding='utf-8') as f:
53
+ data_list = json.load(f)
54
+ manager.upsert_product_detail(spu, 'attribute_template', data_list)
qrpa/shein_ziniao.py CHANGED
@@ -259,6 +259,7 @@ class ZiniaoBrowser:
259
259
  page = browser_context.pages[0]
260
260
  page.goto(launcher_page)
261
261
  page.wait_for_load_state('load')
262
+ page.wait_for_timeout(6000)
262
263
 
263
264
  run_func(page, store_username, store_name, task_key)
264
265
 
@@ -278,15 +279,14 @@ class ZiniaoTaskManager:
278
279
  is_skip_store: Optional[Callable] = None
279
280
  ):
280
281
  """运行单个店铺的任务"""
282
+ store_id = browser_info.get('browserOauth')
283
+ store_name = browser_info.get("browserName")
284
+ store_username = browser_info.get("store_username")
285
+
281
286
  retry_count = 0
282
287
  while True:
283
288
  try:
284
289
  retry_count += 1
285
-
286
- store_id = browser_info.get('browserOauth')
287
- store_name = browser_info.get("browserName")
288
- store_username = browser_info.get("store_username")
289
-
290
290
  # 记录店铺账号与店铺别名对应关系
291
291
  cache_file = f'{self.config.auto_dir}/shein_store_alias.json'
292
292
  write_dict_to_file_ex(cache_file, {store_username: store_name}, [store_username])
@@ -349,7 +349,7 @@ class ZiniaoTaskManager:
349
349
  self.browser.close_store(store_id)
350
350
  break
351
351
  except:
352
- send_exception(f'第{retry_count}次运行失败,准备重新打开店铺')
352
+ send_exception(f'第{retry_count}次运行失败,准备重新打开店铺: {store_username},{store_name},{store_id}')
353
353
  if retry_count > 5:
354
354
  break
355
355
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.65
3
+ Version: 1.0.68
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -10,11 +10,11 @@ qrpa/fun_file.py,sha256=yzjDV16WL5vRys7J4uQcNzIFkX4D5MAlSCwxcD-mwQo,11966
10
10
  qrpa/fun_web.py,sha256=F6cxwwOxB8twg2XK73oAvKla0FqpwDYJiM37CWmFwXo,6322
11
11
  qrpa/fun_win.py,sha256=-LnTeocdTt72NVH6VgLdpAT9_C5oV9okeudXG6CftMA,8034
12
12
  qrpa/shein_daily_report_model.py,sha256=H8oZmIN5Pyqe306W1_xuz87lOqLQ_LI5RjXbaxDkIzE,12589
13
- qrpa/shein_excel.py,sha256=vtmeEF_LbH6cVyitDDCNjuT76Nuov6Zvhsf-gIMd0H0,124780
14
- qrpa/shein_lib.py,sha256=jwJshOIiRtCMQWRwi0QwYIOCyLLE-AYnWPTDUSNgTu8,122127
15
- qrpa/shein_mysql.py,sha256=MGPJsz13evsLygZWhmitpPzUswiTKZ91VFvr8KA0M40,750
13
+ qrpa/shein_excel.py,sha256=1F9QMMsaihDfwr0ajSSQdXksBCjuKSPwMynfP7Jlkso,124825
14
+ qrpa/shein_lib.py,sha256=fTrSRnbAb1X9BUr6_ZrBjWDHbwbRz1wIe5kmGHs2r60,126731
15
+ qrpa/shein_mysql.py,sha256=SrgBYPNWaKHIpD5Q7hqz3GWvytXO8u9SPvKxJvLwYsI,2720
16
16
  qrpa/shein_sqlite.py,sha256=ZQwD0Gz81q9WY7tY2HMEYvSF9r3N_G_Aur3bYfST9WY,5707
17
- qrpa/shein_ziniao.py,sha256=kAOirjawPf-VxBxHFNpdDHCN2zL-O1U-2xybpmt34Xs,18692
17
+ qrpa/shein_ziniao.py,sha256=Xc4pgSa7PxgmR24rClgz_HcTRb2fdbXp6slzfpCOThI,18745
18
18
  qrpa/temu_chrome.py,sha256=CbtFy1QPan9xJdJcNZj-EsVGhUvv3ZTEPVDEA4-im40,2803
19
19
  qrpa/temu_excel.py,sha256=2hGw76YWzkTZGyFCuuUAab4oHptYX9a6U6yjpNsL7FE,6990
20
20
  qrpa/temu_lib.py,sha256=hYB59zsLS3m3NTic_duTwPMOTSxlHyQVa8OhHnHm-1g,7199
@@ -22,8 +22,9 @@ qrpa/time_utils.py,sha256=ef0hhbN_6b-gcnz5ETIVOoxemIMvcxGVGGIRnHnGaBo,29564
22
22
  qrpa/time_utils_example.py,sha256=shHOXKKF3QSzb0SHsNc34M61wEkkLuM30U9X1THKNS8,8053
23
23
  qrpa/wxwork.py,sha256=gIytG19DZ5g7Tsl0-W3EbjfSnpIqZw-ua24gcB78YEg,11264
24
24
  qrpa/mysql_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ qrpa/mysql_module/shein_product_model.py,sha256=qViI_Ik3SkXXxqJ1nXjimvfB_a5uiwW9RXL0fOreBao,18880
25
26
  qrpa/mysql_module/shein_return_order_model.py,sha256=Zt-bGOH_kCDbakW7uaTmqqo_qTT8v424yidcYSfWvWM,26562
26
- qrpa-1.0.65.dist-info/METADATA,sha256=YxYB_bf0U7NZAJ5t0Xf6P_L4gQuQZy2U5UhzLecpbEA,231
27
- qrpa-1.0.65.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- qrpa-1.0.65.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
29
- qrpa-1.0.65.dist-info/RECORD,,
27
+ qrpa-1.0.68.dist-info/METADATA,sha256=g0-y3VWC4m7Jfhzv6ycjS7AQ3G5c6oHcE5l6X5j4x50,231
28
+ qrpa-1.0.68.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ qrpa-1.0.68.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
30
+ qrpa-1.0.68.dist-info/RECORD,,
File without changes