qrpa 1.0.13__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/shein_lib.py CHANGED
@@ -1,13 +1,19 @@
1
- from qrpa import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
2
- from qrpa import log, fetch, send_exception, md5_string
3
- from qrpa import time_utils, get_safe_value
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, NetWorkIdleTimeout
3
+ from .fun_web import fetch, fetch_get, full_screen_shot, safe_goto
4
+ from .time_utils import TimeUtils
5
+ from .wxwork import WxWorkBot
6
+
7
+ from .shein_sqlite import insert_sales, get_last_week_sales, get_near_week_sales, get_near_month_sales, get_last_month_sales
4
8
 
5
9
  import math
6
10
  import time
7
- import json
11
+ import json, traceback
8
12
  from datetime import datetime
9
13
  from playwright.sync_api import Page
10
14
 
15
+ from .shein_daily_report_model import SheinStoreSalesDetailManager
16
+
11
17
  class SheinLib:
12
18
 
13
19
  def __init__(self, config, bridge, web_page: Page, store_username, store_name):
@@ -17,69 +23,2485 @@ class SheinLib:
17
23
  self.store_name = store_name
18
24
  self.web_page = web_page
19
25
  self.dt = None
26
+ self.dt_goods = None
27
+ self.DictQueryTime = {}
20
28
 
21
29
  self.deal_auth()
30
+ self.get_user()
22
31
 
23
32
  # 处理鉴权
24
33
  def deal_auth(self):
25
34
  web_page = self.web_page
26
- while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
27
- if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
28
- web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
29
- log("鉴权确定按钮可见 点击'确定'按钮")
30
- web_page.wait_for_load_state("load")
31
- web_page.wait_for_timeout(1000)
32
- # time.sleep(1)
33
- if web_page.locator('//input[@name="username"]').is_visible():
34
- log("用户名输入框可见 等待5秒点击'登录'按钮")
35
- web_page.wait_for_timeout(5000)
36
- log('点击"登录"')
37
- web_page.locator('//button[contains(@class,"login_btn")]').click()
38
- web_page.wait_for_load_state("load")
39
- log('再延时5秒')
40
- web_page.wait_for_timeout(5000)
41
- if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
42
- log('商品管理菜单可见 退出鉴权处理')
43
- return
44
- log('商家后台不可见', web_page.title(), web_page.url)
45
- web_page.wait_for_load_state("load")
46
- # time.sleep(1)
47
- web_page.wait_for_timeout(1000)
48
- if 'SHEIN全球商家中心' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
49
- log('SHEIN全球商家中心 中断循环')
50
- break
51
- if '后台首页' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
52
- log('后台首页 中断循环')
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)
53
56
  break
54
- if '商家后台' in web_page.title() and 'https://sso.geiwohuo.com/#/home' in web_page.url:
55
- log('后台首页 中断循环')
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
+
74
+ # 定义最大重试次数
75
+ MAX_RETRIES = 5
76
+ retries = 0
77
+ wait_count = 0
78
+ is_send = False
79
+
80
+ # close_modal(web_page) # 不能开启 需要勾选协议弹窗
81
+
82
+ while retries < MAX_RETRIES:
83
+ try:
84
+ retries += 1
85
+
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()
107
+
108
+ if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
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)
113
+
114
+ while web_page.locator('//div[text()="验证码"]').is_visible():
115
+ log(f'等待输入验证码: {wait_count}', self.store_username, self.store_name)
116
+ if not is_send:
117
+ is_send = True
118
+ img_path = full_screen_shot(web_page, self.config)
119
+ WxWorkBot(self.config.wxwork_bot_exception).send_img(img_path)
120
+ WxWorkBot(self.config.wxwork_bot_exception).send_text(f'{self.store_username},{self.store_name} 需要登录验证码')
121
+ time.sleep(5)
122
+ wait_count += 1
123
+
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():
140
+ log("用户名输入框可见 等待5秒点击'登录'按钮", self.store_username, self.store_name)
141
+ web_page.wait_for_timeout(5000)
142
+ log('点击"登录"', self.store_username, self.store_name)
143
+ web_page.locator('//button[contains(@class,"login_btn")]').click()
144
+
145
+ log('再延时5秒', self.store_username, self.store_name)
146
+ web_page.wait_for_timeout(5000)
147
+
148
+ if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
149
+ log('商品管理菜单可见 退出鉴权处理', self.store_username, self.store_name)
150
+ return
151
+
152
+ log('商家后台不可见', web_page.title(), web_page.url, self.store_username, self.store_name)
153
+ if 'https://sso.geiwohuo.com/#/home' in web_page.url:
154
+ web_page.wait_for_timeout(5000)
155
+ web_page.reload()
156
+
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
177
+
178
+ if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
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)
186
+
187
+ if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
188
+ log('检测到鉴权 刷新页面', self.store_username, self.store_name)
189
+ web_page.reload()
190
+ web_page.wait_for_timeout(5000)
191
+ web_page.reload()
192
+ web_page.wait_for_timeout(5000)
193
+
194
+ if web_page.title() == 'SHEIN':
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
+
56
203
  break
