4.0.0b13(详)

配置类和ini文件加上连接重试设置;
ini文件chrome_options改为chromium_options
@&改回@@,优化和修复逻辑问题;
WebPage的driver_options参数改为chromium_options
This commit is contained in:
g1879 2023-11-26 23:15:06 +08:00
parent 6d0f8a27f4
commit acfd774d1f
15 changed files with 184 additions and 146 deletions

View File

@ -13,4 +13,4 @@ from ._configs.chromium_options import ChromiumOptions
from ._configs.session_options import SessionOptions
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
__version__ = '4.0.0b12'
__version__ = '4.0.0b13'

View File

@ -293,7 +293,7 @@ def get_chrome_path(ini_path=None, show_msg=True, from_ini=True,
# -----------从ini文件中获取--------------
if ini_path and from_ini:
try:
path = OptionsManager(ini_path).chrome_options['browser_path']
path = OptionsManager(ini_path).chromium_options['browser_path']
except KeyError:
path = None
else:

View File

@ -68,21 +68,9 @@ def str_to_xpath_loc(loc):
# ------------------------------------------------------------------
# 多属性查找
if loc.startswith('@!') and loc != '@!':
r = split(r'(@!|@&|@\|)', loc)
if '@&' in r and '@|' in r:
raise ValueError('@&和@|不能同时出现在一个定位语句中。')
elif '@&' in r:
loc_str = _make_multi_xpath_str('*', loc)[1]
else: # @|
loc_str = _make_multi_xpath_str('*', loc, False)[1]
elif loc.startswith('@&') and loc != '@&':
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
loc_str = _make_multi_xpath_str('*', loc)[1]
elif loc.startswith('@|') and loc != '@|':
loc_str = _make_multi_xpath_str('*', loc, False)[1]
# 单属性查找
elif loc.startswith('@') and loc != '@':
loc_str = _make_single_xpath_str('*', loc)[1]
@ -92,24 +80,18 @@ def str_to_xpath_loc(loc):
at_ind = loc.find('@')
if at_ind == -1:
loc_str = f'//*[name()="{loc[4:]}"]'
elif loc[at_ind:].startswith(('@@', '@|', '@!')):
loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:])[1]
else:
if loc[at_ind:].startswith(('@&', '@!')):
loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:])[1]
elif loc[at_ind:].startswith('@|'):
loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:], False)[1]
else:
loc_str = _make_single_xpath_str(loc[4:at_ind], loc[at_ind:])[1]
loc_str = _make_single_xpath_str(loc[4:at_ind], loc[at_ind:])[1]
# 根据文本查找
elif loc.startswith('text='):
loc_str = f'//*[text()={_make_search_str(loc[5:])}]'
elif loc.startswith('text:') and loc != 'text:':
loc_str = f'//*/text()[contains(., {_make_search_str(loc[5:])})]/..'
elif loc.startswith('text^') and loc != 'text^':
loc_str = f'//*/text()[starts-with(., {_make_search_str(loc[5:])})]/..'
elif loc.startswith('text$') and loc != 'text$':
loc_str = f'//*/text()[substring(., string-length(.) - string-length({_make_search_str(loc[5:])}) +1) = ' \
f'{_make_search_str(loc[5:])}]/..'
@ -164,20 +146,8 @@ def str_to_css_loc(loc):
# ------------------------------------------------------------------
# 多属性查找
if loc.startswith('@!') and loc != '@!':
r = split(r'(@!|@&|@\|)', loc)
if '@&' in r and '@|' in r:
raise ValueError('@&和@|不能同时出现在一个定位语句中。')
elif '@&' in r:
loc_by, loc_str = _make_multi_css_str('*', loc)
else: # @|
loc_by, loc_str = _make_multi_css_str('*', loc, False)
elif loc.startswith('@&') and loc != '@&':
loc_by, loc_str = _make_multi_css_str('*', loc)
elif loc.startswith('@|') and loc != '@|':
loc_by, loc_str = _make_multi_css_str('*', loc, False)
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
loc_str = _make_multi_css_str('*', loc)[1]
# 单属性查找
elif loc.startswith('@') and loc != '@':
@ -188,39 +158,14 @@ def str_to_css_loc(loc):
at_ind = loc.find('@')
if at_ind == -1:
loc_str = loc[4:]
elif loc[at_ind:].startswith(('@@', '@|', '@!')):
loc_by, loc_str = _make_multi_css_str(loc[4:at_ind], loc[at_ind:])
else:
if loc[at_ind:].startswith(('@&', '@!')):
loc_by, loc_str = _make_multi_css_str(loc[4:at_ind], loc[at_ind:])
elif loc[at_ind:].startswith('@|'):
loc_by, loc_str = _make_multi_css_str(loc[4:at_ind], loc[at_ind:], False)
else:
loc_by, loc_str = _make_single_css_str(loc[4:at_ind], loc[at_ind:])
loc_by, loc_str = _make_single_css_str(loc[4:at_ind], loc[at_ind:])
# 根据文本查找
elif loc.startswith('text='):
loc_by = 'xpath'
loc_str = f'//*[text()={_make_search_str(loc[5:])}]'
elif loc.startswith('text:') and loc != 'text:':
loc_by = 'xpath'
loc_str = f'//*/text()[contains(., {_make_search_str(loc[5:])})]/..'
elif loc.startswith('text^') and loc != 'text^':
loc_by = 'xpath'
loc_str = f'//*/text()[starts-with(., {_make_search_str(loc[5:])})]/..'
elif loc.startswith('text$') and loc != 'text$':
loc_by = 'xpath'
loc_str = f'//*/text()[substring(., string-length(.) - string-length({_make_search_str(loc[5:])}) +1) = ' \
f'{_make_search_str(loc[5:])}]/..'
# 用xpath查找
elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):
loc_by = 'xpath'
loc_str = loc[6:]
elif loc.startswith(('x:', 'x=')) and loc not in ('x:', 'x='):
loc_by = 'xpath'
loc_str = loc[2:]
elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:', 'x:', 'x=')):
loc_by, loc_str = str_to_xpath_loc(loc)
# 用css selector查找
elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):
@ -230,8 +175,7 @@ def str_to_css_loc(loc):
# 根据文本模糊查找
elif loc:
loc_by = 'xpath'
loc_str = f'//*/text()[contains(., {_make_search_str(loc)})]/..'
loc_by, loc_str = str_to_xpath_loc(loc)
else:
loc_str = '*'
@ -296,17 +240,20 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
return 'xpath', f'//*[{arg_str}]{txt_str}' if arg_str else f'//*{txt_str}'
def _make_multi_xpath_str(tag: str, text: str, _and: bool = True) -> tuple:
def _make_multi_xpath_str(tag: str, text: str) -> tuple:
"""生成多属性查找的xpath语句
:param tag: 标签名
:param text: 待处理的字符串
:param _and: 是否与方式
:return: xpath字符串
"""
arg_list = []
args = split(r'(@!|@&)', text)[1:] if _and else split(r'(@!|@\|)', text)[1:]
if (_and and '@|' in args) or (not _and and '@&' in args):
raise ValueError('@&和@|不能同时出现在一个定位语句中。')
args = split(r'(@!|@@|@\|)', text)[1:]
if '@@' in args and '@|' in args:
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
elif '@@' in args:
_and = True
else: # @|
_and = False
for k in range(0, len(args) - 1, 2):
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
@ -371,22 +318,25 @@ def _make_search_str(search_str: str) -> str:
return search_str
def _make_multi_css_str(tag: str, text: str, _and: bool = True) -> tuple:
def _make_multi_css_str(tag: str, text: str) -> tuple:
"""生成多属性查找的css selector语句
:param tag: 标签名
:param text: 待处理的字符串
:param _and: 是否与方式
:return: css selector字符串
"""
arg_list = []
args = split(r'(@!|@&)', text)[1:] if _and else split(r'(@!|@\|)', text)[1:]
if (_and and '@|' in args) or (not _and and '@&' in args):
raise ValueError('@&和@|不能同时出现在一个定位语句中。')
args = split(r'(@!|@@|@\|)', text)[1:]
if '@@' in args and '@|' in args:
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
elif '@@' in args:
_and = True
else: # @|
_and = False
for k in range(0, len(args)-1, 2):
r = split(r'([:=$^])', args[k+1], maxsplit=1)
for k in range(0, len(args) - 1, 2):
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
if not r[0] or r[0].startswith(('text()', 'tx()')):
return _make_multi_xpath_str(tag, text, _and)
return _make_multi_xpath_str(tag, text)
arg_str = ''
len_r = len(r)

