ChromiumOptions和Browser增加is_headless属性;接管浏览器时如无头状态和设置不一致,会按设置重启浏览器

This commit is contained in:
g1879 2024-06-28 23:34:30 +08:00
parent ac3a8ec27c
commit 61dce186c6
5 changed files with 87 additions and 73 deletions

View File

@ -40,13 +40,12 @@ class Browser(object):
:param session_options: 使用双模Tab时使用的默认Session配置为True使用ini文件配置
"""
opt = handle_options(addr_or_opts)
is_exist, browser_id = run_browser(opt)
is_headless, browser_id = run_browser(opt)
if browser_id in cls._BROWSERS:
r = cls._BROWSERS[browser_id]
return r
return cls._BROWSERS[browser_id]
r = object.__new__(cls)
r._chromium_options = opt
r._is_exist = is_exist
r.is_headless = is_headless
r.id = browser_id
r.address = opt.address
cls._BROWSERS[browser_id] = r
@ -63,7 +62,6 @@ class Browser(object):
self._type = 'Browser'
self._driver = BrowserDriver(self.id, 'browser', self.address, self)
self.version = self.run_cdp('Browser.getVersion')['product']
self._frames = {}
self._drivers = {}
@ -82,9 +80,25 @@ class Browser(object):
self.retry_times = self._chromium_options.retry_times
self.retry_interval = self._chromium_options.retry_interval
if self.is_headless != self._chromium_options.is_headless:
self.quit(3, True)
connect_browser(self._chromium_options)
s = Session()
s.trust_env = False
ws = s.get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
self.id = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
self._driver = BrowserDriver(self.id, 'browser', self.address, self)
ws.close()
s.close()
self._frames = {}
self._drivers = {}
self._all_drivers = {}
self.version = self._run_cdp('Browser.getVersion')['product']
self._process_id = None
try:
r = self.run_cdp('SystemInfo.getProcessInfo')
r = self._run_cdp('SystemInfo.getProcessInfo')
for i in r.get('processInfo', []):
if i['type'] == 'browser':
self._process_id = i['id']
@ -92,7 +106,7 @@ class Browser(object):
except:
pass
self.run_cdp('Target.setDiscoverTargets', discover=True)
self._run_cdp('Target.setDiscoverTargets', discover=True)
self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)
self._driver.set_callback('Target.targetCreated', self._onTargetCreated)
self._dl_mgr = DownloadManager(self)
@ -137,7 +151,7 @@ class Browser(object):
self._drivers.pop(tab_id, None)
self._all_drivers.pop(tab_id, None)
def run_cdp(self, cmd, **cmd_args):
def _run_cdp(self, cmd, **cmd_args):
"""执行Chrome DevTools Protocol语句
:param cmd: 协议项目
:param cmd_args: 参数
@ -183,7 +197,7 @@ class Browser(object):
@property
def tabs_count(self):
"""返回标签页数量"""
j = self.run_cdp('Target.getTargets')['targetInfos'] # 不要改用get避免卡死
j = self._run_cdp('Target.getTargets')['targetInfos'] # 不要改用get避免卡死
return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')])
@property
@ -235,7 +249,7 @@ class Browser(object):
"""
tab = None
if new_context:
tab = self.run_cdp('Target.createBrowserContext')['browserContextId']
tab = self._run_cdp('Target.createBrowserContext')['browserContextId']
kwargs = {'url': ''}
if new_window:
@ -245,7 +259,7 @@ class Browser(object):
if tab:
kwargs['browserContextId'] = tab
tab = self.run_cdp('Target.createTarget', **kwargs)['targetId']
tab = self._run_cdp('Target.createTarget', **kwargs)['targetId']
while tab not in self._drivers:
sleep(.1)
tab = obj(self, tab)
@ -401,14 +415,14 @@ class Browser(object):
:param tab_id: 标签页id
:return: None
"""
self.run_cdp('Target.activateTarget', targetId=tab_id)
self._run_cdp('Target.activateTarget', targetId=tab_id)
def reconnect(self):
"""断开重连"""
self._driver.stop()
BrowserDriver.BROWSERS.pop(self.id)
self._driver = BrowserDriver(self.id, 'browser', self.address, self)
self.run_cdp('Target.setDiscoverTargets', discover=True)
self._run_cdp('Target.setDiscoverTargets', discover=True)
self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)
self._driver.set_callback('Target.targetCreated', self._onTargetCreated)
@ -419,7 +433,7 @@ class Browser(object):
:return: None
"""
try:
self.run_cdp('Browser.close')
self._run_cdp('Browser.close')
except PageDisconnectedError:
pass
self._driver.stop()
@ -433,7 +447,7 @@ class Browser(object):
return
try:
pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']]
pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']]
except:
return
@ -516,18 +530,20 @@ def handle_options(addr_or_opts):
def run_browser(chromium_options):
"""连接浏览器"""
is_exist = connect_browser(chromium_options)
connect_browser(chromium_options)
try:
s = Session()
s.trust_env = False
ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'})
if not ws:
raise BrowserConnectError('\n浏览器连接失败如使用全局代理须设置不代理127.0.0.1地址。')
browser_id = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。')
json = ws.json()
browser_id = json['webSocketDebuggerUrl'].split('/')[-1]
is_headless = 'headless' in json['User-Agent'].lower()
ws.close()
s.close()
except KeyError:
raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。')
except:
raise BrowserConnectError('\n浏览器连接失败,如使用全局代理须设置不代理127.0.0.1地址')
return is_exist, browser_id
raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动')
return is_headless, browser_id

View File

@ -24,6 +24,7 @@ class Browser(object):
version: str = ...
retry_times: int = ...
retry_interval: float = ...
is_headless:bool = ...
_BROWSERS: dict = ...
_chromium_options: ChromiumOptions = ...
@ -51,7 +52,7 @@ class Browser(object):
def _get_driver(self, tab_id: str, owner=None) -> Driver: ...
def run_cdp(self, cmd, **cmd_args) -> dict: ...
def _run_cdp(self, cmd, **cmd_args) -> dict: ...
@property
def process_id(self) -> Optional[int]: ...

View File

@ -21,7 +21,7 @@ class ChromiumOptions(object):
self._user = 'Default'
self._prefs_to_del = []
self.clear_file_flags = False
self._headless = None
self._is_headless = False
if read_file is False:
ini_path = False
@ -47,6 +47,10 @@ class ChromiumOptions(object):
self._load_mode = options.get('load_mode', 'normal')
self._system_user_path = options.get('system_user_path', False)
self._existing_only = options.get('existing_only', False)
for i in self._arguments:
if i.startswith('--headless'):
self._is_headless = True
break
self._proxy = om.proxies.get('http', None) or om.proxies.get('https', None)
@ -164,6 +168,11 @@ class ChromiumOptions(object):
"""返回连接失败时的重试间隔(秒)"""
return self._retry_interval
@property
def is_headless(self):
"""返回是否无头模式"""
return self._is_headless
def set_retry(self, times=None, interval=None):
"""设置连接失败时的重试操作
:param times: 重试次数
@ -184,11 +193,19 @@ class ChromiumOptions(object):
"""
self.remove_argument(arg)
if value is not False:
if arg == '--headless' and value is None:
self._arguments.append('--headless=new')
if arg == '--headless':
if value == 'false':
self._is_headless = False
else:
if value is None:
value = 'new'
self._arguments.append(f'--headless={value}')
self._is_headless = True
else:
arg_str = arg if value is None else f'{arg}={value}'
self._arguments.append(arg_str)
elif arg == '--headless':
self._is_headless = False
return self
def remove_argument(self, value):
@ -312,7 +329,7 @@ class ChromiumOptions(object):
:param on_off: 开或关
:return: 当前对象
"""
on_off = 'new' if on_off else 'false'
on_off = 'new' if on_off else on_off
return self.set_argument('--headless', on_off)
def no_imgs(self, on_off=True):