57
- if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
58
- web_page.goto('https://sso.geiwohuo.com/#/home')
59
- web_page.wait_for_load_state("load")
60
- web_page.wait_for_timeout(3000)
61
- # time.sleep(3)
62
- if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
63
- log('检测到鉴权 刷新页面')
64
- web_page.reload()
65
- web_page.wait_for_load_state('load')
66
- # time.sleep(3)
67
- web_page.wait_for_timeout(3000)
68
- web_page.reload()
69
- # time.sleep(3)
70
- web_page.wait_for_timeout(3000)
71
- if web_page.title() == 'SHEIN':
72
- web_page.goto('https://sso.geiwohuo.com/#/home')
73
- web_page.wait_for_load_state("load")
74
- # time.sleep(3)
75
- web_page.wait_for_timeout(3000)
76
-
77
- # web_page.goto('https://sso.geiwohuo.com')
204
+ except Exception as e:
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
237
+ retries += 1
238
+ if retries >= MAX_RETRIES:
239
+ log(f"达到最大重试次数,停止尝试({self.store_username}, {self.store_name})")
240
+ break
241
+ time.sleep(2) # 错误时等待2秒后重试
242
+
78
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
358
+
359
+ # 获取质检报告pdf地址
360
+ def get_qc_report_url(self, deliverCode, purchaseCode):
361
+ log(f'获取质检报告:{deliverCode} {purchaseCode}')
362
+ url = f"https://sso.geiwohuo.com/pfmp/returnPlan/queryQcReport"
363
+ payload = {
364
+ "deliverCode" : deliverCode,
365
+ "purchaseCode": purchaseCode
366
+ }
367
+ response_text = fetch(self.web_page, url, payload)
368
+ error_code = response_text.get('code')
369
+ if str(error_code) != '0':
370
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
371
+ qc_report_url = (response_text.get('info', {}).get('data') or [{'qcReportUrl': '质检报告生成中,请稍后查看'}])[0].get('qcReportUrl')
372
+ log(qc_report_url)
373
+ return qc_report_url
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
+
390
+ def get_return_order_box_detail(self, returnOrderId):
391
+ log(f'获取退货包裹详情: {returnOrderId}')
392
+ url = f"https://sso.geiwohuo.com/pfmp/returnOrder/getReturnOrderBoxDetail"
393
+ payload = {
394
+ "returnOrderId": returnOrderId,
395
+ "page" : 1,
396
+ "perPage" : 50
397
+ }
398
+ response_text = fetch(self.web_page, url, payload)
399
+ error_code = response_text.get('code')
400
+ if str(error_code) != '0':
401
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
402
+ list_item = response_text['info']['data']
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)
424
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
425
+ write_dict_to_file(cache_file, list_item)
426
+ return list_item
427
+
428
+ def get_return_order_list(self, start_date, end_date, only_yesterday=1):
429
+ log(f'获取退货列表: {self.store_username} {self.store_name} {start_date} {end_date}')
430
+
431
+ page_num = 1
432
+ page_size = 200 # 列表最多返回200条数据 大了没有用
433
+
434
+ url = f"https://sso.geiwohuo.com/pfmp/returnOrder/page"
435
+ payload = {
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
441
+ }
442
+ response_text = fetch(self.web_page, url, payload)
443
+ error_code = response_text.get('code')
444
+ if str(error_code) != '0':
445
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
446
+ list_item = response_text['info']['data']
447
+ total = response_text['info']['meta']['count']
448
+ totalPage = math.ceil(total / page_size)
449
+
450
+ for page in range(2, totalPage + 1):
451
+ log(f'获取退供列表 第{page}/{totalPage}页 共{total}条记录')
452
+ payload['page'] = page
453
+ response_text = fetch(self.web_page, url, payload)
454
+ spu_list_new = response_text['info']['data']
455
+ list_item += spu_list_new
456
+ time.sleep(0.1)
457
+
458
+ all_list_item = []
459
+ today_list_item = []
460
+ # 过滤 退货出库时间 是昨天的
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
+
479
+ has_valid_package = item.get('hasPackage') == 1
480
+ if has_valid_package:
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
484
+
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)
489
+
490
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
491
+ write_dict_to_file_ex(cache_file, {self.store_username: today_list_item}, [self.store_username])
492
+
493
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
494
+ write_dict_to_file_ex(cache_file, {self.store_username: all_list_item}, [self.store_username])
495
+
496
+ return list_item
497
+
498
+ # 获取希音退供明细 和 台账明细一个接口
499
+ def get_back_list(self, source='mb'):
500
+ page_num = 1
501
+ page_size = 200 # 列表最多返回200条数据 大了没有用
502
+
503
+ first_day, last_day = TimeUtils.get_last_month_range()
504
+
505
+ cache_file = f'{self.config.auto_dir}/shein/cache/return_detail_{self.store_username}_{first_day}_{last_day}.json'
506
+ list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
507
+ if len(list_item) > 0:
508
+ return list_item
509
+
510
+ url = f"https://sso.geiwohuo.com/mils/changeDetail/page"
511
+ payload = {
512
+ "displayChangeTypeList": ["10"],
513
+ "addTimeStart" : f"{first_day} 00:00:00",
514
+ "addTimeEnd" : f"{last_day} 23:59:59",
515
+ "pageNumber" : page_num,
516
+ "pageSize" : page_size,
517
+ "changeTypeIndex" : "2"
518
+ }
519
+ response_text = fetch(self.web_page, url, payload)
520
+ error_code = response_text.get('code')
521
+ if str(error_code) != '0':
522
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
523
+ list_item = response_text['info']['data']['list']
524
+ total = response_text['info']['data']['count']
525
+ totalPage = math.ceil(total / page_size)
526
+
527
+ for page in range(2, totalPage + 1):
528
+ log(f'获取台账明细列表 第{page}/{totalPage}页')
529
+ payload['pageNumber'] = page
530
+ response_text = fetch(self.web_page, url, payload)
531
+ spu_list_new = response_text['info']['data']['list']
532
+ list_item += spu_list_new
533
+ time.sleep(0.1)
534
+
535
+ # cost_price =
536
+ for item in list_item:
537
+ supplierSku = item['supplierSku']
538
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
539
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
540
+
541
+ write_dict_to_file(cache_file, list_item)
542
+
543
+ return list_item
544
+
545
+ # 不结算列表
546
+ def get_no_settlement_list(self, source='mb'):
547
+ page_num = 1
548
+ page_size = 200 # 列表最多返回200条数据 大了没有用
549
+
550
+ first_day, last_day = TimeUtils.get_last_month_range()
551
+
552
+ cache_file = f'{self.config.auto_dir}/shein/cache/no_settlement_{self.store_username}_{first_day}_{last_day}.json'
553
+ list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
554
+ if len(list_item) > 0:
555
+ return list_item
556
+
557
+ url = f"https://sso.geiwohuo.com/mils/changeDetail/page"
558
+ payload = {
559
+ "addTimeStart" : f"{first_day} 00:00:00",
560
+ "addTimeEnd" : f"{last_day} 23:59:59",
561
+ "pageNumber" : page_num,
562
+ "pageSize" : page_size,
563
+ "changeTypeIndex" : "2",
564
+ "settleTypeList" : ["1"], # 不结算
565
+ "displayChangeTypeList": ["6", "7", "9", "10", "11", "12", "13", "16", "18", "19"] # 出库
566
+ }
567
+ response_text = fetch(self.web_page, url, payload)
568
+ error_code = response_text.get('code')
569
+ if str(error_code) != '0':
570
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
571
+ list_item = response_text['info']['data']['list']
572
+ total = response_text['info']['data']['count']
573
+ totalPage = math.ceil(total / page_size)
574
+
575
+ for page in range(2, totalPage + 1):
576
+ log(f'获取台账明细列表 第{page}/{totalPage}页')
577
+ payload['pageNumber'] = page
578
+ response_text = fetch(self.web_page, url, payload)
579
+ spu_list_new = response_text['info']['data']['list']
580
+ list_item += spu_list_new
581
+ time.sleep(0.1)
582
+
583
+ # cost_price =
584
+ for item in list_item:
585
+ supplierSku = item['supplierSku']
586
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
587
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
588
+
589
+ write_dict_to_file(cache_file, list_item)
590
+
591
+ return list_item
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
+
640
+ def get_ledger_list(self, source='mb'):
641
+ page_num = 1
642
+ page_size = 200 # 列表最多返回200条数据 大了没有用
643
+
644
+ first_day, last_day = TimeUtils.get_last_month_range()
645
+
646
+ cache_file = f'{self.config.auto_dir}/shein/cache/sales_detail_{self.store_username}_{first_day}_{last_day}.json'
647
+ list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
648
+ if len(list_item) > 0:
649
+ return list_item
650
+
651
+ url = f"https://sso.geiwohuo.com/mils/changeDetail/page"
652
+ payload = {
653
+ "displayChangeTypeList": ["6", "7", "9", "10", "11", "12", "13", "16", "18", "19"], # 出库
654
+ "addTimeStart" : f"{first_day} 00:00:00",
655
+ "addTimeEnd" : f"{last_day} 23:59:59",
656
+ "pageNumber" : page_num,
657
+ "pageSize" : page_size,
658
+ "changeTypeIndex" : "2"
659
+ }
660
+ response_text = fetch(self.web_page, url, payload)
661
+ error_code = response_text.get('code')
662
+ if str(error_code) != '0':
663
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
664
+ list_item = response_text['info']['data']['list']
665
+ total = response_text['info']['data']['count']
666
+ totalPage = math.ceil(total / page_size)
667
+
668
+ for page in range(2, totalPage + 1):
669
+ log(f'获取台账明细列表 第{page}/{totalPage}页')
670
+ payload['pageNumber'] = page
671
+ response_text = fetch(self.web_page, url, payload)
672
+ spu_list_new = response_text['info']['data']['list']
673
+ list_item += spu_list_new
674
+ time.sleep(0.1)
675
+
676
+ # cost_price =
677
+ for item in list_item:
678
+ supplierSku = item['supplierSku']
679
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
680
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
681
+
682
+ write_dict_to_file(cache_file, list_item)
683
+
684
+ return list_item
685
+
686
+ def get_shein_stock_list(self, source='mb'):
687
+ page_num = 1
688
+ page_size = 200 # 列表最多返回200条数据 大了没有用
689
+
690
+ first_day, last_day = TimeUtils.get_last_month_range()
691
+
692
+ cache_file = f'{self.config.auto_dir}/shein/cache/stock_detail_{self.store_username}_{first_day}_{last_day}.json'
693
+ list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
694
+ if len(list_item) > 0:
695
+ return list_item
696
+
697
+ url = f"https://sso.geiwohuo.com/mils/report/month/detail/list"
698
+ payload = {
699
+ "reportDateStart": first_day,
700
+ "reportDateEnd" : last_day,
701
+ "pageNumber" : page_num,
702
+ "pageSize" : page_size,
703
+ }
704
+ response_text = fetch(self.web_page, url, payload)
705
+ error_code = response_text.get('code')
706
+ if str(error_code) != '0':
707
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
708
+ list_item = response_text['info']['data']['list']
709
+ total = response_text['info']['data']['count']
710
+ totalPage = math.ceil(total / page_size)
711
+
712
+ for page in range(2, totalPage + 1):
713
+ log(f'获取库存结余明细列表 第{page}/{totalPage}页')
714
+ page_num = page
715
+ payload = {
716
+ "reportDateStart": first_day,
717
+ "reportDateEnd" : last_day,
718
+ "pageNumber" : page_num,
719
+ "pageSize" : page_size,
720
+ }
721
+ response_text = fetch(self.web_page, url, payload)
722
+ spu_list_new = response_text['info']['data']['list']
723
+ list_item += spu_list_new
724
+ time.sleep(0.1)
725
+
726
+ for item in list_item:
727
+ supplierSku = item['supplierSku']
728
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
729
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
730
+
731
+ write_dict_to_file(cache_file, list_item)
732
+
733
+ return list_item
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
+
819
+ def get_replenish_list(self):
820
+ page_num = 1
821
+ page_size = 50
822
+ first_day, last_day = TimeUtils.get_last_month_range()
823
+
824
+ cache_file = f'{self.config.auto_dir}/shein/cache/replenish_list_{self.store_username}_{first_day}_{last_day}.json'
825
+ list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
826
+ if len(list_item) > 0:
827
+ return list_item
828
+
829
+ url = f"https://sso.geiwohuo.com/gsfs/finance/selfReplenish/list"
830
+ payload = {
831
+ "page" : page_num,
832
+ "perPage" : page_size,
833
+ "tabType" : 2,
834
+ "addTimeStart": f"{first_day} 00:00:00",
835
+ "addTimeEnd" : f"{last_day} 23:59:59"
836
+ }
837
+ response_text = fetch(self.web_page, url, payload)
838
+ error_code = response_text.get('code')
839
+ if str(error_code) != '0':
840
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
841
+ list_item = response_text['info']['data']
842
+ total = response_text['info']['meta']['count']
843
+ totalPage = math.ceil(total / page_size)
844
+
845
+ for page in range(2, totalPage + 1):
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})
1457
+ response_text = fetch(self.web_page, url, payload)
1458
+ spu_list_new = response_text['info']['data']
1459
+ spu_list += spu_list_new
1460
+ time.sleep(0.3)
1461
+
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)
1507
+
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)
1510
+
1511
+ return spu_list
1512
+
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}')
1516
+
1517
+ dict_skc_shelf_date = {}
1518
+
1519
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
1520
+ pageNumber = 1
1521
+ pageSize = 100
1522
+ payload = {
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
1556
+ }
1557
+ response_text = fetch(self.web_page, url, payload)
1558
+ error_code = response_text.get('code')
1559
+ if str(error_code) != '0':
1560
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1561
+
1562
+ spu_list = response_text['info']['list']
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)
1572
+ for page in range(2, totalPage + 1):
1573
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
1574
+ payload['pageNumber'] = page
1575
+ response_text = fetch(self.web_page, url, payload)
1576
+
1577
+ new_spu_list = response_text['info']['list']
1578
+ spu_list += new_spu_list
1579
+
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)
1585
+
1586
+ time.sleep(0.3)
1587
+
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])
1591
+
1592
+ for skc_item in spu_list:
1593
+ skc = skc_item['skc']
1594
+ shelfDate = skc_item['shelfDate']
1595
+ dict_skc_shelf_date[skc] = shelfDate
1596
+
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
1610
+ payload = {
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
1644
+ }
1645
+ response_text = fetch(self.web_page, url, payload)
1646
+ error_code = response_text.get('code')
1647
+ if str(error_code) != '0':
1648
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1649
+
1650
+ spu_list = response_text['info']['list']
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)
1661
+ for page in range(2, totalPage + 1):
1662
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
1663
+ payload['pageNumber'] = page
1664
+ response_text = fetch(self.web_page, url, payload)
1665
+
1666
+ new_spu_list = response_text['info']['list']
1667
+ spu_list += new_spu_list
1668
+
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"
1772
+ payload = {
1773
+ "metaIndexIds": [
1774
+ 298,
1775
+ 67,
1776
+ 70,
1777
+ 72
1778
+ ],
1779
+ }
1780
+ response_text = fetch(self.web_page, url, payload)
1781
+ error_code = response_text.get('code')
1782
+ if str(error_code) != '0':
1783
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1784
+ info = response_text.get('info')
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']
1798
+
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
1806
+
1807
+ def get_funds_data(self):
1808
+ log(f'正在获取 {self.store_name} 财务数据')
1809
+ url = "https://sso.geiwohuo.com/sso/homePage/dataOverview/v2/detail"
1810
+ payload = {
1811
+ "metaIndexIds": [
1812
+ 298,
1813
+ 67,
1814
+ 70,
1815
+ 72
1816
+ ],
1817
+ }
1818
+ response_text = fetch(self.web_page, url, payload)
1819
+ error_code = response_text.get('code')
1820
+ if str(error_code) != '0':
1821
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1822
+ info = response_text.get('info')
1823
+ num298 = 0 # 在途商品金额
1824
+ num67 = 0 # 在仓商品金额
1825
+ num70 = 0 # 待结算金额
1826
+ num72 = 0 # 可提现金额
1827
+ for item in info['list']:
1828
+ if item['metaIndexId'] == 298:
1829
+ num298 = item['count']
1830
+ if item['metaIndexId'] == 67:
1831
+ num67 = item['count']
1832
+ if item['metaIndexId'] == 70:
1833
+ num70 = item['count']
1834
+ if item['metaIndexId'] == 72:
1835
+ num72 = item['count']
1836
+
1837
+ outAmount = self.get_last_month_outbound_amount()
1838
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
1839
+ store_manager = dict_store.get(str(self.store_username).lower())
1840
+ NotifyItem = [f'{self.store_name}', self.store_username, store_manager, num298, num67, num70, num72, outAmount, '',
1841
+ TimeUtils.current_datetime()]
1842
+
1843
+ cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_{TimeUtils.today_date()}.json'
1844
+ write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, [self.store_username])
1845
+ return NotifyItem
1846
+
1847
+ def getQueryDate(self):
1848
+ query_time = self.DictQueryTime.get(self.store_username, None)
1849
+ if query_time is not None:
1850
+ log(f'从字典获取query_time: {query_time}')
1851
+ return query_time
1852
+ log('获取日期范围')
1853
+ url = "https://sso.geiwohuo.com/mgs-api-prefix/estimate/queryDateRange"
1854
+ payload = {}
1855
+ response_text = fetch(self.web_page, url, payload)
1856
+ error_code = response_text.get('code')
1857
+ if str(error_code) != '0':
1858
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1859
+ query_time = response_text.get('info').get('quality_goods_query_time')
1860
+ self.DictQueryTime.update({self.store_username: query_time})
1861
+ log(f'query_time: {query_time}')
1862
+ return query_time
1863
+
1864
+ def get_goods_quality_estimate_list(self, query_date):
1865
+ cache_file = f'{self.config.auto_dir}/shein/dict/googs_estimate_{query_date}.json'
1866
+ estimate_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 8)
1867
+ if len(estimate_list) > 0:
1868
+ return estimate_list
1869
+
1870
+ page_num = 1
1871
+ page_size = 100
1872
+
1873
+ url = f"https://sso.geiwohuo.com/mgs-api-prefix/estimate/queryNewQualityGoodsList"
1874
+ payload = {
1875
+ "page_no" : page_num,
1876
+ "page_size" : page_size,
1877
+ "start_date": query_date,
1878
+ "end_date" : query_date,
1879
+ "order_col" : "skc_sale_cnt_14d",
1880
+ "order_type": "desc"
1881
+ }
1882
+ response_text = fetch(self.web_page, url, payload, {'lan': 'CN'})
1883
+ error_code = response_text.get('code')
1884
+ if str(error_code) != '0':
1885
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1886
+
1887
+ estimate_list = response_text['info']['data']
1888
+ total = response_text['info']['meta']['count']
1889
+ totalPage = math.ceil(total / page_size)
1890
+
1891
+ for page in range(2, totalPage + 1):
1892
+ log(f'获取质量评估列表 第{page}/{totalPage}页')
1893
+ page_num = page
1894
+ payload['page'] = page_num
1895
+ response_text = fetch(self.web_page, url, payload)
1896
+ estimate_list = response_text['info']['data']
1897
+ time.sleep(0.1)
1898
+
1899
+ write_dict_to_file_ex(cache_file, {self.store_username: estimate_list}, [self.store_username])
1900
+ return estimate_list
1901
+
1902
+ # 已上架备货款A数量
1903
+ def get_product_bak_A_count(self):
1904
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
1905
+ payload = {
1906
+ "pageNumber" : 1,
1907
+ "pageSize" : 10,
1908
+ "supplierCodes" : "",
1909
+ "skcs" : "",
1910
+ "spu" : "",
1911
+ "c7dSaleCntBegin" : "",
1912
+ "c7dSaleCntEnd" : "",
1913
+ "goodsLevelIdList" : [
1914
+ 61,
1915
+ 90
1916
+ ],
1917
+ "supplyStatus" : "",
1918
+ "shelfStatus" : 1,
1919
+ "categoryIdList" : [],
1920
+ "skcStockBegin" : "",
1921
+ "skcStockEnd" : "",
1922
+ "skuStockBegin" : "",
1923
+ "skuStockEnd" : "",
1924
+ "skcSaleDaysBegin" : "",
1925
+ "skcSaleDaysEnd" : "",
1926
+ "skuSaleDaysBegin" : "",
1927
+ "skuSaleDaysEnd" : "",
1928
+ "planUrgentCountBegin" : "",
1929
+ "planUrgentCountEnd" : "",
1930
+ "skcAvailableOrderBegin": "",
1931
+ "skcAvailableOrderEnd" : "",
1932
+ "skuAvailableOrderBegin": "",
1933
+ "skuAvailableOrderEnd" : "",
1934
+ "shelfDateBegin" : "",
1935
+ "shelfDateEnd" : "",
1936
+ "stockWarnStatusList" : [],
1937
+ "labelFakeIdList" : [],
1938
+ "sheinSaleByInventory" : "",
1939
+ "tspIdList" : [],
1940
+ "adviceStatus" : [],
1941
+ "sortBy7dSaleCnt" : 2,
1942
+ "goodsLevelFakeIdList" : [
1943
+ 3
1944
+ ]
1945
+ }
1946
+ response_text = fetch(self.web_page, url, payload)
1947
+ error_code = response_text.get('code')
1948
+ if str(error_code) != '0':
1949
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
1950
+ info = response_text.get('info')
1951
+ count = info.get('count', 0)
1952
+ log('获取已上架备货款A数量', count)
1953
+ return count
1954
+
1955
+ # 已上架备货款B数量
1956
+ def get_product_bak_B_count(self):
1957
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
1958
+ payload = {
1959
+ "pageNumber" : 1,
1960
+ "pageSize" : 10,
1961
+ "supplierCodes" : "",
1962
+ "skcs" : "",
1963
+ "spu" : "",
1964
+ "c7dSaleCntBegin" : "",
1965
+ "c7dSaleCntEnd" : "",
1966
+ "goodsLevelIdList" : [
1967
+ 62,
1968
+ 227,
1969
+ 12,
1970
+ 230,
1971
+ 80,
1972
+ 58,
1973
+ 224
1974
+ ],
1975
+ "supplyStatus" : "",
1976
+ "shelfStatus" : 1,
1977
+ "categoryIdList" : [],
1978
+ "skcStockBegin" : "",
1979
+ "skcStockEnd" : "",
1980
+ "skuStockBegin" : "",
1981
+ "skuStockEnd" : "",
1982
+ "skcSaleDaysBegin" : None,
1983
+ "skcSaleDaysEnd" : "",
1984
+ "skuSaleDaysBegin" : "",
1985
+ "skuSaleDaysEnd" : "",
1986
+ "planUrgentCountBegin" : "",
1987
+ "planUrgentCountEnd" : "",
1988
+ "skcAvailableOrderBegin": "",
1989
+ "skcAvailableOrderEnd" : "",
1990
+ "skuAvailableOrderBegin": None,
1991
+ "skuAvailableOrderEnd" : "",
1992
+ "shelfDateBegin" : "",
1993
+ "shelfDateEnd" : "",
1994
+ "stockWarnStatusList" : [],
1995
+ "labelFakeIdList" : [],
1996
+ "sheinSaleByInventory" : "",
1997
+ "tspIdList" : [],
1998
+ "adviceStatus" : [],
1999
+ "sortBy7dSaleCnt" : 2,
2000
+ "goodsLevelFakeIdList" : [
2001
+ 4
2002
+ ]
2003
+ }
2004
+ response_text = fetch(self.web_page, url, payload)
2005
+ error_code = response_text.get('code')
2006
+ if str(error_code) != '0':
2007
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2008
+ info = response_text.get('info')
2009
+ count = info.get('count', 0)
2010
+ log('获取已上架备货款B数量', count)
2011
+ return count
2012
+
2013
+ # 已上架新款A数量
2014
+ def get_product_A_count(self):
2015
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
2016
+ payload = {
2017
+ "pageNumber" : 1,
2018
+ "pageSize" : 10,
2019
+ "supplierCodes" : "",
2020
+ "skcs" : "",
2021
+ "spu" : "",
2022
+ "c7dSaleCntBegin" : "",
2023
+ "c7dSaleCntEnd" : "",
2024
+ "goodsLevelIdList" : [
2025
+ 107
2026
+ ],
2027
+ "supplyStatus" : "",
2028
+ "shelfStatus" : 1,
2029
+ "categoryIdList" : [],
2030
+ "skcStockBegin" : "",
2031
+ "skcStockEnd" : "",
2032
+ "skuStockBegin" : "",
2033
+ "skuStockEnd" : "",
2034
+ "skcSaleDaysBegin" : None,
2035
+ "skcSaleDaysEnd" : "",
2036
+ "skuSaleDaysBegin" : "",
2037
+ "skuSaleDaysEnd" : "",
2038
+ "planUrgentCountBegin" : "",
2039
+ "planUrgentCountEnd" : "",
2040
+ "skcAvailableOrderBegin": "",
2041
+ "skcAvailableOrderEnd" : "",
2042
+ "skuAvailableOrderBegin": None,
2043
+ "skuAvailableOrderEnd" : "",
2044
+ "shelfDateBegin" : "",
2045
+ "shelfDateEnd" : "",
2046
+ "stockWarnStatusList" : [],
2047
+ "labelFakeIdList" : [],
2048
+ "sheinSaleByInventory" : "",
2049
+ "tspIdList" : [],
2050
+ "adviceStatus" : [],
2051
+ "sortBy7dSaleCnt" : 2,
2052
+ "goodsLevelFakeIdList" : [
2053
+ 2
2054
+ ]
2055
+ }
2056
+ response_text = fetch(self.web_page, url, payload)
2057
+ error_code = response_text.get('code')
2058
+ if str(error_code) != '0':
2059
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2060
+ info = response_text.get('info')
2061
+ count = info.get('count', 0)
2062
+ log('获取已上架新款A数量', count)
2063
+ return count
2064
+
2065
+ # 本周已上架数量
2066
+ def get_week_shelf_product_count(self, start_date, end_date):
2067
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
2068
+ payload = {
2069
+ "pageNumber" : 1,
2070
+ "pageSize" : 10,
2071
+ "supplierCodes" : "",
2072
+ "skcs" : "",
2073
+ "spu" : "",
2074
+ "c7dSaleCntBegin" : "",
2075
+ "c7dSaleCntEnd" : "",
2076
+ "goodsLevelIdList" : [],
2077
+ "supplyStatus" : "",
2078
+ "shelfStatus" : 1,
2079
+ "categoryIdList" : [],
2080
+ "skcStockBegin" : "",
2081
+ "skcStockEnd" : "",
2082
+ "skuStockBegin" : "",
2083
+ "skuStockEnd" : "",
2084
+ "skcSaleDaysBegin" : "",
2085
+ "skcSaleDaysEnd" : "",
2086
+ "skuSaleDaysBegin" : "",
2087
+ "skuSaleDaysEnd" : "",
2088
+ "planUrgentCountBegin" : "",
2089
+ "planUrgentCountEnd" : "",
2090
+ "skcAvailableOrderBegin": "",
2091
+ "skcAvailableOrderEnd" : "",
2092
+ "skuAvailableOrderBegin": "",
2093
+ "skuAvailableOrderEnd" : "",
2094
+ "shelfDateBegin" : start_date,
2095
+ "shelfDateEnd" : end_date,
2096
+ "stockWarnStatusList" : [],
2097
+ "labelFakeIdList" : [],
2098
+ "sheinSaleByInventory" : "",
2099
+ "tspIdList" : [],
2100
+ "adviceStatus" : [],
2101
+ "sortBy7dSaleCnt" : 2
2102
+ }
2103
+ response_text = fetch(self.web_page, url, payload)
2104
+ error_code = response_text.get('code')
2105
+ if str(error_code) != '0':
2106
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2107
+ info = response_text.get('info')
2108
+ count = info.get('count', 0)
2109
+ log('获取本周上架数量')
2110
+ return count
2111
+
2112
+ # 已上架数量
2113
+ def get_shelf_product_count(self):
2114
+ log('获取所有已上架数量')
2115
+ url = "https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/list?page_num=1&page_size=10"
2116
+ payload = {
2117
+ "language" : "zh-cn",
2118
+ "only_recommend_resell" : False,
2119
+ "only_spmb_copy_product": False,
2120
+ "search_abandon_product": False,
2121
+ "shelf_type" : "ON_SHELF",
2122
+ "sort_type" : 1
2123
+ }
2124
+ response_text = fetch(self.web_page, url, payload)
2125
+ error_code = response_text.get('code')
2126
+ if str(error_code) != '0':
2127
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2128
+
2129
+ info = response_text.get('info')
2130
+ customObj = info.get('meta').get('customObj')
2131
+ # 将数据转换成字典
2132
+ result = {item["shelf_status"]: item["count"] for item in customObj}
2133
+ return result
2134
+
2135
+ def get_yesterday_upload_product_count(self, dt=None):
2136
+ url = "https://sso.geiwohuo.com/spmp-api-prefix/spmp/product/publish/record/page_list?page_num=1&page_size=100"
2137
+ payload = {
2138
+ "edit_type" : 0,
2139
+ "language" : "zh-cn",
2140
+ "only_current_month_recommend": False,
2141
+ "only_spmb_copy_product" : False,
2142
+ "query_time_out" : False,
2143
+ "search_diy_custom" : False
2144
+ }
2145
+ response_text = fetch(self.web_page, url, payload)
2146
+ error_code = response_text.get('code')
2147
+ if str(error_code) != '0':
2148
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2149
+ info = response_text.get('info')
2150
+ count = 0
2151
+ for item in info.get('data', {}):
2152
+ if TimeUtils.is_yesterday(item['create_time'], dt):
2153
+ count += 1
2154
+ log('获取昨日已上传数量', count)
2155
+ return count
2156
+
2157
+ def get_week_sales_stat_detail(self):
2158
+ dt = self.get_dt_time()
2159
+ yesterday = TimeUtils.get_yesterday(dt)
2160
+ date_7_days_ago = TimeUtils.get_past_nth_day(6, None, '%Y-%m-%d')
2161
+ log('-7', date_7_days_ago)
2162
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y-%m-%d')
2163
+ log('-1', date_1_days_ago)
2164
+
2165
+ url = "https://sso.geiwohuo.com/sbn/index/get_critical_indicator_curve_chart"
2166
+ payload = {
2167
+ "areaCd" : "cn",
2168
+ "dt" : dt,
2169
+ "countrySite": [
2170
+ "shein-all"
2171
+ ],
2172
+ "startDate" : date_7_days_ago,
2173
+ "endDate" : date_1_days_ago,
2174
+ "queryType" : 1,
2175
+ "pageNum" : 1,
2176
+ "pageSize" : 100
2177
+ }
2178
+ response_text = fetch(self.web_page, url, payload)
2179
+ error_code = response_text.get('code')
2180
+ if str(error_code) != '0':
2181
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2182
+ info = response_text.get('info', {})
2183
+
2184
+ last_item = SheinStoreSalesDetailManager(self.config.database_url).get_records_as_dict(self.store_username, yesterday)
2185
+ log('last_item', last_item)
2186
+ day_item = info[-1]
2187
+ log(day_item)
2188
+ item = {}
2189
+ item["store_username"] = self.store_username
2190
+ item["store_name"] = self.store_name
2191
+ item["day"] = day_item["dataDate"]
2192
+ item["sales_num"] = day_item["saleCnt1d"] or 0
2193
+ item['sales_num_inc'] = item['sales_num'] - last_item.get('sales_num', 0)
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'])
2202
+ item['sales_amount_inc'] = item['sales_amount'] - float(last_item.get('sales_amount', 0))
2203
+ item['visitor_num'] = day_item['idxShopGoodsUv1d'] or 0
2204
+ item['visitor_num_inc'] = item['visitor_num'] - last_item.get('visitor_num', 0)
2205
+ item['bak_A_num'] = self.get_product_bak_A_count()
2206
+ item['bak_A_num_inc'] = item['bak_A_num'] - last_item.get('bak_A_num', 0)
2207
+ item['new_A_num'] = self.get_product_A_count()
2208
+ item['new_A_num_inc'] = item['new_A_num'] - last_item.get('new_A_num', 0)
2209
+ dictProduct = self.get_shelf_product_count()
2210
+ item['on_sales_product_num'] = dictProduct.get('ON_SHELF')
2211
+ item['on_sales_product_num_inc'] = item['on_sales_product_num'] - last_item.get('on_sales_product_num', 0)
2212
+ item['wait_shelf_product_num'] = dictProduct.get('WAIT_SHELF')
2213
+ item['wait_shelf_product_num_inc'] = item['wait_shelf_product_num'] - last_item.get('wait_shelf_product_num', 0)
2214
+ item['upload_product_num'] = self.get_yesterday_upload_product_count()
2215
+ item['upload_product_num_inc'] = item['upload_product_num'] - last_item.get('upload_product_num', 0)
2216
+ item['sold_out_product_num'] = dictProduct.get('SOLD_OUT')
2217
+ item['shelf_off_product_num'] = dictProduct.get('OUT_SHELF')
2218
+
2219
+ SheinStoreSalesDetailManager(self.config.database_url).insert_data([item])
2220
+
2221
+ def get_delivery_order_list(self, orderType=2):
2222
+ page_num = 1
2223
+ page_size = 200
2224
+
2225
+ url = f"https://sso.geiwohuo.com/pfmp/order/list"
2226
+ payload = {}
2227
+ if orderType == 1:
2228
+ payload = {
2229
+ "orderType": orderType,
2230
+ "page" : page_num,
2231
+ "perPage" : page_size,
2232
+ "status" : [2],
2233
+ }
2234
+ elif orderType == 2:
2235
+ payload = {
2236
+ "orderType" : orderType,
2237
+ "page" : page_num,
2238
+ "perPage" : page_size,
2239
+ "status" : [2],
2240
+ "isJitOrder": 2
2241
+ }
2242
+ response_text = fetch(self.web_page, url, payload)
2243
+ error_code = response_text.get('code')
2244
+ if str(error_code) != '0':
2245
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2246
+
2247
+ spu_list = response_text['info']['data']
2248
+ total = response_text['info']['meta']['count']
2249
+ totalPage = math.ceil(total / page_size)
2250
+
2251
+ skc_list = [item['goods']['skcName'] for item in spu_list]
2252
+ self.get_activity_label(skc_list)
2253
+
2254
+ for page in range(2, totalPage + 1):
2255
+ log(f'获取订单列表 第{page}/{totalPage}页')
2256
+ page_num = page
2257
+ if orderType == 1:
2258
+ payload = {
2259
+ "orderType": orderType,
2260
+ "page" : page_num,
2261
+ "perPage" : page_size,
2262
+ "status" : [2],
2263
+ }
2264
+ elif orderType == 2:
2265
+ payload = {
2266
+ "orderType" : orderType,
2267
+ "page" : page_num,
2268
+ "perPage" : page_size,
2269
+ "status" : [2],
2270
+ "isJitOrder": 2
2271
+ }
2272
+ response_text = fetch(self.web_page, url, payload)
2273
+ spu_list_new = response_text['info']['data']
2274
+ skc_list = [item['goods']['skcName'] for item in spu_list_new]
2275
+ self.get_activity_label(skc_list)
2276
+ spu_list += spu_list_new
2277
+ time.sleep(0.3)
2278
+
2279
+ if len(spu_list) == 0:
2280
+ log(f'无{["", "急采", "备货"][orderType]}发货单')
2281
+ return None
2282
+
2283
+ write_to_excel = [
2284
+ # 0 1 2 3 4 5 6 7
2285
+ ['店铺名称', 'SKC图片', 'SKU图片', '商品信息', '下单/需求数量', '库存(模式/本地/在途/希音)', '成本价', '核价',
2286
+ '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '最晚预约上门取件', '要求实际完成取件',
2287
+ 'SKC', 'SKU']
2288
+ ]
2289
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
2290
+ DictSkcShelf = read_dict_from_file(cache_file2)
2291
+ cache_file3 = f'{self.config.auto_dir}/shein/dict/skc_product_{self.store_username}.json'
2292
+ DictSkcProduct = read_dict_from_file(cache_file3)
2293
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
2294
+ dictActivityPrice = read_dict_from_file(cache_file)
2295
+ cache_file4 = f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json'
2296
+ DictSkuInfo = read_dict_from_file(cache_file4)
2297
+ for spu_item in spu_list:
2298
+ skc = spu_item['goods']['skcName']
2299
+ skcCode = spu_item['goods']['supplierCode']
2300
+ skc_img = str(spu_item['goods']['imgPath'])
2301
+ orderNum = spu_item['sellerOrderNo']
2302
+ orderTime = spu_item['allocateTime']
2303
+ requestTakeParcelTime = spu_item['requestTakeParcelTime']
2304
+ suggestedReserveTime = spu_item['suggestedReserveTime']
2305
+ good_level = spu_item['goods']['goodsLevelName']
2306
+
2307
+ self.get_skc_week_actual_sales(skc)
2308
+
2309
+ spu = DictSkcProduct[skc]['spu_name']
2310
+ log('spu', spu)
2311
+ for sku_item in spu_item['detail']:
2312
+ needQuantity = sku_item['needQuantity']
2313
+ orderQuantity = sku_item['orderQuantity']
2314
+ # sku_img = sku_item['skuThumb']
2315
+ skuCode = sku_item['supplierSku']
2316
+ stock = self.bridge.get_sku_stock(skuCode, 'mb')
2317
+ cost_price = self.bridge.get_sku_cost(skuCode, 'mb')
2318
+ suffixZh = sku_item['suffixZh']
2319
+ sku = sku_item['skuCode']
2320
+ supplyPrice = sku_item['supplyPrice']
2321
+ sku_img = self.bridge.get_sku_img(skuCode, 'mb')
2322
+ sale_model = DictSkuInfo[skuCode][0]
2323
+ shein_stock = DictSkuInfo[skuCode][1]
2324
+ shelf_days = DictSkuInfo[skuCode][2]
2325
+ real_transit = DictSkuInfo[skuCode][3]
2326
+ stock_str = f'{sale_model}\n{stock}/{real_transit}/{shein_stock}'
2327
+
2328
+ item = []
2329
+ item.append(f'{self.store_name}\n{good_level}')
2330
+ item.append(skc_img)
2331
+ item.append(sku_img)
2332
+ if cost_price == '-':
2333
+ profit = '-'
2334
+ else:
2335
+ profit = f'{float(supplyPrice) - float(cost_price):.2f}'
2336
+ # item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}\n下单/需求数量: {orderQuantity}/{needQuantity}\n库存模式/本地/在途/希音: {sale_model}/{stock}/{real_transit}/{shein_stock}\n')
2337
+ item.append(f'订单号: {orderNum}\n下单时间: {orderTime}\n最晚预约上门取件: {suggestedReserveTime}\nSKC: {skc}\nSKC货号: {skcCode}\nSKU货号: {skuCode}\n属性集: {suffixZh}\n上架时间: {DictSkcShelf[skc]}\n上架天数: {shelf_days}\n成本/核价/利润: ¥{cost_price}/¥{supplyPrice}/¥{profit}')
2338
+ item.append(f'[{orderQuantity}/{needQuantity}]')
2339
+ item.append(stock_str)
2340
+ item.append(cost_price)
2341
+ item.append(supplyPrice)
2342
+ sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
2343
+ item.append("\n".join(sale_num_list))
2344
+ item.append("\n".join(sale_data_list))
2345
+ item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
2346
+ item.append(suggestedReserveTime)
2347
+ item.append(requestTakeParcelTime)
2348
+ item.append(skc)
2349
+ item.append(sku)
2350
+ write_to_excel.append(item)
2351
+
2352
+ cache_file = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_{orderType}_{TimeUtils.get_period()}.json'
2353
+ write_dict_to_file_ex(cache_file, {self.store_username: write_to_excel}, {self.store_username})
2354
+
2355
+ return write_to_excel
2356
+
2357
+ # 获取商品包含sku销量的列表
2358
+ def get_dict_sku_stock_detail(self):
2359
+ log(f'获取备货信息商品列表 做成字典')
2360
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
2361
+ pageNumber = 1
2362
+ pageSize = 100
2363
+ dictPayload = {
2364
+ "pageNumber" : pageNumber,
2365
+ "pageSize" : pageSize,
2366
+ "supplierCodes" : "",
2367
+ "skcs" : "",
2368
+ "spu" : "",
2369
+ "c7dSaleCntBegin" : "",
2370
+ "c7dSaleCntEnd" : "",
2371
+ "goodsLevelIdList" : [],
2372
+ "supplyStatus" : "",
2373
+ "shelfStatus" : "",
2374
+ "categoryIdList" : [],
2375
+ "skcStockBegin" : "",
2376
+ "skcStockEnd" : "",
2377
+ "skuStockBegin" : "",
2378
+ "skuStockEnd" : "",
2379
+ "skcSaleDaysBegin" : "",
2380
+ "skcSaleDaysEnd" : "",
2381
+ "skuSaleDaysBegin" : "",
2382
+ "skuSaleDaysEnd" : "",
2383
+ "planUrgentCountBegin" : "",
2384
+ "planUrgentCountEnd" : "",
2385
+ "skcAvailableOrderBegin": "",
2386
+ "skcAvailableOrderEnd" : "",
2387
+ "skuAvailableOrderBegin": "",
2388
+ "skuAvailableOrderEnd" : "",
2389
+ "shelfDateBegin" : "",
2390
+ "shelfDateEnd" : "",
2391
+ "stockWarnStatusList" : [],
2392
+ "labelFakeIdList" : [],
2393
+ "sheinSaleByInventory" : "",
2394
+ "tspIdList" : [],
2395
+ "adviceStatus" : [],
2396
+ "sortBy7dSaleCnt" : 2
2397
+ }
2398
+ payload = dictPayload
2399
+ response_text = fetch(self.web_page, url, payload)
2400
+ error_code = response_text.get('code')
2401
+ if str(error_code) != '0':
2402
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2403
+
2404
+ spu_list = response_text['info']['list']
2405
+
2406
+ total = response_text['info']['count']
2407
+ totalPage = math.ceil(total / pageSize)
2408
+ for page in range(2, totalPage + 1):
2409
+ log(f'获取备货信息商品列表 第{page}/{totalPage}页')
2410
+ dictPayload['pageNumber'] = page
2411
+ payload = dictPayload
2412
+ response_text = fetch(self.web_page, url, payload)
2413
+ spu_list_new = response_text['info']['list']
2414
+ spu_list += spu_list_new
2415
+ time.sleep(0.3)
2416
+
2417
+ DictSkuInfo = {}
2418
+ for spu_info in spu_list:
2419
+ sale_model = spu_info.get('saleModel', {}).get('name') if spu_info.get('saleModel') else '-'
2420
+ shelfDays = spu_info['shelfDays']
2421
+ for sku_info in spu_info['skuList']:
2422
+ attr = sku_info['attr']
2423
+ if attr == '合计':
2424
+ continue
2425
+ skuExtCode = str(sku_info['supplierSku'])
2426
+ shein_stock = sku_info['stock']
2427
+
2428
+ transit = sku_info['transit'] # 在途
2429
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
2430
+
2431
+ DictSkuInfo[skuExtCode] = [sale_model, shein_stock, shelfDays, real_transit]
2432
+
2433
+ write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json', DictSkuInfo)
2434
+
2435
+ return DictSkuInfo
2436
+
2437
+ def get_shop_notify_num(self):
2438
+ log(f'正在获取 {self.store_name} 通知数据')
2439
+ url = "https://sso.geiwohuo.com/sso/homePage/v4/detail"
2440
+ payload = {
2441
+ "metaIndexIds": [
2442
+ 246, # 急采-待发货
2443
+ 245 # 备货-待发货
2444
+ ],
2445
+ "templateType": 0
2446
+ }
2447
+ response_text = fetch(self.web_page, url, payload)
2448
+ error_code = response_text.get('code')
2449
+ if str(error_code) != '0':
2450
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2451
+ info = response_text.get('info')
2452
+
2453
+ cache_file = f'{self.config.auto_dir}/shein/notify/{self.store_name}_{TimeUtils.get_current_datetime()}.json'
2454
+ write_dict_to_file(cache_file, info)
2455
+
2456
+ num245 = 0
2457
+ num246 = 0
2458
+ for item in info['list']:
2459
+ if item['metaIndexId'] == 245:
2460
+ num245 = item['count']
2461
+ if item['metaIndexId'] == 246:
2462
+ num246 = item['count']
2463
+
2464
+ NotifyItem = [self.store_name, num246, num245]
2465
+
2466
+ cache_file = f'{self.config.auto_dir}/shein/cache/jit_notify_{TimeUtils.today_date()}.json'
2467
+ write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, {self.store_username})
2468
+
2469
+ return info
2470
+
2471
+ def get_activity_list(self):
2472
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_activity_list?page_num=1&page_size=100"
2473
+ payload = {}
2474
+ response_text = fetch(self.web_page, url, payload)
2475
+ error_code = response_text.get('code')
2476
+ if str(error_code) != '0':
2477
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2478
+ total = response_text.get('info', {}).get('total_count')
2479
+ excel_data = [[
2480
+ '店铺名称', '活动信息', '已报数量', '可报数量'
2481
+ ]]
2482
+ if total > 0:
2483
+ for item in response_text.get('info', {}).get('activity_detail_list'):
2484
+ activity_tag = item.get('text_tag_content')
2485
+ activity_name = item['activity_name']
2486
+ start_time = item['activity_start_zone_time']
2487
+ end_time = item['activity_end_zone_time']
2488
+ start_time2 = item['start_zone_time']
2489
+ end_time2 = item['end_zone_time']
2490
+ allow_goods_num = item.get('allow_goods_num')
2491
+ apply_goods_num = item.get('apply_goods_num')
2492
+ row_item = [
2493
+ self.store_name,
2494
+ f"活动名称: 【{activity_tag}】{activity_name}\n报名时间: {start_time}~{end_time}\n活动时间: {start_time2}~{end_time2}\n已报数量: {apply_goods_num}/{allow_goods_num}",
2495
+ apply_goods_num,
2496
+ allow_goods_num,
2497
+ ]
2498
+ excel_data.append(row_item)
2499
+ cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
2500
+ write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
79
2501
 
