qrpa 1.0.15__py3-none-any.whl → 1.0.17__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 +3 -0
- qrpa/fun_file.py +147 -1
- qrpa/shein_excel.py +197 -0
- qrpa/shein_lib.py +586 -25
- qrpa/shein_sqlite.py +154 -0
- qrpa/wxwork.py +37 -0
- {qrpa-1.0.15.dist-info → qrpa-1.0.17.dist-info}/METADATA +1 -1
- {qrpa-1.0.15.dist-info → qrpa-1.0.17.dist-info}/RECORD +10 -9
- {qrpa-1.0.15.dist-info → qrpa-1.0.17.dist-info}/WHEEL +0 -0
- {qrpa-1.0.15.dist-info → qrpa-1.0.17.dist-info}/top_level.txt +0 -0
qrpa/__init__.py
CHANGED
|
@@ -9,9 +9,12 @@ 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 *
|
|
15
16
|
|
|
16
17
|
from .shein_excel import SheinExcel
|
|
17
18
|
from .shein_lib import SheinLib
|
|
19
|
+
|
|
20
|
+
from .fun_excel import InsertImageV2
|
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,200 @@ 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)
|
|
162
|
+
|
|
163
|
+
def write_week_report(self):
|
|
164
|
+
excel_path = create_file_path(self.config.excel_week_sales_report)
|
|
165
|
+
log(excel_path)
|
|
166
|
+
|
|
167
|
+
cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
|
|
168
|
+
dict = read_dict_from_file(cache_file)
|
|
169
|
+
|
|
170
|
+
summary_excel_data = []
|
|
171
|
+
header = []
|
|
172
|
+
for store_name, excel_data in dict.items():
|
|
173
|
+
sheet_name = store_name
|
|
174
|
+
write_data(excel_path, sheet_name, excel_data)
|
|
175
|
+
self.format_week_report(excel_path, sheet_name)
|
|
176
|
+
header = excel_data[0]
|
|
177
|
+
summary_excel_data += excel_data[1:]
|
|
178
|
+
summary_excel_data = [header] + summary_excel_data
|
|
179
|
+
sheet_name = 'Sheet1'
|
|
180
|
+
write_data(excel_path, sheet_name, summary_excel_data)
|
|
181
|
+
self.format_week_report(excel_path, sheet_name)
|
|
182
|
+
|
|
183
|
+
def format_week_report(self, excel_path, sheet_name):
|
|
184
|
+
app, wb, sheet = open_excel(excel_path, sheet_name)
|
|
185
|
+
beautify_title(sheet)
|
|
186
|
+
column_to_left(sheet, ['商品信息'])
|
|
187
|
+
format_to_money(sheet, ['申报价', '成本价', '毛利润', '利润'])
|
|
188
|
+
format_to_percent(sheet, ['支付率', '点击率', '毛利率'])
|
|
189
|
+
self.dealFormula(sheet) # 有空再封装优化
|
|
190
|
+
colorize_by_field(app, wb, sheet, 'SPU')
|
|
191
|
+
autofit_column(sheet, ['店铺名称'])
|
|
192
|
+
specify_column_width(sheet, ['商品标题'], 150 / 6)
|
|
193
|
+
add_borders(sheet)
|
|
194
|
+
InsertImageV2(app, wb, sheet, ['SKC图片', 'SKU图片'], 'shein', 90)
|
|
195
|
+
wb.save()
|
|
196
|
+
close_excel(app, wb)
|
|
197
|
+
|
|
198
|
+
# 处理公式计算
|
|
199
|
+
def dealFormula(self, sheet):
|
|
200
|
+
# 增加列 周销增量 月销增量
|
|
201
|
+
col_week_increment = find_column_by_data(sheet, 1, '周销增量')
|
|
202
|
+
if col_week_increment is None:
|
|
203
|
+
col_week_increment = find_column_by_data(sheet, 1, '远30天销量')
|
|
204
|
+
log(f'{col_week_increment}:{col_week_increment}')
|
|
205
|
+
sheet.range(f'{col_week_increment}:{col_week_increment}').insert('right')
|
|
206
|
+
sheet.range(f'{col_week_increment}1').value = '周销增量'
|
|
207
|
+
log('已增加列 周销增量')
|
|
208
|
+
|
|
209
|
+
col_month_increment = find_column_by_data(sheet, 1, '月销增量')
|
|
210
|
+
if col_month_increment is None:
|
|
211
|
+
col_month_increment = find_column_by_data(sheet, 1, '总销量')
|
|
212
|
+
log(f'{col_month_increment}:{col_month_increment}')
|
|
213
|
+
sheet.range(f'{col_month_increment}:{col_month_increment}').insert('right')
|
|
214
|
+
sheet.range(f'{col_month_increment}1').value = '月销增量'
|
|
215
|
+
log('已增加列 月销增量')
|
|
216
|
+
|
|
217
|
+
col_month_profit = find_column_by_data(sheet, 1, '近30天利润')
|
|
218
|
+
if col_month_profit is None:
|
|
219
|
+
col_month_profit = find_column_by_data(sheet, 1, '总利润')
|
|
220
|
+
sheet.range(f'{col_month_profit}:{col_month_profit}').insert('right')
|
|
221
|
+
log((f'{col_month_profit}:{col_month_profit}'))
|
|
222
|
+
sheet.range(f'{col_month_profit}1').value = '近30天利润'
|
|
223
|
+
log('已增加列 近30天利润')
|
|
224
|
+
|
|
225
|
+
col_week_profit = find_column_by_data(sheet, 1, '近7天利润')
|
|
226
|
+
if col_week_profit is None:
|
|
227
|
+
col_week_profit = find_column_by_data(sheet, 1, '近30天利润')
|
|
228
|
+
sheet.range(f'{col_week_profit}:{col_week_profit}').insert('right')
|
|
229
|
+
log((f'{col_week_profit}:{col_week_profit}'))
|
|
230
|
+
sheet.range(f'{col_week_profit}1').value = '近7天利润'
|
|
231
|
+
log('已增加列 近7天利润')
|
|
232
|
+
|
|
233
|
+
# return
|
|
234
|
+
|
|
235
|
+
# 查找 申报价,成本价,毛利润,毛利润率 所在列
|
|
236
|
+
col_verify_price = find_column_by_data(sheet, 1, '申报价')
|
|
237
|
+
col_cost_price = find_column_by_data(sheet, 1, '成本价')
|
|
238
|
+
col_gross_profit = find_column_by_data(sheet, 1, '毛利润')
|
|
239
|
+
col_gross_margin = find_column_by_data(sheet, 1, '毛利率')
|
|
240
|
+
|
|
241
|
+
col_week_1 = find_column_by_data(sheet, 1, '近7天销量')
|
|
242
|
+
col_week_2 = find_column_by_data(sheet, 1, '远7天销量')
|
|
243
|
+
col_month_1 = find_column_by_data(sheet, 1, '近30天销量')
|
|
244
|
+
col_month_2 = find_column_by_data(sheet, 1, '远30天销量')
|
|
245
|
+
|
|
246
|
+
# 遍历可用行
|
|
247
|
+
used_range_row = sheet.range('A1').expand('down')
|
|
248
|
+
for i, cell in enumerate(used_range_row):
|
|
249
|
+
row = i + 1
|
|
250
|
+
if row < 2:
|
|
251
|
+
continue
|
|
252
|
+
rangeA = f'{col_verify_price}{row}'
|
|
253
|
+
rangeB = f'{col_cost_price}{row}'
|
|
254
|
+
|
|
255
|
+
rangeC = f'{col_week_increment}{row}'
|
|
256
|
+
rangeD = f'{col_month_increment}{row}'
|
|
257
|
+
|
|
258
|
+
# rangeE = f'{col_total_profit}{row}'
|
|
259
|
+
rangeF = f'{col_month_profit}{row}'
|
|
260
|
+
rangeG = f'{col_week_profit}{row}'
|
|
261
|
+
|
|
262
|
+
# 设置毛利润和毛利润率列公式与格式
|
|
263
|
+
sheet.range(f'{col_gross_profit}{row}').formula = f'=IF(ISNUMBER({rangeB}),{rangeA}-{rangeB},"")'
|
|
264
|
+
sheet.range(f'{col_gross_profit}{row}').number_format = '0.00'
|
|
265
|
+
sheet.range(f'{col_gross_margin}{row}').formula = f'=IF(ISNUMBER({rangeB}),({rangeA}-{rangeB})/{rangeA},"")'
|
|
266
|
+
sheet.range(f'{col_gross_margin}{row}').number_format = '0.00%'
|
|
267
|
+
|
|
268
|
+
sheet.range(rangeC).formula = f'={col_week_1}{row}-{col_week_2}{row}'
|
|
269
|
+
sheet.range(rangeC).number_format = '0'
|
|
270
|
+
sheet.range(rangeD).formula = f'={col_month_1}{row}-{col_month_2}{row}'
|
|
271
|
+
sheet.range(rangeD).number_format = '0'
|
|
272
|
+
|
|
273
|
+
# sheet.range(rangeE).formula = f'=IF(ISNUMBER({rangeB}),{col_total}{row}*{col_gross_profit}{row},"")'
|
|
274
|
+
# sheet.range(rangeE).number_format = '0.00'
|
|
275
|
+
sheet.range(rangeF).formula = f'=IF(ISNUMBER({rangeB}),{col_month_1}{row}*{col_gross_profit}{row},"")'
|
|
276
|
+
sheet.range(rangeF).number_format = '0.00'
|
|
277
|
+
sheet.range(rangeG).formula = f'=IF(ISNUMBER({rangeB}),{col_week_1}{row}*{col_gross_profit}{row},"")'
|
|
278
|
+
sheet.range(rangeG).number_format = '0.00'
|