qrpa 1.0.96__py3-none-any.whl → 1.0.97__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/fun_excel.py CHANGED
@@ -2149,6 +2149,7 @@ def add_formula_for_column(sheet, col_name, formula, start_row=2):
2149
2149
  # AutoFill 快速填充到所有行(start_row 到 last_row)
2150
2150
  sheet.range(f'{col_letter}{start_row}').api.AutoFill(
2151
2151
  sheet.range(f'{col_letter}{start_row}:{col_letter}{last_row}').api)
2152
+ sheet.range(f'{col_letter}:{col_letter}').autofit()
2152
2153
 
2153
2154
  def autofit_column(sheet, columns=None):
2154
2155
  if columns is None:
qrpa/fun_web.py CHANGED
@@ -10,12 +10,13 @@ import inspect
10
10
  def fetch(page: Page, url: str, params: Optional[Union[dict, list, str]] = None, headers: Optional[dict] = None, config:
11
11
  Optional[dict] = None) -> dict:
12
12
  """
13
- 发送 HTTP POST 请求,支持自定义 headers
13
+ 发送 HTTP POST 请求,支持自定义 headers 和重定向处理。
14
14
 
15
15
  :param page: Playwright 的 Page 对象
16
16
  :param url: 请求地址
17
17
  :param params: 请求参数(dict、list、str 或 None)
18
18
  :param headers: 自定义 headers 字典
19
+ :param config: 请求配置字典
19
20
  :return: 服务器返回的 JSON 响应(dict)
20
21
  """
21
22
  if params is not None and not isinstance(params, (dict, list, str)):
@@ -26,7 +27,7 @@ Optional[dict] = None) -> dict:
26
27
  try:
27
28
  page.wait_for_load_state('load')
28
29
  response = page.evaluate("""
29
- async ({ url, params, extraHeaders }) => {
30
+ async ({ url, params, extraHeaders, config }) => {
30
31
  try {
31
32
  const defaultHeaders = {
32
33
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
@@ -37,9 +38,15 @@ Optional[dict] = None) -> dict:
37
38
  const options = {
38
39
  method: 'POST',
39
40
  credentials: 'include',
41
+ redirect: 'follow', // 明确设置跟随重定向
40
42
  headers: headers
41
43
  };
42
44
 
45
+ // 应用额外配置
46
+ if (config) {
47
+ Object.assign(options, config);
48
+ }
49
+
43
50
  if (params !== null) {
44
51
  if (typeof params === 'string') {
45
52
  options.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
@@ -51,15 +58,40 @@ Optional[dict] = None) -> dict:
51
58
  }
52
59
 
53
60
  const response = await fetch(url, options);
61
+
62
+ // 处理重定向
63
+ if (response.redirected) {
64
+ console.log(`请求被重定向到: ${response.url}`);
65
+ }
66
+
54
67
  if (!response.ok) {
68
+ // 如果是重定向相关的状态码,尝试获取响应内容
69
+ if (response.status >= 300 && response.status < 400) {
70
+ const text = await response.text();
71
+ return {
72
+ "error": "redirect_error",
73
+ "message": `HTTP ${response.status} - ${response.statusText}`,
74
+ "redirect_url": response.url,
75
+ "response_text": text,
76
+ "status": response.status
77
+ };
78
+ }
55
79
  throw new Error(`HTTP ${response.status} - ${response.statusText}`);
56
80
  }
57
- return await response.json();
81
+
82
+ // 尝试解析 JSON,如果失败则返回文本内容
83
+ const contentType = response.headers.get('content-type');
84
+ if (contentType && contentType.includes('application/json')) {
85
+ return await response.json();
86
+ } else {
87
+ const text = await response.text();
88
+ return { "content": text, "content_type": contentType, "final_url": response.url };
89
+ }
58
90
  } catch (error) {
59
91
  return { "error": "fetch_failed", "message": error.message };
60
92
  }
61
93
  }
62
- """, {"url": url, "params": params, "extraHeaders": headers})
94
+ """, {"url": url, "params": params, "extraHeaders": headers, "config": config})
63
95
 
64
96
  return response
65
97
  except Exception as e:
@@ -148,7 +180,7 @@ def full_screen_shot(web_page: Page, config):
148
180
 
149
181
  def fetch_get(page: Page, url: str, headers: Optional[dict] = None, config: Optional[dict] = None) -> dict:
150
182
  """