80
2502
  def get_product_list(self):
81
- self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
82
- self.web_page.wait_for_load_state("load")
2503
+ # self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
2504
+ # self.web_page.wait_for_load_state("load")
83
2505
 
84
2506
  cache_file = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
85
2507
  DictSpuInfo = read_dict_from_file(cache_file, 3600)
@@ -128,7 +2550,7 @@ class SheinLib:
128
2550
 
129
2551
  cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
130
2552
  write_dict_to_file(cache_file2, DictSkcShelf)
131
- 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'
132
2554
  write_dict_to_file(cache_file3, DictSkcProduct)
133
2555
 
134
2556
  write_dict_to_file(cache_file, DictSpuInfo)
@@ -137,15 +2559,15 @@ class SheinLib:
137
2559
  def query_obm_activity_list(self):
138
2560
  page_num = 1
139
2561
  page_size = 100
140
- date_60_days_ago = time_utils.get_past_nth_day(59)
141
- cache_file = f'{self.config.auto_dir}/shein/cache/obm_activity_{self.store_name}_{date_60_days_ago}_{time_utils.today_date()}.json'
2562
+ date_60_days_ago = TimeUtils.get_past_nth_day(59)
2563
+ cache_file = f'{self.config.auto_dir}/shein/cache/obm_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
142
2564
  list_item = read_dict_from_file(cache_file, 3600 * 8)
143
2565
  if len(list_item) > 0:
144
2566
  return list_item
145
2567
 
146
2568
  url = f"https://sso.geiwohuo.com/mrs-api-prefix/promotion/obm/query_obm_activity_list"
147
2569
  payload = {
148
- "insert_end_time" : f"{time_utils.today_date()} 23:59:59",
2570
+ "insert_end_time" : f"{TimeUtils.today_date()} 23:59:59",
149
2571
  "insert_start_time": f"{date_60_days_ago} 00:00:00",
150
2572
  "page_num" : page_num,
151
2573
  "page_size" : page_size,
@@ -207,6 +2629,33 @@ class SheinLib:
207
2629
  list_item += response_text['info']['data']
208
2630
  time.sleep(0.1)
209
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)
210
2659
  write_dict_to_file(cache_file, list_item)
211
2660
  return list_item
212
2661
 
@@ -216,8 +2665,8 @@ class SheinLib:
216
2665
  log(f'正在获取 {self.store_name} 活动列表')
217
2666
  page_num = 1
218
2667
  page_size = 100
219
- date_60_days_ago = time_utils.get_past_nth_day(59)
220
- cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{self.store_name}_{date_60_days_ago}_{time_utils.today_date()}.json'
2668
+ date_60_days_ago = TimeUtils.get_past_nth_day(59)
2669
+ cache_file = f'{self.config.auto_dir}/shein/cache/platform_activity_{self.store_name}_{date_60_days_ago}_{TimeUtils.today_date()}.json'
221
2670
  list_item = read_dict_from_file(cache_file, 3600 * 8)
222
2671
  if len(list_item) > 0:
223
2672
  return list_item
@@ -225,7 +2674,7 @@ class SheinLib:
225
2674
  url = f"https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_partake_activity_goods_list?page_num={page_num}&page_size={page_size}"
226
2675
  payload = {
227
2676
  "goods_audit_status" : 1,
228
- "insert_zone_time_end" : f"{time_utils.today_date()} 23:59:59",
2677
+ "insert_zone_time_end" : f"{TimeUtils.today_date()} 23:59:59",
229
2678
  "insert_zone_time_start": f"{date_60_days_ago} 00:00:00"
230
2679
  }
231
2680
 
@@ -255,8 +2704,8 @@ class SheinLib:
255
2704
  activity_id = activity['activity_id']
256
2705
  activity_name = activity['act_name']
257
2706
  sub_type_id = activity['sub_type_id'] # 1.不限量 2.限量
258
- dateBegin = time_utils.convert_datetime_to_date(activity['start_time'])
259
- dateEnd = time_utils.convert_datetime_to_date(activity['end_time'])
2707
+ dateBegin = TimeUtils.convert_datetime_to_date(activity['start_time'])
2708
+ dateEnd = TimeUtils.convert_datetime_to_date(activity['end_time'])
260
2709
  skc_list = self.query_goods_detail(activity_id)
261
2710
  for skc_item in skc_list:
262
2711
  attend_num_sum = skc_item['attend_num_sum']
@@ -275,8 +2724,8 @@ class SheinLib:
275
2724
  activity_name = platform_activity['activity_name']
276
2725
  text_tag_content = platform_activity['text_tag_content']
277
2726
  attend_num = platform_activity['attend_num']
278
- dateBegin = time_utils.convert_timestamp_to_date(platform_activity['start_time'])
279
- dateEnd = time_utils.convert_timestamp_to_date(platform_activity['end_time'])
2727
+ dateBegin = TimeUtils.convert_timestamp_to_date(platform_activity['start_time'])
2728
+ dateEnd = TimeUtils.convert_timestamp_to_date(platform_activity['end_time'])
280
2729
  if text_tag_content != '新品':
281
2730
  attend_num = '-'
282
2731
  for sku_item in platform_activity['activity_sku_list']:
@@ -287,8 +2736,38 @@ class SheinLib:
287
2736
 
288
2737
  write_dict_to_file(cache_file, dict_activity_price)
289
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
+
290
2769
  def get_skc_week_actual_sales(self, skc):
291
- first_day, last_day = time_utils.TimeUtils.get_past_7_days_range()
2770
+ first_day, last_day = TimeUtils.get_past_7_days_range()
292
2771
  cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
293
2772
  if datetime.now().hour >= 9:
294
2773
  DictSkuSalesDate = read_dict_from_file(cache_file)
@@ -339,6 +2818,22 @@ class SheinLib:
339
2818
 
340
2819
  return dict
341
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
+
342
2837
  def get_activity_label(self, skc_list):
343
2838
  url = f"https://sso.geiwohuo.com/idms/goods-skc/activity-label"
344
2839
  payload = skc_list
@@ -355,6 +2850,23 @@ class SheinLib:
355
2850
 
356
2851
  return dict
357
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
+
358
2870
  def get_sku_price_v2(self, skc_list):
359
2871
  log(f'获取sku价格列表', skc_list)
360
2872
  url = "https://sso.geiwohuo.com/idms/goods-skc/price"
@@ -386,34 +2898,435 @@ class SheinLib:
386
2898
  dict_advice.update(dict)
387
2899
  write_dict_to_file(cache_file, dict_advice)
388
2900
 