View File

@ -28,8 +28,8 @@ class ChromiumOptions(object):
ini_path = str(ini_path) if ini_path else None
om = OptionsManager(ini_path)
self.ini_path = om.ini_path
options = om.chrome_options
options = om.chromium_options
self._download_path = om.paths.get('download_path', None) or None
self._arguments = options.get('arguments', [])
self._browser_path = options.get('browser_path', '')
@ -63,6 +63,11 @@ class ChromiumOptions(object):
port, path = PortFinder().get_port()
self._debugger_address = f'127.0.0.1:{port}'
self.set_argument('--user-data-dir', path)
others = om.others
self._retry_times = others.get('retry_times', 3)
self._retry_interval = others.get('retry_interval', 2)
return
self.ini_path = None
@ -79,6 +84,8 @@ class ChromiumOptions(object):
self._auto_port = False
self._system_user_path = False
self._existing_only = False
self._retry_times = 3
self._retry_interval = 2
@property
def download_path(self):
@ -155,6 +162,28 @@ class ChromiumOptions(object):
"""返回是否只接管现有浏览器方式"""
return self._existing_only
@property
def retry_times(self):
"""返回连接失败时的重试次数"""
return self._retry_times
@property
def retry_interval(self):
"""返回连接失败时的重试间隔(秒)"""
return self._retry_interval
def set_retry(self, times=None, interval=None):
"""设置连接失败时的重试操作
:param times: 重试次数
:param interval: 重试间隔
:return: 当前对象
"""
if times is not None:
self._retry_times = times
if interval is not None:
self._retry_interval = interval
return self
def set_argument(self, arg, value=None):
"""设置浏览器配置的argument属性
:param arg: 属性名
@ -477,22 +506,25 @@ class ChromiumOptions(object):
else:
om = OptionsManager(self.ini_path or str(Path(__file__).parent / 'configs.ini'))
# 设置chrome_options
# 设置chromium_options
attrs = ('debugger_address', 'browser_path', 'arguments', 'extensions', 'user', 'load_mode',
'auto_port', 'system_user_path', 'existing_only', 'flags')
for i in attrs:
om.set_item('chrome_options', i, self.__getattribute__(f'_{i}'))
om.set_item('chromium_options', i, self.__getattribute__(f'_{i}'))
# 设置代理
om.set_item('proxies', 'http', self._proxy)
om.set_item('proxies', 'https', self._proxy)
# 设置路径
om.set_item('paths', 'download_path', self._download_path)
om.set_item('paths', 'download_path', self._download_path or '')
# 设置timeout
om.set_item('timeouts', 'implicit', self._timeouts['implicit'])
om.set_item('timeouts', 'page_load', self._timeouts['pageLoad'])
om.set_item('timeouts', 'script', self._timeouts['script'])
# 设置重试
om.set_item('others', 'retry_times', self.retry_times)
om.set_item('others', 'retry_interval', self.retry_interval)
# 设置prefs
om.set_item('chrome_options', 'prefs', self._prefs)
om.set_item('chromium_options', 'prefs', self._prefs)
path = str(path)
om.save(path)

View File

@ -29,6 +29,8 @@ class ChromiumOptions(object):
self._system_user_path: bool = ...
self._existing_only: bool = ...
self._headless: bool = ...
self._retry_times: int = ...
self._retry_interval: float = ...
@property
def download_path(self) -> str: ...
@ -72,6 +74,14 @@ class ChromiumOptions(object):
@property
def is_existing_only(self) -> bool: ...
@property
def retry_times(self) -> int: ...
@property
def retry_interval(self) -> float: ...
def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions: ...
def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions: ...
def remove_argument(self, value: str) -> ChromiumOptions: ...

View File

@ -1,7 +1,7 @@
[paths]
download_path =
[chrome_options]
[chromium_options]
debugger_address = 127.0.0.1:9222
browser_path = chrome
arguments = ['--no-first-run', '--disable-infobars', '--disable-popup-blocking']
@ -27,3 +27,6 @@ script = 30
http =
https =
[others]
retry_times = 3
retry_interval = 2

View File

@ -37,6 +37,8 @@ class SessionOptions(object):
self._stream = None
self._trust_env = None
self._max_redirects = None
self._retry_times = 3
self._retry_interval = 2
if read_file is False:
return
@ -44,39 +46,43 @@ class SessionOptions(object):
ini_path = str(ini_path) if ini_path else None
om = OptionsManager(ini_path)
self.ini_path = om.ini_path
options_dict = om.session_options
if options_dict.get('headers', None) is not None:
self.set_headers(options_dict['headers'])
options = om.session_options
if options.get('headers', None) is not None:
self.set_headers(options['headers'])
if options_dict.get('cookies', None) is not None:
self.set_cookies(options_dict['cookies'])
if options.get('cookies', None) is not None:
self.set_cookies(options['cookies'])
if options_dict.get('auth', None) is not None:
self._auth = options_dict['auth']
if options.get('auth', None) is not None:
self._auth = options['auth']
if options_dict.get('params', None) is not None:
self._params = options_dict['params']
if options.get('params', None) is not None:
self._params = options['params']
if options_dict.get('verify', None) is not None:
self._verify = options_dict['verify']
if options.get('verify', None) is not None:
self._verify = options['verify']
if options_dict.get('cert', None) is not None:
self._cert = options_dict['cert']
if options.get('cert', None) is not None:
self._cert = options['cert']
if options_dict.get('stream', None) is not None:
self._stream = options_dict['stream']
if options.get('stream', None) is not None:
self._stream = options['stream']
if options_dict.get('trust_env', None) is not None:
self._trust_env = options_dict['trust_env']
if options.get('trust_env', None) is not None:
self._trust_env = options['trust_env']
if options_dict.get('max_redirects', None) is not None:
self._max_redirects = options_dict['max_redirects']
if options.get('max_redirects', None) is not None:
self._max_redirects = options['max_redirects']
self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None))
self._timeout = om.timeouts.get('implicit', 10)
self._download_path = om.paths.get('download_path', None) or None
others = om.others
self._retry_times = others.get('retry_times', 3)
self._retry_interval = others.get('retry_interval', 2)
# ===========须独立处理的项开始============
@property
def download_path(self):
@ -120,6 +126,28 @@ class SessionOptions(object):
self._sets('proxies', {'http': http, 'https': https})
return self
@property
def retry_times(self):
"""返回连接失败时的重试次数"""
return self._retry_times
@property
def retry_interval(self):
"""返回连接失败时的重试间隔(秒)"""
return self._retry_interval
def set_retry(self, times=None, interval=None):
"""设置连接失败时的重试操作
:param times: 重试次数
:param interval: 重试间隔
:return: 当前对象
"""
if times is not None:
self._retry_times = times
if interval is not None:
self._retry_interval = interval
return self
# ===========须独立处理的项结束============
@property
@ -350,10 +378,12 @@ class SessionOptions(object):
if i not in ('download_path', 'timeout', 'proxies'):
om.set_item('session_options', i, options[i])
om.set_item('paths', 'download_path', self.download_path)
om.set_item('paths', 'download_path', self.download_path or '')
om.set_item('timeouts', 'implicit', self.timeout)
om.set_item('proxies', 'http', self.proxies.get('http', 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_interval', self.retry_interval)
for i in self._del_set:
if i == 'download_path':

View File

@ -31,6 +31,8 @@ class SessionOptions(object):
self._max_redirects: int = ...
self._timeout: float = ...
self._del_set: set = ...
self._retry_times: int = ...
self._retry_interval: float = ...
@property
def download_path(self) -> str: ...
@ -66,6 +68,14 @@ class SessionOptions(object):
def set_proxies(self, http: Union[str, None], https: Union[str, None] = None) -> SessionOptions: ...
@property
def retry_times(self) -> int: ...
@property
def retry_interval(self) -> float: ...
def set_retry(self, times: int = None, interval: float = None) -> SessionOptions: ...
@property
def hooks(self) -> dict: ...

View File

@ -42,53 +42,54 @@ class ChromiumPage(ChromiumBase):
:return: 返回浏览器地址
"""
if not addr_or_opts:
self._driver_options = ChromiumOptions(addr_or_opts)
self._chromium_options = ChromiumOptions(addr_or_opts)
elif isinstance(addr_or_opts, ChromiumOptions):
self._driver_options = addr_or_opts
self._chromium_options = addr_or_opts
elif isinstance(addr_or_opts, str):
self._driver_options = ChromiumOptions()
self._driver_options.set_debugger_address(addr_or_opts)
self._chromium_options = ChromiumOptions()
self._chromium_options.set_debugger_address(addr_or_opts)
elif isinstance(addr_or_opts, int):
self._driver_options = ChromiumOptions()
self._driver_options.set_local_port(addr_or_opts)
self._chromium_options = ChromiumOptions()
self._chromium_options.set_local_port(addr_or_opts)
else:
raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。')
return self._driver_options.debugger_address
return self._chromium_options.debugger_address
def _run_browser(self):
"""连接浏览器"""
is_exist = connect_browser(self._driver_options)
ws = get(f'http://{self._driver_options.debugger_address}/json/version',
is_exist = connect_browser(self._chromium_options)
ws = get(f'http://{self._chromium_options.debugger_address}/json/version',
headers={'Connection': 'close'})
if not ws:
raise BrowserConnectError('\n浏览器连接失败请检查是否启用全局代理。如有须开放127.0.0.1地址。')
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
self._browser = Browser(self._driver_options.debugger_address, ws, self)
self._browser = Browser(self._chromium_options.debugger_address, ws, self)
if (is_exist and self._driver_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()):
self._browser.quit(3)
connect_browser(self._driver_options)
ws = get(f'http://{self._driver_options.debugger_address}/json/version', headers={'Connection': 'close'})
connect_browser(self._chromium_options)
ws = get(f'http://{self._chromium_options.debugger_address}/json/version', headers={'Connection': 'close'})
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
self._browser = Browser(self._driver_options.debugger_address, ws, self)
self._browser = Browser(self._chromium_options.debugger_address, ws, self)
def _d_set_runtime_settings(self):
"""设置运行时用到的属性"""
self._timeouts = Timeout(self,
page_load=self._driver_options.timeouts['pageLoad'],
script=self._driver_options.timeouts['script'],
implicit=self._driver_options.timeouts['implicit'])
if self._driver_options.timeouts['implicit'] is not None:
self._timeout = self._driver_options.timeouts['implicit']
self._load_mode = self._driver_options.load_mode
self._download_path = None if self._driver_options.download_path is None \
else str(Path(self._driver_options.download_path).absolute())
self._timeouts = Timeout(self, page_load=self._chromium_options.timeouts['pageLoad'],
script=self._chromium_options.timeouts['script'],
implicit=self._chromium_options.timeouts['implicit'])
if self._chromium_options.timeouts['implicit'] is not None:
self._timeout = self._chromium_options.timeouts['implicit']
self._load_mode = self._chromium_options.load_mode
self._download_path = None if self._chromium_options.download_path is None \
else str(Path(self._chromium_options.download_path).absolute())
self.retry_times = self._chromium_options.retry_times
self.retry_interval = self._chromium_options.retry_interval
def _page_init(self):
"""浏览器相关设置"""

View File

@ -20,7 +20,7 @@ class ChromiumPage(ChromiumBase):
addr_or_opts: Union[str, int, ChromiumOptions] = None,
tab_id: str = None,
timeout: float = None):
self._driver_options: ChromiumOptions = ...
self._chromium_options: ChromiumOptions = ...
self._browser: Browser = ...
self._rect: Optional[TabRect] = ...

View File

@ -274,7 +274,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
# s模式转d模式
if self._mode == 'd':
if self._driver is None:
self._connect_browser(self.page._driver_options)
self._connect_browser(self.page._chromium_options)
self._url = None if not self._has_driver else super(SessionPage, self).url
self._has_driver = True

View File

@ -55,6 +55,8 @@ class SessionPage(BasePage):
self._timeout = self._session_options.timeout
self._download_path = None if self._session_options.download_path is None \
else str(Path(self._session_options.download_path).absolute())
self.retry_times = self._session_options.retry_times
self.retry_interval = self._session_options.retry_interval
def _create_session(self):
"""创建内建Session对象"""

View File

@ -15,15 +15,15 @@ from .._units.setter import WebPageSetter
class WebPage(SessionPage, ChromiumPage, BasePage):
"""整合浏览器和request的页面类"""
def __init__(self, mode='d', timeout=None, driver_options=None, session_or_options=None, driver_or_options=None):
def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
"""初始化函数
:param mode: 'd' 's'即driver模式和session模式
:param timeout: 超时时间d模式时为寻找元素时间s模式时为连接时间默认10秒
:param driver_options: ChromiumDriver对象只使用s模式时应传入False
:param chromium_options: ChromiumDriver对象只使用s模式时应传入False
:param session_or_options: Session对象或SessionOptions对象只使用d模式时应传入False
"""
if not driver_options and driver_or_options:
driver_options = driver_or_options
if not chromium_options and driver_or_options:
chromium_options = driver_or_options
self._mode = mode.lower()
if self._mode not in ('s', 'd'):
raise ValueError('mode参数只能是s或d。')
@ -31,10 +31,10 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
self._has_session = True
super().__init__(session_or_options=session_or_options)
if not driver_options:
driver_options = ChromiumOptions(read_file=driver_options)
driver_options.set_timeouts(implicit=self._timeout).set_paths(download_path=self.download_path)
super(SessionPage, self).__init__(addr_or_opts=driver_options, timeout=timeout)
if not chromium_options:
chromium_options = ChromiumOptions(read_file=chromium_options)
chromium_options.set_timeouts(implicit=self._timeout).set_paths(download_path=self.download_path)
super(SessionPage, self).__init__(addr_or_opts=chromium_options, timeout=timeout)
self.change_mode(self._mode, go=False, copy_cookies=False)
def __call__(self, loc_or_str, timeout=None):
@ -239,7 +239,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
# s模式转d模式
if self._mode == 'd':
if self._driver is None:
self._connect_browser(self._driver_options)
self._connect_browser(self._chromium_options)
self._url = None if not self._has_driver else super(SessionPage, self).url
self._has_driver = True

View File

@ -25,13 +25,13 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def __init__(self,
mode: str = 'd',
timeout: float = None,
driver_options: Union[ChromiumOptions, bool] = None,
chromium_options: Union[ChromiumOptions, bool] = None,
session_or_options: Union[Session, SessionOptions, bool] = None) -> None:
self._mode: str = ...
self._has_driver: bool = ...
self._has_session: bool = ...
self._session_options: Union[SessionOptions, None] = ...
self._driver_options: Union[ChromiumOptions, None] = ...
self._chromium_options: Union[ChromiumOptions, None] = ...
def __call__(self,
loc_or_str: Union[Tuple[str, str], str, ChromiumElement, SessionElement],

View File

@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh:
setup(
name="DrissionPage",
version="4.0.0b12",
version="4.0.0b13",
author="g1879",
author_email="g1879@qq.com",
description="Python based web automation tool. It can control the browser and send and receive data packets.",