151
- 发送 HTTP GET 请求,支持自定义 headers 和配置。
183
+ 发送 HTTP GET 请求,支持自定义 headers 和配置,支持重定向处理。
152
184
 
153
185
  :param page: Playwright 的 Page 对象
154
186
  :param url: 请求地址
@@ -173,17 +205,43 @@ def fetch_get(page: Page, url: str, headers: Optional[dict] = None, config: Opti
173
205
  const defaultConfig = {
174
206
  method: 'GET',
175
207
  credentials: 'include',
176
- mode: 'cors'
208
+ mode: 'cors',
209
+ redirect: 'follow' // 明确设置跟随重定向
177
210
  };
178
211
 
179
212
  const headers = Object.assign({}, defaultHeaders, extraHeaders || {});
180
213
  const options = Object.assign({}, defaultConfig, config || {}, { headers: headers });
181
214
 
182
215
  const response = await fetch(url, options);
216
+
217
+ // 处理重定向
218
+ if (response.redirected) {
219
+ console.log(`请求被重定向到: ${response.url}`);
220
+ }
221
+
183
222
  if (!response.ok) {
223
+ // 如果是重定向相关的状态码,尝试获取响应内容
224
+ if (response.status >= 300 && response.status < 400) {
225
+ const text = await response.text();
226
+ return {
227
+ "error": "redirect_error",
228
+ "message": `HTTP ${response.status} - ${response.statusText}`,
229
+ "redirect_url": response.url,
230
+ "response_text": text,
231
+ "status": response.status
232
+ };
233
+ }
184
234
  throw new Error(`HTTP ${response.status} - ${response.statusText}`);
185
235
  }
186
- return await response.json();
236
+
237
+ // 尝试解析 JSON,如果失败则返回文本内容
238
+ const contentType = response.headers.get('content-type');
239
+ if (contentType && contentType.includes('application/json')) {
240
+ return await response.json();
241
+ } else {
242
+ const text = await response.text();
243
+ return { "content": text, "content_type": contentType, "final_url": response.url };
244
+ }
187
245
  } catch (error) {
188
246
  return { "error": "fetch_failed", "message": error.message };
189
247
  }
qrpa/shein_excel.py CHANGED
@@ -2442,7 +2442,7 @@ class SheinExcel:
2442
2442
  self.dealFormula(sheet) # 有空再封装优化
2443
2443
  colorize_by_field(sheet, 'SPU')
