yuehua-ziniao-webdriver 0.1.0__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.
- yuehua_ziniao_webdriver/__init__.py +149 -0
- yuehua_ziniao_webdriver/browser.py +258 -0
- yuehua_ziniao_webdriver/client.py +435 -0
- yuehua_ziniao_webdriver/config.py +265 -0
- yuehua_ziniao_webdriver/exceptions.py +237 -0
- yuehua_ziniao_webdriver/http_client.py +214 -0
- yuehua_ziniao_webdriver/process.py +270 -0
- yuehua_ziniao_webdriver/store.py +429 -0
- yuehua_ziniao_webdriver/types.py +172 -0
- yuehua_ziniao_webdriver/utils.py +310 -0
- yuehua_ziniao_webdriver-0.1.0.dist-info/METADATA +438 -0
- yuehua_ziniao_webdriver-0.1.0.dist-info/RECORD +15 -0
- yuehua_ziniao_webdriver-0.1.0.dist-info/WHEEL +5 -0
- yuehua_ziniao_webdriver-0.1.0.dist-info/licenses/LICENSE +21 -0
- yuehua_ziniao_webdriver-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""异常定义模块
|
|
2
|
+
|
|
3
|
+
定义所有自定义异常类,提供清晰的错误层次结构。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ZiniaoError(Exception):
|
|
10
|
+
"""紫鸟浏览器 SDK 基础异常类
|
|
11
|
+
|
|
12
|
+
所有自定义异常的基类。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: str, details: Optional[Any] = None) -> None:
|
|
16
|
+
"""初始化异常
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
message: 错误消息
|
|
20
|
+
details: 额外的错误详情(可选)
|
|
21
|
+
"""
|
|
22
|
+
self.message = message
|
|
23
|
+
self.details = details
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
if self.details:
|
|
28
|
+
return f"{self.message} (详情: {self.details})"
|
|
29
|
+
return self.message
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConfigurationError(ZiniaoError):
|
|
33
|
+
"""配置错误
|
|
34
|
+
|
|
35
|
+
当配置参数无效或缺失时抛出。
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AuthenticationError(ZiniaoError):
|
|
41
|
+
"""认证失败错误
|
|
42
|
+
|
|
43
|
+
当登录紫鸟客户端失败时抛出。
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ClientNotStartedError(ZiniaoError):
|
|
49
|
+
"""客户端未启动错误
|
|
50
|
+
|
|
51
|
+
当尝试操作未启动的客户端时抛出。
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, message: str = "紫鸟客户端尚未启动,请先调用 start() 方法") -> None:
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BrowserStartError(ZiniaoError):
|
|
59
|
+
"""浏览器启动失败错误
|
|
60
|
+
|
|
61
|
+
当启动紫鸟客户端进程失败时抛出。
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ProcessError(ZiniaoError):
|
|
67
|
+
"""进程操作错误
|
|
68
|
+
|
|
69
|
+
当进程管理操作失败时抛出(如关闭进程失败)。
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CommunicationError(ZiniaoError):
|
|
75
|
+
"""通信错误
|
|
76
|
+
|
|
77
|
+
当与紫鸟客户端 HTTP 通信失败时抛出。
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TimeoutError(ZiniaoError):
|
|
83
|
+
"""超时错误
|
|
84
|
+
|
|
85
|
+
当操作超时时抛出。
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class StoreError(ZiniaoError):
|
|
91
|
+
"""店铺操作基础错误
|
|
92
|
+
|
|
93
|
+
所有店铺相关错误的基类。
|
|
94
|
+
"""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class StoreNotFoundError(StoreError):
|
|
99
|
+
"""店铺未找到错误
|
|
100
|
+
|
|
101
|
+
当按名称或 ID 查找店铺但未找到时抛出。
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
store_identifier: str,
|
|
107
|
+
search_type: str = "名称"
|
|
108
|
+
) -> None:
|
|
109
|
+
"""初始化错误
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
store_identifier: 店铺标识(名称或 ID)
|
|
113
|
+
search_type: 搜索类型("名称" 或 "ID")
|
|
114
|
+
"""
|
|
115
|
+
message = f"未找到匹配的店铺:{search_type}='{store_identifier}'"
|
|
116
|
+
super().__init__(message, {"identifier": store_identifier, "type": search_type})
|
|
117
|
+
self.store_identifier = store_identifier
|
|
118
|
+
self.search_type = search_type
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MultipleStoresFoundError(StoreError):
|
|
122
|
+
"""找到多个店铺错误
|
|
123
|
+
|
|
124
|
+
当按名称搜索店铺但找到多个匹配结果时抛出。
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
store_name: str,
|
|
130
|
+
count: int,
|
|
131
|
+
store_names: Optional[list] = None
|
|
132
|
+
) -> None:
|
|
133
|
+
"""初始化错误
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
store_name: 搜索的店铺名称
|
|
137
|
+
count: 找到的店铺数量
|
|
138
|
+
store_names: 找到的店铺名称列表(可选)
|
|
139
|
+
"""
|
|
140
|
+
if store_names:
|
|
141
|
+
message = (
|
|
142
|
+
f"找到 {count} 个匹配的店铺:'{store_name}'。"
|
|
143
|
+
f"匹配的店铺:{', '.join(store_names)}"
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
message = f"找到 {count} 个匹配的店铺:'{store_name}'"
|
|
147
|
+
|
|
148
|
+
super().__init__(
|
|
149
|
+
message,
|
|
150
|
+
{"search_name": store_name, "count": count, "stores": store_names}
|
|
151
|
+
)
|
|
152
|
+
self.store_name = store_name
|
|
153
|
+
self.count = count
|
|
154
|
+
self.store_names = store_names
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class StoreOperationError(StoreError):
|
|
158
|
+
"""店铺操作失败错误
|
|
159
|
+
|
|
160
|
+
当打开、关闭店铺等操作失败时抛出。
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
operation: str,
|
|
166
|
+
store_id: str,
|
|
167
|
+
status_code: Optional[int] = None,
|
|
168
|
+
message: Optional[str] = None
|
|
169
|
+
) -> None:
|
|
170
|
+
"""初始化错误
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
operation: 操作类型(如 "打开"、"关闭")
|
|
174
|
+
store_id: 店铺 ID
|
|
175
|
+
status_code: 状态码(可选)
|
|
176
|
+
message: 错误消息(可选)
|
|
177
|
+
"""
|
|
178
|
+
error_msg = f"{operation}店铺失败:{store_id}"
|
|
179
|
+
if status_code is not None:
|
|
180
|
+
error_msg += f",状态码:{status_code}"
|
|
181
|
+
if message:
|
|
182
|
+
error_msg += f",消息:{message}"
|
|
183
|
+
|
|
184
|
+
super().__init__(
|
|
185
|
+
error_msg,
|
|
186
|
+
{
|
|
187
|
+
"operation": operation,
|
|
188
|
+
"store_id": store_id,
|
|
189
|
+
"status_code": status_code,
|
|
190
|
+
"message": message
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
self.operation = operation
|
|
194
|
+
self.store_id = store_id
|
|
195
|
+
self.status_code = status_code
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class IPCheckError(ZiniaoError):
|
|
199
|
+
"""IP 检测错误
|
|
200
|
+
|
|
201
|
+
当 IP 检测失败或超时时抛出。
|
|
202
|
+
"""
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class CoreUpdateError(ZiniaoError):
|
|
207
|
+
"""内核更新错误
|
|
208
|
+
|
|
209
|
+
当更新浏览器内核失败时抛出。
|
|
210
|
+
"""
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class UnsupportedVersionError(ZiniaoError):
|
|
215
|
+
"""不支持的版本错误
|
|
216
|
+
|
|
217
|
+
当客户端版本不支持某个功能时抛出。
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(
|
|
221
|
+
self,
|
|
222
|
+
feature: str,
|
|
223
|
+
required_version: Optional[str] = None
|
|
224
|
+
) -> None:
|
|
225
|
+
"""初始化错误
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
feature: 不支持的功能名称
|
|
229
|
+
required_version: 需要的版本(可选)
|
|
230
|
+
"""
|
|
231
|
+
message = f"当前客户端版本不支持功能:{feature}"
|
|
232
|
+
if required_version:
|
|
233
|
+
message += f",请升级到 {required_version} 或更高版本"
|
|
234
|
+
|
|
235
|
+
super().__init__(message, {"feature": feature, "required_version": required_version})
|
|
236
|
+
self.feature = feature
|
|
237
|
+
self.required_version = required_version
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""HTTP 通信客户端模块
|
|
2
|
+
|
|
3
|
+
封装与紫鸟客户端的 HTTP 通信,提供重试机制和错误处理。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
from .types import HttpRequestData, HttpResponse
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
CommunicationError,
|
|
16
|
+
TimeoutError as ZiniaoTimeoutError,
|
|
17
|
+
AuthenticationError
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HttpClient:
|
|
24
|
+
"""HTTP 通信客户端
|
|
25
|
+
|
|
26
|
+
负责与紫鸟客户端进行 HTTP 通信。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
port: int,
|
|
32
|
+
timeout: int = 120,
|
|
33
|
+
max_retries: int = 3,
|
|
34
|
+
retry_delay: float = 2.0
|
|
35
|
+
) -> None:
|
|
36
|
+
"""初始化 HTTP 客户端
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
port: 通信端口
|
|
40
|
+
timeout: 请求超时时间(秒)
|
|
41
|
+
max_retries: 最大重试次数
|
|
42
|
+
retry_delay: 重试延迟时间(秒)
|
|
43
|
+
"""
|
|
44
|
+
self.port = port
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self.max_retries = max_retries
|
|
47
|
+
self.retry_delay = retry_delay
|
|
48
|
+
self.base_url = f"http://127.0.0.1:{port}"
|
|
49
|
+
|
|
50
|
+
logger.debug(
|
|
51
|
+
f"初始化 HTTP 客户端:port={port}, timeout={timeout}, "
|
|
52
|
+
f"max_retries={max_retries}, retry_delay={retry_delay}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def send_request(
|
|
56
|
+
self,
|
|
57
|
+
data: Dict[str, Any],
|
|
58
|
+
retry_on_none: bool = False
|
|
59
|
+
) -> Optional[HttpResponse]:
|
|
60
|
+
"""发送 HTTP 请求
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data: 请求数据字典
|
|
64
|
+
retry_on_none: 当返回 None 时是否重试,默认 False
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Optional[HttpResponse]: 响应数据,失败返回 None
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
CommunicationError: 通信失败
|
|
71
|
+
ZiniaoTimeoutError: 请求超时
|
|
72
|
+
AuthenticationError: 认证失败
|
|
73
|
+
"""
|
|
74
|
+
action = data.get("action", "unknown")
|
|
75
|
+
request_id = data.get("requestId", "unknown")
|
|
76
|
+
|
|
77
|
+
logger.debug(f"发送请求:action={action}, requestId={request_id}")
|
|
78
|
+
|
|
79
|
+
# 重试逻辑
|
|
80
|
+
last_exception: Optional[Exception] = None
|
|
81
|
+
|
|
82
|
+
for attempt in range(self.max_retries + 1):
|
|
83
|
+
try:
|
|
84
|
+
# 发送 POST 请求
|
|
85
|
+
response = requests.post(
|
|
86
|
+
self.base_url,
|
|
87
|
+
data=json.dumps(data).encode('utf-8'),
|
|
88
|
+
timeout=self.timeout
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 解析响应
|
|
92
|
+
result = json.loads(response.text)
|
|
93
|
+
|
|
94
|
+
# 检查是否需要重试(返回 None)
|
|
95
|
+
if result is None and retry_on_none and attempt < self.max_retries:
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"请求返回 None,{self.retry_delay} 秒后重试 "
|
|
98
|
+
f"(尝试 {attempt + 1}/{self.max_retries})"
|
|
99
|
+
)
|
|
100
|
+
time.sleep(self.retry_delay)
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# 检查状态码
|
|
104
|
+
status_code = result.get("statusCode") if result else None
|
|
105
|
+
|
|
106
|
+
# 认证失败
|
|
107
|
+
if status_code == -10003:
|
|
108
|
+
error_msg = f"认证失败:{json.dumps(result, ensure_ascii=False)}"
|
|
109
|
+
logger.error(error_msg)
|
|
110
|
+
raise AuthenticationError(error_msg, result)
|
|
111
|
+
|
|
112
|
+
logger.debug(
|
|
113
|
+
f"请求成功:action={action}, statusCode={status_code}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
except requests.Timeout as e:
|
|
119
|
+
last_exception = e
|
|
120
|
+
logger.warning(
|
|
121
|
+
f"请求超时:action={action}, attempt={attempt + 1}/{self.max_retries + 1}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if attempt < self.max_retries:
|
|
125
|
+
logger.info(f"等待 {self.retry_delay} 秒后重试...")
|
|
126
|
+
time.sleep(self.retry_delay)
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# 最后一次尝试失败
|
|
130
|
+
error_msg = f"请求超时(已重试 {self.max_retries} 次):{e}"
|
|
131
|
+
logger.error(error_msg)
|
|
132
|
+
raise ZiniaoTimeoutError(error_msg, {"action": action, "error": str(e)})
|
|
133
|
+
|
|
134
|
+
except requests.ConnectionError as e:
|
|
135
|
+
last_exception = e
|
|
136
|
+
logger.warning(
|
|
137
|
+
f"连接失败:action={action}, attempt={attempt + 1}/{self.max_retries + 1}, "
|
|
138
|
+
f"error={e}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if attempt < self.max_retries:
|
|
142
|
+
logger.info(f"等待 {self.retry_delay} 秒后重试...")
|
|
143
|
+
time.sleep(self.retry_delay)
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# 最后一次尝试失败
|
|
147
|
+
error_msg = (
|
|
148
|
+
f"无法连接到紫鸟客户端(已重试 {self.max_retries} 次),"
|
|
149
|
+
f"请确认客户端已启动且端口 {self.port} 可访问"
|
|
150
|
+
)
|
|
151
|
+
logger.error(error_msg)
|
|
152
|
+
raise CommunicationError(error_msg, {"port": self.port, "error": str(e)})
|
|
153
|
+
|
|
154
|
+
except json.JSONDecodeError as e:
|
|
155
|
+
last_exception = e
|
|
156
|
+
error_msg = f"响应 JSON 解析失败:{e}"
|
|
157
|
+
logger.error(error_msg)
|
|
158
|
+
raise CommunicationError(error_msg, {"error": str(e)})
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
last_exception = e
|
|
162
|
+
logger.error(f"未知错误:action={action}, error={e}")
|
|
163
|
+
|
|
164
|
+
if attempt < self.max_retries:
|
|
165
|
+
logger.info(f"等待 {self.retry_delay} 秒后重试...")
|
|
166
|
+
time.sleep(self.retry_delay)
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
# 最后一次尝试失败
|
|
170
|
+
error_msg = f"通信失败(已重试 {self.max_retries} 次):{e}"
|
|
171
|
+
logger.error(error_msg)
|
|
172
|
+
raise CommunicationError(error_msg, {"action": action, "error": str(e)})
|
|
173
|
+
|
|
174
|
+
# 不应该到达这里,但为了类型检查
|
|
175
|
+
if last_exception:
|
|
176
|
+
raise CommunicationError(
|
|
177
|
+
f"请求失败:{last_exception}",
|
|
178
|
+
{"error": str(last_exception)}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def update_port(self, new_port: int) -> None:
|
|
184
|
+
"""更新通信端口
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
new_port: 新的端口号
|
|
188
|
+
"""
|
|
189
|
+
self.port = new_port
|
|
190
|
+
self.base_url = f"http://127.0.0.1:{new_port}"
|
|
191
|
+
logger.info(f"更新通信端口:{new_port}")
|
|
192
|
+
|
|
193
|
+
def test_connection(self) -> bool:
|
|
194
|
+
"""测试连接是否可用
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
bool: 连接正常返回 True,否则返回 False
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
response = requests.get(
|
|
201
|
+
self.base_url,
|
|
202
|
+
timeout=5
|
|
203
|
+
)
|
|
204
|
+
logger.debug(f"连接测试成功:port={self.port}")
|
|
205
|
+
return True
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.debug(f"连接测试失败:port={self.port}, error={e}")
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def __repr__(self) -> str:
|
|
211
|
+
return (
|
|
212
|
+
f"HttpClient(port={self.port}, timeout={self.timeout}, "
|
|
213
|
+
f"max_retries={self.max_retries})"
|
|
214
|
+
)
|