qrpa 1.0.34__py3-none-any.whl → 1.1.50__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.
- qrpa/RateLimitedSender.py +45 -45
- qrpa/__init__.py +31 -26
- qrpa/db_migrator.py +600 -600
- qrpa/feishu_bot_app.py +267 -267
- qrpa/feishu_client.py +410 -0
- qrpa/feishu_logic.py +1443 -0
- qrpa/fun_base.py +339 -337
- qrpa/fun_excel.py +529 -61
- qrpa/fun_file.py +318 -318
- qrpa/fun_web.py +258 -148
- qrpa/fun_win.py +198 -198
- qrpa/mysql_module/__init__.py +0 -0
- qrpa/mysql_module/new_product_analysis_model.py +556 -0
- qrpa/mysql_module/shein_ledger_model.py +468 -0
- qrpa/mysql_module/shein_product_model.py +495 -0
- qrpa/mysql_module/shein_return_order_model.py +569 -0
- qrpa/mysql_module/shein_store_model.py +595 -0
- qrpa/shein_daily_report_model.py +375 -375
- qrpa/shein_excel.py +1248 -109
- qrpa/shein_lib.py +2333 -143
- qrpa/shein_mysql.py +92 -0
- qrpa/shein_sqlite.py +153 -153
- qrpa/shein_ziniao.py +529 -450
- qrpa/temu_chrome.py +56 -56
- qrpa/temu_excel.py +139 -139
- qrpa/temu_lib.py +156 -154
- qrpa/time_utils.py +87 -50
- qrpa/time_utils_example.py +243 -243
- qrpa/wxwork.py +318 -318
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/METADATA +1 -1
- qrpa-1.1.50.dist-info/RECORD +33 -0
- qrpa-1.0.34.dist-info/RECORD +0 -24
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/WHEEL +0 -0
- {qrpa-1.0.34.dist-info → qrpa-1.1.50.dist-info}/top_level.txt +0 -0
qrpa/shein_lib.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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, full_screen_shot
|
|
2
|
+
from .fun_base import log, send_exception, md5_string, get_safe_value, NetWorkIdleTimeout
|
|
3
|
+
from .fun_web import fetch, fetch_get, full_screen_shot, safe_goto
|
|
4
4
|
from .time_utils import TimeUtils
|
|
5
5
|
from .wxwork import WxWorkBot
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ from .shein_sqlite import insert_sales, get_last_week_sales, get_near_week_sales
|
|
|
8
8
|
|
|
9
9
|
import math
|
|
10
10
|
import time
|
|
11
|
-
import json
|
|
11
|
+
import json, traceback
|
|
12
12
|
from datetime import datetime
|
|
13
13
|
from playwright.sync_api import Page
|
|
14
14
|
|
|
@@ -23,14 +23,54 @@ class SheinLib:
|
|
|
23
23
|
self.store_name = store_name
|
|
24
24
|
self.web_page = web_page
|
|
25
25
|
self.dt = None
|
|
26
|
+
self.dt_goods = None
|
|
26
27
|
self.DictQueryTime = {}
|
|
27
28
|
|
|
28
29
|
self.deal_auth()
|
|
30
|
+
self.get_user()
|
|
29
31
|
|
|
30
32
|
# 处理鉴权
|
|
31
33
|
def deal_auth(self):
|
|
32
34
|
web_page = self.web_page
|
|
33
35
|
|
|
36
|
+
# 等待页面稳定并处理导航
|
|
37
|
+
for attempt in range(3):
|
|
38
|
+
try:
|
|
39
|
+
current_url = web_page.url
|
|
40
|
+
log(f"尝试获取页面信息 - URL: {current_url}", self.store_username, self.store_name)
|
|
41
|
+
|
|
42
|
+
# 检查是否在认证页面,如果是则直接跳转到目标页面
|
|
43
|
+
if '/auth/SSLS' in current_url:
|
|
44
|
+
log("检测到SSLS认证页面,直接跳转到首页", self.store_username, self.store_name)
|
|
45
|
+
web_page.goto('https://sso.geiwohuo.com/#/home', wait_until='domcontentloaded', timeout=15000)
|
|
46
|
+
web_page.wait_for_timeout(3000)
|
|
47
|
+
current_url = web_page.url
|
|
48
|
+
log(f"跳转后URL: {current_url}", self.store_username, self.store_name)
|
|
49
|
+
|
|
50
|
+
# 等待导航完成
|
|
51
|
+
web_page.wait_for_load_state("domcontentloaded", timeout=6000)
|
|
52
|
+
|
|
53
|
+
final_url = web_page.url
|
|
54
|
+
final_title = web_page.title()
|
|
55
|
+
log(f"页面稳定 - URL: {final_url}, 标题: {final_title}", self.store_username, self.store_name)
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
log(f"第{attempt + 1}次等待页面稳定失败: {e}", self.store_username, self.store_name)
|
|
60
|
+
if "crashed" in str(e) or "Target" in str(e):
|
|
61
|
+
log("页面稳定检查时崩溃,直接继续", self.store_username, self.store_name)
|
|
62
|
+
break
|
|
63
|
+
elif "destroyed" in str(e) or "navigation" in str(e):
|
|
64
|
+
log("检测到导航中断,等待导航完成", self.store_username, self.store_name)
|
|
65
|
+
web_page.wait_for_timeout(4000)
|
|
66
|
+
continue
|
|
67
|
+
elif attempt == 2:
|
|
68
|
+
log("页面稳定等待最终失败,继续执行", self.store_username, self.store_name)
|
|
69
|
+
break
|
|
70
|
+
web_page.wait_for_timeout(2000)
|
|
71
|
+
|
|
72
|
+
web_page.wait_for_timeout(2000)
|
|
73
|
+
|
|
34
74
|
# 定义最大重试次数
|
|
35
75
|
MAX_RETRIES = 5
|
|
36
76
|
retries = 0
|
|
@@ -41,16 +81,35 @@ class SheinLib:
|
|
|
41
81
|
|
|
42
82
|
while retries < MAX_RETRIES:
|
|
43
83
|
try:
|
|
44
|
-
|
|
45
84
|
retries += 1
|
|
46
85
|
|
|
47
86
|
while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
|
|
87
|
+
try:
|
|
88
|
+
current_url = web_page.url
|
|
89
|
+
current_title = web_page.title()
|
|
90
|
+
log(f"循环检查 - URL: {current_url}, 标题: {current_title}", self.store_username, self.store_name)
|
|
91
|
+
|
|
92
|
+
# 如果在认证页面且出现问题,直接跳转
|
|
93
|
+
if '/auth/SSLS' in current_url:
|
|
94
|
+
log("在主循环中检测到SSLS认证页面,跳转到首页", self.store_username, self.store_name)
|
|
95
|
+
web_page.goto('https://sso.geiwohuo.com/#/home', wait_until='domcontentloaded', timeout=15000)
|
|
96
|
+
web_page.wait_for_timeout(3000)
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
except Exception as status_error:
|
|
100
|
+
log(f"获取页面状态失败: {status_error}", self.store_username, self.store_name)
|
|
101
|
+
if "crashed" in str(status_error):
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
if web_page.locator('xpath=//div[text()="扫码登录"]').is_visible():
|
|
105
|
+
log('检查到扫码登录,切换至账号登录', self.store_username, self.store_name)
|
|
106
|
+
web_page.locator('xpath=//*[@id="container"]/div[2]/div[4]/img').click()
|
|
48
107
|
|
|
49
108
|
if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
if 'https://sso.geiwohuo.com/#/home' not in web_page.url:
|
|
110
|
+
log("鉴权确定按钮可见 点击'确定'按钮", web_page.title(), web_page.url, self.store_username, self.store_name)
|
|
111
|
+
web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
|
|
112
|
+
web_page.wait_for_timeout(5000)
|
|
54
113
|
|
|
55
114
|
while web_page.locator('//div[text()="验证码"]').is_visible():
|
|
56
115
|
log(f'等待输入验证码: {wait_count}', self.store_username, self.store_name)
|
|
@@ -62,12 +121,27 @@ class SheinLib:
|
|
|
62
121
|
time.sleep(5)
|
|
63
122
|
wait_count += 1
|
|
64
123
|
|
|
65
|
-
if web_page.locator('//
|
|
124
|
+
if web_page.locator('//div[contains(text(),"同意签署协议")]').count() > 0:
|
|
125
|
+
while web_page.locator('//div[contains(text(),"同意签署协议")]').count() == 0:
|
|
126
|
+
log('等待协议内容出现')
|
|
127
|
+
web_page.wait_for_timeout(1000)
|
|
128
|
+
|
|
129
|
+
if web_page.locator('//div[contains(text(),"同意签署协议")]').count() > 0:
|
|
130
|
+
log('检测到同意签署协议')
|
|
131
|
+
web_page.wait_for_timeout(1000)
|
|
132
|
+
log('点击同意复选框')
|
|
133
|
+
web_page.locator('//i[@class="so-checkinput-indicator so-checkinput-checkbox"]').click()
|
|
134
|
+
web_page.wait_for_timeout(1000)
|
|
135
|
+
log('点击同意按钮')
|
|
136
|
+
web_page.locator('//button[span[text()="同意"]]').click()
|
|
137
|
+
|
|
138
|
+
# //button[span[text()="登录"]]
|
|
139
|
+
if web_page.locator('//button[span[text()="登录"]]').is_visible() or web_page.locator('//input[@name="username"]').is_visible():
|
|
66
140
|
log("用户名输入框可见 等待5秒点击'登录'按钮", self.store_username, self.store_name)
|
|
67
141
|
web_page.wait_for_timeout(5000)
|
|
68
142
|
log('点击"登录"', self.store_username, self.store_name)
|
|
69
143
|
web_page.locator('//button[contains(@class,"login_btn")]').click()
|
|
70
|
-
|
|
144
|
+
|
|
71
145
|
log('再延时5秒', self.store_username, self.store_name)
|
|
72
146
|
web_page.wait_for_timeout(5000)
|
|
73
147
|
|
|
@@ -76,41 +150,90 @@ class SheinLib:
|
|
|
76
150
|
return
|
|
77
151
|
|
|
78
152
|
log('商家后台不可见', web_page.title(), web_page.url, self.store_username, self.store_name)
|
|
79
|
-
web_page.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if 'SHEIN全球商家中心' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
|
|
83
|
-
log('SHEIN全球商家中心 中断循环', self.store_username, self.store_name)
|
|
84
|
-
break
|
|
85
|
-
|
|
86
|
-
if '后台首页' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
|
|
87
|
-
log('后台首页 中断循环', self.store_username, self.store_name)
|
|
88
|
-
break
|
|
153
|
+
if 'https://sso.geiwohuo.com/#/home' in web_page.url:
|
|
154
|
+
web_page.wait_for_timeout(5000)
|
|
155
|
+
web_page.reload()
|
|
89
156
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
157
|
+
# while r'=/CN' in web_page.url:
|
|
158
|
+
# safe_goto(web_page, 'https://sso.geiwohuo.com/#/home?q=0')
|
|
159
|
+
#
|
|
160
|
+
# web_page.wait_for_timeout(5000)
|
|
161
|
+
# if web_page.locator('//input[@name="username"]').is_visible():
|
|
162
|
+
# log("用户名输入框可见 等待5秒点击'登录'按钮", self.store_username, self.store_name)
|
|
163
|
+
# web_page.wait_for_timeout(5000)
|
|
164
|
+
# log('点击"登录"', self.store_username, self.store_name)
|
|
165
|
+
# web_page.locator('//button[contains(@class,"login_btn")]').click()
|
|
166
|
+
#
|
|
167
|
+
# log('再延时5秒', self.store_username, self.store_name)
|
|
168
|
+
# web_page.wait_for_timeout(5000)
|
|
169
|
+
|
|
170
|
+
web_page.wait_for_timeout(3000)
|
|
171
|
+
|
|
172
|
+
if 'https://sso.geiwohuo.com/#/home' in web_page.url:
|
|
173
|
+
if 'SHEIN全球商家中心' in web_page.title() or '后台首页' in web_page.title() or '商家后台' in web_page.title():
|
|
174
|
+
log(web_page.title(), '中断循环', self.store_username, self.store_name)
|
|
175
|
+
web_page.wait_for_timeout(5000)
|
|
176
|
+
break
|
|
93
177
|
|
|
94
178
|
if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
179
|
+
try:
|
|
180
|
+
web_page.goto('https://sso.geiwohuo.com/#/home?q=1', wait_until='domcontentloaded', timeout=10000)
|
|
181
|
+
web_page.wait_for_timeout(3000)
|
|
182
|
+
except Exception as nav_error:
|
|
183
|
+
log(f"导航失败,尝试重新加载: {nav_error}", self.store_username, self.store_name)
|
|
184
|
+
web_page.reload(wait_until='domcontentloaded', timeout=10000)
|
|
185
|
+
web_page.wait_for_timeout(5000)
|
|
98
186
|
|
|
99
187
|
if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
|
|
100
188
|
log('检测到鉴权 刷新页面', self.store_username, self.store_name)
|
|
101
189
|
web_page.reload()
|
|
102
|
-
web_page.
|
|
103
|
-
web_page.wait_for_timeout(3000)
|
|
190
|
+
web_page.wait_for_timeout(5000)
|
|
104
191
|
web_page.reload()
|
|
105
|
-
web_page.wait_for_timeout(
|
|
192
|
+
web_page.wait_for_timeout(5000)
|
|
106
193
|
|
|
107
194
|
if web_page.title() == 'SHEIN':
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
195
|
+
try:
|
|
196
|
+
web_page.goto('https://sso.geiwohuo.com/#/home?q=2', wait_until='domcontentloaded', timeout=10000)
|
|
197
|
+
web_page.wait_for_timeout(3000)
|
|
198
|
+
except Exception as nav_error:
|
|
199
|
+
log(f"导航失败,尝试重新加载: {nav_error}", self.store_username, self.store_name)
|
|
200
|
+
web_page.reload(wait_until='domcontentloaded', timeout=10000)
|
|
201
|
+
web_page.wait_for_timeout(5000)
|
|
202
|
+
|
|
203
|
+
break
|
|
112
204
|
except Exception as e:
|
|
113
205
|
log(f"错误发生: {e}, 重试中...({self.store_username}, {self.store_name})")
|
|
206
|
+
log(traceback.format_exc())
|
|
207
|
+
|
|
208
|
+
# 收集崩溃时的详细信息
|
|
209
|
+
try:
|
|
210
|
+
crash_url = web_page.url
|
|
211
|
+
crash_title = web_page.title()
|
|
212
|
+
log(f"崩溃时页面信息 - URL: {crash_url}, 标题: {crash_title}", self.store_username, self.store_name)
|
|
213
|
+
|
|
214
|
+
# 尝试截图保存崩溃现场
|
|
215
|
+
try:
|
|
216
|
+
screenshot_path = f"crash_screenshot_{self.store_username}_{int(time.time())}.png"
|
|
217
|
+
web_page.screenshot(path=screenshot_path)
|
|
218
|
+
log(f"已保存崩溃截图: {screenshot_path}", self.store_username, self.store_name)
|
|
219
|
+
except:
|
|
220
|
+
log("无法截取崩溃时的页面截图", self.store_username, self.store_name)
|
|
221
|
+
|
|
222
|
+
except:
|
|
223
|
+
log("无法获取崩溃时的页面信息", self.store_username, self.store_name)
|
|
224
|
+
|
|
225
|
+
# 检查特定类型的错误
|
|
226
|
+
if any(keyword in str(e).lower() for keyword in ['memory', 'out of memory', 'oom']):
|
|
227
|
+
log("检测到内存相关崩溃", self.store_username, self.store_name)
|
|
228
|
+
|
|
229
|
+
if "destroyed" in str(e) or "navigation" in str(e):
|
|
230
|
+
log("检测到导航中断,等待页面稳定后重试", self.store_username, self.store_name)
|
|
231
|
+
web_page.wait_for_timeout(5000)
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
if 'crashed' in str(e) or 'Target' in str(e):
|
|
235
|
+
log("检测到页面或目标崩溃,直接退出当前循环", self.store_username, self.store_name)
|
|
236
|
+
raise e
|
|
114
237
|
retries += 1
|
|
115
238
|
if retries >= MAX_RETRIES:
|
|
116
239
|
log(f"达到最大重试次数,停止尝试({self.store_username}, {self.store_name})")
|
|
@@ -118,6 +241,120 @@ class SheinLib:
|
|
|
118
241
|
time.sleep(2) # 错误时等待2秒后重试
|
|
119
242
|
|
|
120
243
|
log('鉴权处理结束')
|
|
244
|
+
# web_page.wait_for_load_state("load")
|
|
245
|
+
# web_page.wait_for_load_state("networkidle")
|
|
246
|
+
web_page.wait_for_timeout(3000)
|
|
247
|
+
|
|
248
|
+
# 获取用户信息
|
|
249
|
+
def get_user(self, uuid=None):
|
|
250
|
+
log(f'获取用户信息:{self.store_username} {self.store_name}')
|
|
251
|
+
|
|
252
|
+
# 生成 uuid 参数,如果没有提供则使用时间戳
|
|
253
|
+
if uuid is None:
|
|
254
|
+
import time
|
|
255
|
+
uuid = str(int(time.time() * 1000))
|
|
256
|
+
|
|
257
|
+
url = f"https://sso.geiwohuo.com/sso-prefix/auth/getUser?uuid={uuid}"
|
|
258
|
+
|
|
259
|
+
# 设置请求头,根据 Chrome 请求
|
|
260
|
+
headers = {
|
|
261
|
+
"gmpsso-language": "CN",
|
|
262
|
+
"origin-url" : "https://sso.geiwohuo.com/#/home/",
|
|
263
|
+
"x-sso-scene" : "gmpsso"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# 特定于此请求的配置
|
|
267
|
+
fetch_config = {
|
|
268
|
+
"credentials" : "include",
|
|
269
|
+
"referrer" : "https://sso.geiwohuo.com/",
|
|
270
|
+
"referrerPolicy": "strict-origin-when-cross-origin"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
response_text = fetch_get(self.web_page, url, headers, fetch_config)
|
|
274
|
+
error_code = response_text.get('code')
|
|
275
|
+
if str(error_code) != '0':
|
|
276
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
277
|
+
info = response_text.get('info', {})
|
|
278
|
+
cache_file = f'{self.config.auto_dir}/shein_user.json'
|
|
279
|
+
info['store_username'] = self.store_username
|
|
280
|
+
info['store_name'] = self.store_name
|
|
281
|
+
write_dict_to_file_ex(cache_file, {self.store_username: info}, [self.store_username])
|
|
282
|
+
log(info)
|
|
283
|
+
self.user_info = info
|
|
284
|
+
return info
|
|
285
|
+
|
|
286
|
+
# 获取供货商信息
|
|
287
|
+
def get_supplier_data(self):
|
|
288
|
+
self.web_page.goto('https://sso.geiwohuo.com/#/mws/seller/new-account-overview')
|
|
289
|
+
self.web_page.wait_for_load_state('load')
|
|
290
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/supplier_data.json'
|
|
291
|
+
info = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 24 * 10)
|
|
292
|
+
if len(info) > 0:
|
|
293
|
+
return info
|
|
294
|
+
|
|
295
|
+
log(f'正在获取 {self.store_name} 供货商信息')
|
|
296
|
+
url = "https://sso.geiwohuo.com/mgs-api-prefix/supplierGrowth/querySupplierCommonData"
|
|
297
|
+
payload = {}
|
|
298
|
+
response_text = fetch(self.web_page, url, payload)
|
|
299
|
+
error_code = response_text.get('code')
|
|
300
|
+
if str(error_code) != '0':
|
|
301
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
302
|
+
info = response_text.get('info')
|
|
303
|
+
|
|
304
|
+
write_dict_to_file_ex(cache_file, {self.store_username: info}, [self.store_username])
|
|
305
|
+
|
|
306
|
+
return info
|
|
307
|
+
|
|
308
|
+
def get_withdraw_list(self, supplier_id, year=0):
|
|
309
|
+
self.web_page.goto('https://sso.geiwohuo.com/#/mws/seller/new-account-overview')
|
|
310
|
+
self.web_page.wait_for_load_state("load")
|
|
311
|
+
|
|
312
|
+
if year == 0:
|
|
313
|
+
first_day, last_day = TimeUtils.get_last_month_range_time()
|
|
314
|
+
else:
|
|
315
|
+
first_day, last_day = TimeUtils.get_year_range_time(year)
|
|
316
|
+
|
|
317
|
+
page_num = 1
|
|
318
|
+
page_size = 200
|
|
319
|
+
|
|
320
|
+
url = f"https://sso.geiwohuo.com/mws/mwms/sso/withdraw/transferRecordList"
|
|
321
|
+
payload = {
|
|
322
|
+
"reqSystemCode" : "mws-front",
|
|
323
|
+
"supplierId" : supplier_id,
|
|
324
|
+
"pageNum" : page_num,
|
|
325
|
+
"pageSize" : page_size,
|
|
326
|
+
"createTimeStart": first_day,
|
|
327
|
+
"createTimeEnd" : last_day,
|
|
328
|
+
# "withdrawStatusList": [30]
|
|
329
|
+
}
|
|
330
|
+
log(payload)
|
|
331
|
+
response_text = fetch(self.web_page, url, payload)
|
|
332
|
+
log(response_text)
|
|
333
|
+
error_code = response_text.get('code')
|
|
334
|
+
if str(error_code) != '0':
|
|
335
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
336
|
+
|
|
337
|
+
withdraw_list = response_text['info']['list']
|
|
338
|
+
total = response_text['info']['count']
|
|
339
|
+
totalPage = math.ceil(total / page_size)
|
|
340
|
+
|
|
341
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/withdraw_list_{first_day}_{last_day}.json'
|
|
342
|
+
withdraw_list_cache = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 12)
|
|
343
|
+
if len(withdraw_list_cache) == int(total):
|
|
344
|
+
log('返回缓存数据: ', len(withdraw_list_cache), total)
|
|
345
|
+
return withdraw_list_cache
|
|
346
|
+
|
|
347
|
+
for page in range(2, totalPage + 1):
|
|
348
|
+
log(f'获取提现列表 第{page}/{totalPage}页')
|
|
349
|
+
page_num = page
|
|
350
|
+
payload['pageNum'] = page_num
|
|
351
|
+
response_text = fetch(self.web_page, url, payload)
|
|
352
|
+
withdraw_list += response_text['info']['list']
|
|
353
|
+
time.sleep(0.1)
|
|
354
|
+
|
|
355
|
+
write_dict_to_file_ex(cache_file, {self.store_username: withdraw_list}, [self.store_username])
|
|
356
|
+
|
|
357
|
+
return withdraw_list
|
|
121
358
|
|
|
122
359
|
# 获取质检报告pdf地址
|
|
123
360
|
def get_qc_report_url(self, deliverCode, purchaseCode):
|
|
@@ -135,6 +372,21 @@ class SheinLib:
|
|
|
135
372
|
log(qc_report_url)
|
|
136
373
|
return qc_report_url
|
|
137
374
|
|
|
375
|
+
# 获取稽查报表
|
|
376
|
+
def get_inspect_report_url(self, returnOrderId):
|
|
377
|
+
log(f'获取稽查报告:{returnOrderId}')
|
|
378
|
+
url = f"https://sso.geiwohuo.com/pfmp/returnOrder/queryInspectReport"
|
|
379
|
+
payload = {
|
|
380
|
+
"returnOrderId": returnOrderId,
|
|
381
|
+
}
|
|
382
|
+
response_text = fetch(self.web_page, url, payload)
|
|
383
|
+
error_code = response_text.get('code')
|
|
384
|
+
if str(error_code) != '0':
|
|
385
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
386
|
+
log(response_text)
|
|
387
|
+
report_url = response_text.get('info', {}).get('reportUrl')
|
|
388
|
+
return report_url
|
|
389
|
+
|
|
138
390
|
def get_return_order_box_detail(self, returnOrderId):
|
|
139
391
|
log(f'获取退货包裹详情: {returnOrderId}')
|
|
140
392
|
url = f"https://sso.geiwohuo.com/pfmp/returnOrder/getReturnOrderBoxDetail"
|
|
@@ -149,11 +401,31 @@ class SheinLib:
|
|
|
149
401
|
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
150
402
|
list_item = response_text['info']['data']
|
|
151
403
|
|
|
404
|
+
for item in list_item:
|
|
405
|
+
# 遍历每个快递单的包裹列表
|
|
406
|
+
for box in item.get('boxList', []):
|
|
407
|
+
# 遍历每个包裹中的商品列表
|
|
408
|
+
for good in box.get('goods', []):
|
|
409
|
+
# 遍历每个商品的详情列表(包含platformSku的层级)
|
|
410
|
+
for detail in good.get('details', []):
|
|
411
|
+
# 在这里添加新字段
|
|
412
|
+
# 示例:添加一个"status"字段,值为"processed"
|
|
413
|
+
supplier_sku = detail.get('supplierSku')
|
|
414
|
+
erp_supplier_name = self.bridge.get_sku_supplier(supplier_sku, self.config.erp_source)
|
|
415
|
+
log(self.config.erp_source, supplier_sku, erp_supplier_name)
|
|
416
|
+
if erp_supplier_name != '-':
|
|
417
|
+
detail['erp_supplier_name'] = erp_supplier_name
|
|
418
|
+
erp_cost_price = self.bridge.get_sku_cost(supplier_sku, self.config.erp_source)
|
|
419
|
+
log(self.config.erp_source, supplier_sku, erp_cost_price)
|
|
420
|
+
if erp_cost_price != '-':
|
|
421
|
+
detail['erp_cost_price'] = erp_cost_price
|
|
422
|
+
|
|
423
|
+
log(list_item)
|
|
152
424
|
cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
|
|
153
425
|
write_dict_to_file(cache_file, list_item)
|
|
426
|
+
return list_item
|
|
154
427
|
|
|
155
428
|
def get_return_order_list(self, start_date, end_date, only_yesterday=1):
|
|
156
|
-
|
|
157
429
|
log(f'获取退货列表: {self.store_username} {self.store_name} {start_date} {end_date}')
|
|
158
430
|
|
|
159
431
|
page_num = 1
|
|
@@ -161,10 +433,11 @@ class SheinLib:
|
|
|
161
433
|
|
|
162
434
|
url = f"https://sso.geiwohuo.com/pfmp/returnOrder/page"
|
|
163
435
|
payload = {
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
436
|
+
"returnOrderType": 1, # 只查询退货
|
|
437
|
+
"addTimeStart" : f"{start_date} 00:00:00",
|
|
438
|
+
"addTimeEnd" : f"{end_date} 23:59:59",
|
|
439
|
+
"page" : page_num,
|
|
440
|
+
"perPage" : page_size
|
|
168
441
|
}
|
|
169
442
|
response_text = fetch(self.web_page, url, payload)
|
|
170
443
|
error_code = response_text.get('code')
|
|
@@ -186,19 +459,33 @@ class SheinLib:
|
|
|
186
459
|
today_list_item = []
|
|
187
460
|
# 过滤 退货出库时间 是昨天的
|
|
188
461
|
for item in list_item:
|
|
462
|
+
returnOrderId = item['id']
|
|
463
|
+
item['store_username'] = self.store_username
|
|
464
|
+
item['store_name'] = self.store_name
|
|
465
|
+
item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
466
|
+
|
|
467
|
+
item['qc_report_url'] = ''
|
|
468
|
+
if int(item['returnScrapType']) == 1:
|
|
469
|
+
purchaseCode = item['sellerOrderNo']
|
|
470
|
+
delivery_code = item['sellerDeliveryNo']
|
|
471
|
+
item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
|
|
472
|
+
|
|
473
|
+
item['report_url'] = ''
|
|
474
|
+
if int(item['returnScrapType']) == 2:
|
|
475
|
+
item['report_url'] = self.get_inspect_report_url(returnOrderId)
|
|
476
|
+
|
|
477
|
+
item['return_box_detail'] = []
|
|
478
|
+
|
|
189
479
|
has_valid_package = item.get('hasPackage') == 1
|
|
190
|
-
is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
|
|
191
480
|
if has_valid_package:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
|
|
196
|
-
returnOrderId = item['id']
|
|
197
|
-
self.get_return_order_box_detail(returnOrderId)
|
|
481
|
+
return_box_detail = self.get_return_order_box_detail(returnOrderId)
|
|
482
|
+
if len(return_box_detail) > 0:
|
|
483
|
+
item['return_box_detail'] = return_box_detail
|
|
198
484
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
485
|
+
all_list_item.append(item)
|
|
486
|
+
is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
|
|
487
|
+
if is_valid_yesterday:
|
|
488
|
+
today_list_item.append(item)
|
|
202
489
|
|
|
203
490
|
cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
|
|
204
491
|
write_dict_to_file_ex(cache_file, {self.store_username: today_list_item}, [self.store_username])
|
|
@@ -303,6 +590,53 @@ class SheinLib:
|
|
|
303
590
|
|
|
304
591
|
return list_item
|
|
305
592
|
|
|
593
|
+
def get_ledger_record(self, first_day, last_day):
|
|
594
|
+
page_num = 1
|
|
595
|
+
page_size = 200 # 列表最多返回200条数据 大了没有用
|
|
596
|
+
|
|
597
|
+
cache_file = f'{self.config.auto_dir}/shein/ledger/ledger_record_{self.store_username}_{first_day}_{last_day}.json'
|
|
598
|
+
list_item_cache = read_dict_from_file(cache_file)
|
|
599
|
+
|
|
600
|
+
url = f"https://sso.geiwohuo.com/mils/changeDetail/page"
|
|
601
|
+
payload = {
|
|
602
|
+
"displayChangeTypeList": ["6", "7", "9", "10", "11", "12", "13", "16", "18", "19", "21"], # 出库
|
|
603
|
+
"addTimeStart" : f"{first_day} 00:00:00",
|
|
604
|
+
"addTimeEnd" : f"{last_day} 23:59:59",
|
|
605
|
+
"pageNumber" : page_num,
|
|
606
|
+
"pageSize" : page_size,
|
|
607
|
+
"changeTypeIndex" : "2"
|
|
608
|
+
}
|
|
609
|
+
response_text = fetch(self.web_page, url, payload)
|
|
610
|
+
error_code = response_text.get('code')
|
|
611
|
+
if str(error_code) != '0':
|
|
612
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
613
|
+
list_item = response_text['info']['data']['list']
|
|
614
|
+
total = response_text['info']['data']['count']
|
|
615
|
+
totalPage = math.ceil(total / page_size)
|
|
616
|
+
|
|
617
|
+
if len(list_item_cache) == int(total):
|
|
618
|
+
return list_item_cache
|
|
619
|
+
|
|
620
|
+
for page in range(2, totalPage + 1):
|
|
621
|
+
log(f'获取台账明细列表 第{page}/{totalPage}页')
|
|
622
|
+
payload['pageNumber'] = page
|
|
623
|
+
response_text = fetch(self.web_page, url, payload)
|
|
624
|
+
spu_list_new = response_text['info']['data']['list']
|
|
625
|
+
list_item += spu_list_new
|
|
626
|
+
time.sleep(0.1)
|
|
627
|
+
|
|
628
|
+
for item in list_item:
|
|
629
|
+
supplierSku = item['supplierSku']
|
|
630
|
+
item['store_username'] = self.store_username
|
|
631
|
+
item['store_name'] = self.store_name
|
|
632
|
+
item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
633
|
+
item['cost_price'] = self.bridge.get_sku_cost(supplierSku, self.config.erp_source)
|
|
634
|
+
item['sku_img'] = self.bridge.get_sku_img(supplierSku, self.config.erp_source)
|
|
635
|
+
|
|
636
|
+
write_dict_to_file(cache_file, list_item)
|
|
637
|
+
|
|
638
|
+
return list_item
|
|
639
|
+
|
|
306
640
|
def get_ledger_list(self, source='mb'):
|
|
307
641
|
page_num = 1
|
|
308
642
|
page_size = 200 # 列表最多返回200条数据 大了没有用
|
|
@@ -398,12 +732,96 @@ class SheinLib:
|
|
|
398
732
|
|
|
399
733
|
return list_item
|
|
400
734
|
|
|
735
|
+
def refresh_bridge_data_for_list(self, data_list, source='mb', sku_field='supplierSku'):
|
|
736
|
+
"""
|
|
737
|
+
刷新列表中的bridge数据(成本价和SKU图片)
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
data_list: 需要刷新的数据列表
|
|
741
|
+
source: ERP数据源,默认为'mb'
|
|
742
|
+
sku_field: SKU字段名,默认为'supplierSku'
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
刷新后的数据列表
|
|
746
|
+
"""
|
|
747
|
+
log(f'开始刷新Bridge数据,共 {len(data_list)} 条记录', self.store_username, self.store_name)
|
|
748
|
+
|
|
749
|
+
for index, item in enumerate(data_list):
|
|
750
|
+
supplier_sku = item.get(sku_field)
|
|
751
|
+
if supplier_sku:
|
|
752
|
+
item['cost_price'] = self.bridge.get_sku_cost(supplier_sku, source)
|
|
753
|
+
item['sku_img'] = self.bridge.get_sku_img(supplier_sku, source)
|
|
754
|
+
|
|
755
|
+
# 每100条记录输出一次进度
|
|
756
|
+
if (index + 1) % 100 == 0:
|
|
757
|
+
log(f'刷新进度: {index + 1}/{len(data_list)}', self.store_username, self.store_name)
|
|
758
|
+
|
|
759
|
+
log(f'Bridge数据刷新完成', self.store_username, self.store_name)
|
|
760
|
+
return data_list
|
|
761
|
+
|
|
762
|
+
def get_vssv_order_list(self):
|
|
763
|
+
"""
|
|
764
|
+
获取VSSV订单列表
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
web_page: 页面对象
|
|
768
|
+
store_username: 店铺账号
|
|
769
|
+
store_name: 店铺名称
|
|
770
|
+
|
|
771
|
+
Returns:
|
|
772
|
+
list: 订单列表
|
|
773
|
+
"""
|
|
774
|
+
page_num = 1
|
|
775
|
+
page_size = 200
|
|
776
|
+
first_day, last_day = TimeUtils.get_last_month_range()
|
|
777
|
+
|
|
778
|
+
cache_file = f'{self.config.auto_dir}/shein/vssv_order/vssv_order_list_{self.store_username}_{first_day}_{last_day}.json'
|
|
779
|
+
list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
|
|
780
|
+
if len(list_item) > 0:
|
|
781
|
+
return list_item
|
|
782
|
+
|
|
783
|
+
url = f"https://sso.geiwohuo.com/vssv/order/page"
|
|
784
|
+
payload = {
|
|
785
|
+
"deductionStatus": "2",
|
|
786
|
+
"beginTime" : f"{first_day} 00:00:00",
|
|
787
|
+
"endTime" : f"{last_day} 23:59:59",
|
|
788
|
+
"pageNumber" : page_num,
|
|
789
|
+
"pageSize" : page_size
|
|
790
|
+
}
|
|
791
|
+
response_text = fetch(self.web_page, url, payload)
|
|
792
|
+
error_code = response_text.get('code')
|
|
793
|
+
if str(error_code) != '0':
|
|
794
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
795
|
+
raise
|
|
796
|
+
list_item = response_text['info']['list']
|
|
797
|
+
total = response_text['info']['count']
|
|
798
|
+
totalPage = math.ceil(total / page_size)
|
|
799
|
+
|
|
800
|
+
for page in range(2, totalPage + 1):
|
|
801
|
+
log(f'获取VSSV订单列表 第{page}/{totalPage}页')
|
|
802
|
+
page_num = page
|
|
803
|
+
payload = {
|
|
804
|
+
"deductionStatus": "2",
|
|
805
|
+
"beginTime" : f"{first_day} 00:00:00",
|
|
806
|
+
"endTime" : f"{last_day} 23:59:59",
|
|
807
|
+
"pageNumber" : page_num,
|
|
808
|
+
"pageSize" : page_size
|
|
809
|
+
}
|
|
810
|
+
response_text = fetch(self.web_page, url, payload)
|
|
811
|
+
spu_list_new = response_text['info']['list']
|
|
812
|
+
list_item += spu_list_new
|
|
813
|
+
time.sleep(0.1)
|
|
814
|
+
|
|
815
|
+
write_dict_to_file(cache_file, list_item)
|
|
816
|
+
|
|
817
|
+
return list_item
|
|
818
|
+
|
|
401
819
|
def get_replenish_list(self):
|
|
402
820
|
page_num = 1
|
|
403
821
|
page_size = 50
|
|
404
822
|
first_day, last_day = TimeUtils.get_last_month_range()
|
|
405
823
|
|
|
406
|
-
cache_file = f'{self.config.auto_dir}/cache/replenish_list_{self.store_username}_{first_day}_{last_day}.json'
|
|
824
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/replenish_list_{self.store_username}_{first_day}_{last_day}.json'
|
|
407
825
|
list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
|
|
408
826
|
if len(list_item) > 0:
|
|
409
827
|
return list_item
|
|
@@ -425,128 +843,966 @@ class SheinLib:
|
|
|
425
843
|
totalPage = math.ceil(total / page_size)
|
|
426
844
|
|
|
427
845
|
for page in range(2, totalPage + 1):
|
|
428
|
-
log(f'获取不扣款列表 第{page}/{totalPage}页')
|
|
429
|
-
page_num = page
|
|
430
|
-
payload = {
|
|
431
|
-
"page" : page_num,
|
|
432
|
-
"perPage" : page_size,
|
|
433
|
-
"tabType" : 2,
|
|
434
|
-
"addTimeStart": f"{first_day} 00:00:00",
|
|
435
|
-
"addTimeEnd" : f"{last_day} 23:59:59"
|
|
436
|
-
}
|
|
846
|
+
log(f'获取不扣款列表 第{page}/{totalPage}页')
|
|
847
|
+
page_num = page
|
|
848
|
+
payload = {
|
|
849
|
+
"page" : page_num,
|
|
850
|
+
"perPage" : page_size,
|
|
851
|
+
"tabType" : 2,
|
|
852
|
+
"addTimeStart": f"{first_day} 00:00:00",
|
|
853
|
+
"addTimeEnd" : f"{last_day} 23:59:59"
|
|
854
|
+
}
|
|
855
|
+
response_text = fetch(self.web_page, url, payload)
|
|
856
|
+
spu_list_new = response_text['info']['data']
|
|
857
|
+
list_item += spu_list_new
|
|
858
|
+
time.sleep(0.1)
|
|
859
|
+
|
|
860
|
+
write_dict_to_file(cache_file, list_item)
|
|
861
|
+
|
|
862
|
+
return list_item
|
|
863
|
+
|
|
864
|
+
def get_return_list(self):
|
|
865
|
+
page_num = 1
|
|
866
|
+
page_size = 200
|
|
867
|
+
first_day, last_day = TimeUtils.get_last_month_range()
|
|
868
|
+
|
|
869
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/return_list_{self.store_username}_{first_day}_{last_day}.json'
|
|
870
|
+
list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
|
|
871
|
+
if len(list_item) > 0:
|
|
872
|
+
return list_item
|
|
873
|
+
|
|
874
|
+
url = f"https://sso.geiwohuo.com/pfmp/returnOrder/page"
|
|
875
|
+
payload = {
|
|
876
|
+
"addTimeStart" : f"{first_day} 00:00:00",
|
|
877
|
+
"addTimeEnd" : f"{last_day} 23:59:59",
|
|
878
|
+
"returnOrderStatusList": [4],
|
|
879
|
+
"page" : page_num,
|
|
880
|
+
"perPage" : page_size
|
|
881
|
+
}
|
|
882
|
+
response_text = fetch(self.web_page, url, payload)
|
|
883
|
+
error_code = response_text.get('code')
|
|
884
|
+
if str(error_code) != '0':
|
|
885
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
886
|
+
|
|
887
|
+
list_item = response_text['info']['data']
|
|
888
|
+
total = response_text['info']['meta']['count']
|
|
889
|
+
totalPage = math.ceil(total / page_size)
|
|
890
|
+
|
|
891
|
+
for page in range(2, totalPage + 1):
|
|
892
|
+
log(f'获取不扣款列表 第{page}/{totalPage}页')
|
|
893
|
+
page_num = page
|
|
894
|
+
payload = {
|
|
895
|
+
"addTimeStart" : f"{first_day} 00:00:00",
|
|
896
|
+
"addTimeEnd" : f"{last_day} 23:59:59",
|
|
897
|
+
"returnOrderStatusList": [4],
|
|
898
|
+
"page" : page_num,
|
|
899
|
+
"perPage" : page_size
|
|
900
|
+
}
|
|
901
|
+
response_text = fetch(self.web_page, url, payload)
|
|
902
|
+
spu_list_new = response_text['info']['data']
|
|
903
|
+
list_item += spu_list_new
|
|
904
|
+
time.sleep(0.1)
|
|
905
|
+
|
|
906
|
+
write_dict_to_file(cache_file, list_item)
|
|
907
|
+
|
|
908
|
+
return list_item
|
|
909
|
+
|
|
910
|
+
def get_comment_list(self):
|
|
911
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/comment_list_{TimeUtils.today_date()}.json'
|
|
912
|
+
comment_list = read_dict_from_file_ex(cache_file, self.store_username, 3600)
|
|
913
|
+
if len(comment_list) > 0:
|
|
914
|
+
return comment_list
|
|
915
|
+
|
|
916
|
+
page_num = 1
|
|
917
|
+
page_size = 50
|
|
918
|
+
|
|
919
|
+
yesterday = TimeUtils.get_yesterday()
|
|
920
|
+
|
|
921
|
+
url = f"https://sso.geiwohuo.com/gsp/goods/comment/list"
|
|
922
|
+
payload = {
|
|
923
|
+
"page" : page_num,
|
|
924
|
+
"perPage" : page_size,
|
|
925
|
+
"startCommentTime": f"{yesterday} 00:00:00",
|
|
926
|
+
"commentEndTime" : f"{yesterday} 23:59:59",
|
|
927
|
+
"commentStarList" : ["3", "2", "1"]
|
|
928
|
+
}
|
|
929
|
+
response_text = fetch(self.web_page, url, payload)
|
|
930
|
+
error_code = response_text.get('code')
|
|
931
|
+
if str(error_code) != '0':
|
|
932
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
933
|
+
|
|
934
|
+
comment_list = response_text['info']['data']
|
|
935
|
+
total = response_text['info']['meta']['count']
|
|
936
|
+
totalPage = math.ceil(total / page_size)
|
|
937
|
+
|
|
938
|
+
for page in range(2, totalPage + 1):
|
|
939
|
+
log(f'获取评价列表 第{page}/{totalPage}页')
|
|
940
|
+
page_num = page
|
|
941
|
+
payload['page'] = page_num
|
|
942
|
+
response_text = fetch(self.web_page, url, payload)
|
|
943
|
+
comment_list = response_text['info']['data']
|
|
944
|
+
time.sleep(0.1)
|
|
945
|
+
|
|
946
|
+
write_dict_to_file_ex(cache_file, {self.store_username: comment_list}, [self.store_username])
|
|
947
|
+
return comment_list
|
|
948
|
+
|
|
949
|
+
def get_last_month_outbound_amount(self):
|
|
950
|
+
url = "https://sso.geiwohuo.com/mils/report/month/list"
|
|
951
|
+
start, end = TimeUtils.get_current_year_range()
|
|
952
|
+
payload = {
|
|
953
|
+
"reportDateStart": start, "reportDateEnd": end, "pageNumber": 1, "pageSize": 50
|
|
954
|
+
}
|
|
955
|
+
response_text = fetch(self.web_page, url, payload)
|
|
956
|
+
error_code = response_text.get('code')
|
|
957
|
+
if str(error_code) != '0':
|
|
958
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
959
|
+
info = response_text.get('info')
|
|
960
|
+
lst = info.get('data', {}).get('list', [])
|
|
961
|
+
if not lst:
|
|
962
|
+
log(f'⚠️ {self.store_name} 最近一个月无出库记录,金额为0')
|
|
963
|
+
return 0
|
|
964
|
+
|
|
965
|
+
last_item = lst[-1]
|
|
966
|
+
log(f'正在获取 {self.store_name} 最近一个月出库金额: {last_item["totalCustomerAmount"]}')
|
|
967
|
+
return last_item['totalCustomerAmount']
|
|
968
|
+
|
|
969
|
+
def query_attribute_multi(self, attribute_id_list):
|
|
970
|
+
url = "https://sso.geiwohuo.com/spmp-api-prefix/spmp/attribute/query_attribute_multi"
|
|
971
|
+
payload = {
|
|
972
|
+
"attribute_id_list": attribute_id_list,
|
|
973
|
+
}
|
|
974
|
+
response_text = fetch(self.web_page, url, payload)
|
|
975
|
+
error_code = response_text.get('code')
|
|
976
|
+
if str(error_code) != '0':
|
|
977
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
978
|
+
info = response_text.get('info')
|
|
979
|
+
lst = info.get('data', {})
|
|
980
|
+
return lst
|
|
981
|
+
|
|
982
|
+
def get_product_attr(self, spu, attr_name):
|
|
983
|
+
try:
|
|
984
|
+
product_detail = self.get_product_detail(spu)
|
|
985
|
+
product_type_id = product_detail.get('product_type_id')
|
|
986
|
+
category_id = product_detail.get('category_id')
|
|
987
|
+
|
|
988
|
+
if not product_type_id or not category_id:
|
|
989
|
+
return None # 或者根据需要返回一个默认值
|
|
990
|
+
|
|
991
|
+
attribute_template = self.get_attribute_templates(spu, category_id, [product_type_id])
|
|
992
|
+
attr_info = attribute_template.get('attribute_infos', [])
|
|
993
|
+
|
|
994
|
+
# 查找材质属性映射,防止没有匹配项
|
|
995
|
+
attr_item = next((item for item in attr_info if item.get('attribute_name') == attr_name), None)
|
|
996
|
+
if not attr_item:
|
|
997
|
+
return None # 或者返回一个默认值
|
|
998
|
+
|
|
999
|
+
attr_id = attr_item.get('attribute_id')
|
|
1000
|
+
|
|
1001
|
+
# 拿到产品材质的属性值ID
|
|
1002
|
+
product_attribute_list = product_detail.get('product_attribute_list', [])
|
|
1003
|
+
attribute_value_id = next((item['attribute_value_id'] for item in product_attribute_list if item.get('attribute_id') == attr_id), None)
|
|
1004
|
+
if not attribute_value_id:
|
|
1005
|
+
return None # 或者返回一个默认值
|
|
1006
|
+
|
|
1007
|
+
# 获取属性值名称
|
|
1008
|
+
attr_value = next((item['attribute_value'] for item in attr_item.get('attribute_value_info_list', []) if item.get('attribute_value_id') == attribute_value_id), None)
|
|
1009
|
+
return attr_value # 返回找到的属性值
|
|
1010
|
+
except Exception as e:
|
|
1011
|
+
log(f"Error occurred: {e}")
|
|
1012
|
+
send_exception()
|
|
1013
|
+
return None # 或者返回一个默认值
|
|
1014
|
+
|
|
1015
|
+
def get_attribute_templates(self, spu_name, category_id, product_type_id_list):
|
|
1016
|
+
log(f'正在获取 {spu_name} 商品属性模板')
|
|
1017
|
+
|
|
1018
|
+
if not isinstance(product_type_id_list, list):
|
|
1019
|
+
raise '参数错误: product_type_id_list 需要是列表'
|
|
1020
|
+
|
|
1021
|
+
cache_file = f'{self.config.auto_dir}/shein/attribute/attribute_template_{spu_name}.json'
|
|
1022
|
+
attr_list = read_dict_from_file(cache_file, 3600 * 24 * 7)
|
|
1023
|
+
if len(attr_list) > 0:
|
|
1024
|
+
return attr_list
|
|
1025
|
+
|
|
1026
|
+
url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/basic/query_attribute_templates"
|
|
1027
|
+
payload = {
|
|
1028
|
+
"category_id" : category_id,
|
|
1029
|
+
"for_update" : True,
|
|
1030
|
+
"product_type_id_list": product_type_id_list,
|
|
1031
|
+
"spu_name" : spu_name
|
|
1032
|
+
}
|
|
1033
|
+
response_text = fetch(self.web_page, url, payload)
|
|
1034
|
+
error_code = response_text.get('code')
|
|
1035
|
+
if str(error_code) != '0':
|
|
1036
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
1037
|
+
info = response_text.get('info')
|
|
1038
|
+
|
|
1039
|
+
data = info.get('data')[0]
|
|
1040
|
+
write_dict_to_file(cache_file, data)
|
|
1041
|
+
return data
|
|
1042
|
+
|
|
1043
|
+
def get_product_detail(self, spu_name, cache_interval=3600 * 24 * 7):
|
|
1044
|
+
cache_file = f'{self.config.auto_dir}/shein/product_detail/product_detail_{spu_name}.json'
|
|
1045
|
+
info = read_dict_from_file(cache_file, cache_interval)
|
|
1046
|
+
if len(info) > 0:
|
|
1047
|
+
return info
|
|
1048
|
+
|
|
1049
|
+
log(f'正在获取 {spu_name} 商品详情')
|
|
1050
|
+
url = f"https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/get_product_detail"
|
|
1051
|
+
payload = {
|
|
1052
|
+
"spu_name": spu_name
|
|
1053
|
+
}
|
|
1054
|
+
response_text = fetch(self.web_page, url, payload)
|
|
1055
|
+
error_code = response_text.get('code')
|
|
1056
|
+
if str(error_code) != '0':
|
|
1057
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
1058
|
+
info = response_text.get('info')
|
|
1059
|
+
|
|
1060
|
+
# 获取 area_attribute_id
|
|
1061
|
+
sample_sku_back_size = info.get('sample_sku_back_size', None)
|
|
1062
|
+
if sample_sku_back_size is not None:
|
|
1063
|
+
area_attribute_ids = [item['area_attribute_id'] for item in sample_sku_back_size.get('area_info_list', [])]
|
|
1064
|
+
attribute_multi = self.query_attribute_multi(area_attribute_ids)
|
|
1065
|
+
info["attribute_multi"] = attribute_multi
|
|
1066
|
+
|
|
1067
|
+
write_dict_to_file(cache_file, info)
|
|
1068
|
+
return info
|
|
1069
|
+
|
|
1070
|
+
def product_month_analysis(self, start_date, end_date):
|
|
1071
|
+
# 店铺信息包含 店铺名称 skc上架状态 skc商品层级 统计周期
|
|
1072
|
+
# 商品信息包含 SPU,SKC,商家SKC,质量等级,商品分类,上架日期,上架天数
|
|
1073
|
+
# SKU信息包含 商家SKU,属性集
|
|
1074
|
+
# 前9列均是skc维度,从SKU信息开始 后面是SKU维度
|
|
1075
|
+
excel_data = [
|
|
1076
|
+
['店铺信息', '商品信息', 'SKC图片', '30天SKC曝光', '30天SKC点击率', '30天SKC转化率', '评论数', '差评率', '客单退货件数', 'SKU信息', 'SKU图片', 'SKU30天销量', '销售额', '核价', '成本', '30天利润', '30天利润率', 'skc']
|
|
1077
|
+
]
|
|
1078
|
+
excel_data2 = [
|
|
1079
|
+
['店铺信息', '商品信息', 'SKC图片', '日期', 'SKC销量', 'SKC曝光', 'SKC点击率', 'SKC转化率', 'skc']
|
|
1080
|
+
]
|
|
1081
|
+
skc_list = self.get_bak_base_info()
|
|
1082
|
+
cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
|
|
1083
|
+
dict_sku = read_dict_from_file(cache_file)
|
|
1084
|
+
cache_file = f'{self.config.auto_dir}/shein/quality_label/quality_label_{self.store_username}.json'
|
|
1085
|
+
dict_quality_label = read_dict_from_file(cache_file)
|
|
1086
|
+
|
|
1087
|
+
cache_file_analysis = f'{self.config.auto_dir}/shein/product_analysis/skc_skc_analysis_{self.store_username}_{start_date}_{end_date}.json'
|
|
1088
|
+
dict_analysis = read_dict_from_file(cache_file_analysis)
|
|
1089
|
+
|
|
1090
|
+
for skc_item in skc_list:
|
|
1091
|
+
categoryName = skc_item['categoryName']
|
|
1092
|
+
spu = skc_item['spu'] # SPU
|
|
1093
|
+
skc = skc_item['skc'] # SKC
|
|
1094
|
+
supplierCode = skc_item['supplierCode'] # 商家SKC
|
|
1095
|
+
skc_img = skc_item['picUrl'] # SKC图片
|
|
1096
|
+
shelfDate = skc_item['shelfDate'] # 上架日期
|
|
1097
|
+
shelfDays = skc_item['shelfDays'] # 上架天数
|
|
1098
|
+
shelfStatusName = skc_item['shelfStatus']['name'] # 上架状态
|
|
1099
|
+
quality_label = dict_quality_label.get(skc, {}).get('name', '') # 质量等级
|
|
1100
|
+
if quality_label == '无判断':
|
|
1101
|
+
quality_label = ''
|
|
1102
|
+
|
|
1103
|
+
if shelfStatusName == '待上架':
|
|
1104
|
+
log('商品未上架跳过:', skc)
|
|
1105
|
+
continue
|
|
1106
|
+
|
|
1107
|
+
goods_level = skc_item.get('goodsLevel', {}).get('name', '-')
|
|
1108
|
+
if goods_level in ['自主停产', '退供款']:
|
|
1109
|
+
log(f'商品 {goods_level} 跳过:', skc)
|
|
1110
|
+
continue
|
|
1111
|
+
|
|
1112
|
+
dict_sku_sales = self.get_skc_actual_sales_dict(skc, start_date, end_date)
|
|
1113
|
+
dict_skc_trend = self.get_skc_trend(spu, skc, start_date, end_date)
|
|
1114
|
+
|
|
1115
|
+
# 检查这个 SKC 是否有任何 SKU 有销量(与 excel_data 保持一致)
|
|
1116
|
+
has_sales = False
|
|
1117
|
+
for sku_item in skc_item['skuList']:
|
|
1118
|
+
c30dSaleCnt = sku_item.get('c30dSaleCnt', 0)
|
|
1119
|
+
attr = sku_item.get('attr', '')
|
|
1120
|
+
if attr != '合计' and int(c30dSaleCnt) > 0:
|
|
1121
|
+
has_sales = True
|
|
1122
|
+
break
|
|
1123
|
+
|
|
1124
|
+
# 只有当有趋势数据且有销量时才添加到 excel_data2(与 excel_data 保持一致)
|
|
1125
|
+
if dict_skc_trend and has_sales:
|
|
1126
|
+
for stat_date, dict_item in dict_skc_trend.items():
|
|
1127
|
+
store_info = f'{self.store_username}\n{self.store_name}\n({shelfStatusName})\n{goods_level}\n{start_date}\n{end_date}'
|
|
1128
|
+
product_info = f'SPU: {spu}\nSKC: {skc}\n商家SKC: {supplierCode}\n商品分类: {categoryName}\n上架日期: {shelfDate}\n上架天数: {shelfDays}\n质量等级: {quality_label}'
|
|
1129
|
+
|
|
1130
|
+
row_item2 = []
|
|
1131
|
+
row_item2.append(store_info)
|
|
1132
|
+
row_item2.append(product_info)
|
|
1133
|
+
row_item2.append(skc_img)
|
|
1134
|
+
row_item2.append(stat_date)
|
|
1135
|
+
row_item2.append(dict_item.get('saleCnt', 0))
|
|
1136
|
+
row_item2.append(dict_item.get('epsUvIdx', 0))
|
|
1137
|
+
row_item2.append(dict_item.get('epsGdsCtrIdx', 0))
|
|
1138
|
+
row_item2.append(dict_item.get('gdsPayCtrIdx', 0))
|
|
1139
|
+
row_item2.append(skc if skc else '') # 确保 skc 不为 None
|
|
1140
|
+
excel_data2.append(row_item2)
|
|
1141
|
+
|
|
1142
|
+
for sku_item in skc_item['skuList']:
|
|
1143
|
+
supplierSku = sku_item['supplierSku'] # 商家SKU
|
|
1144
|
+
attr = sku_item['attr'] # 属性集
|
|
1145
|
+
sku = sku_item['skuCode'] # SKU
|
|
1146
|
+
|
|
1147
|
+
c30dSaleCnt = sku_item['c30dSaleCnt'] # 近30天销量
|
|
1148
|
+
if attr == '合计' or int(c30dSaleCnt) == 0:
|
|
1149
|
+
log(f'跳过: {supplierSku},近30天销量: {c30dSaleCnt}')
|
|
1150
|
+
continue
|
|
1151
|
+
|
|
1152
|
+
price = dict_sku[sku]
|
|
1153
|
+
|
|
1154
|
+
store_info = f'{self.store_username}\n{self.store_name}\n({shelfStatusName})\n{goods_level}\n{start_date}\n{end_date}'
|
|
1155
|
+
product_info = f'SPU: {spu}\nSKC: {skc}\n商家SKC: {supplierCode}\n商品分类: {categoryName}\n上架日期: {shelfDate}\n上架天数: {shelfDays}\n质量等级: {quality_label}'
|
|
1156
|
+
|
|
1157
|
+
epsUvIdx = dict_analysis.get(skc, {}).get('epsUvIdx', 0)
|
|
1158
|
+
epsGdsCtrIdx = dict_analysis.get(skc, {}).get('epsGdsCtrIdx', 0)
|
|
1159
|
+
gdsPayCtrIdx = dict_analysis.get(skc, {}).get('gdsPayCtrIdx', 0)
|
|
1160
|
+
totalCommentCnt = dict_analysis.get(skc, {}).get('totalCommentCnt', 0)
|
|
1161
|
+
badCommentRate = dict_analysis.get(skc, {}).get('badCommentRate', 0)
|
|
1162
|
+
returnOrderCnt = dict_analysis.get(skc, {}).get('returnOrderCnt', 0)
|
|
1163
|
+
|
|
1164
|
+
sku_info = f'平台SKU: {sku}\n商家SKU: {supplierSku}\n属性集: {attr}'
|
|
1165
|
+
sku_img = self.bridge.get_sku_img(supplierSku, 'mb')
|
|
1166
|
+
# cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], self.config.erp_source)
|
|
1167
|
+
cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], 'mb')
|
|
1168
|
+
|
|
1169
|
+
row_item = []
|
|
1170
|
+
row_item.append(store_info)
|
|
1171
|
+
row_item.append(product_info)
|
|
1172
|
+
row_item.append(skc_img)
|
|
1173
|
+
row_item.append(epsUvIdx)
|
|
1174
|
+
row_item.append(epsGdsCtrIdx)
|
|
1175
|
+
row_item.append(gdsPayCtrIdx)
|
|
1176
|
+
row_item.append(totalCommentCnt)
|
|
1177
|
+
row_item.append(badCommentRate)
|
|
1178
|
+
row_item.append(returnOrderCnt)
|
|
1179
|
+
row_item.append(sku_info)
|
|
1180
|
+
row_item.append(sku_img)
|
|
1181
|
+
row_item.append(dict_sku_sales.get(sku, 0))
|
|
1182
|
+
row_item.append('') # 销售额(公式计算)
|
|
1183
|
+
row_item.append(price) # 核价
|
|
1184
|
+
row_item.append(cost_price) # 成本
|
|
1185
|
+
row_item.append('') # 30天利润(公式计算)
|
|
1186
|
+
row_item.append('') # 30天利润率(公式计算)
|
|
1187
|
+
row_item.append(skc)
|
|
1188
|
+
excel_data.append(row_item)
|
|
1189
|
+
|
|
1190
|
+
cache_file = f'{self.config.auto_dir}/shein/product_analysis/product_analysis_{TimeUtils.today_date()}.json'
|
|
1191
|
+
write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
|
|
1192
|
+
|
|
1193
|
+
cache_file = f'{self.config.auto_dir}/shein/product_analysis/product_analysis_2_{TimeUtils.today_date()}.json'
|
|
1194
|
+
write_dict_to_file_ex(cache_file, {self.store_username: excel_data2}, [self.store_username])
|
|
1195
|
+
return excel_data
|
|
1196
|
+
|
|
1197
|
+
def get_product(self):
|
|
1198
|
+
excel_data = [
|
|
1199
|
+
['店铺信息', '产品信息', 'SKC', '商家SKC', 'SKC图片', '商家SKU', '属性集', '近7天销量', '近30天销量', '核价', 'ERP成本价', '近7天利润', '近30天利润', '导出时间', 'SPU', 'SKC_FOR_STAT']
|
|
1200
|
+
]
|
|
1201
|
+
skc_list = self.get_bak_base_info()
|
|
1202
|
+
cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
|
|
1203
|
+
dict_sku = read_dict_from_file(cache_file)
|
|
1204
|
+
for skc_item in skc_list:
|
|
1205
|
+
categoryName = skc_item['categoryName']
|
|
1206
|
+
spu = skc_item['spu']
|
|
1207
|
+
skc = skc_item['skc']
|
|
1208
|
+
supplierCode = skc_item['supplierCode']
|
|
1209
|
+
skc_img = skc_item['picUrl']
|
|
1210
|
+
shelfDate = skc_item['shelfDate']
|
|
1211
|
+
shelfDays = skc_item['shelfDays']
|
|
1212
|
+
shelfStatusName = skc_item['shelfStatus']['name']
|
|
1213
|
+
# if shelfStatusName != '已下架':
|
|
1214
|
+
# continue
|
|
1215
|
+
for sku_item in skc_item['skuList']:
|
|
1216
|
+
supplierSku = sku_item['supplierSku']
|
|
1217
|
+
attr = sku_item['attr']
|
|
1218
|
+
sku = sku_item['skuCode']
|
|
1219
|
+
c7dSaleCnt = sku_item['c7dSaleCnt']
|
|
1220
|
+
c30dSaleCnt = sku_item['c30dSaleCnt']
|
|
1221
|
+
if attr == '合计' or int(c30dSaleCnt) == 0:
|
|
1222
|
+
log(f'跳过: {supplierSku},近30天销量: {c30dSaleCnt}')
|
|
1223
|
+
continue
|
|
1224
|
+
|
|
1225
|
+
price = dict_sku[sku]
|
|
1226
|
+
|
|
1227
|
+
product_info = f'SPU: {spu}\n商品分类: {categoryName}\n上架日期: {shelfDate}\n上架天数: {shelfDays}\n上架状态: {shelfStatusName}'
|
|
1228
|
+
|
|
1229
|
+
store_info = f'{self.store_username}\n{self.store_name}\n{self.config.shein_store_manager.get(self.store_username)}'
|
|
1230
|
+
|
|
1231
|
+
row_item = []
|
|
1232
|
+
row_item.append(store_info)
|
|
1233
|
+
row_item.append(product_info)
|
|
1234
|
+
row_item.append(skc)
|
|
1235
|
+
row_item.append(supplierCode)
|
|
1236
|
+
row_item.append(skc_img)
|
|
1237
|
+
row_item.append(supplierSku)
|
|
1238
|
+
row_item.append(attr)
|
|
1239
|
+
row_item.append(c7dSaleCnt)
|
|
1240
|
+
row_item.append(c30dSaleCnt)
|
|
1241
|
+
row_item.append(price)
|
|
1242
|
+
row_item.append('')
|
|
1243
|
+
row_item.append('')
|
|
1244
|
+
row_item.append('')
|
|
1245
|
+
row_item.append(TimeUtils.current_datetime())
|
|
1246
|
+
row_item.append(spu)
|
|
1247
|
+
row_item.append(skc)
|
|
1248
|
+
excel_data.append(row_item)
|
|
1249
|
+
|
|
1250
|
+
cache_file = f'{self.config.auto_dir}/shein/product/product_{TimeUtils.today_date()}.json'
|
|
1251
|
+
write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
|
|
1252
|
+
return excel_data
|
|
1253
|
+
|
|
1254
|
+
def generate_product_dict(self):
|
|
1255
|
+
pass
|
|
1256
|
+
dict_sku_to_skc = []
|
|
1257
|
+
dict_sku_not_found = []
|
|
1258
|
+
skc_list = self.get_bak_base_info()
|
|
1259
|
+
for skc_item in skc_list:
|
|
1260
|
+
skc_item['store_username'] = self.store_username
|
|
1261
|
+
skc_item['store_name'] = self.store_name
|
|
1262
|
+
skc_item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
1263
|
+
spu = skc_item['spu']
|
|
1264
|
+
skc = skc_item['skc']
|
|
1265
|
+
supplierCode = skc_item['supplierCode']
|
|
1266
|
+
|
|
1267
|
+
shelf_status = skc_item.get('shelfStatus', {}).get('name', '-')
|
|
1268
|
+
if int(skc_item['shelfStatus']['value']) != 1:
|
|
1269
|
+
log('商品未上架跳过:', skc)
|
|
1270
|
+
continue
|
|
1271
|
+
|
|
1272
|
+
goods_level = skc_item.get('goodsLevel', {}).get('name', '-')
|
|
1273
|
+
if goods_level in ['自主停产', '退供款']:
|
|
1274
|
+
log(f'商品{goods_level}跳过:', skc)
|
|
1275
|
+
continue
|
|
1276
|
+
|
|
1277
|
+
# 倒序遍历 skuList,安全删除
|
|
1278
|
+
for i in range(len(skc_item['skuList']) - 1, -1, -1):
|
|
1279
|
+
sku_item = skc_item['skuList'][i]
|
|
1280
|
+
if sku_item['skuCode'] == '合计':
|
|
1281
|
+
del skc_item['skuList'][i] # 删除“合计”
|
|
1282
|
+
continue
|
|
1283
|
+
|
|
1284
|
+
cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], self.config.erp_source)
|
|
1285
|
+
if not isinstance(cost_price, (int, float)):
|
|
1286
|
+
dict_sku_not_found.append([
|
|
1287
|
+
self.store_username,
|
|
1288
|
+
f'{self.store_name}',
|
|
1289
|
+
self.config.shein_store_manager.get(str(self.store_username).lower()),
|
|
1290
|
+
spu,
|
|
1291
|
+
skc,
|
|
1292
|
+
supplierCode,
|
|
1293
|
+
sku_item['supplierSku'],
|
|
1294
|
+
shelf_status,
|
|
1295
|
+
goods_level,
|
|
1296
|
+
'忆托未匹配到成本价,可能原因: 1.没填商家SKU,2.商家SKU没有绑定本地SKU,3.商家SKU填写错误'
|
|
1297
|
+
])
|
|
1298
|
+
elif cost_price == 0:
|
|
1299
|
+
dict_sku_not_found.append([
|
|
1300
|
+
self.store_username,
|
|
1301
|
+
f'{self.store_name}',
|
|
1302
|
+
self.config.shein_store_manager.get(str(self.store_username).lower()),
|
|
1303
|
+
spu,
|
|
1304
|
+
skc,
|
|
1305
|
+
supplierCode,
|
|
1306
|
+
sku_item['supplierSku'],
|
|
1307
|
+
shelf_status,
|
|
1308
|
+
goods_level,
|
|
1309
|
+
'忆托未匹配到成本价为:0'
|
|
1310
|
+
])
|
|
1311
|
+
|
|
1312
|
+
dict_sku_to_skc.append([
|
|
1313
|
+
sku_item['supplierSku'],
|
|
1314
|
+
supplierCode,
|
|
1315
|
+
])
|
|
1316
|
+
|
|
1317
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/sku_not_found.json'
|
|
1318
|
+
write_dict_to_file_ex(cache_file, {self.store_username: dict_sku_not_found}, [self.store_username])
|
|
1319
|
+
|
|
1320
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/sku_to_skc.json'
|
|
1321
|
+
write_dict_to_file_ex(cache_file, {self.store_username: dict_sku_to_skc}, [self.store_username])
|
|
1322
|
+
|
|
1323
|
+
# 存储商品库
|
|
1324
|
+
def store_product_info(self):
|
|
1325
|
+
# todo 商品详情 属性 规格 图片 重量 与 尺寸
|
|
1326
|
+
skc_list = self.get_bak_base_info()
|
|
1327
|
+
cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
|
|
1328
|
+
dict_sku = read_dict_from_file(cache_file)
|
|
1329
|
+
dict_product_detail = []
|
|
1330
|
+
for skc_item in skc_list:
|
|
1331
|
+
skc_item['store_username'] = self.store_username
|
|
1332
|
+
skc_item['store_name'] = self.store_name
|
|
1333
|
+
skc_item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
1334
|
+
spu = skc_item['spu']
|
|
1335
|
+
if spu not in dict_product_detail:
|
|
1336
|
+
dict_product_detail.append(spu)
|
|
1337
|
+
material = self.get_product_attr(spu, '材质')
|
|
1338
|
+
log(material) # 这一步是为了获取 spu 详情和属性
|
|
1339
|
+
|
|
1340
|
+
# 倒序遍历 skuList,安全删除
|
|
1341
|
+
for i in range(len(skc_item['skuList']) - 1, -1, -1):
|
|
1342
|
+
sku_item = skc_item['skuList'][i]
|
|
1343
|
+
if sku_item['skuCode'] == '合计':
|
|
1344
|
+
del skc_item['skuList'][i] # 删除“合计”
|
|
1345
|
+
continue
|
|
1346
|
+
sku_item['price'] = dict_sku[sku_item['skuCode']]
|
|
1347
|
+
cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], self.config.erp_source)
|
|
1348
|
+
sku_item['erp_cost_price'] = cost_price if isinstance(cost_price, (int, float)) else None
|
|
1349
|
+
sku_item['erp_supplier_name'] = self.bridge.get_sku_supplier(sku_item['supplierSku'], self.config.erp_source)
|
|
1350
|
+
stock = self.bridge.get_sku_stock(sku_item['supplierSku'], self.config.erp_source)
|
|
1351
|
+
sku_item['erp_stock'] = stock if isinstance(stock, (int, float)) else None
|
|
1352
|
+
|
|
1353
|
+
cache_file = f'{self.config.auto_dir}/shein/product/skc_list_{self.store_username}.json'
|
|
1354
|
+
write_dict_to_file_ex(cache_file, {self.store_username: skc_list}, [self.store_username])
|
|
1355
|
+
|
|
1356
|
+
skc_file = f'{self.config.auto_dir}/shein/product/skc_list_file.json'
|
|
1357
|
+
write_dict_to_file_ex(skc_file, {self.store_username: cache_file}, [self.store_username])
|
|
1358
|
+
|
|
1359
|
+
detail_file = f'{self.config.auto_dir}/shein/product/product_detail_file.json'
|
|
1360
|
+
write_dict_to_file_ex(detail_file, {self.store_username: dict_product_detail}, [self.store_username])
|
|
1361
|
+
|
|
1362
|
+
def get_skc_diagnose_dict(self, start_date="", end_date=""):
|
|
1363
|
+
log(f'获取商品分析某个月的字典 {start_date} {end_date} {self.store_name} {self.store_username}')
|
|
1364
|
+
|
|
1365
|
+
cache_file_analysis = f'{self.config.auto_dir}/shein/product_analysis/skc_skc_analysis_{self.store_username}_{start_date}_{end_date}.json'
|
|
1366
|
+
|
|
1367
|
+
dict_analysis = read_dict_from_file(cache_file_analysis)
|
|
1368
|
+
if len(dict_analysis) > 0:
|
|
1369
|
+
return dict_analysis
|
|
1370
|
+
|
|
1371
|
+
dt_goods = self.get_dt_time_goods()
|
|
1372
|
+
if not TimeUtils.is_yesterday_date(dt_goods, "%Y%m%d"):
|
|
1373
|
+
log("数据尚未更新: dt_goods:", dt_goods)
|
|
1374
|
+
return []
|
|
1375
|
+
|
|
1376
|
+
url = "https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_list"
|
|
1377
|
+
page_num = 1
|
|
1378
|
+
page_size = 100
|
|
1379
|
+
payload = {
|
|
1380
|
+
"areaCd" : "cn",
|
|
1381
|
+
"dt" : dt_goods,
|
|
1382
|
+
"countrySite": [
|
|
1383
|
+
"shein-all"
|
|
1384
|
+
],
|
|
1385
|
+
"startDate" : start_date.replace('-', ""),
|
|
1386
|
+
"endDate" : end_date.replace('-', ""),
|
|
1387
|
+
"pageNum" : page_num,
|
|
1388
|
+
"pageSize" : page_size,
|
|
1389
|
+
}
|
|
1390
|
+
response_text = fetch(self.web_page, url, payload)
|
|
1391
|
+
error_code = response_text.get('code')
|
|
1392
|
+
if str(error_code) != '0':
|
|
1393
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
1394
|
+
spu_list = response_text['info']['data']
|
|
1395
|
+
total = response_text['info']['meta']['count']
|
|
1396
|
+
totalPage = math.ceil(total / page_size)
|
|
1397
|
+
|
|
1398
|
+
for page in range(2, totalPage + 1):
|
|
1399
|
+
log(f'获取商品分析列表(最近上架的) 第{page}/{totalPage}页')
|
|
1400
|
+
payload.update({"pageNum": page})
|
|
1401
|
+
response_text = fetch(self.web_page, url, payload)
|
|
1402
|
+
spu_list_new = response_text['info']['data']
|
|
1403
|
+
spu_list += spu_list_new
|
|
1404
|
+
time.sleep(0.3)
|
|
1405
|
+
|
|
1406
|
+
cache_file = f'{self.config.auto_dir}/shein/product_analysis/skc_dict_{self.store_username}_{start_date}_{end_date}.json'
|
|
1407
|
+
write_dict_to_file(cache_file, spu_list)
|
|
1408
|
+
|
|
1409
|
+
for skc_item in spu_list:
|
|
1410
|
+
skc = skc_item['skc']
|
|
1411
|
+
skc_item['store_username'] = self.store_username
|
|
1412
|
+
skc_item['store_name'] = self.store_name
|
|
1413
|
+
dict_analysis[skc] = skc_item
|
|
1414
|
+
|
|
1415
|
+
write_dict_to_file(cache_file_analysis, dict_analysis)
|
|
1416
|
+
|
|
1417
|
+
return dict_analysis
|
|
1418
|
+
|
|
1419
|
+
def get_skc_diagnose_list(self, shelf_date_begin="", shelf_date_end=""):
|
|
1420
|
+
log(f'获取商品分析列表(最近上架的或在售的) {shelf_date_begin} {shelf_date_end} {self.store_name} {self.store_username}')
|
|
1421
|
+
|
|
1422
|
+
dt_goods = self.get_dt_time_goods()
|
|
1423
|
+
if not TimeUtils.is_yesterday_date(dt_goods, "%Y%m%d"):
|
|
1424
|
+
log("数据尚未更新: dt_goods:", dt_goods)
|
|
1425
|
+
return []
|
|
1426
|
+
|
|
1427
|
+
yesterday = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
|
|
1428
|
+
|
|
1429
|
+
url = "https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_list"
|
|
1430
|
+
page_num = 1
|
|
1431
|
+
page_size = 100
|
|
1432
|
+
payload = {
|
|
1433
|
+
"areaCd" : "cn",
|
|
1434
|
+
"dt" : dt_goods,
|
|
1435
|
+
"countrySite": [
|
|
1436
|
+
"shein-all"
|
|
1437
|
+
],
|
|
1438
|
+
"startDate" : yesterday,
|
|
1439
|
+
"endDate" : yesterday,
|
|
1440
|
+
"pageNum" : page_num,
|
|
1441
|
+
"pageSize" : page_size,
|
|
1442
|
+
"onsaleFlag" : 1,
|
|
1443
|
+
# "localFrstSaleBeginDate": shelf_date_begin,
|
|
1444
|
+
# "localFrstSaleEndDate" : shelf_date_end,
|
|
1445
|
+
}
|
|
1446
|
+
response_text = fetch(self.web_page, url, payload)
|
|
1447
|
+
error_code = response_text.get('code')
|
|
1448
|
+
if str(error_code) != '0':
|
|
1449
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
1450
|
+
spu_list = response_text['info']['data']
|
|
1451
|
+
total = response_text['info']['meta']['count']
|
|
1452
|
+
totalPage = math.ceil(total / page_size)
|
|
1453
|
+
|
|
1454
|
+
for page in range(2, totalPage + 1):
|
|
1455
|
+
log(f'获取商品分析列表(最近上架的) 第{page}/{totalPage}页')
|
|
1456
|
+
payload.update({"pageNum": page})
|
|
437
1457
|
response_text = fetch(self.web_page, url, payload)
|
|
438
1458
|
spu_list_new = response_text['info']['data']
|
|
439
|
-
|
|
440
|
-
time.sleep(0.
|
|
1459
|
+
spu_list += spu_list_new
|
|
1460
|
+
time.sleep(0.3)
|
|
441
1461
|
|
|
442
|
-
|
|
1462
|
+
cache_file = f'{self.config.auto_dir}/shein/product_analysis/skc_list_{self.store_username}.json'
|
|
1463
|
+
write_dict_to_file(cache_file, spu_list)
|
|
1464
|
+
|
|
1465
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/skc_shelf_date_{self.store_username}.json'
|
|
1466
|
+
dict_skc_shelf_date = read_dict_from_file(cache_file)
|
|
1467
|
+
|
|
1468
|
+
# 活动信息
|
|
1469
|
+
# AB实验数据
|
|
1470
|
+
|
|
1471
|
+
# 预先过滤掉不需要的商品状态
|
|
1472
|
+
log(f'过滤前商品数量: {len(spu_list)}')
|
|
1473
|
+
exclude_levels = ['退供款', '自主停产', '自主下架']
|
|
1474
|
+
spu_list = [item for item in spu_list if item['layerNm'] not in exclude_levels]
|
|
1475
|
+
log(f'过滤后剩余商品数量: {len(spu_list)}')
|
|
1476
|
+
|
|
1477
|
+
for skc_item in spu_list:
|
|
1478
|
+
skc = skc_item['skc']
|
|
1479
|
+
skc_item['stat_date'] = datetime.strptime(yesterday, "%Y%m%d").strftime("%Y-%m-%d")
|
|
1480
|
+
skc_item['store_username'] = self.store_username
|
|
1481
|
+
skc_item['store_name'] = self.store_name
|
|
1482
|
+
skc_item['shelf_date'] = dict_skc_shelf_date[skc]
|
|
1483
|
+
ab_cache_file = f'{self.config.auto_dir}/shein/cache/ab_test_list_{skc}_{TimeUtils.today_date()}.json'
|
|
1484
|
+
skc_item['ab_test'] = read_dict_from_file(ab_cache_file)
|
|
1485
|
+
for prom_inf_ing in skc_item['promCampaign'].get('promInfIng') or []:
|
|
1486
|
+
prom_id = prom_inf_ing['promId']
|
|
1487
|
+
log('prom_id:', prom_id, len(prom_id))
|
|
1488
|
+
if len(prom_id) >= 11:
|
|
1489
|
+
# 托管活动
|
|
1490
|
+
prom_inf_ing['promDetail'] = self.get_skc_activity_price_info(skc, prom_id)
|
|
1491
|
+
elif len(prom_id) >= 8:
|
|
1492
|
+
# 营销工具
|
|
1493
|
+
prom_inf_ing['promDetail'] = self.query_goods_detail(prom_id)
|
|
1494
|
+
else:
|
|
1495
|
+
# 营销活动
|
|
1496
|
+
prom_inf_ing['promDetail'] = self.get_partake_activity_detail(prom_id, skc)
|
|
1497
|
+
|
|
1498
|
+
for prom_inf_ready in skc_item['promCampaign'].get('promInfReady') or []:
|
|
1499
|
+
prom_id = prom_inf_ready['promId']
|
|
1500
|
+
log('prom_id:', prom_id, len(prom_id))
|
|
1501
|
+
if len(prom_id) >= 11:
|
|
1502
|
+
prom_inf_ready['promDetail'] = self.get_skc_activity_price_info(skc, prom_id)
|
|
1503
|
+
elif len(prom_id) >= 8:
|
|
1504
|
+
prom_inf_ready['promDetail'] = self.query_goods_detail(prom_id)
|
|
1505
|
+
else:
|
|
1506
|
+
prom_inf_ready['promDetail'] = self.get_partake_activity_detail(prom_id, skc)
|
|
443
1507
|
|
|
444
|
-
|
|
1508
|
+
cache_file = f'{self.config.auto_dir}/shein/product_analysis/skc_model_{self.store_username}_{TimeUtils.today_date()}.json'
|
|
1509
|
+
write_dict_to_file(cache_file, spu_list)
|
|
445
1510
|
|
|
446
|
-
|
|
447
|
-
page_num = 1
|
|
448
|
-
page_size = 200
|
|
449
|
-
first_day, last_day = TimeUtils.get_last_month_range()
|
|
1511
|
+
return spu_list
|
|
450
1512
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return list_item
|
|
1513
|
+
# 获取备货信息列表 最近35天上架的
|
|
1514
|
+
def get_latest_shelf_list(self, shelf_date_begin="", shelf_date_end=""):
|
|
1515
|
+
log(f'获取备货信息列表(最近上架的或已上架的) {shelf_date_begin} {shelf_date_end} {self.store_name} {self.store_username}')
|
|
455
1516
|
|
|
456
|
-
|
|
1517
|
+
dict_skc_shelf_date = {}
|
|
1518
|
+
|
|
1519
|
+
url = "https://sso.geiwohuo.com/idms/goods-skc/list"
|
|
1520
|
+
pageNumber = 1
|
|
1521
|
+
pageSize = 100
|
|
457
1522
|
payload = {
|
|
458
|
-
"
|
|
459
|
-
"
|
|
460
|
-
"
|
|
461
|
-
"
|
|
462
|
-
"
|
|
1523
|
+
"pageNumber" : pageNumber,
|
|
1524
|
+
"pageSize" : pageSize,
|
|
1525
|
+
"supplierCodes" : "",
|
|
1526
|
+
"skcs" : "",
|
|
1527
|
+
"spu" : "",
|
|
1528
|
+
"c7dSaleCntBegin" : "",
|
|
1529
|
+
"c7dSaleCntEnd" : "",
|
|
1530
|
+
"goodsLevelIdList" : [],
|
|
1531
|
+
"supplyStatus" : "",
|
|
1532
|
+
"shelfStatus" : 1, # 已上架
|
|
1533
|
+
"categoryIdList" : [],
|
|
1534
|
+
"skcStockBegin" : "",
|
|
1535
|
+
"skcStockEnd" : "",
|
|
1536
|
+
"skuStockBegin" : "",
|
|
1537
|
+
"skuStockEnd" : "",
|
|
1538
|
+
"skcSaleDaysBegin" : "",
|
|
1539
|
+
"skcSaleDaysEnd" : "",
|
|
1540
|
+
"skuSaleDaysBegin" : "",
|
|
1541
|
+
"skuSaleDaysEnd" : "",
|
|
1542
|
+
"planUrgentCountBegin" : "",
|
|
1543
|
+
"planUrgentCountEnd" : "",
|
|
1544
|
+
"skcAvailableOrderBegin": "",
|
|
1545
|
+
"skcAvailableOrderEnd" : "",
|
|
1546
|
+
"skuAvailableOrderBegin": "",
|
|
1547
|
+
"skuAvailableOrderEnd" : "",
|
|
1548
|
+
"shelfDateBegin" : shelf_date_begin,
|
|
1549
|
+
"shelfDateEnd" : shelf_date_end,
|
|
1550
|
+
"stockWarnStatusList" : [],
|
|
1551
|
+
"labelFakeIdList" : [],
|
|
1552
|
+
"sheinSaleByInventory" : "",
|
|
1553
|
+
"tspIdList" : [],
|
|
1554
|
+
"adviceStatus" : [],
|
|
1555
|
+
"sortBy7dSaleCnt" : 2
|
|
463
1556
|
}
|
|
464
1557
|
response_text = fetch(self.web_page, url, payload)
|
|
465
1558
|
error_code = response_text.get('code')
|
|
466
1559
|
if str(error_code) != '0':
|
|
467
1560
|
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
468
1561
|
|
|
469
|
-
|
|
470
|
-
total = response_text['info']['meta']['count']
|
|
471
|
-
totalPage = math.ceil(total / page_size)
|
|
1562
|
+
spu_list = response_text['info']['list']
|
|
472
1563
|
|
|
1564
|
+
# skc_list = [item['skc'] for item in spu_list]
|
|
1565
|
+
# self.get_activity_label(skc_list)
|
|
1566
|
+
# self.get_preemption_list(skc_list)
|
|
1567
|
+
# self.get_sku_price_v2(skc_list)
|
|
1568
|
+
# self.get_stock_advice(skc_list)
|
|
1569
|
+
|
|
1570
|
+
total = response_text['info']['count']
|
|
1571
|
+
totalPage = math.ceil(total / pageSize)
|
|
473
1572
|
for page in range(2, totalPage + 1):
|
|
474
|
-
log(f'
|
|
475
|
-
|
|
476
|
-
payload = {
|
|
477
|
-
"addTimeStart" : f"{first_day} 00:00:00",
|
|
478
|
-
"addTimeEnd" : f"{last_day} 23:59:59",
|
|
479
|
-
"returnOrderStatusList": [4],
|
|
480
|
-
"page" : page_num,
|
|
481
|
-
"perPage" : page_size
|
|
482
|
-
}
|
|
1573
|
+
log(f'获取备货信息商品列表 第{page}/{totalPage}页')
|
|
1574
|
+
payload['pageNumber'] = page
|
|
483
1575
|
response_text = fetch(self.web_page, url, payload)
|
|
484
|
-
spu_list_new = response_text['info']['data']
|
|
485
|
-
list_item += spu_list_new
|
|
486
|
-
time.sleep(0.1)
|
|
487
1576
|
|
|
488
|
-
|
|
1577
|
+
new_spu_list = response_text['info']['list']
|
|
1578
|
+
spu_list += new_spu_list
|
|
489
1579
|
|
|
490
|
-
|
|
1580
|
+
# skc_list = [item['skc'] for item in new_spu_list]
|
|
1581
|
+
# self.get_activity_label(skc_list)
|
|
1582
|
+
# self.get_preemption_list(skc_list)
|
|
1583
|
+
# self.get_sku_price_v2(skc_list)
|
|
1584
|
+
# self.get_stock_advice(skc_list)
|
|
491
1585
|
|
|
492
|
-
|
|
493
|
-
cache_file = f'{self.config.auto_dir}/shein/dict/comment_list_{TimeUtils.today_date()}.json'
|
|
494
|
-
comment_list = read_dict_from_file_ex(cache_file, self.store_username, 3600)
|
|
495
|
-
if len(comment_list) > 0:
|
|
496
|
-
return comment_list
|
|
1586
|
+
time.sleep(0.3)
|
|
497
1587
|
|
|
498
|
-
|
|
499
|
-
|
|
1588
|
+
# key = f'{self.store_username}'
|
|
1589
|
+
# cache_file = f'{self.config.auto_dir}/shein/cache/bak_info_list_{key}_{shelf_date_begin}_{shelf_date_end}.json'
|
|
1590
|
+
# write_dict_to_file_ex(cache_file, {key: spu_list}, [key])
|
|
500
1591
|
|
|
501
|
-
|
|
1592
|
+
for skc_item in spu_list:
|
|
1593
|
+
skc = skc_item['skc']
|
|
1594
|
+
shelfDate = skc_item['shelfDate']
|
|
1595
|
+
dict_skc_shelf_date[skc] = shelfDate
|
|
502
1596
|
|
|
503
|
-
|
|
1597
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/skc_shelf_date_{self.store_username}.json'
|
|
1598
|
+
dict = read_dict_from_file(cache_file)
|
|
1599
|
+
dict.update(dict_skc_shelf_date)
|
|
1600
|
+
write_dict_to_file(cache_file, dict)
|
|
1601
|
+
|
|
1602
|
+
return spu_list
|
|
1603
|
+
|
|
1604
|
+
# 获取备货信息列表
|
|
1605
|
+
def get_bak_base_info(self):
|
|
1606
|
+
log(f'获取备货信息列表 {self.store_name} {self.store_username}')
|
|
1607
|
+
url = "https://sso.geiwohuo.com/idms/goods-skc/list"
|
|
1608
|
+
pageNumber = 1
|
|
1609
|
+
pageSize = 100
|
|
504
1610
|
payload = {
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"
|
|
1611
|
+
"pageNumber" : pageNumber,
|
|
1612
|
+
"pageSize" : pageSize,
|
|
1613
|
+
"supplierCodes" : "",
|
|
1614
|
+
"skcs" : "",
|
|
1615
|
+
"spu" : "",
|
|
1616
|
+
"c7dSaleCntBegin" : "",
|
|
1617
|
+
"c7dSaleCntEnd" : "",
|
|
1618
|
+
"goodsLevelIdList" : [],
|
|
1619
|
+
"supplyStatus" : "",
|
|
1620
|
+
"shelfStatus" : "",
|
|
1621
|
+
"categoryIdList" : [],
|
|
1622
|
+
"skcStockBegin" : "",
|
|
1623
|
+
"skcStockEnd" : "",
|
|
1624
|
+
"skuStockBegin" : "",
|
|
1625
|
+
"skuStockEnd" : "",
|
|
1626
|
+
"skcSaleDaysBegin" : "",
|
|
1627
|
+
"skcSaleDaysEnd" : "",
|
|
1628
|
+
"skuSaleDaysBegin" : "",
|
|
1629
|
+
"skuSaleDaysEnd" : "",
|
|
1630
|
+
"planUrgentCountBegin" : "",
|
|
1631
|
+
"planUrgentCountEnd" : "",
|
|
1632
|
+
"skcAvailableOrderBegin": "",
|
|
1633
|
+
"skcAvailableOrderEnd" : "",
|
|
1634
|
+
"skuAvailableOrderBegin": "",
|
|
1635
|
+
"skuAvailableOrderEnd" : "",
|
|
1636
|
+
"shelfDateBegin" : "",
|
|
1637
|
+
"shelfDateEnd" : "",
|
|
1638
|
+
"stockWarnStatusList" : [],
|
|
1639
|
+
"labelFakeIdList" : [],
|
|
1640
|
+
"sheinSaleByInventory" : "",
|
|
1641
|
+
"tspIdList" : [],
|
|
1642
|
+
"adviceStatus" : [],
|
|
1643
|
+
"sortBy7dSaleCnt" : 2
|
|
510
1644
|
}
|
|
511
1645
|
response_text = fetch(self.web_page, url, payload)
|
|
512
1646
|
error_code = response_text.get('code')
|
|
513
1647
|
if str(error_code) != '0':
|
|
514
1648
|
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
515
1649
|
|
|
516
|
-
|
|
517
|
-
total = response_text['info']['meta']['count']
|
|
518
|
-
totalPage = math.ceil(total / page_size)
|
|
1650
|
+
spu_list = response_text['info']['list']
|
|
519
1651
|
|
|
1652
|
+
skc_list = [item['skc'] for item in spu_list]
|
|
1653
|
+
self.get_activity_label(skc_list)
|
|
1654
|
+
self.get_quality_label(skc_list)
|
|
1655
|
+
self.get_preemption_list(skc_list)
|
|
1656
|
+
self.get_sku_price_v2(skc_list)
|
|
1657
|
+
self.get_stock_advice(skc_list)
|
|
1658
|
+
|
|
1659
|
+
total = response_text['info']['count']
|
|
1660
|
+
totalPage = math.ceil(total / pageSize)
|
|
520
1661
|
for page in range(2, totalPage + 1):
|
|
521
|
-
log(f'
|
|
522
|
-
|
|
523
|
-
payload['page'] = page_num
|
|
1662
|
+
log(f'获取备货信息商品列表 第{page}/{totalPage}页')
|
|
1663
|
+
payload['pageNumber'] = page
|
|
524
1664
|
response_text = fetch(self.web_page, url, payload)
|
|
525
|
-
comment_list = response_text['info']['data']
|
|
526
|
-
time.sleep(0.1)
|
|
527
1665
|
|
|
528
|
-
|
|
529
|
-
|
|
1666
|
+
new_spu_list = response_text['info']['list']
|
|
1667
|
+
spu_list += new_spu_list
|
|
530
1668
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1669
|
+
skc_list = [item['skc'] for item in new_spu_list]
|
|
1670
|
+
self.get_activity_label(skc_list)
|
|
1671
|
+
self.get_quality_label(skc_list)
|
|
1672
|
+
self.get_preemption_list(skc_list)
|
|
1673
|
+
self.get_sku_price_v2(skc_list)
|
|
1674
|
+
self.get_stock_advice(skc_list)
|
|
1675
|
+
|
|
1676
|
+
time.sleep(0.3)
|
|
1677
|
+
|
|
1678
|
+
key = f'{self.store_username}'
|
|
1679
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/bak_info_list_{key}.json'
|
|
1680
|
+
write_dict_to_file_ex(cache_file, {key: spu_list}, [key])
|
|
1681
|
+
|
|
1682
|
+
return spu_list
|
|
1683
|
+
|
|
1684
|
+
def get_skc_week_sale_list(self, spu, skc, start_from=None):
|
|
1685
|
+
dict_skc = self.get_dict_skc_week_trend_v2(spu, skc, start_from)
|
|
1686
|
+
date_list = TimeUtils.get_past_7_days_list(start_from)
|
|
1687
|
+
saleCnt7d = 0
|
|
1688
|
+
sales_detail = []
|
|
1689
|
+
for date in date_list:
|
|
1690
|
+
saleCnt = get_safe_value(dict_skc.get(date, {}), 'saleCnt', 0)
|
|
1691
|
+
epsUvIdx = get_safe_value(dict_skc.get(date, {}), 'epsUvIdx', 0)
|
|
1692
|
+
|
|
1693
|
+
saleCnt7d += saleCnt
|
|
1694
|
+
sales_detail.append(f'{date}({TimeUtils.get_weekday_name(date)}): {saleCnt}/{epsUvIdx}')
|
|
1695
|
+
|
|
1696
|
+
sales_data = []
|
|
1697
|
+
for date in date_list:
|
|
1698
|
+
goodsUvIdx = get_safe_value(dict_skc.get(date, {}), 'goodsUvIdx', 0) # 商详访客
|
|
1699
|
+
epsGdsCtrIdx = get_safe_value(dict_skc.get(date, {}), 'epsGdsCtrIdx', 0) # 点击率
|
|
1700
|
+
|
|
1701
|
+
payUvIdx = get_safe_value(dict_skc.get(date, {}), 'payUvIdx', 0) # 支付人数
|
|
1702
|
+
gdsPayCtrIdx = get_safe_value(dict_skc.get(date, {}), 'gdsPayCtrIdx', 0) # 转化率
|
|
1703
|
+
|
|
1704
|
+
sales_data.append(f'{date}({TimeUtils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
|
|
1705
|
+
|
|
1706
|
+
return sales_detail, sales_data, saleCnt7d
|
|
1707
|
+
|
|
1708
|
+
def stat_new_product_to_bak(self):
|
|
1709
|
+
# 直接调用 get_skc_week_actual_sales 來获取周销 计算是否能转成备货款
|
|
1710
|
+
skc_list = self.get_bak_base_info() # 这个地方 不要加已上架和正常供货参数 直接取所有的skc列表
|
|
1711
|
+
# 以算昨日7.2日为例 上架天数为29天(转换成上架日期),且过去7天销量达到类目备货标准和没有达到备货标准的skc数量
|
|
1712
|
+
# 1.计算某个skc的上架日期
|
|
1713
|
+
# 2.计算某个skc的基于某个日期的过去7天销量
|
|
1714
|
+
# 3.获取叶子类目的备货标准
|
|
1715
|
+
header = ['店铺账号', '店铺名称', '店长', '统计日期', 'SKC图片', '商品信息', '新品成功转备货款', '第4周SKC销量/SKC曝光', '第4周SKC点击率/SKC转化率', 'SKC', 'SPU']
|
|
1716
|
+
excel_data = []
|
|
1717
|
+
stat_date_list = TimeUtils.get_dates_from_first_of_month_to_yesterday()
|
|
1718
|
+
for stat_date in stat_date_list:
|
|
1719
|
+
# 计算 stat_date 这天 的上架日期 是 filter_shelf_date
|
|
1720
|
+
filter_shelf_date = TimeUtils.get_past_nth_day(29, stat_date)
|
|
1721
|
+
log(f'stat_date:{stat_date},filter_shelf_date:{filter_shelf_date}')
|
|
1722
|
+
# 筛选 上架日期是 filter_shelf_date 这天的skc有哪些
|
|
1723
|
+
filter_skc_list = [skc_item for skc_item in skc_list if skc_item['shelfDate'] == filter_shelf_date]
|
|
1724
|
+
# 再统计 这些skc 在 stat_date 这天的 前7天销量
|
|
1725
|
+
# 看看这个7天销量是否达到了类目的备货标准 统计 计数
|
|
1726
|
+
for skc_item in filter_skc_list:
|
|
1727
|
+
skc = skc_item['skc']
|
|
1728
|
+
spu = skc_item['spu']
|
|
1729
|
+
log(f'skc:{skc}, spu:{spu}')
|
|
1730
|
+
|
|
1731
|
+
row_item = []
|
|
1732
|
+
row_item.append(self.store_username)
|
|
1733
|
+
|
|
1734
|
+
status_cn = skc_item.get('shelfStatus').get('name')
|
|
1735
|
+
goods_level = skc_item['goodsLevel']['name']
|
|
1736
|
+
goods_label = [label["name"] for label in skc_item['goodsLabelList']]
|
|
1737
|
+
store_info = f'{self.store_name}\n({status_cn})\n{goods_level}\n{",".join(goods_label).strip()}\n{stat_date_list[-1]}\n{stat_date_list[0]}'
|
|
1738
|
+
row_item.append(store_info)
|
|
1739
|
+
store_manager = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
1740
|
+
row_item.append(store_manager)
|
|
1741
|
+
row_item.append(stat_date)
|
|
1742
|
+
row_item.append(skc_item['picUrl'])
|
|
1743
|
+
|
|
1744
|
+
standard_value = (skc_item.get('stockStandard') or {}).get('value') or 0
|
|
1745
|
+
|
|
1746
|
+
sale_detail, sale_rate, sale_num = self.get_skc_week_sale_list(spu, skc, stat_date)
|
|
1747
|
+
success = int(standard_value > 0 and sale_num >= standard_value)
|
|
1748
|
+
|
|
1749
|
+
categoryName = skc_item['categoryName']
|
|
1750
|
+
shelfDate = skc_item['shelfDate']
|
|
1751
|
+
product_info = (
|
|
1752
|
+
f'SPU: {spu}\n'
|
|
1753
|
+
f'SKC: {skc}\n'
|
|
1754
|
+
f'上架日期: {shelfDate}\n'
|
|
1755
|
+
f'类目: {categoryName}\n'
|
|
1756
|
+
f'备货标准/第4周销: {standard_value}/{sale_num}\n'
|
|
1757
|
+
)
|
|
1758
|
+
row_item.append(product_info)
|
|
1759
|
+
row_item.append(success)
|
|
1760
|
+
row_item.append("\n".join(sale_detail))
|
|
1761
|
+
row_item.append("\n".join(sale_rate))
|
|
1762
|
+
row_item.append(skc)
|
|
1763
|
+
row_item.append(spu)
|
|
1764
|
+
excel_data.append(row_item)
|
|
1765
|
+
|
|
1766
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/new_product_to_bak_{TimeUtils.today_date()}.json'
|
|
1767
|
+
write_dict_to_file_ex(cache_file, {self.store_username: [header] + excel_data}, [self.store_username])
|
|
1768
|
+
|
|
1769
|
+
def get_funds_data_lz(self):
|
|
1770
|
+
log(f'正在获取 {self.store_name} 财务数据')
|
|
1771
|
+
url = "https://sso.geiwohuo.com/sso/homePage/dataOverview/v2/detail"
|
|
534
1772
|
payload = {
|
|
535
|
-
"
|
|
1773
|
+
"metaIndexIds": [
|
|
1774
|
+
298,
|
|
1775
|
+
67,
|
|
1776
|
+
70,
|
|
1777
|
+
72
|
|
1778
|
+
],
|
|
536
1779
|
}
|
|
537
1780
|
response_text = fetch(self.web_page, url, payload)
|
|
538
1781
|
error_code = response_text.get('code')
|
|
539
1782
|
if str(error_code) != '0':
|
|
540
1783
|
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
541
1784
|
info = response_text.get('info')
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1785
|
+
num298 = 0 # 在途商品金额
|
|
1786
|
+
num67 = 0 # 在仓商品金额
|
|
1787
|
+
num70 = 0 # 待结算金额
|
|
1788
|
+
num72 = 0 # 可提现金额
|
|
1789
|
+
for item in info['list']:
|
|
1790
|
+
if item['metaIndexId'] == 298:
|
|
1791
|
+
num298 = item['count']
|
|
1792
|
+
if item['metaIndexId'] == 67:
|
|
1793
|
+
num67 = item['count']
|
|
1794
|
+
if item['metaIndexId'] == 70:
|
|
1795
|
+
num70 = item['count']
|
|
1796
|
+
if item['metaIndexId'] == 72:
|
|
1797
|
+
num72 = item['count']
|
|
546
1798
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
1799
|
+
outAmount = self.get_last_month_outbound_amount()
|
|
1800
|
+
store_manager = self.config.shein_store_manager.get(str(self.store_username).lower())
|
|
1801
|
+
NotifyItem = [f'{self.store_name}', self.store_username, store_manager, num298, num67, num70, num72, '', outAmount, '', '', '', TimeUtils.current_datetime()]
|
|
1802
|
+
log(NotifyItem)
|
|
1803
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_lz_{TimeUtils.today_date()}.json'
|
|
1804
|
+
write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, [self.store_username])
|
|
1805
|
+
return NotifyItem
|
|
550
1806
|
|
|
551
1807
|
def get_funds_data(self):
|
|
552
1808
|
log(f'正在获取 {self.store_name} 财务数据')
|
|
@@ -899,7 +2155,6 @@ class SheinLib:
|
|
|
899
2155
|
return count
|
|
900
2156
|
|
|
901
2157
|
def get_week_sales_stat_detail(self):
|
|
902
|
-
global ListNotify, NotifyItem
|
|
903
2158
|
dt = self.get_dt_time()
|
|
904
2159
|
yesterday = TimeUtils.get_yesterday(dt)
|
|
905
2160
|
date_7_days_ago = TimeUtils.get_past_nth_day(6, None, '%Y-%m-%d')
|
|
@@ -929,15 +2184,23 @@ class SheinLib:
|
|
|
929
2184
|
last_item = SheinStoreSalesDetailManager(self.config.database_url).get_records_as_dict(self.store_username, yesterday)
|
|
930
2185
|
log('last_item', last_item)
|
|
931
2186
|
day_item = info[-1]
|
|
2187
|
+
log(day_item)
|
|
932
2188
|
item = {}
|
|
933
2189
|
item["store_username"] = self.store_username
|
|
934
2190
|
item["store_name"] = self.store_name
|
|
935
2191
|
item["day"] = day_item["dataDate"]
|
|
936
|
-
item["sales_num"] = day_item["saleCnt1d"]
|
|
2192
|
+
item["sales_num"] = day_item["saleCnt1d"] or 0
|
|
937
2193
|
item['sales_num_inc'] = item['sales_num'] - last_item.get('sales_num', 0)
|
|
938
|
-
|
|
2194
|
+
|
|
2195
|
+
if int(self.user_info.get('lv1CategoryId')) == 216506: # 自运营POP店
|
|
2196
|
+
log('gmv1d', day_item['gmv1d'])
|
|
2197
|
+
item['sales_amount'] = day_item['gmv1d'] if isinstance(day_item['gmv1d'], (int, float)) else 0
|
|
2198
|
+
else:
|
|
2199
|
+
item['sales_amount'] = day_item['dealAmt1d'] or 0
|
|
2200
|
+
|
|
2201
|
+
log('sales_amount', item['sales_amount'])
|
|
939
2202
|
item['sales_amount_inc'] = item['sales_amount'] - float(last_item.get('sales_amount', 0))
|
|
940
|
-
item['visitor_num'] = day_item['idxShopGoodsUv1d']
|
|
2203
|
+
item['visitor_num'] = day_item['idxShopGoodsUv1d'] or 0
|
|
941
2204
|
item['visitor_num_inc'] = item['visitor_num'] - last_item.get('visitor_num', 0)
|
|
942
2205
|
item['bak_A_num'] = self.get_product_bak_A_count()
|
|
943
2206
|
item['bak_A_num_inc'] = item['bak_A_num'] - last_item.get('bak_A_num', 0)
|
|
@@ -1076,7 +2339,7 @@ class SheinLib:
|
|
|
1076
2339
|
item.append(stock_str)
|
|
1077
2340
|
item.append(cost_price)
|
|
1078
2341
|
item.append(supplyPrice)
|
|
1079
|
-
sale_num_list, sale_data_list = self.
|
|
2342
|
+
sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
|
|
1080
2343
|
item.append("\n".join(sale_num_list))
|
|
1081
2344
|
item.append("\n".join(sale_data_list))
|
|
1082
2345
|
item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
|
|
@@ -1287,7 +2550,7 @@ class SheinLib:
|
|
|
1287
2550
|
|
|
1288
2551
|
cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
|
|
1289
2552
|
write_dict_to_file(cache_file2, DictSkcShelf)
|
|
1290
|
-
cache_file3 = f'{self.config.auto_dir}/dict/skc_product_{self.store_username}.json'
|
|
2553
|
+
cache_file3 = f'{self.config.auto_dir}/shein/dict/skc_product_{self.store_username}.json'
|
|
1291
2554
|
write_dict_to_file(cache_file3, DictSkcProduct)
|
|
1292
2555
|
|
|
1293
2556
|
write_dict_to_file(cache_file, DictSpuInfo)
|
|
@@ -1366,6 +2629,33 @@ class SheinLib:
|
|
|
1366
2629
|
list_item += response_text['info']['data']
|
|
1367
2630
|
time.sleep(0.1)
|
|
1368
2631
|
|
|
2632
|
+
log(list_item)
|
|
2633
|
+
write_dict_to_file(cache_file, list_item)
|
|
2634
|
+
return list_item
|
|
2635
|
+
|
|
2636
|
+
def get_partake_activity_detail(self, activity_id, skc):
|
|
2637
|
+
log(f'正在获取营销活动报名记录详情 {self.store_name}')
|
|
2638
|
+
page_num = 1
|
|
2639
|
+
page_size = 100
|
|
2640
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{activity_id}_{skc}.json'
|
|
2641
|
+
list_item = read_dict_from_file(cache_file)
|
|
2642
|
+
if len(list_item) > 0:
|
|
2643
|
+
return list_item
|
|
2644
|
+
|
|
2645
|
+
url = f"https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_partake_activity_goods_list?page_num={page_num}&page_size={page_size}"
|
|
2646
|
+
payload = {
|
|
2647
|
+
"goods_audit_status": 1,
|
|
2648
|
+
"activity_id_list" : [activity_id],
|
|
2649
|
+
"skc_list" : [skc]
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
response_text = fetch(self.web_page, url, payload)
|
|
2653
|
+
error_code = response_text.get('code')
|
|
2654
|
+
if str(error_code) != '0':
|
|
2655
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
2656
|
+
list_item = response_text['info']['data']
|
|
2657
|
+
|
|
2658
|
+
log(list_item)
|
|
1369
2659
|
write_dict_to_file(cache_file, list_item)
|
|
1370
2660
|
return list_item
|
|
1371
2661
|
|
|
@@ -1446,6 +2736,36 @@ class SheinLib:
|
|
|
1446
2736
|
|
|
1447
2737
|
write_dict_to_file(cache_file, dict_activity_price)
|
|
1448
2738
|
|
|
2739
|
+
def get_skc_actual_sales_dict(self, skc, first_day, last_day):
|
|
2740
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/actual_sales_{skc}_{first_day}_{last_day}.json'
|
|
2741
|
+
if datetime.now().hour >= 9:
|
|
2742
|
+
DictSkuSales = read_dict_from_file(cache_file)
|
|
2743
|
+
else:
|
|
2744
|
+
DictSkuSales = read_dict_from_file(cache_file, 1800)
|
|
2745
|
+
if len(DictSkuSales) > 0:
|
|
2746
|
+
return DictSkuSales
|
|
2747
|
+
|
|
2748
|
+
url = f"https://sso.geiwohuo.com/idms/sale-trend/detail"
|
|
2749
|
+
payload = {
|
|
2750
|
+
"skc" : skc,
|
|
2751
|
+
"startDate": first_day,
|
|
2752
|
+
"endDate" : last_day,
|
|
2753
|
+
"daysToAdd": 0
|
|
2754
|
+
}
|
|
2755
|
+
response_text = fetch(self.web_page, url, payload)
|
|
2756
|
+
error_code = response_text.get('code')
|
|
2757
|
+
if str(error_code) != '0':
|
|
2758
|
+
log(response_text)
|
|
2759
|
+
return {}
|
|
2760
|
+
info = response_text['info']
|
|
2761
|
+
for sale_item in info['actualSalesVolumeMap']:
|
|
2762
|
+
sku = sale_item['skuCode']
|
|
2763
|
+
if sku is not None:
|
|
2764
|
+
DictSkuSales[sku] = sale_item['actualSalesVolume']
|
|
2765
|
+
|
|
2766
|
+
write_dict_to_file(cache_file, DictSkuSales)
|
|
2767
|
+
return DictSkuSales
|
|
2768
|
+
|
|
1449
2769
|
def get_skc_week_actual_sales(self, skc):
|
|
1450
2770
|
first_day, last_day = TimeUtils.get_past_7_days_range()
|
|
1451
2771
|
cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
|
|
@@ -1498,6 +2818,22 @@ class SheinLib:
|
|
|
1498
2818
|
|
|
1499
2819
|
return dict
|
|
1500
2820
|
|
|
2821
|
+
def get_quality_label(self, skc_list):
|
|
2822
|
+
url = f"https://sso.geiwohuo.com/idms/goods-skc/quality-label"
|
|
2823
|
+
payload = skc_list
|
|
2824
|
+
response_text = fetch(self.web_page, url, payload)
|
|
2825
|
+
error_code = response_text.get('code')
|
|
2826
|
+
if str(error_code) != '0':
|
|
2827
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
2828
|
+
dict = response_text['info']
|
|
2829
|
+
|
|
2830
|
+
cache_file = f'{self.config.auto_dir}/shein/quality_label/quality_label_{self.store_username}.json'
|
|
2831
|
+
dict_label = read_dict_from_file(cache_file)
|
|
2832
|
+
dict_label.update(dict)
|
|
2833
|
+
write_dict_to_file(cache_file, dict_label)
|
|
2834
|
+
|
|
2835
|
+
return dict
|
|
2836
|
+
|
|
1501
2837
|
def get_activity_label(self, skc_list):
|
|
1502
2838
|
url = f"https://sso.geiwohuo.com/idms/goods-skc/activity-label"
|
|
1503
2839
|
payload = skc_list
|
|
@@ -1514,6 +2850,23 @@ class SheinLib:
|
|
|
1514
2850
|
|
|
1515
2851
|
return dict
|
|
1516
2852
|
|
|
2853
|
+
def get_sku_price_pop(self, spu):
|
|
2854
|
+
pass
|
|
2855
|
+
log(f'获取pop sku价格列表', spu)
|
|
2856
|
+
info = self.get_product_detail(spu)
|
|
2857
|
+
|
|
2858
|
+
dict_sku_price_new = {}
|
|
2859
|
+
for skc_item in info['skc_list']:
|
|
2860
|
+
for sku_item in skc_item['sku_list']:
|
|
2861
|
+
sku = sku_item['sku_code']
|
|
2862
|
+
special_price = sku_item['price_info_list'][0]['special_price']
|
|
2863
|
+
dict_sku_price_new[sku] = special_price
|
|
2864
|
+
|
|
2865
|
+
cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
|
|
2866
|
+
dict_sku_price = read_dict_from_file(cache_file)
|
|
2867
|
+
dict_sku_price.update(dict_sku_price_new)
|
|
2868
|
+
write_dict_to_file(cache_file, dict_sku_price)
|
|
2869
|
+
|
|
1517
2870
|
def get_sku_price_v2(self, skc_list):
|
|
1518
2871
|
log(f'获取sku价格列表', skc_list)
|
|
1519
2872
|
url = "https://sso.geiwohuo.com/idms/goods-skc/price"
|
|
@@ -1565,6 +2918,24 @@ class SheinLib:
|
|
|
1565
2918
|
log(f'dt: {self.dt}')
|
|
1566
2919
|
return self.dt
|
|
1567
2920
|
|
|
2921
|
+
def get_dt_time_goods(self):
|
|
2922
|
+
if self.dt_goods is not None:
|
|
2923
|
+
log(f'字典dt_goods: {self.dt_goods}')
|
|
2924
|
+
return self.dt_goods
|
|
2925
|
+
log('获取非实时更新时间')
|
|
2926
|
+
url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
|
|
2927
|
+
payload = {
|
|
2928
|
+
"pageCode": "GoodsPreviewNew",
|
|
2929
|
+
"areaCd" : "cn"
|
|
2930
|
+
}
|
|
2931
|
+
response_text = fetch(self.web_page, url, payload)
|
|
2932
|
+
error_code = response_text.get('code')
|
|
2933
|
+
if str(error_code) != '0':
|
|
2934
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
2935
|
+
self.dt_goods = response_text.get('info').get('dt')
|
|
2936
|
+
log(f'接口dt_goods: {self.dt_goods}')
|
|
2937
|
+
return self.dt_goods
|
|
2938
|
+
|
|
1568
2939
|
def get_dict_skc_week_trend(self):
|
|
1569
2940
|
page_num = 1
|
|
1570
2941
|
page_size = 100
|
|
@@ -1765,6 +3136,7 @@ class SheinLib:
|
|
|
1765
3136
|
skc = str(spu_info['skc'])
|
|
1766
3137
|
# if not shein_db.exists_sales_1_days_ago(skc):
|
|
1767
3138
|
# log(f'未查到昨天销量: {skc}')
|
|
3139
|
+
self.get_skc_week_actual_sales(skc)
|
|
1768
3140
|
self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
|
|
1769
3141
|
skcCode = spu_info['supplierCode']
|
|
1770
3142
|
product_name = DictSpuInfo[spu]['product_name_en']
|
|
@@ -1874,7 +3246,7 @@ class SheinLib:
|
|
|
1874
3246
|
sku_item.append(skc) # SKC
|
|
1875
3247
|
sku_item.append(spu_info['supplierCode']) # SKC货号
|
|
1876
3248
|
|
|
1877
|
-
sale_num_list, sale_data_list = self.
|
|
3249
|
+
sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
|
|
1878
3250
|
sku_item.append("\n".join(sale_num_list))
|
|
1879
3251
|
sku_item.append("\n".join(sale_data_list))
|
|
1880
3252
|
sku_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
|
|
@@ -1885,7 +3257,7 @@ class SheinLib:
|
|
|
1885
3257
|
# SKC趋势数据
|
|
1886
3258
|
sku_item.append(skc_trend.get('saleCnt', 0)) # SKC近7天销量
|
|
1887
3259
|
sku_item.append(skc_trend.get('epsUvIdx', 0)) # SKC近7天曝光人数
|
|
1888
|
-
sku_item.append(skc_trend.get('
|
|
3260
|
+
sku_item.append(skc_trend.get('goodsUv', 0)) # SKC近7天商详访客
|
|
1889
3261
|
sku_item.append(skc_trend.get('epsGdsCtrIdx', 0)) # SKC近7天点击率
|
|
1890
3262
|
sku_item.append(skc_trend.get('payUvIdx', 0)) # SKC近7天支付人数
|
|
1891
3263
|
sku_item.append(skc_trend.get('gdsPayCtrIdx', 0)) # SKC近7天支付率
|
|
@@ -1898,14 +3270,63 @@ class SheinLib:
|
|
|
1898
3270
|
|
|
1899
3271
|
return product_sku_list
|
|
1900
3272
|
|
|
3273
|
+
# 获取一个skc一段时间内的销售趋势(商品明细中的)
|
|
3274
|
+
def get_skc_trend(self, spu, skc, start_date, end_date):
|
|
3275
|
+
dt = self.get_dt_time_goods()
|
|
3276
|
+
|
|
3277
|
+
# 将字符串转换为日期对象
|
|
3278
|
+
date1 = datetime.strptime(end_date, "%Y-%m-%d").date()
|
|
3279
|
+
date2 = datetime.strptime(dt, "%Y%m%d").date()
|
|
3280
|
+
if date1 > date2:
|
|
3281
|
+
log(f'get_skc_trend: dt:{dt} < end_date: {end_date}')
|
|
3282
|
+
|
|
3283
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/skc_trend_{skc}_{start_date}_{end_date}.json'
|
|
3284
|
+
DictSkc = read_dict_from_file(cache_file)
|
|
3285
|
+
if len(DictSkc) > 0:
|
|
3286
|
+
return DictSkc
|
|
3287
|
+
|
|
3288
|
+
url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_trend"
|
|
3289
|
+
payload = {
|
|
3290
|
+
"areaCd" : "cn",
|
|
3291
|
+
"countrySite": [
|
|
3292
|
+
"shein-all"
|
|
3293
|
+
],
|
|
3294
|
+
"dt" : dt,
|
|
3295
|
+
"endDate" : end_date.replace('-', ''),
|
|
3296
|
+
"spu" : [spu],
|
|
3297
|
+
"skc" : [skc],
|
|
3298
|
+
"startDate" : start_date.replace('-', ''),
|
|
3299
|
+
}
|
|
3300
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3301
|
+
log(response_text)
|
|
3302
|
+
error_code = response_text.get('code')
|
|
3303
|
+
if str(error_code) != '0':
|
|
3304
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
3305
|
+
|
|
3306
|
+
data_list = response_text['info']
|
|
3307
|
+
DictSkc = {}
|
|
3308
|
+
for date_item in data_list:
|
|
3309
|
+
dataDate = date_item['dataDate']
|
|
3310
|
+
# epsUvIdx = date_item['epsUvIdx']
|
|
3311
|
+
# saleCnt = date_item['saleCnt']
|
|
3312
|
+
DictSkc[dataDate] = date_item
|
|
3313
|
+
|
|
3314
|
+
log('len(DictSkc)', len(DictSkc))
|
|
3315
|
+
write_dict_to_file(cache_file, DictSkc)
|
|
3316
|
+
return DictSkc
|
|
3317
|
+
|
|
1901
3318
|
# 获取一个skc一周内的销售趋势(商品明细中的)
|
|
1902
|
-
def get_dict_skc_week_trend_v2(self, spu, skc):
|
|
3319
|
+
def get_dict_skc_week_trend_v2(self, spu, skc, start_from=None):
|
|
1903
3320
|
dt = self.get_dt_time()
|
|
1904
3321
|
|
|
1905
|
-
date_7_days_ago = TimeUtils.
|
|
1906
|
-
log('
|
|
1907
|
-
|
|
1908
|
-
|
|
3322
|
+
date_7_days_ago, date_1_days_ago = TimeUtils.get_past_7_days_range_format(start_from, '%Y%m%d')
|
|
3323
|
+
log(date_7_days_ago, date_1_days_ago, 'dt', dt)
|
|
3324
|
+
|
|
3325
|
+
# 将字符串转换为日期对象
|
|
3326
|
+
date1 = datetime.strptime(date_1_days_ago, "%Y%m%d").date()
|
|
3327
|
+
date2 = datetime.strptime(dt, "%Y%m%d").date()
|
|
3328
|
+
if date1 > date2:
|
|
3329
|
+
send_exception(f'get_dict_skc_week_trend_v2: dt:{dt} < date_1_days_ago: {date_1_days_ago}')
|
|
1909
3330
|
|
|
1910
3331
|
cache_file = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{skc}_{date_7_days_ago}_{date_1_days_ago}.json'
|
|
1911
3332
|
if datetime.now().hour >= 9:
|
|
@@ -1944,7 +3365,7 @@ class SheinLib:
|
|
|
1944
3365
|
write_dict_to_file(cache_file, DictSkc)
|
|
1945
3366
|
return DictSkc
|
|
1946
3367
|
|
|
1947
|
-
def
|
|
3368
|
+
def get_sku_week_sale_list(self, spu, skc, sku):
|
|
1948
3369
|
dict_skc = self.get_dict_skc_week_trend_v2(spu, skc)
|
|
1949
3370
|
date_list = TimeUtils.get_past_7_days_list()
|
|
1950
3371
|
first_day, last_day = TimeUtils.get_past_7_days_range()
|
|
@@ -2067,6 +3488,10 @@ class SheinLib:
|
|
|
2067
3488
|
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
2068
3489
|
|
|
2069
3490
|
spu_list = response_text['info']['list']
|
|
3491
|
+
# if int(self.user_info.get('lv1CategoryId')) == 216506: # 自运营POP店
|
|
3492
|
+
# for spu_item in spu_list:
|
|
3493
|
+
# spu = spu_item.get('spu')
|
|
3494
|
+
# self.get_sku_price_pop(spu)
|
|
2070
3495
|
|
|
2071
3496
|
skc_list = [item['skc'] for item in spu_list]
|
|
2072
3497
|
self.get_activity_label(skc_list)
|
|
@@ -2083,6 +3508,11 @@ class SheinLib:
|
|
|
2083
3508
|
response_text = fetch(self.web_page, url, payload)
|
|
2084
3509
|
spu_list_new = response_text['info']['list']
|
|
2085
3510
|
|
|
3511
|
+
# if int(self.user_info.get('lv1CategoryId')) == 216506: # 自运营POP店
|
|
3512
|
+
# for spu_item in spu_list:
|
|
3513
|
+
# spu = spu_item.get('spu')
|
|
3514
|
+
# self.get_product_detail(spu)
|
|
3515
|
+
|
|
2086
3516
|
skc_list = [item['skc'] for item in spu_list_new]
|
|
2087
3517
|
self.get_activity_label(skc_list)
|
|
2088
3518
|
self.get_preemption_list(skc_list)
|
|
@@ -2317,7 +3747,7 @@ class SheinLib:
|
|
|
2317
3747
|
if mode in [6] and sales7cn > 0:
|
|
2318
3748
|
continue
|
|
2319
3749
|
|
|
2320
|
-
sale_num_list, sale_data_list = self.
|
|
3750
|
+
sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
|
|
2321
3751
|
row_item.append("\n".join(sale_num_list))
|
|
2322
3752
|
row_item.append("\n".join(sale_data_list))
|
|
2323
3753
|
row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
|
|
@@ -2333,3 +3763,763 @@ class SheinLib:
|
|
|
2333
3763
|
write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
|
|
2334
3764
|
|
|
2335
3765
|
return excel_data
|
|
3766
|
+
|
|
3767
|
+
def check_order_list(self, source, first_day, last_day):
|
|
3768
|
+
page_num = 1
|
|
3769
|
+
page_size = 200 # 列表最多返回200条数据 大了没有用
|
|
3770
|
+
|
|
3771
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{first_day}_{last_day}.json'
|
|
3772
|
+
list_item_cache = read_dict_from_file_ex(cache_file, self.store_username)
|
|
3773
|
+
|
|
3774
|
+
url = f"https://sso.geiwohuo.com/gsfs/finance/reportOrder/dualMode/checkOrderList/item/union"
|
|
3775
|
+
payload = {
|
|
3776
|
+
"page" : page_num,
|
|
3777
|
+
"perPage" : page_size,
|
|
3778
|
+
"detailAddTimeStart": f"{first_day} 00:00:00",
|
|
3779
|
+
"detailAddTimeEnd" : f"{last_day} 23:59:59"
|
|
3780
|
+
}
|
|
3781
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3782
|
+
error_code = response_text.get('code')
|
|
3783
|
+
if str(error_code) != '0':
|
|
3784
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
3785
|
+
list_item = response_text['info']['data']
|
|
3786
|
+
total = response_text['info']['meta']['count']
|
|
3787
|
+
totalPage = math.ceil(total / page_size)
|
|
3788
|
+
|
|
3789
|
+
log(self.store_name, self.store_username, total, len(list_item_cache))
|
|
3790
|
+
if int(total) == len(list_item_cache):
|
|
3791
|
+
log('总数与缓存数量相同 跳过剩余页抓取', total)
|
|
3792
|
+
return list_item_cache
|
|
3793
|
+
|
|
3794
|
+
for page in range(2, totalPage + 1):
|
|
3795
|
+
log(f'获取收支明细列表 第{page}/{totalPage}页')
|
|
3796
|
+
payload['page'] = page
|
|
3797
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3798
|
+
spu_list_new = response_text['info']['data']
|
|
3799
|
+
list_item += spu_list_new
|
|
3800
|
+
time.sleep(0.1)
|
|
3801
|
+
|
|
3802
|
+
for item in list_item:
|
|
3803
|
+
supplierSku = item['skuSn']
|
|
3804
|
+
item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
|
|
3805
|
+
item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
|
|
3806
|
+
|
|
3807
|
+
write_dict_to_file_ex(cache_file, {self.store_username: list_item}, [self.store_username])
|
|
3808
|
+
return list_item
|
|
3809
|
+
|
|
3810
|
+
def get_ab_test_list(self, status=4, test_type=2):
|
|
3811
|
+
"""
|
|
3812
|
+
获取AB测试列表
|
|
3813
|
+
|
|
3814
|
+
Args:
|
|
3815
|
+
status: 测试状态,可选值:
|
|
3816
|
+
4: 进行中
|
|
3817
|
+
test_type: 测试类型,可选值:
|
|
3818
|
+
2: skc测试
|
|
3819
|
+
|
|
3820
|
+
Returns:
|
|
3821
|
+
list: AB测试列表
|
|
3822
|
+
"""
|
|
3823
|
+
log(f'获取AB测试列表: status={status}, test_type={test_type}')
|
|
3824
|
+
|
|
3825
|
+
# 构建缓存文件名
|
|
3826
|
+
cache_key = f'{test_type}_{status}'
|
|
3827
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/ab_test_list_{self.store_username}_{cache_key}.json'
|
|
3828
|
+
ab_test_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 12)
|
|
3829
|
+
if len(ab_test_list) > 0:
|
|
3830
|
+
log('返回缓存数据: ', len(ab_test_list))
|
|
3831
|
+
return ab_test_list
|
|
3832
|
+
|
|
3833
|
+
page_num = 1
|
|
3834
|
+
page_size = 100
|
|
3835
|
+
|
|
3836
|
+
url = f"https://sso.geiwohuo.com/spmc-api-prefix/spmp/image/ab_test/get_test_list?page_num={page_num}&page_size={page_size}"
|
|
3837
|
+
payload = {}
|
|
3838
|
+
|
|
3839
|
+
# 添加可选参数
|
|
3840
|
+
if status is not None:
|
|
3841
|
+
payload["status"] = status
|
|
3842
|
+
if test_type is not None:
|
|
3843
|
+
payload["test_type"] = test_type
|
|
3844
|
+
|
|
3845
|
+
log(payload)
|
|
3846
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3847
|
+
error_code = response_text.get('code')
|
|
3848
|
+
if str(error_code) != '0':
|
|
3849
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
3850
|
+
|
|
3851
|
+
ab_test_list = response_text['info']['data']
|
|
3852
|
+
total = response_text['info']['meta']['count']
|
|
3853
|
+
totalPage = math.ceil(total / page_size)
|
|
3854
|
+
|
|
3855
|
+
for page in range(2, totalPage + 1):
|
|
3856
|
+
log(f'获取AB测试列表 第{page}/{totalPage}页')
|
|
3857
|
+
page_num = page
|
|
3858
|
+
url = f"https://sso.geiwohuo.com/spmc-api-prefix/spmp/image/ab_test/get_test_list?page_num={page_num}&page_size={page_size}"
|
|
3859
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3860
|
+
ab_test_list += response_text['info']['data']
|
|
3861
|
+
time.sleep(0.1)
|
|
3862
|
+
|
|
3863
|
+
write_dict_to_file_ex(cache_file, {self.store_username: ab_test_list}, [self.store_username])
|
|
3864
|
+
|
|
3865
|
+
for test_list in ab_test_list:
|
|
3866
|
+
test_task_id = test_list['test_task_id']
|
|
3867
|
+
skc = test_list['spu_or_skc_name']
|
|
3868
|
+
test_list['experimental_data'] = self.get_ab_test_result(test_task_id)
|
|
3869
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/ab_test_list_{skc}_{TimeUtils.today_date()}.json'
|
|
3870
|
+
write_dict_to_file(cache_file, test_list)
|
|
3871
|
+
|
|
3872
|
+
return ab_test_list
|
|
3873
|
+
|
|
3874
|
+
def get_ab_test_result(self, test_task_id):
|
|
3875
|
+
"""
|
|
3876
|
+
获取AB测试实验结果
|
|
3877
|
+
|
|
3878
|
+
Args:
|
|
3879
|
+
test_task_id: 测试任务ID
|
|
3880
|
+
|
|
3881
|
+
Returns:
|
|
3882
|
+
dict: 实验结果数据,包含:
|
|
3883
|
+
- control_group_data: 对照组数据
|
|
3884
|
+
- expose_uv: 曝光人数
|
|
3885
|
+
- cart_uv: 加购人数
|
|
3886
|
+
- other_click_uv: 其他点击人数
|
|
3887
|
+
- goods_uv: 商详访客
|
|
3888
|
+
- goods_cnt: 商品数量
|
|
3889
|
+
- click_rate: 点击率
|
|
3890
|
+
- cart_rate: 加购率
|
|
3891
|
+
- conversion_rate: 转化率
|
|
3892
|
+
- experiment_group_data: 实验组数据(字段同对照组)
|
|
3893
|
+
"""
|
|
3894
|
+
log(f'获取AB测试结果: test_task_id={test_task_id}')
|
|
3895
|
+
|
|
3896
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/ab_test_result_{test_task_id}.json'
|
|
3897
|
+
ab_test_result = read_dict_from_file(cache_file, 3600 * 12)
|
|
3898
|
+
if len(ab_test_result) > 0:
|
|
3899
|
+
log('返回缓存数据')
|
|
3900
|
+
return ab_test_result
|
|
3901
|
+
|
|
3902
|
+
url = f"https://sso.geiwohuo.com/spmc-api-prefix/spmp/image/ab_test/compare_experimental_data"
|
|
3903
|
+
payload = {
|
|
3904
|
+
"test_task_id": test_task_id
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3908
|
+
error_code = response_text.get('code')
|
|
3909
|
+
if str(error_code) != '0':
|
|
3910
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
3911
|
+
|
|
3912
|
+
ab_test_result = response_text['info']
|
|
3913
|
+
|
|
3914
|
+
write_dict_to_file(cache_file, ab_test_result)
|
|
3915
|
+
|
|
3916
|
+
return ab_test_result
|
|
3917
|
+
|
|
3918
|
+
def download_finance_details(self, download_dir, start_date, end_date, output_file_name=None):
|
|
3919
|
+
"""
|
|
3920
|
+
下载并处理财务收支明细文件
|
|
3921
|
+
|
|
3922
|
+
功能说明:
|
|
3923
|
+
- 自动下载财务收支明细文件(可能是zip或xlsx格式)
|
|
3924
|
+
- 如果是zip文件(数据超过5000条),自动解压并合并多个Excel
|
|
3925
|
+
- 如果是xlsx文件(数据少于5000条),直接处理
|
|
3926
|
+
- 在Excel开头添加3列:店铺账号、店铺名称、店长
|
|
3927
|
+
- 自动识别需要保持为字符串的列(如业务单号等)
|
|
3928
|
+
|
|
3929
|
+
Args:
|
|
3930
|
+
start_date: 开始日期,格式: YYYY-MM-DD
|
|
3931
|
+
end_date: 结束日期,格式: YYYY-MM-DD
|
|
3932
|
+
|
|
3933
|
+
Returns:
|
|
3934
|
+
str: 处理后的Excel文件路径
|
|
3935
|
+
"""
|
|
3936
|
+
import os
|
|
3937
|
+
import requests
|
|
3938
|
+
from datetime import datetime
|
|
3939
|
+
import zipfile
|
|
3940
|
+
import shutil
|
|
3941
|
+
import openpyxl
|
|
3942
|
+
from openpyxl import Workbook
|
|
3943
|
+
import pandas as pd
|
|
3944
|
+
|
|
3945
|
+
log(f'开始下载财务收支明细: {start_date} ~ {end_date}', self.store_username, self.store_name)
|
|
3946
|
+
|
|
3947
|
+
# 准备下载目录
|
|
3948
|
+
# download_dir = f'{self.config.auto_dir}/shein/finance_details'
|
|
3949
|
+
os.makedirs(download_dir, exist_ok=True)
|
|
3950
|
+
|
|
3951
|
+
# 最终输出文件路径
|
|
3952
|
+
if output_file_name is None:
|
|
3953
|
+
output_file_name = f'finance_details_{self.store_username}_{start_date}_{end_date}.xlsx'
|
|
3954
|
+
output_file_path = os.path.join(download_dir, output_file_name)
|
|
3955
|
+
|
|
3956
|
+
# 如果最终文件已存在,直接返回
|
|
3957
|
+
if os.path.exists(output_file_path):
|
|
3958
|
+
log(f'处理后的文件已存在,直接返回: {output_file_path}')
|
|
3959
|
+
return output_file_path
|
|
3960
|
+
|
|
3961
|
+
# 第一步:查询当前已有的任务列表(用于后续对比)
|
|
3962
|
+
log('步骤1: 查询当前已有的任务列表')
|
|
3963
|
+
url = "https://sso.geiwohuo.com/sso/common/fileExport/list"
|
|
3964
|
+
query_start_time = TimeUtils.get_past_nth_day(1, None, '%Y-%m-%d') # 查询最近1天的任务
|
|
3965
|
+
payload = {
|
|
3966
|
+
"page" : 1,
|
|
3967
|
+
"perPage" : 50,
|
|
3968
|
+
"fileStatusList" : [1], # 1-已生成
|
|
3969
|
+
"createTimeStart": f"{query_start_time} 00:00:00",
|
|
3970
|
+
"createTimeEnd" : f"{TimeUtils.today_date()} 23:59:59"
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3974
|
+
error_code = response_text.get('code')
|
|
3975
|
+
existing_task_ids = set()
|
|
3976
|
+
if str(error_code) == '0':
|
|
3977
|
+
data_list = response_text.get('info', {}).get('data', [])
|
|
3978
|
+
existing_task_ids = {item.get('id') for item in data_list if item.get('fileName') == '财务收支明细'}
|
|
3979
|
+
log(f'当前已有任务数量: {len(existing_task_ids)}')
|
|
3980
|
+
|
|
3981
|
+
# 第二步:记录当前时间并创建导出任务
|
|
3982
|
+
log('步骤2: 创建导出任务')
|
|
3983
|
+
task_create_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
3984
|
+
log(f'任务创建时间: {task_create_time}')
|
|
3985
|
+
|
|
3986
|
+
url = "https://sso.geiwohuo.com/gsfs/common/file/export/financeDetailsItem"
|
|
3987
|
+
payload = {
|
|
3988
|
+
"type" : 1,
|
|
3989
|
+
"mode" : 2,
|
|
3990
|
+
"detailAddTimeStart": f"{start_date} 00:00:00",
|
|
3991
|
+
"detailAddTimeEnd" : f"{end_date} 23:59:59"
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
response_text = fetch(self.web_page, url, payload)
|
|
3995
|
+
error_code = response_text.get('code')
|
|
3996
|
+
if str(error_code) != '0':
|
|
3997
|
+
# 检查是否是"暂无数据可导出"的情况,这是正常情况
|
|
3998
|
+
if str(error_code) == 'gsfs98008':
|
|
3999
|
+
log('暂无数据可导出,返回None')
|
|
4000
|
+
return None
|
|
4001
|
+
raise send_exception(f'创建导出任务失败: {json.dumps(response_text, ensure_ascii=False)}')
|
|
4002
|
+
|
|
4003
|
+
log('导出任务创建成功,等待文件生成...')
|
|
4004
|
+
|
|
4005
|
+
# 第三步:轮询查询任务状态,查找新创建的任务
|
|
4006
|
+
log('步骤3: 轮询查询任务状态')
|
|
4007
|
+
task_id = None
|
|
4008
|
+
file_extension = None
|
|
4009
|
+
max_retry = 60 # 最多查询60次(10分钟)
|
|
4010
|
+
retry_count = 0
|
|
4011
|
+
|
|
4012
|
+
# 使用任务创建时间作为查询起始时间(向前推1分钟以避免时间误差)
|
|
4013
|
+
create_time_obj = datetime.strptime(task_create_time, '%Y-%m-%d %H:%M:%S')
|
|
4014
|
+
from datetime import timedelta
|
|
4015
|
+
query_time_obj = create_time_obj - timedelta(minutes=3)
|
|
4016
|
+
query_start_time_str = query_time_obj.strftime('%Y-%m-%d %H:%M:%S')
|
|
4017
|
+
|
|
4018
|
+
while retry_count < max_retry:
|
|
4019
|
+
retry_count += 1
|
|
4020
|
+
time.sleep(10) # 每10秒查询一次
|
|
4021
|
+
|
|
4022
|
+
log(f'第{retry_count}次查询任务状态...', self.store_username, self.store_name)
|
|
4023
|
+
url = "https://sso.geiwohuo.com/sso/common/fileExport/list"
|
|
4024
|
+
payload = {
|
|
4025
|
+
"page" : 1,
|
|
4026
|
+
"perPage" : 50,
|
|
4027
|
+
"fileStatusList" : [1], # 1-已生成
|
|
4028
|
+
"createTimeStart": query_start_time_str,
|
|
4029
|
+
"createTimeEnd" : f"{TimeUtils.today_date()} 23:59:59"
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
log(payload)
|
|
4033
|
+
response_text = fetch(self.web_page, url, payload)
|
|
4034
|
+
log(response_text)
|
|
4035
|
+
error_code = response_text.get('code')
|
|
4036
|
+
if str(error_code) != '0':
|
|
4037
|
+
log(f'查询任务列表失败: {response_text}')
|
|
4038
|
+
continue
|
|
4039
|
+
|
|
4040
|
+
# 查找新出现的财务收支明细任务
|
|
4041
|
+
data_list = response_text.get('info', {}).get('data', [])
|
|
4042
|
+
for item in data_list:
|
|
4043
|
+
item_id = item.get('id')
|
|
4044
|
+
item_create_time = item.get('createTime')
|
|
4045
|
+
|
|
4046
|
+
log(item_id, existing_task_ids)
|
|
4047
|
+
# 条件:1.文件名匹配 2.状态为已生成 3.不在之前的任务列表中 4.创建时间在任务创建时间之后
|
|
4048
|
+
if (item.get('fileName') == '财务收支明细' and item.get('fileStatus') == 1 and item_id not in existing_task_ids):
|
|
4049
|
+
file_extension = item.get('fileExtension', 'xlsx') # 获取文件扩展名,默认xlsx
|
|
4050
|
+
log(f'找到新创建的任务: ID={item_id}, 创建时间={item_create_time}, 文件类型={file_extension}')
|
|
4051
|
+
task_id = item_id
|
|
4052
|
+
break
|
|
4053
|
+
|
|
4054
|
+
if task_id:
|
|
4055
|
+
log(f'任务已完成,任务ID: {task_id}')
|
|
4056
|
+
break
|
|
4057
|
+
|
|
4058
|
+
if not task_id:
|
|
4059
|
+
raise send_exception(f'导出任务超时,查询{max_retry}次后仍未完成')
|
|
4060
|
+
|
|
4061
|
+
# 第四步:获取下载地址
|
|
4062
|
+
log('步骤4: 获取文件下载地址')
|
|
4063
|
+
url = f"https://sso.geiwohuo.com/sso/common/fileExport/getFileUrl?id={task_id}"
|
|
4064
|
+
headers = {
|
|
4065
|
+
"gmpsso-language": "CN",
|
|
4066
|
+
"origin-url" : "https://sso.geiwohuo.com/#/download-management/list",
|
|
4067
|
+
"x-sso-scene" : "gmpsso"
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
fetch_config = {
|
|
4071
|
+
"credentials" : "include",
|
|
4072
|
+
"referrer" : "https://sso.geiwohuo.com/",
|
|
4073
|
+
"referrerPolicy": "strict-origin-when-cross-origin"
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
response_text = fetch_get(self.web_page, url, headers, fetch_config)
|
|
4077
|
+
error_code = response_text.get('code')
|
|
4078
|
+
if str(error_code) != '0':
|
|
4079
|
+
raise send_exception(f'获取下载地址失败: {json.dumps(response_text, ensure_ascii=False)}')
|
|
4080
|
+
|
|
4081
|
+
download_url = response_text.get('info', {}).get('url')
|
|
4082
|
+
if not download_url:
|
|
4083
|
+
raise send_exception('下载地址为空')
|
|
4084
|
+
|
|
4085
|
+
log(f'获取到下载地址: {download_url}')
|
|
4086
|
+
|
|
4087
|
+
# 第五步:下载文件
|
|
4088
|
+
log(f'步骤5: 下载文件到本地(文件类型: {file_extension})')
|
|
4089
|
+
|
|
4090
|
+
# 下载临时文件
|
|
4091
|
+
temp_file_name = f'finance_details_temp_{self.store_username}_{start_date}_{end_date}.{file_extension}'
|
|
4092
|
+
temp_file_path = os.path.join(download_dir, temp_file_name)
|
|
4093
|
+
|
|
4094
|
+
# 使用requests下载文件
|
|
4095
|
+
response = requests.get(download_url, stream=True)
|
|
4096
|
+
if response.status_code == 200:
|
|
4097
|
+
with open(temp_file_path, 'wb') as f:
|
|
4098
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
4099
|
+
f.write(chunk)
|
|
4100
|
+
log(f'文件下载成功: {temp_file_path}')
|
|
4101
|
+
else:
|
|
4102
|
+
raise send_exception(f'文件下载失败,状态码: {response.status_code}')
|
|
4103
|
+
|
|
4104
|
+
# 第六步:处理文件并添加店铺信息列
|
|
4105
|
+
log('步骤6: 处理文件并添加店铺信息列')
|
|
4106
|
+
|
|
4107
|
+
# 从文件名中提取store_username
|
|
4108
|
+
store_username = self.store_username
|
|
4109
|
+
|
|
4110
|
+
# 自动识别需要保持为字符串的列(包含以下关键词的列保持为字符串)
|
|
4111
|
+
str_keywords = ['业务单号']
|
|
4112
|
+
|
|
4113
|
+
all_data = []
|
|
4114
|
+
header = None
|
|
4115
|
+
dtype_dict = None
|
|
4116
|
+
|
|
4117
|
+
if file_extension == 'zip':
|
|
4118
|
+
# 处理zip文件:解压并合并多个Excel
|
|
4119
|
+
log('文件类型为zip,开始解压和合并...')
|
|
4120
|
+
|
|
4121
|
+
# 解压到临时目录
|
|
4122
|
+
extract_dir = os.path.join(download_dir, 'temp')
|
|
4123
|
+
os.makedirs(extract_dir, exist_ok=True)
|
|
4124
|
+
|
|
4125
|
+
log(f'解压文件到: {extract_dir}')
|
|
4126
|
+
with zipfile.ZipFile(temp_file_path, 'r') as zip_ref:
|
|
4127
|
+
zip_ref.extractall(extract_dir)
|
|
4128
|
+
|
|
4129
|
+
# 查找所有excel文件
|
|
4130
|
+
excel_files = []
|
|
4131
|
+
for root, dirs, files in os.walk(extract_dir):
|
|
4132
|
+
for file in files:
|
|
4133
|
+
if file.endswith(('.xlsx', '.xls')):
|
|
4134
|
+
excel_files.append(os.path.join(root, file))
|
|
4135
|
+
|
|
4136
|
+
log(f'找到 {len(excel_files)} 个excel文件')
|
|
4137
|
+
|
|
4138
|
+
if len(excel_files) == 0:
|
|
4139
|
+
raise Exception('zip文件中未找到excel文件')
|
|
4140
|
+
|
|
4141
|
+
# 读取并合并所有excel数据
|
|
4142
|
+
for idx, excel_file in enumerate(excel_files):
|
|
4143
|
+
log(f'读取文件 {idx + 1}/{len(excel_files)}: {os.path.basename(excel_file)}')
|
|
4144
|
+
|
|
4145
|
+
try:
|
|
4146
|
+
# 第一次读取时,确定需要保持为字符串的列
|
|
4147
|
+
if idx == 0:
|
|
4148
|
+
df_temp = pd.read_excel(excel_file, sheet_name=0, nrows=0)
|
|
4149
|
+
all_columns = df_temp.columns.tolist()
|
|
4150
|
+
|
|
4151
|
+
# 自动识别字符串列
|
|
4152
|
+
str_columns = []
|
|
4153
|
+
for col in all_columns:
|
|
4154
|
+
col_str = str(col)
|
|
4155
|
+
if any(keyword in col_str for keyword in str_keywords):
|
|
4156
|
+
str_columns.append(col)
|
|
4157
|
+
|
|
4158
|
+
if str_columns:
|
|
4159
|
+
log(f'自动识别需要保持为字符串的列: {str_columns}')
|
|
4160
|
+
dtype_dict = {col: str for col in str_columns}
|
|
4161
|
+
|
|
4162
|
+
# 使用pandas读取excel,指定特定列为字符串类型
|
|
4163
|
+
df = pd.read_excel(excel_file, sheet_name=0, dtype=dtype_dict)
|
|
4164
|
+
log(f'pandas读取成功,数据形状: {df.shape} (行数×列数)')
|
|
4165
|
+
|
|
4166
|
+
# 获取表头
|
|
4167
|
+
if idx == 0:
|
|
4168
|
+
header = df.columns.tolist()
|
|
4169
|
+
log(f'表头: {header[:5]}... (显示前5列)')
|
|
4170
|
+
|
|
4171
|
+
# 获取数据
|
|
4172
|
+
data_rows = df.values.tolist()
|
|
4173
|
+
all_data.extend(data_rows)
|
|
4174
|
+
log(f'第{idx + 1}个文件添加了 {len(data_rows)} 行数据')
|
|
4175
|
+
|
|
4176
|
+
except Exception as e:
|
|
4177
|
+
log(f'pandas读取失败: {e},尝试使用openpyxl读取')
|
|
4178
|
+
# 备用方案:使用openpyxl
|
|
4179
|
+
wb = openpyxl.load_workbook(excel_file, read_only=True, data_only=True)
|
|
4180
|
+
|
|
4181
|
+
if '财务收支明细' in wb.sheetnames:
|
|
4182
|
+
ws = wb['财务收支明细']
|
|
4183
|
+
else:
|
|
4184
|
+
ws = wb.worksheets[0]
|
|
4185
|
+
|
|
4186
|
+
log(f'使用工作表: {ws.title}')
|
|
4187
|
+
rows = list(ws.iter_rows(values_only=True))
|
|
4188
|
+
|
|
4189
|
+
if idx == 0 and len(rows) > 0:
|
|
4190
|
+
header = list(rows[0])
|
|
4191
|
+
all_data.extend(rows[1:])
|
|
4192
|
+
elif len(rows) > 1:
|
|
4193
|
+
all_data.extend(rows[1:])
|
|
4194
|
+
|
|
4195
|
+
wb.close()
|
|
4196
|
+
|
|
4197
|
+
# 清理临时解压目录
|
|
4198
|
+
shutil.rmtree(extract_dir)
|
|
4199
|
+
log('临时解压目录已清理')
|
|
4200
|
+
|
|
4201
|
+
else:
|
|
4202
|
+
# 处理单个xlsx文件
|
|
4203
|
+
log('文件类型为xlsx,直接读取...')
|
|
4204
|
+
|
|
4205
|
+
try:
|
|
4206
|
+
# 确定需要保持为字符串的列
|
|
4207
|
+
df_temp = pd.read_excel(temp_file_path, sheet_name=0, nrows=0)
|
|
4208
|
+
all_columns = df_temp.columns.tolist()
|
|
4209
|
+
|
|
4210
|
+
str_columns = []
|
|
4211
|
+
for col in all_columns:
|
|
4212
|
+
col_str = str(col)
|
|
4213
|
+
if any(keyword in col_str for keyword in str_keywords):
|
|
4214
|
+
str_columns.append(col)
|
|
4215
|
+
|
|
4216
|
+
if str_columns:
|
|
4217
|
+
log(f'自动识别需要保持为字符串的列: {str_columns}')
|
|
4218
|
+
dtype_dict = {col: str for col in str_columns}
|
|
4219
|
+
|
|
4220
|
+
# 读取excel
|
|
4221
|
+
df = pd.read_excel(temp_file_path, sheet_name=0, dtype=dtype_dict)
|
|
4222
|
+
log(f'pandas读取成功,数据形状: {df.shape} (行数×列数)')
|
|
4223
|
+
|
|
4224
|
+
header = df.columns.tolist()
|
|
4225
|
+
log(f'表头: {header[:5]}... (显示前5列)')
|
|
4226
|
+
|
|
4227
|
+
all_data = df.values.tolist()
|
|
4228
|
+
log(f'读取了 {len(all_data)} 行数据')
|
|
4229
|
+
|
|
4230
|
+
except Exception as e:
|
|
4231
|
+
log(f'pandas读取失败: {e},尝试使用openpyxl读取')
|
|
4232
|
+
# 备用方案
|
|
4233
|
+
wb = openpyxl.load_workbook(temp_file_path, read_only=True, data_only=True)
|
|
4234
|
+
|
|
4235
|
+
if '财务收支明细' in wb.sheetnames:
|
|
4236
|
+
ws = wb['财务收支明细']
|
|
4237
|
+
else:
|
|
4238
|
+
ws = wb.worksheets[0]
|
|
4239
|
+
|
|
4240
|
+
rows = list(ws.iter_rows(values_only=True))
|
|
4241
|
+
if len(rows) > 0:
|
|
4242
|
+
header = list(rows[0])
|
|
4243
|
+
all_data = rows[1:]
|
|
4244
|
+
|
|
4245
|
+
wb.close()
|
|
4246
|
+
|
|
4247
|
+
log(f'合并完成,共 {len(all_data)} 行数据')
|
|
4248
|
+
|
|
4249
|
+
# 在表头前添加3列
|
|
4250
|
+
new_header = ['店铺账号', '店铺名称', '店长'] + header
|
|
4251
|
+
|
|
4252
|
+
# 在每行数据前添加3列
|
|
4253
|
+
new_data = []
|
|
4254
|
+
for row in all_data:
|
|
4255
|
+
# 将 tuple 转换为 list,并在前面添加3列
|
|
4256
|
+
new_row = [store_username, '', ''] + list(row)
|
|
4257
|
+
new_data.append(new_row)
|
|
4258
|
+
|
|
4259
|
+
# 创建新的工作簿并写入数据
|
|
4260
|
+
log(f'写入合并后的excel: {output_file_path}')
|
|
4261
|
+
wb_new = Workbook()
|
|
4262
|
+
ws_new = wb_new.active
|
|
4263
|
+
ws_new.title = '财务收支明细'
|
|
4264
|
+
|
|
4265
|
+
# 写入表头
|
|
4266
|
+
ws_new.append(new_header)
|
|
4267
|
+
|
|
4268
|
+
# 写入数据
|
|
4269
|
+
for row_data in new_data:
|
|
4270
|
+
ws_new.append(row_data)
|
|
4271
|
+
|
|
4272
|
+
# 保存文件(显式转换为str以避免类型提示警告)
|
|
4273
|
+
wb_new.save(str(output_file_path))
|
|
4274
|
+
log(f'合并完成,文件已保存: {output_file_path}')
|
|
4275
|
+
|
|
4276
|
+
# 删除临时下载文件
|
|
4277
|
+
if os.path.exists(temp_file_path):
|
|
4278
|
+
os.remove(temp_file_path)
|
|
4279
|
+
log('临时下载文件已清理')
|
|
4280
|
+
|
|
4281
|
+
return output_file_path
|
|
4282
|
+
|
|
4283
|
+
def query_hosting_info_list(self):
|
|
4284
|
+
"""
|
|
4285
|
+
查询店铺活动托管规则列表
|
|
4286
|
+
|
|
4287
|
+
Returns:
|
|
4288
|
+
list: 托管规则列表,每个元素包含:
|
|
4289
|
+
- hosting_id: 托管规则ID
|
|
4290
|
+
- scene_type: 场景类型
|
|
4291
|
+
- state: 状态
|
|
4292
|
+
- hosting_name: 托管规则名称
|
|
4293
|
+
- hosting_tools_id: 托管工具ID
|
|
4294
|
+
- hosting_tools_state: 托管工具状态
|
|
4295
|
+
- time_zone: 时区
|
|
4296
|
+
- create_user: 创建用户
|
|
4297
|
+
- last_update_user: 最后更新用户
|
|
4298
|
+
- insert_time: 创建时间
|
|
4299
|
+
- last_update_time: 最后更新时间
|
|
4300
|
+
- exist_act_goods: 是否存在活动商品
|
|
4301
|
+
"""
|
|
4302
|
+
log(f'正在获取 {self.store_name} 店铺活动托管规则列表')
|
|
4303
|
+
|
|
4304
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/hosting_info_list_{self.store_username}_{TimeUtils.today_date()}.json'
|
|
4305
|
+
hosting_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 8)
|
|
4306
|
+
if len(hosting_list) > 0:
|
|
4307
|
+
log('返回缓存数据')
|
|
4308
|
+
return hosting_list
|
|
4309
|
+
|
|
4310
|
+
url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/hosting/query_hosting_info_list"
|
|
4311
|
+
payload = {}
|
|
4312
|
+
|
|
4313
|
+
response_text = fetch(self.web_page, url, payload)
|
|
4314
|
+
error_code = response_text.get('code')
|
|
4315
|
+
if str(error_code) != '0':
|
|
4316
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
4317
|
+
|
|
4318
|
+
hosting_list = response_text.get('info', [])
|
|
4319
|
+
log(f'获取到 {len(hosting_list)} 条托管规则')
|
|
4320
|
+
|
|
4321
|
+
write_dict_to_file_ex(cache_file, {self.store_username: hosting_list}, [self.store_username])
|
|
4322
|
+
|
|
4323
|
+
return hosting_list
|
|
4324
|
+
|
|
4325
|
+
def query_hosting_activity_goods(self, hosting_id, goods_states=None):
|
|
4326
|
+
"""
|
|
4327
|
+
查询托管活动参与的商品
|
|
4328
|
+
|
|
4329
|
+
Args:
|
|
4330
|
+
hosting_id: 托管规则ID
|
|
4331
|
+
goods_states: 商品状态列表,默认为[1](在售)
|
|
4332
|
+
|
|
4333
|
+
Returns:
|
|
4334
|
+
list: 参与托管活动的商品列表,每个元素包含:
|
|
4335
|
+
- goods_state: 商品状态
|
|
4336
|
+
- skc_info_list: SKC信息列表
|
|
4337
|
+
- skc_id: SKC ID
|
|
4338
|
+
- skc_name: SKC名称
|
|
4339
|
+
- goods_name: 商品名称
|
|
4340
|
+
- image_url: 图片URL
|
|
4341
|
+
- act_stock_num: 活动库存数量
|
|
4342
|
+
- act_sales_num: 活动销量
|
|
4343
|
+
- activity_info: 活动信息
|
|
4344
|
+
- sku_info_list: SKU信息列表
|
|
4345
|
+
"""
|
|
4346
|
+
if goods_states is None:
|
|
4347
|
+
goods_states = [1]
|
|
4348
|
+
|
|
4349
|
+
log(f'正在获取 {self.store_name} 托管活动商品列表 hosting_id={hosting_id}')
|
|
4350
|
+
|
|
4351
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/hosting_activity_goods_{self.store_username}_{hosting_id}_{TimeUtils.today_date()}.json'
|
|
4352
|
+
goods_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 8)
|
|
4353
|
+
if len(goods_list) > 0:
|
|
4354
|
+
log('返回缓存数据')
|
|
4355
|
+
return goods_list
|
|
4356
|
+
|
|
4357
|
+
page_num = 1
|
|
4358
|
+
page_size = 100
|
|
4359
|
+
|
|
4360
|
+
url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/hosting/query_hosting_activity_goods"
|
|
4361
|
+
payload = {
|
|
4362
|
+
"goods_states": goods_states,
|
|
4363
|
+
"hosting_id" : str(hosting_id),
|
|
4364
|
+
"page_num" : page_num,
|
|
4365
|
+
"page_size" : page_size
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
response_text = fetch(self.web_page, url, payload)
|
|
4369
|
+
error_code = response_text.get('code')
|
|
4370
|
+
if str(error_code) != '0':
|
|
4371
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
4372
|
+
|
|
4373
|
+
goods_list = response_text.get('info', [])
|
|
4374
|
+
if not goods_list:
|
|
4375
|
+
log('未获取到商品数据')
|
|
4376
|
+
return []
|
|
4377
|
+
|
|
4378
|
+
# 获取第一页数据
|
|
4379
|
+
first_item = goods_list[0] if goods_list else {}
|
|
4380
|
+
skc_info_list = first_item.get('skc_info_list', {})
|
|
4381
|
+
all_data = skc_info_list.get('data', [])
|
|
4382
|
+
meta = skc_info_list.get('meta', {})
|
|
4383
|
+
total = meta.get('count', 0)
|
|
4384
|
+
|
|
4385
|
+
log(f'第1页获取到 {len(all_data)} 条商品,总数: {total}')
|
|
4386
|
+
|
|
4387
|
+
# 如果有多页,继续获取
|
|
4388
|
+
if total > page_size:
|
|
4389
|
+
totalPage = math.ceil(total / page_size)
|
|
4390
|
+
for page in range(2, totalPage + 1):
|
|
4391
|
+
log(f'获取托管活动商品列表 第{page}/{totalPage}页')
|
|
4392
|
+
payload['page_num'] = page
|
|
4393
|
+
response_text = fetch(self.web_page, url, payload)
|
|
4394
|
+
error_code = response_text.get('code')
|
|
4395
|
+
if str(error_code) != '0':
|
|
4396
|
+
log(f'获取第{page}页失败: {response_text}')
|
|
4397
|
+
continue
|
|
4398
|
+
|
|
4399
|
+
page_goods_list = response_text.get('info', [])
|
|
4400
|
+
if page_goods_list:
|
|
4401
|
+
page_data = page_goods_list[0].get('skc_info_list', {}).get('data', [])
|
|
4402
|
+
all_data.extend(page_data)
|
|
4403
|
+
log(f'第{page}页获取到 {len(page_data)} 条商品')
|
|
4404
|
+
|
|
4405
|
+
time.sleep(0.1)
|
|
4406
|
+
|
|
4407
|
+
log(f'总共获取到 {len(all_data)} 条商品')
|
|
4408
|
+
|
|
4409
|
+
# 保存缓存
|
|
4410
|
+
write_dict_to_file_ex(cache_file, {self.store_username: all_data}, [self.store_username])
|
|
4411
|
+
|
|
4412
|
+
return all_data
|
|
4413
|
+
|
|
4414
|
+
def get_skc_activity_price_info(self, skc, activity_id):
|
|
4415
|
+
"""
|
|
4416
|
+
根据SKC和活动ID获取供货价、活动价和活动库存
|
|
4417
|
+
|
|
4418
|
+
Args:
|
|
4419
|
+
skc: SKC名称
|
|
4420
|
+
activity_id: 活动ID(可以是字符串或整数)
|
|
4421
|
+
|
|
4422
|
+
Returns:
|
|
4423
|
+
dict: 包含以下键值的字典,如果未找到则返回None:
|
|
4424
|
+
- sku_price: SKU供货价(取第一个SKU的价格)
|
|
4425
|
+
- act_sku_price: SKU活动价(取第一个SKU的活动价)
|
|
4426
|
+
- act_stock_num: 活动库存数量
|
|
4427
|
+
- skc_name: SKC名称
|
|
4428
|
+
- goods_name: 商品名称
|
|
4429
|
+
- activity_id: 活动ID
|
|
4430
|
+
- currency: 币种
|
|
4431
|
+
- image_url: 商品图片
|
|
4432
|
+
"""
|
|
4433
|
+
log(f'获取SKC活动价格信息: skc={skc}, activity_id={activity_id}')
|
|
4434
|
+
|
|
4435
|
+
# 转换activity_id为整数进行比较
|
|
4436
|
+
try:
|
|
4437
|
+
target_activity_id = int(activity_id)
|
|
4438
|
+
except (ValueError, TypeError):
|
|
4439
|
+
log(f'无效的activity_id: {activity_id}')
|
|
4440
|
+
return None
|
|
4441
|
+
|
|
4442
|
+
# 缓存文件,使用skc和activity_id作为缓存key
|
|
4443
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/skc_activity_price_{self.store_username}_{skc}_{activity_id}_{TimeUtils.today_date()}.json'
|
|
4444
|
+
cached_data = read_dict_from_file(cache_file, 3600 * 8)
|
|
4445
|
+
if cached_data:
|
|
4446
|
+
log('返回缓存的价格信息')
|
|
4447
|
+
return cached_data
|
|
4448
|
+
|
|
4449
|
+
# 获取所有托管规则
|
|
4450
|
+
hosting_list = self.query_hosting_info_list()
|
|
4451
|
+
|
|
4452
|
+
if not hosting_list:
|
|
4453
|
+
log('未找到任何托管规则')
|
|
4454
|
+
return None
|
|
4455
|
+
|
|
4456
|
+
# 遍历所有托管规则,查找匹配的SKC和活动
|
|
4457
|
+
for hosting in hosting_list:
|
|
4458
|
+
hosting_id = hosting.get('hosting_id')
|
|
4459
|
+
if not hosting_id:
|
|
4460
|
+
continue
|
|
4461
|
+
|
|
4462
|
+
log(f'查询托管规则: hosting_id={hosting_id}, hosting_name={hosting.get("hosting_name")}')
|
|
4463
|
+
|
|
4464
|
+
# 获取该托管规则下的商品
|
|
4465
|
+
goods_list = self.query_hosting_activity_goods(hosting_id)
|
|
4466
|
+
|
|
4467
|
+
# 在商品列表中查找匹配的SKC
|
|
4468
|
+
for goods_item in goods_list:
|
|
4469
|
+
skc_name = goods_item.get('skc_name', '')
|
|
4470
|
+
|
|
4471
|
+
# 匹配SKC名称
|
|
4472
|
+
if skc_name != skc:
|
|
4473
|
+
continue
|
|
4474
|
+
|
|
4475
|
+
# 检查活动信息
|
|
4476
|
+
activity_info = goods_item.get('activity_info', {})
|
|
4477
|
+
goods_activity_id = activity_info.get('activity_id')
|
|
4478
|
+
|
|
4479
|
+
# 匹配活动ID
|
|
4480
|
+
try:
|
|
4481
|
+
if int(goods_activity_id) != target_activity_id:
|
|
4482
|
+
continue
|
|
4483
|
+
except (ValueError, TypeError):
|
|
4484
|
+
continue
|
|
4485
|
+
|
|
4486
|
+
log(f'找到匹配的SKC: {skc_name}, activity_id={goods_activity_id}')
|
|
4487
|
+
|
|
4488
|
+
# 提取活动库存
|
|
4489
|
+
act_stock_num = goods_item.get('act_stock_num', 0)
|
|
4490
|
+
|
|
4491
|
+
# 获取第一个SKU的价格信息
|
|
4492
|
+
sku_info_list = goods_item.get('sku_info_list', [])
|
|
4493
|
+
if not sku_info_list:
|
|
4494
|
+
log(f'SKC {skc_name} 没有SKU信息')
|
|
4495
|
+
continue
|
|
4496
|
+
|
|
4497
|
+
first_sku = sku_info_list[0]
|
|
4498
|
+
sku_price = first_sku.get('sku_price', 0)
|
|
4499
|
+
act_sku_price = first_sku.get('act_sku_price', 0)
|
|
4500
|
+
currency = first_sku.get('currency', 'CNY')
|
|
4501
|
+
|
|
4502
|
+
# 构建返回结果
|
|
4503
|
+
result = {
|
|
4504
|
+
'skc_name' : skc_name,
|
|
4505
|
+
'goods_name' : goods_item.get('goods_name', ''),
|
|
4506
|
+
'image_url' : goods_item.get('image_url', ''),
|
|
4507
|
+
'activity_id' : goods_activity_id,
|
|
4508
|
+
'act_stock_num': act_stock_num,
|
|
4509
|
+
'sku_price' : sku_price,
|
|
4510
|
+
'act_sku_price': act_sku_price,
|
|
4511
|
+
'currency' : currency,
|
|
4512
|
+
'start_time' : activity_info.get('start_time', ''),
|
|
4513
|
+
'end_time' : activity_info.get('end_time', ''),
|
|
4514
|
+
'time_zone' : activity_info.get('time_zone', ''),
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
log(f'SKC供货价: {sku_price} {currency}, 活动价: {act_sku_price} {currency}, 活动库存: {act_stock_num}')
|
|
4518
|
+
|
|
4519
|
+
# 保存缓存
|
|
4520
|
+
write_dict_to_file(cache_file, result)
|
|
4521
|
+
|
|
4522
|
+
return result
|
|
4523
|
+
|
|
4524
|
+
log(f'未找到匹配的SKC和活动: skc={skc}, activity_id={activity_id}')
|
|
4525
|
+
return None
|