2444
2444
  autofit_column(sheet, ['商品信息', '店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
2445
- column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动','近7天SKU销量/SKC销量/SKC曝光'])
2445
+ column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动', '近7天SKU销量/SKC销量/SKC曝光'])
2446
2446
  specify_column_width(sheet, ['商品标题'], 150 / 6)
2447
2447
  add_borders(sheet)
2448
2448
  InsertImageV2(sheet, ['SKC图片', 'SKU图片'], 'shein', 120, None, None, True)
@@ -2530,3 +2530,112 @@ class SheinExcel:
2530
2530
  sheet.range(rangeF).number_format = '0.00'
2531
2531
  sheet.range(rangeG).formula = f'=IF(ISNUMBER({rangeB}),{col_week_1}{row}*{col_gross_profit}{row},"")'
2532
2532
  sheet.range(rangeG).number_format = '0.00'
2533
+
2534
+ def write_check_order(self, erp, start_date, end_date):
2535
+ header = ['店铺账号', '店铺别名', '店长', '报账单号', '货号', 'SKC', '平台SKU', '商家SKU', '属性集', '商品数量', '账单类型', '收支类型', '状态', '币种', '金额', 'ERP成本',
2536
+ '成本总额', '业务单号', '费用类型', '备注', '来源单号', '账单创建时间', '台账添加时间', '报账时间', '预计结算日期', '实际结算日期']
2537
+ excel_data = [header]
2538
+
2539
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2540
+
2541
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
2542
+ dict = read_dict_from_file(cache_file)
2543
+ for store_username, data_list in dict.items():
2544
+ for item in data_list:
2545
+ store_name = dict_store.get(store_username)
2546
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
2547
+
2548
+ row_item = []
2549
+ row_item.append(store_username)
2550
+ row_item.append(store_name)
2551
+ row_item.append(store_manager)
2552
+ row_item.append(item['reportOrderNo'])
2553
+ row_item.append(item['goodsSn'])
2554
+ row_item.append(item['skcName'])
2555
+ row_item.append(item['skuCode'])
2556
+ row_item.append(item['skuSn'])
2557
+ row_item.append(item['suffix'])
2558
+ row_item.append(item['goodsCount'])
2559
+ row_item.append(item['secondOrderTypeName'])
2560
+ row_item.append(item['inAndOutName'])
2561
+ row_item.append(item['settlementStatusName'])
2562
+ row_item.append(item['settleCurrencyCode'])
2563
+ row_item.append(item['income'])
2564
+ row_item.append(self.bridge.get_sku_cost(item['skuSn'], erp))
2565
+ row_item.append('')
2566
+ row_item.append(item['bzOrderNo'])
2567
+ row_item.append(item['expenseTypeName'])
2568
+ row_item.append(item['remark'])
2569
+ row_item.append(item['sourceNo'])
2570
+ row_item.append(item['addTime'])
2571
+ row_item.append(item['businessCompletedTime'])
2572
+ row_item.append(item['reportTime'])
2573
+ row_item.append(item['estimatePayTime'])
2574
+ row_item.append(item['completedPayTime'])
2575
+
2576
+ excel_data.append(row_item)
2577
+
2578
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
2579
+ write_dict_to_file(cache_file_excel, excel_data)
2580
+
2581
+ sheet_name = '收支明细'
2582
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
2583
+ (sheet_name, 'write', excel_data, ['R']),
2584
+ (sheet_name, 'format', self.format_check_order)
2585
+ ])
2586
+
2587
+ header = ['店铺账号', '店铺别名', '店长', '出库金额', '出库成本', '备货作业费', '代收服务费', '订单履约服务费', '订单退货', '退货处理费', '退货单履约服务费', '利润']
2588
+ excel_data = [header]
2589
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
2590
+ dict = read_dict_from_file(cache_file)
2591
+ for store_username, data_list in dict.items():
2592
+ store_name = dict_store.get(store_username)
2593
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
2594
+ row_item = []
2595
+ row_item.append(store_username)
2596
+ row_item.append(store_name)
2597
+ row_item.append(store_manager)
2598
+ row_item.append('')
2599
+ row_item.append('')
2600
+ row_item.append('')
2601
+ row_item.append('')
2602
+ row_item.append('')
2603
+ row_item.append('')
2604
+ row_item.append('')
2605
+ row_item.append('')
2606
+ row_item.append('')
2607
+ excel_data.append(row_item)
2608
+
2609
+ sheet_name = '总表'
2610
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
2611
+ (sheet_name, 'write', excel_data),
2612
+ (sheet_name, 'format', self.format_check_order),
2613
+ ('Sheet1', 'delete'),
2614
+ (sheet_name, 'move', 1),
2615
+ ])
2616
+
2617
+ def format_check_order(self, sheet):
2618
+ if sheet.name == '收支明细':
2619
+ beautify_title(sheet)
2620
+ add_borders(sheet)
2621
+ format_to_datetime(sheet, ['时间'])
2622
+ format_to_date(sheet, ['日期'])
2623
+ format_to_money(sheet, ['金额', '成本'])
2624
+ column_to_right(sheet, ['金额', '成本'])
2625
+ column_to_left(sheet, ['货号', '商家SKU'])
2626
+ add_formula_for_column(sheet, '成本总额', '=IF(ISNUMBER(O2),O2*J2,0)')
2627
+
2628
+ if sheet.name == '总表':
2629
+ beautify_title(sheet)
2630
+ add_borders(sheet)
2631
+ format_to_money(sheet, ['金额', '成本', '费', '订单退货', '利润'])
2632
+ column_to_right(sheet, ['金额', '成本', '费', '订单退货', '利润'])
2633
+ add_formula_for_column(sheet, '出库金额', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"收入")')
2634
+ add_formula_for_column(sheet, '出库成本', '=SUMIFS(收支明细!P:P,收支明细!A:A,A2,收支明细!L:L,"收入")')
2635
+ add_formula_for_column(sheet, '备货作业费', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"备货作业费")')
2636
+ add_formula_for_column(sheet, '代收服务费', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"代收服务费")')
2637
+ add_formula_for_column(sheet, '订单履约服务费', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"订单履约服务费")')
2638
+ add_formula_for_column(sheet, '订单退货', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"订单退货")')
2639
+ add_formula_for_column(sheet, '退货处理费', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"退货处理费")')
2640
+ add_formula_for_column(sheet, '退货单履约服务费', '=SUMIFS(收支明细!O:O,收支明细!A:A,A2,收支明细!L:L,"支出",收支明细!K:K,"退货单履约服务费")')
2641
+ add_formula_for_column(sheet, '利润', '=D2-E2-F2-G2-H2-I2-J2')
qrpa/shein_lib.py CHANGED
@@ -26,6 +26,7 @@ class SheinLib:
26
26
  self.DictQueryTime = {}
27
27
 
28
28
  self.deal_auth()
29
+ # self.get_user()
29
30
 
30
31
  # 处理鉴权
31
32
  def deal_auth(self):
@@ -101,6 +102,10 @@ class SheinLib:
101
102
  if "crashed" in str(status_error):
102
103
  break
103
104
 
105
+ if web_page.locator('xpath=//div[text()="扫码登录"]').is_visible():
106
+ log('检查到扫码登录,切换至账号登录', self.store_username, self.store_name)
107
+ web_page.locator('xpath=//*[@id="container"]/div[2]/div[4]/img').click()
108
+
104
109
  if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
105
110
  if 'https://sso.geiwohuo.com/#/home' not in web_page.url:
106
111
  log("鉴权确定按钮可见 点击'确定'按钮", web_page.title(), web_page.url, self.store_username, self.store_name)
@@ -2889,3 +2894,45 @@ class SheinLib:
2889
2894
  write_dict_to_file_ex(cache_file, {self.store_name: NotifyItem}, {self.store_name})
2890
2895
 
2891
2896
  return excel_data
2897
+
2898
+ def check_order_list(self, source, first_day, last_day):
2899
+ page_num = 1
2900
+ page_size = 200 # 列表最多返回200条数据 大了没有用
2901
+
2902
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{first_day}_{last_day}.json'
2903
+ list_item = read_dict_from_file_ex(cache_file, self.store_username)
2904
+
2905
+ url = f"https://sso.geiwohuo.com/gsfs/finance/reportOrder/dualMode/checkOrderList/item/union"
2906
+ payload = {
2907
+ "page" : page_num,
2908
+ "perPage" : page_size,
2909
+ "detailAddTimeStart": f"{first_day} 00:00:00",
2910
+ "detailAddTimeEnd" : f"{last_day} 23:59:59"
2911
+ }
2912
+ response_text = fetch(self.web_page, url, payload)
2913
+ error_code = response_text.get('code')
2914
+ if str(error_code) != '0':
2915
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
2916
+ list_item = response_text['info']['data']
2917
+ total = response_text['info']['meta']['count']
2918
+ totalPage = math.ceil(total / page_size)
2919
+
2920
+ if int(total) == len(list_item):
2921
+ log('总数与缓存数量相同 跳过剩余页抓取', total)
2922
+ return list_item
2923
+
2924
+ for page in range(2, totalPage + 1):
2925
+ log(f'获取收支明细列表 第{page}/{totalPage}页')
2926
+ payload['pageNumber'] = page
2927
+ response_text = fetch(self.web_page, url, payload)
2928
+ spu_list_new = response_text['info']['data']
2929
+ list_item += spu_list_new
2930
+ time.sleep(0.1)
2931
+
2932
+ for item in list_item:
2933
+ supplierSku = item['skuSn']
2934
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, source)
2935
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, source)
2936
+
2937
+ write_dict_to_file_ex(cache_file, {self.store_username: list_item}, [self.store_username])
2938
+ return list_item
qrpa/shein_ziniao.py CHANGED
@@ -488,7 +488,7 @@ class ZiniaoRunner:
488
488
  raise RuntimeError("店铺列表为空")
489
489
 
490
490
  # 多线程并发执行任务
491
- max_threads = 1 if (hostname().lower() == 'krrpa') else 3
491
+ max_threads = 3 if (hostname().lower() == 'krrpa' or hostname().lower() == 'jyrpa') else 3
492
492
  log(f'当前启用线程数: {max_threads}')
493
493
  self.task_manager.run_with_thread_pool(browser_list, max_threads, run, task_key, just_store_username, is_skip_store)
494
494
 
qrpa/time_utils.py CHANGED
@@ -8,7 +8,6 @@ import calendar
8
8
  from datetime import date, datetime, timedelta, timezone
9
9
  from typing import Optional, Tuple, List, Union
10
10
 
11
-
12
11
  class TimeUtils:
13
12
  """时间工具类,提供各种时间相关的静态方法"""
14
13
 
