qrpa 1.0.12__tar.gz → 1.0.13__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.
- {qrpa-1.0.12 → qrpa-1.0.13}/PKG-INFO +1 -1
- {qrpa-1.0.12 → qrpa-1.0.13}/pyproject.toml +1 -1
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/__init__.py +4 -1
- qrpa-1.0.13/qrpa/shein_excel.py +81 -0
- qrpa-1.0.13/qrpa/shein_lib.py +841 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa.egg-info/PKG-INFO +1 -1
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa.egg-info/SOURCES.txt +2 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/README.md +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/RateLimitedSender.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/db_migrator.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/fun_base.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/fun_excel.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/fun_file.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/fun_web.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/fun_win.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/shein_ziniao.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/time_utils.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/time_utils_example.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa/wxwork.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa.egg-info/dependency_links.txt +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/qrpa.egg-info/top_level.txt +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/setup.cfg +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/setup.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/tests/test_db_migrator.py +0 -0
- {qrpa-1.0.12 → qrpa-1.0.13}/tests/test_wxwork.py +0 -0
|
@@ -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,841 @@
|
|
|
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
|
+
DictSkuSalesDate = self.get_skc_week_actual_sales(skc)
|
|
662
|
+
|
|
663
|
+
for sku_info in spu_info['skuList']:
|
|
664
|
+
row_item = []
|
|
665
|
+
attr = sku_info['attr']
|
|
666
|
+
if attr == '合计':
|
|
667
|
+
continue
|
|
668
|
+
predictDaySales = sku_info['predictDaySales']
|
|
669
|
+
availableOrderCount = sku_info['availableOrderCount']
|
|
670
|
+
if mode == 1:
|
|
671
|
+
if availableOrderCount is None or availableOrderCount <= 0:
|
|
672
|
+
log('跳过', skc, availableOrderCount)
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
row_item.append(f'{self.store_name}\n({status_cn})\n{goods_level}\n{",".join(goods_label)}')
|
|
676
|
+
row_item.append(skc_img)
|
|
677
|
+
sku = sku_info['skuCode']
|
|
678
|
+
skuExtCode = str(sku_info['supplierSku'])
|
|
679
|
+
sku_img = self.bridge.get_sku_img(skuExtCode, source)
|
|
680
|
+
row_item.append(sku_img)
|
|
681
|
+
|
|
682
|
+
transit = sku_info['transit'] # 在途
|
|
683
|
+
|
|
684
|
+
stock = self.bridge.get_sku_stock(skuExtCode, source)
|
|
685
|
+
cost_price = self.bridge.get_sku_cost(skuExtCode, source)
|
|
686
|
+
|
|
687
|
+
supplyPrice = dict_sku_price[sku]
|
|
688
|
+
shein_stock = sku_info['stock']
|
|
689
|
+
if cost_price == '-':
|
|
690
|
+
profit = '-'
|
|
691
|
+
else:
|
|
692
|
+
profit = f'{float(supplyPrice) - float(cost_price):.2f}'
|
|
693
|
+
|
|
694
|
+
min_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('minSpotAdvice', 0)
|
|
695
|
+
max_spot_advice = dict_advice.get(skc, {}).get(sku, {}).get('maxSpotAdvice', 0)
|
|
696
|
+
stock_advice = f'{min_spot_advice}~{max_spot_advice}'
|
|
697
|
+
log('stock_advice', stock_advice)
|
|
698
|
+
# 建议现货数量
|
|
699
|
+
advice_stock_number = round((min_spot_advice + max_spot_advice) / 4)
|
|
700
|
+
|
|
701
|
+
# 有现货建议
|
|
702
|
+
if mode in [5, 9, 10] and advice_stock_number == 0:
|
|
703
|
+
continue
|
|
704
|
+
|
|
705
|
+
# 无现货建议
|
|
706
|
+
if mode in [6, 7, 8] and advice_stock_number > 0:
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
stockSaleDays = sku_info['stockSaleDays']
|
|
710
|
+
|
|
711
|
+
product_info = (
|
|
712
|
+
f'SPU: {spu}\n'
|
|
713
|
+
f'SKC: {skc}\n'
|
|
714
|
+
f'SKU货号: {skuExtCode}\n'
|
|
715
|
+
f'属性集: {attr}\n'
|
|
716
|
+
f'商品分类: {categoryName}\n'
|
|
717
|
+
f'上架日期: {shelfDate}\n'
|
|
718
|
+
f'上架天数: {shelfDays}\n'
|
|
719
|
+
f'库存可售天数/现货建议: {stockSaleDays}/{stock_advice}\n'
|
|
720
|
+
)
|
|
721
|
+
row_item.append(product_info)
|
|
722
|
+
|
|
723
|
+
# 建议采购数量逻辑
|
|
724
|
+
try:
|
|
725
|
+
# 尝试将字符串数字转换为 float,再转为 int(如有必要)
|
|
726
|
+
current_stock = float(stock)
|
|
727
|
+
advice_purchase_number = advice_stock_number - int(current_stock)
|
|
728
|
+
|
|
729
|
+
# 建议采购为正
|
|
730
|
+
if (mode == 5 and advice_purchase_number <= 0):
|
|
731
|
+
continue
|
|
732
|
+
|
|
733
|
+
except (ValueError, TypeError):
|
|
734
|
+
# 无法转换为数值时
|
|
735
|
+
advice_purchase_number = '-'
|
|
736
|
+
|
|
737
|
+
if mode in [2, 5, 6, 7, 8, 9, 10]:
|
|
738
|
+
row_item.append(advice_stock_number)
|
|
739
|
+
row_item.append(stock)
|
|
740
|
+
|
|
741
|
+
row_item.append(0)
|
|
742
|
+
row_item.append(predictDaySales)
|
|
743
|
+
row_item.append(0)
|
|
744
|
+
row_item.append(7)
|
|
745
|
+
|
|
746
|
+
row_item.append(advice_purchase_number)
|
|
747
|
+
row_item.append(0) # 产品起定量
|
|
748
|
+
row_item.append(0) # 备货周期(天)
|
|
749
|
+
|
|
750
|
+
adviceOrderCount = sku_info['adviceOrderCount'] if sku_info['adviceOrderCount'] is not None else '-'
|
|
751
|
+
if sku_info['autoOrderStatus'] is not None:
|
|
752
|
+
autoOrderStatus = ['-', '是', '否'][sku_info['autoOrderStatus']] if sku_info[
|
|
753
|
+
'adviceOrderCount'] is not None else '-'
|
|
754
|
+
else:
|
|
755
|
+
autoOrderStatus = '-'
|
|
756
|
+
orderCount = sku_info['orderCount'] # 已下单数
|
|
757
|
+
c7dSaleCnt = sku_info['c7dSaleCnt']
|
|
758
|
+
c30dSaleCnt = sku_info['c30dSaleCnt']
|
|
759
|
+
orderCnt = sku_info['orderCnt']
|
|
760
|
+
totalSaleVolume = sku_info['totalSaleVolume']
|
|
761
|
+
planUrgentCount = sku_info['planUrgentCount']
|
|
762
|
+
preemptionCount = dict_preemption_num[skc][sku]
|
|
763
|
+
predictDaySales = sku_info['predictDaySales']
|
|
764
|
+
goodsDate = sku_info['goodsDate']
|
|
765
|
+
stockDays = sku_info['stockDays']
|
|
766
|
+
|
|
767
|
+
real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
|
|
768
|
+
|
|
769
|
+
sales_info = (
|
|
770
|
+
f'近7天/30天销量: {c7dSaleCnt}/{c30dSaleCnt}\n'
|
|
771
|
+
f'当天销量/购买单数: {totalSaleVolume}/{orderCnt}\n'
|
|
772
|
+
f'预测日销/下单参数: {predictDaySales}/{goodsDate}+{stockDays}\n'
|
|
773
|
+
f'预占数/预计急采数: {preemptionCount}/{planUrgentCount}\n'
|
|
774
|
+
f'建议下单/已下单数: {adviceOrderCount}/{orderCount}\n'
|
|
775
|
+
f'拟下单数/自动下单: {availableOrderCount}/{autoOrderStatus}\n'
|
|
776
|
+
f'模式/本地/在途/希音: {sale_model[:2]}/{stock}/{real_transit}/{shein_stock}\n'
|
|
777
|
+
f'成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n'
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
row_item.append(sales_info)
|
|
781
|
+
|
|
782
|
+
flag_yesterday = 0
|
|
783
|
+
sales7cn = 0
|
|
784
|
+
for date in date_list:
|
|
785
|
+
sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
|
|
786
|
+
sales_num = sales_num if sales_num is not None else 0
|
|
787
|
+
sales7cn += sales_num
|
|
788
|
+
if time_utils.is_yesterday_date(date) and sales_num == 0:
|
|
789
|
+
flag_yesterday = 1
|
|
790
|
+
|
|
791
|
+
if mode == 4 and flag_yesterday:
|
|
792
|
+
continue
|
|
793
|
+
|
|
794
|
+
# 过滤掉未建立马帮信息的
|
|
795
|
+
if mode in [5, 6, 7, 8, 9, 10] and advice_purchase_number == '-':
|
|
796
|
+
continue
|
|
797
|
+
|
|
798
|
+
# 建议采购为正
|
|
799
|
+
if mode in [5] and advice_purchase_number < 0:
|
|
800
|
+
continue
|
|
801
|
+
|
|
802
|
+
# 建议采购为负
|
|
803
|
+
if mode in [6, 7] and advice_purchase_number >= 0:
|
|
804
|
+
continue
|
|
805
|
+
|
|
806
|
+
# 30内
|
|
807
|
+
if mode in [9] and shelfDays > 31:
|
|
808
|
+
continue
|
|
809
|
+
|
|
810
|
+
# 15天内
|
|
811
|
+
if mode in [7] and shelfDays > 15:
|
|
812
|
+
continue
|
|
813
|
+
|
|
814
|
+
# 30外
|
|
815
|
+
if mode in [6, 8, 10] and shelfDays < 31:
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
# 有销量
|
|
819
|
+
if mode in [5, 8, 9, 10] and sales7cn == 0:
|
|
820
|
+
continue
|
|
821
|
+
|
|
822
|
+
# 无销量
|
|
823
|
+
if mode in [6] and sales7cn > 0:
|
|
824
|
+
continue
|
|
825
|
+
|
|
826
|
+
sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
|
|
827
|
+
row_item.append("\n".join(sale_num_list))
|
|
828
|
+
row_item.append("\n".join(sale_data_list))
|
|
829
|
+
row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
|
|
830
|
+
row_item.append(skc)
|
|
831
|
+
row_item.append(sku)
|
|
832
|
+
excel_data.append(row_item)
|
|
833
|
+
|
|
834
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{time_utils.today_date()}.json'
|
|
835
|
+
write_dict_to_file_ex(cache_file, {self.store_name: excel_data}, {self.store_name})
|
|
836
|
+
|
|
837
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{time_utils.today_date()}.json'
|
|
838
|
+
NotifyItem = [self.store_name, len(excel_data[1:])]
|
|
839
|
+
write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
|
|
840
|
+
|
|
841
|
+
return excel_data
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|