qrpa 1.0.12__tar.gz → 1.0.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.12
3
+ Version: 1.0.14
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qrpa"
7
- version = "1.0.12"
7
+ version = "1.0.14"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -11,4 +11,7 @@ from .fun_file import read_dict_from_file, read_dict_from_file_ex, write_dict_to
11
11
  from .fun_file import get_progress_json_ex, check_progress_json_ex, done_progress_json_ex
12
12
 
13
13
  from .fun_web import fetch, fetch_via_iframe, find_all_iframe, full_screen_shot
14
- from .fun_win import *
14
+ from .fun_win import *
15
+
16
+ from .shein_excel import SheinExcel
17
+ from .shein_lib import SheinLib
@@ -0,0 +1,81 @@
1
+ from .fun_excel import *
2
+ from .fun_base import log
3
+ from .fun_file import read_dict_from_file, read_dict_from_file_ex, write_dict_to_file, write_dict_to_file_ex
4
+ from .time_utils import TimeUtils
5
+
6
+ class SheinExcel:
7
+
8
+ def __init__(self, config):
9
+ self.config = config
10
+ pass
11
+
12
+ def format_bak_advice(self, excel_path, sheet_name, mode):
13
+ app, wb, sheet = open_excel(excel_path, sheet_name)
14
+ beautify_title(sheet)
15
+ add_borders(sheet)
16
+ column_to_left(sheet,
17
+ ["商品信息", "备货建议", "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率",
18
+ "自主参与活动"])
19
+ autofit_column(sheet, ['店铺名称', '商品信息', '备货建议', "近7天SKU销量/SKC销量/SKC曝光",
20
+ "SKC点击率/SKC转化率",
21
+ "自主参与活动"])
22
+
23
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
24
+ format_to_number(sheet, ['本地和采购可售天数'], 1)
25
+ add_formula_for_column(sheet, '本地和采购可售天数', '=IF(H2>0, (F2+G2)/H2,0)')
26
+ add_formula_for_column(sheet, '建议采购', '=IF(I2 > J2,0,E2)')
27
+
28
+ colorize_by_field(app, wb, sheet, 'SKC')
29
+ specify_column_width(sheet, ['商品信息'], 180 / 6)
30
+ InsertImageV2(app, wb, sheet, ['SKC图片', 'SKU图片'])
31
+ wb.save()
32
+ close_excel(app, wb)
33
+
34
+ def write_bak_advice(self, mode_list):
35
+ excel_path_list = [
36
+ [1, self.config.Excel_Bak_Advise],
37
+ [2, self.config.Excel_Purchase_Advise2],
38
+ [3, self.config.Excel_Product_On_Shelf_Yesterday],
39
+ [4, f'{self.config.auto_dir}/shein/昨日出单/昨日出单(#len#)_#store_name#_{TimeUtils.today_date()}.xlsx'],
40
+ [5, self.config.Excel_Purchase_Advise],
41
+ [6, self.config.Excel_Purchase_Advise6],
42
+ [7, self.config.Excel_Purchase_Advise7],
43
+ [8, self.config.Excel_Purchase_Advise8],
44
+ [9, self.config.Excel_Purchase_Advise9],
45
+ [10, self.config.Excel_Purchase_Advise10],
46
+ ]
47
+ mode_excel_path_list = [row for row in excel_path_list if row[0] in mode_list]
48
+ new_excel_path_list = []
49
+ for mode, excel_path in mode_excel_path_list:
50
+ summary_excel_data = []
51
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{TimeUtils.today_date()}.json'
52
+ dict = read_dict_from_file(cache_file)
53
+ header = []
54
+ new_excel_path = excel_path
55
+ for store_name, excel_data in dict.items():
56
+ sheet_name = store_name
57
+ # 处理每个店铺的数据
58
+
59
+ if mode in [2, 4]:
60
+ new_excel_path = str(excel_path).replace('#len#', str(len(excel_data[1:])))
61
+ new_excel_path = new_excel_path.replace('#store_name#', store_name)
62
+ new_excel_path_list.append(new_excel_path)
63
+ sheet_name = 'Sheet1'
64
+
65
+ log(new_excel_path)
66
+ if mode in [2]:
67
+ excel_data = sort_by_column(excel_data, 4, 1)
68
+ write_data(new_excel_path, sheet_name, excel_data)
69
+ self.format_bak_advice(new_excel_path, sheet_name, mode)
70
+
71
+ # 是否合并表格数据
72
+ if mode in [1, 3]:
73
+ header = excel_data[0]
74
+ summary_excel_data += excel_data[1:]
75
+
76
+ if mode in [1, 3]:
77
+ sheet_name = 'Sheet1'
78
+ write_data(new_excel_path, sheet_name, [header] + summary_excel_data)
79
+ self.format_bak_advice(new_excel_path, sheet_name, mode)
80
+
81
+ return new_excel_path_list
@@ -0,0 +1,844 @@
1
+ from qrpa import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
2
+ from qrpa import log, fetch, send_exception, md5_string
3
+ from qrpa import time_utils, get_safe_value
4
+
5
+ import math
6
+ import time
7
+ import json
8
+ from datetime import datetime
9
+ from playwright.sync_api import Page
10
+
11
+ class SheinLib:
12
+
13
+ def __init__(self, config, bridge, web_page: Page, store_username, store_name):
14
+ self.config = config
15
+ self.bridge = bridge
16
+ self.store_username = store_username
17
+ self.store_name = store_name
18
+ self.web_page = web_page
19
+ self.dt = None
20
+
21
+ self.deal_auth()
22
+
23
+ # 处理鉴权
24
+ def deal_auth(self):
25
+ web_page = self.web_page
26
+ while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
27
+ if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
28
+ web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
29
+ log("鉴权确定按钮可见 点击'确定'按钮")
30
+ web_page.wait_for_load_state("load")
31
+ web_page.wait_for_timeout(1000)
32
+ # time.sleep(1)
33
+ if web_page.locator('//input[@name="username"]').is_visible():
34
+ log("用户名输入框可见 等待5秒点击'登录'按钮")
35
+ web_page.wait_for_timeout(5000)
36
+ log('点击"登录"')
37
+ web_page.locator('//button[contains(@class,"login_btn")]').click()
38
+ web_page.wait_for_load_state("load")
39
+ log('再延时5秒')
40
+ web_page.wait_for_timeout(5000)
41
+ if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
42
+ log('商品管理菜单可见 退出鉴权处理')
43
+ return
44
+ log('商家后台不可见', web_page.title(), web_page.url)
45
+ web_page.wait_for_load_state("load")
46
+ # time.sleep(1)
47
+ web_page.wait_for_timeout(1000)
48
+ if 'SHEIN全球商家中心' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
49
+ log('SHEIN全球商家中心 中断循环')
50
+ break
51
+ if '后台首页' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
52
+ log('后台首页 中断循环')
53
+ break
54
+ if '商家后台' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
55
+ log('后台首页 中断循环')
56
+ break
57
+ if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
58
+ web_page.goto('https://sso.geiwohuo.com/#/home')
59
+ web_page.wait_for_load_state("load")
60
+ web_page.wait_for_timeout(3000)
61
+ # time.sleep(3)
62
+ if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
63
+ log('检测到鉴权 刷新页面')
64
+ web_page.reload()
65
+ web_page.wait_for_load_state('load')
66
+ # time.sleep(3)
67
+ web_page.wait_for_timeout(3000)
68
+ web_page.reload()
69
+ # time.sleep(3)
70
+ web_page.wait_for_timeout(3000)
71
+ if web_page.title() == 'SHEIN':
72
+ web_page.goto('https://sso.geiwohuo.com/#/home')
73
+ web_page.wait_for_load_state("load")
74
+ # time.sleep(3)
75
+ web_page.wait_for_timeout(3000)
76
+
77
+ # web_page.goto('https://sso.geiwohuo.com')
78
+ log('鉴权处理结束')
79
+
80
+ def get_product_list(self):
81
+ self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
82
+ self.web_page.wait_for_load_state("load")
83
+
84
+ cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
85
+ DictSpuInfo = read_dict_from_file(cache_file, 3600)
86
+ if len(DictSpuInfo) > 0:
87
+ return DictSpuInfo
88
+
89
+ page_num = 1
90
+ page_size = 100
91
+
92
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
93
+ payload = {
94
+ "language" : "zh-cn",
95
+ "only_recommend_resell" : False,
96
+ "only_spmb_copy_product": False,
97
+ "search_abandon_product": False,
98
+ "sort_type" : 1
99
+ }
100
+ response_text = fetch(self.web_page, url, payload)
101
+ error_code = response_text.get('code')
102
+ if str(error_code) != '0':
103
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
104
+
105
+ spu_list = response_text['info']['data']
106
+ total = response_text['info']['meta']['count']
107
+ totalPage = math.ceil(total / page_size)
108
+
109
+ for page_num in range(2, totalPage + 1):
110
+ log(f'获取商品列表 第{page_num}/{totalPage}页')
111
+ url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num={page_num}&page_size={page_size}"
112
+ response_text = fetch(self.web_page, url, payload)
113
+ spu_list_new = response_text['info']['data']
114
+ spu_list += spu_list_new
115
+ time.sleep(0.3)
116
+
117
+ DictSkcShelf = {}
118
+ DictSkcProduct = {}
119
+ DictSpuInfo = {}
120
+ for spu_item in spu_list:
121
+ spu = spu_item['spu_name']
122
+ first_shelf_time = spu_item['first_shelf_time']
123
+ for skc_item in spu_item['skc_info_list']:
124
+ skc_name = skc_item['skc_name']
125
+ DictSkcShelf[skc_name] = first_shelf_time
126
+ DictSkcProduct[skc_name] = spu_item
127
+ DictSpuInfo[spu] = spu_item
128
+
129
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
130
+ write_dict_to_file(cache_file2, DictSkcShelf)
131
+ cache_file3 = f'{self.config.auto_dir}/dict/skc_product_{self.store_username}.json'
132
+ write_dict_to_file(cache_file3, DictSkcProduct)
133
+
134
+ write_dict_to_file(cache_file, DictSpuInfo)
135
+ return DictSpuInfo
136
+
137
+ def query_obm_activity_list(self):
138
+ page_num = 1
139
+ page_size = 100
140
+ date_60_days_ago = time_utils.get_past_nth_day(59)
141
+ cache_file = f'{self.config.auto_dir}/shein/cache/obm_activity_{self.store_name}_{date_60_days_ago}_{time_utils.today_date()}.json'
142
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
143
+ if len(list_item) > 0:
144
+ return list_item
145
+
146
+ url = f"https://sso.geiwohuo.com/mrs-api-prefix/promotion/obm/query_obm_activity_list"
147
+ payload = {
148
+ "insert_end_time" : f"{time_utils.today_date()} 23:59:59",
149
+ "insert_start_time": f"{date_60_days_ago} 00:00:00",
150
+ "page_num" : page_num,
151
+ "page_size" : page_size,
152
+ "system" : "mrs",
153
+ "time_zone" : "Asia/Shanghai",
154
+ # "state": 3, # 活动开启中 不能用这个条件
155
+ "type_id" : 31 # 限时折扣
156
+ }
157
+
158
+ response_text = fetch(self.web_page, url, payload)
159
+ error_code = response_text.get('code')
160
+ if str(error_code) != '0':
161
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
162
+
163
+ list_item = response_text['info']['data']
164
+ total = response_text['info']['meta']['count']
165
+ totalPage = math.ceil(total / page_size)
166
+
167
+ for page in range(2, totalPage + 1):
168
+ log(f'获取营销工具列表 第{page}/{totalPage}页')
169
+ payload["page_num"] = page
170
+ response_text = fetch(self.web_page, url, payload)
171
+ list_item += response_text['info']['data']
172
+ time.sleep(0.1)
173
+
174
+ write_dict_to_file(cache_file, list_item)
175
+ return list_item
176
+
177
+ def query_goods_detail(self, activity_id):
178
+ # web_page.goto(f'https://sso.geiwohuo.com/#/mrs/tools/activity/obm-time-limit-info/{activity_id}')
179
+ # web_page.wait_for_load_state('load')
180
+ log(f'正在获取 {self.store_name} {activity_id} 营销工具商品详情')
181
+
182
+ cache_file = f'{self.config.auto_dir}/shein/cache/query_goods_detail_{activity_id}.json'
183
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
184
+ if len(list_item) > 0:
185
+ return list_item
186
+
187
+ page_num = 1
188
+ page_size = 100
189
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/simple_platform/query_goods_detail"
190
+ payload = {
191
+ "activity_id": activity_id,
192
+ "page_num" : page_num,
193
+ "page_size" : page_size
194
+ }
195
+ response_text = fetch(self.web_page, url, payload)
196
+ error_code = response_text.get('code')
197
+ if str(error_code) != '0':
198
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
199
+ list_item = response_text['info']['data']
200
+ total = response_text['info']['meta']['count']
201
+ totalPage = math.ceil(total / page_size)
202
+
203
+ for page in range(2, totalPage + 1):
204
+ log(f'获取营销工具商品列表 第{page}/{totalPage}页')
205
+ payload["page_num"] = page
206
+ response_text = fetch(self.web_page, url, payload)
207
+ list_item += response_text['info']['data']
208
+ time.sleep(0.1)
209
+
210
+ write_dict_to_file(cache_file, list_item)
211
+ return list_item
212
+
213
+ def get_partake_activity_goods_list(self):
214
+ # self.web_page.goto(f'https://sso.geiwohuo.com/#/mbrs/marketing/list/1')
215
+ # self.web_page.wait_for_load_state('load')
216
+ log(f'正在获取 {self.store_name} 活动列表')
217
+ page_num = 1
218
+ page_size = 100
219
+ date_60_days_ago = time_utils.get_past_nth_day(59)
220
+ cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{self.store_name}_{date_60_days_ago}_{time_utils.today_date()}.json'
221
+ list_item = read_dict_from_file(cache_file, 3600 * 8)
222
+ if len(list_item) > 0:
223
+ return list_item
224
+
225
+ url = f"https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_partake_activity_goods_list?page_num={page_num}&page_size={page_size}"
226
+ payload = {
227
+ "goods_audit_status" : 1,
228
+ "insert_zone_time_end" : f"{time_utils.today_date()} 23:59:59",
229
+ "insert_zone_time_start": f"{date_60_days_ago} 00:00:00"
230
+ }
231
+
232
+ response_text = fetch(self.web_page, url, payload)
233
+ error_code = response_text.get('code')
234
+ if str(error_code) != '0':
235
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
236
+ list_item = response_text['info']['data']
237
+ total = response_text['info']['meta']['count']
238
+ totalPage = math.ceil(total / page_size)
239
+
240
+ for page in range(2, totalPage + 1):
241
+ log(f'获取活动列表 第{page}/{totalPage}页')
242
+ payload["page_num"] = page
243
+ response_text = fetch(self.web_page, url, payload)
244
+ list_item += response_text['info']['data']
245
+ time.sleep(0.1)
246
+
247
+ write_dict_to_file(cache_file, list_item)
248
+ return list_item
249
+
250
+ def generate_activity_price_dict(self):
251
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
252
+ dict_activity_price = {}
253
+ activity_list = self.query_obm_activity_list()
254
+ for activity in activity_list:
255
+ activity_id = activity['activity_id']
256
+ activity_name = activity['act_name']
257
+ sub_type_id = activity['sub_type_id'] # 1.不限量 2.限量
258
+ dateBegin = time_utils.convert_datetime_to_date(activity['start_time'])
259
+ dateEnd = time_utils.convert_datetime_to_date(activity['end_time'])
260
+ skc_list = self.query_goods_detail(activity_id)
261
+ for skc_item in skc_list:
262
+ attend_num_sum = skc_item['attend_num_sum']
263
+ product_act_price = skc_item['product_act_price'] # 活动价
264
+ if sub_type_id == 1:
265
+ attend_num_sum = '不限量'
266
+ for sku_item in skc_item['sku_info_list']:
267
+ sku = sku_item['sku'] # 平台sku
268
+ product_act_price = sku_item['product_act_price'] if sku_item[
269
+ 'product_act_price'] else product_act_price # 活动价
270
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
271
+ dict_activity_price[key] = [product_act_price, attend_num_sum]
272
+
273
+ platform_activity_list = self.get_partake_activity_goods_list()
274
+ for platform_activity in platform_activity_list:
275
+ activity_name = platform_activity['activity_name']
276
+ text_tag_content = platform_activity['text_tag_content']
277
+ attend_num = platform_activity['attend_num']
278
+ dateBegin = time_utils.convert_timestamp_to_date(platform_activity['start_time'])
279
+ dateEnd = time_utils.convert_timestamp_to_date(platform_activity['end_time'])
280
+ if text_tag_content != '新品':
281
+ attend_num = '-'
282
+ for sku_item in platform_activity['activity_sku_list']:
283
+ sku = sku_item['sku_code']
284
+ enroll_price = sku_item['enroll_display_str'][:-3]
285
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
286
+ dict_activity_price[key] = [enroll_price, attend_num]
287
+
288
+ write_dict_to_file(cache_file, dict_activity_price)
289
+
290
+ def get_skc_week_actual_sales(self, skc):
291
+ first_day, last_day = time_utils.TimeUtils.get_past_7_days_range()
292
+ cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
293
+ if datetime.now().hour >= 9:
294
+ DictSkuSalesDate = read_dict_from_file(cache_file)
295
+ else:
296
+ DictSkuSalesDate = read_dict_from_file(cache_file, 1800)
297
+ if len(DictSkuSalesDate) > 0:
298
+ return DictSkuSalesDate
299
+
300
+ url = f"https://sso.geiwohuo.com/idms/sale-trend/detail"
301
+ payload = {
302
+ "skc" : skc,
303
+ "startDate": first_day,
304
+ "endDate" : last_day,
305
+ "daysToAdd": 0
306
+ }
307
+ response_text = fetch(self.web_page, url, payload)
308
+ error_code = response_text.get('code')
309
+ if str(error_code) != '0':
310
+ log(response_text)
311
+ return {}
312
+ list_item = response_text['info']['salesVolumeDateVoList']
313
+ for item in list_item:
314
+ key = item['date']
315
+ DictSkuSalesDate[key] = item['salesVolumeMap']
316
+ list_item2 = response_text['info']['actualSalesVolumeMap']
317
+ for item in list_item2:
318
+ sku = item['skuCode']
319
+ if sku is not None:
320
+ DictSkuSalesDate[sku] = item['actualSalesVolume']
321
+
322
+ write_dict_to_file(cache_file, DictSkuSalesDate)
323
+ return DictSkuSalesDate
324
+
325
+ def get_preemption_list(self, skc_list):
326
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/preemption-num"
327
+ payload = skc_list
328
+ response_text = fetch(self.web_page, url, payload)
329
+ error_code = response_text.get('code')
330
+ if str(error_code) != '0':
331
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
332
+
333
+ dict = response_text['info']
334
+
335
+ cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
336
+ dict_preemption_num = read_dict_from_file(cache_file)
337
+ dict_preemption_num.update(dict)
338
+ write_dict_to_file(cache_file, dict_preemption_num)
339
+
340
+ return dict
341
+
342
+ def get_activity_label(self, skc_list):
343
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/activity-label"
344
+ payload = skc_list
345
+ response_text = fetch(self.web_page, url, payload)
346
+ error_code = response_text.get('code')
347
+ if str(error_code) != '0':
348
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
349
+ dict = response_text['info']
350
+
351
+ cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
352
+ dict_label = read_dict_from_file(cache_file)
353
+ dict_label.update(dict)
354
+ write_dict_to_file(cache_file, dict_label)
355
+
356
+ return dict
357
+
358
+ def get_sku_price_v2(self, skc_list):
359
+ log(f'获取sku价格列表', skc_list)
360
+ url = "https://sso.geiwohuo.com/idms/goods-skc/price"
361
+ response_text = fetch(self.web_page, url, skc_list)
362
+ error_code = response_text.get('code')
363
+ if str(error_code) != '0':
364
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
365
+ dict = response_text['info']
366
+
367
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
368
+ dict_sku_price = read_dict_from_file(cache_file)
369
+ dict_sku_price.update(dict)
370
+ write_dict_to_file(cache_file, dict_sku_price)
371
+
372
+ return dict
373
+
374
+ def get_stock_advice(self, skc_list):
375
+ log(f'获取sku库存建议列表', skc_list)
376
+ url = f"https://sso.geiwohuo.com/idms/goods-skc/get-vmi-spot-advice"
377
+ payload = skc_list
378
+ response_text = fetch(self.web_page, url, payload)
379
+ error_code = response_text.get('code')
380
+ if str(error_code) != '0':
381
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
382
+ dict = response_text['info']
383
+
384
+ cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
385
+ dict_advice = read_dict_from_file(cache_file)
386
+ dict_advice.update(dict)
387
+ write_dict_to_file(cache_file, dict_advice)
388
+
389
+ return dict
390
+
391
+ def get_dt_time(self):
392
+ if self.dt is not None:
393
+ log(f'字典dt: {self.dt}')
394
+ return self.dt
395
+ log('获取非实时更新时间')
396
+ url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
397
+ payload = {
398
+ "pageCode": "Index",
399
+ "areaCd" : "cn"
400
+ }
401
+ response_text = fetch(self.web_page, url, payload)
402
+ error_code = response_text.get('code')
403
+ if str(error_code) != '0':
404
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
405
+ self.dt = response_text.get('info').get('dt')
406
+ log(f'dt: {self.dt}')
407
+ return self.dt
408
+
409
+ # 获取一个skc一周内的销售趋势(商品明细中的)
410
+ def get_dict_skc_week_trend_v2(self, spu, skc):
411
+ dt = self.get_dt_time()
412
+
413
+ date_7_days_ago = time_utils.TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
414
+ log('-7', date_7_days_ago)
415
+ date_1_days_ago = time_utils.TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
416
+ log('-1', date_1_days_ago)
417
+
418
+ cache_file = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{skc}_{date_7_days_ago}_{date_1_days_ago}.json'
419
+ if datetime.now().hour >= 9:
420
+ DictSkc = read_dict_from_file(cache_file)
421
+ else:
422
+ DictSkc = read_dict_from_file(cache_file, 1800)
423
+ if len(DictSkc) > 0:
424
+ return DictSkc
425
+
426
+ url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_trend"
427
+ payload = {
428
+ "areaCd" : "cn",
429
+ "countrySite": [
430
+ "shein-all"
431
+ ],
432
+ "dt" : dt,
433
+ "endDate" : date_1_days_ago,
434
+ "spu" : [spu],
435
+ "skc" : [skc],
436
+ "startDate" : date_7_days_ago,
437
+ }
438
+ response_text = fetch(self.web_page, url, payload)
439
+ error_code = response_text.get('code')
440
+ if str(error_code) != '0':
441
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
442
+
443
+ data_list = response_text['info']
444
+ DictSkc = {}
445
+ for date_item in data_list:
446
+ dataDate = date_item['dataDate']
447
+ # epsUvIdx = date_item['epsUvIdx']
448
+ # saleCnt = date_item['saleCnt']
449
+ DictSkc[dataDate] = date_item
450
+
451
+ log('len(DictSkc)', len(DictSkc))
452
+ write_dict_to_file(cache_file, DictSkc)
453
+ return DictSkc
454
+
455
+ def get_skc_week_sale_list(self, spu, skc, sku):
456
+ dict_skc = self.get_dict_skc_week_trend_v2(spu, skc)
457
+ date_list = time_utils.get_past_7_days_list()
458
+ first_day, last_day = time_utils.TimeUtils.get_past_7_days_range()
459
+ cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
460
+ DictSkuSalesDate = read_dict_from_file(cache_file)
461
+ sales_detail = []
462
+ for date in date_list:
463
+ sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
464
+ sales_num = sales_num if sales_num is not None else 0
465
+
466
+ saleCnt = get_safe_value(dict_skc.get(date, {}), 'saleCnt', 0)
467
+ epsUvIdx = get_safe_value(dict_skc.get(date, {}), 'epsUvIdx', 0)
468
+
469
+ sales_detail.append(f'{date}({time_utils.get_weekday_name(date)}): {sales_num}/{saleCnt}/{epsUvIdx}')
470
+
471
+ sales_data = []
472
+ for date in date_list:
473
+ goodsUvIdx = get_safe_value(dict_skc.get(date, {}), 'goodsUvIdx', 0) # 商详访客
474
+ epsGdsCtrIdx = get_safe_value(dict_skc.get(date, {}), 'epsGdsCtrIdx', 0) # 点击率
475
+
476
+ payUvIdx = get_safe_value(dict_skc.get(date, {}), 'payUvIdx', 0) # 支付人数
477
+ gdsPayCtrIdx = get_safe_value(dict_skc.get(date, {}), 'gdsPayCtrIdx', 0) # 转化率
478
+
479
+ sales_data.append(f'{date}({time_utils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
480
+
481
+ return sales_detail, sales_data
482
+
483
+ def get_activity_price(self, activity_dict, sku, activity_name, dateBegin, dateEnd):
484
+ key = f'{sku}_{dateBegin}_{dateEnd}_{activity_name}'
485
+ price_info = activity_dict.get(key, ['-', '-'])
486
+ return f'活动价:¥{price_info[0]}, 活动库存:{price_info[1]}'
487
+
488
+ def get_skc_activity_label(self, skc, sku, dict_activity_price=None):
489
+ cache_file = f'{self.config.auto_dir}/shein/activity_label/activity_label_{self.store_username}.json'
490
+ dict_label = read_dict_from_file(cache_file)
491
+ operateLabelList = dict_label[skc]['operateLabelList']
492
+ activityList = []
493
+ activityList2 = []
494
+ for item in operateLabelList:
495
+ if item['name'] == '活动中':
496
+ activityList.extend(item.get('activityList', []))
497
+ if item['name'] == '即将开始':
498
+ activityList2.extend(item.get('activityList', []))
499
+
500
+ if activityList:
501
+ activityLabel = '\n'.join([
502
+ f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
503
+ for act in activityList])
504
+ else:
505
+ activityLabel = '无'
506
+ if activityList2:
507
+ activityLabel2 = '\n'.join([
508
+ f' [{act["date"]}]\n【{self.get_activity_price(dict_activity_price, sku, act["name"], act["dateBegin"], act["dateEnd"])}】{act["name"]}\n'
509
+ for act in activityList2])
510
+ else:
511
+ activityLabel2 = '无'
512
+ return f'活动中:\n{activityLabel}\n即将开始:\n{activityLabel2}'
513
+
514
+ # 获取商品包含sku销量的列表
515
+ # mode = 1.备货建议 2.已上架 3.昨日上架 4.昨日出单
516
+ # 5.采购-缺货要补货 (有现货建议 建议采购为正 有销量)
517
+ # 6.运营采购-滞销清库存 (无现货建议 建议采购为负 30天外 无销量)
518
+ # 7.运营-新品上架需要优化 (无现货建议 建议采购为负 上架15天内)
519
+ # 8.运营-潜在滞销款 (无现货建议 30天外 有销量)
520
+ # 9.运营-潜力热销款 (有现货建议 30天内 有销量)
521
+ # 10.运营-热销款 (有现货建议 30天外 有销量)
522
+ def get_bak_advice(self, mode=1, skcs=None, source='mb'):
523
+ log(f'获取备货信息商品列表 做成字典')
524
+ global DictSkuInfo
525
+ if skcs == None or len(skcs) == 0:
526
+ # if mode == 3:
527
+ # skcs = "sh2405133614611175" # 这是一个不存在的skc
528
+ # else:
529
+ skcs = ""
530
+ else:
531
+ skcs = ",".join(skcs)
532
+
533
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
534
+ pageNumber = 1
535
+ pageSize = 100
536
+ dictPayload = {
537
+ "pageNumber" : pageNumber,
538
+ "pageSize" : pageSize,
539
+ "supplierCodes" : "",
540
+ "skcs" : skcs,
541
+ "spu" : "",
542
+ "c7dSaleCntBegin" : "",
543
+ "c7dSaleCntEnd" : "",
544
+ "goodsLevelIdList" : [10, 107, 61, 90, 87, 237, 220, 219, 88, 75, 62, 227, 12, 230, 80, 58, 224, 97],
545
+ "supplyStatus" : "",
546
+ "shelfStatus" : "",
547
+ "categoryIdList" : [],
548
+ "skcStockBegin" : "",
549
+ "skcStockEnd" : "",
550
+ "skuStockBegin" : "",
551
+ "skuStockEnd" : "",
552
+ "skcSaleDaysBegin" : "",
553
+ "skcSaleDaysEnd" : "",
554
+ "skuSaleDaysBegin" : "",
555
+ "skuSaleDaysEnd" : "",
556
+ "planUrgentCountBegin" : "",
557
+ "planUrgentCountEnd" : "",
558
+ "skcAvailableOrderBegin": "",
559
+ "skcAvailableOrderEnd" : "",
560
+ "skuAvailableOrderBegin": "",
561
+ "skuAvailableOrderEnd" : "",
562
+ "shelfDateBegin" : "",
563
+ "shelfDateEnd" : "",
564
+ "stockWarnStatusList" : [],
565
+ "labelFakeIdList" : [],
566
+ "sheinSaleByInventory" : "",
567
+ "tspIdList" : [],
568
+ "adviceStatus" : [],
569
+ "sortBy7dSaleCnt" : 2,
570
+ "goodsLevelFakeIdList" : [1, 2, 3, 8, 14, 15, 4, 11]
571
+ }
572
+ payload = dictPayload
573
+ response_text = fetch(self.web_page, url, payload)
574
+ error_code = response_text.get('code')
575
+ if str(error_code) != '0':
576
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
577
+
578
+ spu_list = response_text['info']['list']
579
+
580
+ skc_list = [item['skc'] for item in spu_list]
581
+ self.get_activity_label(skc_list)
582
+ self.get_preemption_list(skc_list)
583
+ self.get_sku_price_v2(skc_list)
584
+ self.get_stock_advice(skc_list)
585
+
586
+ total = response_text['info']['count']
587
+ totalPage = math.ceil(total / pageSize)
588
+ for page in range(2, totalPage + 1):
589
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
590
+ dictPayload['pageNumber'] = page
591
+ payload = dictPayload
592
+ response_text = fetch(self.web_page, url, payload)
593
+ spu_list_new = response_text['info']['list']
594
+
595
+ skc_list = [item['skc'] for item in spu_list_new]
596
+ self.get_activity_label(skc_list)
597
+ self.get_preemption_list(skc_list)
598
+ self.get_sku_price_v2(skc_list)
599
+ self.get_stock_advice(skc_list)
600
+
601
+ spu_list += spu_list_new
602
+ time.sleep(0.3)
603
+
604
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
605
+ dictActivityPrice = read_dict_from_file(cache_file)
606
+ # cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
607
+ # DictSpuInfo = read_dict_from_file(cache_file, 5)
608
+ cache_file = f'{self.config.auto_dir}/shein/preemption_num/preemption_num_{self.store_username}.json'
609
+ dict_preemption_num = read_dict_from_file(cache_file)
610
+ cache_file = f'{self.config.auto_dir}/shein/vmi_spot_advice/spot_advice_{self.store_username}.json'
611
+ dict_advice = read_dict_from_file(cache_file)
612
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
613
+ dict_sku_price = read_dict_from_file(cache_file)
614
+ date_list = time_utils.get_past_7_days_list()
615
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
616
+ excel_data = [[
617
+ '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '建议现货数量', '现有库存数量', '已采购数量', '预测日销',
618
+ '本地和采购可售天数', '生产天数', '建议采购', '产品起定量',
619
+ '备货周期(天)', '备货建议', '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动',
620
+ 'SKC',
621
+ "SKU"
622
+ ]]
623
+ else:
624
+ excel_data = [[
625
+ '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '备货建议', '近7天SKU销量/SKC销量/SKC曝光',
626
+ 'SKC点击率/SKC转化率', '自主参与活动', 'SKC', "SKU"
627
+ ]]
628
+ for spu_info in spu_list:
629
+ spu = str(spu_info['spu'])
630
+ skc = str(spu_info['skc'])
631
+
632
+ status_cn = spu_info['shelfStatus']['name']
633
+ if status_cn != '已上架':
634
+ continue
635
+
636
+ # shelf_status = DictSpuInfo.get(spu, {}).get('shelf_status', '')
637
+ # if mode != 1:
638
+ # if shelf_status != 'ON_SHELF' and shelf_status != 'SOLD_OUT':
639
+ # log('跳过', skc, shelf_status)
640
+ # continue
641
+
642
+ # if mode in [5, 6, 7, 8, 9, 10] and shelf_status == 'SOLD_OUT':
643
+ # continue
644
+ #
645
+ # dictStatus = {
646
+ # 'WAIT_SHELF': "待上架",
647
+ # 'ON_SHELF': "已上架",
648
+ # 'SOLD_OUT': "已售罄",
649
+ # 'OUT_SHELF': "已下架"
650
+ # }
651
+ # status_cn = dictStatus.get(shelf_status, '-')
652
+
653
+ sale_model = spu_info['saleModel']['name']
654
+ goods_level = spu_info['goodsLevel']['name']
655
+ goods_label = [label["name"] for label in spu_info['goodsLabelList']]
656
+ skc_img = spu_info['picUrl']
657
+ shelfDate = spu_info['shelfDate']
658
+ shelfDays = spu_info['shelfDays']
659
+ categoryName = spu_info['categoryName']
660
+
661
+ if mode in [3] and shelfDays != 1:
662
+ continue
663
+
664
+ DictSkuSalesDate = self.get_skc_week_actual_sales(skc)
665
+
666
+ for sku_info in spu_info['skuList']:
667
+ row_item = []
668
+ attr = sku_info['attr']
669
+ if attr == '合计':
670
+ continue
671
+ predictDaySales = sku_info['predictDaySales']
672
+ availableOrderCount = sku_info['availableOrderCount']
673
+ if mode == 1:
674
+ if availableOrderCount is None or availableOrderCount <= 0:
675
+ log('跳过', skc, availableOrderCount)
676
+ continue
677
+
678
+ row_item.append(f'{self.store_name}\n({status_cn})\n{goods_level}\n{",".join(goods_label)}')
679
+ row_item.append(skc_img)
680
+ sku = sku_info['skuCode']
681
+ skuExtCode = str(sku_info['supplierSku'])
682
+ sku_img = self.bridge.get_sku_img(skuExtCode, source)
683
+ row_item.append(sku_img)
684
+
685
+ transit = sku_info['transit'] # 在途
686
+
687
+ stock = self.bridge.get_sku_stock(skuExtCode, source)
688
+ cost_price = self.bridge.get_sku_cost(skuExtCode, source)
689
+
690
+ supplyPrice = dict_sku_price[sku]
691
+ shein_stock = sku_info['stock']
692
+ if cost_price == '-':
693
+ profit = '-'
694
+ else:
695
+ profit = f'{float(supplyPrice) - float(cost_price):.2f}'
696
+
697
+ min_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('minSpotAdvice', 0)
698
+ max_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('maxSpotAdvice', 0)
699
+ stock_advice = f'{min_spot_advice}~{max_spot_advice}'
700
+ log('stock_advice', stock_advice)
701
+ # 建议现货数量
702
+ advice_stock_number = round((min_spot_advice + max_spot_advice) / 4)
703
+
704
+ # 有现货建议
705
+ if mode in [5, 9, 10] and advice_stock_number == 0:
706
+ continue
707
+
708
+ # 无现货建议
709
+ if mode in [6, 7, 8] and advice_stock_number > 0:
710
+ continue
711
+
712
+ stockSaleDays = sku_info['stockSaleDays']
713
+
714
+ product_info = (
715
+ f'SPU: {spu}\n'
716
+ f'SKC: {skc}\n'
717
+ f'SKU货号: {skuExtCode}\n'
718
+ f'属性集: {attr}\n'
719
+ f'商品分类: {categoryName}\n'
720
+ f'上架日期: {shelfDate}\n'
721
+ f'上架天数: {shelfDays}\n'
722
+ f'库存可售天数/现货建议: {stockSaleDays}/{stock_advice}\n'
723
+ )
724
+ row_item.append(product_info)
725
+
726
+ # 建议采购数量逻辑
727
+ try:
728
+ # 尝试将字符串数字转换为 float,再转为 int(如有必要)
729
+ current_stock = float(stock)
730
+ advice_purchase_number = advice_stock_number - int(current_stock)
731
+
732
+ # 建议采购为正
733
+ if (mode == 5 and advice_purchase_number <= 0):
734
+ continue
735
+
736
+ except (ValueError, TypeError):
737
+ # 无法转换为数值时
738
+ advice_purchase_number = '-'
739
+
740
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
741
+ row_item.append(advice_stock_number)
742
+ row_item.append(stock)
743
+
744
+ row_item.append(0)
745
+ row_item.append(predictDaySales)
746
+ row_item.append(0)
747
+ row_item.append(7)
748
+
749
+ row_item.append(advice_purchase_number)
750
+ row_item.append(0) # 产品起定量
751
+ row_item.append(0) # 备货周期(天)
752
+
753
+ adviceOrderCount = sku_info['adviceOrderCount'] if sku_info['adviceOrderCount'] is not None else '-'
754
+ if sku_info['autoOrderStatus'] is not None:
755
+ autoOrderStatus = ['-', '是', '否'][sku_info['autoOrderStatus']] if sku_info[
756
+ 'adviceOrderCount'] is not None else '-'
757
+ else:
758
+ autoOrderStatus = '-'
759
+ orderCount = sku_info['orderCount'] # 已下单数
760
+ c7dSaleCnt = sku_info['c7dSaleCnt']
761
+ c30dSaleCnt = sku_info['c30dSaleCnt']
762
+ orderCnt = sku_info['orderCnt']
763
+ totalSaleVolume = sku_info['totalSaleVolume']
764
+ planUrgentCount = sku_info['planUrgentCount']
765
+ preemptionCount = dict_preemption_num[skc][sku]
766
+ predictDaySales = sku_info['predictDaySales']
767
+ goodsDate = sku_info['goodsDate']
768
+ stockDays = sku_info['stockDays']
769
+
770
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
771
+
772
+ sales_info = (
773
+ f'近7天/30天销量: {c7dSaleCnt}/{c30dSaleCnt}\n'
774
+ f'当天销量/购买单数: {totalSaleVolume}/{orderCnt}\n'
775
+ f'预测日销/下单参数: {predictDaySales}/{goodsDate}+{stockDays}\n'
776
+ f'预占数/预计急采数: {preemptionCount}/{planUrgentCount}\n'
777
+ f'建议下单/已下单数: {adviceOrderCount}/{orderCount}\n'
778
+ f'拟下单数/自动下单: {availableOrderCount}/{autoOrderStatus}\n'
779
+ f'模式/本地/在途/希音: {sale_model[:2]}/{stock}/{real_transit}/{shein_stock}\n'
780
+ f'成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n'
781
+ )
782
+
783
+ row_item.append(sales_info)
784
+
785
+ flag_yesterday = 0
786
+ sales7cn = 0
787
+ for date in date_list:
788
+ sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
789
+ sales_num = sales_num if sales_num is not None else 0
790
+ sales7cn += sales_num
791
+ if time_utils.is_yesterday_date(date) and sales_num == 0:
792
+ flag_yesterday = 1
793
+
794
+ if mode == 4 and flag_yesterday:
795
+ continue
796
+
797
+ # 过滤掉未建立马帮信息的
798
+ if mode in [5, 6, 7, 8, 9, 10] and advice_purchase_number == '-':
799
+ continue
800
+
801
+ # 建议采购为正
802
+ if mode in [5] and advice_purchase_number < 0:
803
+ continue
804
+
805
+ # 建议采购为负
806
+ if mode in [6, 7] and advice_purchase_number >= 0:
807
+ continue
808
+
809
+ # 30内
810
+ if mode in [9] and shelfDays > 31:
811
+ continue
812
+
813
+ # 15天内
814
+ if mode in [7] and shelfDays > 15:
815
+ continue
816
+
817
+ # 30外
818
+ if mode in [6, 8, 10] and shelfDays < 31:
819
+ continue
820
+
821
+ # 有销量
822
+ if mode in [5, 8, 9, 10] and sales7cn == 0:
823
+ continue
824
+
825
+ # 无销量
826
+ if mode in [6] and sales7cn > 0:
827
+ continue
828
+
829
+ sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
830
+ row_item.append("\n".join(sale_num_list))
831
+ row_item.append("\n".join(sale_data_list))
832
+ row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
833
+ row_item.append(skc)
834
+ row_item.append(sku)
835
+ excel_data.append(row_item)
836
+
837
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{time_utils.today_date()}.json'
838
+ write_dict_to_file_ex(cache_file, {self.store_name: excel_data}, {self.store_name})
839
+
840
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{time_utils.today_date()}.json'
841
+ NotifyItem = [self.store_name, len(excel_data[1:])]
842
+ write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
843
+
844
+ return excel_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.12
3
+ Version: 1.0.14
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -9,6 +9,8 @@ qrpa/fun_excel.py
9
9
  qrpa/fun_file.py
10
10
  qrpa/fun_web.py
11
11
  qrpa/fun_win.py
12
+ qrpa/shein_excel.py
13
+ qrpa/shein_lib.py
12
14
  qrpa/shein_ziniao.py
13
15
  qrpa/time_utils.py
14
16
  qrpa/time_utils_example.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes