qrpa 1.0.65__tar.gz → 1.0.67__tar.gz

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.

Files changed (38) hide show
  1. {qrpa-1.0.65 → qrpa-1.0.67}/PKG-INFO +1 -1
  2. {qrpa-1.0.65 → qrpa-1.0.67}/pyproject.toml +1 -1
  3. qrpa-1.0.67/qrpa/mysql_module/shein_product_model.py +476 -0
  4. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/shein_lib.py +96 -2
  5. qrpa-1.0.67/qrpa/shein_mysql.py +54 -0
  6. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/shein_ziniao.py +6 -6
  7. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa.egg-info/PKG-INFO +1 -1
  8. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa.egg-info/SOURCES.txt +1 -0
  9. qrpa-1.0.65/qrpa/shein_mysql.py +0 -20
  10. {qrpa-1.0.65 → qrpa-1.0.67}/README.md +0 -0
  11. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/RateLimitedSender.py +0 -0
  12. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/__init__.py +0 -0
  13. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/db_migrator.py +0 -0
  14. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/feishu_bot_app.py +0 -0
  15. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/feishu_client.py +0 -0
  16. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/feishu_logic.py +0 -0
  17. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/fun_base.py +0 -0
  18. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/fun_excel.py +0 -0
  19. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/fun_file.py +0 -0
  20. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/fun_web.py +0 -0
  21. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/fun_win.py +0 -0
  22. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/mysql_module/__init__.py +0 -0
  23. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/mysql_module/shein_return_order_model.py +0 -0
  24. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/shein_daily_report_model.py +0 -0
  25. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/shein_excel.py +0 -0
  26. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/shein_sqlite.py +0 -0
  27. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/temu_chrome.py +0 -0
  28. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/temu_excel.py +0 -0
  29. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/temu_lib.py +0 -0
  30. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/time_utils.py +0 -0
  31. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/time_utils_example.py +0 -0
  32. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa/wxwork.py +0 -0
  33. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa.egg-info/dependency_links.txt +0 -0
  34. {qrpa-1.0.65 → qrpa-1.0.67}/qrpa.egg-info/top_level.txt +0 -0
  35. {qrpa-1.0.65 → qrpa-1.0.67}/setup.cfg +0 -0
  36. {qrpa-1.0.65 → qrpa-1.0.67}/setup.py +0 -0
  37. {qrpa-1.0.65 → qrpa-1.0.67}/tests/test_db_migrator.py +0 -0
  38. {qrpa-1.0.65 → qrpa-1.0.67}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.65
3
+ Version: 1.0.67
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qrpa"
7
- version = "1.0.65"
7
+ version = "1.0.67"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -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()
@@ -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:
@@ -631,17 +634,102 @@ class SheinLib:
631
634
  log(f'正在获取 {self.store_name} 最近一个月出库金额: {last_item["totalCustomerAmount"]}')
632
635
  return last_item['totalCustomerAmount']
633
636
 
637
+ def get_product_attr(self, spu, attr_name):
638
+ try:
639
+ product_detail = self.get_product_detail(spu)
640
+ product_type_id = product_detail.get('product_type_id')
641
+ category_id = product_detail.get('category_id')
642
+
643
+ if not product_type_id or not category_id:
644
+ return None # 或者根据需要返回一个默认值
645
+
646
+ attribute_template = self.get_attribute_templates(spu, category_id, [product_type_id])
647
+ attr_info = attribute_template.get('attribute_infos', [])
648
+
649
+ # 查找材质属性映射,防止没有匹配项
650
+ attr_item = next((item for item in attr_info if item.get('attribute_name') == attr_name), None)
651
+ if not attr_item:
652
+ return None # 或者返回一个默认值
653
+
654
+ attr_id = attr_item.get('attribute_id')
655
+
656
+ # 拿到产品材质的属性值ID
657
+ product_attribute_list = product_detail.get('product_attribute_list', [])
658
+ attribute_value_id = next((item['attribute_value_id'] for item in product_attribute_list if item.get('attribute_id') == attr_id), None)
659
+ if not attribute_value_id:
660
+ return None # 或者返回一个默认值
661
+
662
+ # 获取属性值名称
663
+ 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)
664
+ return attr_value # 返回找到的属性值
665
+ except Exception as e:
666
+ log(f"Error occurred: {e}")
667
+ send_exception()
668
+ return None # 或者返回一个默认值
669
+
670
+ def get_attribute_templates(self, spu_name, category_id, product_type_id_list):
671
+ log(f'正在获取 {spu_name} 商品属性模板')
672
+
673
+ if not isinstance(product_type_id_list, list):
674
+ raise '参数错误: product_type_id_list 需要是列表'
675
+
676
+ cache_file = f'{self.config.auto_dir}/shein/attribute/attribute_template_{spu_name}.json'
677
+ attr_list = read_dict_from_file(cache_file, 3600 * 24 * 7)
678
+ if len(attr_list) > 0:
679
+ return attr_list
680
+
681
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/basic/query_attribute_templates"
682
+ payload = {
683
+ "category_id" : category_id,
684
+ "for_update" : True,
685
+ "product_type_id_list": product_type_id_list,
686
+ "spu_name" : spu_name
687
+ }
688
+ response_text = fetch(self.web_page, url, payload)
689
+ error_code = response_text.get('code')
690
+ if str(error_code) != '0':
691
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
692
+ info = response_text.get('info')
693
+
694
+ data = info.get('data')[0]
695
+ write_dict_to_file(cache_file, data)
696
+ return data
697
+
698
+ def get_product_detail(self, spu_name):
699
+ cache_file = f'{self.config.auto_dir}/shein/product_detail/product_detail_{spu_name}.json'
700
+ info = read_dict_from_file(cache_file, 3600 * 24 * 7)
701
+ if len(info) > 0:
702
+ return info
703
+
704
+ log(f'正在获取 {spu_name} 商品详情')
705
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/get_product_detail"
706
+ payload = {
707
+ "spu_name": spu_name
708
+ }
709
+ response_text = fetch(self.web_page, url, payload)
710
+ error_code = response_text.get('code')
711
+ if str(error_code) != '0':
712
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
713
+ info = response_text.get('info')
714
+ write_dict_to_file(cache_file, info)
715
+ return info
716
+
634
717
  # 存储商品库
