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,149 @@
|
|
|
1
|
+
"""紫鸟浏览器自动化 Python SDK
|
|
2
|
+
|
|
3
|
+
一个用于控制紫鸟浏览器的 Python 库,提供店铺管理、浏览器自动化等功能。
|
|
4
|
+
|
|
5
|
+
基本使用:
|
|
6
|
+
>>> from yuehua_ziniao_webdriver import ZiniaoClient, ZiniaoConfig
|
|
7
|
+
>>>
|
|
8
|
+
>>> config = ZiniaoConfig(
|
|
9
|
+
... client_path=r"D:\ziniao\ziniao.exe",
|
|
10
|
+
... company="企业名",
|
|
11
|
+
... username="用户名",
|
|
12
|
+
... password="密码"
|
|
13
|
+
... )
|
|
14
|
+
>>>
|
|
15
|
+
>>> with ZiniaoClient(config) as client:
|
|
16
|
+
... # 通过名称打开店铺
|
|
17
|
+
... session = client.open_store_by_name("我的店铺")
|
|
18
|
+
... if session.check_ip():
|
|
19
|
+
... tab = session.get_tab()
|
|
20
|
+
... tab.get("https://example.com")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
__author__ = "Yuehua"
|
|
25
|
+
__email__ = "shengxi_2000@outlook.com"
|
|
26
|
+
|
|
27
|
+
# ============================================================================
|
|
28
|
+
# 核心类(推荐使用)
|
|
29
|
+
# ============================================================================
|
|
30
|
+
|
|
31
|
+
from .client import ZiniaoClient
|
|
32
|
+
from .config import ZiniaoConfig
|
|
33
|
+
from .browser import BrowserSession
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# 异常类
|
|
37
|
+
# ============================================================================
|
|
38
|
+
|
|
39
|
+
from .exceptions import (
|
|
40
|
+
ZiniaoError,
|
|
41
|
+
ConfigurationError,
|
|
42
|
+
AuthenticationError,
|
|
43
|
+
ClientNotStartedError,
|
|
44
|
+
BrowserStartError,
|
|
45
|
+
ProcessError,
|
|
46
|
+
CommunicationError,
|
|
47
|
+
TimeoutError,
|
|
48
|
+
StoreError,
|
|
49
|
+
StoreNotFoundError,
|
|
50
|
+
MultipleStoresFoundError,
|
|
51
|
+
StoreOperationError,
|
|
52
|
+
IPCheckError,
|
|
53
|
+
CoreUpdateError,
|
|
54
|
+
UnsupportedVersionError,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# ============================================================================
|
|
58
|
+
# 类型定义
|
|
59
|
+
# ============================================================================
|
|
60
|
+
|
|
61
|
+
from .types import (
|
|
62
|
+
Store,
|
|
63
|
+
StoreInfo,
|
|
64
|
+
BrowserStartResult,
|
|
65
|
+
StoreOpenOptions,
|
|
66
|
+
VersionType,
|
|
67
|
+
PlatformType,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# ============================================================================
|
|
71
|
+
# 工具函数
|
|
72
|
+
# ============================================================================
|
|
73
|
+
|
|
74
|
+
from .utils import (
|
|
75
|
+
get_platform,
|
|
76
|
+
is_windows,
|
|
77
|
+
is_mac,
|
|
78
|
+
is_linux,
|
|
79
|
+
delete_cache,
|
|
80
|
+
get_cache_size,
|
|
81
|
+
format_bytes,
|
|
82
|
+
setup_logging,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# ============================================================================
|
|
86
|
+
# 向后兼容的低层 API(高级用户)
|
|
87
|
+
# ============================================================================
|
|
88
|
+
|
|
89
|
+
from .http_client import HttpClient
|
|
90
|
+
from .process import ProcessManager
|
|
91
|
+
from .store import StoreManager
|
|
92
|
+
from .browser import get_browser
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# 导出列表
|
|
96
|
+
# ============================================================================
|
|
97
|
+
|
|
98
|
+
__all__ = [
|
|
99
|
+
# 版本信息
|
|
100
|
+
"__version__",
|
|
101
|
+
"__author__",
|
|
102
|
+
"__email__",
|
|
103
|
+
|
|
104
|
+
# 核心类
|
|
105
|
+
"ZiniaoClient",
|
|
106
|
+
"ZiniaoConfig",
|
|
107
|
+
"BrowserSession",
|
|
108
|
+
|
|
109
|
+
# 异常类
|
|
110
|
+
"ZiniaoError",
|
|
111
|
+
"ConfigurationError",
|
|
112
|
+
"AuthenticationError",
|
|
113
|
+
"ClientNotStartedError",
|
|
114
|
+
"BrowserStartError",
|
|
115
|
+
"ProcessError",
|
|
116
|
+
"CommunicationError",
|
|
117
|
+
"TimeoutError",
|
|
118
|
+
"StoreError",
|
|
119
|
+
"StoreNotFoundError",
|
|
120
|
+
"MultipleStoresFoundError",
|
|
121
|
+
"StoreOperationError",
|
|
122
|
+
"IPCheckError",
|
|
123
|
+
"CoreUpdateError",
|
|
124
|
+
"UnsupportedVersionError",
|
|
125
|
+
|
|
126
|
+
# 类型定义
|
|
127
|
+
"Store",
|
|
128
|
+
"StoreInfo",
|
|
129
|
+
"BrowserStartResult",
|
|
130
|
+
"StoreOpenOptions",
|
|
131
|
+
"VersionType",
|
|
132
|
+
"PlatformType",
|
|
133
|
+
|
|
134
|
+
# 工具函数
|
|
135
|
+
"get_platform",
|
|
136
|
+
"is_windows",
|
|
137
|
+
"is_mac",
|
|
138
|
+
"is_linux",
|
|
139
|
+
"delete_cache",
|
|
140
|
+
"get_cache_size",
|
|
141
|
+
"format_bytes",
|
|
142
|
+
"setup_logging",
|
|
143
|
+
|
|
144
|
+
# 低层 API
|
|
145
|
+
"HttpClient",
|
|
146
|
+
"ProcessManager",
|
|
147
|
+
"StoreManager",
|
|
148
|
+
"get_browser",
|
|
149
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""浏览器会话管理模块
|
|
2
|
+
|
|
3
|
+
提供浏览器会话的管理和操作功能。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional, Callable, Any
|
|
9
|
+
|
|
10
|
+
from DrissionPage import Chromium
|
|
11
|
+
from DrissionPage.common import By
|
|
12
|
+
|
|
13
|
+
from .exceptions import IPCheckError, ZiniaoError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BrowserSession:
|
|
19
|
+
"""浏览器会话类
|
|
20
|
+
|
|
21
|
+
封装 DrissionPage 的 Chromium 对象,提供便捷的操作接口。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
port: int,
|
|
27
|
+
store_id: str,
|
|
28
|
+
store_name: str,
|
|
29
|
+
ip_check_url: Optional[str] = None,
|
|
30
|
+
launcher_page: Optional[str] = None,
|
|
31
|
+
close_callback: Optional[Callable[[str], None]] = None
|
|
32
|
+
) -> None:
|
|
33
|
+
"""初始化浏览器会话
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
port: 浏览器调试端口
|
|
37
|
+
store_id: 店铺 ID/OAuth
|
|
38
|
+
store_name: 店铺名称
|
|
39
|
+
ip_check_url: IP 检测页面 URL(可选)
|
|
40
|
+
launcher_page: 启动页面 URL(可选)
|
|
41
|
+
close_callback: 关闭回调函数(可选)
|
|
42
|
+
"""
|
|
43
|
+
self.port = port
|
|
44
|
+
self.store_id = store_id
|
|
45
|
+
self.store_name = store_name
|
|
46
|
+
self.ip_check_url = ip_check_url
|
|
47
|
+
self.launcher_page = launcher_page
|
|
48
|
+
self.close_callback = close_callback
|
|
49
|
+
self._browser: Optional[Chromium] = None
|
|
50
|
+
self._closed = False
|
|
51
|
+
|
|
52
|
+
logger.debug(
|
|
53
|
+
f"初始化浏览器会话:store={store_name}, "
|
|
54
|
+
f"port={port}, store_id={store_id}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# 创建浏览器实例
|
|
58
|
+
try:
|
|
59
|
+
self._browser = Chromium(port)
|
|
60
|
+
logger.info(f"成功连接到浏览器:{store_name}")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
error_msg = f"连接到浏览器失败:{e}"
|
|
63
|
+
logger.error(error_msg)
|
|
64
|
+
raise ZiniaoError(error_msg, {"port": port, "error": str(e)})
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def browser(self) -> Chromium:
|
|
68
|
+
"""获取底层的 Chromium 浏览器对象
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Chromium: DrissionPage 浏览器对象
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ZiniaoError: 如果会话已关闭
|
|
75
|
+
"""
|
|
76
|
+
if self._closed:
|
|
77
|
+
raise ZiniaoError("浏览器会话已关闭")
|
|
78
|
+
|
|
79
|
+
if self._browser is None:
|
|
80
|
+
raise ZiniaoError("浏览器对象未初始化")
|
|
81
|
+
|
|
82
|
+
return self._browser
|
|
83
|
+
|
|
84
|
+
def get_tab(self, index: int = -1):
|
|
85
|
+
"""获取标签页
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
index: 标签页索引,-1 表示最新的标签页(默认)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
标签页对象
|
|
92
|
+
"""
|
|
93
|
+
if index == -1:
|
|
94
|
+
return self.browser.latest_tab
|
|
95
|
+
else:
|
|
96
|
+
tabs = self.browser.tabs
|
|
97
|
+
if 0 <= index < len(tabs):
|
|
98
|
+
return tabs[index]
|
|
99
|
+
else:
|
|
100
|
+
raise IndexError(f"标签页索引超出范围:{index}")
|
|
101
|
+
|
|
102
|
+
def check_ip(
|
|
103
|
+
self,
|
|
104
|
+
ip_check_url: Optional[str] = None,
|
|
105
|
+
timeout: int = 60
|
|
106
|
+
) -> bool:
|
|
107
|
+
"""检测 IP 是否可用
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
ip_check_url: IP 检测页面 URL,如果为 None 则使用初始化时的 URL
|
|
111
|
+
timeout: 超时时间(秒),默认 60
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
bool: IP 可用返回 True,否则返回 False
|
|
115
|
+
"""
|
|
116
|
+
# 确定使用的 URL
|
|
117
|
+
url = ip_check_url or self.ip_check_url
|
|
118
|
+
|
|
119
|
+
if not url:
|
|
120
|
+
logger.warning("IP 检测页面 URL 为空,跳过检测")
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
logger.info(f"开始 IP 检测:{self.store_name}")
|
|
125
|
+
|
|
126
|
+
tab = self.get_tab()
|
|
127
|
+
tab.get(url)
|
|
128
|
+
|
|
129
|
+
# 等待成功按钮出现
|
|
130
|
+
success_button = tab.ele(
|
|
131
|
+
(By.XPATH, '//button[contains(@class, "styles_btn--success")]'),
|
|
132
|
+
timeout=timeout
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if success_button:
|
|
136
|
+
logger.info(f"IP 检测成功:{self.store_name}")
|
|
137
|
+
return True
|
|
138
|
+
else:
|
|
139
|
+
logger.warning(f"IP 检测超时:{self.store_name}")
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"IP 检测异常:{self.store_name}, 错误:{e}")
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
def open_launcher_page(
|
|
147
|
+
self,
|
|
148
|
+
launcher_page: Optional[str] = None,
|
|
149
|
+
wait_time: int = 6
|
|
150
|
+
) -> None:
|
|
151
|
+
"""打开启动页面(店铺平台主页)
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
launcher_page: 启动页面 URL,如果为 None 则使用初始化时的 URL
|
|
155
|
+
wait_time: 打开后等待时间(秒),默认 6
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
ZiniaoError: 如果启动页面 URL 为空
|
|
159
|
+
"""
|
|
160
|
+
# 确定使用的 URL
|
|
161
|
+
url = launcher_page or self.launcher_page
|
|
162
|
+
|
|
163
|
+
if not url:
|
|
164
|
+
raise ZiniaoError(
|
|
165
|
+
"启动页面 URL 为空",
|
|
166
|
+
{"store_name": self.store_name}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
logger.info(f"打开启动页面:{self.store_name} -> {url}")
|
|
171
|
+
|
|
172
|
+
tab = self.get_tab()
|
|
173
|
+
tab.get(url)
|
|
174
|
+
|
|
175
|
+
time.sleep(wait_time)
|
|
176
|
+
|
|
177
|
+
logger.debug(f"启动页面已打开:{self.store_name}")
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
error_msg = f"打开启动页面失败:{e}"
|
|
181
|
+
logger.error(error_msg)
|
|
182
|
+
raise ZiniaoError(
|
|
183
|
+
error_msg,
|
|
184
|
+
{"store_name": self.store_name, "url": url, "error": str(e)}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def navigate(self, url: str, wait_time: float = 0) -> None:
|
|
188
|
+
"""导航到指定 URL
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
url: 目标 URL
|
|
192
|
+
wait_time: 导航后等待时间(秒),默认 0
|
|
193
|
+
"""
|
|
194
|
+
logger.debug(f"导航到:{url}")
|
|
195
|
+
tab = self.get_tab()
|
|
196
|
+
tab.get(url)
|
|
197
|
+
|
|
198
|
+
if wait_time > 0:
|
|
199
|
+
time.sleep(wait_time)
|
|
200
|
+
|
|
201
|
+
def close(self) -> None:
|
|
202
|
+
"""关闭浏览器会话
|
|
203
|
+
|
|
204
|
+
会调用初始化时传入的 close_callback 来关闭店铺。
|
|
205
|
+
"""
|
|
206
|
+
if self._closed:
|
|
207
|
+
logger.debug(f"浏览器会话已关闭:{self.store_name}")
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
logger.info(f"关闭浏览器会话:{self.store_name}")
|
|
211
|
+
|
|
212
|
+
# 调用关闭回调
|
|
213
|
+
if self.close_callback:
|
|
214
|
+
try:
|
|
215
|
+
self.close_callback(self.store_id)
|
|
216
|
+
logger.debug(f"调用关闭回调成功:{self.store_name}")
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"调用关闭回调失败:{self.store_name}, 错误:{e}")
|
|
219
|
+
|
|
220
|
+
self._closed = True
|
|
221
|
+
self._browser = None
|
|
222
|
+
|
|
223
|
+
def is_closed(self) -> bool:
|
|
224
|
+
"""检查会话是否已关闭
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
bool: 已关闭返回 True
|
|
228
|
+
"""
|
|
229
|
+
return self._closed
|
|
230
|
+
|
|
231
|
+
def __enter__(self) -> "BrowserSession":
|
|
232
|
+
"""上下文管理器入口"""
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
236
|
+
"""上下文管理器退出,自动关闭会话"""
|
|
237
|
+
self.close()
|
|
238
|
+
|
|
239
|
+
def __repr__(self) -> str:
|
|
240
|
+
return (
|
|
241
|
+
f"BrowserSession(store='{self.store_name}', "
|
|
242
|
+
f"port={self.port}, closed={self._closed})"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_browser(port: int) -> Chromium:
|
|
247
|
+
"""获取 DrissionPage 浏览器对象(原始方式)
|
|
248
|
+
|
|
249
|
+
这是一个便捷函数,用于向后兼容。
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
port: 浏览器调试端口
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Chromium: DrissionPage 浏览器对象
|
|
256
|
+
"""
|
|
257
|
+
logger.debug(f"获取浏览器对象:port={port}")
|
|
258
|
+
return Chromium(port)
|