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 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'