qrpa 1.0.89__tar.gz → 1.0.91__tar.gz

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.

Files changed (37) hide show
  1. {qrpa-1.0.89 → qrpa-1.0.91}/PKG-INFO +1 -1
  2. {qrpa-1.0.89 → qrpa-1.0.91}/pyproject.toml +1 -1
  3. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/db_migrator.py +1 -1
  4. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_excel.py +35 -8
  5. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_web.py +48 -0
  6. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/shein_product_model.py +11 -3
  7. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_excel.py +1 -1
  8. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_lib.py +58 -17
  9. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_ziniao.py +73 -2
  10. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/time_utils.py +17 -6
  11. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/PKG-INFO +1 -1
  12. {qrpa-1.0.89 → qrpa-1.0.91}/README.md +0 -0
  13. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/RateLimitedSender.py +0 -0
  14. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/__init__.py +0 -0
  15. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_bot_app.py +0 -0
  16. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_client.py +0 -0
  17. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_logic.py +0 -0
  18. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_base.py +0 -0
  19. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_file.py +0 -0
  20. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_win.py +0 -0
  21. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/__init__.py +0 -0
  22. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/shein_return_order_model.py +0 -0
  23. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_daily_report_model.py +0 -0
  24. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_mysql.py +0 -0
  25. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_sqlite.py +0 -0
  26. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_chrome.py +0 -0
  27. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_excel.py +0 -0
  28. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_lib.py +0 -0
  29. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/time_utils_example.py +0 -0
  30. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/wxwork.py +0 -0
  31. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/SOURCES.txt +0 -0
  32. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/dependency_links.txt +0 -0
  33. {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/top_level.txt +0 -0
  34. {qrpa-1.0.89 → qrpa-1.0.91}/setup.cfg +0 -0
  35. {qrpa-1.0.89 → qrpa-1.0.91}/setup.py +0 -0
  36. {qrpa-1.0.89 → qrpa-1.0.91}/tests/test_db_migrator.py +0 -0
  37. {qrpa-1.0.89 → qrpa-1.0.91}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.89
3
+ Version: 1.0.91
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qrpa"
7
- version = "1.0.89"
7
+ version = "1.0.91"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -580,7 +580,7 @@ def create_default_migrator(silent: bool = False) -> DatabaseMigrator:
580
580
  )
581
581
 
582
582
  remote_config = RemoteConfig(
583
- ssh_host="git@e3",
583
+ ssh_host="git@ecslz",
584
584
  temp_dir="/tmp/db_migration",
585
585
  database=remote_db
586
586
  )
@@ -14,6 +14,9 @@ import threading
14
14
  from playwright.sync_api import sync_playwright
15
15
  import psutil
16
16
 
17
+ import os, sys
18
+ from pathlib import Path
19
+
17
20
  from .fun_base import log, sanitize_filename, create_file_path, copy_file, add_https, send_exception
18
21
 