@@ -94,6 +93,17 @@ class TimeUtils:
94
93
  last_month = today.month - 1 if today.month > 1 else 12
95
94
  return last_month
96
95
 
96
+ @staticmethod
97
+ def get_last_two_month() -> int:
98
+ """获取上上个月的月份"""
99
+ today = datetime.today()
100
+ # 计算上上个月:当前月份减2
101
+ last_two_month = today.month - 2
102
+ # 处理跨年情况
103
+ if last_two_month < 1:
104
+ last_two_month += 12
105
+ return last_two_month
106
+
97
107
  # ==================== 日期范围计算 ====================
98
108
 
99
109
  @staticmethod
@@ -285,6 +295,7 @@ class TimeUtils:
285
295
  current_date += timedelta(days=1)
286
296
 
287
297
  return date_list
298
+
288
299
  # ==================== 月份相关 ====================
289
300
 
290
301
  @staticmethod
@@ -299,6 +310,24 @@ class TimeUtils:
299
310
 
300
311
  return first_day.strftime("%Y-%m-%d"), last_day.strftime("%Y-%m-%d")
301
312
 
313
+ @staticmethod
314
+ def get_last_two_month_range() -> Tuple[str, str]:
315
+ """获取上上个月的第一天和最后一天"""
316
+ today = datetime.today()
317
+ # 计算上上个月
318
+ last_two_month = today.month - 2
319
+ year = today.year
320
+
321
+ # 处理跨年情况
322
+ if last_two_month < 1:
323
+ last_two_month += 12
324
+ year -= 1
325
+
326
+ first_day = datetime(year, last_two_month, 1)
327
+ last_day = datetime(year, last_two_month, calendar.monthrange(year, last_two_month)[1])
328
+
329
+ return first_day.strftime("%Y-%m-%d"), last_day.strftime("%Y-%m-%d")
330
+
302
331
  @staticmethod
303
332
  def get_last_month_range_time_str() -> Tuple[str, str]:
304
333
  """获取上个月第一天和最后一天的时间字符串"""
@@ -618,7 +647,6 @@ class TimeUtils:
618
647
 
619
648
  return mtime_dt.strftime('%Y-%m-%d %H:%M:%S') if to_str else mtime_dt
620
649
 
621
-
622
650
  # ==================== 便捷函数 ====================
623
651
  # 为了保持向后兼容,提供一些便捷函数
624
652
 
@@ -626,197 +654,158 @@ def get_current_date() -> str:
626
654
  """获取当前日期,格式为 YYYYMMDD"""
627
655
  return TimeUtils.get_current_date()
628
656
 
629
-
630
657
  def get_current_datetime() -> str:
631
658
  """获取当前日期时间,格式为 YYYYMMDDHHMMSS"""
632
659
  return TimeUtils.get_current_datetime()
633
660
 
634
-
635
661
  def current_datetime() -> str:
636
662
  """获取当前日期时间,格式为 YYYY-MM-DD HH:MM:SS"""
637
663
  return TimeUtils.current_datetime()
638
664
 
639
-
640
665
  def today_date() -> str:
641
666
  """获取今天的日期,格式为 YYYY-MM-DD"""
642
667
  return TimeUtils.today_date()
643
668
 
644
-
645
669
  def today_date_hour() -> str:
646
670
  """获取今天的日期和小时,格式为 YYYY-MM-DD_HH"""
647
671
  return TimeUtils.today_date_hour()
648
672
 
649
-
650
673
  def get_yesterday(dt: Optional[str] = None) -> str:
651
674
  """获取昨天的日期"""
652
675
  return TimeUtils.get_yesterday(dt)
653
676
 
654
-
655
677
  def tomorrow_date() -> str:
656
678
  """获取明天的日期"""
657
679
  return TimeUtils.tomorrow_date()
658
680
 
659
-
660
681
  def before_yesterday() -> str:
661
682
  """获取前天的日期"""
662
683
  return TimeUtils.before_yesterday()
663
684
 
664
-
665
685
  def get_current_year() -> int:
666
686
  """获取当前年份"""
667
687
  return TimeUtils.get_current_year()
668
688
 
669
-
670
689
  def get_current_month() -> int:
671
690
  """获取当前月份"""
672
691
  return TimeUtils.get_current_month()
673
692
 
674
-
675
693
  def get_last_month() -> int:
676
694
  """获取上个月的月份"""
677
695
  return TimeUtils.get_last_month()
678
696
 
679
-
680
697
  def get_week_num() -> int:
681
698
  """获取当前是第几周"""
682
699
  return TimeUtils.get_week_num()
683
700
 
684
-
685
701
  def get_period() -> str:
686
702
  """获取当前时间段(上午/下午/晚上)"""
687
703
  return TimeUtils.get_period()
688
704
 
689
-
690
705
  def get_period2() -> str:
691
706
  """获取当前时间段(AM/PM)"""
692
707
  return TimeUtils.get_period2()
693
708
 
694
-
695
709
  def get_chinese_weekday(date_str: str) -> str:
696
710
  """根据输入的日期字符串返回中文星期几"""
697
711
  return TimeUtils.get_chinese_weekday(date_str)
698
712
 
699
-
700
713
  def get_weekday_name(date_str: str) -> str:
701
714
  """获取中文星期名称(简短格式)"""
702
715
  return TimeUtils.get_weekday_name(date_str)
703
716
 
704
-
705
717
  def is_in_month(time_str: str, month: int, fmt: str = "%Y-%m-%d") -> bool:
706
718
  """判断时间字符串是否在指定月份"""
707
719
  return TimeUtils.is_in_month(time_str, month, fmt)
708
720
 
709
-
710
721
  def date_trans(d_t: str) -> str:
711
722
  """无斜杠日期转成有斜杠日期"""
712
723
  return TimeUtils.date_trans(d_t)
713
724
 
714
-
715
725
  def is_date_greater_or_equal(date_str: str) -> bool:
716
726
  """比较指定日期是否大于或等于今天的日期"""
717
727
  return TimeUtils.is_date_greater_or_equal(date_str)
718
728
 
719
-
720
729
  def is_yesterday(create_time_str: str, dt: Optional[str] = None) -> bool:
721
730
  """判断给定的时间字符串是否是昨天"""
722
731
  return TimeUtils.is_yesterday(create_time_str, dt)
723
732
 
724
-
725
733
  def is_yesterday_date(date_str: str) -> bool:
726
734
  """判断给定的日期字符串是否是昨天"""
727
735
  return TimeUtils.is_yesterday_date(date_str)
728
736
 
729
-
730
737
  def get_file_mtime(file_path: str, to_str: bool = True, tz_offset: int = 8) -> Union[str, datetime]:
731
738
  """获取文件的修改时间"""
732
739
  return TimeUtils.get_file_mtime(file_path, to_str, tz_offset)
733
740
 
734
-
735
741
  def convert_timestamp_to_str(timestamp_ms: Optional[int]) -> str:
736
742
  """将毫秒时间戳转换为字符串"""
737
743
  return TimeUtils.convert_timestamp_to_str(timestamp_ms)
738
744
 
739
-
740
745
  def convert_timestamp_to_date(timestamp_ms: Union[int, float]) -> str:
741
746
  """将毫秒时间戳转换为日期字符串"""
742
747
  return TimeUtils.convert_timestamp_to_date(timestamp_ms)
743
748
 
744
-
745
749
  def convert_datetime_to_date(datetime_str: str) -> str:
746
750
  """将格式为 'YYYY-MM-DD HH:MM:SS' 的时间字符串转换为 'YYYY-MM-DD' 格式的日期字符串"""
747
751
  return TimeUtils.convert_datetime_to_date(datetime_str)
748
752
 
749
-
750
753
  def get_current_year_range() -> Tuple[str, str]:
751
754
  """获取当前年份的开始和结束日期"""
752
755
  return TimeUtils.get_current_year_range()
753
756
 
754
-
755
757
  def get_start_timestamps(date_str: str) -> int:
756
758
  """获取指定日期的开始毫秒时间戳(00:00:00.000)"""
757
759
  return TimeUtils.get_start_timestamps(date_str)
758
760
 
759
-
760
761
  def get_end_timestamps(date_str: str) -> int:
761
762
  """获取指定日期的结束毫秒时间戳(23:59:59.999)"""
762
763
  return TimeUtils.get_end_timestamps(date_str)
763
764
 
764
-
765
765
  def format_date_cross_platform(date_str: str) -> str:
766
766
  """跨平台格式化日期"""
767
767
  return TimeUtils.format_date_cross_platform(date_str)
768
768
 
769
-
770
769
  def get_past_7_days_range(start_from: Optional[str] = None) -> Tuple[str, str]:
771
770
  """获取过去7天的日期范围(包括结束日,共7天)"""
772
771
  return TimeUtils.get_past_7_days_range(start_from)
773
772
 
774
-
775
773
  def get_past_7_days_range_format(start_from: Optional[str] = None, format_str: str = '%Y-%m-%d') -> Tuple[str, str]:
776
774
  """获取过去7天的日期范围(包括结束日,共7天),支持自定义格式"""
777
775
  return TimeUtils.get_past_7_days_range_format(start_from, format_str)
778
776
 
779
-
780
777
  def get_past_nth_day(n: int, start_from: Optional[str] = None) -> str:
781
778
  """获取过去第n天的日期"""
782
779
  return TimeUtils.get_past_nth_day(n, start_from)
783
780
 
784
-
785
781
  def get_past_n_days_list(n: int, start_from: Optional[str] = None) -> List[str]:
786
782
  """获取过去n天的日期列表,从最旧到最近的日期"""
787
783
  return TimeUtils.get_past_n_days_list(n, start_from)
788
784
 
789
-
790
785
  def get_past_7_days_list(start_from: Optional[str] = None) -> List[str]:
791
786
  """获取过去7天的日期列表(不包含 start_from 当天),共7天"""
792
787
  return TimeUtils.get_past_7_days_list(start_from)
793
788
 
794
-
795
789
  def date_range(start_date: str, end_date: str) -> List[str]:
796
790
  """生成两个日期之间的日期列表"""
797
791
  return TimeUtils.date_range(start_date, end_date)
798
792
 
799
-
800
793
  def get_dates_from_first_of_month_to_yesterday() -> List[str]:
801
794
  """获取从本月第一天到昨天的日期列表"""
802
795
  return TimeUtils.get_dates_from_first_of_month_to_yesterday()
803
796
 
804
-
805
797
  def get_last_month_range() -> Tuple[str, str]:
806
798
  """获取上个月的第一天和最后一天"""
807
799
  return TimeUtils.get_last_month_range()
808
800
 
809
-
810
801
  def get_last_month_range_time_str() -> Tuple[str, str]:
811
802
  """获取上个月第一天和最后一天的时间字符串"""
812
803
  return TimeUtils.get_last_month_range_time_str()
813
804
 
814
-
815
805
  def get_last_month_range_time() -> Tuple[int, int]:
816
806
  """获取上个月第一天和最后一天的毫秒级时间戳"""
817
807
  return TimeUtils.get_last_month_range_time()
818
808
 
819
-
820
809
  # 为了向后兼容,保留一些旧函数名
821
810
  def get_past_7_days_range_old() -> Tuple[str, str]:
822
811
  """获取过去7天的日期范围(旧版本)"""
@@ -824,7 +813,6 @@ def get_past_7_days_range_old() -> Tuple[str, str]:
824
813
  start_date = end_date - timedelta(days=6) # 往前推6天为开始日期
825
814
  return start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d')
826
815
 
827
-
828
816
  def get_past_7_days_list_old() -> List[str]:
829
817
  """获取过去7天的日期列表(旧版本)"""
830
818
  today = datetime.today()
@@ -833,7 +821,6 @@ def get_past_7_days_list_old() -> List[str]:
833
821
  for i in range(1, 8)
834
822
  ]
835
823
 
836
-
837
824
  if __name__ == "__main__":
838
825
  # 测试示例
839
826
  print(f"当前日期: {today_date()}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.96
3
+ Version: 1.0.97
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -5,26 +5,26 @@ qrpa/feishu_bot_app.py,sha256=6r2YqCAMUN7y3F7onoABRmHC7S-UPOLHwbhsQnQ3IRc,9452
5
5
  qrpa/feishu_client.py,sha256=gXvyhf7r-IqeDhPjM01SfsGf17t8g1ZwUAkhymBkBeg,17544
6
6
  qrpa/feishu_logic.py,sha256=yuwb-LeZiHKGlz-W8JobinorHonVa8L-5h12WxnU7_Q,67508
7
7
  qrpa/fun_base.py,sha256=lMzqPwsbVfe968CUR2MVNImzIskIUZqPCE2tWxYqb5o,10728
8
- qrpa/fun_excel.py,sha256=knjBXTYdAz3HGAL3MHrLaF0OxfnR7qkCdzU0e7BKEHQ,117531
8
+ qrpa/fun_excel.py,sha256=58Fjr1bX1KvRIm0NwaXMDq8yIcjBv4c8e5Zar4Mxaaw,117592
9
9
  qrpa/fun_file.py,sha256=yzjDV16WL5vRys7J4uQcNzIFkX4D5MAlSCwxcD-mwQo,11966
10
- qrpa/fun_web.py,sha256=Mv0m2P5fh2-U4DzRoVivqRcXD56BJ_Svj6TZznx49YU,8645
10
+ qrpa/fun_web.py,sha256=2aYrtRlf-p-P6AyjrlIcmMVq8oSDePEY48YLm4ZRS1A,11857
11
11
  qrpa/fun_win.py,sha256=-LnTeocdTt72NVH6VgLdpAT9_C5oV9okeudXG6CftMA,8034
12
12
  qrpa/shein_daily_report_model.py,sha256=H8oZmIN5Pyqe306W1_xuz87lOqLQ_LI5RjXbaxDkIzE,12589
13
- qrpa/shein_excel.py,sha256=-X7RuE5VdEEvnpnGqHh_z8NLodsGCqOKbHADW0X-gcs,131132
14
- qrpa/shein_lib.py,sha256=Mzvooz7abPP61XzzrLj8EzDxJYPk-tPjqQLHgdOk64A,134380
13
+ qrpa/shein_excel.py,sha256=QMgXHELu7-zR9CujN1W66qj8S3tDVJPfToW9i90_WaI,137668
14
+ qrpa/shein_lib.py,sha256=kTA1CNITz99VnC3EqpHHkOk4B7dVdT1BDkUHjx3WrqI,136643
15
15
  qrpa/shein_mysql.py,sha256=Sgz6U0_3f4cT5zPf1Ht1OjvSFhrVPLkMxt91NV-ZPCM,3005
16
16
  qrpa/shein_sqlite.py,sha256=ZQwD0Gz81q9WY7tY2HMEYvSF9r3N_G_Aur3bYfST9WY,5707
17
- qrpa/shein_ziniao.py,sha256=fnxWchn6GcS4IohxhFnY6szUTrP3KLtJzxlE-5-43oY,21306
17
+ qrpa/shein_ziniao.py,sha256=X7BupME0y80-MGMTbJ4C9EG2tGDJuVIymFV1MR61m_c,21339
18
18
  qrpa/temu_chrome.py,sha256=CbtFy1QPan9xJdJcNZj-EsVGhUvv3ZTEPVDEA4-im40,2803
19
19
  qrpa/temu_excel.py,sha256=2hGw76YWzkTZGyFCuuUAab4oHptYX9a6U6yjpNsL7FE,6990
20
20
  qrpa/temu_lib.py,sha256=hYB59zsLS3m3NTic_duTwPMOTSxlHyQVa8OhHnHm-1g,7199
21
- qrpa/time_utils.py,sha256=KFXnF1pMWACAKOSy483riPqvTsMjhQtFJAIhQsZKaMk,30075
21
+ qrpa/time_utils.py,sha256=BMaR5gUl9K2z1qOpKgwrK7a3_NQ5dqcRQCbxn0mXusY,30971
22
22
  qrpa/time_utils_example.py,sha256=shHOXKKF3QSzb0SHsNc34M61wEkkLuM30U9X1THKNS8,8053
23
23
  qrpa/wxwork.py,sha256=gIytG19DZ5g7Tsl0-W3EbjfSnpIqZw-ua24gcB78YEg,11264
24
24
  qrpa/mysql_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  qrpa/mysql_module/shein_product_model.py,sha256=v21_juWsZqTHj2k08GUGNkoRZ4hkKZKjaE-wmRl2a2k,19257
26
26
  qrpa/mysql_module/shein_return_order_model.py,sha256=Zt-bGOH_kCDbakW7uaTmqqo_qTT8v424yidcYSfWvWM,26562
27
- qrpa-1.0.96.dist-info/METADATA,sha256=vqX0zVnRLx8ZSVaZ5njERMQflZTzENd38ky14eNQj-Q,231
28
- qrpa-1.0.96.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- qrpa-1.0.96.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
30
- qrpa-1.0.96.dist-info/RECORD,,
27
+ qrpa-1.0.97.dist-info/METADATA,sha256=aptoNJ0O4Cl_wzSgwydE3RD11yfHm-_NRi9sdZWJs1g,231
28
+ qrpa-1.0.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ qrpa-1.0.97.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
30
+ qrpa-1.0.97.dist-info/RECORD,,
File without changes