View File

@ -10,30 +10,31 @@ from typing import Union, Any, Literal, Optional, Tuple
class ChromiumOptions(object):
def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None):
self.ini_path: str = ...
self._driver_path: str = ...
self._user_data_path: str = ...
self._download_path: str = ...
self._tmp_path: str = ...
self._arguments: list = ...
self._browser_path: str = ...
self._user: str = ...
self._load_mode: str = ...
self._timeouts: dict = ...
self._proxy: str = ...
self._address: str = ...
self._extensions: list = ...
self._prefs: dict = ...
self._flags: dict = ...
self._prefs_to_del: list = ...
self.clear_file_flags: bool = ...
self._auto_port: bool = ...
self._system_user_path: bool = ...
self._existing_only: bool = ...
self._headless: bool = ...
self._retry_times: int = ...
self._retry_interval: float = ...
ini_path: Optional[str] = ...
_driver_path: str = ...
_user_data_path: Optional[str] = ...
_download_path: str = ...
_tmp_path: str = ...
_arguments: list = ...
_browser_path: str = ...
_user: str = ...
_load_mode: str = ...
_timeouts: dict = ...
_proxy: str = ...
_address: str = ...
_extensions: list = ...
_prefs: dict = ...
_flags: dict = ...
_prefs_to_del: list = ...
clear_file_flags: bool = ...
_auto_port: bool = ...
_system_user_path: bool = ...
_existing_only: bool = ...
_retry_times: int = ...
_retry_interval: float = ...
_is_headless: bool = ...
def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): ...
@property
def download_path(self) -> str: ...
@ -89,6 +90,9 @@ class ChromiumOptions(object):
@property
def retry_interval(self) -> float: ...
@property
def is_headless(self) -> bool: ...
def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions: ...
def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions: ...

View File

@ -30,11 +30,6 @@ def connect_browser(option):
ip, port = address.split(':')
if ip != '127.0.0.1' or port_is_using(ip, port) or option.is_existing_only:
test_connect(ip, port)
option._headless = False
for i in option.arguments:
if i.startswith('--headless') and not i.endswith('=false'):
option._headless = True
break
return True
# ----------创建浏览器进程----------
@ -65,7 +60,6 @@ def get_launch_args(opt):
# ----------处理arguments-----------
result = set()
has_user_path = False
headless = None
for i in opt.arguments:
if i.startswith(('--load-extension=', '--remote-debugging-port=')):
continue
@ -73,16 +67,6 @@ def get_launch_args(opt):
result.add(f'--user-data-dir={Path(i[16:]).absolute()}')
has_user_path = True
continue
elif i.startswith('--headless'):
if i == '--headless=false':
headless = False
continue
elif i == '--headless':
i = '--headless=new'
headless = True
else:
headless = True
result.add(i)
if not has_user_path and not opt.system_user_path:
@ -93,15 +77,7 @@ def get_launch_args(opt):
opt.set_user_data_path(path)
result.add(f'--user-data-dir={path}')
# if headless is None and system().lower() == 'linux': # 无界面Linux自动加入无头
# from os import popen
# r = popen('systemctl list-units | grep graphical.target')
# if 'graphical.target' not in r.read():
# headless = True
# result.add('--headless=new')
result = list(result)
opt._headless = headless
# ----------处理插件extensions-------------
ext = [str(Path(e).absolute()) for e in opt.extensions]