mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
4.0.0b16(+)
timeouts的implicit改成base; debugger_address改成address ActionChains改成Actions; 一些文件和内部类改名; wait.data_packets()即将废弃; iframe切换了id也可继续监听; 修复监听器有时不能获取postData的问题; 修复监听器不能获取同域iframe数据包的问题; 修复等待数据包timeout无效问题
This commit is contained in:
parent
018c944405
commit
364700df2c
@ -13,4 +13,4 @@ from ._configs.chromium_options import ChromiumOptions
|
|||||||
from ._configs.session_options import SessionOptions
|
from ._configs.session_options import SessionOptions
|
||||||
|
|
||||||
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
|
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
|
||||||
__version__ = '4.0.0b15'
|
__version__ = '4.0.0b16'
|
||||||
|
@ -7,7 +7,7 @@ from time import sleep, perf_counter
|
|||||||
|
|
||||||
from .chromium_driver import BrowserDriver, ChromiumDriver
|
from .chromium_driver import BrowserDriver, ChromiumDriver
|
||||||
from .._commons.tools import stop_process_on_port, raise_error
|
from .._commons.tools import stop_process_on_port, raise_error
|
||||||
from .._units.download_manager import DownloadManager
|
from .._units.downloader import DownloadManager
|
||||||
|
|
||||||
__ERROR__ = 'error'
|
__ERROR__ = 'error'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from typing import List, Optional, Union
|
|||||||
|
|
||||||
from .chromium_driver import BrowserDriver, ChromiumDriver
|
from .chromium_driver import BrowserDriver, ChromiumDriver
|
||||||
from .._pages.chromium_page import ChromiumPage
|
from .._pages.chromium_page import ChromiumPage
|
||||||
from .._units.download_manager import DownloadManager
|
from .._units.downloader import DownloadManager
|
||||||
|
|
||||||
|
|
||||||
class Browser(object):
|
class Browser(object):
|
||||||
|
@ -9,7 +9,7 @@ from threading import Thread, Event
|
|||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
|
|
||||||
from requests import get
|
from requests import get
|
||||||
from websocket import (WebSocketTimeoutException, WebSocketException, WebSocketConnectionClosedException, \
|
from websocket import (WebSocketTimeoutException, WebSocketException, WebSocketConnectionClosedException,
|
||||||
create_connection)
|
create_connection)
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ class ChromiumDriver(object):
|
|||||||
self.method_results[ws_id] = Queue()
|
self.method_results[ws_id] = Queue()
|
||||||
try:
|
try:
|
||||||
self._ws.send(message_json)
|
self._ws.send(message_json)
|
||||||
except OSError:
|
except (OSError, WebSocketConnectionClosedException):
|
||||||
self.method_results.pop(ws_id, None)
|
self.method_results.pop(ws_id, None)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -190,14 +190,14 @@ class ChromiumDriver(object):
|
|||||||
event = self.event_queue.get_nowait()
|
event = self.event_queue.get_nowait()
|
||||||
function = self.event_handlers.get(event['method'])
|
function = self.event_handlers.get(event['method'])
|
||||||
if function:
|
if function:
|
||||||
if self._debug:
|
# if self._debug:
|
||||||
print(f'开始执行 {function.__name__}')
|
# print(f'开始执行 {function.__name__}')
|
||||||
try:
|
try:
|
||||||
function(**event['params'])
|
function(**event['params'])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if self._debug:
|
# if self._debug:
|
||||||
print(f'执行 {function.__name__}完毕')
|
# print(f'执行 {function.__name__}完毕')
|
||||||
|
|
||||||
self.event_handlers.clear()
|
self.event_handlers.clear()
|
||||||
self.method_results.clear()
|
self.method_results.clear()
|
||||||
|
@ -24,10 +24,10 @@ def connect_browser(option):
|
|||||||
:param option: ChromiumOptions对象
|
:param option: ChromiumOptions对象
|
||||||
:return: 返回是否接管的浏览器
|
:return: 返回是否接管的浏览器
|
||||||
"""
|
"""
|
||||||
debugger_address = option.debugger_address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
|
address = option.address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
|
||||||
chrome_path = option.browser_path
|
chrome_path = option.browser_path
|
||||||
|
|
||||||
ip, port = debugger_address.split(':')
|
ip, port = address.split(':')
|
||||||
if ip != '127.0.0.1' or port_is_using(ip, port) or option.is_existing_only:
|
if ip != '127.0.0.1' or port_is_using(ip, port) or option.is_existing_only:
|
||||||
test_connect(ip, port)
|
test_connect(ip, port)
|
||||||
option._headless = False
|
option._headless = False
|
||||||
@ -86,7 +86,7 @@ def get_launch_args(opt):
|
|||||||
result.add(i)
|
result.add(i)
|
||||||
|
|
||||||
if not has_user_path and not opt.system_user_path:
|
if not has_user_path and not opt.system_user_path:
|
||||||
port = opt.debugger_address.split(':')[-1] if opt.debugger_address else '0'
|
port = opt.address.split(':')[-1] if opt.address else '0'
|
||||||
path = Path(gettempdir()) / 'DrissionPage' / f'userData_{port}'
|
path = Path(gettempdir()) / 'DrissionPage' / f'userData_{port}'
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
opt.set_user_data_path(path)
|
opt.set_user_data_path(path)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
@Contact : g1879@qq.com
|
@Contact : g1879@qq.com
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from re import search
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from tempfile import gettempdir, TemporaryDirectory
|
from tempfile import gettempdir, TemporaryDirectory
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
@ -36,12 +37,13 @@ class ChromiumOptions(object):
|
|||||||
self._extensions = options.get('extensions', [])
|
self._extensions = options.get('extensions', [])
|
||||||
self._prefs = options.get('prefs', {})
|
self._prefs = options.get('prefs', {})
|
||||||
self._flags = options.get('flags', {})
|
self._flags = options.get('flags', {})
|
||||||
self._debugger_address = options.get('debugger_address', None)
|
self._address = options.get('address', None)
|
||||||
self._load_mode = options.get('load_mode', 'normal')
|
self._load_mode = options.get('load_mode', 'normal')
|
||||||
self._proxy = om.proxies.get('http', None)
|
|
||||||
self._system_user_path = options.get('system_user_path', False)
|
self._system_user_path = options.get('system_user_path', False)
|
||||||
self._existing_only = options.get('existing_only', False)
|
self._existing_only = options.get('existing_only', False)
|
||||||
|
|
||||||
|
self._proxy = om.proxies.get('http', None) or om.proxies.get('https', None)
|
||||||
|
|
||||||
user_path = user = False
|
user_path = user = False
|
||||||
for arg in self._arguments:
|
for arg in self._arguments:
|
||||||
if arg.startswith('--user-data-dir='):
|
if arg.startswith('--user-data-dir='):
|
||||||
@ -54,14 +56,14 @@ class ChromiumOptions(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
timeouts = om.timeouts
|
timeouts = om.timeouts
|
||||||
self._timeouts = {'implicit': timeouts['implicit'],
|
self._timeouts = {'base': timeouts['base'],
|
||||||
'pageLoad': timeouts['page_load'],
|
'pageLoad': timeouts['page_load'],
|
||||||
'script': timeouts['script']}
|
'script': timeouts['script']}
|
||||||
|
|
||||||
self._auto_port = options.get('auto_port', False)
|
self._auto_port = options.get('auto_port', False)
|
||||||
if self._auto_port:
|
if self._auto_port:
|
||||||
port, path = PortFinder().get_port()
|
port, path = PortFinder().get_port()
|
||||||
self._debugger_address = f'127.0.0.1:{port}'
|
self._address = f'127.0.0.1:{port}'
|
||||||
self.set_argument('--user-data-dir', path)
|
self.set_argument('--user-data-dir', path)
|
||||||
|
|
||||||
others = om.others
|
others = om.others
|
||||||
@ -77,8 +79,8 @@ class ChromiumOptions(object):
|
|||||||
self._extensions = []
|
self._extensions = []
|
||||||
self._prefs = {}
|
self._prefs = {}
|
||||||
self._flags = {}
|
self._flags = {}
|
||||||
self._timeouts = {'implicit': 10, 'pageLoad': 30, 'script': 30}
|
self._timeouts = {'base': 10, 'pageLoad': 30, 'script': 30}
|
||||||
self._debugger_address = '127.0.0.1:9222'
|
self._address = '127.0.0.1:9222'
|
||||||
self._load_mode = 'normal'
|
self._load_mode = 'normal'
|
||||||
self._proxy = None
|
self._proxy = None
|
||||||
self._auto_port = False
|
self._auto_port = False
|
||||||
@ -125,12 +127,17 @@ class ChromiumOptions(object):
|
|||||||
@property
|
@property
|
||||||
def debugger_address(self):
|
def debugger_address(self):
|
||||||
"""返回浏览器地址,ip:port"""
|
"""返回浏览器地址,ip:port"""
|
||||||
return self._debugger_address
|
return self._address
|
||||||
|
|
||||||
@debugger_address.setter
|
@debugger_address.setter
|
||||||
def debugger_address(self, address):
|
def debugger_address(self, address):
|
||||||
"""设置浏览器地址,格式ip:port"""
|
"""设置浏览器地址,格式ip:port"""
|
||||||
self.set_debugger_address(address)
|
self.set_address(address)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address(self):
|
||||||
|
"""返回浏览器地址,ip:port"""
|
||||||
|
return self._address
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arguments(self):
|
def arguments(self):
|
||||||
@ -275,15 +282,16 @@ class ChromiumOptions(object):
|
|||||||
self.clear_file_flags = True
|
self.clear_file_flags = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_timeouts(self, implicit=None, pageLoad=None, script=None):
|
def set_timeouts(self, base=None, pageLoad=None, script=None, implicit=None):
|
||||||
"""设置超时时间,单位为秒
|
"""设置超时时间,单位为秒
|
||||||
:param implicit: 默认超时时间
|
:param base: 默认超时时间
|
||||||
:param pageLoad: 页面加载超时时间
|
:param pageLoad: 页面加载超时时间
|
||||||
:param script: 脚本运行超时时间
|
:param script: 脚本运行超时时间
|
||||||
:return: 当前对象
|
:return: 当前对象
|
||||||
"""
|
"""
|
||||||
if implicit is not None:
|
base = base if base is not None else implicit
|
||||||
self._timeouts['implicit'] = implicit
|
if base is not None:
|
||||||
|
self._timeouts['base'] = base
|
||||||
if pageLoad is not None:
|
if pageLoad is not None:
|
||||||
self._timeouts['pageLoad'] = pageLoad
|
self._timeouts['pageLoad'] = pageLoad
|
||||||
if script is not None:
|
if script is not None:
|
||||||
@ -352,6 +360,10 @@ class ChromiumOptions(object):
|
|||||||
:param proxy: 代理url和端口
|
:param proxy: 代理url和端口
|
||||||
:return: 当前对象
|
:return: 当前对象
|
||||||
"""
|
"""
|
||||||
|
if search(r'.*?:.*?@.*?\..*', proxy):
|
||||||
|
print('你似乎在设置使用账号密码的代理,暂时不支持这种代理,可自行用插件实现需求。')
|
||||||
|
if not proxy.lower().startswith('socks'):
|
||||||
|
print('你似乎在设置使用socks代理,暂时不支持这种代理,可自行用插件实现需求。')
|
||||||
self._proxy = proxy
|
self._proxy = proxy
|
||||||
return self.set_argument('--proxy-server', proxy)
|
return self.set_argument('--proxy-server', proxy)
|
||||||
|
|
||||||
@ -368,25 +380,26 @@ class ChromiumOptions(object):
|
|||||||
self._load_mode = value.lower()
|
self._load_mode = value.lower()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_paths(self, browser_path=None, local_port=None, debugger_address=None, download_path=None,
|
def set_paths(self, browser_path=None, local_port=None, address=None, download_path=None,
|
||||||
user_data_path=None, cache_path=None):
|
user_data_path=None, cache_path=None, debugger_address=None):
|
||||||
"""快捷的路径设置函数
|
"""快捷的路径设置函数
|
||||||
:param browser_path: 浏览器可执行文件路径
|
:param browser_path: 浏览器可执行文件路径
|
||||||
:param local_port: 本地端口号
|
:param local_port: 本地端口号
|
||||||
:param debugger_address: 调试浏览器地址,例:127.0.0.1:9222
|
:param address: 调试浏览器地址,例:127.0.0.1:9222
|
||||||
:param download_path: 下载文件路径
|
:param download_path: 下载文件路径
|
||||||
:param user_data_path: 用户数据路径
|
:param user_data_path: 用户数据路径
|
||||||
:param cache_path: 缓存路径
|
:param cache_path: 缓存路径
|
||||||
:return: 当前对象
|
:return: 当前对象
|
||||||
"""
|
"""
|
||||||
|
address = address or debugger_address
|
||||||
if browser_path is not None:
|
if browser_path is not None:
|
||||||
self.set_browser_path(browser_path)
|
self.set_browser_path(browser_path)
|
||||||
|
|
||||||
if local_port is not None:
|
if local_port is not None:
|
||||||
self.set_local_port(local_port)
|
self.set_local_port(local_port)
|
||||||
|
|
||||||
if debugger_address is not None:
|
if address is not None:
|
||||||
self.set_debugger_address(debugger_address)
|
self.set_address(address)
|
||||||
|
|
||||||
if download_path is not None:
|
if download_path is not None:
|
||||||
self.set_download_path(download_path)
|
self.set_download_path(download_path)
|
||||||
@ -404,17 +417,17 @@ class ChromiumOptions(object):
|
|||||||
:param port: 端口号
|
:param port: 端口号
|
||||||
:return: 当前对象
|
:return: 当前对象
|
||||||
"""
|
"""
|
||||||
self._debugger_address = f'127.0.0.1:{port}'
|
self._address = f'127.0.0.1:{port}'
|
||||||
self._auto_port = False
|
self._auto_port = False
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_debugger_address(self, address):
|
def set_address(self, address):
|
||||||
"""设置浏览器地址,格式'ip:port'
|
"""设置浏览器地址,格式'ip:port'
|
||||||
:param address: 浏览器地址
|
:param address: 浏览器地址
|
||||||
:return: 当前对象
|
:return: 当前对象
|
||||||
"""
|
"""
|
||||||
address = address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
|
address = address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
|
||||||
self._debugger_address = address
|
self._address = address
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_browser_path(self, path):
|
def set_browser_path(self, path):
|
||||||
@ -507,7 +520,7 @@ class ChromiumOptions(object):
|
|||||||
om = OptionsManager(self.ini_path or str(Path(__file__).parent / 'configs.ini'))
|
om = OptionsManager(self.ini_path or str(Path(__file__).parent / 'configs.ini'))
|
||||||
|
|
||||||
# 设置chromium_options
|
# 设置chromium_options
|
||||||
attrs = ('debugger_address', 'browser_path', 'arguments', 'extensions', 'user', 'load_mode',
|
attrs = ('address', 'browser_path', 'arguments', 'extensions', 'user', 'load_mode',
|
||||||
'auto_port', 'system_user_path', 'existing_only', 'flags')
|
'auto_port', 'system_user_path', 'existing_only', 'flags')
|
||||||
for i in attrs:
|
for i in attrs:
|
||||||
om.set_item('chromium_options', i, self.__getattribute__(f'_{i}'))
|
om.set_item('chromium_options', i, self.__getattribute__(f'_{i}'))
|
||||||
@ -517,7 +530,7 @@ class ChromiumOptions(object):
|
|||||||
# 设置路径
|
# 设置路径
|
||||||
om.set_item('paths', 'download_path', self._download_path or '')
|
om.set_item('paths', 'download_path', self._download_path or '')
|
||||||
# 设置timeout
|
# 设置timeout
|
||||||
om.set_item('timeouts', 'implicit', self._timeouts['implicit'])
|
om.set_item('timeouts', 'base', self._timeouts['base'])
|
||||||
om.set_item('timeouts', 'page_load', self._timeouts['pageLoad'])
|
om.set_item('timeouts', 'page_load', self._timeouts['pageLoad'])
|
||||||
om.set_item('timeouts', 'script', self._timeouts['script'])
|
om.set_item('timeouts', 'script', self._timeouts['script'])
|
||||||
# 设置重试
|
# 设置重试
|
||||||
|
@ -19,7 +19,7 @@ class ChromiumOptions(object):
|
|||||||
self._load_mode: str = ...
|
self._load_mode: str = ...
|
||||||
self._timeouts: dict = ...
|
self._timeouts: dict = ...
|
||||||
self._proxy: str = ...
|
self._proxy: str = ...
|
||||||
self._debugger_address: str = ...
|
self._address: str = ...
|
||||||
self._extensions: list = ...
|
self._extensions: list = ...
|
||||||
self._prefs: dict = ...
|
self._prefs: dict = ...
|
||||||
self._flags: dict = ...
|
self._flags: dict = ...
|
||||||
@ -54,7 +54,7 @@ class ChromiumOptions(object):
|
|||||||
def proxy(self) -> str: ...
|
def proxy(self) -> str: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def debugger_address(self) -> str: ...
|
def address(self) -> str: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arguments(self) -> list: ...
|
def arguments(self) -> list: ...
|
||||||
@ -100,7 +100,7 @@ class ChromiumOptions(object):
|
|||||||
|
|
||||||
def clear_flags_in_file(self) -> ChromiumOptions: ...
|
def clear_flags_in_file(self) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def set_timeouts(self, implicit: float = None, pageLoad: float = None,
|
def set_timeouts(self, base: float = None, pageLoad: float = None,
|
||||||
script: float = None) -> ChromiumOptions: ...
|
script: float = None) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def set_user(self, user: str = 'Default') -> ChromiumOptions: ...
|
def set_user(self, user: str = 'Default') -> ChromiumOptions: ...
|
||||||
@ -125,7 +125,7 @@ class ChromiumOptions(object):
|
|||||||
|
|
||||||
def set_local_port(self, port: Union[str, int]) -> ChromiumOptions: ...
|
def set_local_port(self, port: Union[str, int]) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def set_debugger_address(self, address: str) -> ChromiumOptions: ...
|
def set_address(self, address: str) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def set_download_path(self, path: Union[str, Path]) -> ChromiumOptions: ...
|
def set_download_path(self, path: Union[str, Path]) -> ChromiumOptions: ...
|
||||||
|
|
||||||
@ -134,8 +134,8 @@ class ChromiumOptions(object):
|
|||||||
def set_cache_path(self, path: Union[str, Path]) -> ChromiumOptions: ...
|
def set_cache_path(self, path: Union[str, Path]) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def set_paths(self, browser_path: Union[str, Path] = None, local_port: Union[int, str] = None,
|
def set_paths(self, browser_path: Union[str, Path] = None, local_port: Union[int, str] = None,
|
||||||
debugger_address: str = None, download_path: Union[str, Path] = None,
|
address: str = None, download_path: Union[str, Path] = None, user_data_path: Union[str, Path] = None,
|
||||||
user_data_path: Union[str, Path] = None, cache_path: Union[str, Path] = None) -> ChromiumOptions: ...
|
cache_path: Union[str, Path] = None) -> ChromiumOptions: ...
|
||||||
|
|
||||||
def use_system_user_path(self, on_off: bool = True) -> ChromiumOptions: ...
|
def use_system_user_path(self, on_off: bool = True) -> ChromiumOptions: ...
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
download_path =
|
download_path =
|
||||||
|
|
||||||
[chromium_options]
|
[chromium_options]
|
||||||
debugger_address = 127.0.0.1:9222
|
address = 127.0.0.1:9222
|
||||||
browser_path = chrome
|
browser_path = chrome
|
||||||
arguments = ['--no-first-run', '--disable-infobars', '--disable-popup-blocking']
|
arguments = ['--no-first-run', '--disable-infobars', '--disable-popup-blocking']
|
||||||
extensions = []
|
extensions = []
|
||||||
@ -18,7 +18,7 @@ existing_only = False
|
|||||||
headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}
|
headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'}
|
||||||
|
|
||||||
[timeouts]
|
[timeouts]
|
||||||
implicit = 10
|
base = 10
|
||||||
page_load = 30
|
page_load = 30
|
||||||
script = 30
|
script = 30
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class SessionOptions(object):
|
|||||||
self._max_redirects = options['max_redirects']
|
self._max_redirects = options['max_redirects']
|
||||||
|
|
||||||
self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None))
|
self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None))
|
||||||
self._timeout = om.timeouts.get('implicit', 10)
|
self._timeout = om.timeouts.get('base', 10)
|
||||||
self._download_path = om.paths.get('download_path', None) or None
|
self._download_path = om.paths.get('download_path', None) or None
|
||||||
|
|
||||||
others = om.others
|
others = om.others
|
||||||
@ -379,7 +379,7 @@ class SessionOptions(object):
|
|||||||
om.set_item('session_options', i, options[i])
|
om.set_item('session_options', i, options[i])
|
||||||
|
|
||||||
om.set_item('paths', 'download_path', self.download_path or '')
|
om.set_item('paths', 'download_path', self.download_path or '')
|
||||||
om.set_item('timeouts', 'implicit', self.timeout)
|
om.set_item('timeouts', 'base', self.timeout)
|
||||||
om.set_item('proxies', 'http', self.proxies.get('http', None))
|
om.set_item('proxies', 'http', self.proxies.get('http', None))
|
||||||
om.set_item('proxies', 'https', self.proxies.get('https', None))
|
om.set_item('proxies', 'https', self.proxies.get('https', None))
|
||||||
om.set_item('others', 'retry_times', self.retry_times)
|
om.set_item('others', 'retry_times', self.retry_times)
|
||||||
|
@ -19,7 +19,7 @@ from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_f
|
|||||||
from .._units.clicker import Clicker
|
from .._units.clicker import Clicker
|
||||||
from .._units.rect import ElementRect
|
from .._units.rect import ElementRect
|
||||||
from .._units.scroller import ElementScroller
|
from .._units.scroller import ElementScroller
|
||||||
from .._units.select_element import SelectElement
|
from .._units.selector import SelectElement
|
||||||
from .._units.setter import ChromiumElementSetter
|
from .._units.setter import ChromiumElementSetter
|
||||||
from .._units.states import ElementStates, ShadowRootStates
|
from .._units.states import ElementStates, ShadowRootStates
|
||||||
from .._units.waiter import ElementWaiter
|
from .._units.waiter import ElementWaiter
|
||||||
|
@ -16,7 +16,7 @@ from .._pages.web_page import WebPage
|
|||||||
from .._units.clicker import Clicker
|
from .._units.clicker import Clicker
|
||||||
from .._units.rect import ElementRect
|
from .._units.rect import ElementRect
|
||||||
from .._units.scroller import ElementScroller
|
from .._units.scroller import ElementScroller
|
||||||
from .._units.select_element import SelectElement
|
from .._units.selector import SelectElement
|
||||||
from .._units.setter import ChromiumElementSetter
|
from .._units.setter import ChromiumElementSetter
|
||||||
from .._units.states import ShadowRootStates, ElementStates
|
from .._units.states import ShadowRootStates, ElementStates
|
||||||
from .._units.waiter import ElementWaiter
|
from .._units.waiter import ElementWaiter
|
||||||
|
@ -17,8 +17,8 @@ from .._commons.web import location_in_viewport
|
|||||||
from .._elements.chromium_element import ChromiumElement, run_js, make_chromium_ele
|
from .._elements.chromium_element import ChromiumElement, run_js, make_chromium_ele
|
||||||
from .._elements.none_element import NoneElement
|
from .._elements.none_element import NoneElement
|
||||||
from .._elements.session_element import make_session_ele
|
from .._elements.session_element import make_session_ele
|
||||||
from .._units.action_chains import ActionChains
|
from .._units.actions import Actions
|
||||||
from .._units.network_listener import NetworkListener
|
from .._units.listener import Listener
|
||||||
from .._units.rect import TabRect
|
from .._units.rect import TabRect
|
||||||
from .._units.screencast import Screencast
|
from .._units.screencast import Screencast
|
||||||
from .._units.scroller import PageScroller
|
from .._units.scroller import PageScroller
|
||||||
@ -46,7 +46,6 @@ class ChromiumBase(BasePage):
|
|||||||
self._set = None
|
self._set = None
|
||||||
self._screencast = None
|
self._screencast = None
|
||||||
self._actions = None
|
self._actions = None
|
||||||
self._listener = None
|
|
||||||
self._states = None
|
self._states = None
|
||||||
self._has_alert = False
|
self._has_alert = False
|
||||||
self._ready_state = None
|
self._ready_state = None
|
||||||
@ -57,6 +56,8 @@ class ChromiumBase(BasePage):
|
|||||||
self._doc_got = False # 用于在LoadEventFired和FrameStoppedLoading间标记是否已获取doc
|
self._doc_got = False # 用于在LoadEventFired和FrameStoppedLoading间标记是否已获取doc
|
||||||
self._download_path = None
|
self._download_path = None
|
||||||
self._load_end_time = 0
|
self._load_end_time = 0
|
||||||
|
if not hasattr(self, '_listener'):
|
||||||
|
self._listener = None
|
||||||
|
|
||||||
if isinstance(address, int) or (isinstance(address, str) and address.isdigit()):
|
if isinstance(address, int) or (isinstance(address, str) and address.isdigit()):
|
||||||
address = f'127.0.0.1:{address}'
|
address = f'127.0.0.1:{address}'
|
||||||
@ -313,7 +314,7 @@ class ChromiumBase(BasePage):
|
|||||||
def actions(self):
|
def actions(self):
|
||||||
"""返回用于执行动作链的对象"""
|
"""返回用于执行动作链的对象"""
|
||||||
if self._actions is None:
|
if self._actions is None:
|
||||||
self._actions = ActionChains(self)
|
self._actions = Actions(self)
|
||||||
self.wait.load_complete()
|
self.wait.load_complete()
|
||||||
return self._actions
|
return self._actions
|
||||||
|
|
||||||
@ -321,7 +322,7 @@ class ChromiumBase(BasePage):
|
|||||||
def listen(self):
|
def listen(self):
|
||||||
"""返回用于聆听数据包的对象"""
|
"""返回用于聆听数据包的对象"""
|
||||||
if self._listener is None:
|
if self._listener is None:
|
||||||
self._listener = NetworkListener(self)
|
self._listener = Listener(self)
|
||||||
return self._listener
|
return self._listener
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1054,20 +1055,21 @@ class ChromiumBase(BasePage):
|
|||||||
class Timeout(object):
|
class Timeout(object):
|
||||||
"""用于保存d模式timeout信息的类"""
|
"""用于保存d模式timeout信息的类"""
|
||||||
|
|
||||||
def __init__(self, page, implicit=None, page_load=None, script=None):
|
def __init__(self, page, base=None, page_load=None, script=None, implicit=None):
|
||||||
"""
|
"""
|
||||||
:param page: ChromiumBase页面
|
:param page: ChromiumBase页面
|
||||||
:param implicit: 默认超时时间
|
:param base: 默认超时时间
|
||||||
:param page_load: 页面加载超时时间
|
:param page_load: 页面加载超时时间
|
||||||
:param script: js超时时间
|
:param script: js超时时间
|
||||||
"""
|
"""
|
||||||
self._page = page
|
self._page = page
|
||||||
self.implicit = 10 if implicit is None else implicit
|
base = base if base is not None else implicit
|
||||||
|
self.base = 10 if base is None else base
|
||||||
self.page_load = 30 if page_load is None else page_load
|
self.page_load = 30 if page_load is None else page_load
|
||||||
self.script = 30 if script is None else script
|
self.script = 30 if script is None else script
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str({'implicit': self.implicit, 'page_load': self.page_load, 'script': self.script})
|
return str({'base': self.base, 'page_load': self.page_load, 'script': self.script})
|
||||||
|
|
||||||
|
|
||||||
class Alert(object):
|
class Alert(object):
|
||||||
|
@ -14,8 +14,8 @@ from .._elements.none_element import NoneElement
|
|||||||
from .._elements.session_element import SessionElement
|
from .._elements.session_element import SessionElement
|
||||||
from .._pages.chromium_frame import ChromiumFrame
|
from .._pages.chromium_frame import ChromiumFrame
|
||||||
from .._pages.chromium_page import ChromiumPage
|
from .._pages.chromium_page import ChromiumPage
|
||||||
from .._units.action_chains import ActionChains
|
from .._units.actions import Actions
|
||||||
from .._units.network_listener import NetworkListener
|
from .._units.listener import Listener
|
||||||
from .._units.rect import TabRect
|
from .._units.rect import TabRect
|
||||||
from .._units.screencast import Screencast
|
from .._units.screencast import Screencast
|
||||||
from .._units.scroller import Scroller, PageScroller
|
from .._units.scroller import Scroller, PageScroller
|
||||||
@ -48,8 +48,8 @@ class ChromiumBase(BasePage):
|
|||||||
self._wait: BaseWaiter = ...
|
self._wait: BaseWaiter = ...
|
||||||
self._set: ChromiumBaseSetter = ...
|
self._set: ChromiumBaseSetter = ...
|
||||||
self._screencast: Screencast = ...
|
self._screencast: Screencast = ...
|
||||||
self._actions: ActionChains = ...
|
self._actions: Actions = ...
|
||||||
self._listener: NetworkListener = ...
|
self._listener: Listener = ...
|
||||||
self._states: PageStates = ...
|
self._states: PageStates = ...
|
||||||
self._alert: Alert = ...
|
self._alert: Alert = ...
|
||||||
self._has_alert: bool = ...
|
self._has_alert: bool = ...
|
||||||
@ -152,10 +152,10 @@ class ChromiumBase(BasePage):
|
|||||||
def screencast(self) -> Screencast: ...
|
def screencast(self) -> Screencast: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def actions(self) -> ActionChains: ...
|
def actions(self) -> Actions: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listen(self) -> NetworkListener: ...
|
def listen(self) -> Listener: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def states(self) -> PageStates: ...
|
def states(self) -> PageStates: ...
|
||||||
@ -237,9 +237,9 @@ class ChromiumBase(BasePage):
|
|||||||
|
|
||||||
class Timeout(object):
|
class Timeout(object):
|
||||||
|
|
||||||
def __init__(self, page: ChromiumBase, implicit=None, page_load=None, script=None):
|
def __init__(self, page: ChromiumBase, base=None, page_load=None, script=None):
|
||||||
self._page: ChromiumBase = ...
|
self._page: ChromiumBase = ...
|
||||||
self.implicit: float = ...
|
self.base: float = ...
|
||||||
self.page_load: float = ...
|
self.page_load: float = ...
|
||||||
self.script: float = ...
|
self.script: float = ...
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from time import sleep, perf_counter
|
|||||||
|
|
||||||
from .._elements.chromium_element import ChromiumElement
|
from .._elements.chromium_element import ChromiumElement
|
||||||
from .._pages.chromium_base import ChromiumBase
|
from .._pages.chromium_base import ChromiumBase
|
||||||
|
from .._units.listener import FrameListener
|
||||||
from .._units.rect import FrameRect
|
from .._units.rect import FrameRect
|
||||||
from .._units.scroller import FrameScroller
|
from .._units.scroller import FrameScroller
|
||||||
from .._units.setter import ChromiumFrameSetter
|
from .._units.setter import ChromiumFrameSetter
|
||||||
@ -122,12 +123,16 @@ class ChromiumFrame(ChromiumBase):
|
|||||||
self._is_diff_domain = False
|
self._is_diff_domain = False
|
||||||
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||||
self._frame_id = node['frameId']
|
self._frame_id = node['frameId']
|
||||||
|
if self._listener:
|
||||||
|
self._listener._to_target(self._target_page.tab_id, self.address, self)
|
||||||
super().__init__(self.address, self._target_page.tab_id, self._target_page.timeout)
|
super().__init__(self.address, self._target_page.tab_id, self._target_page.timeout)
|
||||||
self._debug = debug
|
self._debug = debug
|
||||||
self.driver._debug = d_debug
|
self.driver._debug = d_debug
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._is_diff_domain = True
|
self._is_diff_domain = True
|
||||||
|
if self._listener:
|
||||||
|
self._listener._to_target(node['frameId'], self.address, self)
|
||||||
super().__init__(self.address, node['frameId'], self._target_page.timeout)
|
super().__init__(self.address, node['frameId'], self._target_page.timeout)
|
||||||
end_time = perf_counter() + self.timeouts.page_load
|
end_time = perf_counter() + self.timeouts.page_load
|
||||||
while perf_counter() < end_time:
|
while perf_counter() < end_time:
|
||||||
@ -251,6 +256,13 @@ class ChromiumFrame(ChromiumBase):
|
|||||||
self._rect = FrameRect(self)
|
self._rect = FrameRect(self)
|
||||||
return self._rect
|
return self._rect
|
||||||
|
|
||||||
|
@property
|
||||||
|
def listen(self):
|
||||||
|
"""返回用于聆听数据包的对象"""
|
||||||
|
if self._listener is None:
|
||||||
|
self._listener = FrameListener(self)
|
||||||
|
return self._listener
|
||||||
|
|
||||||
# ----------挂件----------
|
# ----------挂件----------
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -11,10 +11,11 @@ from .chromium_page import ChromiumPage
|
|||||||
from .chromium_tab import ChromiumTab
|
from .chromium_tab import ChromiumTab
|
||||||
from .web_page import WebPage
|
from .web_page import WebPage
|
||||||
from .._elements.chromium_element import ChromiumElement
|
from .._elements.chromium_element import ChromiumElement
|
||||||
from .._units.states import FrameStates
|
from .._units.listener import FrameListener
|
||||||
from .._units.rect import FrameRect
|
from .._units.rect import FrameRect
|
||||||
from .._units.scroller import FrameScroller
|
from .._units.scroller import FrameScroller
|
||||||
from .._units.setter import ChromiumFrameSetter
|
from .._units.setter import ChromiumFrameSetter
|
||||||
|
from .._units.states import FrameStates
|
||||||
from .._units.waiter import FrameWaiter
|
from .._units.waiter import FrameWaiter
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class ChromiumFrame(ChromiumBase):
|
|||||||
self._states: FrameStates = ...
|
self._states: FrameStates = ...
|
||||||
self._reloading: bool = ...
|
self._reloading: bool = ...
|
||||||
self._rect: FrameRect = ...
|
self._rect: FrameRect = ...
|
||||||
|
self._listener: FrameListener = ...
|
||||||
|
|
||||||
def __call__(self, loc_or_str: Union[Tuple[str, str], str],
|
def __call__(self, loc_or_str: Union[Tuple[str, str], str],
|
||||||
timeout: float = None) -> Union[ChromiumElement, str]: ...
|
timeout: float = None) -> Union[ChromiumElement, str]: ...
|
||||||
@ -83,6 +85,9 @@ class ChromiumFrame(ChromiumBase):
|
|||||||
@property
|
@property
|
||||||
def rect(self) -> FrameRect: ...
|
def rect(self) -> FrameRect: ...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def listen(self) -> FrameListener: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _obj_id(self) -> str: ...
|
def _obj_id(self) -> str: ...
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class ChromiumPage(ChromiumBase):
|
|||||||
address = self._handle_options(addr_or_opts)
|
address = self._handle_options(addr_or_opts)
|
||||||
self._run_browser()
|
self._run_browser()
|
||||||
super().__init__(address, tab_id)
|
super().__init__(address, tab_id)
|
||||||
self.set.timeouts(implicit=timeout)
|
self.set.timeouts(base=timeout)
|
||||||
self._page_init()
|
self._page_init()
|
||||||
|
|
||||||
def _handle_options(self, addr_or_opts):
|
def _handle_options(self, addr_or_opts):
|
||||||
@ -48,7 +48,7 @@ class ChromiumPage(ChromiumBase):
|
|||||||
|
|
||||||
elif isinstance(addr_or_opts, str):
|
elif isinstance(addr_or_opts, str):
|
||||||
self._chromium_options = ChromiumOptions()
|
self._chromium_options = ChromiumOptions()
|
||||||
self._chromium_options.set_debugger_address(addr_or_opts)
|
self._chromium_options.set_address(addr_or_opts)
|
||||||
|
|
||||||
elif isinstance(addr_or_opts, int):
|
elif isinstance(addr_or_opts, int):
|
||||||
self._chromium_options = ChromiumOptions()
|
self._chromium_options = ChromiumOptions()
|
||||||
@ -57,36 +57,36 @@ class ChromiumPage(ChromiumBase):
|
|||||||
else:
|
else:
|
||||||
raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。')
|
raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。')
|
||||||
|
|
||||||
return self._chromium_options.debugger_address
|
return self._chromium_options.address
|
||||||
|
|
||||||
def _run_browser(self):
|
def _run_browser(self):
|
||||||
"""连接浏览器"""
|
"""连接浏览器"""
|
||||||
is_exist = connect_browser(self._chromium_options)
|
is_exist = connect_browser(self._chromium_options)
|
||||||
try:
|
try:
|
||||||
ws = get(f'http://{self._chromium_options.debugger_address}/json/version', headers={'Connection': 'close'})
|
ws = get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
|
||||||
if not ws:
|
if not ws:
|
||||||
raise BrowserConnectError('\n浏览器连接失败,请检查是否启用全局代理。如是,须设置不代理127.0.0.1地址。')
|
raise BrowserConnectError('\n浏览器连接失败,请检查是否启用全局代理。如是,须设置不代理127.0.0.1地址。')
|
||||||
except :
|
except :
|
||||||
raise BrowserConnectError('\n浏览器连接失败,请检查是否启用全局代理。如是,须设置不代理127.0.0.1地址。')
|
raise BrowserConnectError('\n浏览器连接失败,请检查是否启用全局代理。如是,须设置不代理127.0.0.1地址。')
|
||||||
|
|
||||||
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
||||||
self._browser = Browser(self._chromium_options.debugger_address, ws, self)
|
self._browser = Browser(self._chromium_options.address, ws, self)
|
||||||
|
|
||||||
if (is_exist and self._chromium_options._headless is False and
|
if (is_exist and self._chromium_options._headless is False and
|
||||||
'headless' in self._browser.run_cdp('Browser.getVersion')['userAgent'].lower()):
|
'headless' in self._browser.run_cdp('Browser.getVersion')['userAgent'].lower()):
|
||||||
self._browser.quit(3)
|
self._browser.quit(3)
|
||||||
connect_browser(self._chromium_options)
|
connect_browser(self._chromium_options)
|
||||||
ws = get(f'http://{self._chromium_options.debugger_address}/json/version', headers={'Connection': 'close'})
|
ws = get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
|
||||||
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
||||||
self._browser = Browser(self._chromium_options.debugger_address, ws, self)
|
self._browser = Browser(self._chromium_options.address, ws, self)
|
||||||
|
|
||||||
def _d_set_runtime_settings(self):
|
def _d_set_runtime_settings(self):
|
||||||
"""设置运行时用到的属性"""
|
"""设置运行时用到的属性"""
|
||||||
self._timeouts = Timeout(self, page_load=self._chromium_options.timeouts['pageLoad'],
|
self._timeouts = Timeout(self, page_load=self._chromium_options.timeouts['pageLoad'],
|
||||||
script=self._chromium_options.timeouts['script'],
|
script=self._chromium_options.timeouts['script'],
|
||||||
implicit=self._chromium_options.timeouts['implicit'])
|
base=self._chromium_options.timeouts['base'])
|
||||||
if self._chromium_options.timeouts['implicit'] is not None:
|
if self._chromium_options.timeouts['base'] is not None:
|
||||||
self._timeout = self._chromium_options.timeouts['implicit']
|
self._timeout = self._chromium_options.timeouts['base']
|
||||||
self._load_mode = self._chromium_options.load_mode
|
self._load_mode = self._chromium_options.load_mode
|
||||||
self._download_path = None if self._chromium_options.download_path is None \
|
self._download_path = None if self._chromium_options.download_path is None \
|
||||||
else str(Path(self._chromium_options.download_path).absolute())
|
else str(Path(self._chromium_options.download_path).absolute())
|
||||||
|
@ -174,7 +174,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
|||||||
@property
|
@property
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
"""返回通用timeout设置"""
|
"""返回通用timeout设置"""
|
||||||
return self.timeouts.implicit
|
return self.timeouts.base
|
||||||
|
|
||||||
@timeout.setter
|
@timeout.setter
|
||||||
def timeout(self, second):
|
def timeout(self, second):
|
||||||
@ -182,7 +182,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
|||||||
:param second: 秒数
|
:param second: 秒数
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.set.timeouts(implicit=second)
|
self.set.timeouts(base=second)
|
||||||
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
||||||
"""跳转到一个url
|
"""跳转到一个url
|
||||||
|
@ -32,7 +32,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
|||||||
super().__init__(session_or_options=session_or_options)
|
super().__init__(session_or_options=session_or_options)
|
||||||
if not chromium_options:
|
if not chromium_options:
|
||||||
chromium_options = ChromiumOptions(read_file=chromium_options)
|
chromium_options = ChromiumOptions(read_file=chromium_options)
|
||||||
chromium_options.set_timeouts(implicit=self._timeout).set_paths(download_path=self.download_path)
|
chromium_options.set_timeouts(base=self._timeout).set_paths(download_path=self.download_path)
|
||||||
super(SessionPage, self).__init__(addr_or_opts=chromium_options, timeout=timeout)
|
super(SessionPage, self).__init__(addr_or_opts=chromium_options, timeout=timeout)
|
||||||
self.change_mode(self._mode, go=False, copy_cookies=False)
|
self.change_mode(self._mode, go=False, copy_cookies=False)
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
|||||||
@property
|
@property
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
"""返回通用timeout设置"""
|
"""返回通用timeout设置"""
|
||||||
return self.timeouts.implicit
|
return self.timeouts.base
|
||||||
|
|
||||||
@timeout.setter
|
@timeout.setter
|
||||||
def timeout(self, second):
|
def timeout(self, second):
|
||||||
@ -146,7 +146,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
|||||||
:param second: 秒数
|
:param second: 秒数
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.set.timeouts(implicit=second)
|
self.set.timeouts(base=second)
|
||||||
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
||||||
"""跳转到一个url
|
"""跳转到一个url
|
||||||
|
@ -9,7 +9,7 @@ from .._commons.keys import modifierBit, keyDescriptionForString
|
|||||||
from .._commons.web import location_in_viewport
|
from .._commons.web import location_in_viewport
|
||||||
|
|
||||||
|
|
||||||
class ActionChains:
|
class Actions:
|
||||||
"""用于实现动作链的类"""
|
"""用于实现动作链的类"""
|
||||||
|
|
||||||
def __init__(self, page):
|
def __init__(self, page):
|
@ -10,7 +10,7 @@ from .._elements.chromium_element import ChromiumElement
|
|||||||
from .._pages.chromium_base import ChromiumBase
|
from .._pages.chromium_base import ChromiumBase
|
||||||
|
|
||||||
|
|
||||||
class ActionChains:
|
class Actions:
|
||||||
|
|
||||||
def __init__(self, page: ChromiumBase):
|
def __init__(self, page: ChromiumBase):
|
||||||
self.page: ChromiumBase = ...
|
self.page: ChromiumBase = ...
|
||||||
@ -20,53 +20,53 @@ class ActionChains:
|
|||||||
self.curr_y: int = ...
|
self.curr_y: int = ...
|
||||||
|
|
||||||
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[int, int], str],
|
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[int, int], str],
|
||||||
offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> ActionChains: ...
|
offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> Actions: ...
|
||||||
|
|
||||||
def move(self, offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> ActionChains: ...
|
def move(self, offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> Actions: ...
|
||||||
|
|
||||||
def click(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def r_click(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def r_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def m_click(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def m_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def db_click(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def db_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def hold(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def release(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def r_hold(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def r_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def r_release(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def r_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def m_hold(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def m_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def m_release(self, on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
def m_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def _hold(self, on_ele: Union[ChromiumElement, str] = None, button: str = 'left',
|
def _hold(self, on_ele: Union[ChromiumElement, str] = None, button: str = 'left',
|
||||||
count: int = 1) -> ActionChains: ...
|
count: int = 1) -> Actions: ...
|
||||||
|
|
||||||
def _release(self, button: str) -> ActionChains: ...
|
def _release(self, button: str) -> Actions: ...
|
||||||
|
|
||||||
def scroll(self, delta_x: int = 0, delta_y: int = 0,
|
def scroll(self, delta_x: int = 0, delta_y: int = 0,
|
||||||
on_ele: Union[ChromiumElement, str] = None) -> ActionChains: ...
|
on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||||
|
|
||||||
def up(self, pixel: int) -> ActionChains: ...
|
def up(self, pixel: int) -> Actions: ...
|
||||||
|
|
||||||
def down(self, pixel: int) -> ActionChains: ...
|
def down(self, pixel: int) -> Actions: ...
|
||||||
|
|
||||||
def left(self, pixel: int) -> ActionChains: ...
|
def left(self, pixel: int) -> Actions: ...
|
||||||
|
|
||||||
def right(self, pixel: int) -> ActionChains: ...
|
def right(self, pixel: int) -> Actions: ...
|
||||||
|
|
||||||
def key_down(self, key: str) -> ActionChains: ...
|
def key_down(self, key: str) -> Actions: ...
|
||||||
|
|
||||||
def key_up(self, key: str) -> ActionChains: ...
|
def key_up(self, key: str) -> Actions: ...
|
||||||
|
|
||||||
def type(self, text: Union[str, list, tuple]) -> ActionChains: ...
|
def type(self, text: Union[str, list, tuple]) -> Actions: ...
|
||||||
|
|
||||||
def wait(self, second: float) -> ActionChains: ...
|
def wait(self, second: float) -> Actions: ...
|
||||||
|
|
||||||
def _get_key_data(self, key: str, action: str) -> dict: ...
|
def _get_key_data(self, key: str, action: str) -> dict: ...
|
||||||
|
|
@ -14,7 +14,7 @@ from requests.structures import CaseInsensitiveDict
|
|||||||
from .._base.chromium_driver import ChromiumDriver
|
from .._base.chromium_driver import ChromiumDriver
|
||||||
|
|
||||||
|
|
||||||
class NetworkListener(object):
|
class Listener(object):
|
||||||
"""监听器基类"""
|
"""监听器基类"""
|
||||||
|
|
||||||
def __init__(self, page):
|
def __init__(self, page):
|
||||||
@ -22,6 +22,8 @@ class NetworkListener(object):
|
|||||||
:param page: ChromiumBase对象
|
:param page: ChromiumBase对象
|
||||||
"""
|
"""
|
||||||
self._page = page
|
self._page = page
|
||||||
|
self._address = page.address
|
||||||
|
self._target_id = page._target_id
|
||||||
self._driver = None
|
self._driver = None
|
||||||
|
|
||||||
self._caught = None # 临存捕捉到的数据
|
self._caught = None # 临存捕捉到的数据
|
||||||
@ -77,7 +79,7 @@ class NetworkListener(object):
|
|||||||
if self.listening:
|
if self.listening:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._driver = ChromiumDriver(self._page.tab_id, 'page', self._page.address)
|
self._driver = ChromiumDriver(self._target_id, 'page', self._address)
|
||||||
self._driver.run('Network.enable')
|
self._driver.run('Network.enable')
|
||||||
|
|
||||||
self.listening = True
|
self.listening = True
|
||||||
@ -102,7 +104,7 @@ class NetworkListener(object):
|
|||||||
fail = False
|
fail = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
end = perf_counter() + count
|
end = perf_counter() + timeout
|
||||||
while True:
|
while True:
|
||||||
if perf_counter() > end:
|
if perf_counter() > end:
|
||||||
fail = True
|
fail = True
|
||||||
@ -179,6 +181,26 @@ class NetworkListener(object):
|
|||||||
self._extra_info_ids = {}
|
self._extra_info_ids = {}
|
||||||
self._caught.queue.clear()
|
self._caught.queue.clear()
|
||||||
|
|
||||||
|
def _to_target(self, target_id, address, page):
|
||||||
|
"""切换监听的页面对象
|
||||||
|
:param target_id: 新页面对象_target_id
|
||||||
|
:param address: 新页面对象address
|
||||||
|
:param page: 新页面对象
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self._target_id = target_id
|
||||||
|
self._address = address
|
||||||
|
self._page = page
|
||||||
|
debug = False
|
||||||
|
if self._driver:
|
||||||
|
debug = self._driver._debug
|
||||||
|
self._driver.stop()
|
||||||
|
if self.listening:
|
||||||
|
self._driver = ChromiumDriver(self._target_id, 'page', self._address)
|
||||||
|
self._driver._debug = debug
|
||||||
|
self._driver.run('Network.enable')
|
||||||
|
self._set_callback()
|
||||||
|
|
||||||
def _set_callback(self):
|
def _set_callback(self):
|
||||||
"""设置监听请求的回调函数"""
|
"""设置监听请求的回调函数"""
|
||||||
self._driver.set_callback('Network.requestWillBeSent', self._requestWillBeSent)
|
self._driver.set_callback('Network.requestWillBeSent', self._requestWillBeSent)
|
||||||
@ -190,8 +212,6 @@ class NetworkListener(object):
|
|||||||
|
|
||||||
def _requestWillBeSent(self, **kwargs):
|
def _requestWillBeSent(self, **kwargs):
|
||||||
"""接收到请求时的回调函数"""
|
"""接收到请求时的回调函数"""
|
||||||
if kwargs.get('frameId', self._page._frame_id) != self._page._frame_id:
|
|
||||||
return
|
|
||||||
p = None
|
p = None
|
||||||
if not self._targets:
|
if not self._targets:
|
||||||
if not self._method or kwargs['request']['method'] in self._method:
|
if not self._method or kwargs['request']['method'] in self._method:
|
||||||
@ -210,9 +230,6 @@ class NetworkListener(object):
|
|||||||
not self._method or kwargs['request']['method'] in self._method):
|
not self._method or kwargs['request']['method'] in self._method):
|
||||||
p = self._request_ids.setdefault(rid, DataPacket(self._page.tab_id, target))
|
p = self._request_ids.setdefault(rid, DataPacket(self._page.tab_id, target))
|
||||||
p._raw_request = kwargs
|
p._raw_request = kwargs
|
||||||
if kwargs['request'].get('hasPostData', None) and not kwargs['request'].get('postData', None):
|
|
||||||
p._raw_post_data = self._driver.run('Network.getRequestPostData',
|
|
||||||
requestId=rid)['postData']
|
|
||||||
break
|
break
|
||||||
|
|
||||||
self._extra_info_ids.setdefault(kwargs['requestId'], {})['obj'] = p if p else False
|
self._extra_info_ids.setdefault(kwargs['requestId'], {})['obj'] = p if p else False
|
||||||
@ -223,8 +240,6 @@ class NetworkListener(object):
|
|||||||
|
|
||||||
def _response_received(self, **kwargs):
|
def _response_received(self, **kwargs):
|
||||||
"""接收到返回信息时处理方法"""
|
"""接收到返回信息时处理方法"""
|
||||||
if kwargs.get('frameId', self._page._frame_id) != self._page._frame_id:
|
|
||||||
return
|
|
||||||
request = self._request_ids.get(kwargs['requestId'], None)
|
request = self._request_ids.get(kwargs['requestId'], None)
|
||||||
if request:
|
if request:
|
||||||
request._raw_response = kwargs['response']
|
request._raw_response = kwargs['response']
|
||||||
@ -238,7 +253,7 @@ class NetworkListener(object):
|
|||||||
if obj is False:
|
if obj is False:
|
||||||
self._extra_info_ids.pop(kwargs['requestId'], None)
|
self._extra_info_ids.pop(kwargs['requestId'], None)
|
||||||
elif isinstance(obj, DataPacket):
|
elif isinstance(obj, DataPacket):
|
||||||
obj._requestExtraInfo = r['request']
|
obj._requestExtraInfo = r.get('request', None)
|
||||||
obj._responseExtraInfo = kwargs
|
obj._responseExtraInfo = kwargs
|
||||||
self._extra_info_ids.pop(kwargs['requestId'], None)
|
self._extra_info_ids.pop(kwargs['requestId'], None)
|
||||||
else:
|
else:
|
||||||
@ -246,16 +261,21 @@ class NetworkListener(object):
|
|||||||
|
|
||||||
def _loading_finished(self, **kwargs):
|
def _loading_finished(self, **kwargs):
|
||||||
"""请求完成时处理方法"""
|
"""请求完成时处理方法"""
|
||||||
r_id = kwargs['requestId']
|
rid = kwargs['requestId']
|
||||||
dp = self._request_ids.get(r_id)
|
packet = self._request_ids.get(rid)
|
||||||
if dp:
|
if packet:
|
||||||
r = self._driver.run('Network.getResponseBody', requestId=r_id)
|
r = self._driver.run('Network.getResponseBody', requestId=rid)
|
||||||
if 'body' in r:
|
if 'body' in r:
|
||||||
dp._raw_body = r['body']
|
packet._raw_body = r['body']
|
||||||
dp._base64_body = r['base64Encoded']
|
packet._base64_body = r['base64Encoded']
|
||||||
else:
|
else:
|
||||||
dp._raw_body = ''
|
packet._raw_body = ''
|
||||||
dp._base64_body = False
|
packet._base64_body = False
|
||||||
|
|
||||||
|
if (packet._raw_request['request'].get('hasPostData', None)
|
||||||
|
and not packet._raw_request['request'].get('postData', None)):
|
||||||
|
r = self._driver.run('Network.getRequestPostData', requestId=rid, _timeout=1)
|
||||||
|
packet._raw_post_data = r.get('postData', None)
|
||||||
|
|
||||||
r = self._extra_info_ids.get(kwargs['requestId'], None)
|
r = self._extra_info_ids.get(kwargs['requestId'], None)
|
||||||
if r:
|
if r:
|
||||||
@ -268,10 +288,10 @@ class NetworkListener(object):
|
|||||||
obj._responseExtraInfo = response
|
obj._responseExtraInfo = response
|
||||||
self._extra_info_ids.pop(kwargs['requestId'], None)
|
self._extra_info_ids.pop(kwargs['requestId'], None)
|
||||||
|
|
||||||
self._request_ids.pop(r_id, None)
|
self._request_ids.pop(rid, None)
|
||||||
|
|
||||||
if dp:
|
if packet:
|
||||||
self._caught.put(dp)
|
self._caught.put(packet)
|
||||||
|
|
||||||
def _loading_failed(self, **kwargs):
|
def _loading_failed(self, **kwargs):
|
||||||
"""请求失败时的回调方法"""
|
"""请求失败时的回调方法"""
|
||||||
@ -299,6 +319,20 @@ class NetworkListener(object):
|
|||||||
self._caught.put(dp)
|
self._caught.put(dp)
|
||||||
|
|
||||||
|
|
||||||
|
class FrameListener(Listener):
|
||||||
|
def _requestWillBeSent(self, **kwargs):
|
||||||
|
"""接收到请求时的回调函数"""
|
||||||
|
if not self._page._is_diff_domain and kwargs.get('frameId', None) != self._page._frame_id:
|
||||||
|
return
|
||||||
|
super()._requestWillBeSent(**kwargs)
|
||||||
|
|
||||||
|
def _response_received(self, **kwargs):
|
||||||
|
"""接收到返回信息时处理方法"""
|
||||||
|
if not self._page._is_diff_domain and kwargs.get('frameId', None) != self._page._frame_id:
|
||||||
|
return
|
||||||
|
super()._response_received(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DataPacket(object):
|
class DataPacket(object):
|
||||||
"""返回的数据包管理类"""
|
"""返回的数据包管理类"""
|
||||||
|
|
@ -10,11 +10,14 @@ from requests.structures import CaseInsensitiveDict
|
|||||||
|
|
||||||
from .._base.chromium_driver import ChromiumDriver
|
from .._base.chromium_driver import ChromiumDriver
|
||||||
from .._pages.chromium_base import ChromiumBase
|
from .._pages.chromium_base import ChromiumBase
|
||||||
|
from .._pages.chromium_frame import ChromiumFrame
|
||||||
|
|
||||||
|
|
||||||
class NetworkListener(object):
|
class Listener(object):
|
||||||
def __init__(self, page: ChromiumBase):
|
def __init__(self, page: ChromiumBase):
|
||||||
self._page: ChromiumBase = ...
|
self._page: ChromiumBase = ...
|
||||||
|
self._address: str = ...
|
||||||
|
self._target_id: str = ...
|
||||||
self._targets: Union[str, dict] = ...
|
self._targets: Union[str, dict] = ...
|
||||||
self._method: set = ...
|
self._method: set = ...
|
||||||
self._caught: Queue = ...
|
self._caught: Queue = ...
|
||||||
@ -44,6 +47,8 @@ class NetworkListener(object):
|
|||||||
|
|
||||||
def clear(self) -> None: ...
|
def clear(self) -> None: ...
|
||||||
|
|
||||||
|
def _to_target(self, target_id: str, address: str, page: ChromiumBase) -> None: ...
|
||||||
|
|
||||||
def start(self, targets: Union[str, List[str], Tuple, bool, None] = None, is_regex: bool = False,
|
def start(self, targets: Union[str, List[str], Tuple, bool, None] = None, is_regex: bool = False,
|
||||||
method: Union[str, list, tuple, set] = None) \
|
method: Union[str, list, tuple, set] = None) \
|
||||||
-> Union[DataPacket, Dict[str, List[DataPacket]], False]: ...
|
-> Union[DataPacket, Dict[str, List[DataPacket]], False]: ...
|
||||||
@ -66,6 +71,12 @@ class NetworkListener(object):
|
|||||||
def _set_callback(self) -> None: ...
|
def _set_callback(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class FrameListener(Listener):
|
||||||
|
def __init__(self, page: ChromiumFrame, is_diff: bool):
|
||||||
|
self._page: ChromiumFrame = ...
|
||||||
|
self._is_diff: bool = ...
|
||||||
|
|
||||||
|
|
||||||
class DataPacket(object):
|
class DataPacket(object):
|
||||||
"""返回的数据包管理类"""
|
"""返回的数据包管理类"""
|
||||||
|
|
@ -47,16 +47,17 @@ class ChromiumBaseSetter(BasePageSetter):
|
|||||||
"""设置连接失败重连间隔"""
|
"""设置连接失败重连间隔"""
|
||||||
self._page.retry_interval = interval
|
self._page.retry_interval = interval
|
||||||
|
|
||||||
def timeouts(self, implicit=None, page_load=None, script=None):
|
def timeouts(self, base=None, page_load=None, script=None, implicit=None):
|
||||||
"""设置超时时间,单位为秒
|
"""设置超时时间,单位为秒
|
||||||
:param implicit: 查找元素超时时间
|
:param base: 基本等待时间,除页面加载和脚本超时,其它等待默认使用
|
||||||
:param page_load: 页面加载超时时间
|
:param page_load: 页面加载超时时间
|
||||||
:param script: 脚本运行超时时间
|
:param script: 脚本运行超时时间
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if implicit is not None:
|
base = base if base is not None else implicit
|
||||||
self._page.timeouts.implicit = implicit
|
if base is not None:
|
||||||
self._page._timeout = implicit
|
self._page.timeouts.base = base
|
||||||
|
self._page._timeout = base
|
||||||
|
|
||||||
if page_load is not None:
|
if page_load is not None:
|
||||||
self._page.timeouts.page_load = page_load
|
self._page.timeouts.page_load = page_load
|
||||||
|
@ -45,7 +45,7 @@ class ChromiumBaseSetter(BasePageSetter):
|
|||||||
|
|
||||||
def retry_interval(self, interval: float) -> None: ...
|
def retry_interval(self, interval: float) -> None: ...
|
||||||
|
|
||||||
def timeouts(self, implicit: float = None, page_load: float = None, script: float = None) -> None: ...
|
def timeouts(self, base: float = None, page_load: float = None, script: float = None) -> None: ...
|
||||||
|
|
||||||
def user_agent(self, ua: str, platform: str = None) -> None: ...
|
def user_agent(self, ua: str, platform: str = None) -> None: ...
|
||||||
|
|
||||||
|
@ -130,14 +130,6 @@ class BaseWaiter(object):
|
|||||||
"""
|
"""
|
||||||
return self._change('title', text, exclude, timeout, raise_err)
|
return self._change('title', text, exclude, timeout, raise_err)
|
||||||
|
|
||||||
def data_packets(self, count=1, timeout=None, fix_count: bool = True):
|
|
||||||
"""等待符合要求的数据包到达指定数量
|
|
||||||
:param count: 需要捕捉的数据包数量
|
|
||||||
:param timeout: 超时时间,为None无限等待
|
|
||||||
:param fix_count: 是否必须满足总数要求,发生超时,为True返回False,为False返回已捕捉到的数据包
|
|
||||||
:return: count为1时返回数据包对象,大于1时返回列表,超时且fix_count为True时返回False"""
|
|
||||||
return self._driver.listen.wait(count, timeout, fix_count)
|
|
||||||
|
|
||||||
def _change(self, arg, text, exclude=False, timeout=None, raise_err=None):
|
def _change(self, arg, text, exclude=False, timeout=None, raise_err=None):
|
||||||
"""等待指定属性变成包含或不包含指定文本
|
"""等待指定属性变成包含或不包含指定文本
|
||||||
:param arg: 要被匹配的属性
|
:param arg: 要被匹配的属性
|
||||||
@ -189,6 +181,16 @@ class BaseWaiter(object):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# -----------即将废弃-----------
|
||||||
|
|
||||||
|
def data_packets(self, count=1, timeout=None, fix_count: bool = True):
|
||||||
|
"""等待符合要求的数据包到达指定数量
|
||||||
|
:param count: 需要捕捉的数据包数量
|
||||||
|
:param timeout: 超时时间,为None无限等待
|
||||||
|
:param fix_count: 是否必须满足总数要求,发生超时,为True返回False,为False返回已捕捉到的数据包
|
||||||
|
:return: count为1时返回数据包对象,大于1时返回列表,超时且fix_count为True时返回False"""
|
||||||
|
return self._driver.listen.wait(count, timeout, fix_count)
|
||||||
|
|
||||||
|
|
||||||
class TabWaiter(BaseWaiter):
|
class TabWaiter(BaseWaiter):
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
"""
|
"""
|
||||||
from typing import Union, List
|
from typing import Union, List
|
||||||
|
|
||||||
from .download_manager import DownloadMission
|
from .downloader import DownloadMission
|
||||||
from .network_listener import DataPacket
|
from .listener import DataPacket
|
||||||
from .._elements.chromium_element import ChromiumElement
|
from .._elements.chromium_element import ChromiumElement
|
||||||
from .._pages.chromium_base import ChromiumBase
|
from .._pages.chromium_base import ChromiumBase
|
||||||
from .._pages.chromium_frame import ChromiumFrame
|
from .._pages.chromium_frame import ChromiumFrame
|
||||||
@ -47,9 +47,6 @@ class BaseWaiter(object):
|
|||||||
|
|
||||||
def title_change(self, text: str, exclude: bool = False, timeout: float = None, raise_err: bool = None) -> bool: ...
|
def title_change(self, text: str, exclude: bool = False, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||||
|
|
||||||
def data_packets(self, count: int = 1, timeout: float = None,
|
|
||||||
fix_count: bool = True) -> Union[List[DataPacket], DataPacket, None]: ...
|
|
||||||
|
|
||||||
def _change(self, arg: str, text: str, exclude: bool = False, timeout: float = None,
|
def _change(self, arg: str, text: str, exclude: bool = False, timeout: float = None,
|
||||||
raise_err: bool = None) -> bool: ...
|
raise_err: bool = None) -> bool: ...
|
||||||
|
|
||||||
|
@ -8,6 +8,6 @@ from ._commons.keys import Keys
|
|||||||
from ._commons.settings import Settings
|
from ._commons.settings import Settings
|
||||||
from ._commons.tools import wait_until, configs_to_here
|
from ._commons.tools import wait_until, configs_to_here
|
||||||
from ._elements.session_element import make_session_ele
|
from ._elements.session_element import make_session_ele
|
||||||
from ._units.action_chains import ActionChains
|
from ._units.actions import Actions
|
||||||
|
|
||||||
__all__ = ['make_session_ele', 'ActionChains', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here']
|
__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here']
|
||||||
|
2
setup.py
2
setup.py
@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="DrissionPage",
|
name="DrissionPage",
|
||||||
version="4.0.0b15",
|
version="4.0.0b16",
|
||||||
author="g1879",
|
author="g1879",
|
||||||
author_email="g1879@qq.com",
|
author_email="g1879@qq.com",
|
||||||
description="Python based web automation tool. It can control the browser and send and receive data packets.",
|
description="Python based web automation tool. It can control the browser and send and receive data packets.",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user