19
22
  excel_color_index = {
@@ -195,13 +198,13 @@ def merge_by_column_v2(sheet, column_name, other_columns):
195
198
  # 更安全的数据获取方式,确保获取完整的数据范围
196
199
  last_row = get_last_row(sheet, col_letter)
197
200
  data = sheet.range(f'{col_letter}1:{col_letter}{last_row}').value
198
-
201
+
199
202
  # 确保data是列表格式
200
203
  if not isinstance(data, list):
201
204
  data = [data]
202
-
205
+
203
206
  log(f'数据范围: {col_letter}1:{col_letter}{last_row}, 数据长度: {len(data)}')
204
-
207
+
205
208
  start_row = 2 # 从第2行开始,跳过表头
206
209
  merge_row_ranges = [] # 用来存储需要合并的行范围 (start_row, end_row)
207
210
 
@@ -211,13 +214,13 @@ def merge_by_column_v2(sheet, column_name, other_columns):
211
214
  col_name = find_column_by_data(sheet, 1, col)
212
215
  if col_name:
213
216
  all_columns.append(col_name)
214
-
217
+
215
218
  log(f'需要合并的列: {all_columns}')
216
219
 
217
220
  # 遍历数据行,从第3行开始比较(因为第1行是表头,第2行是第一个数据行)
218
221
  for row in range(3, len(data) + 1):
219
- log(f'查找 {row}/{len(data)}, 当前值: {data[row-1] if row-1 < len(data) else "超出范围"}, 前一个值: {data[row-2] if row-2 < len(data) else "超出范围"}')
220
-
222
+ log(f'查找 {row}/{len(data)}, 当前值: {data[row - 1] if row - 1 < len(data) else "超出范围"}, 前一个值: {data[row - 2] if row - 2 < len(data) else "超出范围"}')
223
+
221
224
  # 检查值是否发生变化
222
225
  if row <= len(data) and data[row - 1] != data[row - 2]:
223
226
  # 值发生变化,处理前一组
@@ -233,7 +236,7 @@ def merge_by_column_v2(sheet, column_name, other_columns):
233
236
  merge_row_ranges.append((start_row, end_row))
234
237
 
235
238
  log(f'行合并范围: {merge_row_ranges}')
236
-
239
+
237
240
  # 对每个行范围,在所有指定列中执行合并
238
241
  for start_row, end_row in merge_row_ranges:
239
242
  if start_row < end_row: # 只有当开始行小于结束行时才合并(多行)
@@ -756,9 +759,33 @@ def download_images_concurrently(image_urls, platform='shein', img_save_dir=None
756
759
  results = list(executor.map(lambda url: download_img_v2(url, platform, img_save_path=img_save_dir), image_urls))
757
760
  return results
758
761
 
762
+ def get_chromium_executable():
763
+ """
764
+ 返回 Chromium 可执行文件路径,兼容 PyInstaller 打包后的 exe
765
+ """
766
+ # PyInstaller 临时目录
767
+ if getattr(sys, 'frozen', False):
768
+ base_path = Path(sys._MEIPASS)
769
+ else:
770
+ base_path = Path(__file__).parent
771
+
772
+ # 尝试查找打包后的浏览器文件
773
+ possible_path = base_path / "playwright" / "driver" / "package" / "chromium_headless_shell-1169" / "chrome-win" / "headless_shell.exe"
774
+ if possible_path.exists():
775
+ return str(possible_path)
776
+
777
+ # fallback: 系统 Playwright 安装目录
778
+ local_appdata = Path(os.environ.get("LOCALAPPDATA", ""))
779
+ fallback_path = local_appdata / "ms-playwright" / "chromium" / "chrome-win" / "chrome.exe"
780
+ if fallback_path.exists():
781
+ return str(fallback_path)
782
+
783
+ raise FileNotFoundError("Chromium 可执行文件未找到,请先执行 'playwright install' 下载浏览器。")
784
+
759
785
  def download_img_by_chrome(image_url, save_name):
786
+ chromium_path = get_chromium_executable()
760
787
  with sync_playwright() as p:
761
- browser = p.chromium.launch(headless=True) # 运行时可以看到浏览器
788
+ browser = p.chromium.launch(headless=True, executable_path=chromium_path) # 运行时可以看到浏览器
762
789
  context = browser.new_context()
763
790
  page = context.new_page()
764
791
  # 直接通过Playwright下载图片
@@ -146,6 +146,54 @@ def full_screen_shot(web_page: Page, config):
146
146
  web_page.screenshot(path=full_screenshot_image_path, full_page=True)
147
147
  return full_screenshot_image_path
148
148
 
149
+ def fetch_get(page: Page, url: str, headers: Optional[dict] = None, config: Optional[dict] = None) -> dict:
150
+ """
151
+ 发送 HTTP GET 请求,支持自定义 headers 和配置。
152
+
153
+ :param page: Playwright 的 Page 对象
154
+ :param url: 请求地址
155
+ :param headers: 自定义 headers 字典
156
+ :param config: 请求配置字典,可包含 credentials, mode, referrer, referrerPolicy 等
157
+ :return: 服务器返回的 JSON 响应(dict)
158
+ """
159
+ if headers is not None and not isinstance(headers, dict):
160
+ raise ValueError("headers 参数必须是 dict 或 None")
161
+ if config is not None and not isinstance(config, dict):
162
+ raise ValueError("config 参数必须是 dict 或 None")
163
+
164
+ try:
165
+ page.wait_for_load_state('load')
166
+ response = page.evaluate("""
167
+ async ({ url, extraHeaders, config }) => {
168
+ try {
169
+ const defaultHeaders = {
170
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
171
+ };
172
+
173
+ const defaultConfig = {
174
+ method: 'GET',
175
+ credentials: 'include',
176
+ mode: 'cors'
177
+ };
178
+
179
+ const headers = Object.assign({}, defaultHeaders, extraHeaders || {});
180
+ const options = Object.assign({}, defaultConfig, config || {}, { headers: headers });
181
+
182
+ const response = await fetch(url, options);
183
+ if (!response.ok) {
184
+ throw new Error(`HTTP ${response.status} - ${response.statusText}`);
185
+ }
186
+ return await response.json();
187
+ } catch (error) {
188
+ return { "error": "fetch_failed", "message": error.message };
189
+ }
190
+ }
191
+ """, {"url": url, "extraHeaders": headers, "config": config})
192
+
193
+ return response
194
+ except Exception as e:
195
+ raise send_exception()
196
+
149
197
  def safe_goto(page, url, **kwargs):
150
198
  caller = inspect.stack()[1]
151
199
  log(f"[DEBUG] goto called from {caller.filename}:{caller.lineno} url={url}")
@@ -64,6 +64,9 @@ class SheinProductSkc(Base):
64
64
  created_at = Column(DateTime, default=datetime.now, comment='创建时间')
65
65
  updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
66
66
 
67
+ # 用户备注字段(供后续web界面使用)
68
+ user_notes = Column(Text, nullable=True, comment='用户备注')
69
+
67
70
  # 定义索引
68
71
  __table_args__ = (
69
72
  Index('ix_skc_id', 'skc_id'),
@@ -108,6 +111,7 @@ class SheinProductSku(Base):
108
111
  price = Column(DECIMAL(10, 2), nullable=True, comment='价格')
109
112
  erp_cost_price = Column(DECIMAL(10, 2), nullable=True, comment='ERP成本价')
110
113
  erp_supplier_name = Column(String(100), nullable=True, comment='ERP默认供货商')
114
+ erp_stock = Column(Integer, nullable=True, comment='ERP库存')
111
115
 
112
116
  # 时间戳
113
117
  created_at = Column(DateTime, default=datetime.now, comment='创建时间')
@@ -218,10 +222,10 @@ class SheinProductManager:
218
222
  session = self.Session()
219
223
  try:
220
224
  for data in data_list:
221
-
225
+
222
226
  # 处理SKC数据
223
227
  skc_record = self._upsert_skc_data(session, data)
224
-
228
+
225
229
  # 处理SKU数据
226
230
  for sku_data in data.get('skuList', []):
227
231
  sku_data['local_skc_id'] = skc_record.id
@@ -319,6 +323,7 @@ class SheinProductManager:
319
323
  existing_sku.price = sku_data.get('price')
320
324
  existing_sku.erp_cost_price = sku_data.get('erp_cost_price')
321
325
  existing_sku.erp_supplier_name = sku_data.get('erp_supplier_name')
326
+ existing_sku.erp_stock = sku_data.get('erp_stock')
322
327
  existing_sku.updated_at = datetime.now()
323
328
  else:
324
329
  # 插入新记录
@@ -473,4 +478,7 @@ def example_usage2():
473
478
 
474
479
  if __name__ == "__main__":
475
480
  pass
476
- # example_usage()
481
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
482
+ manager = SheinProductManager(database_url)
483
+ manager.create_tables()
484
+ # example_usage()
@@ -2341,7 +2341,7 @@ class SheinExcel:
2341
2341
  self.dealFormula(sheet) # 有空再封装优化
2342
2342
  colorize_by_field(sheet, 'SPU')
2343
2343
  autofit_column(sheet, ['商品信息', '店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
2344
- column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
2344
+ column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动','近7天SKU销量/SKC销量/SKC曝光'])
2345
2345
  specify_column_width(sheet, ['商品标题'], 150 / 6)
2346
2346
  add_borders(sheet)
2347
2347
  InsertImageV2(sheet, ['SKC图片', 'SKU图片'], 'shein', 120, None, None, True)
@@ -1,6 +1,6 @@
1
1
  from .fun_file import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
2
2
  from .fun_base import log, send_exception, md5_string, get_safe_value, NetWorkIdleTimeout
3
- from .fun_web import fetch, full_screen_shot, safe_goto
3
+ from .fun_web import fetch, fetch_get, full_screen_shot, safe_goto
4
4
  from .time_utils import TimeUtils
5
5
  from .wxwork import WxWorkBot
6
6
 
@@ -36,7 +36,7 @@ class SheinLib:
36
36
  try:
37
37
  current_url = web_page.url
38
38
  log(f"尝试获取页面信息 - URL: {current_url}", self.store_username, self.store_name)
39
-
39
+
40
40
  # 检查是否在认证页面,如果是则直接跳转到目标页面
41
41
  if '/auth/SSLS' in current_url:
42
42
  log("检测到SSLS认证页面,直接跳转到首页", self.store_username, self.store_name)
@@ -44,17 +44,17 @@ class SheinLib:
44
44
  web_page.wait_for_timeout(3000)
45
45
  current_url = web_page.url
46
46
  log(f"跳转后URL: {current_url}", self.store_username, self.store_name)
47
-
47
+
48
48
  # 等待导航完成
49
49
  web_page.wait_for_load_state("domcontentloaded", timeout=6000)
50
-
50
+
51
51
  final_url = web_page.url
52
52
  final_title = web_page.title()
53
53
  log(f"页面稳定 - URL: {final_url}, 标题: {final_title}", self.store_username, self.store_name)
54
54
  break
55
-
55
+
56
56
  except Exception as e:
57
- log(f"第{attempt+1}次等待页面稳定失败: {e}", self.store_username, self.store_name)
57
+ log(f"第{attempt + 1}次等待页面稳定失败: {e}", self.store_username, self.store_name)
58
58
  if "crashed" in str(e) or "Target" in str(e):
59
59
  log("页面稳定检查时崩溃,直接继续", self.store_username, self.store_name)
60
60
  break
@@ -66,7 +66,7 @@ class SheinLib:
66
66
  log("页面稳定等待最终失败,继续执行", self.store_username, self.store_name)
67
67
  break
68
68
  web_page.wait_for_timeout(2000)
69
-
69
+
70
70
  web_page.wait_for_timeout(2000)
71
71
 
72
72
  # 定义最大重试次数
@@ -83,19 +83,19 @@ class SheinLib:
83
83
  retries += 1
84
84
 
85
85
  while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
86
-
86
+
87
87
  try:
88
88
  current_url = web_page.url
89
89
  current_title = web_page.title()
90
90
  log(f"循环检查 - URL: {current_url}, 标题: {current_title}", self.store_username, self.store_name)
91
-
91
+
92
92
  # 如果在认证页面且出现问题,直接跳转
93
93
  if '/auth/SSLS' in current_url:
94
94
  log("在主循环中检测到SSLS认证页面,跳转到首页", self.store_username, self.store_name)
95
95
  web_page.goto('https://sso.geiwohuo.com/#/home', wait_until='domcontentloaded', timeout=15000)
96
96
  web_page.wait_for_timeout(3000)
97
97
  continue
98
-
98
+
99
99
  except Exception as status_error:
100
100
  log(f"获取页面状态失败: {status_error}", self.store_username, self.store_name)
101
101
  if "crashed" in str(status_error):
@@ -200,13 +200,13 @@ class SheinLib:
200
200
  except Exception as e:
201
201
  log(f"错误发生: {e}, 重试中...({self.store_username}, {self.store_name})")
202
202
  log(traceback.format_exc())
203
-
203
+
204
204
  # 收集崩溃时的详细信息
205
205
  try:
206
206
  crash_url = web_page.url
207
207
  crash_title = web_page.title()
208
208
  log(f"崩溃时页面信息 - URL: {crash_url}, 标题: {crash_title}", self.store_username, self.store_name)
209
-
209
+
210
210
  # 尝试截图保存崩溃现场
211
211
  try:
212
212
  screenshot_path = f"crash_screenshot_{self.store_username}_{int(time.time())}.png"
@@ -214,19 +214,19 @@ class SheinLib:
214
214
  log(f"已保存崩溃截图: {screenshot_path}", self.store_username, self.store_name)
215
215
  except:
216
216
  log("无法截取崩溃时的页面截图", self.store_username, self.store_name)
217
-
217
+
218
218
  except:
219
219
  log("无法获取崩溃时的页面信息", self.store_username, self.store_name)
220
-
220
+
221
221
  # 检查特定类型的错误
222
222
  if any(keyword in str(e).lower() for keyword in ['memory', 'out of memory', 'oom']):
223
223
  log("检测到内存相关崩溃", self.store_username, self.store_name)
224
-
224
+
225
225
  if "destroyed" in str(e) or "navigation" in str(e):
226
226
  log("检测到导航中断,等待页面稳定后重试", self.store_username, self.store_name)
227
227
  web_page.wait_for_timeout(5000)
228
228
  continue
229
-
229
+
230
230
  if 'crashed' in str(e) or 'Target' in str(e):
231
231
  log("检测到页面或目标崩溃,直接退出当前循环", self.store_username, self.store_name)
232
232
  raise e
@@ -241,6 +241,44 @@ class SheinLib:
241
241
  # web_page.wait_for_load_state("networkidle")
242
242
  web_page.wait_for_timeout(3000)
243
243
 
244
+ # 获取用户信息
245
+ def get_user(self, uuid=None):
246
+ log(f'获取用户信息:{self.store_username} {self.store_name}')
247
+
248
+ # 生成 uuid 参数,如果没有提供则使用时间戳
249
+ if uuid is None:
250
+ import time
251
+ uuid = str(int(time.time() * 1000))
252
+
253
+ url = f"https://sso.geiwohuo.com/sso-prefix/auth/getUser?uuid={uuid}"
254
+
255
+ # 设置请求头,根据 Chrome 请求
256
+ headers = {
257
+ "gmpsso-language": "CN",
258
+ "origin-url" : "https://sso.geiwohuo.com/#/home/",
259
+ "x-sso-scene" : "gmpsso"
260
+ }
261
+
262
+ # 特定于此请求的配置
263
+ fetch_config = {
264
+ "credentials" : "omit",
265
+ "referrer" : "https://sso.geiwohuo.com/",
266
+ "referrerPolicy": "strict-origin-when-cross-origin"
267
+ }
268
+
269
+ response_text = fetch_get(self.web_page, url, headers, fetch_config)
270
+ error_code = response_text.get('code')
271
+ if str(error_code) != '0':
272
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
273
+ info = response_text.get('info', {})
274
+ log(info)
275
+ cache_file = f'{self.config.auto_dir}/shein_user.json'
276
+ info['store_username'] = self.store_username
277
+ info['store_name'] = self.store_name
278
+ write_dict_to_file_ex(cache_file, {self.store_username: info}, [self.store_username])
279
+
280
+ return info
281
+
244
282
  # 获取质检报告pdf地址
245
283
  def get_qc_report_url(self, deliverCode, purchaseCode):
246
284
  log(f'获取质检报告:{deliverCode} {purchaseCode}')
@@ -849,6 +887,8 @@ class SheinLib:
849
887
  cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], self.config.erp_source)
850
888
  sku_item['erp_cost_price'] = cost_price if isinstance(cost_price, (int, float)) else None
851
889
  sku_item['erp_supplier_name'] = self.bridge.get_sku_supplier(sku_item['supplierSku'], self.config.erp_source)
890
+ stock = self.bridge.get_sku_stock(sku_item['supplierSku'], self.config.erp_source)
891
+ sku_item['erp_stock'] = stock if isinstance(stock, (int, float)) else None
852
892
 
853
893
  cache_file = f'{self.config.auto_dir}/shein/product/skc_list_{self.store_username}.json'
854
894
  write_dict_to_file_ex(cache_file, {self.store_username: skc_list}, [self.store_username])
@@ -2276,6 +2316,7 @@ class SheinLib:
2276
2316
  skc = str(spu_info['skc'])
2277
2317
  # if not shein_db.exists_sales_1_days_ago(skc):
2278
2318
  # log(f'未查到昨天销量: {skc}')
2319
+ self.get_skc_week_actual_sales(skc)
2279
2320
  self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
2280
2321
  skcCode = spu_info['supplierCode']
2281
2322
  product_name = DictSpuInfo[spu]['product_name_en']
@@ -2396,7 +2437,7 @@ class SheinLib:
2396
2437
  # SKC趋势数据
2397
2438
  sku_item.append(skc_trend.get('saleCnt', 0)) # SKC近7天销量
2398
2439
  sku_item.append(skc_trend.get('epsUvIdx', 0)) # SKC近7天曝光人数
2399
- sku_item.append(skc_trend.get('goodsUvIdx', 0)) # SKC近7天商详访客
2440
+ sku_item.append(skc_trend.get('goodsUv', 0)) # SKC近7天商详访客
2400
2441
  sku_item.append(skc_trend.get('epsGdsCtrIdx', 0)) # SKC近7天点击率
2401
2442
  sku_item.append(skc_trend.get('payUvIdx', 0)) # SKC近7天支付人数
2402
2443
  sku_item.append(skc_trend.get('gdsPayCtrIdx', 0)) # SKC近7天支付率
@@ -5,7 +5,7 @@
5
5
  import os
6
6
  import platform
7
7
  import shutil
8
- import time
8
+ import time, datetime
9
9
  import traceback
10
10
  import uuid
11
11
  import json
@@ -74,7 +74,7 @@ class ZiniaoClient:
74
74
  """启动客户端"""
75
75
  try:
76
76
  if self.is_windows:
77
- cmd = [self.client_path, '--run_type=web_driver', '--ipc_type=http', '--port=' + str(self.socket_port)]
77
+ cmd = [self.client_path, '--run_type=web_driver', '--show_sidb=true', '--ipc_type=http', '--port=' + str(self.socket_port)]
78
78
  elif self.is_mac:
79
79
  cmd = ['open', '-a', self.client_path, '--args', '--run_type=web_driver', '--ipc_type=http',
80
80
  '--port=' + str(self.socket_port)]
@@ -272,6 +272,73 @@ class ZiniaoTaskManager:
272
272
  self.browser = browser
273
273
  self.config = config
274
274
 
275
+ def daily_cleanup_superbrowser(self, browser_id):
276
+ """
277
+ 每天删除一次SuperBrowser缓存文件夹
278
+
279
+ Args:
280
+ browser_id (str): 浏览器ID,如 '26986387919128'
281
+ """
282
+
283
+ # 获取本地AppData路径
284
+ local_appdata = os.getenv('LOCALAPPDATA')
285
+ if not local_appdata:
286
+ log("错误: 无法获取LOCALAPPDATA路径")
287
+ return False
288
+
289
+ # 构建路径
290
+ cache_path = os.path.join(local_appdata, 'SuperBrowser')
291
+ target_folder = os.path.join(cache_path, f'User Data\\Chromium_{browser_id}')
292
+ flag_file = os.path.join(cache_path, f'User Data\\cleanup_flag_{browser_id}.txt')
293
+
294
+ # 检查目标文件夹是否存在
295
+ if not os.path.exists(target_folder):
296
+ log(f"目标文件夹不存在: {target_folder}")
297
+ return False
298
+
299
+ # 获取当前日期
300
+ today = datetime.date.today()
301
+ today_str = today.strftime('%Y-%m-%d')
302
+
303
+ # 检查标志文件
304
+ need_cleanup = True
305
+
306
+ if os.path.exists(flag_file):
307
+ try:
308
+ # 读取标志文件中的日期
309
+ with open(flag_file, 'r', encoding='utf-8') as f:
310
+ last_cleanup_date = f.read().strip()
311
+
312
+ # 如果是今天已经清理过,则跳过
313
+ if last_cleanup_date == today_str:
314
+ log(f"今天({today_str})已经清理过,跳过删除操作")
315
+ need_cleanup = False
316
+
317
+ except Exception as e:
318
+ log(f"读取标志文件时出错: {e}")
319
+ # 如果读取出错,继续执行清理
320
+
321
+ if need_cleanup:
322
+ try:
323
+ # 删除目标文件夹
324
+ log(f"正在删除文件夹: {target_folder}")
325
+ shutil.rmtree(target_folder)
326
+ log("删除成功!")
327
+
328
+ # 创建/更新标志文件
329
+ os.makedirs(os.path.dirname(flag_file), exist_ok=True)
330
+ with open(flag_file, 'w', encoding='utf-8') as f:
331
+ f.write(today_str)
332
+
333
+ log(f"已创建标志文件: {flag_file}")
334
+ return True
335
+
336
+ except Exception as e:
337
+ log(f"删除文件夹时出错: {e}")
338
+ return False
339
+
340
+ return True
341
+
275
342
  def run_single_store_task(self, browser_info: Dict[str, Any],
276
343
  run_func: Callable, task_key: str,
277
344
  just_store_username: Optional[List[str]] = None,
@@ -282,6 +349,10 @@ class ZiniaoTaskManager:
282
349
  store_name = browser_info.get("browserName")
283
350
  store_username = browser_info.get("store_username")
284
351
 
352
+ # 删除浏览器缓存,一天一删
353
+ browser_id = browser_info.get("browserId")
354
+ self.daily_cleanup_superbrowser(browser_id)
355
+
285
356
  retry_count = 0
286
357
  while True:
287
358
  try:
@@ -260,20 +260,31 @@ class TimeUtils:
260
260
 
261
261
  @staticmethod
262
262
  def get_dates_from_first_of_month_to_yesterday() -> List[str]:
263
- """获取从本月第一天到昨天的日期列表"""
263
+ """获取从本月第一天到昨天的日期列表
264
+ 如果今天是本月第一天,则返回上个月的日期列表
265
+ """
264
266
  today = datetime.today()
265
- first_day_of_month = today.replace(day=1)
266
267
  yesterday = today - timedelta(days=1)
267
268
 
268
- date_list = []
269
- current_date = first_day_of_month
269
+ # 如果今天是本月第一天,取上个月
270
+ if today.day == 1:
271
+ # 找到上个月最后一天
272
+ last_month_last_day = today - timedelta(days=1)
273
+ # 上个月第一天
274
+ first_day_of_last_month = last_month_last_day.replace(day=1)
275
+ start_date = first_day_of_last_month
276
+ end_date = last_month_last_day
277
+ else:
278
+ start_date = today.replace(day=1)
279
+ end_date = yesterday
270
280
 
271
- while current_date <= yesterday:
281
+ date_list = []
282
+ current_date = start_date
283
+ while current_date <= end_date:
272
284
  date_list.append(current_date.strftime('%Y-%m-%d'))
273
285
  current_date += timedelta(days=1)
274
286
 
275
287
  return date_list
276
-
277
288
  # ==================== 月份相关 ====================
278
289
 
279
290
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.89
3
+ Version: 1.0.91
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes