qrpa 1.0.15__py3-none-any.whl → 1.0.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qrpa might be problematic. Click here for more details.
- qrpa/__init__.py +1 -0
- qrpa/fun_file.py +147 -1
- qrpa/shein_excel.py +80 -0
- qrpa/shein_lib.py +282 -2
- qrpa/wxwork.py +37 -0
- {qrpa-1.0.15.dist-info → qrpa-1.0.16.dist-info}/METADATA +1 -1
- {qrpa-1.0.15.dist-info → qrpa-1.0.16.dist-info}/RECORD +9 -9
- {qrpa-1.0.15.dist-info → qrpa-1.0.16.dist-info}/WHEEL +0 -0
- {qrpa-1.0.15.dist-info → qrpa-1.0.16.dist-info}/top_level.txt +0 -0
qrpa/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from .time_utils import TimeUtils
|
|
|
9
9
|
|
|
10
10
|
from .fun_file import read_dict_from_file, read_dict_from_file_ex, write_dict_to_file, write_dict_to_file_ex
|
|
11
11
|
from .fun_file import get_progress_json_ex, check_progress_json_ex, done_progress_json_ex
|
|
12
|
+
from .fun_file import delete_file, delete_file_simple
|
|
12
13
|
|
|
13
14
|
from .fun_web import fetch, fetch_via_iframe, find_all_iframe, full_screen_shot
|
|
14
15
|
from .fun_win import *
|
qrpa/fun_file.py
CHANGED
|
@@ -8,6 +8,152 @@ from datetime import date, datetime, timedelta, timezone
|
|
|
8
8
|
|
|
9
9
|
from .fun_base import log
|
|
10
10
|
|
|
11
|
+
import os
|
|
12
|
+
import gc
|
|
13
|
+
import time
|
|
14
|
+
import psutil
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
def delete_file(file_path):
|
|
18
|
+
"""
|
|
19
|
+
删除文件的优化函数,自动处理已打开的文件
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
file_path (str): 要删除的文件路径
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
bool: 删除成功返回True,失败返回False
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
file_path = Path(file_path).resolve() # 规范化路径
|
|
29
|
+
|
|
30
|
+
if not file_path.exists():
|
|
31
|
+
log(f"文件 {file_path} 不存在。")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# 强制垃圾回收,关闭可能的文件句柄
|
|
35
|
+
gc.collect()
|
|
36
|
+
|
|
37
|
+
# 第一次尝试直接删除
|
|
38
|
+
try:
|
|
39
|
+
os.remove(file_path)
|
|
40
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
41
|
+
return True
|
|
42
|
+
except PermissionError:
|
|
43
|
+
log(f"文件 {file_path} 可能被占用,尝试关闭相关进程...")
|
|
44
|
+
|
|
45
|
+
# 尝试找到并关闭占用该文件的进程
|
|
46
|
+
if close_file_handles(file_path):
|
|
47
|
+
# 等待一小段时间让系统释放句柄
|
|
48
|
+
time.sleep(0.1)
|
|
49
|
+
|
|
50
|
+
# 再次尝试删除
|
|
51
|
+
try:
|
|
52
|
+
os.remove(file_path)
|
|
53
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
54
|
+
return True
|
|
55
|
+
except PermissionError:
|
|
56
|
+
log(f"错误:即使尝试关闭文件句柄后,仍无法删除文件 {file_path}。")
|
|
57
|
+
return False
|
|
58
|
+
else:
|
|
59
|
+
log(f"错误:无法关闭文件 {file_path} 的句柄,删除失败。")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
except FileNotFoundError:
|
|
63
|
+
log(f"错误:文件 {file_path} 未找到。")
|
|
64
|
+
return False
|
|
65
|
+
except Exception as e:
|
|
66
|
+
log(f"错误:删除文件时发生未知错误:{e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def close_file_handles(file_path):
|
|
70
|
+
"""
|
|
71
|
+
尝试关闭指定文件的所有句柄
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path (Path): 文件路径
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: 成功关闭返回True,失败返回False
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
file_path_str = str(file_path)
|
|
81
|
+
closed_any = False
|
|
82
|
+
|
|
83
|
+
# 遍历所有进程,查找打开该文件的进程
|
|
84
|
+
for proc in psutil.process_iter(['pid', 'name']):
|
|
85
|
+
try:
|
|
86
|
+
# 获取进程打开的文件列表
|
|
87
|
+
open_files = proc.open_files()
|
|
88
|
+
for open_file in open_files:
|
|
89
|
+
if os.path.samefile(open_file.path, file_path_str):
|
|
90
|
+
log(f"发现进程 {proc.info['name']} (PID: {proc.info['pid']}) 正在使用文件")
|
|
91
|
+
|
|
92
|
+
# 如果是当前Python进程,尝试强制关闭文件句柄
|
|
93
|
+
if proc.info['pid'] == os.getpid():
|
|
94
|
+
closed_any = True
|
|
95
|
+
else:
|
|
96
|
+
# 对于其他进程,可以选择终止(谨慎使用)
|
|
97
|
+
log(f"警告:文件被其他进程占用,请手动关闭应用程序")
|
|
98
|
+
|
|
99
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
100
|
+
continue
|
|
101
|
+
except Exception as e:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
return closed_any
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
log(f"关闭文件句柄时出错:{e}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def delete_file_simple(file_path):
|
|
111
|
+
"""
|
|
112
|
+
简化版本的删除文件函数(不需要额外依赖)
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
file_path (str): 要删除的文件路径
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: 删除成功返回True,失败返回False
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
file_path = os.path.abspath(file_path) # 获取绝对路径
|
|
122
|
+
|
|
123
|
+
if not os.path.exists(file_path):
|
|
124
|
+
log(f"文件 {file_path} 不存在。")
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# 强制垃圾回收
|
|
128
|
+
gc.collect()
|
|
129
|
+
|
|
130
|
+
# 尝试删除
|
|
131
|
+
try:
|
|
132
|
+
os.remove(file_path)
|
|
133
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
134
|
+
return True
|
|
135
|
+
except PermissionError:
|
|
136
|
+
# 在Windows上,尝试修改文件权限
|
|
137
|
+
if os.name == 'nt': # Windows
|
|
138
|
+
try:
|
|
139
|
+
os.chmod(file_path, 0o777)
|
|
140
|
+
time.sleep(0.1) # 短暂等待
|
|
141
|
+
os.remove(file_path)
|
|
142
|
+
log(f"文件 {file_path} 已成功删除。")
|
|
143
|
+
return True
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
log(f"错误:没有权限删除文件 {file_path},可能文件正在被使用。")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
except FileNotFoundError:
|
|
151
|
+
log(f"错误:文件 {file_path} 未找到。")
|
|
152
|
+
return False
|
|
153
|
+
except Exception as e:
|
|
154
|
+
log(f"错误:删除文件时发生未知错误:{e}")
|
|
155
|
+
return False
|
|
156
|
+
|
|
11
157
|
def read_dict_from_file(file_path, cache_interval=3600 * 24 * 365 * 10):
|
|
12
158
|
"""
|
|
13
159
|
从文件中读取字典。
|
|
@@ -170,4 +316,4 @@ def check_progress_json_ex(config, task_key, just_store_username=None):
|
|
|
170
316
|
return False
|
|
171
317
|
else:
|
|
172
318
|
log(f"进度文件不存在或为空: {progress_file}")
|
|
173
|
-
return True
|
|
319
|
+
return True
|
qrpa/shein_excel.py
CHANGED
|
@@ -79,3 +79,83 @@ class SheinExcel:
|
|
|
79
79
|
self.format_bak_advice(new_excel_path, sheet_name, mode)
|
|
80
80
|
|
|
81
81
|
return new_excel_path_list
|
|
82
|
+
|
|
83
|
+
def write_activity_list(self):
|
|
84
|
+
cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
|
|
85
|
+
dict_activity = read_dict_from_file(cache_file)
|
|
86
|
+
all_data = []
|
|
87
|
+
header = []
|
|
88
|
+
for store_username, excel_data in dict_activity.items():
|
|
89
|
+
header = excel_data[:1]
|
|
90
|
+
all_data += excel_data[1:]
|
|
91
|
+
|
|
92
|
+
all_data = header + all_data
|
|
93
|
+
|
|
94
|
+
excel_path = create_file_path(self.config.excel_activity_list)
|
|
95
|
+
sheet_name = 'Sheet1'
|
|
96
|
+
write_data(excel_path, sheet_name, all_data)
|
|
97
|
+
self.format_activity_list(excel_path, sheet_name)
|
|
98
|
+
|
|
99
|
+
def format_activity_list(self, excel_path, sheet_name):
|
|
100
|
+
app, wb, sheet = open_excel(excel_path, sheet_name)
|
|
101
|
+
beautify_title(sheet)
|
|
102
|
+
add_borders(sheet)
|
|
103
|
+
column_to_left(sheet, ['活动信息'])
|
|
104
|
+
colorize_by_field(app, wb, sheet, '店铺名称')
|
|
105
|
+
autofit_column(sheet, ['店铺名称', '活动信息'])
|
|
106
|
+
wb.save()
|
|
107
|
+
close_excel(app, wb)
|
|
108
|
+
|
|
109
|
+
def write_jit_data(self):
|
|
110
|
+
excel_path_1 = create_file_path(self.config.Excel_Order_Type_1)
|
|
111
|
+
summary_excel_data_1 = []
|
|
112
|
+
|
|
113
|
+
cache_file_1 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_1_{TimeUtils.get_period()}.json'
|
|
114
|
+
dict_1 = read_dict_from_file(cache_file_1)
|
|
115
|
+
dict_store = read_dict_from_file(f'{self.config.auto_dir}/shein_store_alias.json')
|
|
116
|
+
|
|
117
|
+
header = []
|
|
118
|
+
for store_username, excel_data in dict_1.items():
|
|
119
|
+
store_name = dict_store.get(store_username)
|
|
120
|
+
sheet_name = store_name
|
|
121
|
+
write_data(excel_path_1, sheet_name, excel_data)
|
|
122
|
+
self.format_jit(excel_path_1, sheet_name)
|
|
123
|
+
header = excel_data[0]
|
|
124
|
+
summary_excel_data_1 += excel_data[1:]
|
|
125
|
+
|
|
126
|
+
if len(summary_excel_data_1) > 0:
|
|
127
|
+
sheet_name = 'Sheet1'
|
|
128
|
+
write_data(excel_path_1, sheet_name, [header] + summary_excel_data_1)
|
|
129
|
+
self.format_jit(excel_path_1, sheet_name)
|
|
130
|
+
|
|
131
|
+
excel_path_2 = create_file_path(self.config.Excel_Order_Type_2)
|
|
132
|
+
summary_excel_data_2 = []
|
|
133
|
+
|
|
134
|
+
cache_file_2 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_2_{TimeUtils.get_period()}.json'
|
|
135
|
+
dict_2 = read_dict_from_file(cache_file_2)
|
|
136
|
+
|
|
137
|
+
header = []
|
|
138
|
+
for store_username, excel_data in dict_2.items():
|
|
139
|
+
store_name = dict_store.get(store_username)
|
|
140
|
+
sheet_name = store_name
|
|
141
|
+
write_data(excel_path_2, sheet_name, excel_data)
|
|
142
|
+
self.format_jit(excel_path_2, sheet_name)
|
|
143
|
+
header = excel_data[0]
|
|
144
|
+
summary_excel_data_2 += excel_data[1:]
|
|
145
|
+
|
|
146
|
+
if len(summary_excel_data_2) > 0:
|
|
147
|
+
sheet_name = 'Sheet1'
|
|
148
|
+
write_data(excel_path_2, sheet_name, [header] + summary_excel_data_2)
|
|
149
|
+
self.format_jit(excel_path_2, sheet_name)
|
|
150
|
+
|
|
151
|
+
def format_jit(self, excel_path, sheet_name):
|
|
152
|
+
app, wb, sheet = open_excel(excel_path, sheet_name)
|
|
153
|
+
beautify_title(sheet)
|
|
154
|
+
add_borders(sheet)
|
|
155
|
+
colorize_by_field(app, wb, sheet, 'SKC')
|
|
156
|
+
column_to_left(sheet, ["商品信息", "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
|
|
157
|
+
autofit_column(sheet,
|
|
158
|
+
['店铺名称', '商品信息', "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
|
|
159
|
+
InsertImageV2(app, wb, sheet, ['SKC图片', 'SKU图片'])
|
|
160
|
+
wb.save()
|
|
161
|
+
close_excel(app, wb)
|
qrpa/shein_lib.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from qrpa import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
|
|
2
2
|
from qrpa import log, fetch, send_exception, md5_string
|
|
3
|
-
from qrpa import time_utils, get_safe_value
|
|
3
|
+
from qrpa import time_utils, TimeUtils, get_safe_value
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
6
|
import time
|
|
@@ -77,6 +77,287 @@ class SheinLib:
|
|
|
77
77
|
# web_page.goto('https://sso.geiwohuo.com')
|
|
78
78
|
log('鉴权处理结束')
|
|
79
79
|
|
|
80
|
+
def get_delivery_order_list(self, orderType=2):
|
|
81
|
+
page_num = 1
|
|
82
|
+
page_size = 200
|
|
83
|
+
|
|
84
|
+
url = f"https://sso.geiwohuo.com/pfmp/order/list"
|
|
85
|
+
payload = {}
|
|
86
|
+
if orderType == 1:
|
|
87
|
+
payload = {
|
|
88
|
+
"orderType": orderType,
|
|
89
|
+
"page" : page_num,
|
|
90
|
+
"perPage" : page_size,
|
|
91
|
+
"status" : [2],
|
|
92
|
+
}
|
|
93
|
+
elif orderType == 2:
|
|
94
|
+
payload = {
|
|
95
|
+
"orderType" : orderType,
|
|
96
|
+
"page" : page_num,
|
|
97
|
+
"perPage" : page_size,
|
|
98
|
+
"status" : [2],
|
|
99
|
+
"isJitOrder": 2
|
|
100
|
+
}
|
|
101
|
+
response_text = fetch(self.web_page, url, payload)
|
|
102
|
+
error_code = response_text.get('code')
|
|
103
|
+
if str(error_code) != '0':
|
|
104
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
105
|
+
|
|
106
|
+
spu_list = response_text['info']['data']
|
|
107
|
+
total = response_text['info']['meta']['count']
|
|
108
|
+
totalPage = math.ceil(total / page_size)
|
|
109
|
+
|
|
110
|
+
skc_list = [item['goods']['skcName'] for item in spu_list]
|
|
111
|
+
self.get_activity_label(skc_list)
|
|
112
|
+
|
|
113
|
+
for page in range(2, totalPage + 1):
|
|
114
|
+
log(f'获取订单列表 第{page}/{totalPage}页')
|
|
115
|
+
page_num = page
|
|
116
|
+
if orderType == 1:
|
|
117
|
+
payload = {
|
|
118
|
+
"orderType": orderType,
|
|
119
|
+
"page" : page_num,
|
|
120
|
+
"perPage" : page_size,
|
|
121
|
+
"status" : [2],
|
|
122
|
+
}
|
|
123
|
+
elif orderType == 2:
|
|
124
|
+
payload = {
|
|
125
|
+
"orderType" : orderType,
|
|
126
|
+
"page" : page_num,
|
|
127
|
+
"perPage" : page_size,
|
|
128
|
+
"status" : [2],
|
|
129
|
+
"isJitOrder": 2
|
|
130
|
+
}
|
|
131
|
+
response_text = fetch(self.web_page, url, payload)
|
|
132
|
+
spu_list_new = response_text['info']['data']
|
|
133
|
+
skc_list = [item['goods']['skcName'] for item in spu_list_new]
|
|
134
|
+
self.get_activity_label(skc_list)
|
|
135
|
+
spu_list += spu_list_new
|
|
136
|
+
time.sleep(0.3)
|
|
137
|
+
|
|
138
|
+
if len(spu_list) == 0:
|
|
139
|
+
log(f'无{["", "急采", "备货"][orderType]}发货单')
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
write_to_excel = [
|
|
143
|
+
# 0 1 2 3 4 5 6 7
|
|
144
|
+
['店铺名称', 'SKC图片', 'SKU图片', '商品信息', '下单/需求数量', '库存(模式/本地/在途/希音)', '成本价', '核价',
|
|
145
|
+
'近7天SKU销量/SKC销量/SKC曝光', 'SKC点击率/SKC转化率', '自主参与活动', '最晚预约上门取件', '要求实际完成取件',
|
|
146
|
+
'SKC', 'SKU']
|
|
147
|
+
]
|
|
148
|
+
cache_file2 = f'{self.config.auto_dir}/shein/dict/skc_shelf_{self.store_username}.json'
|
|
149
|
+
DictSkcShelf = read_dict_from_file(cache_file2)
|
|
150
|
+
cache_file3 = f'{self.config.auto_dir}/shein/dict/skc_product_{self.store_username}.json'
|
|
151
|
+
DictSkcProduct = read_dict_from_file(cache_file3)
|
|
152
|
+
cache_file = f'{self.config.auto_dir}/shein/dict/activity_price_{self.store_name}.json'
|
|
153
|
+
dictActivityPrice = read_dict_from_file(cache_file)
|
|
154
|
+
cache_file4 = f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json'
|
|
155
|
+
DictSkuInfo = read_dict_from_file(cache_file4)
|
|
156
|
+
for spu_item in spu_list:
|
|
157
|
+
skc = spu_item['goods']['skcName']
|
|
158
|
+
skcCode = spu_item['goods']['supplierCode']
|
|
159
|
+
skc_img = str(spu_item['goods']['imgPath'])
|
|
160
|
+
orderNum = spu_item['sellerOrderNo']
|
|
161
|
+
orderTime = spu_item['allocateTime']
|
|
162
|
+
requestTakeParcelTime = spu_item['requestTakeParcelTime']
|
|
163
|
+
suggestedReserveTime = spu_item['suggestedReserveTime']
|
|
164
|
+
good_level = spu_item['goods']['goodsLevelName']
|
|
165
|
+
|
|
166
|
+
self.get_skc_week_actual_sales(skc)
|
|
167
|
+
|
|
168
|
+
spu = DictSkcProduct[skc]['spu_name']
|
|
169
|
+
log('spu', spu)
|
|
170
|
+
for sku_item in spu_item['detail']:
|
|
171
|
+
needQuantity = sku_item['needQuantity']
|
|
172
|
+
orderQuantity = sku_item['orderQuantity']
|
|
173
|
+
# sku_img = sku_item['skuThumb']
|
|
174
|
+
skuCode = sku_item['supplierSku']
|
|
175
|
+
stock = self.bridge.get_sku_stock(skuCode, 'mb')
|
|
176
|
+
cost_price = self.bridge.get_sku_cost(skuCode, 'mb')
|
|
177
|
+
suffixZh = sku_item['suffixZh']
|
|
178
|
+
sku = sku_item['skuCode']
|
|
179
|
+
supplyPrice = sku_item['supplyPrice']
|
|
180
|
+
sku_img = self.bridge.get_sku_img(skuCode, 'mb')
|
|
181
|
+
sale_model = DictSkuInfo[skuCode][0]
|
|
182
|
+
shein_stock = DictSkuInfo[skuCode][1]
|
|
183
|
+
shelf_days = DictSkuInfo[skuCode][2]
|
|
184
|
+
real_transit = DictSkuInfo[skuCode][3]
|
|
185
|
+
stock_str = f'{sale_model}\n{stock}/{real_transit}/{shein_stock}'
|
|
186
|
+
|
|
187
|
+
item = []
|
|
188
|
+
item.append(f'{self.store_name}\n{good_level}')
|
|
189
|
+
item.append(skc_img)
|
|
190
|
+
item.append(sku_img)
|
|
191
|
+
if cost_price == '-':
|
|
192
|
+
profit = '-'
|
|
193
|
+
else:
|
|
194
|
+
profit = f'{float(supplyPrice) - float(cost_price):.2f}'
|
|
195
|
+
# 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')
|
|
196
|
+
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}')
|
|
197
|
+
item.append(f'[{orderQuantity}/{needQuantity}]')
|
|
198
|
+
item.append(stock_str)
|
|
199
|
+
item.append(cost_price)
|
|
200
|
+
item.append(supplyPrice)
|
|
201
|
+
sale_num_list, sale_data_list = self.get_skc_week_sale_list(spu, skc, sku)
|
|
202
|
+
item.append("\n".join(sale_num_list))
|
|
203
|
+
item.append("\n".join(sale_data_list))
|
|
204
|
+
item.append(self.get_skc_activity_label(skc, sku, dictActivityPrice))
|
|
205
|
+
item.append(suggestedReserveTime)
|
|
206
|
+
item.append(requestTakeParcelTime)
|
|
207
|
+
item.append(skc)
|
|
208
|
+
item.append(sku)
|
|
209
|
+
write_to_excel.append(item)
|
|
210
|
+
|
|
211
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_{orderType}_{TimeUtils.get_period()}.json'
|
|
212
|
+
write_dict_to_file_ex(cache_file, {self.store_username: write_to_excel}, {self.store_username})
|
|
213
|
+
|
|
214
|
+
return write_to_excel
|
|
215
|
+
|
|
216
|
+
# 获取商品包含sku销量的列表
|
|
217
|
+
def get_dict_sku_stock_detail(self):
|
|
218
|
+
log(f'获取备货信息商品列表 做成字典')
|
|
219
|
+
url = "https://sso.geiwohuo.com/idms/goods-skc/list"
|
|
220
|
+
pageNumber = 1
|
|
221
|
+
pageSize = 100
|
|
222
|
+
dictPayload = {
|
|
223
|
+
"pageNumber" : pageNumber,
|
|
224
|
+
"pageSize" : pageSize,
|
|
225
|
+
"supplierCodes" : "",
|
|
226
|
+
"skcs" : "",
|
|
227
|
+
"spu" : "",
|
|
228
|
+
"c7dSaleCntBegin" : "",
|
|
229
|
+
"c7dSaleCntEnd" : "",
|
|
230
|
+
"goodsLevelIdList" : [],
|
|
231
|
+
"supplyStatus" : "",
|
|
232
|
+
"shelfStatus" : "",
|
|
233
|
+
"categoryIdList" : [],
|
|
234
|
+
"skcStockBegin" : "",
|
|
235
|
+
"skcStockEnd" : "",
|
|
236
|
+
"skuStockBegin" : "",
|
|
237
|
+
"skuStockEnd" : "",
|
|
238
|
+
"skcSaleDaysBegin" : "",
|
|
239
|
+
"skcSaleDaysEnd" : "",
|
|
240
|
+
"skuSaleDaysBegin" : "",
|
|
241
|
+
"skuSaleDaysEnd" : "",
|
|
242
|
+
"planUrgentCountBegin" : "",
|
|
243
|
+
"planUrgentCountEnd" : "",
|
|
244
|
+
"skcAvailableOrderBegin": "",
|
|
245
|
+
"skcAvailableOrderEnd" : "",
|
|
246
|
+
"skuAvailableOrderBegin": "",
|
|
247
|
+
"skuAvailableOrderEnd" : "",
|
|
248
|
+
"shelfDateBegin" : "",
|
|
249
|
+
"shelfDateEnd" : "",
|
|
250
|
+
"stockWarnStatusList" : [],
|
|
251
|
+
"labelFakeIdList" : [],
|
|
252
|
+
"sheinSaleByInventory" : "",
|
|
253
|
+
"tspIdList" : [],
|
|
254
|
+
"adviceStatus" : [],
|
|
255
|
+
"sortBy7dSaleCnt" : 2
|
|
256
|
+
}
|
|
257
|
+
payload = dictPayload
|
|
258
|
+
response_text = fetch(self.web_page, url, payload)
|
|
259
|
+
error_code = response_text.get('code')
|
|
260
|
+
if str(error_code) != '0':
|
|
261
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
262
|
+
|
|
263
|
+
spu_list = response_text['info']['list']
|
|
264
|
+
|
|
265
|
+
total = response_text['info']['count']
|
|
266
|
+
totalPage = math.ceil(total / pageSize)
|
|
267
|
+
for page in range(2, totalPage + 1):
|
|
268
|
+
log(f'获取备货信息商品列表 第{page}/{totalPage}页')
|
|
269
|
+
dictPayload['pageNumber'] = page
|
|
270
|
+
payload = dictPayload
|
|
271
|
+
response_text = fetch(self.web_page, url, payload)
|
|
272
|
+
spu_list_new = response_text['info']['list']
|
|
273
|
+
spu_list += spu_list_new
|
|
274
|
+
time.sleep(0.3)
|
|
275
|
+
|
|
276
|
+
DictSkuInfo = {}
|
|
277
|
+
for spu_info in spu_list:
|
|
278
|
+
sale_model = spu_info.get('saleModel', {}).get('name') if spu_info.get('saleModel') else '-'
|
|
279
|
+
shelfDays = spu_info['shelfDays']
|
|
280
|
+
for sku_info in spu_info['skuList']:
|
|
281
|
+
attr = sku_info['attr']
|
|
282
|
+
if attr == '合计':
|
|
283
|
+
continue
|
|
284
|
+
skuExtCode = str(sku_info['supplierSku'])
|
|
285
|
+
shein_stock = sku_info['stock']
|
|
286
|
+
|
|
287
|
+
transit = sku_info['transit'] # 在途
|
|
288
|
+
real_transit = transit + sku_info['stayShelf'] - sku_info['transitSale']
|
|
289
|
+
|
|
290
|
+
DictSkuInfo[skuExtCode] = [sale_model, shein_stock, shelfDays, real_transit]
|
|
291
|
+
|
|
292
|
+
write_dict_to_file(f'{self.config.auto_dir}/shein/dict/dict_sku_info_{self.store_username}.json', DictSkuInfo)
|
|
293
|
+
|
|
294
|
+
return DictSkuInfo
|
|
295
|
+
|
|
296
|
+
def get_shop_notify_num(self):
|
|
297
|
+
log(f'正在获取 {self.store_name} 通知数据')
|
|
298
|
+
url = "https://sso.geiwohuo.com/sso/homePage/v4/detail"
|
|
299
|
+
payload = {
|
|
300
|
+
"metaIndexIds": [
|
|
301
|
+
246, # 急采-待发货
|
|
302
|
+
245 # 备货-待发货
|
|
303
|
+
],
|
|
304
|
+
"templateType": 0
|
|
305
|
+
}
|
|
306
|
+
response_text = fetch(self.web_page, url, payload)
|
|
307
|
+
error_code = response_text.get('code')
|
|
308
|
+
if str(error_code) != '0':
|
|
309
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
310
|
+
info = response_text.get('info')
|
|
311
|
+
|
|
312
|
+
cache_file = f'{self.config.auto_dir}/shein/notify/{self.store_name}_{TimeUtils.get_current_datetime()}.json'
|
|
313
|
+
write_dict_to_file(cache_file, info)
|
|
314
|
+
|
|
315
|
+
num245 = 0
|
|
316
|
+
num246 = 0
|
|
317
|
+
for item in info['list']:
|
|
318
|
+
if item['metaIndexId'] == 245:
|
|
319
|
+
num245 = item['count']
|
|
320
|
+
if item['metaIndexId'] == 246:
|
|
321
|
+
num246 = item['count']
|
|
322
|
+
|
|
323
|
+
NotifyItem = [self.store_name, num246, num245]
|
|
324
|
+
|
|
325
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/jit_notify_{TimeUtils.today_date()}.json'
|
|
326
|
+
write_dict_to_file_ex(cache_file, {self.store_username: NotifyItem}, {self.store_username})
|
|
327
|
+
|
|
328
|
+
return info
|
|
329
|
+
|
|
330
|
+
def get_activity_list(self):
|
|
331
|
+
url = "https://sso.geiwohuo.com/mrs-api-prefix/mbrs/activity/get_activity_list?page_num=1&page_size=100"
|
|
332
|
+
payload = {}
|
|
333
|
+
response_text = fetch(self.web_page, url, payload)
|
|
334
|
+
error_code = response_text.get('code')
|
|
335
|
+
if str(error_code) != '0':
|
|
336
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
337
|
+
total = response_text.get('info', {}).get('total_count')
|
|
338
|
+
excel_data = [[
|
|
339
|
+
'店铺名称', '活动信息', '已报数量', '可报数量'
|
|
340
|
+
]]
|
|
341
|
+
if total > 0:
|
|
342
|
+
for item in response_text.get('info', {}).get('activity_detail_list'):
|
|
343
|
+
activity_tag = item.get('text_tag_content')
|
|
344
|
+
activity_name = item['activity_name']
|
|
345
|
+
start_time = item['activity_start_zone_time']
|
|
346
|
+
end_time = item['activity_end_zone_time']
|
|
347
|
+
start_time2 = item['start_zone_time']
|
|
348
|
+
end_time2 = item['end_zone_time']
|
|
349
|
+
allow_goods_num = item.get('allow_goods_num')
|
|
350
|
+
apply_goods_num = item.get('apply_goods_num')
|
|
351
|
+
row_item = [
|
|
352
|
+
self.store_name,
|
|
353
|
+
f"活动名称: 【{activity_tag}】{activity_name}\n报名时间: {start_time}~{end_time}\n活动时间: {start_time2}~{end_time2}\n已报数量: {apply_goods_num}/{allow_goods_num}",
|
|
354
|
+
apply_goods_num,
|
|
355
|
+
allow_goods_num,
|
|
356
|
+
]
|
|
357
|
+
excel_data.append(row_item)
|
|
358
|
+
cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
|
|
359
|
+
write_dict_to_file_ex(cache_file, {self.store_username: excel_data}, [self.store_username])
|
|
360
|
+
|
|
80
361
|
def get_product_list(self):
|
|
81
362
|
self.web_page.goto('https://sso.geiwohuo.com/#/spmp/commdities/list')
|
|
82
363
|
self.web_page.wait_for_load_state("load")
|
|
@@ -521,7 +802,6 @@ class SheinLib:
|
|
|
521
802
|
# 10.运营-热销款 (有现货建议 30天外 有销量)
|
|
522
803
|
def get_bak_advice(self, mode=1, skcs=None, source='mb'):
|
|
523
804
|
log(f'获取备货信息商品列表 做成字典')
|
|
524
|
-
global DictSkuInfo
|
|
525
805
|
if skcs == None or len(skcs) == 0:
|
|
526
806
|
# if mode == 3:
|
|
527
807
|
# skcs = "sh2405133614611175" # 这是一个不存在的skc
|
qrpa/wxwork.py
CHANGED
|
@@ -17,6 +17,7 @@ import hashlib
|
|
|
17
17
|
import base64
|
|
18
18
|
import requests
|
|
19
19
|
from requests_toolbelt import MultipartEncoder
|
|
20
|
+
from datetime import datetime
|
|
20
21
|
|
|
21
22
|
# 通过企微群机器人发送消息
|
|
22
23
|
class WxWorkBot:
|
|
@@ -99,6 +100,42 @@ class WxWorkBot:
|
|
|
99
100
|
}
|
|
100
101
|
self.send_msg(data)
|
|
101
102
|
|
|
103
|
+
def send_notify(self, title, sub_title_list, data_list):
|
|
104
|
+
"""
|
|
105
|
+
发送Markdown消息
|
|
106
|
+
:param content:
|
|
107
|
+
:return:
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
current_date = datetime.now().strftime("%Y-%m-%d")
|
|
111
|
+
header = f"{current_date} {title}\n\n"
|
|
112
|
+
|
|
113
|
+
arr_color = ['warning', 'info', 'warning']
|
|
114
|
+
arr_sub_header = [f"<font color='{arr_color[index]}'>{title}</font>" for index, title in enumerate(sub_title_list)]
|
|
115
|
+
sub_header = "\t".join(arr_sub_header) + "\n\n"
|
|
116
|
+
|
|
117
|
+
# 获取每个元素的行索引和列索引
|
|
118
|
+
arr_content = [
|
|
119
|
+
[
|
|
120
|
+
f'{value}' if col_idx == 0 else f"<font color='{arr_color[col_idx - 1]}'>{value}</font>"
|
|
121
|
+
for col_idx, value in enumerate(row)
|
|
122
|
+
] # 每行的元素组成一个子列表
|
|
123
|
+
for row_idx, row in enumerate(data_list) # 外层循环控制行
|
|
124
|
+
]
|
|
125
|
+
# 将二维数组转换为字符串
|
|
126
|
+
content = "\n".join(
|
|
127
|
+
# 对每行的元素列表使用 join(),用 \t 连接
|
|
128
|
+
"\t".join(row) for row in arr_content
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
data = {
|
|
132
|
+
"msgtype" : "markdown",
|
|
133
|
+
"markdown": {
|
|
134
|
+
"content": header + sub_header + content
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
self.send_msg(data)
|
|
138
|
+
|
|
102
139
|
def send_img(self, img_path):
|
|
103
140
|
"""
|
|
104
141
|
发送图片消息
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
qrpa/RateLimitedSender.py,sha256=hqvb1qspDFaW4RsLuVufylOrefkMgixANKeBaGEqYb4,1421
|
|
2
|
-
qrpa/__init__.py,sha256=
|
|
2
|
+
qrpa/__init__.py,sha256=0EV-ZBTPzRP4rjVqFU2FYe1gxrtzdomd84a3dzNGLpM,761
|
|
3
3
|
qrpa/db_migrator.py,sha256=2VmhzcMsU0MKpl-mNCwKyV8tLTqyEysSpP27-S_rQZ8,21862
|
|
4
4
|
qrpa/fun_base.py,sha256=W_owEa8-yuGG18n9kX3remm9YTzym69ztQjtYCNMTw4,3308
|
|
5
5
|
qrpa/fun_excel.py,sha256=S-A-kTZ2e3dJ8NqdhiOFqTlcr0d6XLMP6imagztRel0,103605
|
|
6
|
-
qrpa/fun_file.py,sha256=
|
|
6
|
+
qrpa/fun_file.py,sha256=yzjDV16WL5vRys7J4uQcNzIFkX4D5MAlSCwxcD-mwQo,11966
|
|
7
7
|
qrpa/fun_web.py,sha256=5QLQorAhRzMOGMRh4eCJ2UH8ZhVHvxkHwobWhmgU5qM,6286
|
|
8
8
|
qrpa/fun_win.py,sha256=-LnTeocdTt72NVH6VgLdpAT9_C5oV9okeudXG6CftMA,8034
|
|
9
|
-
qrpa/shein_excel.py,sha256=
|
|
10
|
-
qrpa/shein_lib.py,sha256=
|
|
9
|
+
qrpa/shein_excel.py,sha256=zP_WRZigbbDXXSeNei1j0sKO_-_gMBTd6wz05TwDfM0,7429
|
|
10
|
+
qrpa/shein_lib.py,sha256=xOyXgyS1a6GMjho5RFrfPo0kp56Hjj0IquIuF64-1jo,52689
|
|
11
11
|
qrpa/shein_ziniao.py,sha256=nSqqcEPh4nVQtUxUnIRzeZfTLyXywGPjPZn5pP-w57U,18309
|
|
12
12
|
qrpa/time_utils.py,sha256=ef0hhbN_6b-gcnz5ETIVOoxemIMvcxGVGGIRnHnGaBo,29564
|
|
13
13
|
qrpa/time_utils_example.py,sha256=shHOXKKF3QSzb0SHsNc34M61wEkkLuM30U9X1THKNS8,8053
|
|
14
|
-
qrpa/wxwork.py,sha256=
|
|
15
|
-
qrpa-1.0.
|
|
16
|
-
qrpa-1.0.
|
|
17
|
-
qrpa-1.0.
|
|
18
|
-
qrpa-1.0.
|
|
14
|
+
qrpa/wxwork.py,sha256=8LzRmoHYo0KBfZ1cmkWPj824Zp52NAUYm3RXYzmIIi0,10507
|
|
15
|
+
qrpa-1.0.16.dist-info/METADATA,sha256=Z7iNE0BPDJucPDz6Gg8ptn1ZHTil51WyTeylRcdud94,231
|
|
16
|
+
qrpa-1.0.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
qrpa-1.0.16.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
|
|
18
|
+
qrpa-1.0.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|