635
718
  def store_product_info(self):
636
719
  # todo 商品详情 属性 规格 图片 重量 与 尺寸
637
720
  skc_list = self.get_bak_base_info()
638
721
  cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
639
722
  dict_sku = read_dict_from_file(cache_file)
723
+ dict_product_detail = []
640
724
  for skc_item in skc_list:
641
725
  skc_item['store_username'] = self.store_username
642
726
  skc_item['store_name'] = self.store_name
643
727
  skc_item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
644
728
  spu = skc_item['spu']
729
+ if spu not in dict_product_detail:
730
+ dict_product_detail.append(spu)
731
+ material = self.get_product_attr(spu, '材质')
732
+ log(material) # 这一步是为了获取 spu 详情和属性
645
733
 
646
734
  # 倒序遍历 skuList,安全删除
647
735
  for i in range(len(skc_item['skuList']) - 1, -1, -1):
@@ -657,6 +745,12 @@ class SheinLib:
657
745
  cache_file = f'{self.config.auto_dir}/shein/product/skc_list_{self.store_username}.json'
658
746
  write_dict_to_file_ex(cache_file, {self.store_username: skc_list}, [self.store_username])
659
747
 
748
+ skc_file = f'{self.config.auto_dir}/shein/product/skc_list_file.json'
749
+ write_dict_to_file_ex(skc_file, {self.store_username: cache_file}, [self.store_username])
750
+
751
+ detail_file = f'{self.config.auto_dir}/shein/product/product_detail_file.json'
752
+ write_dict_to_file_ex(detail_file, {self.store_username: dict_product_detail}, [self.store_username])
753
+
660
754
  # 获取备货信息列表
661
755
  def get_bak_base_info(self):
662
756
  log(f'获取备货信息列表 {self.store_name} {self.store_username}')
@@ -0,0 +1,54 @@
1
+ import json
2
+
3
+ from .mysql_module.shein_return_order_model import SheinReturnOrderManager
4
+ from .mysql_module.shein_product_model import SheinProductManager
5
+ from .fun_base import log
6
+
7
+ class SheinMysql:
8
+ def __init__(self, config):
9
+ self.config = config
10
+
11
+ def upsert_shein_return_order(self, json_file):
12
+ log(f'当前使用的数据库: {self.config.db.database_url}')
13
+ # 创建管理器实例
14
+ manager = SheinReturnOrderManager(self.config.db.database_url)
15
+ # 创建数据表
16
+ manager.create_tables()
17
+ # 读取JSON文件
18
+ with open(json_file, 'r', encoding='utf-8') as f:
19
+ dict = json.load(f)
20
+ for store_username, data_list in dict.items():
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)
@@ -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.67
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -29,6 +29,7 @@ qrpa.egg-info/SOURCES.txt
29
29
  qrpa.egg-info/dependency_links.txt
30
30
  qrpa.egg-info/top_level.txt
31
31
  qrpa/mysql_module/__init__.py
32
+ qrpa/mysql_module/shein_product_model.py
32
33
  qrpa/mysql_module/shein_return_order_model.py
33
34
  tests/test_db_migrator.py
34
35
  tests/test_wxwork.py
@@ -1,20 +0,0 @@
1
- import json
2
-
3
- from .mysql_module.shein_return_order_model import SheinReturnOrderManager
4
- from .fun_base import log
5
-
6
- class SheinMysql:
7
- def __init__(self, config):
8
- self.config = config
9
-
10
- def upsert_shein_return_order(self, json_file):
11
- log(f'当前使用的数据库: {self.config.db.database_url}')
12
- # 创建管理器实例
13
- manager = SheinReturnOrderManager(self.config.db.database_url)
14
- # 创建数据表
15
- manager.create_tables()
16
- # 读取JSON文件
17
- with open(json_file, 'r', encoding='utf-8') as f:
18
- dict = json.load(f)
19
- for store_username, data_list in dict.items():
20
- manager.upsert_return_order_data(store_username, data_list)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes