qrpa 1.0.39__tar.gz → 1.0.41__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 (33) hide show
  1. {qrpa-1.0.39 → qrpa-1.0.41}/PKG-INFO +1 -1
  2. {qrpa-1.0.39 → qrpa-1.0.41}/pyproject.toml +1 -1
  3. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/feishu_client.py +76 -2
  4. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/fun_web.py +3 -6
  5. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa.egg-info/PKG-INFO +1 -1
  6. {qrpa-1.0.39 → qrpa-1.0.41}/README.md +0 -0
  7. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/RateLimitedSender.py +0 -0
  8. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/__init__.py +0 -0
  9. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/db_migrator.py +0 -0
  10. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/feishu_bot_app.py +0 -0
  11. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/feishu_logic.py +0 -0
  12. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/fun_base.py +0 -0
  13. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/fun_excel.py +0 -0
  14. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/fun_file.py +0 -0
  15. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/fun_win.py +0 -0
  16. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/shein_daily_report_model.py +0 -0
  17. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/shein_excel.py +0 -0
  18. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/shein_lib.py +0 -0
  19. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/shein_sqlite.py +0 -0
  20. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/shein_ziniao.py +0 -0
  21. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/temu_chrome.py +0 -0
  22. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/temu_excel.py +0 -0
  23. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/temu_lib.py +0 -0
  24. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/time_utils.py +0 -0
  25. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/time_utils_example.py +0 -0
  26. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa/wxwork.py +0 -0
  27. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa.egg-info/SOURCES.txt +0 -0
  28. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa.egg-info/dependency_links.txt +0 -0
  29. {qrpa-1.0.39 → qrpa-1.0.41}/qrpa.egg-info/top_level.txt +0 -0
  30. {qrpa-1.0.39 → qrpa-1.0.41}/setup.cfg +0 -0
  31. {qrpa-1.0.39 → qrpa-1.0.41}/setup.py +0 -0
  32. {qrpa-1.0.39 → qrpa-1.0.41}/tests/test_db_migrator.py +0 -0
  33. {qrpa-1.0.39 → qrpa-1.0.41}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.39
3
+ Version: 1.0.41
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.39"
7
+ version = "1.0.41"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -3,19 +3,29 @@ import requests
3
3
  import json
4
4
  import time
5
5
  import base64, os
6
+ import threading
6
7
  from urllib.parse import urlparse
7
8
  from typing import Optional, Dict, List, Any
9
+ from collections import deque
8
10
 
9
11
  from .fun_base import log
10
12
 
11
13
  class FeishuClient:
12
- def __init__(self, app_id: str, app_secret: str):
14
+ def __init__(self, app_id: str, app_secret: str, max_requests_per_second: int = 90, skip_token_refresh: bool = False):
13
15
  self.app_id = app_id
14
16
  self.app_secret = app_secret
15
17
  self.base_url = "https://open.feishu.cn/open-apis"
16
18
  self.tenant_access_token = None
17
19
  self.token_expire_time = 0
18
- self._refresh_token()
20
+
21
+ # 速率限制相关参数
22
+ self.max_requests_per_second = max_requests_per_second
23
+ self.request_times = deque() # 存储请求时间戳
24
+ self.rate_limit_lock = threading.Lock() # 保证线程安全
25
+
26
+ # 只有在不跳过token刷新时才获取token
27
+ if not skip_token_refresh:
28
+ self._refresh_token()
19
29
 
20
30
  def _refresh_token(self):
21
31
  url = f"{self.base_url}/auth/v3/tenant_access_token/internal"
@@ -41,8 +51,72 @@ class FeishuClient:
41
51
  def _ensure_valid_token(self):
42
52
  if not self.tenant_access_token or time.time() > self.token_expire_time:
43
53
  self._refresh_token()
54
+
55
+ def _wait_for_rate_limit(self):
56
+ """
57
+ 实现请求频率限制,确保不超过90次/秒
58
+ 使用滑动窗口算法控制请求频率
59
+ """
60
+ with self.rate_limit_lock:
61
+ current_time = time.time()
62
+
63
+ # 清理1秒前的请求记录
64
+ while self.request_times and current_time - self.request_times[0] > 1.0:
65
+ self.request_times.popleft()
66
+
67
+ # 如果当前秒内请求数已达到限制,则等待
68
+ if len(self.request_times) >= self.max_requests_per_second:
69
+ # 计算需要等待的时间
70
+ oldest_request_time = self.request_times[0]
71
+ wait_time = 1.0 - (current_time - oldest_request_time)
72
+ if wait_time > 0:
73
+ log(f"请求频率限制: 等待 {wait_time:.3f} 秒")
74
+ time.sleep(wait_time)
75
+ # 重新获取当前时间并清理过期记录
76
+ current_time = time.time()
77
+ while self.request_times and current_time - self.request_times[0] > 1.0:
78
+ self.request_times.popleft()
79
+
80
+ # 记录当前请求时间
81
+ self.request_times.append(current_time)
82
+
83
+ def get_current_request_rate(self):
84
+ """
85
+ 获取当前的请求频率 (最近1秒内的请求数)
86
+
87
+ Returns:
88
+ int: 最近1秒内的请求数
89
+ """
90
+ with self.rate_limit_lock:
91
+ current_time = time.time()
92
+ # 清理1秒前的请求记录
93
+ while self.request_times and current_time - self.request_times[0] > 1.0:
94
+ self.request_times.popleft()
95
+ return len(self.request_times)
96
+
97
+ def set_rate_limit(self, max_requests_per_second: int):
98
+ """
99
+ 动态设置请求频率限制
100
+
101
+ Args:
102
+ max_requests_per_second: 每秒最大请求数
103
+ """
104
+ with self.rate_limit_lock:
105
+ self.max_requests_per_second = max_requests_per_second
106
+ log(f"请求频率限制已更新为: {max_requests_per_second} 次/秒")
107
+
108
+ def reset_rate_limit_counter(self):
109
+ """
110
+ 重置频率限制计数器
111
+ """
112
+ with self.rate_limit_lock:
113
+ self.request_times.clear()
114
+ log("频率限制计数器已重置")
44
115
 
45
116
  def _make_request(self, method: str, url: str, **kwargs):
117
+ # 在发送请求前进行速率限制检查
118
+ self._wait_for_rate_limit()
119
+
46
120
  self._ensure_valid_token()
47
121
 
48
122
  headers = kwargs.get('headers', {})
@@ -5,7 +5,6 @@ from playwright.sync_api import Page
5
5
  from .fun_base import log, send_exception
6
6
  from .time_utils import get_current_datetime
7
7
 
8
-
9
8
  def fetch(page: Page, url: str, params: Optional[Union[dict, list, str]] = None, headers: Optional[dict] = None, config:
10
9
  Optional[dict] = None) -> dict:
11
10
  """
@@ -23,6 +22,7 @@ Optional[dict] = None) -> dict:
23
22
  raise ValueError("headers 参数必须是 dict 或 None")
24
23
 
25
24
  try:
25
+ page.wait_for_load_state('load')
26
26
  response = page.evaluate("""
27
27
  async ({ url, params, extraHeaders }) => {
28
28
  try {
@@ -64,7 +64,6 @@ Optional[dict] = None) -> dict:
64
64
  raise send_exception()
65
65
  # return {"error": "fetch error", "message": str(e)}
66
66
 
67
-
68
67
  def fetch_via_iframe(page: Page, target_domain: str, url: str, params: Optional[Union[dict, list, str]] = None, config:
69
68
  Optional[dict] = None) -> dict:
70
69
  """
@@ -126,9 +125,8 @@ Optional[dict] = None) -> dict:
126
125
 
127
126
  return response
128
127
  except Exception as e:
129
- raise send_exception()
130
- # return {"error": "iframe_exception", "message": str(e)}
131
-
128
+ raise send_exception()
129
+ # return {"error": "iframe_exception", "message": str(e)}
132
130
 
133
131
  # 找到一个页面里面所有的iframe
134
132
  def find_all_iframe(page: Page):
@@ -137,7 +135,6 @@ def find_all_iframe(page: Page):
137
135
  log("找到 iframe:", frame.url)
138
136
  return [frame.url for frame in frames]
139
137
 
140
-
141
138
  # 全屏幕截图
142
139
  def full_screen_shot(web_page: Page, config):
143
140
  # 设置页面的视口大小为一个较大的值,确保截图高清
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.39
3
+ Version: 1.0.41
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
File without changes
File without changes
File without changes
File without changes