389
- return dict
2901
+ return dict
2902
+
2903
+ def get_dt_time(self):
2904
+ if self.dt is not None:
2905
+ log(f'字典dt: {self.dt}')
2906
+ return self.dt
2907
+ log('获取非实时更新时间')
2908
+ url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
2909
+ payload = {
2910
+ "pageCode": "Index",
2911
+ "areaCd" : "cn"
2912
+ }
2913
+ response_text = fetch(self.web_page, url, payload)
2914
+ error_code = response_text.get('code')
2915
+ if str(error_code) != '0':
2916
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2917
+ self.dt = response_text.get('info').get('dt')
2918
+ log(f'dt: {self.dt}')
2919
+ return self.dt
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
+
2939
+ def get_dict_skc_week_trend(self):
2940
+ page_num = 1
2941
+ page_size = 100
2942
+
2943
+ date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
2944
+ log('-7', date_7_days_ago)
2945
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
2946
+ log('-1', date_1_days_ago)
2947
+
2948
+ url = f"https://sso.geiwohuo.com/sbn/new_goods/get_skc_diagnose_list"
2949
+ payload = {
2950
+ "areaCd" : "cn",
2951
+ "countrySite": [
2952
+ "shein-all"
2953
+ ],
2954
+ "startDate" : date_7_days_ago,
2955
+ "endDate" : date_1_days_ago,
2956
+ "pageNum" : page_num,
2957
+ "pageSize" : page_size
2958
+ }
2959
+ response_text = fetch(self.web_page, url, payload)
2960
+ error_code = response_text.get('code')
2961
+ if str(error_code) != '0':
2962
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2963
+ spu_list = response_text['info']['data']
2964
+ total = response_text['info']['meta']['count']
2965
+ totalPage = math.ceil(total / page_size)
2966
+
2967
+ for page in range(2, totalPage + 1):
2968
+ log(f'获取商品列表 第{page}/{totalPage}页')
2969
+ page_num = page
2970
+ payload = {
2971
+ "areaCd" : "cn",
2972
+ "countrySite": [
2973
+ "shein-all"
2974
+ ],
2975
+ "startDate" : date_7_days_ago,
2976
+ "endDate" : date_1_days_ago,
2977
+ "pageNum" : page_num,
2978
+ "pageSize" : page_size
2979
+ }
2980
+ response_text = fetch(self.web_page, url, payload)
2981
+ spu_list_new = response_text['info']['data']
2982
+ spu_list += spu_list_new
2983
+ time.sleep(0.3)
2984
+
2985
+ DictSkcWeekTrend = {}
2986
+ for spu_item in spu_list:
2987
+ skc = str(spu_item['skc'])
2988
+ DictSkcWeekTrend[skc] = spu_item
2989
+
2990
+ log('len(DictSkcWeekTrend)', len(DictSkcWeekTrend))
2991
+ write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json', DictSkcWeekTrend)
2992
+ return DictSkcWeekTrend
2993
+
2994
+ def get_skc_sales(self, skc, start_date, end_date):
2995
+ url = "https://sso.geiwohuo.com/idms/stockadvice/saleTrendDetail"
2996
+ payload = {
2997
+ "skc" : skc,
2998
+ "startDate": start_date,
2999
+ "endDate" : end_date
3000
+ }
3001
+ response_text = fetch(self.web_page, url, payload)
3002
+ error_code = response_text.get('code')
3003
+ error_msg = response_text.get('msg')
3004
+ if str(error_code) != '0':
3005
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
3006
+
3007
+ sales_list = response_text['info']['saleTrendDetailList']
3008
+ if sales_list:
3009
+ for skc_list in sales_list:
3010
+ date = skc_list['date']
3011
+ if date == '合计':
3012
+ log('无销量skc: ', skc)
3013
+ continue
3014
+ skc_sale = skc_list['skcSale']
3015
+ skc_order = skc_list['skcOrder']
3016
+ for sku_list in skc_list['skuSaleTrendDetailList']:
3017
+ sku = sku_list['skuCode']
3018
+ attr_name = sku_list['attributeName']
3019
+ sku_sale = sku_list['skuSale']
3020
+ sku_order = sku_list['skuOrder']
3021
+ if sku_sale > 0:
3022
+ insert_sales(skc, date, skc_sale, skc_order, sku, attr_name, sku_sale, sku_order)
3023
+ return sales_list
3024
+
3025
+ # 获取商品包含sku销量的列表
3026
+ def get_product_sku_sales_list(self, source='mb'):
3027
+ log(f'获取销量列表')
3028
+ url = "https://sso.geiwohuo.com/idms/goods-skc/list"
3029
+ pageNumber = 1
3030
+ pageSize = 100
3031
+ dictPayload = {
3032
+ "pageNumber" : pageNumber,
3033
+ "pageSize" : pageSize,
3034
+ "supplierCodes" : "",
3035
+ "skcs" : "",
3036
+ "spu" : "",
3037
+ "c7dSaleCntBegin" : "",
3038
+ "c7dSaleCntEnd" : "",
3039
+ "goodsLevelIdList" : [],
3040
+ "supplyStatus" : "",
3041
+ "shelfStatus" : 1,
3042
+ "categoryIdList" : [],
3043
+ "skcStockBegin" : "",
3044
+ "skcStockEnd" : "",
3045
+ "skuStockBegin" : "",
3046
+ "skuStockEnd" : "",
3047
+ "skcSaleDaysBegin" : "",
3048
+ "skcSaleDaysEnd" : "",
3049
+ "skuSaleDaysBegin" : "",
3050
+ "skuSaleDaysEnd" : "",
3051
+ "planUrgentCountBegin" : "",
3052
+ "planUrgentCountEnd" : "",
3053
+ "skcAvailableOrderBegin": "",
3054
+ "skcAvailableOrderEnd" : "",
3055
+ "skuAvailableOrderBegin": "",
3056
+ "skuAvailableOrderEnd" : "",
3057
+ "shelfDateBegin" : "",
3058
+ "shelfDateEnd" : "",
3059
+ "stockWarnStatusList" : [],
3060
+ "labelFakeIdList" : [],
3061
+ "sheinSaleByInventory" : "",
3062
+ "tspIdList" : [],
3063
+ "adviceStatus" : [],
3064
+ "sortBy7dSaleCnt" : 2
3065
+ }
3066
+ payload = dictPayload
3067
+ response_text = fetch(self.web_page, url, payload)
3068
+ error_code = response_text.get('code')
3069
+ if str(error_code) != '0':
3070
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
3071
+
3072
+ spu_list = response_text['info']['list']
3073
+
3074
+ skc_list = [item['skc'] for item in spu_list]
3075
+ self.get_activity_label(skc_list)
3076
+ self.get_preemption_list(skc_list)
3077
+ self.get_sku_price_v2(skc_list)
3078
+ self.get_stock_advice(skc_list)
3079
+
3080
+ total = response_text['info']['count']
3081
+ totalPage = math.ceil(total / pageSize)
3082
+ for page in range(2, totalPage + 1):
3083
+ log(f'获取SKU销量列表 第{page}/{totalPage}页')
3084
+ dictPayload['pageNumber'] = page
3085
+ payload = dictPayload
3086
+ response_text = fetch(self.web_page, url, payload)
3087
+ spu_list_new = response_text['info']['list']
3088
+
3089
+ skc_list = [item['skc'] for item in spu_list_new]
3090
+ self.get_activity_label(skc_list)
3091
+ self.get_preemption_list(skc_list)
3092
+ self.get_sku_price_v2(skc_list)
3093
+ self.get_stock_advice(skc_list)
3094
+
3095
+ spu_list += spu_list_new
3096
+ time.sleep(0.3)
3097
+
3098
+ cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
3099
+ dict_sku_price = read_dict_from_file(cache_file)
3100
+
3101
+ cache_file2 = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{self.store_username}.json'
3102
+ DictSkcWeekTrend = read_dict_from_file(cache_file2)
3103
+
3104
+ cache_file3 = f'{self.config.auto_dir}/shein/dict/product_list_{self.store_username}.json'
3105
+ DictSpuInfo = read_dict_from_file(cache_file3)
3106
+
3107
+ cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
3108
+ dictActivityPrice = read_dict_from_file(cache_file)
3109
+
3110
+ product_sku_list = [
3111
+ [
3112
+ '店铺名称', '商品信息', 'SKC图片', 'SKU图片', 'SKU', 'SKU货号', '在售天数', '库存(模式/本地/在途/希音)',
3113
+ '今天销量', '今日订单数', # 9
3114
+ '远7天销量', '远7天订单数', '近7天销量', '近7天订单数', '周销增量', '远30天销量', '远30天订单数',
3115
+ '近30天销量', '近30天订单数', '月销增量', '总销量', # 11
3116
+ '申报价', '成本价', '毛利润', '毛利率', '近7天利润', '近30天利润', # 6
3117
+ 'SPU', 'SKC', 'SKC货号', '近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '商品标题', '叶子类目', # 5
3118
+ 'SKC近7天销量', 'SKC近7天曝光人数', 'SKC近7天商详访客', 'SKC近7天点击率', 'SKC近7天支付人数',
3119
+ 'SKC近7天支付率', 'SKC近7天评论数'
3120
+ ]
3121
+ ]
3122
+
3123
+ date_60_days_ago = TimeUtils.get_past_nth_day(60, None, '%Y-%m-%d')
3124
+ log('-60', date_60_days_ago)
3125
+ date_7_days_ago = TimeUtils.get_past_nth_day(7, None, '%Y-%m-%d')
3126
+ log('-7', date_7_days_ago)
3127
+ date_1_days_ago = TimeUtils.get_past_nth_day(1, None, '%Y-%m-%d')
3128
+ log('-1', date_1_days_ago)
3129
+
3130
+ count = 0
3131
+ for spu_info in spu_list:
3132
+ count += 1
3133
+ # if count > 10:
3134
+ # break
3135
+ spu = spu_info['spu']
3136
+ skc = str(spu_info['skc'])
3137
+ # if not shein_db.exists_sales_1_days_ago(skc):
3138
+ # log(f'未查到昨天销量: {skc}')
3139
+ self.get_skc_week_actual_sales(skc)
3140
+ self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
3141
+ skcCode = spu_info['supplierCode']
3142
+ product_name = DictSpuInfo[spu]['product_name_en']
3143
+ category_name = spu_info['categoryName']
3144
+ shelfDays = spu_info['shelfDays']
3145
+ shelf_status = DictSpuInfo[spu]['shelf_status']
3146
+ dictStatus = {
3147
+ 'WAIT_SHELF': "待上架",
3148
+ 'ON_SHELF' : "已上架",
3149
+ 'SOLD_OUT' : "已售罄",
3150
+ 'OUT_SHELF' : "已下架"
3151
+ }
3152
+ status_cn = dictStatus[shelf_status]
3153
+ good_level = spu_info['goodsLevel']['name']
3154
+ sale_model = spu_info['saleModel']['name']
3155
+
3156
+ # 过滤已经售罄
3157
+ if shelf_status == 'SOLD_OUT':
3158
+ log(f'过滤已售罄: {skc} {category_name} {product_name}')
3159
+ continue
3160
+
3161
+ for sku_info in spu_info['skuList']:
3162
+ sku = sku_info['skuCode']
3163
+ skuExtCode = str(sku_info['supplierSku'])
3164
+ if sku == '合计':
3165
+ continue
3166
+
3167
+ # 获取基础数据
3168
+ stock = self.bridge.get_sku_stock(skuExtCode, source)
3169
+ cost_price = self.bridge.get_sku_cost(skuExtCode, source)
3170
+ sku_img = self.bridge.get_sku_img(skuExtCode, source)
3171
+
3172
+ # 计算库存相关数据
3173
+ shein_stock = sku_info['stock']
3174
+ transit = sku_info['transit'] # 在途
3175
+ real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
3176
+
3177
+ # 获取销量数据
3178
+ week_sales = get_last_week_sales(sku)
3179
+ week_sales2 = get_near_week_sales(sku)
3180
+ month_sales = get_last_month_sales(sku)
3181
+ month_sales2 = get_near_month_sales(sku)
3182
+
3183
+ # 获取SKC趋势数据
3184
+ key = str(skc)
3185
+ skc_trend = DictSkcWeekTrend.get(key, {})
3186
+
3187
+ # 使用append组装数据
3188
+ sku_item = []
3189
+
3190
+ # 店铺名称
3191
+ sku_item.append(f'{self.store_name}\n({status_cn})\n{good_level}\n{date_7_days_ago}\n{date_1_days_ago}')
3192
+
3193
+ # 商品信息
3194
+ product_info = f"SPU: {spu}\nSKC: {skc}\nSKC货号: {skcCode}\n类目: {category_name}\n在售天数: {shelfDays}"
3195
+ sku_item.append(product_info)
3196
+
3197
+ # SKC图片
3198
+ sku_item.append(spu_info['picUrl'])
3199
+
3200
+ # SKU图片
3201
+ sku_item.append(sku_img)
3202
+
3203
+ # SKU基本信息
3204
+ sku_item.append(sku) # SKU
3205
+ sku_item.append(f"{sku_info['supplierSku']}") # SKU货号
3206
+ sku_item.append(shelfDays) # 在售天数
3207
+
3208
+ # 库存信息
3209
+ sku_item.append(f'{sale_model}\n{stock}/{real_transit}/{shein_stock}')
3210
+
3211
+ # 今日销量数据
3212
+ sku_item.append(sku_info['totalSaleVolume']) # 今日销量
3213
+ sku_item.append(sku_info['orderCnt']) # 今日订单数
3214
+
3215
+ # 远7天销量数据
3216
+ sku_item.append(week_sales[0]) # 远7日销量
3217
+ sku_item.append(week_sales[1]) # 远7日订单数
3218
+
3219
+ # 近7天销量数据
3220
+ sku_item.append(week_sales2[0]) # 近7日销量
3221
+ sku_item.append(week_sales2[1]) # 近7日订单数
3222
+ sku_item.append(week_sales2[1] - week_sales2[0]) # 周增销量
3223
+
3224
+ # 远30天销量数据
3225
+ sku_item.append(month_sales[0]) # 远30日销量
3226
+ sku_item.append(month_sales[1]) # 远30日订单数
3227
+
3228
+ # 近30天销量数据
3229
+ sku_item.append(month_sales2[0]) # 近30日销量
3230
+ sku_item.append(month_sales2[1]) # 近30日订单数
3231
+ sku_item.append(month_sales2[1] - month_sales2[0]) # 月增销量
3232
+
3233
+ # 总销量
3234
+ sku_item.append('-')
3235
+
3236
+ # 价格相关
3237
+ sku_item.append(dict_sku_price[sku]) # 申报价
3238
+ sku_item.append(cost_price) # 成本价
3239
+ sku_item.append('') # 毛利润
3240
+ sku_item.append('') # 毛利率
3241
+ sku_item.append('') # 近7天利润
3242
+ sku_item.append('') # 近30天利润
3243
+
3244
+ # 商品标识
3245
+ sku_item.append(spu) # SPU
3246
+ sku_item.append(skc) # SKC
3247
+ sku_item.append(spu_info['supplierCode']) # SKC货号
3248
+
3249
+ sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
3250
+ sku_item.append("\n".join(sale_num_list))
3251
+ sku_item.append("\n".join(sale_data_list))
3252
+ sku_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
3253
+
3254
+ sku_item.append(product_name) # 商品标题
3255
+ sku_item.append(category_name) # 叶子类目
3256
+
3257
+ # SKC趋势数据
3258
+ sku_item.append(skc_trend.get('saleCnt', 0)) # SKC近7天销量
3259
+ sku_item.append(skc_trend.get('epsUvIdx', 0)) # SKC近7天曝光人数
3260
+ sku_item.append(skc_trend.get('goodsUv', 0)) # SKC近7天商详访客
3261
+ sku_item.append(skc_trend.get('epsGdsCtrIdx', 0)) # SKC近7天点击率
3262
+ sku_item.append(skc_trend.get('payUvIdx', 0)) # SKC近7天支付人数
3263
+ sku_item.append(skc_trend.get('gdsPayCtrIdx', 0)) # SKC近7天支付率
3264
+ sku_item.append(skc_trend.get('totalCommentCnt', 0)) # 评论数
3265
+
3266
+ product_sku_list.append(sku_item)
3267
+
3268
+ cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
3269
+ write_dict_to_file_ex(cache_file, {self.store_name: product_sku_list}, [self.store_name])
3270
+
3271
+ return product_sku_list
3272
+
3273
+ # 获取一个skc一段时间内的销售趋势(商品明细中的)
3274
+ def get_skc_trend(self, spu, skc, start_date, end_date):
3275
+ dt = self.get_dt_time_goods()
390
3276
 
391
- def get_dt_time(self):
392
- if self.dt is not None:
393
- log(f'字典dt: {self.dt}')
394
- return self.dt
395
- log('获取非实时更新时间')
396
- url = "https://sso.geiwohuo.com/sbn/common/get_update_time"
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"
397
3289
  payload = {
398
- "pageCode": "Index",
399
- "areaCd" : "cn"
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('-', ''),
400
3299
  }
401
3300
  response_text = fetch(self.web_page, url, payload)
3301
+ log(response_text)
402
3302
  error_code = response_text.get('code')
403
3303
  if str(error_code) != '0':
404
3304
  raise send_exception(json.dumps(response_text, ensure_ascii=False))
405
- self.dt = response_text.get('info').get('dt')
406
- log(f'dt: {self.dt}')
407
- return self.dt
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
408
3317
 
409
3318
  # 获取一个skc一周内的销售趋势(商品明细中的)
410
- def get_dict_skc_week_trend_v2(self, spu, skc):
3319
+ def get_dict_skc_week_trend_v2(self, spu, skc, start_from=None):
411
3320
  dt = self.get_dt_time()
412
3321
 
413
- date_7_days_ago = time_utils.TimeUtils.get_past_nth_day(7, None, '%Y%m%d')
414
- log('-7', date_7_days_ago)
415
- date_1_days_ago = time_utils.TimeUtils.get_past_nth_day(1, None, '%Y%m%d')
416
- log('-1', date_1_days_ago)
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}')
417
3330
 
418
3331
  cache_file = f'{self.config.auto_dir}/shein/dict/dict_skc_week_trend_{skc}_{date_7_days_ago}_{date_1_days_ago}.json'
419
3332
  if datetime.now().hour >= 9:
@@ -452,10 +3365,10 @@ class SheinLib:
452
3365
  write_dict_to_file(cache_file, DictSkc)
453
3366
  return DictSkc
454
3367
 
455
- def get_skc_week_sale_list(self, spu, skc, sku):
3368
+ def get_sku_week_sale_list(self, spu, skc, sku):
456
3369
  dict_skc = self.get_dict_skc_week_trend_v2(spu, skc)
457
- date_list = time_utils.get_past_7_days_list()
458
- first_day, last_day = time_utils.TimeUtils.get_past_7_days_range()
3370
+ date_list = TimeUtils.get_past_7_days_list()
3371
+ first_day, last_day = TimeUtils.get_past_7_days_range()
459
3372
  cache_file = f'{self.config.auto_dir}/shein/cache/{skc}_{first_day}_{last_day}.json'
460
3373
  DictSkuSalesDate = read_dict_from_file(cache_file)
461
3374
  sales_detail = []
@@ -466,7 +3379,7 @@ class SheinLib:
466
3379
  saleCnt = get_safe_value(dict_skc.get(date, {}), 'saleCnt', 0)
467
3380
  epsUvIdx = get_safe_value(dict_skc.get(date, {}), 'epsUvIdx', 0)
468
3381
 
469
- sales_detail.append(f'{date}({time_utils.get_weekday_name(date)}): {sales_num}/{saleCnt}/{epsUvIdx}')
3382
+ sales_detail.append(f'{date}({TimeUtils.get_weekday_name(date)}): {sales_num}/{saleCnt}/{epsUvIdx}')
470
3383
 
471
3384
  sales_data = []
472
3385
  for date in date_list:
@@ -476,7 +3389,7 @@ class SheinLib:
476
3389
  payUvIdx = get_safe_value(dict_skc.get(date, {}), 'payUvIdx', 0) # 支付人数
477
3390
  gdsPayCtrIdx = get_safe_value(dict_skc.get(date, {}), 'gdsPayCtrIdx', 0) # 转化率
478
3391
 
479
- sales_data.append(f'{date}({time_utils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
3392
+ sales_data.append(f'{date}({TimeUtils.get_weekday_name(date)}): {epsGdsCtrIdx:.2%}({goodsUvIdx})/{gdsPayCtrIdx:.2%}({payUvIdx})')
480
3393
 
481
3394
  return sales_detail, sales_data
482
3395
 
@@ -521,12 +3434,11 @@ class SheinLib:
521
3434
  # 10.运营-热销款 (有现货建议 30天外 有销量)
522
3435
  def get_bak_advice(self, mode=1, skcs=None, source='mb'):
523
3436
  log(f'获取备货信息商品列表 做成字典')
524
- global DictSkuInfo
525
3437
  if skcs == None or len(skcs) == 0:
526
- if mode == 3:
527
- skcs = "sh2405133614611175" # 这是一个不存在的skc
528
- else:
529
- skcs = ""
3438
+ # if mode == 3:
3439
+ # skcs = "sh2405133614611175" # 这是一个不存在的skc
3440
+ # else:
3441
+ skcs = ""
530
3442
  else:
531
3443
  skcs = ",".join(skcs)
532
3444
 
@@ -576,6 +3488,10 @@ class SheinLib:
576
3488
  raise send_exception(json.dumps(response_text, ensure_ascii=False))
577
3489
 
578
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)
579
3495
 
580
3496
  skc_list = [item['skc'] for item in spu_list]
581
3497
  self.get_activity_label(skc_list)
@@ -592,6 +3508,11 @@ class SheinLib:
592
3508
  response_text = fetch(self.web_page, url, payload)
593
3509
  spu_list_new = response_text['info']['list']
594
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
+
595
3516
  skc_list = [item['skc'] for item in spu_list_new]
596
3517
  self.get_activity_label(skc_list)
597
3518
  self.get_preemption_list(skc_list)
@@ -611,7 +3532,7 @@ class SheinLib:
611
3532
  dict_advice = read_dict_from_file(cache_file)
612
3533
  cache_file = f'{self.config.auto_dir}/shein/sku_price/sku_price_{self.store_username}.json'
613
3534
  dict_sku_price = read_dict_from_file(cache_file)
614
- date_list = time_utils.get_past_7_days_list()
3535
+ date_list = TimeUtils.get_past_7_days_list()
615
3536
  if mode in [2, 5, 6, 7, 8, 9, 10]:
616
3537
  excel_data = [[
617
3538
  '店铺名称', 'SKC图片', 'SKU图片', '商品信息', '建议现货数量', '现有库存数量', '已采购数量', '预测日销',
@@ -658,6 +3579,9 @@ class SheinLib:
658
3579
  shelfDays = spu_info['shelfDays']
659
3580
  categoryName = spu_info['categoryName']
660
3581
 
3582
+ if mode in [3] and shelfDays != 1:
3583
+ continue
3584
+
661
3585
  DictSkuSalesDate = self.get_skc_week_actual_sales(skc)
662
3586
 
663
3587
  for sku_info in spu_info['skuList']:
@@ -785,7 +3709,7 @@ class SheinLib:
785
3709
  sales_num = DictSkuSalesDate.get(date, {}).get(sku, {}).get("hisActualValue", 0)
786
3710
  sales_num = sales_num if sales_num is not None else 0
787
3711
  sales7cn += sales_num
788
- if time_utils.is_yesterday_date(date) and sales_num == 0:
3712
+ if TimeUtils.is_yesterday_date(date) and sales_num == 0:
789
3713
  flag_yesterday = 1
790
3714
 
791
3715
  if mode == 4 and flag_yesterday:
@@ -823,7 +3747,7 @@ class SheinLib:
823
3747
  if mode in [6] and sales7cn > 0:
824
3748
  continue
825
3749
 
826
- sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
3750
+ sale_num_list, sale_data_list = self.get_sku_week_sale_list(spu, skc, sku)
827
3751
  row_item.append("\n".join(sale_num_list))
828
3752
  row_item.append("\n".join(sale_data_list))
829
3753
  row_item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
@@ -831,11 +3755,771 @@ class SheinLib:
831
3755
  row_item.append(sku)
832
3756
  excel_data.append(row_item)
833
3757
 
834
- cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{time_utils.today_date()}.json'
3758
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{TimeUtils.today_date()}.json'
835
3759
  write_dict_to_file_ex(cache_file, {self.store_name: excel_data}, {self.store_name})
836
3760
 
837
- cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{time_utils.today_date()}.json'
3761
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_notify_{mode}_{TimeUtils.today_date()}.json'
838
3762
  NotifyItem = [self.store_name, len(excel_data[1:])]
839
3763
  write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
840
3764
 
841
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