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

qrpa/shein_lib.py CHANGED
@@ -1,1405 +1,1442 @@
1
- from .fun_file import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
2
- from .fun_base import log, send_exception, md5_string, get_safe_value
3
- from .fun_web import fetch
4
- from .time_utils import TimeUtils
5
-
6
- from .shein_sqlite import insert_sales,get_last_week_sales,get_near_week_sales,get_near_month_sales,get_last_month_sales
7
-
8
- import math
9
- import time
10
- import json
11
- from datetime import datetime
12
- from playwright.sync_api import Page
13
-
14
- class SheinLib:
15
-
16
- def __init__(self, config, bridge, web_page: Page, store_username, store_name):
17
- self.config = config
18
- self.bridge = bridge
19
- self.store_username = store_username
20
- self.store_name = store_name
21
- self.web_page = web_page
22
- self.dt = None
23
-
24
- self.deal_auth()
25
-
26
- # 处理鉴权
27
- def deal_auth(self):
28
- web_page = self.web_page
29
- while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
30
- if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
31
- web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
32
- log("鉴权确定按钮可见 点击'确定'按钮")
33
- web_page.wait_for_load_state("load")
34
- web_page.wait_for_timeout(1000)
35
- # time.sleep(1)
36
- if web_page.locator('//input[@name="username"]').is_visible():
37
- log("用户名输入框可见 等待5秒点击'登录'按钮")
38
- web_page.wait_for_timeout(5000)
39
- log('点击"登录"')
40
- web_page.locator('//button[contains(@class,"login_btn")]').click()
41
- web_page.wait_for_load_state("load")
42
- log('再延时5秒')
43
- web_page.wait_for_timeout(5000)
44
- if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
45
- log('商品管理菜单可见 退出鉴权处理')
46
- return
47
- log('商家后台不可见', web_page.title(), web_page.url)
48
- web_page.wait_for_load_state("load")
49
- # time.sleep(1)
50
- web_page.wait_for_timeout(1000)
51
- if 'SHEIN全球商家中心' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
52
- log('SHEIN全球商家中心 中断循环')
53
- break
54
- if '后台首页' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
55
- log('后台首页 中断循环')
56
- break
57
- if '商家后台' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
58
- log('后台首页 中断循环')
59
- break
60
- if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
61
- web_page.goto('https://sso.geiwohuo.com/#/home')
62
- web_page.wait_for_load_state("load")
63
- web_page.wait_for_timeout(3000)
64
- # time.sleep(3)
65
- if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
66
- log('检测到鉴权 刷新页面')
67
- web_page.reload()
68
- web_page.wait_for_load_state('load')
69
- # time.sleep(3)
70
- web_page.wait_for_timeout(3000)
71
- web_page.reload()
72
- # time.sleep(3)
73
- web_page.wait_for_timeout(3000)
74
- if web_page.title() == 'SHEIN':
75
- web_page.goto('https://sso.geiwohuo.com/#/home')
76
- web_page.wait_for_load_state("load")
77
- # time.sleep(3)
78
- web_page.wait_for_timeout(3000)
79
-
80
- # web_page.goto('https://sso.geiwohuo.com')
81
- log('鉴权处理结束')
82
-
83
- def get_delivery_order_list(self, orderType=2):
84
- page_num = 1
85
- page_size = 200
86
-
87
- url = f"https://sso.geiwohuo.com/pfmp/order/list"
88
- payload = {}
89
- if orderType == 1:
90
- payload = {
91
- "orderType": orderType,
92
- "page" : page_num,
93
- "perPage" : page_size,
94
- "status" : [2],
95
- }
96
- elif orderType == 2:
97
- payload = {
98
- "orderType" : orderType,
99
- "page" : page_num,
100
- "perPage" : page_size,
101
- "status" : [2],
102
- "isJitOrder": 2
103
- }
104
- response_text = fetch(self.web_page, url, payload)
105
- error_code = response_text.get('code')
106
- if str(error_code) != '0':
107
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
108
-
109
- spu_list = response_text['info']['data']
110
- total = response_text['info']['meta']['count']
111
- totalPage = math.ceil(total / page_size)
112
-
113
- skc_list = [item['goods']['skcName'] for item in spu_list]
114
- self.get_activity_label(skc_list)
115
-
116
- for page in range(2, totalPage + 1):
117
- log(f'获取订单列表 第{page}/{totalPage}页')
118
- page_num = page
119
- if orderType == 1:
120
- payload = {
121
- "orderType": orderType,
122
- "page" : page_num,
123
- "perPage" : page_size,
124
- "status" : [2],
125
- }
126
- elif orderType == 2:
127
- payload = {
128
- "orderType" : orderType,
129
- "page" : page_num,
130
- "perPage" : page_size,
131
- "status" : [2],
132
- "isJitOrder": 2
133
- }
134
- response_text = fetch(self.web_page, url, payload)
135
- spu_list_new = response_text['info']['data']
136
- skc_list = [item['goods']['skcName'] for item in spu_list_new]
137
- self.get_activity_label(skc_list)
138
- spu_list += spu_list_new
139
- time.sleep(0.3)
140
-
141
- if len(spu_list) == 0:
142
- log(f'无{["", "急采", "备货"][orderType]}发货单')
143
- return None
144
-
145
- write_to_excel = [
146
- # 0 1 2 3 4 5 6 7
147
- ['店铺名称', 'SKC图片', 'SKU图片', '商品信息', '下单/需求数量', '库存(模式/本地/在途/希音)', '成本价', '核价',
148
- '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '最晚预约上门取件', '要求实际完成取件',
149
- 'SKC', 'SKU']
150
- ]
151
- cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
152
- DictSkcShelf = read_dict_from_file(cache_file2)
153
- cache_file3 = f'{self.config.auto_dir}/shein/dict/skc_product_{self.store_username}.json'
154
- DictSkcProduct = read_dict_from_file(cache_file3)
155
- cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
156
- dictActivityPrice = read_dict_from_file(cache_file)
157
- cache_file4 = f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json'
158
- DictSkuInfo = read_dict_from_file(cache_file4)
159
- for spu_item in spu_list:
160
- skc = spu_item['goods']['skcName']
161
- skcCode = spu_item['goods']['supplierCode']
162
- skc_img = str(spu_item['goods']['imgPath'])
163
- orderNum = spu_item['sellerOrderNo']
164
- orderTime = spu_item['allocateTime']
165
- requestTakeParcelTime = spu_item['requestTakeParcelTime']
166
- suggestedReserveTime = spu_item['suggestedReserveTime']
167
- good_level = spu_item['goods']['goodsLevelName']
168
-
169
- self.get_skc_week_actual_sales(skc)
170
-
171
- spu = DictSkcProduct[skc]['spu_name']
172
- log('spu', spu)
173
- for sku_item in spu_item['detail']:
174
- needQuantity = sku_item['needQuantity']
175
- orderQuantity = sku_item['orderQuantity']
176
- # sku_img = sku_item['skuThumb']
177
- skuCode = sku_item['supplierSku']
178
- stock = self.bridge.get_sku_stock(skuCode, 'mb')
179
- cost_price = self.bridge.get_sku_cost(skuCode, 'mb')
180
- suffixZh = sku_item['suffixZh']
181
- sku = sku_item['skuCode']
182
- supplyPrice = sku_item['supplyPrice']
183
- sku_img = self.bridge.get_sku_img(skuCode, 'mb')
184
- sale_model = DictSkuInfo[skuCode][0]
185
- shein_stock = DictSkuInfo[skuCode][1]
186
- shelf_days = DictSkuInfo[skuCode][2]
187
- real_transit = DictSkuInfo[skuCode][3]
188
- stock_str = f'{sale_model}\n{stock}/{real_transit}/{shein_stock}'
189
-
190
- item = []
191
- item.append(f'{self.store_name}\n{good_level}')
192
- item.append(skc_img)
193
- item.append(sku_img)
194
- if cost_price == '-':
195
- profit = '-'
196
- else:
197
- profit = f'{float(supplyPrice) - float(cost_price):.2f}'
198
- # item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n下单/需求数量: {orderQuantity}/{needQuantity}\n库存模式/本地/在途/希音: {sale_model}/{stock}/{real_transit}/{shein_stock}\n')
199
- item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}')
200
- item.append(f'[{orderQuantity}/{needQuantity}]')
201
- item.append(stock_str)
202
- item.append(cost_price)
203
- item.append(supplyPrice)
204
- sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
205
- item.append("\n".join(sale_num_list))
206
- item.append("\n".join(sale_data_list))
207
- item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
208
- item.append(suggestedReserveTime)
209
- item.append(requestTakeParcelTime)
210
- item.append(skc)
211
- item.append(sku)
212
- write_to_excel.append(item)
213
-
214
- cache_file = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_{orderType}_{TimeUtils.get_period()}.json'
215
- write_dict_to_file_ex(cache_file, {self.store_username: write_to_excel}, {self.store_username})
216
-
217
- return write_to_excel
218
-
219
- # 获取商品包含sku销量的列表
220
- def get_dict_sku_stock_detail(self):
221
- log(f'获取备货信息商品列表 做成字典')
222
- url = "https://sso.geiwohuo.com/idms/goods-skc/list"
223
- pageNumber = 1
224
- pageSize = 100
225
- dictPayload = {
226
- "pageNumber" : pageNumber,
227
- "pageSize" : pageSize,
228
- "supplierCodes" : "",
229
- "skcs" : "",
230
- "spu" : "",
231
- "c7dSaleCntBegin" : "",
232
- "c7dSaleCntEnd" : "",
233
- "goodsLevelIdList" : [],
234
- "supplyStatus" : "",
235
- "shelfStatus" : "",
236
- "categoryIdList" : [],
237
- "skcStockBegin" : "",
238
- "skcStockEnd" : "",
239
- "skuStockBegin" : "",
240
- "skuStockEnd" : "",
241
- "skcSaleDaysBegin" : "",
242
- "skcSaleDaysEnd" : "",
243
- "skuSaleDaysBegin" : "",
244
- "skuSaleDaysEnd" : "",
245
- "planUrgentCountBegin" : "",
246
- "planUrgentCountEnd" : "",
247
- "skcAvailableOrderBegin": "",
248
- "skcAvailableOrderEnd" : "",
249
- "skuAvailableOrderBegin": "",
250
- "skuAvailableOrderEnd" : "",
251
- "shelfDateBegin" : "",
252
- "shelfDateEnd" : "",
253
- "stockWarnStatusList" : [],
254
- "labelFakeIdList" : [],
255
- "sheinSaleByInventory" : "",
256
- "tspIdList" : [],
257
- "adviceStatus" : [],
258
- "sortBy7dSaleCnt" : 2
259
- }
260
- payload = dictPayload
261
- response_text = fetch(self.web_page, url, payload)
262
- error_code = response_text.get('code')
263
- if str(error_code) != '0':
264
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
265
-
266
- spu_list = response_text['info']['list']
267
-
268
- total = response_text['info']['count']
269
- totalPage = math.ceil(total / pageSize)
270
- for page in range(2, totalPage + 1):
271
- log(f'获取备货信息商品列表 第{page}/{totalPage}页')
272
- dictPayload['pageNumber'] = page
273
- payload = dictPayload
274
- response_text = fetch(self.web_page, url, payload)
275
- spu_list_new = response_text['info']['list']
276
- spu_list += spu_list_new
277
- time.sleep(0.3)
278
-
279
- DictSkuInfo = {}
280
- for spu_info in spu_list:
281
- sale_model = spu_info.get('saleModel', {}).get('name') if spu_info.get('saleModel') else '-'
282
- shelfDays = spu_info['shelfDays']
283
- for sku_info in spu_info['skuList']:
284
- attr = sku_info['attr']
285
- if attr == '合计':
286
- continue
287
- skuExtCode = str(sku_info['supplierSku'])
288
- shein_stock = sku_info['stock']
289
-
290
- transit = sku_info['transit'] # 在途
291
- real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
292
-
293
- DictSkuInfo[skuExtCode] = [sale_model, shein_stock, shelfDays, real_transit]
294
-
295
- write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json', DictSkuInfo)
296
-
297
- return DictSkuInfo
298
-
299
- def get_shop_notify_num(self):
300
- log(f'正在获取 {self.store_name} 通知数据')
301
- url = "https://sso.geiwohuo.com/sso/homePage/v4/detail"
302
- payload = {
303
- "metaIndexIds": [
304
- 246, # 急采-待发货
305
- 245 # 备货-待发货
306
- ],
307
- "templateType": 0
308
- }
309
- response_text = fetch(self.web_page, url, payload)
310
- error_code = response_text.get('code')
311
- if str(error_code) != '0':
312
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
313
- info = response_text.get('info')
314
-
315
- cache_file = f'{self.config.auto_dir}/shein/notify/{self.store_name}_{TimeUtils.get_current_datetime()}.json'
316
- write_dict_to_file(cache_file, info)
317
-
318
- num245 = 0
319
- num246 = 0
320
- for item in info['list']:
321
- if item['metaIndexId'] == 245:
322
- num245 = item['count']
323
- if item['metaIndexId'] == 246:
324
- num246 = item['count']
325
-
326
- NotifyItem = [self.store_name, num246, num245]
327
-
328
- cache_file = f'{self.config.auto_dir}/shein/cache/jit_notify_{TimeUtils.today_date()}.json'
329
- write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, {self.store_username})
330
-
331
- return info
332
-
333
- def get_activity_list(self):
334
- url = "https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_activity_list?page_num=1&page_size=100"
335
- payload = {}
336
- response_text = fetch(self.web_page, url, payload)
337
- error_code = response_text.get('code')
338
- if str(error_code) != '0':
339
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
340
- total = response_text.get('info', {}).get('total_count')
341
- excel_data = [[
342
- '店铺名称', '活动信息', '已报数量', '可报数量'
343
- ]]
344
- if total > 0:
345
- for item in response_text.get('info', {}).get('activity_detail_list'):
346
- activity_tag = item.get('text_tag_content')
347
- activity_name = item['activity_name']
348
- start_time = item['activity_start_zone_time']
349
- end_time = item['activity_end_zone_time']
350
- start_time2 = item['start_zone_time']
351
- end_time2 = item['end_zone_time']
352
- allow_goods_num = item.get('allow_goods_num')
353
- apply_goods_num = item.get('apply_goods_num')
354
- row_item = [
355
- self.store_name,
356
- f"活动名称: 【{activity_tag}】{activity_name}\n报名时间: {start_time}~{end_time}\n活动时间: {start_time2}~{end_time2}\n已报数量: {apply_goods_num}/{allow_goods_num}",
357
- apply_goods_num,
358
- allow_goods_num,
359
- ]
360
- excel_data.append(row_item)
361
- cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
362
- write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
363
-
364
- def get_product_list(self):
365
- self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
366
- self.web_page.wait_for_load_state("load")
367
-
368
- cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
369
- DictSpuInfo = read_dict_from_file(cache_file, 3600)
370
- if len(DictSpuInfo) > 0:
371
- return DictSpuInfo
372
-
373
- page_num = 1
374
- page_size = 100
375
-
376
- url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
377
- payload = {
378
- "language" : "zh-cn",
379
- "only_recommend_resell" : False,
380
- "only_spmb_copy_product": False,
381
- "search_abandon_product": False,
382
- "sort_type" : 1
383
- }
384
- response_text = fetch(self.web_page, url, payload)
385
- error_code = response_text.get('code')
386
- if str(error_code) != '0':
387
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
388
-
389
- spu_list = response_text['info']['data']
390
- total = response_text['info']['meta']['count']
391
- totalPage = math.ceil(total / page_size)
392
-
393
- for page_num in range(2, totalPage + 1):
394
- log(f'获取商品列表 第{page_num}/{totalPage}页')
395
- url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
396
- response_text = fetch(self.web_page, url, payload)
397
- spu_list_new = response_text['info']['data']
398
- spu_list += spu_list_new
399
- time.sleep(0.3)
400
-
401
- DictSkcShelf = {}
402
- DictSkcProduct = {}
403
- DictSpuInfo = {}
404
- for spu_item in spu_list:
405
- spu = spu_item['spu_name']
406
- first_shelf_time = spu_item['first_shelf_time']
407
- for skc_item in spu_item['skc_info_list']:
408
- skc_name = skc_item['skc_name']
409
- DictSkcShelf[skc_name] = first_shelf_time
410
- DictSkcProduct[skc_name] = spu_item
411
- DictSpuInfo[spu] = spu_item
412
-
413
- cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
414
- write_dict_to_file(cache_file2, DictSkcShelf)
415
- cache_file3 = f'{self.config.auto_dir}/dict/skc_product_{self.store_username}.json'
416
- write_dict_to_file(cache_file3, DictSkcProduct)
417
-
418
- write_dict_to_file(cache_file, DictSpuInfo)
419
- return DictSpuInfo
420
-
421
- def query_obm_activity_list(self):
422
- page_num = 1
423
- page_size = 100
424
- date_60_days_ago = TimeUtils.get_past_nth_day(59)
425
- cache_file = f'{self.config.auto_dir}/shein/cache/obm_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
426
- list_item = read_dict_from_file(cache_file, 3600 * 8)
427
- if len(list_item) > 0:
428
- return list_item
429
-
430
- url = f"https://sso.geiwohuo.com/mrs-api-prefix/promotion/obm/query_obm_activity_list"
431
- payload = {
432
- "insert_end_time" : f"{TimeUtils.today_date()} 23:59:59",
433
- "insert_start_time": f"{date_60_days_ago} 00:00:00",
434
- "page_num" : page_num,
435
- "page_size" : page_size,
436
- "system" : "mrs",
437
- "time_zone" : "Asia/Shanghai",
438
- # "state": 3, # 活动开启中 不能用这个条件
439
- "type_id" : 31 # 限时折扣
440
- }
441
-
442
- response_text = fetch(self.web_page, url, payload)
443
- error_code = response_text.get('code')
444
- if str(error_code) != '0':
445
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
446
-
447
- list_item = response_text['info']['data']
448
- total = response_text['info']['meta']['count']
449
- totalPage = math.ceil(total / page_size)
450
-
451
- for page in range(2, totalPage + 1):
452
- log(f'获取营销工具列表 第{page}/{totalPage}页')
453
- payload["page_num"] = page
454
- response_text = fetch(self.web_page, url, payload)
455
- list_item += response_text['info']['data']
456
- time.sleep(0.1)
457
-
458
- write_dict_to_file(cache_file, list_item)
459
- return list_item
460
-
461
- def query_goods_detail(self, activity_id):
462
- # web_page.goto(f'https://sso.geiwohuo.com/#/mrs/tools/activity/obm-time-limit-info/{activity_id}')
463
- # web_page.wait_for_load_state('load')
464
- log(f'正在获取 {self.store_name} {activity_id} 营销工具商品详情')
465
-
466
- cache_file = f'{self.config.auto_dir}/shein/cache/query_goods_detail_{activity_id}.json'
467
- list_item = read_dict_from_file(cache_file, 3600 * 8)
468
- if len(list_item) > 0:
469
- return list_item
470
-
471
- page_num = 1
472
- page_size = 100
473
- url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/simple_platform/query_goods_detail"
474
- payload = {
475
- "activity_id": activity_id,
476
- "page_num" : page_num,
477
- "page_size" : page_size
478
- }
479
- response_text = fetch(self.web_page, url, payload)
480
- error_code = response_text.get('code')
481
- if str(error_code) != '0':
482
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
483
- list_item = response_text['info']['data']
484
- total = response_text['info']['meta']['count']
485
- totalPage = math.ceil(total / page_size)
486
-
487
- for page in range(2, totalPage + 1):
488
- log(f'获取营销工具商品列表 第{page}/{totalPage}页')
489
- payload["page_num"] = page
490
- response_text = fetch(self.web_page, url, payload)
491
- list_item += response_text['info']['data']
492
- time.sleep(0.1)
493
-
494
- write_dict_to_file(cache_file, list_item)
495
- return list_item
496
-
497
- def get_partake_activity_goods_list(self):
498
- # self.web_page.goto(f'https://sso.geiwohuo.com/#/mbrs/marketing/list/1')
499
- # self.web_page.wait_for_load_state('load')
500
- log(f'正在获取 {self.store_name} 活动列表')
501
- page_num = 1
502
- page_size = 100
503
- date_60_days_ago = TimeUtils.get_past_nth_day(59)
504
- cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
505
- list_item = read_dict_from_file(cache_file, 3600 * 8)
506
- if len(list_item) > 0:
507
- return list_item
508
-
509
- url = f"https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_partake_activity_goods_list?page_num={page_num}&page_size={page_size}"
510
- payload = {
511
- "goods_audit_status" : 1,
512
- "insert_zone_time_end" : f"{TimeUtils.today_date()} 23:59:59",
513
- "insert_zone_time_start": f"{date_60_days_ago} 00:00:00"
514
- }
515
-
516
- response_text = fetch(self.web_page, url, payload)
517
- error_code = response_text.get('code')
518
- if str(error_code) != '0':
519
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
520
- list_item = response_text['info']['data']
521
- total = response_text['info']['meta']['count']
522
- totalPage = math.ceil(total / page_size)
523
-
524
- for page in range(2, totalPage + 1):
525
- log(f'获取活动列表 第{page}/{totalPage}页')
526
- payload["page_num"] = page
527
- response_text = fetch(self.web_page, url, payload)
528
- list_item += response_text['info']['data']
529
- time.sleep(0.1)
530
-
531
- write_dict_to_file(cache_file, list_item)
532
- return list_item
533
-
534
- def generate_activity_price_dict(self):
535
- cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
536
- dict_activity_price = {}
537
- activity_list = self.query_obm_activity_list()
538
- for activity in activity_list:
539
- activity_id = activity['activity_id']
540
- activity_name = activity['act_name']
541
- sub_type_id = activity['sub_type_id'] # 1.不限量 2.限量
542
- dateBegin = TimeUtils.convert_datetime_to_date(activity['start_time'])
543
- dateEnd = TimeUtils.convert_datetime_to_date(activity['end_time'])
544
- skc_list = self.query_goods_detail(activity_id)
545
- for skc_item in skc_list:
546
- attend_num_sum = skc_item['attend_num_sum']
547
- product_act_price = skc_item['product_act_price'] # 活动价
548
- if sub_type_id == 1:
549
- attend_num_sum = '不限量'
550
- for sku_item in skc_item['sku_info_list']:
551
- sku = sku_item['sku'] # 平台sku
552
- product_act_price = sku_item['product_act_price'] if sku_item[
553
- 'product_act_price'] else product_act_price # 活动价
554
- key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
555
- dict_activity_price[key] = [product_act_price, attend_num_sum]
556
-
557
- platform_activity_list = self.get_partake_activity_goods_list()
558
- for platform_activity in platform_activity_list:
559
- activity_name = platform_activity['activity_name']
560
- text_tag_content = platform_activity['text_tag_content']
561
- attend_num = platform_activity['attend_num']
562
- dateBegin = TimeUtils.convert_timestamp_to_date(platform_activity['start_time'])
563
- dateEnd = TimeUtils.convert_timestamp_to_date(platform_activity['end_time'])
564
- if text_tag_content != '新品':
565
- attend_num = '-'
566
- for sku_item in platform_activity['activity_sku_list']:
567
- sku = sku_item['sku_code']
568
- enroll_price = sku_item['enroll_display_str'][:-3]
569
- key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
570
- dict_activity_price[key] = [enroll_price, attend_num]
571
-
572
- write_dict_to_file(cache_file, dict_activity_price)
573
-
574
- def get_skc_week_actual_sales(self, skc):
575
- first_day, last_day = TimeUtils.get_past_7_days_range()
576
- cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
577
- if datetime.now().hour >= 9:
578
- DictSkuSalesDate = read_dict_from_file(cache_file)
579
- else:
580
- DictSkuSalesDate = read_dict_from_file(cache_file, 1800)
581
- if len(DictSkuSalesDate) > 0:
582
- return DictSkuSalesDate
583
-
584
- url = f"https://sso.geiwohuo.com/idms/sale-trend/detail"
585
- payload = {
586
- "skc" : skc,
587
- "startDate": first_day,
588
- "endDate" : last_day,
589
- "daysToAdd": 0
590
- }
591
- response_text = fetch(self.web_page, url, payload)
592
- error_code = response_text.get('code')
593
- if str(error_code) != '0':
594
- log(response_text)
595
- return {}
596
- list_item = response_text['info']['salesVolumeDateVoList']
597
- for item in list_item:
598
- key = item['date']
599
- DictSkuSalesDate[key] = item['salesVolumeMap']
600
- list_item2 = response_text['info']['actualSalesVolumeMap']
601
- for item in list_item2:
602
- sku = item['skuCode']
603
- if sku is not None:
604
- DictSkuSalesDate[sku] = item['actualSalesVolume']
605
-
606
- write_dict_to_file(cache_file, DictSkuSalesDate)
607
- return DictSkuSalesDate
608
-
609
- def get_preemption_list(self, skc_list):
610
- url = f"https://sso.geiwohuo.com/idms/goods-skc/preemption-num"
611
- payload = skc_list
612
- response_text = fetch(self.web_page, url, payload)
613
- error_code = response_text.get('code')
614
- if str(error_code) != '0':
615
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
616
-
617
- dict = response_text['info']
618
-
619
- cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
620
- dict_preemption_num = read_dict_from_file(cache_file)
621
- dict_preemption_num.update(dict)
622
- write_dict_to_file(cache_file, dict_preemption_num)
623
-
624
- return dict
625
-
626
- def get_activity_label(self, skc_list):
627
- url = f"https://sso.geiwohuo.com/idms/goods-skc/activity-label"
628
- payload = skc_list
629
- response_text = fetch(self.web_page, url, payload)
630
- error_code = response_text.get('code')
631
- if str(error_code) != '0':
632
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
633
- dict = response_text['info']
634
-
635
- cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
636
- dict_label = read_dict_from_file(cache_file)
637
- dict_label.update(dict)
638
- write_dict_to_file(cache_file, dict_label)
639
-
640
- return dict
641
-
642
- def get_sku_price_v2(self, skc_list):
643
- log(f'获取sku价格列表', skc_list)
644
- url = "https://sso.geiwohuo.com/idms/goods-skc/price"
645
- response_text = fetch(self.web_page, url, skc_list)
646
- error_code = response_text.get('code')
647
- if str(error_code) != '0':
648
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
649
- dict = response_text['info']
650
-
651
- cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
652
- dict_sku_price = read_dict_from_file(cache_file)
653
- dict_sku_price.update(dict)
654
- write_dict_to_file(cache_file, dict_sku_price)
655
-
656
- return dict
657
-
658
- def get_stock_advice(self, skc_list):
659
- log(f'获取sku库存建议列表', skc_list)
660
- url = f"https://sso.geiwohuo.com/idms/goods-skc/get-vmi-spot-advice"
661
- payload = skc_list
662
- response_text = fetch(self.web_page, url, payload)
663
- error_code = response_text.get('code')
664
- if str(error_code) != '0':
665
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
666
- dict = response_text['info']
667
-
668
- cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
669
- dict_advice = read_dict_from_file(cache_file)
670
- dict_advice.update(dict)
671
- write_dict_to_file(cache_file, dict_advice)
672
-
673
- return dict
674
-
675
- def get_dt_time(self):
676
- if self.dt is not None:
677
- log(f'字典dt: {self.dt}')
678
- return self.dt
679
- log('获取非实时更新时间')
680
- url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
681
- payload = {
682
- "pageCode": "Index",
683
- "areaCd" : "cn"
684
- }
685
- response_text = fetch(self.web_page, url, payload)
686
- error_code = response_text.get('code')
687
- if str(error_code) != '0':
688
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
689
- self.dt = response_text.get('info').get('dt')
690
- log(f'dt: {self.dt}')
691
- return self.dt
692
-
693
- def get_dict_skc_week_trend(self):
694
- page_num = 1
695
- page_size = 100
696
-
697
- date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
698
- log('-7', date_7_days_ago)
699
- date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
700
- log('-1', date_1_days_ago)
701
-
702
- url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_list"
703
- payload = {
704
- "areaCd" : "cn",
705
- "countrySite": [
706
- "shein-all"
707
- ],
708
- "startDate" : date_7_days_ago,
709
- "endDate" : date_1_days_ago,
710
- "pageNum" : page_num,
711
- "pageSize" : page_size
712
- }
713
- response_text = fetch(self.web_page, url, payload)
714
- error_code = response_text.get('code')
715
- if str(error_code) != '0':
716
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
717
- spu_list = response_text['info']['data']
718
- total = response_text['info']['meta']['count']
719
- totalPage = math.ceil(total / page_size)
720
-
721
- for page in range(2, totalPage + 1):
722
- log(f'获取商品列表 第{page}/{totalPage}页')
723
- page_num = page
724
- payload = {
725
- "areaCd" : "cn",
726
- "countrySite": [
727
- "shein-all"
728
- ],
729
- "startDate" : date_7_days_ago,
730
- "endDate" : date_1_days_ago,
731
- "pageNum" : page_num,
732
- "pageSize" : page_size
733
- }
734
- response_text = fetch(self.web_page, url, payload)
735
- spu_list_new = response_text['info']['data']
736
- spu_list += spu_list_new
737
- time.sleep(0.3)
738
-
739
- DictSkcWeekTrend = {}
740
- for spu_item in spu_list:
741
- skc = str(spu_item['skc'])
742
- DictSkcWeekTrend[skc] = spu_item
743
-
744
- log('len(DictSkcWeekTrend)', len(DictSkcWeekTrend))
745
- write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json', DictSkcWeekTrend)
746
- return DictSkcWeekTrend
747
-
748
- def get_skc_sales(self, skc, start_date, end_date):
749
- url = "https://sso.geiwohuo.com/idms/stockadvice/saleTrendDetail"
750
- payload = {
751
- "skc" : skc,
752
- "startDate": start_date,
753
- "endDate" : end_date
754
- }
755
- response_text = fetch(self.web_page, url, payload)
756
- error_code = response_text.get('code')
757
- error_msg = response_text.get('msg')
758
- if str(error_code) != '0':
759
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
760
-
761
- sales_list = response_text['info']['saleTrendDetailList']
762
- if sales_list:
763
- for skc_list in sales_list:
764
- date = skc_list['date']
765
- if date == '合计':
766
- log('无销量skc: ', skc)
767
- continue
768
- skc_sale = skc_list['skcSale']
769
- skc_order = skc_list['skcOrder']
770
- for sku_list in skc_list['skuSaleTrendDetailList']:
771
- sku = sku_list['skuCode']
772
- attr_name = sku_list['attributeName']
773
- sku_sale = sku_list['skuSale']
774
- sku_order = sku_list['skuOrder']
775
- if sku_sale > 0:
776
- insert_sales(skc, date, skc_sale, skc_order, sku, attr_name, sku_sale, sku_order)
777
- return sales_list
778
-
779
- # 获取商品包含sku销量的列表
780
- def get_product_sku_sales_list(self, source='mb'):
781
- log(f'获取销量列表')
782
- url = "https://sso.geiwohuo.com/idms/goods-skc/list"
783
- pageNumber = 1
784
- pageSize = 100
785
- dictPayload = {
786
- "pageNumber" : pageNumber,
787
- "pageSize" : pageSize,
788
- "supplierCodes" : "",
789
- "skcs" : "",
790
- "spu" : "",
791
- "c7dSaleCntBegin" : "",
792
- "c7dSaleCntEnd" : "",
793
- "goodsLevelIdList" : [],
794
- "supplyStatus" : "",
795
- "shelfStatus" : 1,
796
- "categoryIdList" : [],
797
- "skcStockBegin" : "",
798
- "skcStockEnd" : "",
799
- "skuStockBegin" : "",
800
- "skuStockEnd" : "",
801
- "skcSaleDaysBegin" : "",
802
- "skcSaleDaysEnd" : "",
803
- "skuSaleDaysBegin" : "",
804
- "skuSaleDaysEnd" : "",
805
- "planUrgentCountBegin" : "",
806
- "planUrgentCountEnd" : "",
807
- "skcAvailableOrderBegin": "",
808
- "skcAvailableOrderEnd" : "",
809
- "skuAvailableOrderBegin": "",
810
- "skuAvailableOrderEnd" : "",
811
- "shelfDateBegin" : "",
812
- "shelfDateEnd" : "",
813
- "stockWarnStatusList" : [],
814
- "labelFakeIdList" : [],
815
- "sheinSaleByInventory" : "",
816
- "tspIdList" : [],
817
- "adviceStatus" : [],
818
- "sortBy7dSaleCnt" : 2
819
- }
820
- payload = dictPayload
821
- response_text = fetch(self.web_page, url, payload)
822
- error_code = response_text.get('code')
823
- if str(error_code) != '0':
824
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
825
-
826
- spu_list = response_text['info']['list']
827
-
828
- total = response_text['info']['count']
829
- totalPage = math.ceil(total / pageSize)
830
- for page in range(2, totalPage + 1):
831
- log(f'获取SKU销量列表 第{page}/{totalPage}页')
832
- dictPayload['pageNumber'] = page
833
- payload = dictPayload
834
- response_text = fetch(self.web_page, url, payload)
835
- spu_list_new = response_text['info']['list']
836
- spu_list += spu_list_new
837
- time.sleep(0.3)
838
-
839
- cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
840
- dict_sku_price = read_dict_from_file(cache_file)
841
-
842
- cache_file2 = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json'
843
- DictSkcWeekTrend = read_dict_from_file(cache_file2)
844
-
845
- cache_file3 = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
846
- DictSpuInfo = read_dict_from_file(cache_file3)
847
-
848
- product_sku_list = [
849
- [
850
- '店铺名称', '商品信息', 'SKC图片', 'SKU图片', 'SKU', 'SKU货号', '在售天数', '库存(模式/本地/在途/希音)',
851
- '今天销量', '今日订单数', # 9
852
- '远7天销量', '远7天订单数', '近7天销量', '近7天订单数', '周销增量', '远30天销量', '远30天订单数',
853
- '近30天销量', '近30天订单数', '月销增量', '总销量', # 11
854
- '申报价', '成本价', '毛利润', '毛利率', '近7天利润', '近30天利润', # 6
855
- 'SPU', 'SKC', 'SKC货号', '商品标题', '叶子类目', # 5
856
- 'SKC7天销量', 'SKC7天曝光人数', 'SKC近7天商详访客', 'SKC近7天点击率', 'SKC近7天支付人数',
857
- 'SKC近7天支付率', 'SKC7天评论数'
858
- ]
859
- ]
860
-
861
- date_60_days_ago = TimeUtils.get_past_nth_day(60, None, '%Y-%m-%d')
862
- log('-60', date_60_days_ago)
863
- date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y-%m-%d')
864
- log('-7', date_7_days_ago)
865
- date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y-%m-%d')
866
- log('-1', date_1_days_ago)
867
-
868
- count = 0
869
- for spu_info in spu_list:
870
- count += 1
871
- # if count > 10:
872
- # break
873
- spu = spu_info['spu']
874
- skc = str(spu_info['skc'])
875
- # if not shein_db.exists_sales_1_days_ago(skc):
876
- # log(f'未查到昨天销量: {skc}')
877
- self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
878
- skcCode = spu_info['supplierCode']
879
- product_name = DictSpuInfo[spu]['product_name_en']
880
- category_name = spu_info['categoryName']
881
- shelfDays = spu_info['shelfDays']
882
- shelf_status = DictSpuInfo[spu]['shelf_status']
883
- dictStatus = {
884
- 'WAIT_SHELF': "待上架",
885
- 'ON_SHELF' : "已上架",
886
- 'SOLD_OUT' : "已售罄",
887
- 'OUT_SHELF' : "已下架"
888
- }
889
- status_cn = dictStatus[shelf_status]
890
- good_level = spu_info['goodsLevel']['name']
891
- sale_model = spu_info['saleModel']['name']
892
-
893
- for sku_info in spu_info['skuList']:
894
- sku = sku_info['skuCode']
895
- skuExtCode = str(sku_info['supplierSku'])
896
- if sku == '合计':
897
- continue
898
- sku_item = ["" for _ in range(len(product_sku_list[0]))]
899
- sku_item[0] = f'{self.store_name}\n({status_cn})\n{good_level}\n{date_7_days_ago}\n{date_1_days_ago}' # 店铺名称
900
- product_info = f"{product_name}\n类目: {category_name}\nSKC: {skc}\nSKC货号: {skcCode}\nSPU: {spu}\n在售天数: {shelfDays}"
901
- sku_item[1] = product_info # 商品信息
902
- sku_item[2] = spu_info['picUrl'] # skc图片
903
-
904
- sku_img = self.bridge.get_sku_img(skuExtCode, source)
905
-
906
- sku_item[3] = sku_img # sku图片
907
- sku_item[4] = sku # sku
908
- sku_item[5] = f"{sku_info['supplierSku']}" # sku货号
909
- sku_item[6] = shelfDays # 在售天数
910
-
911
- stock = self.bridge.get_sku_stock(skuExtCode, source)
912
-
913
- shein_stock = sku_info['stock']
914
- transit = sku_info['transit'] # 在途
915
- real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
916
- sku_item[7] = f'{sale_model}\n{stock}/{real_transit}/{shein_stock}' # 本地组合库存
917
-
918
- sku_item[8] = sku_info['totalSaleVolume'] # 今日销量
919
- sku_item[9] = sku_info['orderCnt'] # 今日订单数
920
-
921
- week_sales = get_last_week_sales(sku)
922
- sku_item[10] = week_sales[0] # 远7日销量
923
- sku_item[11] = week_sales[1] # 远7日订单数
924
-
925
- week_sales2 = get_near_week_sales(sku)
926
- sku_item[12] = week_sales2[0] # 近7日销量
927
- sku_item[13] = week_sales2[1] # 近7日订单数
928
- sku_item[14] = week_sales2[1] - week_sales2[0] # 周增销量
929
-
930
- month_sales = get_last_month_sales(sku)
931
- sku_item[15] = month_sales[0] # 远30日销量
932
- sku_item[16] = month_sales[1] # 远30日订单数
933
-
934
- month_sales2 = get_near_month_sales(sku)
935
- sku_item[17] = month_sales2[0] # 近30日销量
936
- sku_item[18] = month_sales2[1] # 近30日订单数
937
- sku_item[19] = month_sales2[1] - month_sales2[0] # 月增销量
938
- sku_item[20] = '-' # 总销量
939
-
940
- sku_item[21] = dict_sku_price[sku] # 申报价
941
-
942
- cost_price = self.bridge.get_sku_cost(skuExtCode, source)
943
-
944
- sku_item[22] = cost_price # 成本价
945
- sku_item[23] = '' # 毛利润
946
- sku_item[24] = '' # 毛利率
947
- sku_item[25] = '' # 近7天利润
948
- sku_item[26] = '' # 近30天利润
949
- sku_item[27] = spu # spu
950
- sku_item[28] = skc # skc
951
- sku_item[29] = spu_info['supplierCode'] # skc货号
952
- sku_item[30] = product_name # 商品标题
953
- sku_item[31] = category_name # 叶子类目
954
-
955
- key = str(skc)
956
- sku_item[32] = DictSkcWeekTrend.get(key, {'saleCnt': 0})['saleCnt'] # 'SKC近7天销量',
957
- sku_item[33] = DictSkcWeekTrend.get(key, {'epsUvIdx': 0})['epsUvIdx'] # 'SKC近7天曝光人数',
958
- sku_item[34] = DictSkcWeekTrend.get(key, {'goodsUvIdx': 0})['goodsUvIdx'] # 'SKC近7天商详访客',
959
- sku_item[35] = DictSkcWeekTrend.get(key, {'epsGdsCtrIdx': 0})['epsGdsCtrIdx'] # 'SKC近7天点击率',
960
- sku_item[36] = DictSkcWeekTrend.get(key, {'payUvIdx': 0})['payUvIdx'] # 'SKC近7天支付人数',
961
- sku_item[37] = DictSkcWeekTrend.get(key, {'gdsPayCtrIdx': 0})['gdsPayCtrIdx'] # 'SKC近7天支付率',
962
- sku_item[38] = DictSkcWeekTrend.get(key, {'totalCommentCnt': 0})['totalCommentCnt'] # '评论数'
963
-
964
- product_sku_list.append(sku_item)
965
-
966
- cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
967
- write_dict_to_file_ex(cache_file, {self.store_name: product_sku_list}, [self.store_name])
968
-
969
- return product_sku_list
970
-
971
- # 获取一个skc一周内的销售趋势(商品明细中的)
972
- def get_dict_skc_week_trend_v2(self, spu, skc):
973
- dt = self.get_dt_time()
974
-
975
- date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
976
- log('-7', date_7_days_ago)
977
- date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
978
- log('-1', date_1_days_ago)
979
-
980
- cache_file = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{skc}_{date_7_days_ago}_{date_1_days_ago}.json'
981
- if datetime.now().hour >= 9:
982
- DictSkc = read_dict_from_file(cache_file)
983
- else:
984
- DictSkc = read_dict_from_file(cache_file, 1800)
985
- if len(DictSkc) > 0:
986
- return DictSkc
987
-
988
- url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_trend"
989
- payload = {
990
- "areaCd" : "cn",
991
- "countrySite": [
992
- "shein-all"
993
- ],
994
- "dt" : dt,
995
- "endDate" : date_1_days_ago,
996
- "spu" : [spu],
997
- "skc" : [skc],
998
- "startDate" : date_7_days_ago,
999
- }
1000
- response_text = fetch(self.web_page, url, payload)
1001
- error_code = response_text.get('code')
1002
- if str(error_code) != '0':
1003
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
1004
-
1005
- data_list = response_text['info']
1006
- DictSkc = {}
1007
- for date_item in data_list:
1008
- dataDate = date_item['dataDate']
1009
- # epsUvIdx = date_item['epsUvIdx']
1010
- # saleCnt = date_item['saleCnt']
1011
- DictSkc[dataDate] = date_item
1012
-
1013
- log('len(DictSkc)', len(DictSkc))
1014
- write_dict_to_file(cache_file, DictSkc)
1015
- return DictSkc
1016
-
1017
- def get_skc_week_sale_list(self, spu, skc, sku):
1018
- dict_skc = self.get_dict_skc_week_trend_v2(spu, skc)
1019
- date_list = TimeUtils.get_past_7_days_list()
1020
- first_day, last_day = TimeUtils.get_past_7_days_range()
1021
- cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
1022
- DictSkuSalesDate = read_dict_from_file(cache_file)
1023
- sales_detail = []
1024
- for date in date_list:
1025
- sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
1026
- sales_num = sales_num if sales_num is not None else 0
1027
-
1028
- saleCnt = get_safe_value(dict_skc.get(date, {}), 'saleCnt', 0)
1029
- epsUvIdx = get_safe_value(dict_skc.get(date, {}), 'epsUvIdx', 0)
1030
-
1031
- sales_detail.append(f'{date}({TimeUtils.get_weekday_name(date)}): {sales_num}/{saleCnt}/{epsUvIdx}')
1032
-
1033
- sales_data = []
1034
- for date in date_list:
1035
- goodsUvIdx = get_safe_value(dict_skc.get(date, {}), 'goodsUvIdx', 0) # 商详访客
1036
- epsGdsCtrIdx = get_safe_value(dict_skc.get(date, {}), 'epsGdsCtrIdx', 0) # 点击率
1037
-
1038
- payUvIdx = get_safe_value(dict_skc.get(date, {}), 'payUvIdx', 0) # 支付人数
1039
- gdsPayCtrIdx = get_safe_value(dict_skc.get(date, {}), 'gdsPayCtrIdx', 0) # 转化率
1040
-
1041
- sales_data.append(f'{date}({TimeUtils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
1042
-
1043
- return sales_detail, sales_data
1044
-
1045
- def get_activity_price(self, activity_dict, sku, activity_name, dateBegin, dateEnd):
1046
- key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
1047
- price_info = activity_dict.get(key, ['-', '-'])
1048
- return f'活动价:¥{price_info[0]}, 活动库存:{price_info[1]}'
1049
-
1050
- def get_skc_activity_label(self, skc, sku, dict_activity_price=None):
1051
- cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
1052
- dict_label = read_dict_from_file(cache_file)
1053
- operateLabelList = dict_label[skc]['operateLabelList']
1054
- activityList = []
1055
- activityList2 = []
1056
- for item in operateLabelList:
1057
- if item['name'] == '活动中':
1058
- activityList.extend(item.get('activityList', []))
1059
- if item['name'] == '即将开始':
1060
- activityList2.extend(item.get('activityList', []))
1061
-
1062
- if activityList:
1063
- activityLabel = '\n'.join([
1064
- f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
1065
- for act in activityList])
1066
- else:
1067
- activityLabel = '无'
1068
- if activityList2:
1069
- activityLabel2 = '\n'.join([
1070
- f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
1071
- for act in activityList2])
1072
- else:
1073
- activityLabel2 = ''
1074
- return f'活动中:\n{activityLabel}\n即将开始:\n{activityLabel2}'
1075
-
1076
- # 获取商品包含sku销量的列表
1077
- # mode = 1.备货建议 2.已上架 3.昨日上架 4.昨日出单
1078
- # 5.采购-缺货要补货 (有现货建议 建议采购为正 有销量)
1079
- # 6.运营采购-滞销清库存 (无现货建议 建议采购为负 30天外 无销量)
1080
- # 7.运营-新品上架需要优化 (无现货建议 建议采购为负 上架15天内)
1081
- # 8.运营-潜在滞销款 (无现货建议 30天外 有销量)
1082
- # 9.运营-潜力热销款 (有现货建议 30天内 有销量)
1083
- # 10.运营-热销款 (有现货建议 30天外 有销量)
1084
- def get_bak_advice(self, mode=1, skcs=None, source='mb'):
1085
- log(f'获取备货信息商品列表 做成字典')
1086
- if skcs == None or len(skcs) == 0:
1087
- # if mode == 3:
1088
- # skcs = "sh2405133614611175" # 这是一个不存在的skc
1089
- # else:
1090
- skcs = ""
1091
- else:
1092
- skcs = ",".join(skcs)
1093
-
1094
- url = "https://sso.geiwohuo.com/idms/goods-skc/list"
1095
- pageNumber = 1
1096
- pageSize = 100
1097
- dictPayload = {
1098
- "pageNumber" : pageNumber,
1099
- "pageSize" : pageSize,
1100
- "supplierCodes" : "",
1101
- "skcs" : skcs,
1102
- "spu" : "",
1103
- "c7dSaleCntBegin" : "",
1104
- "c7dSaleCntEnd" : "",
1105
- "goodsLevelIdList" : [10, 107, 61, 90, 87, 237, 220, 219, 88, 75, 62, 227, 12, 230, 80, 58, 224, 97],
1106
- "supplyStatus" : "",
1107
- "shelfStatus" : "",
1108
- "categoryIdList" : [],
1109
- "skcStockBegin" : "",
1110
- "skcStockEnd" : "",
1111
- "skuStockBegin" : "",
1112
- "skuStockEnd" : "",
1113
- "skcSaleDaysBegin" : "",
1114
- "skcSaleDaysEnd" : "",
1115
- "skuSaleDaysBegin" : "",
1116
- "skuSaleDaysEnd" : "",
1117
- "planUrgentCountBegin" : "",
1118
- "planUrgentCountEnd" : "",
1119
- "skcAvailableOrderBegin": "",
1120
- "skcAvailableOrderEnd" : "",
1121
- "skuAvailableOrderBegin": "",
1122
- "skuAvailableOrderEnd" : "",
1123
- "shelfDateBegin" : "",
1124
- "shelfDateEnd" : "",
1125
- "stockWarnStatusList" : [],
1126
- "labelFakeIdList" : [],
1127
- "sheinSaleByInventory" : "",
1128
- "tspIdList" : [],
1129
- "adviceStatus" : [],
1130
- "sortBy7dSaleCnt" : 2,
1131
- "goodsLevelFakeIdList" : [1, 2, 3, 8, 14, 15, 4, 11]
1132
- }
1133
- payload = dictPayload
1134
- response_text = fetch(self.web_page, url, payload)
1135
- error_code = response_text.get('code')
1136
- if str(error_code) != '0':
1137
- raise send_exception(json.dumps(response_text, ensure_ascii=False))
1138
-
1139
- spu_list = response_text['info']['list']
1140
-
1141
- skc_list = [item['skc'] for item in spu_list]
1142
- self.get_activity_label(skc_list)
1143
- self.get_preemption_list(skc_list)
1144
- self.get_sku_price_v2(skc_list)
1145
- self.get_stock_advice(skc_list)
1146
-
1147
- total = response_text['info']['count']
1148
- totalPage = math.ceil(total / pageSize)
1149
- for page in range(2, totalPage + 1):
1150
- log(f'获取备货信息商品列表 第{page}/{totalPage}页')
1151
- dictPayload['pageNumber'] = page
1152
- payload = dictPayload
1153
- response_text = fetch(self.web_page, url, payload)
1154
- spu_list_new = response_text['info']['list']
1155
-
1156
- skc_list = [item['skc'] for item in spu_list_new]
1157
- self.get_activity_label(skc_list)
1158
- self.get_preemption_list(skc_list)
1159
- self.get_sku_price_v2(skc_list)
1160
- self.get_stock_advice(skc_list)
1161
-
1162
- spu_list += spu_list_new
1163
- time.sleep(0.3)
1164
-
1165
- cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
1166
- dictActivityPrice = read_dict_from_file(cache_file)
1167
- # cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
1168
- # DictSpuInfo = read_dict_from_file(cache_file, 5)
1169
- cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
1170
- dict_preemption_num = read_dict_from_file(cache_file)
1171
- cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
1172
- dict_advice = read_dict_from_file(cache_file)
1173
- cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
1174
- dict_sku_price = read_dict_from_file(cache_file)
1175
- date_list = TimeUtils.get_past_7_days_list()
1176
- if mode in [2, 5, 6, 7, 8, 9, 10]:
1177
- excel_data = [[
1178
- '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '建议现货数量', '现有库存数量', '已采购数量', '预测日销',
1179
- '本地和采购可售天数', '生产天数', '建议采购', '产品起定量',
1180
- '备货周期()', '备货建议', '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动',
1181
- 'SKC',
1182
- "SKU"
1183
- ]]
1184
- else:
1185
- excel_data = [[
1186
- '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '备货建议', '近7天SKU销量/SKC销量/SKC曝光',
1187
- 'SKC点击率/SKC转化率', '自主参与活动', 'SKC', "SKU"
1188
- ]]
1189
- for spu_info in spu_list:
1190
- spu = str(spu_info['spu'])
1191
- skc = str(spu_info['skc'])
1192
-
1193
- status_cn = spu_info['shelfStatus']['name']
1194
- if status_cn != '已上架':
1195
- continue
1196
-
1197
- # shelf_status = DictSpuInfo.get(spu, {}).get('shelf_status', '')
1198
- # if mode != 1:
1199
- # if shelf_status != 'ON_SHELF' and shelf_status != 'SOLD_OUT':
1200
- # log('跳过', skc, shelf_status)
1201
- # continue
1202
-
1203
- # if mode in [5, 6, 7, 8, 9, 10] and shelf_status == 'SOLD_OUT':
1204
- # continue
1205
- #
1206
- # dictStatus = {
1207
- # 'WAIT_SHELF': "待上架",
1208
- # 'ON_SHELF': "已上架",
1209
- # 'SOLD_OUT': "已售罄",
1210
- # 'OUT_SHELF': "已下架"
1211
- # }
1212
- # status_cn = dictStatus.get(shelf_status, '-')
1213
-
1214
- sale_model = spu_info['saleModel']['name']
1215
- goods_level = spu_info['goodsLevel']['name']
1216
- goods_label = [label["name"] for label in spu_info['goodsLabelList']]
1217
- skc_img = spu_info['picUrl']
1218
- shelfDate = spu_info['shelfDate']
1219
- shelfDays = spu_info['shelfDays']
1220
- categoryName = spu_info['categoryName']
1221
-
1222
- if mode in [3] and shelfDays != 1:
1223
- continue
1224
-
1225
- DictSkuSalesDate = self.get_skc_week_actual_sales(skc)
1226
-
1227
- for sku_info in spu_info['skuList']:
1228
- row_item = []
1229
- attr = sku_info['attr']
1230
- if attr == '合计':
1231
- continue
1232
- predictDaySales = sku_info['predictDaySales']
1233
- availableOrderCount = sku_info['availableOrderCount']
1234
- if mode == 1:
1235
- if availableOrderCount is None or availableOrderCount <= 0:
1236
- log('跳过', skc, availableOrderCount)
1237
- continue
1238
-
1239
- row_item.append(f'{self.store_name}\n({status_cn})\n{goods_level}\n{",".join(goods_label)}')
1240
- row_item.append(skc_img)
1241
- sku = sku_info['skuCode']
1242
- skuExtCode = str(sku_info['supplierSku'])
1243
- sku_img = self.bridge.get_sku_img(skuExtCode, source)
1244
- row_item.append(sku_img)
1245
-
1246
- transit = sku_info['transit'] # 在途
1247
-
1248
- stock = self.bridge.get_sku_stock(skuExtCode, source)
1249
- cost_price = self.bridge.get_sku_cost(skuExtCode, source)
1250
-
1251
- supplyPrice = dict_sku_price[sku]
1252
- shein_stock = sku_info['stock']
1253
- if cost_price == '-':
1254
- profit = '-'
1255
- else:
1256
- profit = f'{float(supplyPrice) - float(cost_price):.2f}'
1257
-
1258
- min_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('minSpotAdvice', 0)
1259
- max_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('maxSpotAdvice', 0)
1260
- stock_advice = f'{min_spot_advice}~{max_spot_advice}'
1261
- log('stock_advice', stock_advice)
1262
- # 建议现货数量
1263
- advice_stock_number = round((min_spot_advice + max_spot_advice) / 4)
1264
-
1265
- # 有现货建议
1266
- if mode in [5, 9, 10] and advice_stock_number == 0:
1267
- continue
1268
-
1269
- # 无现货建议
1270
- if mode in [6, 7, 8] and advice_stock_number > 0:
1271
- continue
1272
-
1273
- stockSaleDays = sku_info['stockSaleDays']
1274
-
1275
- product_info = (
1276
- f'SPU: {spu}\n'
1277
- f'SKC: {skc}\n'
1278
- f'SKU货号: {skuExtCode}\n'
1279
- f'属性集: {attr}\n'
1280
- f'商品分类: {categoryName}\n'
1281
- f'上架日期: {shelfDate}\n'
1282
- f'上架天数: {shelfDays}\n'
1283
- f'库存可售天数/现货建议: {stockSaleDays}/{stock_advice}\n'
1284
- )
1285
- row_item.append(product_info)
1286
-
1287
- # 建议采购数量逻辑
1288
- try:
1289
- # 尝试将字符串数字转换为 float,再转为 int(如有必要)
1290
- current_stock = float(stock)
1291
- advice_purchase_number = advice_stock_number - int(current_stock)
1292
-
1293
- # 建议采购为正
1294
- if (mode == 5 and advice_purchase_number <= 0):
1295
- continue
1296
-
1297
- except (ValueError, TypeError):
1298
- # 无法转换为数值时
1299
- advice_purchase_number = '-'
1300
-
1301
- if mode in [2, 5, 6, 7, 8, 9, 10]:
1302
- row_item.append(advice_stock_number)
1303
- row_item.append(stock)
1304
-
1305
- row_item.append(0)
1306
- row_item.append(predictDaySales)
1307
- row_item.append(0)
1308
- row_item.append(7)
1309
-
1310
- row_item.append(advice_purchase_number)
1311
- row_item.append(0) # 产品起定量
1312
- row_item.append(0) # 备货周期(天)
1313
-
1314
- adviceOrderCount = sku_info['adviceOrderCount'] if sku_info['adviceOrderCount'] is not None else '-'
1315
- if sku_info['autoOrderStatus'] is not None:
1316
- autoOrderStatus = ['-', '是', '否'][sku_info['autoOrderStatus']] if sku_info[
1317
- 'adviceOrderCount'] is not None else '-'
1318
- else:
1319
- autoOrderStatus = '-'
1320
- orderCount = sku_info['orderCount'] # 已下单数
1321
- c7dSaleCnt = sku_info['c7dSaleCnt']
1322
- c30dSaleCnt = sku_info['c30dSaleCnt']
1323
- orderCnt = sku_info['orderCnt']
1324
- totalSaleVolume = sku_info['totalSaleVolume']
1325
- planUrgentCount = sku_info['planUrgentCount']
1326
- preemptionCount = dict_preemption_num[skc][sku]
1327
- predictDaySales = sku_info['predictDaySales']
1328
- goodsDate = sku_info['goodsDate']
1329
- stockDays = sku_info['stockDays']
1330
-
1331
- real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
1332
-
1333
- sales_info = (
1334
- f'近7天/30天销量: {c7dSaleCnt}/{c30dSaleCnt}\n'
1335
- f'当天销量/购买单数: {totalSaleVolume}/{orderCnt}\n'
1336
- f'预测日销/下单参数: {predictDaySales}/{goodsDate}+{stockDays}\n'
1337
- f'预占数/预计急采数: {preemptionCount}/{planUrgentCount}\n'
1338
- f'建议下单/已下单数: {adviceOrderCount}/{orderCount}\n'
1339
- f'拟下单数/自动下单: {availableOrderCount}/{autoOrderStatus}\n'
1340
- f'模式/本地/在途/希音: {sale_model[:2]}/{stock}/{real_transit}/{shein_stock}\n'
1341
- f'成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n'
1342
- )
1343
-
1344
- row_item.append(sales_info)
1345
-
1346
- flag_yesterday = 0
1347
- sales7cn = 0
1348
- for date in date_list:
1349
- sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
1350
- sales_num = sales_num if sales_num is not None else 0
1351
- sales7cn += sales_num
1352
- if TimeUtils.is_yesterday_date(date) and sales_num == 0:
1353
- flag_yesterday = 1
1354
-
1355
- if mode == 4 and flag_yesterday:
1356
- continue
1357
-
1358
- # 过滤掉未建立马帮信息的
1359
- if mode in [5, 6, 7, 8, 9, 10] and advice_purchase_number == '-':
1360
- continue
1361
-
1362
- # 建议采购为正
1363
- if mode in [5] and advice_purchase_number < 0:
1364
- continue
1365
-
1366
- # 建议采购为负
1367
- if mode in [6, 7] and advice_purchase_number >= 0:
1368
- continue
1369
-
1370
- # 30内
1371
- if mode in [9] and shelfDays > 31:
1372
- continue
1373
-
1374
- # 15天内
1375
- if mode in [7] and shelfDays > 15:
1376
- continue
1377
-
1378
- # 30外
1379
- if mode in [6, 8, 10] and shelfDays < 31:
1380
- continue
1381
-
1382
- # 有销量
1383
- if mode in [5, 8, 9, 10] and sales7cn == 0:
1384
- continue
1385
-
1386
- # 无销量
1387
- if mode in [6] and sales7cn > 0:
1388
- continue
1389
-
1390
- sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
1391
- row_item.append("\n".join(sale_num_list))
1392
- row_item.append("\n".join(sale_data_list))
1393
- row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
1394
- row_item.append(skc)
1395
- row_item.append(sku)
1396
- excel_data.append(row_item)
1397
-
1398
- cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{TimeUtils.today_date()}.json'
1399
- write_dict_to_file_ex(cache_file, {self.store_name: excel_data}, {self.store_name})
1400
-
1401
- cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{TimeUtils.today_date()}.json'
1402
- NotifyItem = [self.store_name, len(excel_data[1:])]
1403
- write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
1404
-
1405
- return excel_data
1
+ from .fun_file import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
2
+ from .fun_base import log, send_exception, md5_string, get_safe_value
3
+ from .fun_web import fetch
4
+ from .time_utils import TimeUtils
5
+
6
+ from .shein_sqlite import insert_sales, get_last_week_sales, get_near_week_sales, get_near_month_sales, get_last_month_sales
7
+
8
+ import math
9
+ import time
10
+ import json
11
+ from datetime import datetime
12
+ from playwright.sync_api import Page
13
+
14
+ class SheinLib:
15
+
16
+ def __init__(self, config, bridge, web_page: Page, store_username, store_name):
17
+ self.config = config
18
+ self.bridge = bridge
19
+ self.store_username = store_username
20
+ self.store_name = store_name
21
+ self.web_page = web_page
22
+ self.dt = None
23
+
24
+ self.deal_auth()
25
+
26
+ # 处理鉴权
27
+ def deal_auth(self):
28
+ web_page = self.web_page
29
+ while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
30
+ if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
31
+ web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
32
+ log("鉴权确定按钮可见 点击'确定'按钮")
33
+ web_page.wait_for_load_state("load")
34
+ web_page.wait_for_timeout(1000)
35
+ # time.sleep(1)
36
+ if web_page.locator('//input[@name="username"]').is_visible():
37
+ log("用户名输入框可见 等待5秒点击'登录'按钮")
38
+ web_page.wait_for_timeout(5000)
39
+ log('点击"登录"')
40
+ web_page.locator('//button[contains(@class,"login_btn")]').click()
41
+ web_page.wait_for_load_state("load")
42
+ log('再延时5秒')
43
+ web_page.wait_for_timeout(5000)
44
+ if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
45
+ log('商品管理菜单可见 退出鉴权处理')
46
+ return
47
+ log('商家后台不可见', web_page.title(), web_page.url)
48
+ web_page.wait_for_load_state("load")
49
+ # time.sleep(1)
50
+ web_page.wait_for_timeout(1000)
51
+ if 'SHEIN全球商家中心' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
52
+ log('SHEIN全球商家中心 中断循环')
53
+ break
54
+ if '后台首页' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
55
+ log('后台首页 中断循环')
56
+ break
57
+ if '商家后台' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
58
+ log('后台首页 中断循环')
59
+ break
60
+ if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
61
+ web_page.goto('https://sso.geiwohuo.com/#/home')
62
+ web_page.wait_for_load_state("load")
63
+ web_page.wait_for_timeout(3000)
64
+ # time.sleep(3)
65
+ if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
66
+ log('检测到鉴权 刷新页面')
67
+ web_page.reload()
68
+ web_page.wait_for_load_state('load')
69
+ # time.sleep(3)
70
+ web_page.wait_for_timeout(3000)
71
+ web_page.reload()
72
+ # time.sleep(3)
73
+ web_page.wait_for_timeout(3000)
74
+ if web_page.title() == 'SHEIN':
75
+ web_page.goto('https://sso.geiwohuo.com/#/home')
76
+ web_page.wait_for_load_state("load")
77
+ # time.sleep(3)
78
+ web_page.wait_for_timeout(3000)
79
+
80
+ # web_page.goto('https://sso.geiwohuo.com')
81
+ log('鉴权处理结束')
82
+
83
+ def get_delivery_order_list(self, orderType=2):
84
+ page_num = 1
85
+ page_size = 200
86
+
87
+ url = f"https://sso.geiwohuo.com/pfmp/order/list"
88
+ payload = {}
89
+ if orderType == 1:
90
+ payload = {
91
+ "orderType": orderType,
92
+ "page" : page_num,
93
+ "perPage" : page_size,
94
+ "status" : [2],
95
+ }
96
+ elif orderType == 2:
97
+ payload = {
98
+ "orderType" : orderType,
99
+ "page" : page_num,
100
+ "perPage" : page_size,
101
+ "status" : [2],
102
+ "isJitOrder": 2
103
+ }
104
+ response_text = fetch(self.web_page, url, payload)
105
+ error_code = response_text.get('code')
106
+ if str(error_code) != '0':
107
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
108
+
109
+ spu_list = response_text['info']['data']
110
+ total = response_text['info']['meta']['count']
111
+ totalPage = math.ceil(total / page_size)
112
+
113
+ skc_list = [item['goods']['skcName'] for item in spu_list]
114
+ self.get_activity_label(skc_list)
115
+
116
+ for page in range(2, totalPage + 1):
117
+ log(f'获取订单列表 第{page}/{totalPage}页')
118
+ page_num = page
119
+ if orderType == 1:
120
+ payload = {
121
+ "orderType": orderType,
122
+ "page" : page_num,
123
+ "perPage" : page_size,
124
+ "status" : [2],
125
+ }
126
+ elif orderType == 2:
127
+ payload = {
128
+ "orderType" : orderType,
129
+ "page" : page_num,
130
+ "perPage" : page_size,
131
+ "status" : [2],
132
+ "isJitOrder": 2
133
+ }
134
+ response_text = fetch(self.web_page, url, payload)
135
+ spu_list_new = response_text['info']['data']
136
+ skc_list = [item['goods']['skcName'] for item in spu_list_new]
137
+ self.get_activity_label(skc_list)
138
+ spu_list += spu_list_new
139
+ time.sleep(0.3)
140
+
141
+ if len(spu_list) == 0:
142
+ log(f'无{["", "急采", "备货"][orderType]}发货单')
143
+ return None
144
+
145
+ write_to_excel = [
146
+ # 0 1 2 3 4 5 6 7
147
+ ['店铺名称', 'SKC图片', 'SKU图片', '商品信息', '下单/需求数量', '库存(模式/本地/在途/希音)', '成本价', '核价',
148
+ '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '最晚预约上门取件', '要求实际完成取件',
149
+ 'SKC', 'SKU']
150
+ ]
151
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
152
+ DictSkcShelf = read_dict_from_file(cache_file2)
153
+ cache_file3 = f'{self.config.auto_dir}/shein/dict/skc_product_{self.store_username}.json'
154
+ DictSkcProduct = read_dict_from_file(cache_file3)
155
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
156
+ dictActivityPrice = read_dict_from_file(cache_file)
157
+ cache_file4 = f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json'
158
+ DictSkuInfo = read_dict_from_file(cache_file4)
159
+ for spu_item in spu_list:
160
+ skc = spu_item['goods']['skcName']
161
+ skcCode = spu_item['goods']['supplierCode']
162
+ skc_img = str(spu_item['goods']['imgPath'])
163
+ orderNum = spu_item['sellerOrderNo']
164
+ orderTime = spu_item['allocateTime']
165
+ requestTakeParcelTime = spu_item['requestTakeParcelTime']
166
+ suggestedReserveTime = spu_item['suggestedReserveTime']
167
+ good_level = spu_item['goods']['goodsLevelName']
168
+
169
+ self.get_skc_week_actual_sales(skc)
170
+
171
+ spu = DictSkcProduct[skc]['spu_name']
172
+ log('spu', spu)
173
+ for sku_item in spu_item['detail']:
174
+ needQuantity = sku_item['needQuantity']
175
+ orderQuantity = sku_item['orderQuantity']
176
+ # sku_img = sku_item['skuThumb']
177
+ skuCode = sku_item['supplierSku']
178
+ stock = self.bridge.get_sku_stock(skuCode, 'mb')
179
+ cost_price = self.bridge.get_sku_cost(skuCode, 'mb')
180
+ suffixZh = sku_item['suffixZh']
181
+ sku = sku_item['skuCode']
182
+ supplyPrice = sku_item['supplyPrice']
183
+ sku_img = self.bridge.get_sku_img(skuCode, 'mb')
184
+ sale_model = DictSkuInfo[skuCode][0]
185
+ shein_stock = DictSkuInfo[skuCode][1]
186
+ shelf_days = DictSkuInfo[skuCode][2]
187
+ real_transit = DictSkuInfo[skuCode][3]
188
+ stock_str = f'{sale_model}\n{stock}/{real_transit}/{shein_stock}'
189
+
190
+ item = []
191
+ item.append(f'{self.store_name}\n{good_level}')
192
+ item.append(skc_img)
193
+ item.append(sku_img)
194
+ if cost_price == '-':
195
+ profit = '-'
196
+ else:
197
+ profit = f'{float(supplyPrice) - float(cost_price):.2f}'
198
+ # item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n下单/需求数量: {orderQuantity}/{needQuantity}\n库存模式/本地/在途/希音: {sale_model}/{stock}/{real_transit}/{shein_stock}\n')
199
+ item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}')
200
+ item.append(f'[{orderQuantity}/{needQuantity}]')
201
+ item.append(stock_str)
202
+ item.append(cost_price)
203
+ item.append(supplyPrice)
204
+ sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
205
+ item.append("\n".join(sale_num_list))
206
+ item.append("\n".join(sale_data_list))
207
+ item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
208
+ item.append(suggestedReserveTime)
209
+ item.append(requestTakeParcelTime)
210
+ item.append(skc)
211
+ item.append(sku)
212
+ write_to_excel.append(item)
213
+
214
+ cache_file = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_{orderType}_{TimeUtils.get_period()}.json'
215
+ write_dict_to_file_ex(cache_file, {self.store_username: write_to_excel}, {self.store_username})
216
+
217
+ return write_to_excel
218
+
219
+ # 获取商品包含sku销量的列表
220
+ def get_dict_sku_stock_detail(self):
221
+ log(f'获取备货信息商品列表 做成字典')
222
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
223
+ pageNumber = 1
224
+ pageSize = 100
225
+ dictPayload = {
226
+ "pageNumber" : pageNumber,
227
+ "pageSize" : pageSize,
228
+ "supplierCodes" : "",
229
+ "skcs" : "",
230
+ "spu" : "",
231
+ "c7dSaleCntBegin" : "",
232
+ "c7dSaleCntEnd" : "",
233
+ "goodsLevelIdList" : [],
234
+ "supplyStatus" : "",
235
+ "shelfStatus" : "",
236
+ "categoryIdList" : [],
237
+ "skcStockBegin" : "",
238
+ "skcStockEnd" : "",
239
+ "skuStockBegin" : "",
240
+ "skuStockEnd" : "",
241
+ "skcSaleDaysBegin" : "",
242
+ "skcSaleDaysEnd" : "",
243
+ "skuSaleDaysBegin" : "",
244
+ "skuSaleDaysEnd" : "",
245
+ "planUrgentCountBegin" : "",
246
+ "planUrgentCountEnd" : "",
247
+ "skcAvailableOrderBegin": "",
248
+ "skcAvailableOrderEnd" : "",
249
+ "skuAvailableOrderBegin": "",
250
+ "skuAvailableOrderEnd" : "",
251
+ "shelfDateBegin" : "",
252
+ "shelfDateEnd" : "",
253
+ "stockWarnStatusList" : [],
254
+ "labelFakeIdList" : [],
255
+ "sheinSaleByInventory" : "",
256
+ "tspIdList" : [],
257
+ "adviceStatus" : [],
258
+ "sortBy7dSaleCnt" : 2
259
+ }
260
+ payload = dictPayload
261
+ response_text = fetch(self.web_page, url, payload)
262
+ error_code = response_text.get('code')
263
+ if str(error_code) != '0':
264
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
265
+
266
+ spu_list = response_text['info']['list']
267
+
268
+ total = response_text['info']['count']
269
+ totalPage = math.ceil(total / pageSize)
270
+ for page in range(2, totalPage + 1):
271
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
272
+ dictPayload['pageNumber'] = page
273
+ payload = dictPayload
274
+ response_text = fetch(self.web_page, url, payload)
275
+ spu_list_new = response_text['info']['list']
276
+ spu_list += spu_list_new
277
+ time.sleep(0.3)
278
+
279
+ DictSkuInfo = {}
280
+ for spu_info in spu_list:
281
+ sale_model = spu_info.get('saleModel', {}).get('name') if spu_info.get('saleModel') else '-'
282
+ shelfDays = spu_info['shelfDays']
283
+ for sku_info in spu_info['skuList']:
284
+ attr = sku_info['attr']
285
+ if attr == '合计':
286
+ continue
287
+ skuExtCode = str(sku_info['supplierSku'])
288
+ shein_stock = sku_info['stock']
289
+
290
+ transit = sku_info['transit'] # 在途
291
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
292
+
293
+ DictSkuInfo[skuExtCode] = [sale_model, shein_stock, shelfDays, real_transit]
294
+
295
+ write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json', DictSkuInfo)
296
+
297
+ return DictSkuInfo
298
+
299
+ def get_shop_notify_num(self):
300
+ log(f'正在获取 {self.store_name} 通知数据')
301
+ url = "https://sso.geiwohuo.com/sso/homePage/v4/detail"
302
+ payload = {
303
+ "metaIndexIds": [
304
+ 246, # 急采-待发货
305
+ 245 # 备货-待发货
306
+ ],
307
+ "templateType": 0
308
+ }
309
+ response_text = fetch(self.web_page, url, payload)
310
+ error_code = response_text.get('code')
311
+ if str(error_code) != '0':
312
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
313
+ info = response_text.get('info')
314
+
315
+ cache_file = f'{self.config.auto_dir}/shein/notify/{self.store_name}_{TimeUtils.get_current_datetime()}.json'
316
+ write_dict_to_file(cache_file, info)
317
+
318
+ num245 = 0
319
+ num246 = 0
320
+ for item in info['list']:
321
+ if item['metaIndexId'] == 245:
322
+ num245 = item['count']
323
+ if item['metaIndexId'] == 246:
324
+ num246 = item['count']
325
+
326
+ NotifyItem = [self.store_name, num246, num245]
327
+
328
+ cache_file = f'{self.config.auto_dir}/shein/cache/jit_notify_{TimeUtils.today_date()}.json'
329
+ write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, {self.store_username})
330
+
331
+ return info
332
+
333
+ def get_activity_list(self):
334
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_activity_list?page_num=1&page_size=100"
335
+ payload = {}
336
+ response_text = fetch(self.web_page, url, payload)
337
+ error_code = response_text.get('code')
338
+ if str(error_code) != '0':
339
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
340
+ total = response_text.get('info', {}).get('total_count')
341
+ excel_data = [[
342
+ '店铺名称', '活动信息', '已报数量', '可报数量'
343
+ ]]
344
+ if total > 0:
345
+ for item in response_text.get('info', {}).get('activity_detail_list'):
346
+ activity_tag = item.get('text_tag_content')
347
+ activity_name = item['activity_name']
348
+ start_time = item['activity_start_zone_time']
349
+ end_time = item['activity_end_zone_time']
350
+ start_time2 = item['start_zone_time']
351
+ end_time2 = item['end_zone_time']
352
+ allow_goods_num = item.get('allow_goods_num')
353
+ apply_goods_num = item.get('apply_goods_num')
354
+ row_item = [
355
+ self.store_name,
356
+ f"活动名称: 【{activity_tag}】{activity_name}\n报名时间: {start_time}~{end_time}\n活动时间: {start_time2}~{end_time2}\n已报数量: {apply_goods_num}/{allow_goods_num}",
357
+ apply_goods_num,
358
+ allow_goods_num,
359
+ ]
360
+ excel_data.append(row_item)
361
+ cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
362
+ write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
363
+
364
+ def get_product_list(self):
365
+ # self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
366
+ # self.web_page.wait_for_load_state("load")
367
+
368
+ cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
369
+ DictSpuInfo = read_dict_from_file(cache_file, 3600)
370
+ if len(DictSpuInfo) > 0:
371
+ return DictSpuInfo
372
+
373
+ page_num = 1
374
+ page_size = 100
375
+
376
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
377
+ payload = {
378
+ "language" : "zh-cn",
379
+ "only_recommend_resell" : False,
380
+ "only_spmb_copy_product": False,
381
+ "search_abandon_product": False,
382
+ "sort_type" : 1
383
+ }
384
+ response_text = fetch(self.web_page, url, payload)
385
+ error_code = response_text.get('code')
386
+ if str(error_code) != '0':
387
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
388
+
389
+ spu_list = response_text['info']['data']
390
+ total = response_text['info']['meta']['count']
391
+ totalPage = math.ceil(total / page_size)
392
+
393
+ for page_num in range(2, totalPage + 1):
394
+ log(f'获取商品列表 第{page_num}/{totalPage}页')
395
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
396
+ response_text = fetch(self.web_page, url, payload)
397
+ spu_list_new = response_text['info']['data']
398
+ spu_list += spu_list_new
399
+ time.sleep(0.3)
400
+
401
+ DictSkcShelf = {}
402
+ DictSkcProduct = {}
403
+ DictSpuInfo = {}
404
+ for spu_item in spu_list:
405
+ spu = spu_item['spu_name']
406
+ first_shelf_time = spu_item['first_shelf_time']
407
+ for skc_item in spu_item['skc_info_list']:
408
+ skc_name = skc_item['skc_name']
409
+ DictSkcShelf[skc_name] = first_shelf_time
410
+ DictSkcProduct[skc_name] = spu_item
411
+ DictSpuInfo[spu] = spu_item
412
+
413
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
414
+ write_dict_to_file(cache_file2, DictSkcShelf)
415
+ cache_file3 = f'{self.config.auto_dir}/dict/skc_product_{self.store_username}.json'
416
+ write_dict_to_file(cache_file3, DictSkcProduct)
417
+
418
+ write_dict_to_file(cache_file, DictSpuInfo)
419
+ return DictSpuInfo
420
+
421
+ def query_obm_activity_list(self):
422
+ page_num = 1
423
+ page_size = 100
424
+ date_60_days_ago = TimeUtils.get_past_nth_day(59)
425
+ cache_file = f'{self.config.auto_dir}/shein/cache/obm_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
426
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
427
+ if len(list_item) > 0:
428
+ return list_item
429
+
430
+ url = f"https://sso.geiwohuo.com/mrs-api-prefix/promotion/obm/query_obm_activity_list"
431
+ payload = {
432
+ "insert_end_time" : f"{TimeUtils.today_date()} 23:59:59",
433
+ "insert_start_time": f"{date_60_days_ago} 00:00:00",
434
+ "page_num" : page_num,
435
+ "page_size" : page_size,
436
+ "system" : "mrs",
437
+ "time_zone" : "Asia/Shanghai",
438
+ # "state": 3, # 活动开启中 不能用这个条件
439
+ "type_id" : 31 # 限时折扣
440
+ }
441
+
442
+ response_text = fetch(self.web_page, url, payload)
443
+ error_code = response_text.get('code')
444
+ if str(error_code) != '0':
445
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
446
+
447
+ list_item = response_text['info']['data']
448
+ total = response_text['info']['meta']['count']
449
+ totalPage = math.ceil(total / page_size)
450
+
451
+ for page in range(2, totalPage + 1):
452
+ log(f'获取营销工具列表 第{page}/{totalPage}页')
453
+ payload["page_num"] = page
454
+ response_text = fetch(self.web_page, url, payload)
455
+ list_item += response_text['info']['data']
456
+ time.sleep(0.1)
457
+
458
+ write_dict_to_file(cache_file, list_item)
459
+ return list_item
460
+
461
+ def query_goods_detail(self, activity_id):
462
+ # web_page.goto(f'https://sso.geiwohuo.com/#/mrs/tools/activity/obm-time-limit-info/{activity_id}')
463
+ # web_page.wait_for_load_state('load')
464
+ log(f'正在获取 {self.store_name} {activity_id} 营销工具商品详情')
465
+
466
+ cache_file = f'{self.config.auto_dir}/shein/cache/query_goods_detail_{activity_id}.json'
467
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
468
+ if len(list_item) > 0:
469
+ return list_item
470
+
471
+ page_num = 1
472
+ page_size = 100
473
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/simple_platform/query_goods_detail"
474
+ payload = {
475
+ "activity_id": activity_id,
476
+ "page_num" : page_num,
477
+ "page_size" : page_size
478
+ }
479
+ response_text = fetch(self.web_page, url, payload)
480
+ error_code = response_text.get('code')
481
+ if str(error_code) != '0':
482
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
483
+ list_item = response_text['info']['data']
484
+ total = response_text['info']['meta']['count']
485
+ totalPage = math.ceil(total / page_size)
486
+
487
+ for page in range(2, totalPage + 1):
488
+ log(f'获取营销工具商品列表 第{page}/{totalPage}页')
489
+ payload["page_num"] = page
490
+ response_text = fetch(self.web_page, url, payload)
491
+ list_item += response_text['info']['data']
492
+ time.sleep(0.1)
493
+
494
+ write_dict_to_file(cache_file, list_item)
495
+ return list_item
496
+
497
+ def get_partake_activity_goods_list(self):
498
+ # self.web_page.goto(f'https://sso.geiwohuo.com/#/mbrs/marketing/list/1')
499
+ # self.web_page.wait_for_load_state('load')
500
+ log(f'正在获取 {self.store_name} 活动列表')
501
+ page_num = 1
502
+ page_size = 100
503
+ date_60_days_ago = TimeUtils.get_past_nth_day(59)
504
+ cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
505
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
506
+ if len(list_item) > 0:
507
+ return list_item
508
+
509
+ url = f"https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_partake_activity_goods_list?page_num={page_num}&page_size={page_size}"
510
+ payload = {
511
+ "goods_audit_status" : 1,
512
+ "insert_zone_time_end" : f"{TimeUtils.today_date()} 23:59:59",
513
+ "insert_zone_time_start": f"{date_60_days_ago} 00:00:00"
514
+ }
515
+
516
+ response_text = fetch(self.web_page, url, payload)
517
+ error_code = response_text.get('code')
518
+ if str(error_code) != '0':
519
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
520
+ list_item = response_text['info']['data']
521
+ total = response_text['info']['meta']['count']
522
+ totalPage = math.ceil(total / page_size)
523
+
524
+ for page in range(2, totalPage + 1):
525
+ log(f'获取活动列表 第{page}/{totalPage}页')
526
+ payload["page_num"] = page
527
+ response_text = fetch(self.web_page, url, payload)
528
+ list_item += response_text['info']['data']
529
+ time.sleep(0.1)
530
+
531
+ write_dict_to_file(cache_file, list_item)
532
+ return list_item
533
+
534
+ def generate_activity_price_dict(self):
535
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
536
+ dict_activity_price = {}
537
+ activity_list = self.query_obm_activity_list()
538
+ for activity in activity_list:
539
+ activity_id = activity['activity_id']
540
+ activity_name = activity['act_name']
541
+ sub_type_id = activity['sub_type_id'] # 1.不限量 2.限量
542
+ dateBegin = TimeUtils.convert_datetime_to_date(activity['start_time'])
543
+ dateEnd = TimeUtils.convert_datetime_to_date(activity['end_time'])
544
+ skc_list = self.query_goods_detail(activity_id)
545
+ for skc_item in skc_list:
546
+ attend_num_sum = skc_item['attend_num_sum']
547
+ product_act_price = skc_item['product_act_price'] # 活动价
548
+ if sub_type_id == 1:
549
+ attend_num_sum = '不限量'
550
+ for sku_item in skc_item['sku_info_list']:
551
+ sku = sku_item['sku'] # 平台sku
552
+ product_act_price = sku_item['product_act_price'] if sku_item[
553
+ 'product_act_price'] else product_act_price # 活动价
554
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
555
+ dict_activity_price[key] = [product_act_price, attend_num_sum]
556
+
557
+ platform_activity_list = self.get_partake_activity_goods_list()
558
+ for platform_activity in platform_activity_list:
559
+ activity_name = platform_activity['activity_name']
560
+ text_tag_content = platform_activity['text_tag_content']
561
+ attend_num = platform_activity['attend_num']
562
+ dateBegin = TimeUtils.convert_timestamp_to_date(platform_activity['start_time'])
563
+ dateEnd = TimeUtils.convert_timestamp_to_date(platform_activity['end_time'])
564
+ if text_tag_content != '新品':
565
+ attend_num = '-'
566
+ for sku_item in platform_activity['activity_sku_list']:
567
+ sku = sku_item['sku_code']
568
+ enroll_price = sku_item['enroll_display_str'][:-3]
569
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
570
+ dict_activity_price[key] = [enroll_price, attend_num]
571
+
572
+ write_dict_to_file(cache_file, dict_activity_price)
573
+
574
+ def get_skc_week_actual_sales(self, skc):
575
+ first_day, last_day = TimeUtils.get_past_7_days_range()
576
+ cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
577
+ if datetime.now().hour >= 9:
578
+ DictSkuSalesDate = read_dict_from_file(cache_file)
579
+ else:
580
+ DictSkuSalesDate = read_dict_from_file(cache_file, 1800)
581
+ if len(DictSkuSalesDate) > 0:
582
+ return DictSkuSalesDate
583
+
584
+ url = f"https://sso.geiwohuo.com/idms/sale-trend/detail"
585
+ payload = {
586
+ "skc" : skc,
587
+ "startDate": first_day,
588
+ "endDate" : last_day,
589
+ "daysToAdd": 0
590
+ }
591
+ response_text = fetch(self.web_page, url, payload)
592
+ error_code = response_text.get('code')
593
+ if str(error_code) != '0':
594
+ log(response_text)
595
+ return {}
596
+ list_item = response_text['info']['salesVolumeDateVoList']
597
+ for item in list_item:
598
+ key = item['date']
599
+ DictSkuSalesDate[key] = item['salesVolumeMap']
600
+ list_item2 = response_text['info']['actualSalesVolumeMap']
601
+ for item in list_item2:
602
+ sku = item['skuCode']
603
+ if sku is not None:
604
+ DictSkuSalesDate[sku] = item['actualSalesVolume']
605
+
606
+ write_dict_to_file(cache_file, DictSkuSalesDate)
607
+ return DictSkuSalesDate
608
+
609
+ def get_preemption_list(self, skc_list):
610
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/preemption-num"
611
+ payload = skc_list
612
+ response_text = fetch(self.web_page, url, payload)
613
+ error_code = response_text.get('code')
614
+ if str(error_code) != '0':
615
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
616
+
617
+ dict = response_text['info']
618
+
619
+ cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
620
+ dict_preemption_num = read_dict_from_file(cache_file)
621
+ dict_preemption_num.update(dict)
622
+ write_dict_to_file(cache_file, dict_preemption_num)
623
+
624
+ return dict
625
+
626
+ def get_activity_label(self, skc_list):
627
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/activity-label"
628
+ payload = skc_list
629
+ response_text = fetch(self.web_page, url, payload)
630
+ error_code = response_text.get('code')
631
+ if str(error_code) != '0':
632
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
633
+ dict = response_text['info']
634
+
635
+ cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
636
+ dict_label = read_dict_from_file(cache_file)
637
+ dict_label.update(dict)
638
+ write_dict_to_file(cache_file, dict_label)
639
+
640
+ return dict
641
+
642
+ def get_sku_price_v2(self, skc_list):
643
+ log(f'获取sku价格列表', skc_list)
644
+ url = "https://sso.geiwohuo.com/idms/goods-skc/price"
645
+ response_text = fetch(self.web_page, url, skc_list)
646
+ error_code = response_text.get('code')
647
+ if str(error_code) != '0':
648
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
649
+ dict = response_text['info']
650
+
651
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
652
+ dict_sku_price = read_dict_from_file(cache_file)
653
+ dict_sku_price.update(dict)
654
+ write_dict_to_file(cache_file, dict_sku_price)
655
+
656
+ return dict
657
+
658
+ def get_stock_advice(self, skc_list):
659
+ log(f'获取sku库存建议列表', skc_list)
660
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/get-vmi-spot-advice"
661
+ payload = skc_list
662
+ response_text = fetch(self.web_page, url, payload)
663
+ error_code = response_text.get('code')
664
+ if str(error_code) != '0':
665
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
666
+ dict = response_text['info']
667
+
668
+ cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
669
+ dict_advice = read_dict_from_file(cache_file)
670
+ dict_advice.update(dict)
671
+ write_dict_to_file(cache_file, dict_advice)
672
+
673
+ return dict
674
+
675
+ def get_dt_time(self):
676
+ if self.dt is not None:
677
+ log(f'字典dt: {self.dt}')
678
+ return self.dt
679
+ log('获取非实时更新时间')
680
+ url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
681
+ payload = {
682
+ "pageCode": "Index",
683
+ "areaCd" : "cn"
684
+ }
685
+ response_text = fetch(self.web_page, url, payload)
686
+ error_code = response_text.get('code')
687
+ if str(error_code) != '0':
688
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
689
+ self.dt = response_text.get('info').get('dt')
690
+ log(f'dt: {self.dt}')
691
+ return self.dt
692
+
693
+ def get_dict_skc_week_trend(self):
694
+ page_num = 1
695
+ page_size = 100
696
+
697
+ date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
698
+ log('-7', date_7_days_ago)
699
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
700
+ log('-1', date_1_days_ago)
701
+
702
+ url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_list"
703
+ payload = {
704
+ "areaCd" : "cn",
705
+ "countrySite": [
706
+ "shein-all"
707
+ ],
708
+ "startDate" : date_7_days_ago,
709
+ "endDate" : date_1_days_ago,
710
+ "pageNum" : page_num,
711
+ "pageSize" : page_size
712
+ }
713
+ response_text = fetch(self.web_page, url, payload)
714
+ error_code = response_text.get('code')
715
+ if str(error_code) != '0':
716
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
717
+ spu_list = response_text['info']['data']
718
+ total = response_text['info']['meta']['count']
719
+ totalPage = math.ceil(total / page_size)
720
+
721
+ for page in range(2, totalPage + 1):
722
+ log(f'获取商品列表 第{page}/{totalPage}页')
723
+ page_num = page
724
+ payload = {
725
+ "areaCd" : "cn",
726
+ "countrySite": [
727
+ "shein-all"
728
+ ],
729
+ "startDate" : date_7_days_ago,
730
+ "endDate" : date_1_days_ago,
731
+ "pageNum" : page_num,
732
+ "pageSize" : page_size
733
+ }
734
+ response_text = fetch(self.web_page, url, payload)
735
+ spu_list_new = response_text['info']['data']
736
+ spu_list += spu_list_new
737
+ time.sleep(0.3)
738
+
739
+ DictSkcWeekTrend = {}
740
+ for spu_item in spu_list:
741
+ skc = str(spu_item['skc'])
742
+ DictSkcWeekTrend[skc] = spu_item
743
+
744
+ log('len(DictSkcWeekTrend)', len(DictSkcWeekTrend))
745
+ write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json', DictSkcWeekTrend)
746
+ return DictSkcWeekTrend
747
+
748
+ def get_skc_sales(self, skc, start_date, end_date):
749
+ url = "https://sso.geiwohuo.com/idms/stockadvice/saleTrendDetail"
750
+ payload = {
751
+ "skc" : skc,
752
+ "startDate": start_date,
753
+ "endDate" : end_date
754
+ }
755
+ response_text = fetch(self.web_page, url, payload)
756
+ error_code = response_text.get('code')
757
+ error_msg = response_text.get('msg')
758
+ if str(error_code) != '0':
759
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
760
+
761
+ sales_list = response_text['info']['saleTrendDetailList']
762
+ if sales_list:
763
+ for skc_list in sales_list:
764
+ date = skc_list['date']
765
+ if date == '合计':
766
+ log('无销量skc: ', skc)
767
+ continue
768
+ skc_sale = skc_list['skcSale']
769
+ skc_order = skc_list['skcOrder']
770
+ for sku_list in skc_list['skuSaleTrendDetailList']:
771
+ sku = sku_list['skuCode']
772
+ attr_name = sku_list['attributeName']
773
+ sku_sale = sku_list['skuSale']
774
+ sku_order = sku_list['skuOrder']
775
+ if sku_sale > 0:
776
+ insert_sales(skc, date, skc_sale, skc_order, sku, attr_name, sku_sale, sku_order)
777
+ return sales_list
778
+
779
+ # 获取商品包含sku销量的列表
780
+ def get_product_sku_sales_list(self, source='mb'):
781
+ log(f'获取销量列表')
782
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
783
+ pageNumber = 1
784
+ pageSize = 100
785
+ dictPayload = {
786
+ "pageNumber" : pageNumber,
787
+ "pageSize" : pageSize,
788
+ "supplierCodes" : "",
789
+ "skcs" : "",
790
+ "spu" : "",
791
+ "c7dSaleCntBegin" : "",
792
+ "c7dSaleCntEnd" : "",
793
+ "goodsLevelIdList" : [],
794
+ "supplyStatus" : "",
795
+ "shelfStatus" : 1,
796
+ "categoryIdList" : [],
797
+ "skcStockBegin" : "",
798
+ "skcStockEnd" : "",
799
+ "skuStockBegin" : "",
800
+ "skuStockEnd" : "",
801
+ "skcSaleDaysBegin" : "",
802
+ "skcSaleDaysEnd" : "",
803
+ "skuSaleDaysBegin" : "",
804
+ "skuSaleDaysEnd" : "",
805
+ "planUrgentCountBegin" : "",
806
+ "planUrgentCountEnd" : "",
807
+ "skcAvailableOrderBegin": "",
808
+ "skcAvailableOrderEnd" : "",
809
+ "skuAvailableOrderBegin": "",
810
+ "skuAvailableOrderEnd" : "",
811
+ "shelfDateBegin" : "",
812
+ "shelfDateEnd" : "",
813
+ "stockWarnStatusList" : [],
814
+ "labelFakeIdList" : [],
815
+ "sheinSaleByInventory" : "",
816
+ "tspIdList" : [],
817
+ "adviceStatus" : [],
818
+ "sortBy7dSaleCnt" : 2
819
+ }
820
+ payload = dictPayload
821
+ response_text = fetch(self.web_page, url, payload)
822
+ error_code = response_text.get('code')
823
+ if str(error_code) != '0':
824
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
825
+
826
+ spu_list = response_text['info']['list']
827
+
828
+ total = response_text['info']['count']
829
+ totalPage = math.ceil(total / pageSize)
830
+ for page in range(2, totalPage + 1):
831
+ log(f'获取SKU销量列表 第{page}/{totalPage}页')
832
+ dictPayload['pageNumber'] = page
833
+ payload = dictPayload
834
+ response_text = fetch(self.web_page, url, payload)
835
+ spu_list_new = response_text['info']['list']
836
+ spu_list += spu_list_new
837
+ time.sleep(0.3)
838
+
839
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
840
+ dict_sku_price = read_dict_from_file(cache_file)
841
+
842
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json'
843
+ DictSkcWeekTrend = read_dict_from_file(cache_file2)
844
+
845
+ cache_file3 = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
846
+ DictSpuInfo = read_dict_from_file(cache_file3)
847
+
848
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
849
+ dictActivityPrice = read_dict_from_file(cache_file)
850
+
851
+ product_sku_list = [
852
+ [
853
+ '店铺名称', '商品信息', 'SKC图片', 'SKU图片', 'SKU', 'SKU货号', '在售天数', '库存(模式/本地/在途/希音)',
854
+ '今天销量', '今日订单数', # 9
855
+ '远7天销量', '远7天订单数', '近7天销量', '近7天订单数', '周销增量', '远30天销量', '远30天订单数',
856
+ '近30天销量', '近30天订单数', '月销增量', '总销量', # 11
857
+ '申报价', '成本价', '毛利润', '毛利率', '近7天利润', '近30天利润', # 6
858
+ 'SPU', 'SKC', 'SKC货号', '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '商品标题', '叶子类目', # 5
859
+ 'SKC近7天销量', 'SKC近7天曝光人数', 'SKC近7天商详访客', 'SKC近7天点击率', 'SKC近7天支付人数',
860
+ 'SKC近7天支付率', 'SKC近7天评论数'
861
+ ]
862
+ ]
863
+
864
+ date_60_days_ago = TimeUtils.get_past_nth_day(60, None, '%Y-%m-%d')
865
+ log('-60', date_60_days_ago)
866
+ date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y-%m-%d')
867
+ log('-7', date_7_days_ago)
868
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y-%m-%d')
869
+ log('-1', date_1_days_ago)
870
+
871
+ count = 0
872
+ for spu_info in spu_list:
873
+ count += 1
874
+ # if count > 10:
875
+ # break
876
+ spu = spu_info['spu']
877
+ skc = str(spu_info['skc'])
878
+ # if not shein_db.exists_sales_1_days_ago(skc):
879
+ # log(f'未查到昨天销量: {skc}')
880
+ self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
881
+ skcCode = spu_info['supplierCode']
882
+ product_name = DictSpuInfo[spu]['product_name_en']
883
+ category_name = spu_info['categoryName']
884
+ shelfDays = spu_info['shelfDays']
885
+ shelf_status = DictSpuInfo[spu]['shelf_status']
886
+ dictStatus = {
887
+ 'WAIT_SHELF': "待上架",
888
+ 'ON_SHELF' : "已上架",
889
+ 'SOLD_OUT' : "已售罄",
890
+ 'OUT_SHELF' : "已下架"
891
+ }
892
+ status_cn = dictStatus[shelf_status]
893
+ good_level = spu_info['goodsLevel']['name']
894
+ sale_model = spu_info['saleModel']['name']
895
+
896
+ for sku_info in spu_info['skuList']:
897
+ sku = sku_info['skuCode']
898
+ skuExtCode = str(sku_info['supplierSku'])
899
+ if sku == '合计':
900
+ continue
901
+
902
+ # 获取基础数据
903
+ stock = self.bridge.get_sku_stock(skuExtCode, source)
904
+ cost_price = self.bridge.get_sku_cost(skuExtCode, source)
905
+ sku_img = self.bridge.get_sku_img(skuExtCode, source)
906
+
907
+ # 计算库存相关数据
908
+ shein_stock = sku_info['stock']
909
+ transit = sku_info['transit'] # 在途
910
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
911
+
912
+ # 获取销量数据
913
+ week_sales = get_last_week_sales(sku)
914
+ week_sales2 = get_near_week_sales(sku)
915
+ month_sales = get_last_month_sales(sku)
916
+ month_sales2 = get_near_month_sales(sku)
917
+
918
+ # 获取SKC趋势数据
919
+ key = str(skc)
920
+ skc_trend = DictSkcWeekTrend.get(key, {})
921
+
922
+ # 使用append组装数据
923
+ sku_item = []
924
+
925
+ # 店铺名称
926
+ sku_item.append(f'{self.store_name}\n({status_cn})\n{good_level}\n{date_7_days_ago}\n{date_1_days_ago}')
927
+
928
+ # 商品信息
929
+ product_info = f"SPU: {spu}\nSKC: {skc}\nSKC货号: {skcCode}\n类目: {category_name}\n在售天数: {shelfDays}"
930
+ sku_item.append(product_info)
931
+
932
+ # SKC图片
933
+ sku_item.append(spu_info['picUrl'])
934
+
935
+ # SKU图片
936
+ sku_item.append(sku_img)
937
+
938
+ # SKU基本信息
939
+ sku_item.append(sku) # SKU
940
+ sku_item.append(f"{sku_info['supplierSku']}") # SKU货号
941
+ sku_item.append(shelfDays) # 在售天数
942
+
943
+ # 库存信息
944
+ sku_item.append(f'{sale_model}\n{stock}/{real_transit}/{shein_stock}')
945
+
946
+ # 今日销量数据
947
+ sku_item.append(sku_info['totalSaleVolume']) # 今日销量
948
+ sku_item.append(sku_info['orderCnt']) # 今日订单数
949
+
950
+ # 远7天销量数据
951
+ sku_item.append(week_sales[0]) # 远7日销量
952
+ sku_item.append(week_sales[1]) # 远7日订单数
953
+
954
+ # 近7天销量数据
955
+ sku_item.append(week_sales2[0]) # 近7日销量
956
+ sku_item.append(week_sales2[1]) # 近7日订单数
957
+ sku_item.append(week_sales2[1] - week_sales2[0]) # 周增销量
958
+
959
+ # 远30天销量数据
960
+ sku_item.append(month_sales[0]) # 远30日销量
961
+ sku_item.append(month_sales[1]) # 远30日订单数
962
+
963
+ # 近30天销量数据
964
+ sku_item.append(month_sales2[0]) # 近30日销量
965
+ sku_item.append(month_sales2[1]) # 近30日订单数
966
+ sku_item.append(month_sales2[1] - month_sales2[0]) # 月增销量
967
+
968
+ # 总销量
969
+ sku_item.append('-')
970
+
971
+ # 价格相关
972
+ sku_item.append(dict_sku_price[sku]) # 申报价
973
+ sku_item.append(cost_price) # 成本价
974
+ sku_item.append('') # 毛利润
975
+ sku_item.append('') # 毛利率
976
+ sku_item.append('') # 近7天利润
977
+ sku_item.append('') # 近30天利润
978
+
979
+ # 商品标识
980
+ sku_item.append(spu) # SPU
981
+ sku_item.append(skc) # SKC
982
+ sku_item.append(spu_info['supplierCode']) # SKC货号
983
+
984
+ sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
985
+ sku_item.append("\n".join(sale_num_list))
986
+ sku_item.append("\n".join(sale_data_list))
987
+ sku_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
988
+
989
+ sku_item.append(product_name) # 商品标题
990
+ sku_item.append(category_name) # 叶子类目
991
+
992
+ # SKC趋势数据
993
+ sku_item.append(skc_trend.get('saleCnt', 0)) # SKC近7天销量
994
+ sku_item.append(skc_trend.get('epsUvIdx', 0)) # SKC近7天曝光人数
995
+ sku_item.append(skc_trend.get('goodsUvIdx', 0)) # SKC近7天商详访客
996
+ sku_item.append(skc_trend.get('epsGdsCtrIdx', 0)) # SKC近7天点击率
997
+ sku_item.append(skc_trend.get('payUvIdx', 0)) # SKC近7天支付人数
998
+ sku_item.append(skc_trend.get('gdsPayCtrIdx', 0)) # SKC近7天支付率
999
+ sku_item.append(skc_trend.get('totalCommentCnt', 0)) # 评论数
1000
+
1001
+ product_sku_list.append(sku_item)
1002
+
1003
+ cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
1004
+ write_dict_to_file_ex(cache_file, {self.store_name: product_sku_list}, [self.store_name])
1005
+
1006
+ return product_sku_list
1007
+
1008
+ # 获取一个skc一周内的销售趋势(商品明细中的)
1009
+ def get_dict_skc_week_trend_v2(self, spu, skc):
1010
+ dt = self.get_dt_time()
1011
+
1012
+ date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
1013
+ log('-7', date_7_days_ago)
1014
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
1015
+ log('-1', date_1_days_ago)
1016
+
1017
+ cache_file = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{skc}_{date_7_days_ago}_{date_1_days_ago}.json'
1018
+ if datetime.now().hour >= 9:
1019
+ DictSkc = read_dict_from_file(cache_file)
1020
+ else:
1021
+ DictSkc = read_dict_from_file(cache_file, 1800)
1022
+ if len(DictSkc) > 0:
1023
+ return DictSkc
1024
+
1025
+ url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_trend"
1026
+ payload = {
1027
+ "areaCd" : "cn",
1028
+ "countrySite": [
1029
+ "shein-all"
1030
+ ],
1031
+ "dt" : dt,
1032
+ "endDate" : date_1_days_ago,
1033
+ "spu" : [spu],
1034
+ "skc" : [skc],
1035
+ "startDate" : date_7_days_ago,
1036
+ }
1037
+ response_text = fetch(self.web_page, url, payload)
1038
+ error_code = response_text.get('code')
1039
+ if str(error_code) != '0':
1040
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1041
+
1042
+ data_list = response_text['info']
1043
+ DictSkc = {}
1044
+ for date_item in data_list:
1045
+ dataDate = date_item['dataDate']
1046
+ # epsUvIdx = date_item['epsUvIdx']
1047
+ # saleCnt = date_item['saleCnt']
1048
+ DictSkc[dataDate] = date_item
1049
+
1050
+ log('len(DictSkc)', len(DictSkc))
1051
+ write_dict_to_file(cache_file, DictSkc)
1052
+ return DictSkc
1053
+
1054
+ def get_skc_week_sale_list(self, spu, skc, sku):
1055
+ dict_skc = self.get_dict_skc_week_trend_v2(spu, skc)
1056
+ date_list = TimeUtils.get_past_7_days_list()
1057
+ first_day, last_day = TimeUtils.get_past_7_days_range()
1058
+ cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
1059
+ DictSkuSalesDate = read_dict_from_file(cache_file)
1060
+ sales_detail = []
1061
+ for date in date_list:
1062
+ sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
1063
+ sales_num = sales_num if sales_num is not None else 0
1064
+
1065
+ saleCnt = get_safe_value(dict_skc.get(date, {}), 'saleCnt', 0)
1066
+ epsUvIdx = get_safe_value(dict_skc.get(date, {}), 'epsUvIdx', 0)
1067
+
1068
+ sales_detail.append(f'{date}({TimeUtils.get_weekday_name(date)}): {sales_num}/{saleCnt}/{epsUvIdx}')
1069
+
1070
+ sales_data = []
1071
+ for date in date_list:
1072
+ goodsUvIdx = get_safe_value(dict_skc.get(date, {}), 'goodsUvIdx', 0) # 商详访客
1073
+ epsGdsCtrIdx = get_safe_value(dict_skc.get(date, {}), 'epsGdsCtrIdx', 0) # 点击率
1074
+
1075
+ payUvIdx = get_safe_value(dict_skc.get(date, {}), 'payUvIdx', 0) # 支付人数
1076
+ gdsPayCtrIdx = get_safe_value(dict_skc.get(date, {}), 'gdsPayCtrIdx', 0) # 转化率
1077
+
1078
+ sales_data.append(f'{date}({TimeUtils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
1079
+
1080
+ return sales_detail, sales_data
1081
+
1082
+ def get_activity_price(self, activity_dict, sku, activity_name, dateBegin, dateEnd):
1083
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
1084
+ price_info = activity_dict.get(key, ['-', '-'])
1085
+ return f'活动价:¥{price_info[0]}, 活动库存:{price_info[1]}'
1086
+
1087
+ def get_skc_activity_label(self, skc, sku, dict_activity_price=None):
1088
+ cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
1089
+ dict_label = read_dict_from_file(cache_file)
1090
+ operateLabelList = dict_label[skc]['operateLabelList']
1091
+ activityList = []
1092
+ activityList2 = []
1093
+ for item in operateLabelList:
1094
+ if item['name'] == '活动中':
1095
+ activityList.extend(item.get('activityList', []))
1096
+ if item['name'] == '即将开始':
1097
+ activityList2.extend(item.get('activityList', []))
1098
+
1099
+ if activityList:
1100
+ activityLabel = '\n'.join([
1101
+ f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
1102
+ for act in activityList])
1103
+ else:
1104
+ activityLabel = '无'
1105
+ if activityList2:
1106
+ activityLabel2 = '\n'.join([
1107
+ f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
1108
+ for act in activityList2])
1109
+ else:
1110
+ activityLabel2 = '无'
1111
+ return f'活动中:\n{activityLabel}\n即将开始:\n{activityLabel2}'
1112
+
1113
+ # 获取商品包含sku销量的列表
1114
+ # mode = 1.备货建议 2.已上架 3.昨日上架 4.昨日出单
1115
+ # 5.采购-缺货要补货 (有现货建议 建议采购为正 有销量)
1116
+ # 6.运营采购-滞销清库存 (无现货建议 建议采购为负 30天外 无销量)
1117
+ # 7.运营-新品上架需要优化 (无现货建议 建议采购为负 上架15天内)
1118
+ # 8.运营-潜在滞销款 (无现货建议 30天外 有销量)
1119
+ # 9.运营-潜力热销款 (有现货建议 30天内 有销量)
1120
+ # 10.运营-热销款 (有现货建议 30天外 有销量)
1121
+ def get_bak_advice(self, mode=1, skcs=None, source='mb'):
1122
+ log(f'获取备货信息商品列表 做成字典')
1123
+ if skcs == None or len(skcs) == 0:
1124
+ # if mode == 3:
1125
+ # skcs = "sh2405133614611175" # 这是一个不存在的skc
1126
+ # else:
1127
+ skcs = ""
1128
+ else:
1129
+ skcs = ",".join(skcs)
1130
+
1131
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
1132
+ pageNumber = 1
1133
+ pageSize = 100
1134
+ dictPayload = {
1135
+ "pageNumber" : pageNumber,
1136
+ "pageSize" : pageSize,
1137
+ "supplierCodes" : "",
1138
+ "skcs" : skcs,
1139
+ "spu" : "",
1140
+ "c7dSaleCntBegin" : "",
1141
+ "c7dSaleCntEnd" : "",
1142
+ "goodsLevelIdList" : [10, 107, 61, 90, 87, 237, 220, 219, 88, 75, 62, 227, 12, 230, 80, 58, 224, 97],
1143
+ "supplyStatus" : "",
1144
+ "shelfStatus" : "",
1145
+ "categoryIdList" : [],
1146
+ "skcStockBegin" : "",
1147
+ "skcStockEnd" : "",
1148
+ "skuStockBegin" : "",
1149
+ "skuStockEnd" : "",
1150
+ "skcSaleDaysBegin" : "",
1151
+ "skcSaleDaysEnd" : "",
1152
+ "skuSaleDaysBegin" : "",
1153
+ "skuSaleDaysEnd" : "",
1154
+ "planUrgentCountBegin" : "",
1155
+ "planUrgentCountEnd" : "",
1156
+ "skcAvailableOrderBegin": "",
1157
+ "skcAvailableOrderEnd" : "",
1158
+ "skuAvailableOrderBegin": "",
1159
+ "skuAvailableOrderEnd" : "",
1160
+ "shelfDateBegin" : "",
1161
+ "shelfDateEnd" : "",
1162
+ "stockWarnStatusList" : [],
1163
+ "labelFakeIdList" : [],
1164
+ "sheinSaleByInventory" : "",
1165
+ "tspIdList" : [],
1166
+ "adviceStatus" : [],
1167
+ "sortBy7dSaleCnt" : 2,
1168
+ "goodsLevelFakeIdList" : [1, 2, 3, 8, 14, 15, 4, 11]
1169
+ }
1170
+ payload = dictPayload
1171
+ response_text = fetch(self.web_page, url, payload)
1172
+ error_code = response_text.get('code')
1173
+ if str(error_code) != '0':
1174
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1175
+
1176
+ spu_list = response_text['info']['list']
1177
+
1178
+ skc_list = [item['skc'] for item in spu_list]
1179
+ self.get_activity_label(skc_list)
1180
+ self.get_preemption_list(skc_list)
1181
+ self.get_sku_price_v2(skc_list)
1182
+ self.get_stock_advice(skc_list)
1183
+
1184
+ total = response_text['info']['count']
1185
+ totalPage = math.ceil(total / pageSize)
1186
+ for page in range(2, totalPage + 1):
1187
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
1188
+ dictPayload['pageNumber'] = page
1189
+ payload = dictPayload
1190
+ response_text = fetch(self.web_page, url, payload)
1191
+ spu_list_new = response_text['info']['list']
1192
+
1193
+ skc_list = [item['skc'] for item in spu_list_new]
1194
+ self.get_activity_label(skc_list)
1195
+ self.get_preemption_list(skc_list)
1196
+ self.get_sku_price_v2(skc_list)
1197
+ self.get_stock_advice(skc_list)
1198
+
1199
+ spu_list += spu_list_new
1200
+ time.sleep(0.3)
1201
+
1202
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
1203
+ dictActivityPrice = read_dict_from_file(cache_file)
1204
+ # cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
1205
+ # DictSpuInfo = read_dict_from_file(cache_file, 5)
1206
+ cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
1207
+ dict_preemption_num = read_dict_from_file(cache_file)
1208
+ cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
1209
+ dict_advice = read_dict_from_file(cache_file)
1210
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
1211
+ dict_sku_price = read_dict_from_file(cache_file)
1212
+ date_list = TimeUtils.get_past_7_days_list()
1213
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
1214
+ excel_data = [[
1215
+ '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '建议现货数量', '现有库存数量', '已采购数量', '预测日销',
1216
+ '本地和采购可售天数', '生产天数', '建议采购', '产品起定量',
1217
+ '备货周期(天)', '备货建议', '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动',
1218
+ 'SKC',
1219
+ "SKU"
1220
+ ]]
1221
+ else:
1222
+ excel_data = [[
1223
+ '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '备货建议', '近7天SKU销量/SKC销量/SKC曝光',
1224
+ 'SKC点击率/SKC转化率', '自主参与活动', 'SKC', "SKU"
1225
+ ]]
1226
+ for spu_info in spu_list:
1227
+ spu = str(spu_info['spu'])
1228
+ skc = str(spu_info['skc'])
1229
+
1230
+ status_cn = spu_info['shelfStatus']['name']
1231
+ if status_cn != '已上架':
1232
+ continue
1233
+
1234
+ # shelf_status = DictSpuInfo.get(spu, {}).get('shelf_status', '')
1235
+ # if mode != 1:
1236
+ # if shelf_status != 'ON_SHELF' and shelf_status != 'SOLD_OUT':
1237
+ # log('跳过', skc, shelf_status)
1238
+ # continue
1239
+
1240
+ # if mode in [5, 6, 7, 8, 9, 10] and shelf_status == 'SOLD_OUT':
1241
+ # continue
1242
+ #
1243
+ # dictStatus = {
1244
+ # 'WAIT_SHELF': "待上架",
1245
+ # 'ON_SHELF': "已上架",
1246
+ # 'SOLD_OUT': "已售罄",
1247
+ # 'OUT_SHELF': "已下架"
1248
+ # }
1249
+ # status_cn = dictStatus.get(shelf_status, '-')
1250
+
1251
+ sale_model = spu_info['saleModel']['name']
1252
+ goods_level = spu_info['goodsLevel']['name']
1253
+ goods_label = [label["name"] for label in spu_info['goodsLabelList']]
1254
+ skc_img = spu_info['picUrl']
1255
+ shelfDate = spu_info['shelfDate']
1256
+ shelfDays = spu_info['shelfDays']
1257
+ categoryName = spu_info['categoryName']
1258
+
1259
+ if mode in [3] and shelfDays != 1:
1260
+ continue
1261
+
1262
+ DictSkuSalesDate = self.get_skc_week_actual_sales(skc)
1263
+
1264
+ for sku_info in spu_info['skuList']:
1265
+ row_item = []
1266
+ attr = sku_info['attr']
1267
+ if attr == '合计':
1268
+ continue
1269
+ predictDaySales = sku_info['predictDaySales']
1270
+ availableOrderCount = sku_info['availableOrderCount']
1271
+ if mode == 1:
1272
+ if availableOrderCount is None or availableOrderCount <= 0:
1273
+ log('跳过', skc, availableOrderCount)
1274
+ continue
1275
+
1276
+ row_item.append(f'{self.store_name}\n({status_cn})\n{goods_level}\n{",".join(goods_label)}')
1277
+ row_item.append(skc_img)
1278
+ sku = sku_info['skuCode']
1279
+ skuExtCode = str(sku_info['supplierSku'])
1280
+ sku_img = self.bridge.get_sku_img(skuExtCode, source)
1281
+ row_item.append(sku_img)
1282
+
1283
+ transit = sku_info['transit'] # 在途
1284
+
1285
+ stock = self.bridge.get_sku_stock(skuExtCode, source)
1286
+ cost_price = self.bridge.get_sku_cost(skuExtCode, source)
1287
+
1288
+ supplyPrice = dict_sku_price[sku]
1289
+ shein_stock = sku_info['stock']
1290
+ if cost_price == '-':
1291
+ profit = '-'
1292
+ else:
1293
+ profit = f'{float(supplyPrice) - float(cost_price):.2f}'
1294
+
1295
+ min_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('minSpotAdvice', 0)
1296
+ max_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('maxSpotAdvice', 0)
1297
+ stock_advice = f'{min_spot_advice}~{max_spot_advice}'
1298
+ log('stock_advice', stock_advice)
1299
+ # 建议现货数量
1300
+ advice_stock_number = round((min_spot_advice + max_spot_advice) / 4)
1301
+
1302
+ # 有现货建议
1303
+ if mode in [5, 9, 10] and advice_stock_number == 0:
1304
+ continue
1305
+
1306
+ # 无现货建议
1307
+ if mode in [6, 7, 8] and advice_stock_number > 0:
1308
+ continue
1309
+
1310
+ stockSaleDays = sku_info['stockSaleDays']
1311
+
1312
+ product_info = (
1313
+ f'SPU: {spu}\n'
1314
+ f'SKC: {skc}\n'
1315
+ f'SKU货号: {skuExtCode}\n'
1316
+ f'属性集: {attr}\n'
1317
+ f'商品分类: {categoryName}\n'
1318
+ f'上架日期: {shelfDate}\n'
1319
+ f'上架天数: {shelfDays}\n'
1320
+ f'库存可售天数/现货建议: {stockSaleDays}/{stock_advice}\n'
1321
+ )
1322
+ row_item.append(product_info)
1323
+
1324
+ # 建议采购数量逻辑
1325
+ try:
1326
+ # 尝试将字符串数字转换为 float,再转为 int(如有必要)
1327
+ current_stock = float(stock)
1328
+ advice_purchase_number = advice_stock_number - int(current_stock)
1329
+
1330
+ # 建议采购为正
1331
+ if (mode == 5 and advice_purchase_number <= 0):
1332
+ continue
1333
+
1334
+ except (ValueError, TypeError):
1335
+ # 无法转换为数值时
1336
+ advice_purchase_number = '-'
1337
+
1338
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
1339
+ row_item.append(advice_stock_number)
1340
+ row_item.append(stock)
1341
+
1342
+ row_item.append(0)
1343
+ row_item.append(predictDaySales)
1344
+ row_item.append(0)
1345
+ row_item.append(7)
1346
+
1347
+ row_item.append(advice_purchase_number)
1348
+ row_item.append(0) # 产品起定量
1349
+ row_item.append(0) # 备货周期()
1350
+
1351
+ adviceOrderCount = sku_info['adviceOrderCount'] if sku_info['adviceOrderCount'] is not None else '-'
1352
+ if sku_info['autoOrderStatus'] is not None:
1353
+ autoOrderStatus = ['-', '是', '否'][sku_info['autoOrderStatus']] if sku_info[
1354
+ 'adviceOrderCount'] is not None else '-'
1355
+ else:
1356
+ autoOrderStatus = '-'
1357
+ orderCount = sku_info['orderCount'] # 已下单数
1358
+ c7dSaleCnt = sku_info['c7dSaleCnt']
1359
+ c30dSaleCnt = sku_info['c30dSaleCnt']
1360
+ orderCnt = sku_info['orderCnt']
1361
+ totalSaleVolume = sku_info['totalSaleVolume']
1362
+ planUrgentCount = sku_info['planUrgentCount']
1363
+ preemptionCount = dict_preemption_num[skc][sku]
1364
+ predictDaySales = sku_info['predictDaySales']
1365
+ goodsDate = sku_info['goodsDate']
1366
+ stockDays = sku_info['stockDays']
1367
+
1368
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
1369
+
1370
+ sales_info = (
1371
+ f'近7天/30天销量: {c7dSaleCnt}/{c30dSaleCnt}\n'
1372
+ f'当天销量/购买单数: {totalSaleVolume}/{orderCnt}\n'
1373
+ f'预测日销/下单参数: {predictDaySales}/{goodsDate}+{stockDays}\n'
1374
+ f'预占数/预计急采数: {preemptionCount}/{planUrgentCount}\n'
1375
+ f'建议下单/已下单数: {adviceOrderCount}/{orderCount}\n'
1376
+ f'拟下单数/自动下单: {availableOrderCount}/{autoOrderStatus}\n'
1377
+ f'模式/本地/在途/希音: {sale_model[:2]}/{stock}/{real_transit}/{shein_stock}\n'
1378
+ f'成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n'
1379
+ )
1380
+
1381
+ row_item.append(sales_info)
1382
+
1383
+ flag_yesterday = 0
1384
+ sales7cn = 0
1385
+ for date in date_list:
1386
+ sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
1387
+ sales_num = sales_num if sales_num is not None else 0
1388
+ sales7cn += sales_num
1389
+ if TimeUtils.is_yesterday_date(date) and sales_num == 0:
1390
+ flag_yesterday = 1
1391
+
1392
+ if mode == 4 and flag_yesterday:
1393
+ continue
1394
+
1395
+ # 过滤掉未建立马帮信息的
1396
+ if mode in [5, 6, 7, 8, 9, 10] and advice_purchase_number == '-':
1397
+ continue
1398
+
1399
+ # 建议采购为正
1400
+ if mode in [5] and advice_purchase_number < 0:
1401
+ continue
1402
+
1403
+ # 建议采购为负
1404
+ if mode in [6, 7] and advice_purchase_number >= 0:
1405
+ continue
1406
+
1407
+ # 30内
1408
+ if mode in [9] and shelfDays > 31:
1409
+ continue
1410
+
1411
+ # 15天内
1412
+ if mode in [7] and shelfDays > 15:
1413
+ continue
1414
+
1415
+ # 30外
1416
+ if mode in [6, 8, 10] and shelfDays < 31:
1417
+ continue
1418
+
1419
+ # 有销量
1420
+ if mode in [5, 8, 9, 10] and sales7cn == 0:
1421
+ continue
1422
+
1423
+ # 无销量
1424
+ if mode in [6] and sales7cn > 0:
1425
+ continue
1426
+
1427
+ sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
1428
+ row_item.append("\n".join(sale_num_list))
1429
+ row_item.append("\n".join(sale_data_list))
1430
+ row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
1431
+ row_item.append(skc)
1432
+ row_item.append(sku)
1433
+ excel_data.append(row_item)
1434
+
1435
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{TimeUtils.today_date()}.json'
1436
+ write_dict_to_file_ex(cache_file, {self.store_name: excel_data}, {self.store_name})
1437
+
1438
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{TimeUtils.today_date()}.json'
1439
+ NotifyItem = [self.store_name, len(excel_data[1:])]
1440
+ write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
1441
+
1442
+ return excel_data