mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
5baa9f217d
@ -9,3 +9,6 @@
|
||||
2. 请附上代码和报错信息(如有)
|
||||
3. DrissionPage、浏览器、python版本号是多少?
|
||||
4. 有什么意见建议?
|
||||
|
||||
请在下方写正文,不要把内容插入到上面的问题中。
|
||||
---
|
@ -5,13 +5,13 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from ._pages.chromium_page import ChromiumPage
|
||||
from ._pages.session_page import SessionPage
|
||||
from ._pages.web_page import WebPage
|
||||
|
||||
# 启动配置类
|
||||
from ._base.browser import Chromium
|
||||
from ._configs.chromium_options import ChromiumOptions
|
||||
from ._configs.session_options import SessionOptions
|
||||
from ._pages.session_page import SessionPage
|
||||
|
||||
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
|
||||
__version__ = '4.0.5.4'
|
||||
from ._pages.chromium_page import ChromiumPage
|
||||
from ._pages.mix_page import MixPage
|
||||
from ._pages.mix_page import MixPage as WebPage
|
||||
|
||||
__version__ = '4.1.0.0b11'
|
||||
|
18
DrissionPage/__init__.pyi
Normal file
18
DrissionPage/__init__.pyi
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Author : g1879
|
||||
@Contact : g1879@qq.com
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from ._base.browser import Chromium
|
||||
from ._configs.chromium_options import ChromiumOptions
|
||||
from ._configs.session_options import SessionOptions
|
||||
from ._pages.session_page import SessionPage
|
||||
|
||||
from ._pages.chromium_page import ChromiumPage
|
||||
from ._pages.mix_page import MixPage
|
||||
from ._pages.mix_page import MixPage as WebPage
|
||||
|
||||
__all__ = ['MixPage', 'WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__']
|
||||
__version__: str = ...
|
@ -12,10 +12,11 @@ from urllib.parse import quote
|
||||
|
||||
from DownloadKit import DownloadKit
|
||||
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.locator import get_loc
|
||||
from .._functions.web import format_html
|
||||
from .._elements.none_element import NoneElement
|
||||
from .._functions.elements import get_frame
|
||||
from .._functions.locator import get_loc
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.web import format_html
|
||||
from ..errors import ElementNotFoundError
|
||||
|
||||
|
||||
@ -54,7 +55,6 @@ class BaseElement(BaseParser):
|
||||
|
||||
def __init__(self, owner=None):
|
||||
self.owner = owner
|
||||
self.page = owner._page if owner else None
|
||||
self._type = 'BaseElement'
|
||||
|
||||
# ----------------以下属性或方法由后代实现----------------
|
||||
@ -71,6 +71,16 @@ class BaseElement(BaseParser):
|
||||
def nexts(self):
|
||||
pass
|
||||
|
||||
def get_frame(self, loc_or_ind, timeout=None):
|
||||
"""获取元素中一个frame对象
|
||||
:param loc_or_ind: 定位符、iframe序号,序号从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:return: ChromiumFrame对象
|
||||
"""
|
||||
if not isinstance(loc_or_ind, (int, str, tuple)):
|
||||
raise TypeError('loc_or_ind参数是定位符或序号。')
|
||||
return get_frame(self, loc_ind_ele=loc_or_ind, timeout=timeout)
|
||||
|
||||
def _ele(self, locator, timeout=None, index=1, relative=False, raise_err=None, method=None):
|
||||
"""调用获取元素的方法
|
||||
:param locator: 定位符
|
||||
@ -81,6 +91,8 @@ class BaseElement(BaseParser):
|
||||
:param method: 调用的方法名
|
||||
:return: 元素对象或它们组成的列表
|
||||
"""
|
||||
if hasattr(locator, '_type'):
|
||||
return locator
|
||||
r = self._find_elements(locator, timeout=timeout, index=index, relative=relative, raise_err=raise_err)
|
||||
if r or isinstance(r, list):
|
||||
return r
|
||||
@ -120,11 +132,8 @@ class DrissionElement(BaseElement):
|
||||
:param text_node_only: 是否只返回文本节点
|
||||
:return: 文本列表
|
||||
"""
|
||||
if text_node_only:
|
||||
texts = self.eles('xpath:/text()')
|
||||
else:
|
||||
texts = [x if isinstance(x, str) else x.text for x in self.eles('xpath:./text() | *')]
|
||||
|
||||
texts = self.eles('xpath:/text()') if text_node_only else [x if isinstance(x, str) else x.text
|
||||
for x in self.eles('xpath:./text() | *')]
|
||||
return [format_html(x.strip(' ').rstrip('\n')) for x in texts if x and sub('[\r\n\t ]', '', x) != '']
|
||||
|
||||
def parent(self, level_or_loc=1, index=1):
|
||||
@ -138,10 +147,8 @@ class DrissionElement(BaseElement):
|
||||
|
||||
elif isinstance(level_or_loc, (tuple, str)):
|
||||
loc = get_loc(level_or_loc, True)
|
||||
|
||||
if loc[0] == 'css selector':
|
||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
||||
|
||||
loc = f'xpath:./ancestor::{loc[1].lstrip(". / ")}[{index}]'
|
||||
|
||||
else:
|
||||
@ -345,7 +352,6 @@ class BasePage(BaseParser):
|
||||
def __init__(self):
|
||||
"""初始化函数"""
|
||||
self._url = None
|
||||
self._timeout = 10
|
||||
self._url_available = None
|
||||
self.retry_times = 3
|
||||
self.retry_interval = 2
|
||||
@ -361,16 +367,6 @@ class BasePage(BaseParser):
|
||||
ele = self._ele('xpath://title', raise_err=False, method='title')
|
||||
return ele.text if ele else None
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回查找元素时等待的秒数"""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second):
|
||||
"""设置查找元素时等待的秒数"""
|
||||
self._timeout = second
|
||||
|
||||
@property
|
||||
def url_available(self):
|
||||
"""返回当前访问的url有效性"""
|
||||
@ -420,10 +416,6 @@ class BasePage(BaseParser):
|
||||
def user_agent(self):
|
||||
return
|
||||
|
||||
@abstractmethod
|
||||
def cookies(self, as_dict=False, all_info=False):
|
||||
return {}
|
||||
|
||||
@abstractmethod
|
||||
def get(self, url, show_errmsg=False, retry=None, interval=None):
|
||||
pass
|
||||
|
@ -15,7 +15,7 @@ from .._elements.session_element import SessionElement
|
||||
from .._functions.elements import SessionElementsList
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._pages.web_page import WebPage
|
||||
from .._pages.mix_page import MixPage
|
||||
|
||||
|
||||
class BaseParser(object):
|
||||
@ -59,7 +59,6 @@ class BaseElement(BaseParser):
|
||||
|
||||
def __init__(self, owner: BasePage = None):
|
||||
self.owner: BasePage = ...
|
||||
self.page: Union[ChromiumPage, SessionPage, WebPage] = ...
|
||||
|
||||
# ----------------以下属性或方法由后代实现----------------
|
||||
@property
|
||||
@ -200,22 +199,15 @@ class BasePage(BaseParser):
|
||||
self._url_available: bool = ...
|
||||
self.retry_times: int = ...
|
||||
self.retry_interval: float = ...
|
||||
self._timeout: float = ...
|
||||
self._download_path: str = ...
|
||||
self._DownloadKit: DownloadKit = ...
|
||||
self._none_ele_return_value: bool = ...
|
||||
self._none_ele_value: Any = ...
|
||||
self._page: Union[ChromiumPage, SessionPage, WebPage]=...
|
||||
self._page: Union[ChromiumPage, SessionPage, MixPage] = ...
|
||||
|
||||
@property
|
||||
def title(self) -> Union[str, None]: ...
|
||||
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second: float) -> None: ...
|
||||
|
||||
@property
|
||||
def url_available(self) -> bool: ...
|
||||
|
||||
@ -237,9 +229,6 @@ class BasePage(BaseParser):
|
||||
@property
|
||||
def user_agent(self) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def cookies(self, as_dict: bool = False, all_info: bool = False) -> Union[list, dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ...
|
||||
|
||||
|
@ -7,54 +7,100 @@
|
||||
"""
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from time import perf_counter, sleep
|
||||
from threading import Lock
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from requests import Session
|
||||
from websocket import WebSocketBadStatusException
|
||||
|
||||
from .driver import BrowserDriver, Driver
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._functions.browser import connect_browser
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import PortFinder
|
||||
from .._functions.tools import raise_error
|
||||
from .._pages.chromium_base import Timeout
|
||||
from .._pages.tabs import ChromiumTab, MixTab
|
||||
from .._units.downloader import DownloadManager
|
||||
from .._units.setter import BrowserSetter
|
||||
from .._units.waiter import BrowserWaiter
|
||||
from ..errors import BrowserConnectError, CDPError
|
||||
from ..errors import PageDisconnectedError
|
||||
|
||||
__ERROR__ = 'error'
|
||||
|
||||
|
||||
class Browser(object):
|
||||
BROWSERS = {}
|
||||
class Chromium(object):
|
||||
_BROWSERS = {}
|
||||
_lock = Lock()
|
||||
|
||||
def __new__(cls, address, browser_id, page):
|
||||
def __new__(cls, addr_or_opts=None, session_options=None):
|
||||
"""
|
||||
:param address: 浏览器地址
|
||||
:param browser_id: 浏览器id
|
||||
:param page: ChromiumPage对象
|
||||
:param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int)
|
||||
:param session_options: 使用双模Tab时使用的默认Session配置,为True使用ini文件配置
|
||||
"""
|
||||
if browser_id in cls.BROWSERS:
|
||||
return cls.BROWSERS[browser_id]
|
||||
return object.__new__(cls)
|
||||
opt = handle_options(addr_or_opts)
|
||||
is_headless, browser_id, is_exists = run_browser(opt)
|
||||
with cls._lock:
|
||||
if browser_id in cls._BROWSERS:
|
||||
r = cls._BROWSERS[browser_id]
|
||||
while not hasattr(r, '_driver'):
|
||||
sleep(.1)
|
||||
return r
|
||||
r = object.__new__(cls)
|
||||
r._chromium_options = opt
|
||||
r.is_headless = is_headless
|
||||
r._is_exists = is_exists
|
||||
r.id = browser_id
|
||||
cls._BROWSERS[browser_id] = r
|
||||
return r
|
||||
|
||||
def __init__(self, address, browser_id, page):
|
||||
def __init__(self, addr_or_opts=None, session_options=None):
|
||||
"""
|
||||
:param address: 浏览器地址
|
||||
:param browser_id: 浏览器id
|
||||
:param page: ChromiumPage对象
|
||||
:param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int)
|
||||
:param session_options: 使用双模Tab时使用的默认Session配置,为True使用ini文件配置
|
||||
"""
|
||||
if hasattr(self, '_created'):
|
||||
return
|
||||
self._created = True
|
||||
Browser.BROWSERS[browser_id] = self
|
||||
|
||||
self.page = page
|
||||
self.address = address
|
||||
self._driver = BrowserDriver(browser_id, 'browser', address, self)
|
||||
self.id = browser_id
|
||||
self._type = 'Chromium'
|
||||
self._frames = {}
|
||||
self._drivers = {}
|
||||
self._all_drivers = {}
|
||||
self._connected = False
|
||||
|
||||
self._set = None
|
||||
self._wait = None
|
||||
self._timeouts = Timeout(**self._chromium_options.timeouts)
|
||||
self._load_mode = self._chromium_options.load_mode
|
||||
self._download_path = str(Path(self._chromium_options.download_path).absolute())
|
||||
self.retry_times = self._chromium_options.retry_times
|
||||
self.retry_interval = self._chromium_options.retry_interval
|
||||
self.address = self._chromium_options.address
|
||||
self._driver = BrowserDriver(self.id, 'browser', self.address, self)
|
||||
|
||||
if self.is_headless != self._chromium_options.is_headless or (
|
||||
self._is_exists and self._chromium_options._new_env):
|
||||
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']
|
||||
@ -62,9 +108,348 @@ 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)
|
||||
|
||||
self._session_options = SessionOptions() if session_options is True else session_options
|
||||
|
||||
@property
|
||||
def user_data_path(self):
|
||||
"""返回用户文件夹路径"""
|
||||
return self._chromium_options.user_data_path
|
||||
|
||||
@property
|
||||
def process_id(self):
|
||||
"""返回浏览器进程id"""
|
||||
return self._process_id
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回timeouts设置"""
|
||||
return self._timeouts.base
|
||||
|
||||
@property
|
||||
def timeouts(self):
|
||||
"""返回timeouts设置"""
|
||||
return self._timeouts
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
"""返回加载模式"""
|
||||
return self._load_mode
|
||||
|
||||
@property
|
||||
def download_path(self):
|
||||
"""返回默认下载路径"""
|
||||
return self._download_path
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
if self._set is None:
|
||||
self._set = BrowserSetter(self)
|
||||
return self._set
|
||||
|
||||
@property
|
||||
def wait(self):
|
||||
"""返回用于等待的对象"""
|
||||
if self._wait is None:
|
||||
self._wait = BrowserWaiter(self)
|
||||
return self._wait
|
||||
|
||||
@property
|
||||
def tabs_count(self):
|
||||
"""返回标签页数量"""
|
||||
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
|
||||
def tab_ids(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp,因为顺序不对
|
||||
return [i['id'] for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]
|
||||
|
||||
@property
|
||||
def latest_tab(self):
|
||||
"""返回最新的标签页,最新标签页指最后创建或最后被激活的
|
||||
当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id"""
|
||||
return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj)
|
||||
|
||||
def cookies(self, all_info=False):
|
||||
"""以list格式返回所有域名的cookies
|
||||
:param all_info: 是否返回所有内容,False则只返回name, value, domain
|
||||
:return: cookies组成的列表
|
||||
"""
|
||||
cks = self._run_cdp(f'Storage.getCookies')['cookies']
|
||||
r = cks if all_info else [{'name': c['name'], 'value': c['value'], 'domain': c['domain']} for c in cks]
|
||||
return CookiesList(r)
|
||||
|
||||
def new_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param url: 新标签页跳转到的网址
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
return self._new_tab(ChromiumTab, url=url, new_window=new_window,
|
||||
background=background, new_context=new_context)
|
||||
|
||||
def new_mix_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param url: 新标签页跳转到的网址
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
return self._new_tab(MixTab, url=url, new_window=new_window,
|
||||
background=background, new_context=new_context)
|
||||
|
||||
def _new_tab(self, obj, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param obj: 要创建的Tab类型
|
||||
:param url: 新标签页跳转到的网址
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
tab = None
|
||||
if new_context:
|
||||
tab = self._run_cdp('Target.createBrowserContext')['browserContextId']
|
||||
|
||||
kwargs = {'url': ''}
|
||||
if new_window:
|
||||
kwargs['newWindow'] = True
|
||||
if background:
|
||||
kwargs['background'] = True
|
||||
if tab:
|
||||
kwargs['browserContextId'] = tab
|
||||
|
||||
try:
|
||||
tab = self._run_cdp('Target.createTarget', **kwargs)['targetId']
|
||||
except CDPError:
|
||||
data = ('a', {'href': url or 'https://#', 'target': '_new' if new_window else '_blank'})
|
||||
tab = self.get_mix_tab() if isinstance(obj, MixTab) else self.get_tab()
|
||||
return tab.add_ele(data).click.for_new_tab(by_js=True)
|
||||
|
||||
while tab not in self._drivers:
|
||||
sleep(.1)
|
||||
tab = obj(self, tab)
|
||||
if url:
|
||||
tab.get(url)
|
||||
return tab
|
||||
|
||||
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
:param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: Tab对象
|
||||
"""
|
||||
return self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, as_id=as_id)
|
||||
|
||||
def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表,title和url是与关系
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: Tab对象列表
|
||||
"""
|
||||
return self._get_tabs(title=title, url=url, tab_type=tab_type, as_id=as_id)
|
||||
|
||||
def get_mix_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
:param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: Tab对象
|
||||
"""
|
||||
return self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, mix=True, as_id=as_id)
|
||||
|
||||
def get_mix_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表,title和url是与关系
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: Tab对象列表
|
||||
"""
|
||||
return self._get_tabs(title=title, url=url, tab_type=tab_type, mix=True, as_id=as_id)
|
||||
|
||||
def _get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', mix=False, as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
:param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param mix: 是否返回可切换模式的Tab对象
|
||||
:param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效
|
||||
:return: Tab对象
|
||||
"""
|
||||
if id_or_num is not None:
|
||||
if isinstance(id_or_num, str):
|
||||
id_or_num = id_or_num
|
||||
elif isinstance(id_or_num, int):
|
||||
id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]
|
||||
elif isinstance(id_or_num, ChromiumTab):
|
||||
return id_or_num.tab_id if as_id else ChromiumTab(self, id_or_num.tab_id)
|
||||
|
||||
elif title == url is None and tab_type == 'page':
|
||||
id_or_num = self.tab_ids[0]
|
||||
|
||||
else:
|
||||
tabs = self._get_tabs(title=title, url=url, tab_type=tab_type, as_id=True)
|
||||
if tabs:
|
||||
id_or_num = tabs[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
if as_id:
|
||||
return id_or_num
|
||||
with self._lock:
|
||||
return MixTab(self, id_or_num) if mix else ChromiumTab(self, id_or_num)
|
||||
|
||||
def _get_tabs(self, title=None, url=None, tab_type='page', mix=False, as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表,title和url是与关系
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param mix: 是否返回可切换模式的Tab对象
|
||||
:param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效
|
||||
:return: Tab对象列表
|
||||
"""
|
||||
tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp
|
||||
|
||||
if isinstance(tab_type, str):
|
||||
tab_type = {tab_type}
|
||||
elif isinstance(tab_type, (list, tuple, set)):
|
||||
tab_type = set(tab_type)
|
||||
elif tab_type is not None:
|
||||
raise TypeError('tab_type只能是set、list、tuple、str、None。')
|
||||
|
||||
tabs = [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
|
||||
and (tab_type is None or i['type'] in tab_type))]
|
||||
if as_id:
|
||||
return [tab['id'] for tab in tabs]
|
||||
with self._lock:
|
||||
if mix:
|
||||
return [MixTab(self, tab['id']) for tab in tabs]
|
||||
else:
|
||||
return [ChromiumTab(self, tab['id']) for tab in tabs]
|
||||
|
||||
def close_tabs(self, tabs_or_ids=None, others=False):
|
||||
"""关闭传入的标签页,默认关闭当前页。可传入多个
|
||||
:param tabs_or_ids: 要关闭的标签页对象或id,可传入列表或元组,为None时关闭最后操作的
|
||||
:param others: 是否关闭指定标签页之外的
|
||||
:return: None
|
||||
"""
|
||||
all_tabs = set(self.tab_ids)
|
||||
if isinstance(tabs_or_ids, str):
|
||||
tabs = {tabs_or_ids}
|
||||
elif isinstance(tabs_or_ids, ChromiumTab):
|
||||
tabs = {tabs_or_ids.tab_id}
|
||||
elif tabs_or_ids is None:
|
||||
tabs = {self.tab_ids[0]}
|
||||
elif isinstance(tabs_or_ids, (list, tuple)):
|
||||
tabs = set(i.tab_id if isinstance(i, ChromiumTab) else i for i in tabs_or_ids)
|
||||
else:
|
||||
raise TypeError('tabs_or_ids参数只能传入标签页对象或id。')
|
||||
|
||||
if others:
|
||||
tabs = all_tabs - tabs
|
||||
|
||||
end_len = len(set(all_tabs) - set(tabs))
|
||||
if end_len <= 0:
|
||||
self.quit()
|
||||
return
|
||||
|
||||
for tab in tabs:
|
||||
self._onTargetDestroyed(targetId=tab)
|
||||
self._driver.run('Target.closeTarget', targetId=tab)
|
||||
sleep(.2)
|
||||
end_time = perf_counter() + 3
|
||||
while self.tabs_count != end_len and perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
|
||||
def activate_tab(self, id_ind_tab):
|
||||
"""使标签页变为活动状态
|
||||
:param id_ind_tab: 标签页id(str)、Tab对象或标签页序号(int),序号从1开始
|
||||
:return: None
|
||||
"""
|
||||
if isinstance(id_ind_tab, int):
|
||||
id_ind_tab += -1 if id_ind_tab else 1
|
||||
id_ind_tab = self.tab_ids[id_ind_tab]
|
||||
elif isinstance(id_ind_tab, ChromiumTab):
|
||||
id_ind_tab = id_ind_tab.tab_id
|
||||
self._run_cdp('Target.activateTarget', targetId=id_ind_tab)
|
||||
|
||||
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._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)
|
||||
self._driver.set_callback('Target.targetCreated', self._onTargetCreated)
|
||||
|
||||
def quit(self, timeout=5, force=False):
|
||||
"""关闭浏览器
|
||||
:param timeout: 等待浏览器关闭超时时间(秒)
|
||||
:param force: 是否立刻强制终止进程
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._run_cdp('Browser.close')
|
||||
except PageDisconnectedError:
|
||||
pass
|
||||
self._driver.stop()
|
||||
|
||||
drivers = list(self._all_drivers.values())
|
||||
for tab in drivers:
|
||||
for driver in tab:
|
||||
driver.stop()
|
||||
|
||||
if not force:
|
||||
return
|
||||
|
||||
try:
|
||||
pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']]
|
||||
except:
|
||||
return
|
||||
|
||||
from psutil import Process
|
||||
for pid in pids:
|
||||
try:
|
||||
Process(pid).kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
from os import popen
|
||||
from platform import system
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
ok = True
|
||||
for pid in pids:
|
||||
txt = f'tasklist | findstr {pid}' if system().lower() == 'windows' else f'ps -ef | grep {pid}'
|
||||
p = popen(txt)
|
||||
sleep(.05)
|
||||
try:
|
||||
if f' {pid} ' in p.read():
|
||||
ok = False
|
||||
break
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if ok:
|
||||
break
|
||||
|
||||
def _get_driver(self, tab_id, owner=None):
|
||||
"""新建并返回指定tab id的Driver
|
||||
@ -95,7 +480,6 @@ class Browser(object):
|
||||
def _onTargetDestroyed(self, **kwargs):
|
||||
"""标签页关闭时执行"""
|
||||
tab_id = kwargs['targetId']
|
||||
if hasattr(self, '_dl_mgr'):
|
||||
self._dl_mgr.clear_tab_info(tab_id)
|
||||
for key in [k for k, i in self._frames.items() if i == tab_id]:
|
||||
self._frames.pop(key, None)
|
||||
@ -104,13 +488,7 @@ class Browser(object):
|
||||
self._drivers.pop(tab_id, None)
|
||||
self._all_drivers.pop(tab_id, None)
|
||||
|
||||
def connect_to_page(self):
|
||||
"""执行与page相关的逻辑"""
|
||||
if not self._connected:
|
||||
self._dl_mgr = DownloadManager(self)
|
||||
self._connected = True
|
||||
|
||||
def run_cdp(self, cmd, **cmd_args):
|
||||
def _run_cdp(self, cmd, **cmd_args):
|
||||
"""执行Chrome DevTools Protocol语句
|
||||
:param cmd: 协议项目
|
||||
:param cmd_args: 参数
|
||||
@ -120,166 +498,10 @@ class Browser(object):
|
||||
r = self._driver.run(cmd, **cmd_args)
|
||||
return r if __ERROR__ not in r else raise_error(r, ignore)
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
return self._driver
|
||||
|
||||
@property
|
||||
def tabs_count(self):
|
||||
"""返回标签页数量"""
|
||||
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
|
||||
def tab_ids(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp,因为顺序不对
|
||||
return [i['id'] for i in j if i['type'] in ('page', 'webview')
|
||||
and not i['url'].startswith('devtools://')]
|
||||
|
||||
@property
|
||||
def process_id(self):
|
||||
"""返回浏览器进程id"""
|
||||
return self._process_id
|
||||
|
||||
def find_tabs(self, title=None, url=None, tab_type=None):
|
||||
"""查找符合条件的tab,返回它们组成的列表,title和url是与关系
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:return: dict格式的tab信息列表列表
|
||||
"""
|
||||
tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp
|
||||
|
||||
if isinstance(tab_type, str):
|
||||
tab_type = {tab_type}
|
||||
elif isinstance(tab_type, (list, tuple, set)):
|
||||
tab_type = set(tab_type)
|
||||
elif tab_type is not None:
|
||||
raise TypeError('tab_type只能是set、list、tuple、str、None。')
|
||||
|
||||
return [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
|
||||
and (tab_type is None or i['type'] in tab_type))]
|
||||
|
||||
def close_tab(self, tab_id):
|
||||
"""关闭标签页
|
||||
:param tab_id: 标签页id
|
||||
:return: None
|
||||
"""
|
||||
self._onTargetDestroyed(targetId=tab_id)
|
||||
self.driver.run('Target.closeTarget', targetId=tab_id)
|
||||
|
||||
def stop_driver(self, driver):
|
||||
"""停止一个Driver
|
||||
:param driver: Driver对象
|
||||
:return: None
|
||||
"""
|
||||
driver.stop()
|
||||
self._all_drivers.get(driver.id, set()).discard(driver)
|
||||
|
||||
def activate_tab(self, tab_id):
|
||||
"""使标签页变为活动状态
|
||||
:param tab_id: 标签页id
|
||||
:return: None
|
||||
"""
|
||||
self.run_cdp('Target.activateTarget', targetId=tab_id)
|
||||
|
||||
def get_window_bounds(self, tab_id=None):
|
||||
"""返回浏览器窗口位置和大小信息
|
||||
:param tab_id: 标签页id
|
||||
:return: 窗口大小字典
|
||||
"""
|
||||
return self.run_cdp('Browser.getWindowForTarget', targetId=tab_id or self.id)['bounds']
|
||||
|
||||
def new_tab(self, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页id
|
||||
"""
|
||||
bid = None
|
||||
if new_context:
|
||||
bid = self.run_cdp('Target.createBrowserContext')['browserContextId']
|
||||
|
||||
kwargs = {'url': ''}
|
||||
if new_window:
|
||||
kwargs['newWindow'] = True
|
||||
if background:
|
||||
kwargs['background'] = True
|
||||
if bid:
|
||||
kwargs['browserContextId'] = bid
|
||||
|
||||
tid = self.run_cdp('Target.createTarget', **kwargs)['targetId']
|
||||
while tid not in self._drivers:
|
||||
sleep(.1)
|
||||
return tid
|
||||
|
||||
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._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed)
|
||||
self._driver.set_callback('Target.targetCreated', self._onTargetCreated)
|
||||
|
||||
def quit(self, timeout=5, force=False):
|
||||
"""关闭浏览器
|
||||
:param timeout: 等待浏览器关闭超时时间(秒)
|
||||
:param force: 是否立刻强制终止进程
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.run_cdp('Browser.close')
|
||||
except PageDisconnectedError:
|
||||
pass
|
||||
self.driver.stop()
|
||||
|
||||
drivers = list(self._all_drivers.values())
|
||||
for tab in drivers:
|
||||
for driver in tab:
|
||||
driver.stop()
|
||||
|
||||
if not force:
|
||||
return
|
||||
|
||||
try:
|
||||
pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']]
|
||||
except:
|
||||
return
|
||||
|
||||
from psutil import Process
|
||||
for pid in pids:
|
||||
try:
|
||||
Process(pid).kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
from os import popen
|
||||
from platform import system
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
ok = True
|
||||
for pid in pids:
|
||||
txt = f'tasklist | findstr {pid}' if system().lower() == 'windows' else f'ps -ef | grep {pid}'
|
||||
p = popen(txt)
|
||||
sleep(.05)
|
||||
try:
|
||||
if f' {pid} ' in p.read():
|
||||
ok = False
|
||||
break
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if ok:
|
||||
break
|
||||
|
||||
def _on_disconnect(self):
|
||||
self.page._on_disconnect()
|
||||
Browser.BROWSERS.pop(self.id, None)
|
||||
if self.page._chromium_options.is_auto_port and self.page._chromium_options.user_data_path:
|
||||
path = Path(self.page._chromium_options.user_data_path)
|
||||
Chromium._BROWSERS.pop(self.id, None)
|
||||
if self._chromium_options.is_auto_port and self._chromium_options.user_data_path:
|
||||
path = Path(self._chromium_options.user_data_path)
|
||||
end_time = perf_counter() + 7
|
||||
while perf_counter() < end_time:
|
||||
if not path.exists():
|
||||
@ -290,3 +512,59 @@ class Browser(object):
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
pass
|
||||
sleep(.03)
|
||||
|
||||
|
||||
def handle_options(addr_or_opts):
|
||||
"""设置浏览器启动属性
|
||||
:param addr_or_opts: 'ip:port'、ChromiumOptions、Driver
|
||||
:return: 返回ChromiumOptions对象
|
||||
"""
|
||||
if not addr_or_opts:
|
||||
_chromium_options = ChromiumOptions(addr_or_opts)
|
||||
if _chromium_options.is_auto_port:
|
||||
port, path = PortFinder(_chromium_options.tmp_path).get_port(_chromium_options.is_auto_port)
|
||||
_chromium_options.set_address(f'127.0.0.1:{port}')
|
||||
_chromium_options.set_user_data_path(path)
|
||||
_chromium_options.auto_port(scope=_chromium_options.is_auto_port)
|
||||
|
||||
elif isinstance(addr_or_opts, ChromiumOptions):
|
||||
if addr_or_opts.is_auto_port:
|
||||
port, path = PortFinder(addr_or_opts.tmp_path).get_port(addr_or_opts.is_auto_port)
|
||||
addr_or_opts.set_address(f'127.0.0.1:{port}')
|
||||
addr_or_opts.set_user_data_path(path)
|
||||
addr_or_opts.auto_port(scope=addr_or_opts.is_auto_port)
|
||||
_chromium_options = addr_or_opts
|
||||
|
||||
elif isinstance(addr_or_opts, str):
|
||||
_chromium_options = ChromiumOptions()
|
||||
_chromium_options.set_address(addr_or_opts)
|
||||
|
||||
elif isinstance(addr_or_opts, int):
|
||||
_chromium_options = ChromiumOptions()
|
||||
_chromium_options.set_local_port(addr_or_opts)
|
||||
|
||||
else:
|
||||
raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。')
|
||||
|
||||
return _chromium_options
|
||||
|
||||
|
||||
def run_browser(chromium_options):
|
||||
"""连接浏览器"""
|
||||
is_exists = 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浏览器连接失败,请确认浏览器是否启动。')
|
||||
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浏览器连接失败,请确认浏览器是否启动。')
|
||||
return is_headless, browser_id, is_exists
|
||||
|
@ -5,36 +5,80 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from typing import List, Optional, Union, Set, Dict
|
||||
from threading import Lock
|
||||
from typing import List, Optional, Set, Dict, Union, Tuple
|
||||
|
||||
from .driver import BrowserDriver, Driver
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._pages.chromium_base import Timeout
|
||||
from .._pages.tabs import ChromiumTab, MixTab
|
||||
from .._units.downloader import DownloadManager
|
||||
from .._units.setter import BrowserSetter
|
||||
from .._units.waiter import BrowserWaiter
|
||||
|
||||
|
||||
class Browser(object):
|
||||
BROWSERS: dict = ...
|
||||
page: ChromiumPage = ...
|
||||
_driver: BrowserDriver = ...
|
||||
class Chromium(object):
|
||||
id: str = ...
|
||||
address: str = ...
|
||||
version: str = ...
|
||||
retry_times: int = ...
|
||||
retry_interval: float = ...
|
||||
is_headless: bool = ...
|
||||
|
||||
_BROWSERS: dict = ...
|
||||
_chromium_options: ChromiumOptions = ...
|
||||
_session_options: SessionOptions = ...
|
||||
_driver: BrowserDriver = ...
|
||||
_frames: dict = ...
|
||||
_drivers: Dict[str, Driver] = ...
|
||||
_all_drivers: Dict[str, Set[Driver]] = ...
|
||||
_process_id: Optional[int] = ...
|
||||
_dl_mgr: DownloadManager = ...
|
||||
_connected: bool = ...
|
||||
_lock: Lock = ...
|
||||
|
||||
def __new__(cls, address: str, browser_id: str, page: ChromiumPage): ...
|
||||
_set: Optional[BrowserSetter] = ...
|
||||
_wait: Optional[BrowserWaiter] = ...
|
||||
_timeouts: Timeout = ...
|
||||
_load_mode: str = ...
|
||||
_download_path: str = ...
|
||||
_is_exists: bool = ...
|
||||
|
||||
def __init__(self, address: str, browser_id: str, page: ChromiumPage): ...
|
||||
def __new__(cls,
|
||||
addr_or_opts: Union[str, int, ChromiumOptions] = None,
|
||||
session_options: Optional[SessionOptions] = None): ...
|
||||
|
||||
def __init__(self, addr_or_opts: Union[str, int, ChromiumOptions] = None,
|
||||
session_options: Optional[SessionOptions] = None): ...
|
||||
|
||||
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 driver(self) -> BrowserDriver: ...
|
||||
def user_data_path(self) -> str: ...
|
||||
|
||||
@property
|
||||
def process_id(self) -> Optional[int]: ...
|
||||
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
@property
|
||||
def timeouts(self) -> Timeout: ...
|
||||
|
||||
@property
|
||||
def load_mode(self) -> str: ...
|
||||
|
||||
@property
|
||||
def download_path(self) -> str: ...
|
||||
|
||||
@property
|
||||
def set(self) -> BrowserSetter: ...
|
||||
|
||||
@property
|
||||
def wait(self) -> BrowserWaiter: ...
|
||||
|
||||
@property
|
||||
def tabs_count(self) -> int: ...
|
||||
@ -43,25 +87,77 @@ class Browser(object):
|
||||
def tab_ids(self) -> List[str]: ...
|
||||
|
||||
@property
|
||||
def process_id(self) -> Optional[int]: ...
|
||||
def latest_tab(self) -> Union[ChromiumTab, str]: ...
|
||||
|
||||
def find_tabs(self, title: str = None, url: str = None,
|
||||
tab_type: Union[str, list, tuple] = None) -> List[dict]: ...
|
||||
def cookies(self, all_info: bool = False) -> CookiesList: ...
|
||||
|
||||
def close_tab(self, tab_id: str) -> None: ...
|
||||
def close_tabs(self,
|
||||
tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],
|
||||
Tuple[Union[str, ChromiumTab]]] = None,
|
||||
others: bool = False) -> None: ...
|
||||
|
||||
def stop_driver(self, driver: Driver) -> None: ...
|
||||
def get_tab(self,
|
||||
id_or_num: Union[str, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page',
|
||||
as_id: bool = False) -> Union[ChromiumTab, str]: ...
|
||||
|
||||
def activate_tab(self, tab_id: str) -> None: ...
|
||||
def get_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page',
|
||||
as_id: bool = False) -> List[ChromiumTab, str]: ...
|
||||
|
||||
def get_window_bounds(self, tab_id: str = None) -> dict: ...
|
||||
def get_mix_tab(self,
|
||||
id_or_num: Union[str, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page') -> Union[MixTab, str]: ...
|
||||
|
||||
def new_tab(self, new_window: bool = False, background: bool = False, new_context: bool = False) -> str: ...
|
||||
def get_mix_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page') -> List[MixTab, str]: ...
|
||||
|
||||
def _get_tab(self,
|
||||
id_or_num: Union[str, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page',
|
||||
mix: bool = False,
|
||||
as_id: bool = False) -> Union[ChromiumTab, str]: ...
|
||||
|
||||
def _get_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: str = 'page',
|
||||
mix: bool = False,
|
||||
as_id: bool = False) -> List[ChromiumTab, str]: ...
|
||||
|
||||
def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None: ...
|
||||
|
||||
def _new_tab(self,
|
||||
obj,
|
||||
url: str = None,
|
||||
new_window: bool = False,
|
||||
background: bool = False,
|
||||
new_context: bool = False) -> Union[ChromiumTab, MixTab]: ...
|
||||
|
||||
def new_tab(self,
|
||||
url: str = None,
|
||||
new_window: bool = False,
|
||||
background: bool = False,
|
||||
new_context: bool = False) -> ChromiumTab: ...
|
||||
|
||||
def new_mix_tab(self,
|
||||
url: str = None,
|
||||
new_window: bool = False,
|
||||
background: bool = False,
|
||||
new_context: bool = False) -> MixTab: ...
|
||||
|
||||
def reconnect(self) -> None: ...
|
||||
|
||||
def connect_to_page(self) -> None: ...
|
||||
|
||||
def _onTargetCreated(self, **kwargs) -> None: ...
|
||||
|
||||
def _onTargetDestroyed(self, **kwargs) -> None: ...
|
||||
|
@ -7,7 +7,7 @@
|
||||
"""
|
||||
from json import dumps, loads, JSONDecodeError
|
||||
from queue import Queue, Empty
|
||||
from threading import Thread, Event
|
||||
from threading import Thread
|
||||
from time import perf_counter, sleep
|
||||
|
||||
from requests import Session
|
||||
@ -15,7 +15,7 @@ from websocket import (WebSocketTimeoutException, WebSocketConnectionClosedExcep
|
||||
WebSocketException, WebSocketBadStatusException)
|
||||
|
||||
from .._functions.settings import Settings
|
||||
from ..errors import PageDisconnectedError
|
||||
from ..errors import PageDisconnectedError, BrowserConnectError
|
||||
|
||||
|
||||
class Driver(object):
|
||||
@ -30,6 +30,7 @@ class Driver(object):
|
||||
self.address = address
|
||||
self.type = tab_type
|
||||
self.owner = owner
|
||||
# self._debug = True
|
||||
# self._debug = False
|
||||
self.alert_flag = False # 标记alert出现,跳过一条请求后复原
|
||||
|
||||
@ -43,7 +44,7 @@ class Driver(object):
|
||||
self._handle_event_th.daemon = True
|
||||
self._handle_immediate_event_th = None
|
||||
|
||||
self._stopped = Event()
|
||||
self.is_running = False
|
||||
|
||||
self.event_handlers = {}
|
||||
self.immediate_event_handlers = {}
|
||||
@ -86,7 +87,7 @@ class Driver(object):
|
||||
self.method_results.pop(ws_id, None)
|
||||
return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'}
|
||||
|
||||
while not self._stopped.is_set():
|
||||
while self.is_running:
|
||||
try:
|
||||
result = self.method_results[ws_id].get(timeout=.2)
|
||||
self.method_results.pop(ws_id, None)
|
||||
@ -107,7 +108,7 @@ class Driver(object):
|
||||
|
||||
def _recv_loop(self):
|
||||
"""接收浏览器信息的守护线程方法"""
|
||||
while not self._stopped.is_set():
|
||||
while self.is_running:
|
||||
try:
|
||||
# self._ws.settimeout(1)
|
||||
msg_json = self._ws.recv()
|
||||
@ -145,7 +146,7 @@ class Driver(object):
|
||||
|
||||
def _handle_event_loop(self):
|
||||
"""当接收到浏览器信息,执行已绑定的方法"""
|
||||
while not self._stopped.is_set():
|
||||
while self.is_running:
|
||||
try:
|
||||
event = self.event_queue.get(timeout=1)
|
||||
except Empty:
|
||||
@ -158,7 +159,7 @@ class Driver(object):
|
||||
self.event_queue.task_done()
|
||||
|
||||
def _handle_immediate_event_loop(self):
|
||||
while not self._stopped.is_set() and not self.immediate_event_queue.empty():
|
||||
while not self.immediate_event_queue.empty():
|
||||
function, kwargs = self.immediate_event_queue.get(timeout=1)
|
||||
try:
|
||||
function(**kwargs)
|
||||
@ -183,7 +184,7 @@ class Driver(object):
|
||||
:param kwargs: cdp参数
|
||||
:return: 执行结果
|
||||
"""
|
||||
if self._stopped.is_set():
|
||||
if not self.is_running:
|
||||
return {'error': 'connection disconnected', 'type': 'connection_error'}
|
||||
|
||||
timeout = kwargs.pop('_timeout', Settings.cdp_timeout)
|
||||
@ -191,13 +192,13 @@ class Driver(object):
|
||||
if 'result' not in result and 'error' in result:
|
||||
kwargs['_timeout'] = timeout
|
||||
return {'error': result['error']['message'], 'type': result.get('type', 'call_method_error'),
|
||||
'method': _method, 'args': kwargs}
|
||||
'method': _method, 'args': kwargs, 'data': result['error'].get('data')}
|
||||
else:
|
||||
return result['result']
|
||||
|
||||
def start(self):
|
||||
"""启动连接"""
|
||||
self._stopped.clear()
|
||||
self.is_running = True
|
||||
try:
|
||||
self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True)
|
||||
except WebSocketBadStatusException as e:
|
||||
@ -205,6 +206,8 @@ class Driver(object):
|
||||
raise RuntimeError('请升级websocket-client库。')
|
||||
else:
|
||||
return
|
||||
except ConnectionRefusedError:
|
||||
raise BrowserConnectError('浏览器未开启或已关闭。')
|
||||
self._recv_th.start()
|
||||
self._handle_event_th.start()
|
||||
return True
|
||||
@ -218,15 +221,24 @@ class Driver(object):
|
||||
|
||||
def _stop(self):
|
||||
"""中断连接"""
|
||||
if self._stopped.is_set():
|
||||
if not self.is_running:
|
||||
return False
|
||||
|
||||
self._stopped.set()
|
||||
self.is_running = False
|
||||
if self._ws:
|
||||
self._ws.close()
|
||||
self._ws = None
|
||||
|
||||
# try:
|
||||
# while not self.immediate_event_queue.empty():
|
||||
# function, kwargs = self.immediate_event_queue.get_nowait()
|
||||
# try:
|
||||
# function(**kwargs)
|
||||
# except PageDisconnectedError:
|
||||
# raise
|
||||
# pass
|
||||
# sleep(.1)
|
||||
#
|
||||
# while not self.event_queue.empty():
|
||||
# event = self.event_queue.get_nowait()
|
||||
# function = self.event_handlers.get(event['method'])
|
||||
|
@ -6,13 +6,13 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from queue import Queue
|
||||
from threading import Thread, Event
|
||||
from threading import Thread
|
||||
from typing import Union, Callable, Dict, Optional
|
||||
|
||||
from requests import Response, Session
|
||||
from websocket import WebSocket
|
||||
|
||||
from .browser import Browser
|
||||
from .browser import Chromium
|
||||
|
||||
|
||||
class GenericAttr(object):
|
||||
@ -35,7 +35,8 @@ class Driver(object):
|
||||
_recv_th: Thread
|
||||
_handle_event_th: Thread
|
||||
_handle_immediate_event_th: Optional[Thread]
|
||||
_stopped: Event
|
||||
# _stopped: Event
|
||||
is_running: bool
|
||||
event_handlers: dict
|
||||
immediate_event_handlers: dict
|
||||
method_results: dict
|
||||
@ -67,11 +68,11 @@ class Driver(object):
|
||||
|
||||
class BrowserDriver(Driver):
|
||||
BROWSERS: Dict[str, Driver] = ...
|
||||
owner: Browser = ...
|
||||
owner: Chromium = ...
|
||||
_control_session: Session = ...
|
||||
|
||||
def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Browser): ...
|
||||
def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Chromium): ...
|
||||
|
||||
def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser): ...
|
||||
def __init__(self, tab_id: str, tab_type: str, address: str, owner: Chromium): ...
|
||||
|
||||
def get(self, url) -> Response: ...
|
||||
|
@ -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
|
||||
@ -33,10 +33,10 @@ class ChromiumOptions(object):
|
||||
self.ini_path = str(ini_path)
|
||||
else:
|
||||
self.ini_path = str(Path(__file__).parent / 'configs.ini')
|
||||
om = OptionsManager(ini_path)
|
||||
|
||||
om = OptionsManager(ini_path)
|
||||
options = om.chromium_options
|
||||
self._download_path = om.paths.get('download_path', None) or None
|
||||
self._download_path = om.paths.get('download_path', '.') or '.'
|
||||
self._tmp_path = om.paths.get('tmp_path', None) or None
|
||||
self._arguments = options.get('arguments', [])
|
||||
self._browser_path = options.get('browser_path', '')
|
||||
@ -47,6 +47,11 @@ 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)
|
||||
self._new_env = options.get('new_env', 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 +169,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 +194,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):
|
||||
@ -196,14 +214,14 @@ class ChromiumOptions(object):
|
||||
:param value: 设置项名,有值的设置项传入设置名称即可
|
||||
:return: 当前对象
|
||||
"""
|
||||
del_list = []
|
||||
elements_to_delete = [arg for arg in self._arguments if arg == value or arg.startswith(f'{value}=')]
|
||||
if not elements_to_delete:
|
||||
return self
|
||||
|
||||
for argument in self._arguments:
|
||||
if argument == value or argument.startswith(f'{value}='):
|
||||
del_list.append(argument)
|
||||
|
||||
for del_arg in del_list:
|
||||
self._arguments.remove(del_arg)
|
||||
if len(elements_to_delete) == 1:
|
||||
self._arguments.remove(elements_to_delete[0])
|
||||
else:
|
||||
self._arguments = [arg for arg in self._arguments if arg not in elements_to_delete]
|
||||
|
||||
return self
|
||||
|
||||
@ -282,14 +300,13 @@ class ChromiumOptions(object):
|
||||
self._prefs = {}
|
||||
return self
|
||||
|
||||
def set_timeouts(self, base=None, page_load=None, script=None, implicit=None):
|
||||
def set_timeouts(self, base=None, page_load=None, script=None):
|
||||
"""设置超时时间,单位为秒
|
||||
:param base: 默认超时时间
|
||||
:param page_load: 页面加载超时时间
|
||||
:param script: 脚本运行超时时间
|
||||
:return: 当前对象
|
||||
"""
|
||||
base = base if base is not None else implicit
|
||||
if base is not None:
|
||||
self._timeouts['base'] = base
|
||||
if page_load is not None:
|
||||
@ -313,7 +330,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):
|
||||
@ -348,6 +365,14 @@ class ChromiumOptions(object):
|
||||
on_off = None if on_off else False
|
||||
return self.set_argument('--incognito', on_off)
|
||||
|
||||
def new_env(self, on_off=True):
|
||||
"""设置是否使用全新浏览器环境
|
||||
:param on_off: 开或关
|
||||
:return: 当前对象
|
||||
"""
|
||||
self._new_env = on_off
|
||||
return self
|
||||
|
||||
def ignore_certificate_errors(self, on_off=True):
|
||||
"""设置是否忽略证书错误
|
||||
:param on_off: 开或关
|
||||
@ -450,7 +475,7 @@ class ChromiumOptions(object):
|
||||
:param path: 下载路径
|
||||
:return: 当前对象
|
||||
"""
|
||||
self._download_path = str(path)
|
||||
self._download_path = '.' if path is None else str(path)
|
||||
return self
|
||||
|
||||
def set_tmp_path(self, path):
|
||||
@ -488,17 +513,14 @@ class ChromiumOptions(object):
|
||||
self._system_user_path = on_off
|
||||
return self
|
||||
|
||||
def auto_port(self, on_off=True, tmp_path=None, scope=None):
|
||||
def auto_port(self, on_off=True, scope=None):
|
||||
"""自动获取可用端口
|
||||
:param on_off: 是否开启自动获取端口号
|
||||
:param tmp_path: 临时文件保存路径,为None时保存到系统临时文件夹,on_off为False时此参数无效
|
||||
:param scope: 指定端口范围,不含最后的数字,为None则使用[9600-19600)
|
||||
:param scope: 指定端口范围,不含最后的数字,为None则使用[9600-59600)
|
||||
:return: 当前对象
|
||||
"""
|
||||
if on_off:
|
||||
self._auto_port = scope if scope else True
|
||||
if tmp_path:
|
||||
self._tmp_path = str(tmp_path)
|
||||
self._auto_port = scope if scope else (9600, 59600)
|
||||
else:
|
||||
self._auto_port = False
|
||||
return self
|
||||
@ -537,7 +559,7 @@ class ChromiumOptions(object):
|
||||
|
||||
# 设置chromium_options
|
||||
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', 'new_env')
|
||||
for i in attrs:
|
||||
om.set_item('chromium_options', i, self.__getattribute__(f'_{i}'))
|
||||
# 设置代理
|
||||
|
@ -10,30 +10,32 @@ 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 = ...
|
||||
_new_env: bool = ...
|
||||
clear_file_flags: bool = ...
|
||||
_auto_port: Union[Tuple[int, int], False] = ...
|
||||
_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 +91,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: ...
|
||||
@ -132,6 +137,8 @@ class ChromiumOptions(object):
|
||||
|
||||
def incognito(self, on_off: bool = True) -> ChromiumOptions: ...
|
||||
|
||||
def new_env(self, on_off: bool = True) -> ChromiumOptions: ...
|
||||
|
||||
def set_user_agent(self, user_agent: str) -> ChromiumOptions: ...
|
||||
|
||||
def set_proxy(self, proxy: str) -> ChromiumOptions: ...
|
||||
@ -162,7 +169,6 @@ class ChromiumOptions(object):
|
||||
|
||||
def auto_port(self,
|
||||
on_off: bool = True,
|
||||
tmp_path: Union[str, Path] = None,
|
||||
scope: Tuple[int, int] = None) -> ChromiumOptions: ...
|
||||
|
||||
def existing_only(self, on_off: bool = True) -> ChromiumOptions: ...
|
||||
|
@ -14,6 +14,7 @@ user = Default
|
||||
auto_port = False
|
||||
system_user_path = False
|
||||
existing_only = False
|
||||
new_env = False
|
||||
|
||||
[session_options]
|
||||
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'}
|
||||
|
@ -64,6 +64,7 @@ class OptionsManager(object):
|
||||
self.set_item('chromium_options', 'auto_port', 'False')
|
||||
self.set_item('chromium_options', 'system_user_path', 'False')
|
||||
self.set_item('chromium_options', 'existing_only', 'False')
|
||||
self.set_item('chromium_options', 'new_env', 'False')
|
||||
self.set_item('session_options', '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"
|
||||
|
@ -12,7 +12,8 @@ from requests import Session
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .options_manage import OptionsManager
|
||||
from .._functions.web import cookies_to_tuple, set_session_cookies, format_headers
|
||||
from .._functions.cookies import cookies_to_tuple, set_session_cookies
|
||||
from .._functions.web import format_headers
|
||||
|
||||
|
||||
class SessionOptions(object):
|
||||
@ -24,7 +25,7 @@ class SessionOptions(object):
|
||||
:param ini_path: ini文件路径
|
||||
"""
|
||||
self.ini_path = None
|
||||
self._download_path = None
|
||||
self._download_path = '.'
|
||||
self._timeout = 10
|
||||
self._del_set = set() # 记录要从ini文件删除的参数
|
||||
|
||||
@ -83,7 +84,7 @@ class SessionOptions(object):
|
||||
|
||||
self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None))
|
||||
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', '.') or '.'
|
||||
|
||||
others = om.others
|
||||
self._retry_times = others.get('retry_times', 3)
|
||||
@ -100,7 +101,7 @@ class SessionOptions(object):
|
||||
:param path: 下载路径
|
||||
:return: 返回当前对象
|
||||
"""
|
||||
self._download_path = str(path)
|
||||
self._download_path = '.' if path is None else str(path)
|
||||
return self
|
||||
|
||||
@property
|
||||
@ -419,7 +420,7 @@ class SessionOptions(object):
|
||||
return session_options_to_dict(self)
|
||||
|
||||
def make_session(self):
|
||||
"""根据内在的配置生成Session对象,ua从对象中分离"""
|
||||
"""根据内在的配置生成Session对象,headers从对象中分离"""
|
||||
s = Session()
|
||||
h = CaseInsensitiveDict(self.headers) if self.headers else CaseInsensitiveDict()
|
||||
|
||||
|
@ -16,10 +16,10 @@ from DataRecorder.tools import get_usable_path, make_valid_name
|
||||
from .none_element import NoneElement
|
||||
from .session_element import make_session_ele
|
||||
from .._base.base import DrissionElement, BaseElement
|
||||
from .._functions.elements import ChromiumElementsList, SessionElementsList
|
||||
from .._functions.keys import input_text_or_keys
|
||||
from .._functions.locator import get_loc, locator_to_tuple
|
||||
from .._functions.elements import ChromiumElementsList
|
||||
from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll, get_blob
|
||||
from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, get_blob
|
||||
from .._units.clicker import Clicker
|
||||
from .._units.rect import ElementRect
|
||||
from .._units.scroller import ElementScroller
|
||||
@ -44,7 +44,7 @@ class ChromiumElement(DrissionElement):
|
||||
:param backend_id: backend id
|
||||
"""
|
||||
super().__init__(owner)
|
||||
self.tab = self.owner.tab
|
||||
self.tab = self.owner._tab
|
||||
self._select = None
|
||||
self._scroll = None
|
||||
self._rect = None
|
||||
@ -95,29 +95,29 @@ class ChromiumElement(DrissionElement):
|
||||
def tag(self):
|
||||
"""返回元素tag"""
|
||||
if self._tag is None:
|
||||
self._tag = self.owner.run_cdp('DOM.describeNode',
|
||||
self._tag = self.owner._run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._backend_id)['node']['localName'].lower()
|
||||
return self._tag
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
"""返回元素outerHTML文本"""
|
||||
return self.owner.run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML']
|
||||
return self.owner._run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML']
|
||||
|
||||
@property
|
||||
def inner_html(self):
|
||||
"""返回元素innerHTML文本"""
|
||||
return self.run_js('return this.innerHTML;')
|
||||
return self._run_js('return this.innerHTML;')
|
||||
|
||||
@property
|
||||
def attrs(self):
|
||||
"""返回元素所有attribute属性"""
|
||||
try:
|
||||
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
|
||||
except ElementLostError:
|
||||
self._refresh_id()
|
||||
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
|
||||
except CDPError: # 文档根元素不能调用此方法
|
||||
return {}
|
||||
@ -162,18 +162,18 @@ class ChromiumElement(DrissionElement):
|
||||
return self._rect
|
||||
|
||||
@property
|
||||
def shadow_root(self):
|
||||
def sr(self):
|
||||
"""返回当前元素的shadow_root元素对象"""
|
||||
info = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
info = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
if not info.get('shadowRoots', None):
|
||||
return None
|
||||
|
||||
return ShadowRoot(self, backend_id=info['shadowRoots'][0]['backendNodeId'])
|
||||
|
||||
@property
|
||||
def sr(self):
|
||||
def shadow_root(self):
|
||||
"""返回当前元素的shadow_root元素对象"""
|
||||
return self.shadow_root
|
||||
return self.sr
|
||||
|
||||
@property
|
||||
def scroll(self):
|
||||
@ -193,7 +193,7 @@ class ChromiumElement(DrissionElement):
|
||||
def wait(self):
|
||||
"""返回用于等待的对象"""
|
||||
if self._wait is None:
|
||||
self._wait = ElementWaiter(self.owner, self)
|
||||
self._wait = ElementWaiter(self)
|
||||
return self._wait
|
||||
|
||||
@property
|
||||
@ -225,8 +225,8 @@ class ChromiumElement(DrissionElement):
|
||||
elif not is_checked and not uncheck:
|
||||
js = 'this.checked=true'
|
||||
if js:
|
||||
self.run_js(js)
|
||||
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
self._run_js(js)
|
||||
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
|
||||
else:
|
||||
if (is_checked and uncheck) or (not is_checked and not uncheck):
|
||||
@ -351,20 +351,56 @@ class ChromiumElement(DrissionElement):
|
||||
else:
|
||||
return NoneElement(page=self.owner, method='on()', args={'timeout': timeout})
|
||||
|
||||
def offset(self, offset_x, offset_y):
|
||||
"""获取相对本元素左上角左边指定偏移量位置的元素
|
||||
:param offset_x: 横坐标偏移量,向右为正
|
||||
:param offset_y: 纵坐标偏移量,向下为正
|
||||
def offset(self, locator=None, x=None, y=None, timeout=None):
|
||||
"""获取相对本元素左上角左边指定偏移量位置的元素,如果offset_x和offset_y都是None,定位到元素中间点
|
||||
:param locator: 定位符,只支持str,且不支持xpath和css方式
|
||||
:param x: 横坐标偏移量,向右为正
|
||||
:param y: 纵坐标偏移量,向下为正
|
||||
:param timeout: 超时时间(秒),为None使用所在页面设置
|
||||
:return: 元素对象
|
||||
"""
|
||||
x, y = self.rect.location
|
||||
if locator and not (isinstance(locator, str) and not locator.startswith(
|
||||
('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css='))):
|
||||
raise ValueError('locator参数只能是str格式且不支持xpath和css形式。')
|
||||
|
||||
if x == y is None:
|
||||
x, y = self.rect.midpoint
|
||||
x = int(x)
|
||||
y = int(y)
|
||||
else:
|
||||
nx, ny = self.rect.location
|
||||
nx += x if x else 0
|
||||
ny += y if y else 0
|
||||
x = int(nx)
|
||||
y = int(ny)
|
||||
loc_data = locator_to_tuple(locator) if locator else None
|
||||
timeout = timeout if timeout is not None else self.owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
try:
|
||||
return ChromiumElement(owner=self.owner,
|
||||
backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x,
|
||||
y=y + offset_y, includeUserAgentShadowDOM=True,
|
||||
ele = ChromiumElement(owner=self.owner,
|
||||
backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y,
|
||||
includeUserAgentShadowDOM=True,
|
||||
ignorePointerEventsNone=False)['backendNodeId'])
|
||||
except CDPError:
|
||||
return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y})
|
||||
ele = False
|
||||
if ele and (loc_data is None or _check_ele(ele, loc_data)):
|
||||
return ele
|
||||
|
||||
while perf_counter() < end_time:
|
||||
try:
|
||||
ele = ChromiumElement(owner=self.owner,
|
||||
backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y,
|
||||
includeUserAgentShadowDOM=True,
|
||||
ignorePointerEventsNone=False)['backendNodeId'])
|
||||
except CDPError:
|
||||
ele = False
|
||||
|
||||
if ele and (loc_data is None or _check_ele(ele, loc_data)):
|
||||
return ele
|
||||
sleep(.1)
|
||||
|
||||
return NoneElement(page=self.owner, method='offset()',
|
||||
args={'locator': locator, 'offset_x': x, 'offset_y': y, 'timeout': timeout})
|
||||
|
||||
def east(self, loc_or_pixel=None, index=1):
|
||||
"""获取元素右边某个指定元素
|
||||
@ -439,7 +475,7 @@ class ChromiumElement(DrissionElement):
|
||||
cdp_data[variable] += locator
|
||||
try:
|
||||
return ChromiumElement(owner=self.owner,
|
||||
backend_id=self.owner.run_cdp('DOM.getNodeForLocation',
|
||||
backend_id=self.owner._run_cdp('DOM.getNodeForLocation',
|
||||
**cdp_data)['backendNodeId'])
|
||||
except CDPError:
|
||||
return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})
|
||||
@ -453,7 +489,7 @@ class ChromiumElement(DrissionElement):
|
||||
while 0 < cdp_data[variable] < max_len:
|
||||
cdp_data[variable] += value
|
||||
try:
|
||||
bid = self.owner.run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId']
|
||||
bid = self.owner._run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId']
|
||||
if bid == curr_ele:
|
||||
continue
|
||||
else:
|
||||
@ -505,7 +541,7 @@ class ChromiumElement(DrissionElement):
|
||||
:param name: 属性名
|
||||
:return: None
|
||||
"""
|
||||
self.run_js(f'this.removeAttribute("{name}");')
|
||||
self._run_js(f'this.removeAttribute("{name}");')
|
||||
|
||||
def property(self, name):
|
||||
"""获取一个property属性值
|
||||
@ -513,12 +549,22 @@ class ChromiumElement(DrissionElement):
|
||||
:return: 属性值文本
|
||||
"""
|
||||
try:
|
||||
value = self.run_js(f'return this.{name};')
|
||||
value = self._run_js(f'return this.{name};')
|
||||
return format_html(value) if isinstance(value, str) else value
|
||||
except:
|
||||
return None
|
||||
|
||||
def run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""对本元素执行javascript代码
|
||||
:param script: js文本,文本中用this表示本元素
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
:param as_expr: 是否作为表达式运行,为True时args无效
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script设置
|
||||
:return: 运行的结果
|
||||
"""
|
||||
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def _run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""对本元素执行javascript代码
|
||||
:param script: js文本,文本中用this表示本元素
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
@ -554,27 +600,32 @@ class ChromiumElement(DrissionElement):
|
||||
"""
|
||||
return self._ele(locator, timeout=timeout, index=None)
|
||||
|
||||
def s_ele(self, locator=None, index=1):
|
||||
def s_ele(self, locator=None, index=1, timeout=None):
|
||||
"""查找一个符合条件的元素,以SessionElement形式返回
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param index: 获取第几个,从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
return make_session_ele(self, locator, index=index, method='s_ele()')
|
||||
return (make_session_ele(self, locator, index=index, method='s_ele()')
|
||||
if self.ele(locator, index=index, timeout=timeout)
|
||||
else NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index}))
|
||||
|
||||
def s_eles(self, locator=None):
|
||||
def s_eles(self, locator=None, timeout=None):
|
||||
"""查找所有符合条件的元素,以SessionElement列表形式返回
|
||||
:param locator: 定位符
|
||||
:param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致
|
||||
:return: SessionElement或属性、文本组成的列表
|
||||
"""
|
||||
return make_session_ele(self, locator, index=None)
|
||||
return (make_session_ele(self, locator, index=None)
|
||||
if self.ele(locator, timeout=timeout) else SessionElementsList())
|
||||
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
"""返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: ChromiumElement对象或文本、属性或其组成的列表
|
||||
"""
|
||||
@ -588,7 +639,7 @@ class ChromiumElement(DrissionElement):
|
||||
"""
|
||||
if pseudo_ele:
|
||||
pseudo_ele = f', "{pseudo_ele}"' if pseudo_ele.startswith(':') else f', "::{pseudo_ele}"'
|
||||
return self.run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");')
|
||||
return self._run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");')
|
||||
|
||||
def src(self, timeout=None, base64_to_bytes=True):
|
||||
"""返回元素src资源,base64的可转为bytes返回,其它返回str
|
||||
@ -602,7 +653,7 @@ class ChromiumElement(DrissionElement):
|
||||
'&& this.naturalWidth > 0 && typeof this.naturalHeight != "undefined" '
|
||||
'&& this.naturalHeight > 0')
|
||||
end_time = perf_counter() + timeout
|
||||
while not self.run_js(js) and perf_counter() < end_time:
|
||||
while not self._run_js(js) and perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
|
||||
src = self.attr('src')
|
||||
@ -631,11 +682,11 @@ class ChromiumElement(DrissionElement):
|
||||
if not src:
|
||||
continue
|
||||
|
||||
node = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
node = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
frame = node.get('frameId', None) or self.owner._frame_id
|
||||
|
||||
try:
|
||||
result = self.owner.run_cdp('Page.getResourceContent', frameId=frame, url=src)
|
||||
result = self.owner._run_cdp('Page.getResourceContent', frameId=frame, url=src)
|
||||
break
|
||||
except CDPError:
|
||||
pass
|
||||
@ -698,7 +749,7 @@ class ChromiumElement(DrissionElement):
|
||||
js = ('return this.complete && typeof this.naturalWidth != "undefined" && this.naturalWidth > 0 '
|
||||
'&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0')
|
||||
end_time = perf_counter() + self.owner.timeout
|
||||
while not self.run_js(js) and perf_counter() < end_time:
|
||||
while not self._run_js(js) and perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
if scroll_to_center:
|
||||
self.scroll.to_see(center=True)
|
||||
@ -729,7 +780,7 @@ class ChromiumElement(DrissionElement):
|
||||
if isinstance(vals, (list, tuple)):
|
||||
vals = ''.join([str(i) for i in vals])
|
||||
self.set.property('value', str(vals))
|
||||
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
return
|
||||
|
||||
self.wait.clickable(wait_moved=False, timeout=.5)
|
||||
@ -738,7 +789,11 @@ class ChromiumElement(DrissionElement):
|
||||
else:
|
||||
self._input_focus()
|
||||
|
||||
if isinstance(vals, str) and vals not in ('\ue003', '\ue017', '\ue010', '\ue011',
|
||||
'\ue012', '\ue013', '\ue014', '\ue015',):
|
||||
input_text_or_keys(self.owner, vals)
|
||||
else:
|
||||
self.owner.actions.type(vals)
|
||||
|
||||
def clear(self, by_js=False):
|
||||
"""清空元素文本
|
||||
@ -746,8 +801,8 @@ class ChromiumElement(DrissionElement):
|
||||
:return: None
|
||||
"""
|
||||
if by_js:
|
||||
self.run_js("this.value='';")
|
||||
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
self._run_js("this.value='';")
|
||||
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
|
||||
return
|
||||
|
||||
self._input_focus()
|
||||
@ -756,26 +811,24 @@ class ChromiumElement(DrissionElement):
|
||||
def _input_focus(self):
|
||||
"""输入前使元素获取焦点"""
|
||||
try:
|
||||
self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id)
|
||||
self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)
|
||||
except Exception:
|
||||
self.click(by_js=None)
|
||||
|
||||
def focus(self):
|
||||
"""使元素获取焦点"""
|
||||
try:
|
||||
self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id)
|
||||
self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)
|
||||
except Exception:
|
||||
self.run_js('this.focus();')
|
||||
self._run_js('this.focus();')
|
||||
|
||||
def hover(self, offset_x=None, offset_y=None):
|
||||
"""鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入x或y值时悬停在元素中点
|
||||
"""鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入offset_x和offset_y值时悬停在元素中点
|
||||
:param offset_x: 相对元素左上角坐标的x轴偏移量
|
||||
:param offset_y: 相对元素左上角坐标的y轴偏移量
|
||||
:return: None
|
||||
"""
|
||||
self.owner.scroll.to_see(self)
|
||||
x, y = offset_scroll(self, offset_x, offset_y)
|
||||
self.owner.run_cdp('Input.dispatchMouseEvent', type='mouseMoved', x=x, y=y, _ignore=AlertExistsError)
|
||||
self.owner.actions.move_to(self, offset_x=offset_x, offset_y=offset_y, duration=.1)
|
||||
|
||||
def drag(self, offset_x=0, offset_y=0, duration=.5):
|
||||
"""拖拽当前元素到相对位置
|
||||
@ -808,9 +861,9 @@ class ChromiumElement(DrissionElement):
|
||||
:return: js中的object id
|
||||
"""
|
||||
if node_id:
|
||||
return self.owner.run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId']
|
||||
return self.owner._run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId']
|
||||
else:
|
||||
return self.owner.run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId']
|
||||
return self.owner._run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId']
|
||||
|
||||
def _get_node_id(self, obj_id=None, backend_id=None):
|
||||
"""根据传入object id或backend id获取cdp中的node id
|
||||
@ -819,9 +872,9 @@ class ChromiumElement(DrissionElement):
|
||||
:return: cdp中的node id
|
||||
"""
|
||||
if obj_id:
|
||||
return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
|
||||
return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
|
||||
else:
|
||||
n = self.owner.run_cdp('DOM.describeNode', backendNodeId=backend_id)['node']
|
||||
n = self.owner._run_cdp('DOM.describeNode', backendNodeId=backend_id)['node']
|
||||
self._tag = n['localName']
|
||||
return n['nodeId']
|
||||
|
||||
@ -830,7 +883,7 @@ class ChromiumElement(DrissionElement):
|
||||
:param node_id:
|
||||
:return: backend id
|
||||
"""
|
||||
n = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node']
|
||||
n = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']
|
||||
self._tag = n['localName']
|
||||
return n['backendNodeId']
|
||||
|
||||
@ -850,7 +903,11 @@ class ChromiumElement(DrissionElement):
|
||||
txt5 = '''return path;'''
|
||||
|
||||
elif mode == 'css':
|
||||
txt1 = ''
|
||||
txt1 = '''
|
||||
let i = el.getAttribute("id");
|
||||
if (i){path = '>' + el.tagName.toLowerCase() + "#" + i + path;
|
||||
break;}
|
||||
'''
|
||||
txt3 = ''
|
||||
txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;'''
|
||||
txt5 = '''return path.substr(1);'''
|
||||
@ -860,6 +917,7 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
js = '''function(){
|
||||
function e(el) {
|
||||
//return el;
|
||||
if (!(el instanceof Element)) return;
|
||||
let path = '';
|
||||
while (el.nodeType === Node.ELEMENT_NODE) {
|
||||
@ -876,7 +934,7 @@ class ChromiumElement(DrissionElement):
|
||||
}
|
||||
return e(this);}
|
||||
'''
|
||||
t = self.run_js(js)
|
||||
t = self._run_js(js)
|
||||
return f'{t}' if mode == 'css' else t
|
||||
|
||||
def _set_file_input(self, files):
|
||||
@ -887,7 +945,7 @@ class ChromiumElement(DrissionElement):
|
||||
if isinstance(files, str):
|
||||
files = files.split('\n')
|
||||
files = [str(Path(i).absolute()) for i in files]
|
||||
self.owner.run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id)
|
||||
self.owner._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id)
|
||||
|
||||
|
||||
class ShadowRoot(BaseElement):
|
||||
@ -900,7 +958,7 @@ class ShadowRoot(BaseElement):
|
||||
:param backend_id: cdp中的backend id
|
||||
"""
|
||||
super().__init__(parent_ele.owner)
|
||||
self.tab = self.owner.tab
|
||||
self.tab = self.owner._tab
|
||||
self.parent_ele = parent_ele
|
||||
if backend_id:
|
||||
self._backend_id = backend_id
|
||||
@ -942,7 +1000,7 @@ class ShadowRoot(BaseElement):
|
||||
@property
|
||||
def inner_html(self):
|
||||
"""返回内部的html文本"""
|
||||
return self.run_js('return this.innerHTML;')
|
||||
return self._run_js('return this.innerHTML;')
|
||||
|
||||
@property
|
||||
def states(self):
|
||||
@ -952,6 +1010,16 @@ class ShadowRoot(BaseElement):
|
||||
return self._states
|
||||
|
||||
def run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
:param script: js文本
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
:param as_expr: 是否作为表达式运行,为True时args无效
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script设置
|
||||
:return: 运行的结果
|
||||
"""
|
||||
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def _run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
:param script: js文本
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
@ -1128,31 +1196,32 @@ class ShadowRoot(BaseElement):
|
||||
"""
|
||||
return self._ele(locator, timeout=timeout, index=None)
|
||||
|
||||
def s_ele(self, locator=None, index=1):
|
||||
def s_ele(self, locator=None, index=1, timeout=None):
|
||||
"""查找一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param index: 获取第几个,从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
r = make_session_ele(self, locator, index=index)
|
||||
if isinstance(r, NoneElement):
|
||||
r.method = 's_ele()'
|
||||
r.args = {'locator': locator}
|
||||
return r
|
||||
return (make_session_ele(self, locator, index=index, method='s_ele()')
|
||||
if self.ele(locator, index=index, timeout=timeout)
|
||||
else NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index}))
|
||||
|
||||
def s_eles(self, locator):
|
||||
def s_eles(self, locator, timeout=None):
|
||||
"""查找所有符合条件的元素以SessionElement列表形式返回,处理复杂页面时效率很高
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致
|
||||
:return: SessionElement对象
|
||||
"""
|
||||
return make_session_ele(self, locator, index=None)
|
||||
return (make_session_ele(self, locator, index=None)
|
||||
if self.ele(locator, timeout=timeout) else SessionElementsList())
|
||||
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
"""返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: ChromiumElement对象或其组成的列表
|
||||
"""
|
||||
@ -1163,13 +1232,13 @@ class ShadowRoot(BaseElement):
|
||||
def do_find():
|
||||
if loc[0] == 'css selector':
|
||||
if index == 1:
|
||||
nod_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId']
|
||||
nod_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId']
|
||||
if nod_id:
|
||||
r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False)
|
||||
return None if r is False else r
|
||||
|
||||
else:
|
||||
nod_ids = self.owner.run_cdp('DOM.querySelectorAll',
|
||||
nod_ids = self.owner._run_cdp('DOM.querySelectorAll',
|
||||
nodeId=self._node_id, selector=loc[1])['nodeId']
|
||||
r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False)
|
||||
return None if r is False else r
|
||||
@ -1179,17 +1248,22 @@ class ShadowRoot(BaseElement):
|
||||
if not eles:
|
||||
return None
|
||||
|
||||
css = [i.css_path[61:] for i in eles]
|
||||
css = []
|
||||
for i in eles:
|
||||
c = i.css_path
|
||||
if c.startswith('html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'):
|
||||
c = c[61:]
|
||||
css.append(c)
|
||||
if index is not None:
|
||||
try:
|
||||
node_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id,
|
||||
node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id,
|
||||
selector=css[index - 1])['nodeId']
|
||||
except IndexError:
|
||||
return None
|
||||
r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)
|
||||
return None if r is False else r
|
||||
else:
|
||||
node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
|
||||
node_ids = [self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
|
||||
for i in css]
|
||||
if 0 in node_ids:
|
||||
return None
|
||||
@ -1209,15 +1283,15 @@ class ShadowRoot(BaseElement):
|
||||
|
||||
def _get_node_id(self, obj_id):
|
||||
"""返回元素node id"""
|
||||
return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
|
||||
return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
|
||||
|
||||
def _get_obj_id(self, back_id):
|
||||
"""返回元素object id"""
|
||||
return self.owner.run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId']
|
||||
return self.owner._run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId']
|
||||
|
||||
def _get_backend_id(self, node_id):
|
||||
"""返回元素object id"""
|
||||
r = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node']
|
||||
r = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']
|
||||
self._tag = r['localName'].lower()
|
||||
return r['backendNodeId']
|
||||
|
||||
@ -1228,7 +1302,7 @@ def find_in_chromium_ele(ele, locator, index=1, timeout=None, relative=True):
|
||||
:param locator: 元素定位元组
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:param relative: WebPage用于标记是否相对定位使用
|
||||
:param relative: MixTab用于标记是否相对定位使用
|
||||
:return: 返回ChromiumElement元素或它们组成的列表
|
||||
"""
|
||||
# ---------------处理定位符---------------
|
||||
@ -1269,14 +1343,14 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
|
||||
ele.owner.wait.doc_loaded()
|
||||
|
||||
def do_find():
|
||||
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
|
||||
res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
|
||||
returnByValue=False, awaitPromise=True, userGesture=True)
|
||||
if res['result']['type'] == 'string':
|
||||
return res['result']['value']
|
||||
if 'exceptionDetails' in res:
|
||||
if 'The result is not a node set' in res['result']['description']:
|
||||
js1 = make_js_for_find_ele_by_xpath(xpath, '1', node_txt)
|
||||
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id,
|
||||
res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id,
|
||||
returnByValue=False, awaitPromise=True, userGesture=True)
|
||||
return res['result']['value']
|
||||
else:
|
||||
@ -1290,7 +1364,7 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
|
||||
return None if r is False else r
|
||||
|
||||
else:
|
||||
res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'],
|
||||
res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'],
|
||||
ownProperties=True)['result'][:-1]
|
||||
if index is None:
|
||||
r = ChromiumElementsList(page=ele.owner)
|
||||
@ -1341,7 +1415,7 @@ def find_by_css(ele, selector, index, timeout):
|
||||
ele.owner.wait.doc_loaded()
|
||||
|
||||
def do_find():
|
||||
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
|
||||
res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
|
||||
returnByValue=False, awaitPromise=True, userGesture=True)
|
||||
|
||||
if 'exceptionDetails' in res:
|
||||
@ -1354,7 +1428,7 @@ def find_by_css(ele, selector, index, timeout):
|
||||
return None if r is False else r
|
||||
|
||||
else:
|
||||
obj_ids = [i['value']['objectId'] for i in ele.owner.run_cdp('Runtime.getProperties',
|
||||
obj_ids = [i['value']['objectId'] for i in ele.owner._run_cdp('Runtime.getProperties',
|
||||
objectId=res['result']['objectId'],
|
||||
ownProperties=True)['result']]
|
||||
r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True)
|
||||
@ -1535,14 +1609,14 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None):
|
||||
end_time = perf_counter() + timeout
|
||||
try:
|
||||
if as_expr:
|
||||
res = page.run_cdp('Runtime.evaluate', expression=script, returnByValue=False,
|
||||
res = page._run_cdp('Runtime.evaluate', expression=script, returnByValue=False,
|
||||
awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)
|
||||
|
||||
else:
|
||||
args = args or ()
|
||||
if not is_js_func(script):
|
||||
script = f'function(){{{script}}}'
|
||||
res = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id,
|
||||
res = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id,
|
||||
arguments=[convert_argument(arg) for arg in args], returnByValue=False,
|
||||
awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)
|
||||
except TimeoutError:
|
||||
@ -1591,7 +1665,7 @@ def parse_js_result(page, ele, result, end_time):
|
||||
return r
|
||||
|
||||
elif sub_type == 'array':
|
||||
r = page.run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result']
|
||||
r = page._run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result']
|
||||
return [parse_js_result(page, ele, result=i['value'], end_time=end_time) for i in r if i['name'].isdigit()]
|
||||
|
||||
elif 'objectId' in result:
|
||||
@ -1599,7 +1673,7 @@ def parse_js_result(page, ele, result, end_time):
|
||||
if timeout < 0:
|
||||
return
|
||||
js = 'function(){return JSON.stringify(this);}'
|
||||
r = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'],
|
||||
r = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'],
|
||||
returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError,
|
||||
_timeout=timeout)
|
||||
return loads(parse_js_result(page, ele, r['result'], end_time))
|
||||
@ -1610,6 +1684,9 @@ def parse_js_result(page, ele, result, end_time):
|
||||
elif the_type == 'undefined':
|
||||
return None
|
||||
|
||||
elif the_type == 'function':
|
||||
return result['description']
|
||||
|
||||
else:
|
||||
return result['value']
|
||||
|
||||
|
@ -14,8 +14,8 @@ from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._pages.web_page import WebPage
|
||||
from .._pages.tabs import ChromiumTab
|
||||
from .._pages.mix_page import MixPage
|
||||
from .._units.clicker import Clicker
|
||||
from .._units.rect import ElementRect
|
||||
from .._units.scroller import ElementScroller
|
||||
@ -31,9 +31,8 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def __init__(self, owner: ChromiumBase, node_id: int = None, obj_id: str = None, backend_id: int = None):
|
||||
self._tag: str = ...
|
||||
# self.page: Union[ChromiumPage, WebPage] = ...
|
||||
self.owner: ChromiumBase = ...
|
||||
self.page: Union[ChromiumPage, WebPage] = ...
|
||||
self.page: Union[ChromiumPage, MixPage] = ...
|
||||
self.tab: Union[ChromiumPage, ChromiumTab] = ...
|
||||
self._node_id: int = ...
|
||||
self._obj_id: str = ...
|
||||
@ -169,7 +168,11 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def east(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ...
|
||||
|
||||
def offset(self, offset_x: int, offset_y: int) -> ChromiumElement: ...
|
||||
def offset(self,
|
||||
locator: Optional[str] = None,
|
||||
x: int = None,
|
||||
y: int = None,
|
||||
timeout: float = None) -> ChromiumElement: ...
|
||||
|
||||
def _get_relative_eles(self,
|
||||
mode: str = 'north',
|
||||
@ -183,7 +186,7 @@ class ChromiumElement(DrissionElement):
|
||||
def select(self) -> SelectElement: ...
|
||||
|
||||
@property
|
||||
def value(self) -> None: ...
|
||||
def value(self) -> str: ...
|
||||
|
||||
def check(self, uncheck: bool = False, by_js: bool = False) -> None: ...
|
||||
|
||||
@ -195,6 +198,8 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def _run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def run_async_js(self, script: str, *args, as_expr: bool = False) -> None: ...
|
||||
|
||||
def ele(self,
|
||||
@ -208,9 +213,12 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def s_ele(self,
|
||||
locator: Union[Tuple[str, str], str] = None,
|
||||
index: int = 1) -> SessionElement: ...
|
||||
index: int = 1,
|
||||
timeout: float = None) -> SessionElement: ...
|
||||
|
||||
def s_eles(self, locator: Union[Tuple[str, str], str] = None) -> SessionElementsList: ...
|
||||
def s_eles(self,
|
||||
locator: Union[Tuple[str, str], str] = None,
|
||||
timeout: float = None) -> SessionElementsList: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
@ -266,7 +274,6 @@ class ChromiumElement(DrissionElement):
|
||||
class ShadowRoot(BaseElement):
|
||||
|
||||
def __init__(self, parent_ele: ChromiumElement, obj_id: str = None, backend_id: int = None):
|
||||
# self.page: Union[ChromiumPage, WebPage] = ...
|
||||
self.owner: ChromiumBase = ...
|
||||
self.tab: Union[ChromiumPage, ChromiumTab] = ...
|
||||
self._obj_id: str = ...
|
||||
@ -298,6 +305,8 @@ class ShadowRoot(BaseElement):
|
||||
|
||||
def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def _run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def run_async_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> None: ...
|
||||
|
||||
def parent(self, level_or_loc: Union[str, int] = 1, index: int = 1) -> ChromiumElement: ...
|
||||
@ -337,9 +346,10 @@ class ShadowRoot(BaseElement):
|
||||
|
||||
def s_ele(self,
|
||||
locator: Union[Tuple[str, str], str] = None,
|
||||
index: int = 1) -> SessionElement: ...
|
||||
index: int = 1,
|
||||
timeout: float = None) -> SessionElement: ...
|
||||
|
||||
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
|
||||
def s_eles(self, locator: Union[Tuple[str, str], str], timeout: float = None) -> SessionElementsList: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
@ -375,7 +385,7 @@ def find_by_css(ele: ChromiumElement,
|
||||
timeout: float) -> Union[ChromiumElement, List[ChromiumElement],]: ...
|
||||
|
||||
|
||||
def make_chromium_eles(page: Union[ChromiumBase, ChromiumPage, WebPage, ChromiumTab, ChromiumFrame],
|
||||
def make_chromium_eles(page: Union[ChromiumBase, ChromiumPage, MixPage, ChromiumTab, ChromiumFrame],
|
||||
_ids: Union[tuple, list, str, int],
|
||||
index: Optional[int] = 1,
|
||||
is_obj_id: bool = True,
|
||||
|
@ -260,7 +260,7 @@ class SessionElement(DrissionElement):
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param timeout: 不起实际作用,用于和父类对应
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: SessionElement对象
|
||||
"""
|
||||
@ -276,6 +276,10 @@ class SessionElement(DrissionElement):
|
||||
|
||||
while ele:
|
||||
if mode == 'css':
|
||||
id_ = ele.attr('id')
|
||||
if id_:
|
||||
path_str = f'>{ele.tag}#{id_}{path_str}'
|
||||
break
|
||||
brothers = len(ele.eles(f'xpath:./preceding-sibling::*'))
|
||||
path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}'
|
||||
else:
|
||||
@ -349,11 +353,11 @@ def make_session_ele(html_or_ele, loc=None, index=1, method=None):
|
||||
xpath = html_or_ele.xpath
|
||||
# ChromiumElement,兼容传入的元素在iframe内的情况
|
||||
if html_or_ele._doc_id is None:
|
||||
doc = html_or_ele.run_js('return this.ownerDocument;')
|
||||
doc = html_or_ele._run_js('return this.ownerDocument;')
|
||||
html_or_ele._doc_id = doc['objectId'] if doc else False
|
||||
|
||||
if html_or_ele._doc_id:
|
||||
html = html_or_ele.owner.run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML']
|
||||
html = html_or_ele.owner._run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML']
|
||||
else:
|
||||
html = html_or_ele.owner.html
|
||||
html_or_ele = fromstring(html)
|
||||
|
@ -8,6 +8,7 @@
|
||||
from json import load, dump, JSONDecodeError
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from subprocess import Popen, DEVNULL
|
||||
from tempfile import gettempdir
|
||||
from time import perf_counter, sleep
|
||||
@ -28,17 +29,22 @@ def connect_browser(option):
|
||||
browser_path = option.browser_path
|
||||
|
||||
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
|
||||
using = port_is_using(ip, port)
|
||||
if ip != '127.0.0.1' or using or option.is_existing_only:
|
||||
if test_connect(ip, port):
|
||||
return True
|
||||
elif ip != '127.0.0.1':
|
||||
raise BrowserConnectError(f'\n{address}浏览器连接失败。')
|
||||
elif using:
|
||||
raise BrowserConnectError(f'\n{address}浏览器连接失败,请检查{port}端口是否浏览器,'
|
||||
f'且已添加\'--remote-debugging-port={port}\'启动项。')
|
||||
else: # option.is_existing_only
|
||||
raise BrowserConnectError(f'\n{address}浏览器连接失败,请确认浏览器已启动。')
|
||||
|
||||
# ----------创建浏览器进程----------
|
||||
args = get_launch_args(option)
|
||||
args, user_path = get_launch_args(option)
|
||||
if option._new_env:
|
||||
rmtree(user_path, ignore_errors=True)
|
||||
set_prefs(option)
|
||||
set_flags(option)
|
||||
try:
|
||||
@ -47,13 +53,16 @@ def connect_browser(option):
|
||||
# 传入的路径找不到,主动在ini文件、注册表、系统变量中找
|
||||
except FileNotFoundError:
|
||||
browser_path = get_chrome_path(option.ini_path)
|
||||
|
||||
if not browser_path:
|
||||
raise FileNotFoundError('无法找到浏览器可执行文件路径,请手动配置。')
|
||||
|
||||
_run_browser(port, browser_path, args)
|
||||
|
||||
test_connect(ip, port)
|
||||
if not test_connect(ip, port):
|
||||
raise BrowserConnectError(f'\n{address}浏览器连接失败。\n请确认:\n'
|
||||
f'1、用户文件夹没有和已打开的浏览器冲突\n'
|
||||
f'2、如为无界面系统,请添加\'--headless=new\'启动参数\n'
|
||||
f'3、如果是Linux系统,尝试添加\'--no-sandbox\'启动参数\n'
|
||||
f'可使用ChromiumOptions设置端口和用户文件夹路径。')
|
||||
return False
|
||||
|
||||
|
||||
@ -64,44 +73,26 @@ def get_launch_args(opt):
|
||||
"""
|
||||
# ----------处理arguments-----------
|
||||
result = set()
|
||||
has_user_path = False
|
||||
headless = None
|
||||
user_path = False
|
||||
for i in opt.arguments:
|
||||
if i.startswith(('--load-extension=', '--remote-debugging-port=')):
|
||||
continue
|
||||
elif i.startswith('--user-data-dir') and not opt.system_user_path:
|
||||
result.add(f'--user-data-dir={Path(i[16:]).absolute()}')
|
||||
has_user_path = True
|
||||
user_path = f'--user-data-dir={Path(i[16:]).absolute()}'
|
||||
result.add(user_path)
|
||||
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:
|
||||
if not user_path and not opt.system_user_path:
|
||||
port = opt.address.split(':')[-1] if opt.address else '0'
|
||||
p = Path(opt.tmp_path) if opt.tmp_path else Path(gettempdir()) / 'DrissionPage'
|
||||
path = p / f'userData_{port}'
|
||||
path = p / 'userData' / port
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
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')
|
||||
user_path = path.absolute()
|
||||
opt.set_user_data_path(user_path)
|
||||
result.add(f'--user-data-dir={user_path}')
|
||||
|
||||
result = list(result)
|
||||
opt._headless = headless
|
||||
|
||||
# ----------处理插件extensions-------------
|
||||
ext = [str(Path(e).absolute()) for e in opt.extensions]
|
||||
@ -110,7 +101,7 @@ def get_launch_args(opt):
|
||||
ext = f'--load-extension={ext}'
|
||||
result.append(ext)
|
||||
|
||||
return result
|
||||
return result, user_path
|
||||
|
||||
|
||||
def set_prefs(opt):
|
||||
@ -208,18 +199,13 @@ def test_connect(ip, port, timeout=30):
|
||||
if tab['type'] in ('page', 'webview'):
|
||||
r.close()
|
||||
s.close()
|
||||
return
|
||||
return True
|
||||
r.close()
|
||||
except Exception:
|
||||
sleep(.2)
|
||||
|
||||
s.close()
|
||||
raise BrowserConnectError(f'\n{ip}:{port}浏览器无法链接。\n请确认:\n1、该端口为浏览器\n'
|
||||
f'2、已添加\'--remote-debugging-port={port}\'启动项\n'
|
||||
f'3、用户文件夹没有和已打开的浏览器冲突\n'
|
||||
f'4、如为无界面系统,请添加\'--headless=new\'参数\n'
|
||||
f'5、如果是Linux系统,可能还要添加\'--no-sandbox\'启动参数\n'
|
||||
f'可使用ChromiumOptions设置端口和用户文件夹路径。')
|
||||
return False
|
||||
|
||||
|
||||
def _run_browser(port, path: str, args) -> Popen:
|
||||
|
@ -22,7 +22,7 @@ def set_prefs(opt: ChromiumOptions) -> None: ...
|
||||
def set_flags(opt: ChromiumOptions) -> None: ...
|
||||
|
||||
|
||||
def test_connect(ip: str, port: Union[int, str], timeout: float = 30) -> None: ...
|
||||
def test_connect(ip: str, port: Union[int, str], timeout: float = 30) -> bool: ...
|
||||
|
||||
|
||||
def get_chrome_path(ini_path: str) -> Union[str, None]: ...
|
||||
|
234
DrissionPage/_functions/cookies.py
Normal file
234
DrissionPage/_functions/cookies.py
Normal file
@ -0,0 +1,234 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Author : g1879
|
||||
@Contact : g1879@qq.com
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
|
||||
from tldextract import extract
|
||||
|
||||
|
||||
def cookie_to_dict(cookie):
|
||||
"""把Cookie对象转为dict格式
|
||||
:param cookie: Cookie对象、字符串或字典
|
||||
:return: cookie字典
|
||||
"""
|
||||
if isinstance(cookie, Cookie):
|
||||
cookie_dict = cookie.__dict__.copy()
|
||||
cookie_dict.pop('rfc2109', None)
|
||||
cookie_dict.pop('_rest', None)
|
||||
return cookie_dict
|
||||
|
||||
elif isinstance(cookie, dict):
|
||||
cookie_dict = cookie
|
||||
|
||||
elif isinstance(cookie, str):
|
||||
cookie_dict = {}
|
||||
for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):
|
||||
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
else:
|
||||
cookie_dict['name'] = attr_val[0]
|
||||
cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
|
||||
return cookie_dict
|
||||
|
||||
else:
|
||||
raise TypeError('cookie参数必须为Cookie、str或dict类型。')
|
||||
|
||||
return cookie_dict
|
||||
|
||||
|
||||
def cookies_to_tuple(cookies):
|
||||
"""把cookies转为tuple格式
|
||||
:param cookies: cookies信息,可为CookieJar, list, tuple, str, dict
|
||||
:return: 返回tuple形式的cookies
|
||||
"""
|
||||
if isinstance(cookies, (list, tuple, CookieJar)):
|
||||
cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)
|
||||
|
||||
elif isinstance(cookies, str):
|
||||
c_dict = {}
|
||||
cookies = cookies.rstrip('; ')
|
||||
cookies = cookies.split(';')
|
||||
|
||||
for attr in cookies:
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
|
||||
cookies = _dict_cookies_to_tuple(c_dict)
|
||||
|
||||
elif isinstance(cookies, dict):
|
||||
cookies = _dict_cookies_to_tuple(cookies)
|
||||
|
||||
elif isinstance(cookies, Cookie):
|
||||
cookies = (cookie_to_dict(cookies),)
|
||||
|
||||
else:
|
||||
raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。')
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
def set_session_cookies(session, cookies):
|
||||
"""设置Session对象的cookies
|
||||
:param session: Session对象
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
if cookie['value'] is None:
|
||||
cookie['value'] = ''
|
||||
|
||||
kwargs = {x: cookie[x] for x in cookie
|
||||
if x.lower() in ('version', 'port', 'domain', 'path', 'secure',
|
||||
'expires', 'discard', 'comment', 'comment_url', 'rest')}
|
||||
|
||||
if 'expiry' in cookie:
|
||||
kwargs['expires'] = cookie['expiry']
|
||||
|
||||
session.cookies.set(cookie['name'], cookie['value'], **kwargs)
|
||||
|
||||
|
||||
def set_browser_cookies(browser, cookies):
|
||||
"""设置cookies值
|
||||
:param browser: 页面对象
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
c = []
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
if 'domain' not in cookie and 'url' not in cookie:
|
||||
raise ValueError(f"cookie必须带有'domain'或'url'字段:{cookie}")
|
||||
c.append(format_cookie(cookie))
|
||||
browser._run_cdp('Storage.setCookies', cookies=c)
|
||||
|
||||
|
||||
def set_tab_cookies(page, cookies):
|
||||
"""设置cookies值
|
||||
:param page: 页面对象
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
cookie = format_cookie(cookie)
|
||||
|
||||
if cookie['name'].startswith('__Host-'):
|
||||
if not page.url.startswith('http'):
|
||||
cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)
|
||||
else:
|
||||
cookie['url'] = page.url
|
||||
page._run_cdp_loaded('Network.setCookie', **cookie)
|
||||
continue # 不用设置域名,可退出
|
||||
|
||||
if cookie.get('domain', None):
|
||||
try:
|
||||
page._run_cdp_loaded('Network.setCookie', **cookie)
|
||||
if not is_cookie_in_driver(page, cookie):
|
||||
page.browser.set.cookies(cookie)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
url = page._browser_url
|
||||
if not url.startswith('http'):
|
||||
raise RuntimeError(f'未设置域名,请设置cookie的domain参数或先访问一个网站。{cookie}')
|
||||
ex_url = extract(url)
|
||||
d_list = ex_url.subdomain.split('.')
|
||||
d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)
|
||||
|
||||
tmp = [d_list[0]]
|
||||
if len(d_list) > 1:
|
||||
for i in d_list[1:]:
|
||||
tmp.append('.')
|
||||
tmp.append(i)
|
||||
|
||||
for i in range(len(tmp)):
|
||||
cookie['domain'] = ''.join(tmp[i:])
|
||||
page._run_cdp_loaded('Network.setCookie', **cookie)
|
||||
if is_cookie_in_driver(page, cookie):
|
||||
break
|
||||
|
||||
|
||||
def is_cookie_in_driver(page, cookie):
|
||||
"""查询cookie是否在浏览器内
|
||||
:param page: BasePage对象
|
||||
:param cookie: dict格式cookie
|
||||
:return: bool
|
||||
"""
|
||||
if 'domain' in cookie:
|
||||
for c in page.cookies(all_domains=True):
|
||||
if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain',
|
||||
None):
|
||||
return True
|
||||
else:
|
||||
for c in page.cookies(all_domains=True):
|
||||
if cookie['name'] == c['name'] and cookie['value'] == c['value']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def format_cookie(cookie):
|
||||
"""设置cookie为可用格式
|
||||
:param cookie: dict格式cookie
|
||||
:return: 格式化后的cookie字典
|
||||
"""
|
||||
if 'expiry' in cookie:
|
||||
cookie['expires'] = int(cookie['expiry'])
|
||||
cookie.pop('expiry')
|
||||
|
||||
if 'expires' in cookie:
|
||||
if not cookie['expires']:
|
||||
cookie.pop('expires')
|
||||
|
||||
elif isinstance(cookie['expires'], str):
|
||||
if cookie['expires'].isdigit():
|
||||
cookie['expires'] = int(cookie['expires'])
|
||||
|
||||
elif cookie['expires'].replace('.', '').isdigit():
|
||||
cookie['expires'] = float(cookie['expires'])
|
||||
|
||||
else:
|
||||
try:
|
||||
cookie['expires'] = datetime.strptime(cookie['expires'], '%a, %d %b %Y %H:%M:%S GMT').timestamp()
|
||||
except ValueError:
|
||||
cookie['expires'] = datetime.strptime(cookie['expires'], '%a, %d %b %y %H:%M:%S GMT').timestamp()
|
||||
|
||||
if cookie['value'] is None:
|
||||
cookie['value'] = ''
|
||||
elif not isinstance(cookie['value'], str):
|
||||
cookie['value'] = str(cookie['value'])
|
||||
|
||||
if cookie['name'].startswith('__Host-'):
|
||||
cookie['path'] = '/'
|
||||
cookie['secure'] = True
|
||||
|
||||
elif cookie['name'].startswith('__Secure-'):
|
||||
cookie['secure'] = True
|
||||
|
||||
return cookie
|
||||
|
||||
|
||||
class CookiesList(list):
|
||||
def as_dict(self):
|
||||
"""以dict格式返回,只包含name和value字段"""
|
||||
return {c['name']: c['value'] for c in self}
|
||||
|
||||
def as_str(self):
|
||||
"""以str格式返回,只包含name和value字段"""
|
||||
return '; '.join([f'{c["name"]}={c["value"]}' for c in self])
|
||||
|
||||
|
||||
def _dict_cookies_to_tuple(cookies: dict):
|
||||
"""把dict形式的cookies转换为tuple形式
|
||||
:param cookies: 单个或多个cookies,单个时包含'name'和'value'
|
||||
:return: 多个dict格式cookies组成的列表
|
||||
"""
|
||||
if 'name' in cookies and 'value' in cookies: # 单个cookie
|
||||
return (cookies,)
|
||||
keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')
|
||||
template = {k: v for k, v in cookies.items() if k in keys}
|
||||
return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)
|
44
DrissionPage/_functions/cookies.pyi
Normal file
44
DrissionPage/_functions/cookies.pyi
Normal file
@ -0,0 +1,44 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Author : g1879
|
||||
@Contact : g1879@qq.com
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import Cookie
|
||||
from typing import Union
|
||||
|
||||
from requests import Session
|
||||
from requests.cookies import RequestsCookieJar
|
||||
|
||||
from .._base.browser import Chromium
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
|
||||
def cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict: ...
|
||||
|
||||
|
||||
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple: ...
|
||||
|
||||
|
||||
def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
|
||||
def set_browser_cookies(browser: Chromium, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
|
||||
def set_tab_cookies(page: ChromiumBase, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
|
||||
def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
|
||||
|
||||
|
||||
def format_cookie(cookie: dict) -> dict: ...
|
||||
|
||||
|
||||
class CookiesList(list):
|
||||
def as_dict(self) -> dict: ...
|
||||
|
||||
def as_str(self) -> str: ...
|
||||
|
||||
def __next__(self) -> dict: ...
|
@ -7,6 +7,7 @@
|
||||
"""
|
||||
from time import perf_counter
|
||||
|
||||
from .locator import is_loc
|
||||
from .._elements.none_element import NoneElement
|
||||
|
||||
|
||||
@ -402,6 +403,50 @@ def get_eles(locators, owner, any_one=False, first_ele=True, timeout=10):
|
||||
return res
|
||||
|
||||
|
||||
def get_frame(owner, loc_ind_ele, timeout=None):
|
||||
"""获取页面中一个frame对象
|
||||
:param owner: 要在其中查找元素的对象
|
||||
:param loc_ind_ele: 定位符、iframe序号、ChromiumFrame对象,序号从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:return: ChromiumFrame对象
|
||||
"""
|
||||
if isinstance(loc_ind_ele, str):
|
||||
if not is_loc(loc_ind_ele):
|
||||
xpath = f'xpath://*[(name()="iframe" or name()="frame") and ' \
|
||||
f'(@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]'
|
||||
else:
|
||||
xpath = loc_ind_ele
|
||||
ele = owner._ele(xpath, timeout=timeout)
|
||||
if ele and ele._type != 'ChromiumFrame':
|
||||
raise TypeError('该定位符不是指向frame元素。')
|
||||
r = ele
|
||||
|
||||
elif isinstance(loc_ind_ele, tuple):
|
||||
ele = owner._ele(loc_ind_ele, timeout=timeout)
|
||||
if ele and ele._type != 'ChromiumFrame':
|
||||
raise TypeError('该定位符不是指向frame元素。')
|
||||
r = ele
|
||||
|
||||
elif isinstance(loc_ind_ele, int):
|
||||
if loc_ind_ele == 0:
|
||||
loc_ind_ele = 1
|
||||
elif loc_ind_ele < 0:
|
||||
loc_ind_ele = f'last()+{loc_ind_ele}+1'
|
||||
xpath = f'xpath:(//*[name()="frame" or name()="iframe"])[{loc_ind_ele}]'
|
||||
r = owner._ele(xpath, timeout=timeout)
|
||||
|
||||
elif loc_ind_ele._type == 'ChromiumFrame':
|
||||
r = loc_ind_ele
|
||||
|
||||
else:
|
||||
raise TypeError('必须传入定位符、iframe序号、id、name、ChromiumFrame对象其中之一。')
|
||||
|
||||
if isinstance(r, NoneElement):
|
||||
r.method = 'get_frame()'
|
||||
r.args = {'loc_ind_ele': loc_ind_ele}
|
||||
return r
|
||||
|
||||
|
||||
def _get_attr_all(src_list, aim_list, name, value, method, equal=True):
|
||||
if equal:
|
||||
for i in src_list:
|
||||
|
@ -10,6 +10,7 @@ from typing import Union, List, Optional, Iterable
|
||||
from .._base.base import BaseParser
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
|
||||
|
||||
def get_eles(locators: Union[List[str], tuple],
|
||||
@ -19,6 +20,11 @@ def get_eles(locators: Union[List[str], tuple],
|
||||
timeout: float = 10) -> dict: ...
|
||||
|
||||
|
||||
def get_frame(owner: BaseParser,
|
||||
loc_ind_ele: Union[str, int, tuple, ChromiumFrame, ChromiumElement],
|
||||
timeout: float = None) -> ChromiumFrame: ...
|
||||
|
||||
|
||||
class SessionElementsList(list):
|
||||
_page = ...
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from platform import system
|
||||
|
||||
from ..errors import AlertExistsError
|
||||
|
||||
|
||||
@ -21,18 +23,14 @@ class Keys:
|
||||
CANCEL = '\ue001' # ^break
|
||||
HELP = '\ue002'
|
||||
BACKSPACE = '\ue003'
|
||||
BACK_SPACE = BACKSPACE
|
||||
TAB = '\ue004'
|
||||
CLEAR = '\ue005'
|
||||
RETURN = '\ue006'
|
||||
ENTER = '\ue007'
|
||||
SHIFT = '\ue008'
|
||||
LEFT_SHIFT = SHIFT
|
||||
CONTROL = '\ue009'
|
||||
CTRL = '\ue009'
|
||||
LEFT_CONTROL = CONTROL
|
||||
ALT = '\ue00a'
|
||||
LEFT_ALT = ALT
|
||||
PAUSE = '\ue00b'
|
||||
ESCAPE = '\ue00c'
|
||||
SPACE = '\ue00d'
|
||||
@ -41,13 +39,9 @@ class Keys:
|
||||
END = '\ue010'
|
||||
HOME = '\ue011'
|
||||
LEFT = '\ue012'
|
||||
ARROW_LEFT = LEFT
|
||||
UP = '\ue013'
|
||||
ARROW_UP = UP
|
||||
RIGHT = '\ue014'
|
||||
ARROW_RIGHT = RIGHT
|
||||
DOWN = '\ue015'
|
||||
ARROW_DOWN = DOWN
|
||||
INSERT = '\ue016'
|
||||
DELETE = '\ue017'
|
||||
DEL = '\ue017'
|
||||
@ -219,15 +213,15 @@ keyDefinitions = {
|
||||
'\ue005': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},
|
||||
'\ue006': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3},
|
||||
'\ue00b': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},
|
||||
'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},
|
||||
# 'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},
|
||||
'\ue00c': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},
|
||||
'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},
|
||||
'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},
|
||||
# 'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},
|
||||
# 'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},
|
||||
'\ue010': {'keyCode': 35, 'code': 'End', 'key': 'End'},
|
||||
# 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},
|
||||
'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},
|
||||
'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},
|
||||
'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},
|
||||
# 'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},
|
||||
# 'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},
|
||||
# 'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},
|
||||
'\ue016': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'},
|
||||
# 'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3},
|
||||
'\ue017': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'},
|
||||
@ -243,35 +237,6 @@ keyDefinitions = {
|
||||
'\ue021': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'},
|
||||
'\ue022': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'},
|
||||
'\ue023': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': '\(', 'key': '9'},
|
||||
'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'},
|
||||
'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'},
|
||||
'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'},
|
||||
'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'},
|
||||
'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'},
|
||||
'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'},
|
||||
'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'},
|
||||
'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'},
|
||||
'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'},
|
||||
'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'},
|
||||
'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'},
|
||||
'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'},
|
||||
'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'},
|
||||
'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'},
|
||||
'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'},
|
||||
'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'},
|
||||
'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'},
|
||||
'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'},
|
||||
'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'},
|
||||
'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'},
|
||||
'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'},
|
||||
'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'},
|
||||
'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'},
|
||||
'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'},
|
||||
'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'},
|
||||
'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'},
|
||||
'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'},
|
||||
'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'},
|
||||
'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'},
|
||||
'\ue024': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3},
|
||||
'\ue025': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3},
|
||||
'\ue027': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3},
|
||||
@ -288,64 +253,91 @@ keyDefinitions = {
|
||||
'\ue03a': {'keyCode': 121, 'code': 'F10', 'key': 'F10'},
|
||||
'\ue03b': {'keyCode': 122, 'code': 'F11', 'key': 'F11'},
|
||||
'\ue03c': {'keyCode': 123, 'code': 'F12', 'key': 'F12'},
|
||||
'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'},
|
||||
'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'},
|
||||
'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'},
|
||||
'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'},
|
||||
'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'},
|
||||
'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'},
|
||||
'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'},
|
||||
'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'},
|
||||
'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'},
|
||||
'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'},
|
||||
'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'},
|
||||
'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'},
|
||||
'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'},
|
||||
'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'},
|
||||
'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'},
|
||||
'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'},
|
||||
'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'},
|
||||
'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'},
|
||||
'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'},
|
||||
'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'},
|
||||
'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'},
|
||||
'\ue018': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'},
|
||||
'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='},
|
||||
'\ue019': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3},
|
||||
'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','},
|
||||
'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'},
|
||||
'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'},
|
||||
'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'},
|
||||
'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'},
|
||||
'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['},
|
||||
'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'},
|
||||
'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'},
|
||||
'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''},
|
||||
'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'},
|
||||
'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'},
|
||||
'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'},
|
||||
'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3},
|
||||
'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'},
|
||||
'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'},
|
||||
'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'},
|
||||
'Accept': {'keyCode': 30, 'key': 'Accept'},
|
||||
'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},
|
||||
'Print': {'keyCode': 42, 'key': 'Print'},
|
||||
'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},
|
||||
'\u0000': {'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3},
|
||||
'Attn': {'keyCode': 246, 'key': 'Attn'},
|
||||
'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},
|
||||
'ExSel': {'keyCode': 248, 'key': 'ExSel'},
|
||||
'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},
|
||||
'Play': {'keyCode': 250, 'key': 'Play'},
|
||||
'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},
|
||||
'Power': {'key': 'Power', 'code': 'Power'},
|
||||
'Eject': {'key': 'Eject', 'code': 'Eject'},
|
||||
# 'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'},
|
||||
# 'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'},
|
||||
# 'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'},
|
||||
# 'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'},
|
||||
# 'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'},
|
||||
# 'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'},
|
||||
# 'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'},
|
||||
# 'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'},
|
||||
# 'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'},
|
||||
# 'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'},
|
||||
# 'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'},
|
||||
# 'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'},
|
||||
# 'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'},
|
||||
# 'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'},
|
||||
# 'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'},
|
||||
# 'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'},
|
||||
# 'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'},
|
||||
# 'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'},
|
||||
# 'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'},
|
||||
# 'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'},
|
||||
# 'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'},
|
||||
# 'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'},
|
||||
# 'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'},
|
||||
# 'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'},
|
||||
# 'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'},
|
||||
# 'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'},
|
||||
# 'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'},
|
||||
# 'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'},
|
||||
# 'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'},
|
||||
# 'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'},
|
||||
# 'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'},
|
||||
# 'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'},
|
||||
# 'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'},
|
||||
# 'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'},
|
||||
# 'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'},
|
||||
# 'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'},
|
||||
# 'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'},
|
||||
# 'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'},
|
||||
# 'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'},
|
||||
# 'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'},
|
||||
# 'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'},
|
||||
# 'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'},
|
||||
# 'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'},
|
||||
# 'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'},
|
||||
# 'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'},
|
||||
# 'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'},
|
||||
# 'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'},
|
||||
# 'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'},
|
||||
# 'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'},
|
||||
# 'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'},
|
||||
# 'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='},
|
||||
# 'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','},
|
||||
# 'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'},
|
||||
# 'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'},
|
||||
# 'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'},
|
||||
# 'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'},
|
||||
# 'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['},
|
||||
# 'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'},
|
||||
# 'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'},
|
||||
# 'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''},
|
||||
# 'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'},
|
||||
# 'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'},
|
||||
# 'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'},
|
||||
# 'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3},
|
||||
# 'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'},
|
||||
# 'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'},
|
||||
# 'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'},
|
||||
# 'Accept': {'keyCode': 30, 'key': 'Accept'},
|
||||
# 'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},
|
||||
# 'Print': {'keyCode': 42, 'key': 'Print'},
|
||||
# 'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},
|
||||
# 'Attn': {'keyCode': 246, 'key': 'Attn'},
|
||||
# 'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},
|
||||
# 'ExSel': {'keyCode': 248, 'key': 'ExSel'},
|
||||
# 'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},
|
||||
# 'Play': {'keyCode': 250, 'key': 'Play'},
|
||||
# 'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},
|
||||
# 'Power': {'key': 'Power', 'code': 'Power'},
|
||||
# 'Eject': {'key': 'Eject', 'code': 'Eject'},
|
||||
}
|
||||
modifierBit = {'\ue00a': 1,
|
||||
'\ue009': 2,
|
||||
'\ue03d': 4,
|
||||
'\ue008': 8}
|
||||
modifierBit = {'\ue00a': 1, '\ue009': 2, '\ue03d': 4, '\ue008': 8}
|
||||
sys = system().lower()
|
||||
|
||||
|
||||
def keys_to_typing(value):
|
||||
@ -368,71 +360,69 @@ def keys_to_typing(value):
|
||||
return modifier, ''.join(typing)
|
||||
|
||||
|
||||
def keyDescriptionForString(_modifiers, keyString): # noqa: C901
|
||||
shift = _modifiers & 8
|
||||
description = {'key': '',
|
||||
'keyCode': 0,
|
||||
'code': '',
|
||||
'text': '',
|
||||
'location': 0}
|
||||
def make_input_data(modifiers, key, key_up=False):
|
||||
"""
|
||||
:param modifiers: 功能键设置
|
||||
:param key: 按键字符
|
||||
:param key_up: 是否提起
|
||||
:return: None
|
||||
"""
|
||||
data = keyDefinitions.get(key)
|
||||
if not data:
|
||||
return None
|
||||
|
||||
definition = keyDefinitions.get(keyString) # type: ignore
|
||||
if not definition:
|
||||
raise ValueError(f'未知按键:{keyString}')
|
||||
result = {'modifiers': modifiers, 'autoRepeat': False, '_ignore': AlertExistsError}
|
||||
shift = modifiers & 8
|
||||
|
||||
if 'key' in definition:
|
||||
description['key'] = definition['key']
|
||||
if shift and definition.get('shiftKey'):
|
||||
description['key'] = definition['shiftKey']
|
||||
if shift and data.get('shiftKey'):
|
||||
result['key'] = data['shiftKey']
|
||||
result['text'] = data['shiftKey']
|
||||
elif 'key' in data:
|
||||
result['key'] = data['key']
|
||||
|
||||
if 'keyCode' in definition:
|
||||
description['keyCode'] = definition['keyCode']
|
||||
if shift and definition.get('shiftKeyCode'):
|
||||
description['keyCode'] = definition['shiftKeyCode']
|
||||
if len(result.get('key', '')) == 1: # type: ignore
|
||||
result['text'] = data['key']
|
||||
|
||||
if 'code' in definition:
|
||||
description['code'] = definition['code']
|
||||
sys_text = 'windowsVirtualKeyCode' if sys == 'windows' else 'nativeVirtualKeyCode'
|
||||
if shift and data.get('shiftKeyCode'):
|
||||
result[sys_text] = data['shiftKeyCode']
|
||||
elif 'keyCode' in data:
|
||||
result[sys_text] = data['keyCode']
|
||||
|
||||
if 'location' in definition:
|
||||
description['location'] = definition['location']
|
||||
if 'code' in data:
|
||||
result['code'] = data['code']
|
||||
|
||||
if len(description['key']) == 1: # type: ignore
|
||||
description['text'] = description['key']
|
||||
if 'location' in data:
|
||||
result['location'] = data['location']
|
||||
result['isKeypad'] = data['location'] == 3
|
||||
else:
|
||||
result['location'] = 0
|
||||
result['isKeypad'] = False
|
||||
|
||||
if 'text' in definition:
|
||||
description['text'] = definition['text']
|
||||
if shift and definition.get('shiftText'):
|
||||
description['text'] = definition['shiftText']
|
||||
if shift and data.get('shiftText'):
|
||||
result['text'] = data['shiftText']
|
||||
result['unmodifiedText'] = data['shiftText']
|
||||
elif 'text' in data:
|
||||
result['text'] = data['text']
|
||||
result['unmodifiedText'] = data['text']
|
||||
|
||||
if _modifiers & ~8:
|
||||
description['text'] = ''
|
||||
if modifiers & ~8:
|
||||
result['text'] = ''
|
||||
|
||||
return description
|
||||
result['type'] = 'keyUp' if key_up else ('keyDown' if result.get('text') else 'rawKeyDown')
|
||||
return result
|
||||
|
||||
|
||||
def send_key(page, modifier, key):
|
||||
"""发送一个字,在键盘中的字符触发按键,其它直接发送文本"""
|
||||
if key in keyDefinitions:
|
||||
description = keyDescriptionForString(modifier, key)
|
||||
text = description['text']
|
||||
data = {'type': 'keyDown' if text else 'rawKeyDown',
|
||||
'modifiers': modifier,
|
||||
'windowsVirtualKeyCode': description['keyCode'],
|
||||
'code': description['code'],
|
||||
'key': description['key'],
|
||||
'text': text,
|
||||
'autoRepeat': False,
|
||||
'unmodifiedText': text,
|
||||
'location': description['location'],
|
||||
'isKeypad': description['location'] == 3,
|
||||
'_ignore': AlertExistsError}
|
||||
|
||||
page.run_cdp('Input.dispatchKeyEvent', **data)
|
||||
data = make_input_data(modifier, key)
|
||||
if data:
|
||||
page._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
data['type'] = 'keyUp'
|
||||
page.run_cdp('Input.dispatchKeyEvent', **data)
|
||||
page._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
|
||||
else:
|
||||
page.run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)
|
||||
page._run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)
|
||||
|
||||
|
||||
def input_text_or_keys(page, text_or_keys):
|
||||
@ -451,7 +441,7 @@ def input_text_or_keys(page, text_or_keys):
|
||||
return
|
||||
|
||||
if text_or_keys.endswith(('\n', '\ue007')):
|
||||
page.run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError)
|
||||
page._run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError)
|
||||
send_key(page, modifier, '\n')
|
||||
else:
|
||||
page.run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError)
|
||||
page._run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError)
|
||||
|
@ -5,7 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from typing import Tuple, Dict, Union, Any
|
||||
from typing import Tuple, Union, Any
|
||||
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
@ -23,18 +23,14 @@ class Keys:
|
||||
CANCEL: str
|
||||
HELP: str
|
||||
BACKSPACE: str
|
||||
BACK_SPACE: str
|
||||
TAB: str
|
||||
CLEAR: str
|
||||
RETURN: str
|
||||
ENTER: str
|
||||
SHIFT: str
|
||||
LEFT_SHIFT: str
|
||||
CONTROL: str
|
||||
CTRL: str
|
||||
LEFT_CONTROL: str
|
||||
ALT: str
|
||||
LEFT_ALT: str
|
||||
PAUSE: str
|
||||
ESCAPE: str
|
||||
SPACE: str
|
||||
@ -43,13 +39,9 @@ class Keys:
|
||||
END: str
|
||||
HOME: str
|
||||
LEFT: str
|
||||
ARROW_LEFT: str
|
||||
UP: str
|
||||
ARROW_UP: str
|
||||
RIGHT: str
|
||||
ARROW_RIGHT: str
|
||||
DOWN: str
|
||||
ARROW_DOWN: str
|
||||
INSERT: str
|
||||
DELETE: str
|
||||
DEL: str
|
||||
@ -96,7 +88,7 @@ modifierBit: dict = ...
|
||||
def keys_to_typing(value: Union[str, int, list, tuple]) -> Tuple[int, str]: ...
|
||||
|
||||
|
||||
def keyDescriptionForString(_modifiers: int, keyString: str) -> Dict: ...
|
||||
def make_input_data(modifiers: int, key: str, key_up: bool = False) -> dict: ...
|
||||
|
||||
|
||||
def send_key(page: ChromiumBase, modifier: int, key: str) -> None: ...
|
||||
|
@ -8,7 +8,7 @@
|
||||
from pathlib import Path
|
||||
from platform import system
|
||||
from shutil import rmtree
|
||||
from tempfile import gettempdir, TemporaryDirectory
|
||||
from tempfile import gettempdir
|
||||
from threading import Lock
|
||||
from time import perf_counter, sleep
|
||||
|
||||
@ -18,43 +18,51 @@ from ..errors import (ContextLostError, ElementLostError, CDPError, PageDisconne
|
||||
|
||||
|
||||
class PortFinder(object):
|
||||
used_port = {}
|
||||
used_port = set()
|
||||
prev_time = 0
|
||||
lock = Lock()
|
||||
checked_paths = set()
|
||||
|
||||
def __init__(self, path=None):
|
||||
"""
|
||||
:param path: 临时文件保存路径,为None时使用系统临时文件夹
|
||||
"""
|
||||
tmp = Path(path) if path else Path(gettempdir()) / 'DrissionPage'
|
||||
self.tmp_dir = tmp / 'UserTempFolder'
|
||||
self.tmp_dir = tmp / 'autoPortData'
|
||||
self.tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||
if not PortFinder.used_port:
|
||||
clean_folder(self.tmp_dir)
|
||||
if str(self.tmp_dir.absolute()) not in PortFinder.checked_paths:
|
||||
for i in self.tmp_dir.iterdir():
|
||||
if i.is_dir() and not port_is_using('127.0.0.1', i.name):
|
||||
rmtree(i, ignore_errors=True)
|
||||
PortFinder.checked_paths.add(str(self.tmp_dir.absolute()))
|
||||
|
||||
def get_port(self, scope=None):
|
||||
"""查找一个可用端口
|
||||
:param scope: 指定端口范围,不含最后的数字,为None则使用[9600-19600)
|
||||
:param scope: 指定端口范围,不含最后的数字,为None则使用[9600-59600)
|
||||
:return: 可以使用的端口和用户文件夹路径组成的元组
|
||||
"""
|
||||
from random import randint
|
||||
with PortFinder.lock:
|
||||
if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 60:
|
||||
PortFinder.used_port.clear()
|
||||
if scope in (True, None):
|
||||
scope = (9600, 19600)
|
||||
for i in range(scope[0], scope[1]):
|
||||
if i in PortFinder.used_port:
|
||||
scope = (9600, 59600)
|
||||
max_times = scope[1] - scope[0]
|
||||
times = 0
|
||||
while times < max_times:
|
||||
times += 1
|
||||
port = randint(*scope)
|
||||
if port in PortFinder.used_port or port_is_using('127.0.0.1', port):
|
||||
continue
|
||||
elif port_is_using('127.0.0.1', i):
|
||||
PortFinder.used_port[i] = None
|
||||
path = self.tmp_dir / str(port)
|
||||
if path.exists():
|
||||
try:
|
||||
rmtree(path)
|
||||
except:
|
||||
continue
|
||||
path = TemporaryDirectory(dir=self.tmp_dir).name
|
||||
PortFinder.used_port[i] = path
|
||||
return i, path
|
||||
|
||||
for i in range(scope[0], scope[1]):
|
||||
if port_is_using('127.0.0.1', i):
|
||||
continue
|
||||
rmtree(PortFinder.used_port[i], ignore_errors=True)
|
||||
return i, TemporaryDirectory(dir=self.tmp_dir).name
|
||||
|
||||
PortFinder.used_port.add(port)
|
||||
PortFinder.prev_time = perf_counter()
|
||||
return port, str(path)
|
||||
raise OSError('未找到可用端口。')
|
||||
|
||||
|
||||
@ -95,7 +103,7 @@ def show_or_hide_browser(page, hide=True):
|
||||
:param hide: 是否隐藏
|
||||
:return: None
|
||||
"""
|
||||
if not page.address.startswith(('127.0.0.1', 'localhost')):
|
||||
if not page.browser.address.startswith(('127.0.0.1', 'localhost')):
|
||||
return
|
||||
|
||||
if system().lower() != 'windows':
|
||||
@ -191,10 +199,11 @@ def configs_to_here(save_name=None):
|
||||
om.save(save_name)
|
||||
|
||||
|
||||
def raise_error(result, ignore=None):
|
||||
def raise_error(result, ignore=None, user=False):
|
||||
"""抛出error对应报错
|
||||
:param result: 包含error的dict
|
||||
:param ignore: 要忽略的错误
|
||||
:param user: 是否用户调用的
|
||||
:return: None
|
||||
"""
|
||||
error = result['error']
|
||||
@ -220,13 +229,18 @@ def raise_error(result, ignore=None):
|
||||
elif error == 'Given expression does not evaluate to a function':
|
||||
r = JavaScriptError(f'传入的js无法解析成函数:\n{result["args"]["functionDeclaration"]}')
|
||||
elif error.endswith("' wasn't found"):
|
||||
r = RuntimeError(f'你的浏览器可能太旧。\n方法:{result["method"]}\n参数:{result["args"]}')
|
||||
elif result['type'] in ('call_method_error', 'timeout'):
|
||||
r = RuntimeError(f'没有找到对应功能,方法错误或你的浏览器太旧。\n方法:{result["method"]}\n参数:{result["args"]}')
|
||||
elif result['type'] == 'timeout':
|
||||
from DrissionPage import __version__
|
||||
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \
|
||||
f'版本:{__version__}\n超时,可能是浏览器卡了。'
|
||||
r = TimeoutError(txt)
|
||||
elif result['type'] == 'call_method_error' and not user:
|
||||
from DrissionPage import __version__
|
||||
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \
|
||||
f'版本:{__version__}\n出现这个错误可能意味着程序有bug,请把错误信息和重现方法' \
|
||||
'告知作者,谢谢。\n报告网站:https://gitee.com/g1879/DrissionPage/issues'
|
||||
r = TimeoutError(txt) if result['type'] == 'timeout' else CDPError(txt)
|
||||
r = CDPError(txt)
|
||||
else:
|
||||
r = RuntimeError(result)
|
||||
|
||||
|
@ -14,9 +14,11 @@ from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
|
||||
class PortFinder(object):
|
||||
used_port: dict = ...
|
||||
used_port: set = ...
|
||||
prev_time: float = ...
|
||||
lock: Lock = ...
|
||||
tmp_dir: Path = ...
|
||||
checked_paths: set = ...
|
||||
|
||||
def __init__(self, path: Union[str, Path] = None): ...
|
||||
|
||||
@ -45,4 +47,4 @@ def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): ..
|
||||
def configs_to_here(file_name: Union[Path, str] = None) -> None: ...
|
||||
|
||||
|
||||
def raise_error(result: dict, ignore=None) -> None: ...
|
||||
def raise_error(result: dict, ignore=None, user: bool = False) -> None: ...
|
||||
|
@ -5,16 +5,14 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from html import unescape
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from os.path import sep
|
||||
from pathlib import Path
|
||||
from re import sub, match
|
||||
from re import sub
|
||||
from urllib.parse import urlparse, urljoin, urlunparse
|
||||
|
||||
from DataRecorder.tools import make_valid_name
|
||||
from tldextract import extract
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
|
||||
def get_ele_txt(e):
|
||||
@ -59,7 +57,7 @@ def get_ele_txt(e):
|
||||
if sub('[ \n\t\r]', '', el) != '': # 字符除了回车和空格还有其它内容
|
||||
txt = el
|
||||
if not pre:
|
||||
txt = txt.replace('\r\n', ' ').replace('\n', ' ').strip(' ')
|
||||
txt = txt.replace('\r\n', ' ').replace('\n', ' ')
|
||||
txt = sub(r' {2,}', ' ', txt)
|
||||
str_list.append(txt)
|
||||
|
||||
@ -80,8 +78,31 @@ def get_ele_txt(e):
|
||||
re_str = get_node_txt(e)
|
||||
if re_str and re_str[-1] == '\n':
|
||||
re_str.pop()
|
||||
re_str = ''.join([i if i is not True else '\n' for i in re_str])
|
||||
return format_html(re_str)
|
||||
|
||||
l = len(re_str)
|
||||
if l > 1:
|
||||
r = []
|
||||
for i in range(l - 1):
|
||||
i1 = re_str[i]
|
||||
i2 = re_str[i + 1]
|
||||
if i1 is True:
|
||||
r.append('\n')
|
||||
continue
|
||||
if i2 is True:
|
||||
r.append(i1)
|
||||
continue
|
||||
if i1.endswith(' ') and i2.startswith(' '):
|
||||
i1 = i1[:-1]
|
||||
r.append(i1)
|
||||
r.append('\n' if re_str[-1] is True else re_str[-1])
|
||||
re_str = ''.join(r)
|
||||
|
||||
elif not l:
|
||||
re_str = ''
|
||||
else:
|
||||
re_str = re_str[0] if re_str[0] is not True else '\n'
|
||||
|
||||
return format_html(re_str.strip())
|
||||
|
||||
|
||||
def format_html(text):
|
||||
@ -106,30 +127,30 @@ def location_in_viewport(page, loc_x, loc_y):
|
||||
const vHeight = document.documentElement.clientHeight;
|
||||
if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}}
|
||||
return true;}}'''
|
||||
return page.run_js(js)
|
||||
return page._run_js(js)
|
||||
|
||||
|
||||
def offset_scroll(ele, offset_x, offset_y):
|
||||
"""接收元素及偏移坐标,把坐标滚动到页面中间,返回该点在视口中的坐标
|
||||
"""接收元素及偏移坐标,把坐标滚动到页面中间,返回该点坐标
|
||||
有偏移量时以元素左上角坐标为基准,没有时以click_point为基准
|
||||
:param ele: 元素对象
|
||||
:param offset_x: 偏移量x
|
||||
:param offset_y: 偏移量y
|
||||
:return: 视口中的坐标
|
||||
:return: 绝对坐标和相对坐标
|
||||
"""
|
||||
loc_x, loc_y = ele.rect.location
|
||||
cp_x, cp_y = ele.rect.click_point
|
||||
lx = loc_x + offset_x if offset_x else cp_x
|
||||
ly = loc_y + offset_y if offset_y else cp_y
|
||||
if not location_in_viewport(ele.owner, lx, ly):
|
||||
clientWidth = ele.owner.run_js('return document.body.clientWidth;')
|
||||
clientHeight = ele.owner.run_js('return document.body.clientHeight;')
|
||||
clientWidth = ele.owner._run_js('return document.body.clientWidth;')
|
||||
clientHeight = ele.owner._run_js('return document.body.clientHeight;')
|
||||
ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)
|
||||
cl_x, cl_y = ele.rect.viewport_location
|
||||
ccp_x, ccp_y = ele.rect.viewport_click_point
|
||||
cx = cl_x + offset_x if offset_x else ccp_x
|
||||
cy = cl_y + offset_y if offset_y else ccp_y
|
||||
return cx, cy
|
||||
return lx, ly, cx, cy
|
||||
|
||||
|
||||
def make_absolute_link(link, baseURI=None):
|
||||
@ -144,6 +165,9 @@ def make_absolute_link(link, baseURI=None):
|
||||
link = link.strip().replace('\\', '/')
|
||||
parsed = urlparse(link)._asdict()
|
||||
if baseURI:
|
||||
if link.startswith('./'):
|
||||
baseURI = baseURI[:baseURI.rfind('/') + 1]
|
||||
else:
|
||||
p = urlparse(baseURI)._asdict()
|
||||
baseURI = f'{p["scheme"]}://{p["netloc"]}'
|
||||
|
||||
@ -171,188 +195,6 @@ def is_js_func(func):
|
||||
return False
|
||||
|
||||
|
||||
def cookie_to_dict(cookie):
|
||||
"""把Cookie对象转为dict格式
|
||||
:param cookie: Cookie对象、字符串或字典
|
||||
:return: cookie字典
|
||||
"""
|
||||
if isinstance(cookie, Cookie):
|
||||
cookie_dict = cookie.__dict__.copy()
|
||||
cookie_dict.pop('rfc2109', None)
|
||||
cookie_dict.pop('_rest', None)
|
||||
return cookie_dict
|
||||
|
||||
elif isinstance(cookie, dict):
|
||||
cookie_dict = cookie
|
||||
|
||||
elif isinstance(cookie, str):
|
||||
cookie_dict = {}
|
||||
for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):
|
||||
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
else:
|
||||
cookie_dict['name'] = attr_val[0]
|
||||
cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
|
||||
return cookie_dict
|
||||
|
||||
else:
|
||||
raise TypeError('cookie参数必须为Cookie、str或dict类型。')
|
||||
|
||||
return cookie_dict
|
||||
|
||||
|
||||
def cookies_to_tuple(cookies):
|
||||
"""把cookies转为tuple格式
|
||||
:param cookies: cookies信息,可为CookieJar, list, tuple, str, dict
|
||||
:return: 返回tuple形式的cookies
|
||||
"""
|
||||
if isinstance(cookies, (list, tuple, CookieJar)):
|
||||
cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)
|
||||
|
||||
elif isinstance(cookies, str):
|
||||
c_dict = {}
|
||||
cookies = cookies.rstrip('; ')
|
||||
cookies = cookies.split(';')
|
||||
# r = match(r'.*?=([^=]+)=', cookies)
|
||||
# if not r: # 只有一个
|
||||
# cookies = [cookies.rstrip(',;')]
|
||||
# else:
|
||||
# s = match(r'.*([,;]).*', r.group(1)).group(1)
|
||||
# cookies = cookies.rstrip(s).split(s)
|
||||
|
||||
for attr in cookies:
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
|
||||
cookies = _dict_cookies_to_tuple(c_dict)
|
||||
|
||||
elif isinstance(cookies, dict):
|
||||
cookies = _dict_cookies_to_tuple(cookies)
|
||||
|
||||
elif isinstance(cookies, Cookie):
|
||||
cookies = (cookie_to_dict(cookies),)
|
||||
|
||||
else:
|
||||
raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。')
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
def set_session_cookies(session, cookies):
|
||||
"""设置Session对象的cookies
|
||||
:param session: Session对象
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
if cookie['value'] is None:
|
||||
cookie['value'] = ''
|
||||
|
||||
kwargs = {x: cookie[x] for x in cookie
|
||||
if x.lower() in ('version', 'port', 'domain', 'path', 'secure',
|
||||
'expires', 'discard', 'comment', 'comment_url', 'rest')}
|
||||
|
||||
if 'expiry' in cookie:
|
||||
kwargs['expires'] = cookie['expiry']
|
||||
|
||||
session.cookies.set(cookie['name'], cookie['value'], **kwargs)
|
||||
|
||||
|
||||
def set_browser_cookies(page, cookies):
|
||||
"""设置cookies值
|
||||
:param page: 页面对象
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
if 'expiry' in cookie:
|
||||
cookie['expires'] = int(cookie['expiry'])
|
||||
cookie.pop('expiry')
|
||||
|
||||
if 'expires' in cookie:
|
||||
if not cookie['expires']:
|
||||
cookie.pop('expires')
|
||||
|
||||
elif isinstance(cookie['expires'], str):
|
||||
if cookie['expires'].isdigit():
|
||||
cookie['expires'] = int(cookie['expires'])
|
||||
|
||||
elif cookie['expires'].replace('.', '').isdigit():
|
||||
cookie['expires'] = float(cookie['expires'])
|
||||
|
||||
else:
|
||||
try:
|
||||
cookie['expires'] = datetime.strptime(cookie['expires'],
|
||||
'%a, %d %b %Y %H:%M:%S GMT').timestamp()
|
||||
except ValueError:
|
||||
cookie['expires'] = datetime.strptime(cookie['expires'],
|
||||
'%a, %d %b %y %H:%M:%S GMT').timestamp()
|
||||
|
||||
if cookie['value'] is None:
|
||||
cookie['value'] = ''
|
||||
elif not isinstance(cookie['value'], str):
|
||||
cookie['value'] = str(cookie['value'])
|
||||
|
||||
if cookie['name'].startswith('__Host-'):
|
||||
cookie['path'] = '/'
|
||||
cookie['secure'] = True
|
||||
if not page.url.startswith('http'):
|
||||
cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)
|
||||
else:
|
||||
cookie['url'] = page.url
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
continue # 不用设置域名,可退出
|
||||
|
||||
if cookie['name'].startswith('__Secure-'):
|
||||
cookie['secure'] = True
|
||||
|
||||
if cookie.get('domain', None):
|
||||
try:
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
if is_cookie_in_driver(page, cookie):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
url = page._browser_url
|
||||
if not url.startswith('http'):
|
||||
raise RuntimeError(f'未设置域名,请设置cookie的domain参数或先访问一个网站。{cookie}')
|
||||
ex_url = extract(url)
|
||||
d_list = ex_url.subdomain.split('.')
|
||||
d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)
|
||||
|
||||
tmp = [d_list[0]]
|
||||
if len(d_list) > 1:
|
||||
for i in d_list[1:]:
|
||||
tmp.append('.')
|
||||
tmp.append(i)
|
||||
|
||||
for i in range(len(tmp)):
|
||||
cookie['domain'] = ''.join(tmp[i:])
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
if is_cookie_in_driver(page, cookie):
|
||||
break
|
||||
|
||||
|
||||
def is_cookie_in_driver(page, cookie):
|
||||
"""查询cookie是否在浏览器内
|
||||
:param page: BasePage对象
|
||||
:param cookie: dict格式cookie
|
||||
:return: bool
|
||||
"""
|
||||
if 'domain' in cookie:
|
||||
for c in page.cookies(all_domains=True):
|
||||
if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain',
|
||||
None):
|
||||
return True
|
||||
else:
|
||||
for c in page.cookies(all_domains=True):
|
||||
if cookie['name'] == c['name'] and cookie['value'] == c['value']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_blob(page, url, as_bytes=True):
|
||||
"""获取知道blob资源
|
||||
:param page: 资源所在页面对象
|
||||
@ -378,7 +220,7 @@ def get_blob(page, url, as_bytes=True):
|
||||
}
|
||||
"""
|
||||
try:
|
||||
result = page.run_js(js, url)
|
||||
result = page._run_js(js, url)
|
||||
except:
|
||||
raise RuntimeError('无法获取该资源。')
|
||||
if as_bytes:
|
||||
@ -426,7 +268,7 @@ def get_mhtml(page, path=None, name=None):
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:return: mhtml文本
|
||||
"""
|
||||
r = page.run_cdp('Page.captureSnapshot')['data']
|
||||
r = page._run_cdp('Page.captureSnapshot')['data']
|
||||
if path is None and name is None:
|
||||
return r
|
||||
|
||||
@ -452,7 +294,7 @@ def get_pdf(page, path=None, name=None, kwargs=None):
|
||||
if 'printBackground' not in kwargs:
|
||||
kwargs['printBackground'] = True
|
||||
try:
|
||||
r = page.run_cdp('Page.printToPDF', **kwargs)['data']
|
||||
r = page._run_cdp('Page.printToPDF', **kwargs)['data']
|
||||
except:
|
||||
raise RuntimeError('保存失败,可能浏览器版本不支持。')
|
||||
from base64 import b64decode
|
||||
@ -528,7 +370,9 @@ def format_headers(txt):
|
||||
:param txt: 从浏览器复制的原始文本格式headers
|
||||
:return: dict格式headers
|
||||
"""
|
||||
if not isinstance(txt, str):
|
||||
if isinstance(txt, (dict, CaseInsensitiveDict)):
|
||||
for k, v in txt.items():
|
||||
txt[k] = str(v)
|
||||
return txt
|
||||
headers = {}
|
||||
for header in txt.split('\n'):
|
||||
@ -536,15 +380,3 @@ def format_headers(txt):
|
||||
name, value = header.split(': ', maxsplit=1)
|
||||
headers[name] = value
|
||||
return headers
|
||||
|
||||
|
||||
def _dict_cookies_to_tuple(cookies: dict):
|
||||
"""把dict形式的cookies转换为tuple形式
|
||||
:param cookies: 单个或多个cookies,单个时包含'name'和'value'
|
||||
:return: 多个dict格式cookies组成的列表
|
||||
"""
|
||||
if 'name' in cookies and 'value' in cookies: # 单个cookie
|
||||
return (cookies,)
|
||||
keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')
|
||||
template = {k: v for k, v in cookies.items() if k in keys}
|
||||
return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)
|
||||
|
@ -5,18 +5,14 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import Cookie
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
|
||||
from requests import Session
|
||||
from requests.cookies import RequestsCookieJar
|
||||
from typing import Union, Optional, Tuple
|
||||
|
||||
from .._base.base import DrissionElement, BaseParser
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._pages.tabs import ChromiumTab
|
||||
|
||||
|
||||
def get_ele_txt(e: DrissionElement) -> str: ...
|
||||
@ -28,7 +24,7 @@ def format_html(text: str) -> str: ...
|
||||
def location_in_viewport(page: ChromiumBase, loc_x: float, loc_y: float) -> bool: ...
|
||||
|
||||
|
||||
def offset_scroll(ele: ChromiumElement, offset_x: float, offset_y: float) -> tuple: ...
|
||||
def offset_scroll(ele: ChromiumElement, offset_x: float, offset_y: float) -> Tuple[int, int, int, int]: ...
|
||||
|
||||
|
||||
def make_absolute_link(link: str, baseURI: str = None) -> str: ...
|
||||
@ -37,21 +33,6 @@ def make_absolute_link(link: str, baseURI: str = None) -> str: ...
|
||||
def is_js_func(func: str) -> bool: ...
|
||||
|
||||
|
||||
def cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict: ...
|
||||
|
||||
|
||||
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple: ...
|
||||
|
||||
|
||||
def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
|
||||
def set_browser_cookies(page: ChromiumBase, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
|
||||
def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
|
||||
|
||||
|
||||
def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ...
|
||||
|
||||
|
||||
|
@ -18,7 +18,9 @@ from .._base.base import BasePage
|
||||
from .._elements.chromium_element import run_js, make_chromium_eles
|
||||
from .._elements.none_element import NoneElement
|
||||
from .._elements.session_element import make_session_ele
|
||||
from .._functions.locator import get_loc, is_loc
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._functions.elements import SessionElementsList, get_frame
|
||||
from .._functions.locator import get_loc
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import raise_error
|
||||
from .._functions.web import location_in_viewport
|
||||
@ -36,15 +38,15 @@ __ERROR__ = 'error'
|
||||
|
||||
|
||||
class ChromiumBase(BasePage):
|
||||
"""标签页、frame、页面基类"""
|
||||
"""标签页、Frame、Page基类"""
|
||||
|
||||
def __init__(self, address, tab_id=None, timeout=None):
|
||||
def __init__(self, browser, target_id=None):
|
||||
"""
|
||||
:param address: 浏览器 ip:port
|
||||
:param tab_id: 要控制的标签页id,不指定默认为激活的
|
||||
:param timeout: 超时时间(秒)
|
||||
:param browser: Chromium
|
||||
:param target_id: 要控制的target id,不指定默认为激活的标签页
|
||||
"""
|
||||
super().__init__()
|
||||
self._browser = browser
|
||||
self._is_loading = None
|
||||
self._root_id = None # object id
|
||||
self._set = None
|
||||
@ -65,35 +67,21 @@ class ChromiumBase(BasePage):
|
||||
if not hasattr(self, '_listener'):
|
||||
self._listener = None
|
||||
|
||||
if isinstance(address, int) or (isinstance(address, str) and address.isdigit()):
|
||||
address = f'127.0.0.1:{address}'
|
||||
|
||||
self._d_set_start_options(address)
|
||||
self._d_set_runtime_settings()
|
||||
self._connect_browser(tab_id)
|
||||
if timeout is not None:
|
||||
self.timeout = timeout
|
||||
|
||||
def _d_set_start_options(self, address):
|
||||
"""设置浏览器启动属性
|
||||
:param address: 'ip:port'
|
||||
:return: None
|
||||
"""
|
||||
self.address = address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
|
||||
self._connect_browser(target_id)
|
||||
|
||||
def _d_set_runtime_settings(self):
|
||||
self._timeouts = Timeout(self)
|
||||
self._load_mode = 'normal'
|
||||
pass
|
||||
|
||||
def _connect_browser(self, tab_id=None):
|
||||
def _connect_browser(self, target_id=None):
|
||||
"""连接浏览器,在第一次时运行
|
||||
:param tab_id: 要控制的标签页id,不指定默认为激活的
|
||||
:param target_id: 要控制的target id,不指定默认为激活的标签页
|
||||
:return: None
|
||||
"""
|
||||
self._is_reading = False
|
||||
|
||||
if not tab_id:
|
||||
tabs = self.browser.driver.get(f'http://{self.address}/json').json()
|
||||
if not target_id:
|
||||
tabs = self.browser._driver.get(f'http://{self.browser.address}/json').json()
|
||||
tabs = [(i['id'], i['url']) for i in tabs
|
||||
if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]
|
||||
dialog = None
|
||||
@ -101,30 +89,30 @@ class ChromiumBase(BasePage):
|
||||
for k, t in enumerate(tabs):
|
||||
if t[1] == 'chrome://privacy-sandbox-dialog/notice':
|
||||
dialog = k
|
||||
elif not tab_id:
|
||||
tab_id = t[0]
|
||||
elif not target_id:
|
||||
target_id = t[0]
|
||||
|
||||
if tab_id and dialog is not None:
|
||||
if target_id and dialog is not None:
|
||||
break
|
||||
|
||||
if dialog is not None:
|
||||
close_privacy_dialog(self, tabs[dialog][0])
|
||||
|
||||
else:
|
||||
tab_id = tabs[0][0]
|
||||
target_id = tabs[0][0]
|
||||
|
||||
self._driver_init(tab_id)
|
||||
self._driver_init(target_id)
|
||||
if self._js_ready_state == 'complete' and self._ready_state is None:
|
||||
self._get_document()
|
||||
self._ready_state = 'complete'
|
||||
|
||||
def _driver_init(self, tab_id):
|
||||
def _driver_init(self, target_id):
|
||||
"""新建页面、页面刷新、切换标签页后要进行的cdp参数初始化
|
||||
:param tab_id: 要跳转到的标签页id
|
||||
:param target_id: 要跳转到的target id
|
||||
:return: None
|
||||
"""
|
||||
self._is_loading = True
|
||||
self._driver = self.browser._get_driver(tab_id, self)
|
||||
self._driver = self.browser._get_driver(target_id, self)
|
||||
|
||||
self._alert = Alert()
|
||||
self._driver.set_callback('Page.javascriptDialogOpening', self._on_alert_open, immediate=True)
|
||||
@ -134,7 +122,7 @@ class ChromiumBase(BasePage):
|
||||
self._driver.run('Page.enable')
|
||||
self._driver.run('Emulation.setFocusEmulationEnabled', enabled=True)
|
||||
|
||||
r = self.run_cdp('Page.getFrameTree')
|
||||
r = self._run_cdp('Page.getFrameTree')
|
||||
for i in findall(r"'id': '(.*?)'", str(r)):
|
||||
self.browser._frames[i] = self.tab_id
|
||||
if not hasattr(self, '_frame_id'):
|
||||
@ -160,10 +148,10 @@ class ChromiumBase(BasePage):
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
try:
|
||||
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
timeout = end_time - perf_counter()
|
||||
timeout = 1 if timeout <= 1 else timeout
|
||||
self._root_id = self.run_cdp('DOM.resolveNode', backendNodeId=b_id,
|
||||
self._root_id = self._run_cdp('DOM.resolveNode', backendNodeId=b_id,
|
||||
_timeout=timeout)['object']['objectId']
|
||||
result = True
|
||||
break
|
||||
@ -180,7 +168,7 @@ class ChromiumBase(BasePage):
|
||||
result = False
|
||||
|
||||
if result:
|
||||
r = self.run_cdp('Page.getFrameTree')
|
||||
r = self._run_cdp('Page.getFrameTree')
|
||||
for i in findall(r"'id': '(.*?)'", str(r)):
|
||||
self.browser._frames[i] = self.tab_id
|
||||
|
||||
@ -217,7 +205,7 @@ class ChromiumBase(BasePage):
|
||||
def _onDomContentEventFired(self, **kwargs):
|
||||
"""在页面刷新、变化后重新读取页面内容"""
|
||||
if self._load_mode == 'eager':
|
||||
self.run_cdp('Page.stopLoading')
|
||||
self._run_cdp('Page.stopLoading')
|
||||
if self._get_document(self._load_end_time - perf_counter() - .1):
|
||||
self._doc_got = True
|
||||
self._ready_state = 'interactive'
|
||||
@ -242,10 +230,10 @@ class ChromiumBase(BasePage):
|
||||
if 'backendNodeId' not in kwargs:
|
||||
raise TypeError('该输入框无法接管,请改用对<input>元素输入路径的方法设置。')
|
||||
files = self._upload_list if kwargs['mode'] == 'selectMultiple' else self._upload_list[:1]
|
||||
self.run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=kwargs['backendNodeId'])
|
||||
self._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=kwargs['backendNodeId'])
|
||||
|
||||
self.driver.set_callback('Page.fileChooserOpened', None)
|
||||
self.run_cdp('Page.setInterceptFileChooserDialog', enabled=False)
|
||||
self._run_cdp('Page.setInterceptFileChooserDialog', enabled=False)
|
||||
self._upload_list = None
|
||||
|
||||
def __call__(self, locator, index=1, timeout=None):
|
||||
@ -326,6 +314,11 @@ class ChromiumBase(BasePage):
|
||||
self._rect = TabRect(self)
|
||||
return self._rect
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回timeout设置"""
|
||||
return self._timeouts.base
|
||||
|
||||
@property
|
||||
def timeouts(self):
|
||||
"""返回timeouts设置"""
|
||||
@ -347,23 +340,23 @@ class ChromiumBase(BasePage):
|
||||
@property
|
||||
def title(self):
|
||||
"""返回当前页面title"""
|
||||
return self.run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['title']
|
||||
return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['title']
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""返回当前页面url"""
|
||||
return self.run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['url']
|
||||
return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['url']
|
||||
|
||||
@property
|
||||
def _browser_url(self):
|
||||
"""用于被WebPage覆盖"""
|
||||
"""用于被MixTab覆盖"""
|
||||
return self.url
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
"""返回当前页面html文本"""
|
||||
self.wait.doc_loaded()
|
||||
return self.run_cdp('DOM.getOuterHTML', objectId=self._root_id)['outerHTML']
|
||||
return self._run_cdp('DOM.getOuterHTML', objectId=self._root_id)['outerHTML']
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
@ -381,12 +374,12 @@ class ChromiumBase(BasePage):
|
||||
@property
|
||||
def _target_id(self):
|
||||
"""返回当前标签页id"""
|
||||
return self.driver.id if not self.driver._stopped.is_set() else ''
|
||||
return self.driver.id if self.driver.is_running else ''
|
||||
|
||||
@property
|
||||
def active_ele(self):
|
||||
"""返回当前焦点所在元素"""
|
||||
return self.run_js_loaded('return document.activeElement;')
|
||||
return self._run_js_loaded('return document.activeElement;')
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
@ -396,7 +389,7 @@ class ChromiumBase(BasePage):
|
||||
@property
|
||||
def user_agent(self):
|
||||
"""返回user agent"""
|
||||
return self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
return self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
|
||||
@property
|
||||
def upload_list(self):
|
||||
@ -407,7 +400,7 @@ class ChromiumBase(BasePage):
|
||||
def _js_ready_state(self):
|
||||
"""返回js获取的ready state信息"""
|
||||
try:
|
||||
return self.run_cdp('Runtime.evaluate', expression='document.readyState;', _timeout=3)['result']['value']
|
||||
return self._run_cdp('Runtime.evaluate', expression='document.readyState;', _timeout=3)['result']['value']
|
||||
except ContextLostError:
|
||||
return None
|
||||
except TimeoutError:
|
||||
@ -419,9 +412,8 @@ class ChromiumBase(BasePage):
|
||||
:param cmd_args: 参数
|
||||
:return: 执行的结果
|
||||
"""
|
||||
ignore = cmd_args.pop('_ignore', None)
|
||||
r = self.driver.run(cmd, **cmd_args)
|
||||
return r if __ERROR__ not in r else raise_error(r, ignore)
|
||||
return r if __ERROR__ not in r else raise_error(r, user=True)
|
||||
|
||||
def run_cdp_loaded(self, cmd, **cmd_args):
|
||||
"""执行Chrome DevTools Protocol语句,执行前等待页面加载完毕
|
||||
@ -430,7 +422,27 @@ class ChromiumBase(BasePage):
|
||||
:return: 执行的结果
|
||||
"""
|
||||
self.wait.doc_loaded()
|
||||
return self.run_cdp(cmd, **cmd_args)
|
||||
r = self.driver.run(cmd, **cmd_args)
|
||||
return r if __ERROR__ not in r else raise_error(r, user=True)
|
||||
|
||||
def _run_cdp(self, cmd, **cmd_args):
|
||||
"""执行Chrome DevTools Protocol语句
|
||||
:param cmd: 协议项目
|
||||
:param cmd_args: 参数
|
||||
:return: 执行的结果
|
||||
"""
|
||||
ignore = cmd_args.pop('_ignore', None)
|
||||
r = self.driver.run(cmd, **cmd_args)
|
||||
return r if __ERROR__ not in r else raise_error(r, ignore)
|
||||
|
||||
def _run_cdp_loaded(self, cmd, **cmd_args):
|
||||
"""执行Chrome DevTools Protocol语句,执行前等待页面加载完毕
|
||||
:param cmd: 协议项目
|
||||
:param cmd_args: 参数
|
||||
:return: 执行的结果
|
||||
"""
|
||||
self.wait.doc_loaded()
|
||||
return self._run_cdp(cmd, **cmd_args)
|
||||
|
||||
def run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
@ -440,9 +452,30 @@ class ChromiumBase(BasePage):
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script设置
|
||||
:return: 运行的结果
|
||||
"""
|
||||
return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args)
|
||||
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def run_js_loaded(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码,执行前等待页面加载完毕
|
||||
:param script: js文本或js文件路径
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
:param as_expr: 是否作为表达式运行,为True时args无效
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script属性值
|
||||
:return: 运行的结果
|
||||
"""
|
||||
self.wait.doc_loaded()
|
||||
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def _run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
:param script: js文本或js文件路径
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
:param as_expr: 是否作为表达式运行,为True时args无效
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script设置
|
||||
:return: 运行的结果
|
||||
"""
|
||||
return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args)
|
||||
|
||||
def _run_js_loaded(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码,执行前等待页面加载完毕
|
||||
:param script: js文本或js文件路径
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
@ -476,29 +509,27 @@ class ChromiumBase(BasePage):
|
||||
show_errmsg=show_errmsg, timeout=timeout)
|
||||
return self._url_available
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
def cookies(self, all_domains=False, all_info=False):
|
||||
"""返回cookies信息
|
||||
:param as_dict: 为True时以dict格式返回且all_info无效,为False时返回list
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,为False时只返回name、value、domain
|
||||
:return: cookies信息
|
||||
"""
|
||||
txt = 'Storage' if all_domains else 'Network'
|
||||
cookies = self.run_cdp_loaded(f'{txt}.getCookies')['cookies']
|
||||
cookies = self._run_cdp_loaded(f'{txt}.getCookies')['cookies']
|
||||
|
||||
if as_dict:
|
||||
return {cookie['name']: cookie['value'] for cookie in cookies}
|
||||
elif all_info:
|
||||
return cookies
|
||||
if all_info:
|
||||
r = cookies
|
||||
else:
|
||||
return [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']}
|
||||
for cookie in cookies]
|
||||
r = [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} for cookie in cookies]
|
||||
|
||||
return CookiesList(r)
|
||||
|
||||
def ele(self, locator, index=1, timeout=None):
|
||||
"""获取一个符合条件的元素对象
|
||||
:param locator: 定位符或元素对象
|
||||
:param index: 获取第几个元素,从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找超时时间(秒)
|
||||
:param timeout: 查找超时时间(秒),默认与页面等待时间一致
|
||||
:return: ChromiumElement对象
|
||||
"""
|
||||
return self._ele(locator, timeout=timeout, index=index, method='ele()')
|
||||
@ -506,32 +537,37 @@ class ChromiumBase(BasePage):
|
||||
def eles(self, locator, timeout=None):
|
||||
"""获取所有符合条件的元素对象
|
||||
:param locator: 定位符或元素对象
|
||||
:param timeout: 查找超时时间(秒)
|
||||
:param timeout: 查找超时时间(秒),默认与页面等待时间一致
|
||||
:return: ChromiumElement对象组成的列表
|
||||
"""
|
||||
return self._ele(locator, timeout=timeout, index=None)
|
||||
|
||||
def s_ele(self, locator=None, index=1):
|
||||
def s_ele(self, locator=None, index=1, timeout=None):
|
||||
"""查找一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param index: 获取第几个,从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 查找元素超时时间(秒),默认与页面等待时间一致
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
return make_session_ele(self, locator, index=index, method='s_ele()')
|
||||
return (NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index})
|
||||
if locator and not self.wait.eles_loaded(locator, timeout=timeout)
|
||||
else make_session_ele(self, locator, index=index, method='s_ele()'))
|
||||
|
||||
def s_eles(self, locator):
|
||||
def s_eles(self, locator, timeout=None):
|
||||
"""查找所有符合条件的元素以SessionElement列表形式返回
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒),默认与页面等待时间一致
|
||||
:return: SessionElement对象组成的列表
|
||||
"""
|
||||
return make_session_ele(self, locator, index=None)
|
||||
return (make_session_ele(self, locator, index=None)
|
||||
if self.wait.eles_loaded(locator, timeout=timeout) else SessionElementsList())
|
||||
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
"""执行元素查找
|
||||
:param locator: 定位符或元素对象
|
||||
:param timeout: 查找超时时间(秒)
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: ChromiumElement对象或元素对象组成的列表
|
||||
"""
|
||||
@ -605,7 +641,7 @@ class ChromiumBase(BasePage):
|
||||
:return: None
|
||||
"""
|
||||
self._is_loading = True
|
||||
self.run_cdp('Page.reload', ignoreCache=ignore_cache)
|
||||
self._run_cdp('Page.reload', ignoreCache=ignore_cache)
|
||||
self.wait.load_start()
|
||||
|
||||
def forward(self, steps=1):
|
||||
@ -630,7 +666,7 @@ class ChromiumBase(BasePage):
|
||||
if steps == 0:
|
||||
return
|
||||
|
||||
history = self.run_cdp('Page.getNavigationHistory')
|
||||
history = self._run_cdp('Page.getNavigationHistory')
|
||||
index = history['currentIndex']
|
||||
history = history['entries']
|
||||
direction = 1 if steps > 0 else -1
|
||||
@ -646,12 +682,12 @@ class ChromiumBase(BasePage):
|
||||
|
||||
if nid:
|
||||
self._is_loading = True
|
||||
self.run_cdp('Page.navigateToHistoryEntry', entryId=nid)
|
||||
self._run_cdp('Page.navigateToHistoryEntry', entryId=nid)
|
||||
|
||||
def stop_loading(self):
|
||||
"""页面停止加载"""
|
||||
try:
|
||||
self.run_cdp('Page.stopLoading')
|
||||
self._run_cdp('Page.stopLoading')
|
||||
end_time = perf_counter() + 5
|
||||
while self._ready_state != 'complete' and perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
@ -669,7 +705,7 @@ class ChromiumBase(BasePage):
|
||||
return
|
||||
ele = self._ele(loc_or_ele, raise_err=False)
|
||||
if ele:
|
||||
self.run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError)
|
||||
self._run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError)
|
||||
|
||||
def add_ele(self, html_or_info, insert_to=None, before=None):
|
||||
"""新建一个元素
|
||||
@ -725,7 +761,7 @@ class ChromiumBase(BasePage):
|
||||
else:
|
||||
raise TypeError('html_or_info参数必须是html文本或tuple,tuple格式为(tag, {name: value})。')
|
||||
|
||||
ele = self.run_js(js, *args)
|
||||
ele = self._run_js(js, *args)
|
||||
return ele
|
||||
|
||||
def get_frame(self, loc_ind_ele, timeout=None):
|
||||
@ -734,41 +770,7 @@ class ChromiumBase(BasePage):
|
||||
:param timeout: 查找元素超时时间(秒)
|
||||
:return: ChromiumFrame对象
|
||||
"""
|
||||
if isinstance(loc_ind_ele, str):
|
||||
if not is_loc(loc_ind_ele):
|
||||
xpath = f'xpath://*[(name()="iframe" or name()="frame") and ' \
|
||||
f'(@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]'
|
||||
else:
|
||||
xpath = loc_ind_ele
|
||||
ele = self._ele(xpath, timeout=timeout)
|
||||
if ele and ele._type != 'ChromiumFrame':
|
||||
raise TypeError('该定位符不是指向frame元素。')
|
||||
r = ele
|
||||
|
||||
elif isinstance(loc_ind_ele, tuple):
|
||||
ele = self._ele(loc_ind_ele, timeout=timeout)
|
||||
if ele and ele._type != 'ChromiumFrame':
|
||||
raise TypeError('该定位符不是指向frame元素。')
|
||||
r = ele
|
||||
|
||||
elif isinstance(loc_ind_ele, int):
|
||||
if loc_ind_ele == 0:
|
||||
loc_ind_ele = 1
|
||||
elif loc_ind_ele < 0:
|
||||
loc_ind_ele = f'last()+{loc_ind_ele}+1'
|
||||
xpath = f'xpath:(//*[name()="frame" or name()="iframe"])[{loc_ind_ele}]'
|
||||
r = self._ele(xpath, timeout=timeout)
|
||||
|
||||
elif loc_ind_ele._type == 'ChromiumFrame':
|
||||
r = loc_ind_ele
|
||||
|
||||
else:
|
||||
raise TypeError('必须传入定位符、iframe序号、id、name、ChromiumFrame对象其中之一。')
|
||||
|
||||
if isinstance(r, NoneElement):
|
||||
r.method = 'get_frame()'
|
||||
r.args = {'loc_ind_ele': loc_ind_ele}
|
||||
return r
|
||||
return get_frame(self, loc_ind_ele=loc_ind_ele, timeout=timeout)
|
||||
|
||||
def get_frames(self, locator=None, timeout=None):
|
||||
"""获取所有符合条件的frame对象
|
||||
@ -786,7 +788,7 @@ class ChromiumBase(BasePage):
|
||||
:return: sessionStorage一个或所有项内容
|
||||
"""
|
||||
js = f'sessionStorage.getItem("{item}")' if item else 'sessionStorage'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
return self._run_js_loaded(js, as_expr=True)
|
||||
|
||||
def local_storage(self, item=None):
|
||||
"""返回localStorage信息,不设置item则获取全部
|
||||
@ -794,7 +796,7 @@ class ChromiumBase(BasePage):
|
||||
:return: localStorage一个或所有项内容
|
||||
"""
|
||||
js = f'localStorage.getItem("{item}")' if item else 'localStorage'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
return self._run_js_loaded(js, as_expr=True)
|
||||
|
||||
def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None,
|
||||
full_page=False, left_top=None, right_bottom=None):
|
||||
@ -816,7 +818,7 @@ class ChromiumBase(BasePage):
|
||||
:param script: js文本
|
||||
:return: 添加的脚本的id
|
||||
"""
|
||||
js_id = self.run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script,
|
||||
js_id = self._run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script,
|
||||
includeCommandLineAPI=True)['identifier']
|
||||
self._init_jss.append(js_id)
|
||||
return js_id
|
||||
@ -828,11 +830,11 @@ class ChromiumBase(BasePage):
|
||||
"""
|
||||
if script_id is None:
|
||||
for js_id in self._init_jss:
|
||||
self.run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=js_id)
|
||||
self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=js_id)
|
||||
self._init_jss.clear()
|
||||
|
||||
elif script_id in self._init_jss:
|
||||
self.run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=script_id)
|
||||
self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=script_id)
|
||||
self._init_jss.remove(script_id)
|
||||
|
||||
def clear_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True):
|
||||
@ -844,24 +846,25 @@ class ChromiumBase(BasePage):
|
||||
:return: None
|
||||
"""
|
||||
if session_storage or local_storage:
|
||||
self.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self.run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey']
|
||||
self._run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey']
|
||||
if session_storage:
|
||||
self.run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': False})
|
||||
self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': False})
|
||||
if local_storage:
|
||||
self.run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True})
|
||||
self.run_cdp_loaded('DOMStorage.disable')
|
||||
self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True})
|
||||
self._run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
if cache:
|
||||
self.run_cdp_loaded('Network.clearBrowserCache')
|
||||
self._run_cdp_loaded('Network.clearBrowserCache')
|
||||
|
||||
if cookies:
|
||||
self.run_cdp_loaded('Network.clearBrowserCookies')
|
||||
self._run_cdp_loaded('Network.clearBrowserCookies')
|
||||
|
||||
def disconnect(self):
|
||||
"""断开与页面的连接,不关闭页面"""
|
||||
if self._driver:
|
||||
self.browser.stop_driver(self._driver)
|
||||
self._driver.stop()
|
||||
self.browser._all_drivers.get(self._driver.id, set()).discard(self._driver)
|
||||
|
||||
def reconnect(self, wait=0):
|
||||
"""断开与页面原来的页面,重新建立连接
|
||||
@ -987,7 +990,7 @@ class ChromiumBase(BasePage):
|
||||
err = None
|
||||
end_time = perf_counter() + timeout
|
||||
try:
|
||||
result = self.run_cdp('Page.navigate', frameId=self._frame_id, url=to_url, _timeout=timeout)
|
||||
result = self._run_cdp('Page.navigate', frameId=self._frame_id, url=to_url, _timeout=timeout)
|
||||
if 'errorText' in result:
|
||||
err = ConnectionError(result['errorText'])
|
||||
except TimeoutError:
|
||||
@ -1087,8 +1090,8 @@ class ChromiumBase(BasePage):
|
||||
|
||||
v = not (location_in_viewport(self, x, y) and
|
||||
location_in_viewport(self, right_bottom[0], right_bottom[1]))
|
||||
if v and (self.run_js('return document.body.scrollHeight > window.innerHeight;') and
|
||||
not self.run_js('return document.body.scrollWidth > window.innerWidth;')):
|
||||
if v and (self._run_js('return document.body.scrollHeight > window.innerHeight;') and
|
||||
not self._run_js('return document.body.scrollWidth > window.innerWidth;')):
|
||||
x += 10
|
||||
|
||||
vp = {'x': x, 'y': y, 'width': w, 'height': h, 'scale': 1}
|
||||
@ -1099,7 +1102,7 @@ class ChromiumBase(BasePage):
|
||||
|
||||
if pic_type == 'jpeg':
|
||||
args['quality'] = 100
|
||||
png = self.run_cdp_loaded('Page.captureScreenshot', **args)['data']
|
||||
png = self._run_cdp_loaded('Page.captureScreenshot', **args)['data']
|
||||
|
||||
if as_base64:
|
||||
return png
|
||||
@ -1119,15 +1122,12 @@ class ChromiumBase(BasePage):
|
||||
class Timeout(object):
|
||||
"""用于保存d模式timeout信息的类"""
|
||||
|
||||
def __init__(self, page, base=None, page_load=None, script=None, implicit=None):
|
||||
def __init__(self, base=None, page_load=None, script=None):
|
||||
"""
|
||||
:param page: ChromiumBase页面
|
||||
:param base: 默认超时时间
|
||||
:param page_load: 页面加载超时时间
|
||||
:param script: js超时时间
|
||||
"""
|
||||
self._page = page
|
||||
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.script = 30 if script is None else script
|
||||
@ -1135,6 +1135,10 @@ class Timeout(object):
|
||||
def __repr__(self):
|
||||
return str({'base': self.base, 'page_load': self.page_load, 'script': self.script})
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
return {'base': self.base, 'page_load': self.page_load, 'script': self.script}
|
||||
|
||||
|
||||
class Alert(object):
|
||||
"""用于保存alert信息的类"""
|
||||
@ -1158,6 +1162,7 @@ def close_privacy_dialog(page, tid):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
print('ooo')
|
||||
driver = page.browser._get_driver(tid)
|
||||
driver.run('Runtime.enable')
|
||||
driver.run('DOM.enable')
|
||||
|
@ -8,12 +8,13 @@
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple, List, Any, Optional, Literal
|
||||
|
||||
from .chromium_tab import ChromiumTab
|
||||
from .tabs import ChromiumTab, MixTab
|
||||
from .._base.base import BasePage
|
||||
from .._base.browser import Browser
|
||||
from .._base.browser import Chromium
|
||||
from .._base.driver import Driver
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
@ -31,13 +32,10 @@ PIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True]
|
||||
|
||||
class ChromiumBase(BasePage):
|
||||
def __init__(self,
|
||||
address: Union[str, int],
|
||||
tab_id: str = None,
|
||||
timeout: float = None):
|
||||
self._browser: Browser = ...
|
||||
self._page: ChromiumPage = ...
|
||||
self.tab: Union[ChromiumPage, ChromiumTab] = ...
|
||||
self.address: str = ...
|
||||
browser: Chromium,
|
||||
tab_id: str = None):
|
||||
self._tab: Union[ChromiumTab, MixTab, ChromiumFrame] = ...
|
||||
self._browser: Chromium = ...
|
||||
self._driver: Driver = ...
|
||||
self._frame_id: str = ...
|
||||
self._is_reading: bool = ...
|
||||
@ -65,9 +63,9 @@ class ChromiumBase(BasePage):
|
||||
self._rect: TabRect = ...
|
||||
self._type: str = ...
|
||||
|
||||
def _connect_browser(self, tab_id: str = None) -> None: ...
|
||||
def _connect_browser(self, target_id: str = None) -> None: ...
|
||||
|
||||
def _driver_init(self, tab_id: str) -> None: ...
|
||||
def _driver_init(self, target_id: str) -> None: ...
|
||||
|
||||
def _get_document(self, timeout: float = 10) -> bool: ...
|
||||
|
||||
@ -91,7 +89,7 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def _wait_to_stop(self): ...
|
||||
|
||||
def _d_set_start_options(self, address) -> None: ...
|
||||
# def _d_set_start_options(self, address) -> None: ...
|
||||
|
||||
def _d_set_runtime_settings(self) -> None: ...
|
||||
|
||||
@ -104,7 +102,7 @@ class ChromiumBase(BasePage):
|
||||
def _js_ready_state(self) -> str: ...
|
||||
|
||||
@property
|
||||
def browser(self) -> Browser: ...
|
||||
def browser(self) -> Chromium: ...
|
||||
|
||||
@property
|
||||
def title(self) -> str: ...
|
||||
@ -145,6 +143,9 @@ class ChromiumBase(BasePage):
|
||||
@property
|
||||
def rect(self) -> TabRect: ...
|
||||
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
@property
|
||||
def timeouts(self) -> Timeout: ...
|
||||
|
||||
@ -173,13 +174,16 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def _run_js(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def _run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
|
||||
|
||||
def run_async_js(self, script: Union[str, Path], *args, as_expr: bool = False) -> None: ...
|
||||
|
||||
def get(self, url: str, show_errmsg: bool = False, retry: int = None,
|
||||
interval: float = None, timeout: float = None) -> Union[None, bool]: ...
|
||||
|
||||
def cookies(self, as_dict: bool = False, all_domains: bool = False, all_info: bool = False) -> Union[
|
||||
list, dict]: ...
|
||||
def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ...
|
||||
|
||||
def ele(self,
|
||||
locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],
|
||||
@ -192,9 +196,12 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def s_ele(self,
|
||||
locator: Union[Tuple[str, str], str] = None,
|
||||
index: int = 1) -> SessionElement: ...
|
||||
index: int = 1,
|
||||
timeout: float = None) -> SessionElement: ...
|
||||
|
||||
def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ...
|
||||
def s_eles(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
timeout: float = None) -> SessionElementsList: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],
|
||||
@ -220,7 +227,9 @@ class ChromiumBase(BasePage):
|
||||
insert_to: Union[ChromiumElement, str, Tuple[str, str], None] = None,
|
||||
before: Union[ChromiumElement, str, Tuple[str, str], None] = None) -> ChromiumElement: ...
|
||||
|
||||
def get_frame(self, loc_ind_ele: Union[str, int, tuple, ChromiumFrame], timeout: float = None) -> ChromiumFrame: ...
|
||||
def get_frame(self,
|
||||
loc_ind_ele: Union[str, int, tuple, ChromiumFrame, ChromiumElement],
|
||||
timeout: float = None) -> ChromiumFrame: ...
|
||||
|
||||
def get_frames(self, locator: Union[str, tuple] = None, timeout: float = None) -> List[ChromiumFrame]: ...
|
||||
|
||||
@ -228,6 +237,10 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ...
|
||||
|
||||
def _run_cdp(self, cmd: str, **cmd_args) -> dict: ...
|
||||
|
||||
def _run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ...
|
||||
|
||||
def session_storage(self, item: str = None) -> Union[str, dict, None]: ...
|
||||
|
||||
def local_storage(self, item: str = None) -> Union[str, dict, None]: ...
|
||||
@ -267,12 +280,14 @@ class ChromiumBase(BasePage):
|
||||
|
||||
class Timeout(object):
|
||||
|
||||
def __init__(self, page: ChromiumBase, base=None, page_load=None, script=None):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, base=None, page_load=None, script=None):
|
||||
self.base: float = ...
|
||||
self.page_load: float = ...
|
||||
self.script: float = ...
|
||||
|
||||
@property
|
||||
def as_dict(self) -> dict: ...
|
||||
|
||||
|
||||
class Alert(object):
|
||||
|
||||
|
@ -27,42 +27,26 @@ class ChromiumFrame(ChromiumBase):
|
||||
:param ele: frame所在元素
|
||||
:param info: frame所在元素信息
|
||||
"""
|
||||
if owner._type in ('ChromiumPage', 'WebPage'):
|
||||
self._page = self._target_page = self.tab = owner
|
||||
self._browser = owner.browser
|
||||
else: # Tab、Frame
|
||||
self._page = owner.page
|
||||
self._browser = self._page.browser
|
||||
self._tab = owner._tab
|
||||
self._target_page = owner
|
||||
self.tab = owner.tab if owner._type == 'ChromiumFrame' else owner
|
||||
|
||||
self.address = owner.address
|
||||
self._tab_id = owner.tab_id
|
||||
self._backend_id = ele._backend_id
|
||||
self._frame_ele = ele
|
||||
self._states = None
|
||||
self._reloading = False
|
||||
|
||||
node = info['node'] if not info else owner.run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node']
|
||||
node = info['node'] if not info else owner._run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node']
|
||||
self._frame_id = node['frameId']
|
||||
if self._is_inner_frame():
|
||||
self._is_diff_domain = False
|
||||
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||
super().__init__(owner.address, owner.tab_id, owner.timeout)
|
||||
super().__init__(owner.browser, owner.driver.id)
|
||||
else:
|
||||
self._is_diff_domain = True
|
||||
delattr(self, '_frame_id')
|
||||
super().__init__(owner.address, node['frameId'], owner.timeout)
|
||||
obj_id = super().run_js('document;', as_expr=True)['objectId']
|
||||
super().__init__(owner.browser, node['frameId'])
|
||||
obj_id = super()._run_js('document;', as_expr=True)['objectId']
|
||||
self.doc_ele = ChromiumElement(self, obj_id=obj_id)
|
||||
|
||||
self._rect = None
|
||||
self._type = 'ChromiumFrame'
|
||||
# end_time = perf_counter() + 2
|
||||
# while perf_counter() < end_time:
|
||||
# if self.url not in (None, 'about:blank'):
|
||||
# break
|
||||
# sleep(.1)
|
||||
|
||||
def __call__(self, locator, index=1, timeout=None):
|
||||
"""在内部查找元素
|
||||
@ -90,16 +74,16 @@ class ChromiumFrame(ChromiumBase):
|
||||
self._download_path = self._target_page.download_path
|
||||
self._load_mode = self._target_page._load_mode if not self._is_diff_domain else 'normal'
|
||||
|
||||
def _driver_init(self, tab_id, is_init=True):
|
||||
def _driver_init(self, target_id, is_init=True):
|
||||
"""避免出现服务器500错误
|
||||
:param tab_id: 要跳转到的标签页id
|
||||
:param target_id: 要跳转到的target id
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
super()._driver_init(tab_id)
|
||||
super()._driver_init(target_id)
|
||||
except:
|
||||
self.browser.driver.get(f'http://{self.address}/json')
|
||||
super()._driver_init(tab_id)
|
||||
self.browser._driver.get(f'http://{self._browser.address}/json')
|
||||
super()._driver_init(target_id)
|
||||
self._driver.set_callback('Inspector.detached', self._onInspectorDetached, immediate=True)
|
||||
self._driver.set_callback('Page.frameDetached', None)
|
||||
self._driver.set_callback('Page.frameDetached', self._onFrameDetached, immediate=True)
|
||||
@ -116,7 +100,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
self._frame_ele = ChromiumElement(self._target_page, backend_id=self._backend_id)
|
||||
end_time = perf_counter() + 2
|
||||
while perf_counter() < end_time:
|
||||
node = self._target_page.run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node']
|
||||
node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node']
|
||||
if 'frameId' in node:
|
||||
break
|
||||
sleep(.05)
|
||||
@ -132,16 +116,16 @@ class ChromiumFrame(ChromiumBase):
|
||||
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||
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)
|
||||
self._listener._to_target(self._target_page.tab_id, self._browser.address, self)
|
||||
super().__init__(self._browser, self._target_page.tab_id)
|
||||
# self.driver._debug = d_debug
|
||||
|
||||
else:
|
||||
self._is_diff_domain = True
|
||||
if self._listener:
|
||||
self._listener._to_target(node['frameId'], self.address, self)
|
||||
self._listener._to_target(node['frameId'], self._browser.address, self)
|
||||
end_time = perf_counter() + self.timeouts.page_load
|
||||
super().__init__(self.address, node['frameId'], self._target_page.timeout)
|
||||
super().__init__(self._browser, node['frameId'])
|
||||
timeout = end_time - perf_counter()
|
||||
if timeout <= 0:
|
||||
timeout = .5
|
||||
@ -161,17 +145,17 @@ class ChromiumFrame(ChromiumBase):
|
||||
self._is_reading = True
|
||||
try:
|
||||
if self._is_diff_domain is False:
|
||||
node = self._target_page.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
|
||||
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||
|
||||
else:
|
||||
timeout = max(timeout, 2)
|
||||
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
self.doc_ele = ChromiumElement(self, backend_id=b_id)
|
||||
|
||||
self._root_id = self.doc_ele._obj_id
|
||||
|
||||
r = self.run_cdp('Page.getFrameTree')
|
||||
r = self._run_cdp('Page.getFrameTree')
|
||||
for i in findall(r"'id': '(.*?)'", str(r)):
|
||||
self.browser._frames[i] = self.tab_id
|
||||
return True
|
||||
@ -250,11 +234,6 @@ class ChromiumFrame(ChromiumBase):
|
||||
"""返回cdp中的node id"""
|
||||
return self.frame_ele._node_id
|
||||
|
||||
@property
|
||||
def page(self):
|
||||
"""返回所属Page对象"""
|
||||
return self._page
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
"""返回所属页面对象"""
|
||||
@ -274,7 +253,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
def url(self):
|
||||
"""返回frame当前访问的url"""
|
||||
try:
|
||||
return self.doc_ele.run_js('return this.location.href;')
|
||||
return self.doc_ele._run_js('return this.location.href;')
|
||||
except JavaScriptError:
|
||||
return None
|
||||
|
||||
@ -282,14 +261,14 @@ class ChromiumFrame(ChromiumBase):
|
||||
def html(self):
|
||||
"""返回元素outerHTML文本"""
|
||||
tag = self.tag
|
||||
out_html = self._target_page.run_cdp('DOM.getOuterHTML', backendNodeId=self.frame_ele._backend_id)['outerHTML']
|
||||
out_html = self._target_page._run_cdp('DOM.getOuterHTML', backendNodeId=self.frame_ele._backend_id)['outerHTML']
|
||||
sign = search(rf'<{tag}.*?>', out_html, DOTALL).group(0)
|
||||
return f'{sign}{self.inner_html}</{tag}>'
|
||||
|
||||
@property
|
||||
def inner_html(self):
|
||||
"""返回元素innerHTML文本"""
|
||||
return self.doc_ele.run_js('return this.documentElement.outerHTML;')
|
||||
return self.doc_ele._run_js('return this.documentElement.outerHTML;')
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
@ -305,7 +284,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
@property
|
||||
def active_ele(self):
|
||||
"""返回当前焦点所在元素"""
|
||||
return self.doc_ele.run_js('return this.activeElement;')
|
||||
return self.doc_ele._run_js('return this.activeElement;')
|
||||
|
||||
@property
|
||||
def xpath(self):
|
||||
@ -317,15 +296,30 @@ class ChromiumFrame(ChromiumBase):
|
||||
"""返回frame的css selector绝对路径"""
|
||||
return self.frame_ele.css_path
|
||||
|
||||
@property
|
||||
def tab(self):
|
||||
"""返回frame所在tab的id"""
|
||||
return self._tab
|
||||
|
||||
@property
|
||||
def tab_id(self):
|
||||
"""返回frame所在tab的id"""
|
||||
return self._tab_id
|
||||
return self.tab.tab_id
|
||||
|
||||
@property
|
||||
def download_path(self):
|
||||
return self._download_path
|
||||
|
||||
@property
|
||||
def sr(self):
|
||||
"""返回iframe的shadow-root元素对象"""
|
||||
return self.frame_ele.sr
|
||||
|
||||
@property
|
||||
def shadow_root(self):
|
||||
"""返回iframe的shadow-root元素对象"""
|
||||
return self.frame_ele.sr
|
||||
|
||||
@property
|
||||
def _js_ready_state(self):
|
||||
"""返回当前页面加载状态,'loading' 'interactive' 'complete'"""
|
||||
@ -334,18 +328,18 @@ class ChromiumFrame(ChromiumBase):
|
||||
|
||||
else:
|
||||
try:
|
||||
return self.doc_ele.run_js('return this.readyState;')
|
||||
return self.doc_ele._run_js('return this.readyState;')
|
||||
except ContextLostError:
|
||||
try:
|
||||
node = self.run_cdp('DOM.describeNode', backendNodeId=self.frame_ele._backend_id)['node']
|
||||
node = self._run_cdp('DOM.describeNode', backendNodeId=self.frame_ele._backend_id)['node']
|
||||
doc = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||
return doc.run_js('return this.readyState;')
|
||||
return doc._run_js('return this.readyState;')
|
||||
except:
|
||||
return None
|
||||
|
||||
def refresh(self):
|
||||
"""刷新frame页面"""
|
||||
self.doc_ele.run_js('this.location.reload();')
|
||||
self.doc_ele._run_js('this.location.reload();')
|
||||
|
||||
def property(self, name):
|
||||
"""返回frame元素一个property属性值
|
||||
@ -369,6 +363,16 @@ class ChromiumFrame(ChromiumBase):
|
||||
self.frame_ele.remove_attr(name)
|
||||
|
||||
def run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
:param script: js文本
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
:param as_expr: 是否作为表达式运行,为True时args无效
|
||||
:param timeout: js超时时间(秒),为None则使用页面timeouts.script设置
|
||||
:return: 运行的结果
|
||||
"""
|
||||
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def _run_js(self, script, *args, as_expr=False, timeout=None):
|
||||
"""运行javascript代码
|
||||
:param script: js文本
|
||||
:param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]...
|
||||
@ -377,9 +381,9 @@ class ChromiumFrame(ChromiumBase):
|
||||
:return: 运行的结果
|
||||
"""
|
||||
if script.startswith('this.scrollIntoView'):
|
||||
return self.frame_ele.run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
return self.frame_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
else:
|
||||
return self.doc_ele.run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
return self.doc_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)
|
||||
|
||||
def parent(self, level_or_loc=1, index=1):
|
||||
"""返回上面某一级父元素,可指定层数或用查询语法定位
|
||||
@ -542,12 +546,12 @@ class ChromiumFrame(ChromiumBase):
|
||||
img.style.setProperty("position","fixed");
|
||||
arguments[0].insertBefore(img, this);
|
||||
return img;'''
|
||||
new_ele = first_child.run_js(js, body)
|
||||
new_ele = first_child._run_js(js, body)
|
||||
new_ele.scroll.to_see(center=True)
|
||||
top = int(self.frame_ele.style('border-top').split('px')[0])
|
||||
left = int(self.frame_ele.style('border-left').split('px')[0])
|
||||
|
||||
r = self.tab.run_cdp('Page.getLayoutMetrics')['visualViewport']
|
||||
r = self.tab._run_cdp('Page.getLayoutMetrics')['visualViewport']
|
||||
sx = r['pageX']
|
||||
sy = r['pageY']
|
||||
r = self.tab.get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64,
|
||||
@ -561,7 +565,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
:param locator: 定位符或元素对象
|
||||
:param timeout: 查找超时时间(秒)
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: ChromiumElement对象
|
||||
"""
|
||||
@ -573,4 +577,4 @@ class ChromiumFrame(ChromiumBase):
|
||||
|
||||
def _is_inner_frame(self):
|
||||
"""返回当前frame是否同域"""
|
||||
return self._frame_id in str(self._target_page.run_cdp('Page.getFrameTree')['frameTree'])
|
||||
return self._frame_id in str(self._target_page._run_cdp('Page.getFrameTree')['frameTree'])
|
||||
|
@ -9,10 +9,8 @@ from pathlib import Path
|
||||
from typing import Union, Tuple, List, Any, Optional
|
||||
|
||||
from .chromium_base import ChromiumBase
|
||||
from .chromium_page import ChromiumPage
|
||||
from .chromium_tab import ChromiumTab
|
||||
from .web_page import WebPage
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .tabs import ChromiumTab, MixTab
|
||||
from .._elements.chromium_element import ChromiumElement, ShadowRoot
|
||||
from .._functions.elements import ChromiumElementsList
|
||||
from .._units.listener import FrameListener
|
||||
from .._units.rect import FrameRect
|
||||
@ -25,13 +23,11 @@ from .._units.waiter import FrameWaiter
|
||||
class ChromiumFrame(ChromiumBase):
|
||||
|
||||
def __init__(self,
|
||||
owner: Union[ChromiumPage, WebPage, ChromiumTab, ChromiumFrame],
|
||||
owner: Union[ChromiumTab, ChromiumFrame],
|
||||
ele: ChromiumElement,
|
||||
info: dict = None):
|
||||
self._target_page: ChromiumBase = ...
|
||||
self._page: ChromiumPage = ...
|
||||
self.tab: Union[ChromiumPage, ChromiumTab] = ...
|
||||
self._tab_id: str = ...
|
||||
self._target_page: Union[ChromiumTab, ChromiumFrame] = ...
|
||||
self._tab: Union[MixTab, ChromiumTab] = ...
|
||||
self._set: ChromiumFrameSetter = ...
|
||||
self._frame_ele: ChromiumElement = ...
|
||||
self._backend_id: int = ...
|
||||
@ -40,7 +36,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
self.doc_ele: ChromiumElement = ...
|
||||
self._states: FrameStates = ...
|
||||
self._reloading: bool = ...
|
||||
self._rect: FrameRect = ...
|
||||
self._rect: Optional[FrameRect] = ...
|
||||
self._listener: FrameListener = ...
|
||||
|
||||
def __call__(self,
|
||||
@ -56,7 +52,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
|
||||
def _d_set_runtime_settings(self) -> None: ...
|
||||
|
||||
def _driver_init(self, tab_id: str) -> None: ...
|
||||
def _driver_init(self, target_id: str, is_init: bool = True) -> None: ...
|
||||
|
||||
def _reload(self) -> None: ...
|
||||
|
||||
@ -66,9 +62,6 @@ class ChromiumFrame(ChromiumBase):
|
||||
|
||||
def _onInspectorDetached(self, **kwargs): ...
|
||||
|
||||
@property
|
||||
def page(self) -> Union[ChromiumPage, WebPage]: ...
|
||||
|
||||
@property
|
||||
def owner(self) -> ChromiumBase: ...
|
||||
|
||||
@ -126,12 +119,21 @@ class ChromiumFrame(ChromiumBase):
|
||||
@property
|
||||
def wait(self) -> FrameWaiter: ...
|
||||
|
||||
@property
|
||||
def tab(self) -> Union[ChromiumTab, MixTab]: ...
|
||||
|
||||
@property
|
||||
def tab_id(self) -> str: ...
|
||||
|
||||
@property
|
||||
def download_path(self) -> str: ...
|
||||
|
||||
@property
|
||||
def sr(self) -> Union[None, ShadowRoot]: ...
|
||||
|
||||
@property
|
||||
def shadow_root(self) -> Union[None, ShadowRoot]: ...
|
||||
|
||||
def refresh(self) -> None: ...
|
||||
|
||||
def property(self, name: str) -> Union[str, None]: ...
|
||||
@ -146,6 +148,12 @@ class ChromiumFrame(ChromiumBase):
|
||||
as_expr: bool = False,
|
||||
timeout: float = None) -> Any: ...
|
||||
|
||||
def _run_js(self,
|
||||
script: str,
|
||||
*args,
|
||||
as_expr: bool = False,
|
||||
timeout: float = None) -> Any: ...
|
||||
|
||||
def parent(self,
|
||||
level_or_loc: Union[Tuple[str, str], str, int] = 1,
|
||||
index: int = 1) -> ChromiumElement: ...
|
||||
|
@ -5,23 +5,13 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from time import sleep, perf_counter
|
||||
from time import sleep
|
||||
|
||||
from requests import Session
|
||||
|
||||
from .._base.browser import Browser
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._functions.browser import connect_browser
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import PortFinder
|
||||
from .._base.browser import Chromium
|
||||
from .._functions.web import save_page
|
||||
from .._pages.chromium_base import ChromiumBase, Timeout
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._units.setter import ChromiumPageSetter
|
||||
from .._units.waiter import PageWaiter
|
||||
from ..errors import BrowserConnectError
|
||||
|
||||
|
||||
class ChromiumPage(ChromiumBase):
|
||||
@ -34,19 +24,16 @@ class ChromiumPage(ChromiumBase):
|
||||
:param tab_id: 要控制的标签页id,不指定默认为激活的
|
||||
:param timeout: 超时时间(秒)
|
||||
"""
|
||||
opt = handle_options(addr_or_opts)
|
||||
is_exist, browser_id = run_browser(opt)
|
||||
if browser_id in cls._PAGES:
|
||||
r = cls._PAGES[browser_id]
|
||||
browser = Chromium(addr_or_opts=addr_or_opts)
|
||||
if browser.id in cls._PAGES:
|
||||
r = cls._PAGES[browser.id]
|
||||
while not hasattr(r, '_frame_id'):
|
||||
sleep(.1)
|
||||
return r
|
||||
|
||||
r = object.__new__(cls)
|
||||
r._chromium_options = opt
|
||||
r._is_exist = is_exist
|
||||
r._browser_id = browser_id
|
||||
r.address = opt.address
|
||||
cls._PAGES[browser_id] = r
|
||||
r._browser = browser
|
||||
cls._PAGES[browser.id] = r
|
||||
return r
|
||||
|
||||
def __init__(self, addr_or_opts=None, tab_id=None, timeout=None):
|
||||
@ -59,49 +46,19 @@ class ChromiumPage(ChromiumBase):
|
||||
return
|
||||
self._created = True
|
||||
|
||||
self._page = self
|
||||
self.tab = self
|
||||
self._run_browser()
|
||||
super().__init__(self.address, tab_id)
|
||||
super().__init__(self.browser, tab_id)
|
||||
self._type = 'ChromiumPage'
|
||||
self._lock = Lock()
|
||||
self.set.timeouts(base=timeout)
|
||||
self._page_init()
|
||||
|
||||
def _run_browser(self):
|
||||
"""连接浏览器"""
|
||||
self._browser = Browser(self._chromium_options.address, self._browser_id, self)
|
||||
r = self._browser.run_cdp('Browser.getVersion')
|
||||
self._browser_version = r['product']
|
||||
if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower():
|
||||
self._browser.quit(3)
|
||||
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'})
|
||||
bid = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
||||
self._browser = Browser(self._chromium_options.address, bid, self)
|
||||
ws.close()
|
||||
s.close()
|
||||
self._tab = self
|
||||
|
||||
def _d_set_runtime_settings(self):
|
||||
"""设置运行时用到的属性"""
|
||||
self._timeouts = Timeout(self, page_load=self._chromium_options.timeouts['page_load'],
|
||||
script=self._chromium_options.timeouts['script'],
|
||||
base=self._chromium_options.timeouts['base'])
|
||||
if self._chromium_options.timeouts['base'] is not None:
|
||||
self._timeout = self._chromium_options.timeouts['base']
|
||||
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):
|
||||
"""浏览器相关设置"""
|
||||
self._browser.connect_to_page()
|
||||
|
||||
# ----------挂件----------
|
||||
self._timeouts = self.browser.timeouts
|
||||
self._load_mode = self.browser._load_mode
|
||||
self._download_path = self.browser.download_path
|
||||
self.retry_times = self.browser.retry_times
|
||||
self.retry_interval = self.browser.retry_interval
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
@ -138,7 +95,7 @@ class ChromiumPage(ChromiumBase):
|
||||
def latest_tab(self):
|
||||
"""返回最新的标签页,最新标签页指最后创建或最后被激活的
|
||||
当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id"""
|
||||
return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj)
|
||||
return self.browser.latest_tab
|
||||
|
||||
@property
|
||||
def process_id(self):
|
||||
@ -148,7 +105,12 @@ class ChromiumPage(ChromiumBase):
|
||||
@property
|
||||
def browser_version(self):
|
||||
"""返回所控制的浏览器版本号"""
|
||||
return self._browser_version
|
||||
return self._browser.version
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""返回浏览器地址ip:port"""
|
||||
return self.browser.address
|
||||
|
||||
def save(self, path=None, name=None, as_pdf=False, **kwargs):
|
||||
"""把当前页面保存为文件,如果path和name参数都为None,只返回文本
|
||||
@ -169,34 +131,7 @@ class ChromiumPage(ChromiumBase):
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象
|
||||
"""
|
||||
if id_or_num is not None:
|
||||
if isinstance(id_or_num, str):
|
||||
id_or_num = id_or_num
|
||||
elif isinstance(id_or_num, int):
|
||||
id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]
|
||||
elif isinstance(id_or_num, ChromiumTab):
|
||||
if as_id:
|
||||
return id_or_num.tab_id
|
||||
elif Settings.singleton_tab_obj:
|
||||
return id_or_num
|
||||
else:
|
||||
return self.get_tab(id_or_num.tab_id)
|
||||
|
||||
elif title == url == tab_type is None:
|
||||
id_or_num = self.tab_id
|
||||
|
||||
else:
|
||||
id_or_num = self._browser.find_tabs(title, url, tab_type)
|
||||
if id_or_num:
|
||||
id_or_num = id_or_num[0]['id']
|
||||
else:
|
||||
return None
|
||||
|
||||
if as_id:
|
||||
return id_or_num
|
||||
|
||||
with self._lock:
|
||||
return ChromiumTab(self, id_or_num)
|
||||
return self.browser.get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, as_id=as_id)
|
||||
|
||||
def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
@ -206,10 +141,7 @@ class ChromiumPage(ChromiumBase):
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象组成的列表
|
||||
"""
|
||||
if as_id:
|
||||
return [tab['id'] for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
with self._lock:
|
||||
return [ChromiumTab(self, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
return self.browser.get_tabs(title=title, url=url, tab_type=tab_type, as_id=as_id)
|
||||
|
||||
def new_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
@ -219,10 +151,14 @@ class ChromiumPage(ChromiumBase):
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
tab = ChromiumTab(self, tab_id=self.browser.new_tab(new_window, background, new_context))
|
||||
if url:
|
||||
tab.get(url)
|
||||
return tab
|
||||
return self.browser.new_tab(url=url, new_window=new_window, background=background, new_context=new_context)
|
||||
|
||||
def activate_tab(self, id_ind_tab):
|
||||
"""使标签页变为活动状态
|
||||
:param id_ind_tab: 标签页id(str)、Tab对象或标签页序号(int),序号从1开始
|
||||
:return: None
|
||||
"""
|
||||
self.browser.activate_tab(id_ind_tab)
|
||||
|
||||
def close(self):
|
||||
"""关闭Page管理的标签页"""
|
||||
@ -234,32 +170,7 @@ class ChromiumPage(ChromiumBase):
|
||||
:param others: 是否关闭指定标签页之外的
|
||||
:return: None
|
||||
"""
|
||||
all_tabs = set(self.tab_ids)
|
||||
if isinstance(tabs_or_ids, str):
|
||||
tabs = {tabs_or_ids}
|
||||
elif isinstance(tabs_or_ids, ChromiumTab):
|
||||
tabs = {tabs_or_ids.tab_id}
|
||||
elif tabs_or_ids is None:
|
||||
tabs = {self.tab_id}
|
||||
elif isinstance(tabs_or_ids, (list, tuple)):
|
||||
tabs = set(i.tab_id if isinstance(i, ChromiumTab) else i for i in tabs_or_ids)
|
||||
else:
|
||||
raise TypeError('tabs_or_ids参数只能传入标签页对象或id。')
|
||||
|
||||
if others:
|
||||
tabs = all_tabs - tabs
|
||||
|
||||
end_len = len(set(all_tabs) - set(tabs))
|
||||
if end_len <= 0:
|
||||
self.quit()
|
||||
return
|
||||
|
||||
for tab in tabs:
|
||||
self.browser.close_tab(tab)
|
||||
sleep(.2)
|
||||
end_time = perf_counter() + 3
|
||||
while self.tabs_count != end_len and perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
self.browser.close_tabs(tabs_or_ids=tabs_or_ids, others=others)
|
||||
|
||||
def quit(self, timeout=5, force=True):
|
||||
"""关闭浏览器
|
||||
@ -271,69 +182,7 @@ class ChromiumPage(ChromiumBase):
|
||||
|
||||
def _on_disconnect(self):
|
||||
"""浏览器退出时执行"""
|
||||
ChromiumPage._PAGES.pop(self._browser_id, None)
|
||||
ChromiumPage._PAGES.pop(self._browser.id, None)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ChromiumPage browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
|
||||
|
||||
def handle_options(addr_or_opts):
|
||||
"""设置浏览器启动属性
|
||||
:param addr_or_opts: 'ip:port'、ChromiumOptions、Driver
|
||||
:return: 返回ChromiumOptions对象
|
||||
"""
|
||||
if not addr_or_opts:
|
||||
_chromium_options = ChromiumOptions(addr_or_opts)
|
||||
if _chromium_options.is_auto_port:
|
||||
port, path = PortFinder(_chromium_options.tmp_path).get_port(_chromium_options.is_auto_port)
|
||||
_chromium_options.set_address(f'127.0.0.1:{port}')
|
||||
_chromium_options.set_user_data_path(path)
|
||||
_chromium_options.auto_port(scope=_chromium_options.is_auto_port)
|
||||
|
||||
elif isinstance(addr_or_opts, ChromiumOptions):
|
||||
if addr_or_opts.is_auto_port:
|
||||
port, path = PortFinder(addr_or_opts.tmp_path).get_port(addr_or_opts.is_auto_port)
|
||||
addr_or_opts.set_address(f'127.0.0.1:{port}')
|
||||
addr_or_opts.set_user_data_path(path)
|
||||
addr_or_opts.auto_port(scope=addr_or_opts.is_auto_port)
|
||||
_chromium_options = addr_or_opts
|
||||
|
||||
elif isinstance(addr_or_opts, str):
|
||||
_chromium_options = ChromiumOptions()
|
||||
_chromium_options.set_address(addr_or_opts)
|
||||
|
||||
elif isinstance(addr_or_opts, int):
|
||||
_chromium_options = ChromiumOptions()
|
||||
_chromium_options.set_local_port(addr_or_opts)
|
||||
|
||||
else:
|
||||
raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。')
|
||||
|
||||
return _chromium_options
|
||||
|
||||
|
||||
def run_browser(chromium_options):
|
||||
"""连接浏览器"""
|
||||
is_exist = 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]
|
||||
ws.close()
|
||||
s.close()
|
||||
except KeyError:
|
||||
raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。')
|
||||
except:
|
||||
raise BrowserConnectError('\n浏览器连接失败,如使用全局代理,须设置不代理127.0.0.1地址。')
|
||||
return is_exist, browser_id
|
||||
|
||||
|
||||
def get_rename(original, rename):
|
||||
if '.' in rename:
|
||||
return rename
|
||||
else:
|
||||
suffix = original[original.rfind('.'):] if '.' in original else ''
|
||||
return f'{rename}{suffix}'
|
||||
|
@ -6,13 +6,12 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Union, Tuple, List, Optional
|
||||
|
||||
from .._base.browser import Browser
|
||||
from .._base.browser import Chromium
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._pages.tabs import ChromiumTab
|
||||
from .._units.rect import TabRect
|
||||
from .._units.setter import ChromiumPageSetter
|
||||
from .._units.waiter import PageWaiter
|
||||
@ -20,6 +19,10 @@ from .._units.waiter import PageWaiter
|
||||
|
||||
class ChromiumPage(ChromiumBase):
|
||||
_PAGES: dict = ...
|
||||
tab: ChromiumPage = ...
|
||||
_browser: Chromium = ...
|
||||
_rect: Optional[TabRect] = ...
|
||||
_is_exist: bool = ...
|
||||
|
||||
def __new__(cls,
|
||||
addr_or_opts: Union[str, int, ChromiumOptions] = None,
|
||||
@ -29,15 +32,7 @@ class ChromiumPage(ChromiumBase):
|
||||
def __init__(self,
|
||||
addr_or_opts: Union[str, int, ChromiumOptions] = None,
|
||||
tab_id: str = None,
|
||||
timeout: float = None):
|
||||
self.tab: ChromiumPage = ...
|
||||
self._chromium_options: ChromiumOptions = ...
|
||||
self._browser: Browser = ...
|
||||
self._browser_id: str = ...
|
||||
self._rect: Optional[TabRect] = ...
|
||||
self._is_exist: bool = ...
|
||||
self._lock: Lock = ...
|
||||
self._browser_version: str = ...
|
||||
timeout: float = None):...
|
||||
|
||||
def _handle_options(self, addr_or_opts: Union[str, ChromiumOptions]) -> str: ...
|
||||
|
||||
@ -46,7 +41,7 @@ class ChromiumPage(ChromiumBase):
|
||||
def _page_init(self) -> None: ...
|
||||
|
||||
@property
|
||||
def browser(self) -> Browser: ...
|
||||
def browser(self) -> Chromium: ...
|
||||
|
||||
@property
|
||||
def tabs_count(self) -> int: ...
|
||||
@ -66,6 +61,9 @@ class ChromiumPage(ChromiumBase):
|
||||
@property
|
||||
def browser_version(self) -> str: ...
|
||||
|
||||
@property
|
||||
def address(self) -> str: ...
|
||||
|
||||
@property
|
||||
def set(self) -> ChromiumPageSetter: ...
|
||||
|
||||
@ -106,6 +104,8 @@ class ChromiumPage(ChromiumBase):
|
||||
def new_tab(self, url: str = None, new_window: bool = False, background: bool = False,
|
||||
new_context: bool = False) -> ChromiumTab: ...
|
||||
|
||||
def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],
|
||||
@ -114,12 +114,3 @@ class ChromiumPage(ChromiumBase):
|
||||
def quit(self, timeout: float = 5, force: bool = True) -> None: ...
|
||||
|
||||
def _on_disconnect(self) -> None: ...
|
||||
|
||||
|
||||
def handle_options(addr_or_opts): ...
|
||||
|
||||
|
||||
def run_browser(chromium_options): ...
|
||||
|
||||
|
||||
def get_rename(original: str, rename: str) -> str: ...
|
||||
|
@ -6,15 +6,14 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from .chromium_page import ChromiumPage
|
||||
from .chromium_tab import WebPageTab
|
||||
from .session_page import SessionPage
|
||||
from .._base.base import BasePage
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._functions.web import set_session_cookies, set_browser_cookies
|
||||
from .._units.setter import WebPageSetter
|
||||
from .._functions.cookies import set_session_cookies, set_tab_cookies
|
||||
from .._units.setter import MixPageSetter
|
||||
|
||||
|
||||
class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
class MixPage(SessionPage, ChromiumPage, BasePage):
|
||||
"""整合浏览器和request的页面类"""
|
||||
|
||||
def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None):
|
||||
@ -26,7 +25,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
"""
|
||||
return super().__new__(cls, chromium_options)
|
||||
|
||||
def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
|
||||
def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None):
|
||||
"""初始化函数
|
||||
:param mode: 'd' 或 's',即driver模式和session模式
|
||||
:param timeout: 超时时间(秒),d模式时为寻找元素时间,s模式时为连接时间,默认10秒
|
||||
@ -47,7 +46,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
chromium_options = ChromiumOptions(read_file=chromium_options)
|
||||
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)
|
||||
self._type = 'WebPage'
|
||||
self._type = 'MixPage'
|
||||
self.change_mode(self._mode, go=False, copy_cookies=False)
|
||||
|
||||
def __call__(self, locator, index=1, timeout=None):
|
||||
@ -67,7 +66,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
def set(self):
|
||||
"""返回用于设置的对象"""
|
||||
if self._set is None:
|
||||
self._set = WebPageSetter(self)
|
||||
self._set = MixPageSetter(self)
|
||||
return self._set
|
||||
|
||||
@property
|
||||
@ -148,15 +147,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回通用timeout设置"""
|
||||
return self.timeouts.base
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second):
|
||||
"""设置通用超时时间
|
||||
:param second: 秒数
|
||||
:return: None
|
||||
"""
|
||||
self.set.timeouts(base=second)
|
||||
return self._timeout if self._mode == 's' else self.timeouts.base
|
||||
|
||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
||||
"""跳转到一个url
|
||||
@ -284,7 +275,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
return
|
||||
|
||||
if copy_user_agent:
|
||||
user_agent = self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
self._headers.update({"User-Agent": user_agent})
|
||||
|
||||
set_session_cookies(self.session, super(SessionPage, self).cookies())
|
||||
@ -293,19 +284,18 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
"""把session对象的cookies复制到浏览器"""
|
||||
if not self._has_driver:
|
||||
return
|
||||
set_browser_cookies(self, super().cookies())
|
||||
set_tab_cookies(self, super().cookies())
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
def cookies(self, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
"""
|
||||
if self._mode == 's':
|
||||
return super().cookies(as_dict, all_domains, all_info)
|
||||
return super().cookies(all_domains, all_info)
|
||||
elif self._mode == 'd':
|
||||
return super(SessionPage, self).cookies(as_dict, all_domains, all_info)
|
||||
return super(SessionPage, self).cookies(all_domains, all_info)
|
||||
|
||||
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
@ -314,31 +304,10 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: WebPageTab对象
|
||||
:return: MixTab对象
|
||||
"""
|
||||
if id_or_num is not None:
|
||||
if isinstance(id_or_num, str):
|
||||
id_or_num = id_or_num
|
||||
elif isinstance(id_or_num, int):
|
||||
id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]
|
||||
elif isinstance(id_or_num, WebPageTab):
|
||||
return id_or_num.tab_id if as_id else id_or_num
|
||||
|
||||
elif title == url == tab_type is None:
|
||||
id_or_num = self.tab_id
|
||||
|
||||
else:
|
||||
id_or_num = self._browser.find_tabs(title, url, tab_type)
|
||||
if id_or_num:
|
||||
id_or_num = id_or_num[0]['id']
|
||||
else:
|
||||
return None
|
||||
|
||||
if as_id:
|
||||
return id_or_num
|
||||
|
||||
with self._lock:
|
||||
return WebPageTab(self, id_or_num)
|
||||
return self.browser._get_tab(id_or_num=id_or_num, title=title, url=url,
|
||||
tab_type=tab_type, mix=True, as_id=as_id)
|
||||
|
||||
def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
@ -348,10 +317,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象组成的列表
|
||||
"""
|
||||
if as_id:
|
||||
return [tab['id'] for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
with self._lock:
|
||||
return [WebPageTab(self, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
return self.browser._get_tabs(title=title, url=url, tab_type=tab_type, mix=True, as_id=as_id)
|
||||
|
||||
def new_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
@ -361,10 +327,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
tab = WebPageTab(self, tab_id=self.browser.new_tab(new_window, background, new_context))
|
||||
if url:
|
||||
tab.get(url)
|
||||
return tab
|
||||
return self.browser.new_mix_tab(url=url, new_window=new_window, background=background, new_context=new_context)
|
||||
|
||||
def close_driver(self):
|
||||
"""关闭driver及浏览器"""
|
||||
@ -403,7 +366,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
:param locator: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒),d模式专用
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: 元素对象或属性、文本节点文本
|
||||
"""
|
||||
@ -429,4 +392,4 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
self._has_driver = None
|
||||
|
||||
def __repr__(self):
|
||||
return f'<WebPage browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
return f'<MixPage browser_id={self.browser.id} tab_id={self.tab_id}>'
|
@ -11,7 +11,7 @@ from requests import Session, Response
|
||||
|
||||
from .chromium_frame import ChromiumFrame
|
||||
from .chromium_page import ChromiumPage
|
||||
from .chromium_tab import WebPageTab
|
||||
from .tabs import MixTab
|
||||
from .session_page import SessionPage
|
||||
from .._base.base import BasePage
|
||||
from .._base.driver import Driver
|
||||
@ -20,10 +20,10 @@ from .._configs.session_options import SessionOptions
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._units.setter import WebPageSetter
|
||||
from .._units.setter import MixPageSetter
|
||||
|
||||
|
||||
class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
class MixPage(SessionPage, ChromiumPage, BasePage):
|
||||
|
||||
def __init__(self,
|
||||
mode: str = 'd',
|
||||
@ -31,7 +31,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
chromium_options: Union[ChromiumOptions, bool] = None,
|
||||
session_or_options: Union[Session, SessionOptions, bool] = None) -> None:
|
||||
self._mode: str = ...
|
||||
self._set: WebPageSetter = ...
|
||||
self._set: MixPageSetter = ...
|
||||
self._has_driver: bool = ...
|
||||
self._has_session: bool = ...
|
||||
self._session_options: Union[SessionOptions, None] = ...
|
||||
@ -79,9 +79,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second: float) -> None: ...
|
||||
|
||||
def get(self,
|
||||
url: str,
|
||||
show_errmsg: bool = False,
|
||||
@ -124,28 +121,27 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
def cookies_to_browser(self) -> None: ...
|
||||
|
||||
def cookies(self,
|
||||
as_dict: bool = False,
|
||||
all_domains: bool = False,
|
||||
all_info: bool = False) -> Union[dict, list]: ...
|
||||
|
||||
def get_tab(self,
|
||||
id_or_num: Union[str, WebPageTab, int] = None,
|
||||
id_or_num: Union[str, MixTab, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[WebPageTab, str, None]: ...
|
||||
as_id: bool = False) -> Union[MixTab, str, None]: ...
|
||||
|
||||
def get_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[List[WebPageTab], List[str]]: ...
|
||||
as_id: bool = False) -> Union[List[MixTab], List[str]]: ...
|
||||
|
||||
def new_tab(self,
|
||||
url: str = None,
|
||||
new_window: bool = False,
|
||||
background: bool = False,
|
||||
new_context: bool = False) -> WebPageTab: ...
|
||||
new_context: bool = False) -> MixTab: ...
|
||||
|
||||
def close_driver(self) -> None: ...
|
||||
|
||||
@ -175,10 +171,10 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
cert: Any | None = ...) -> Union[bool, Response]: ...
|
||||
|
||||
@property
|
||||
def latest_tab(self) -> Union[WebPageTab, WebPage]: ...
|
||||
def latest_tab(self) -> Union[MixTab, MixPage]: ...
|
||||
|
||||
@property
|
||||
def set(self) -> WebPageSetter: ...
|
||||
def set(self) -> MixPageSetter: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement, ChromiumFrame],
|
@ -18,17 +18,17 @@ from tldextract import extract
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._elements.session_element import SessionElement, make_session_ele
|
||||
from .._functions.web import cookie_to_dict, format_headers
|
||||
from .._functions.cookies import cookie_to_dict, CookiesList
|
||||
from .._functions.web import format_headers
|
||||
from .._units.setter import SessionPageSetter
|
||||
|
||||
|
||||
class SessionPage(BasePage):
|
||||
"""SessionPage封装了页面操作的常用功能,使用requests来获取、解析网页"""
|
||||
|
||||
def __init__(self, session_or_options=None, timeout=None):
|
||||
def __init__(self, session_or_options=None):
|
||||
"""
|
||||
:param session_or_options: Session对象或SessionOptions对象
|
||||
:param timeout: 连接超时时间(秒),为None时从ini文件读取或默认10
|
||||
"""
|
||||
super(SessionPage, SessionPage).__init__(self)
|
||||
self._headers = None
|
||||
@ -38,11 +38,10 @@ class SessionPage(BasePage):
|
||||
self._encoding = None
|
||||
self._type = 'SessionPage'
|
||||
self._page = self
|
||||
self._timeout = 10
|
||||
self._s_set_start_options(session_or_options)
|
||||
self._s_set_runtime_settings()
|
||||
self._create_session()
|
||||
if timeout is not None:
|
||||
self.timeout = timeout
|
||||
|
||||
def _s_set_start_options(self, session_or_options):
|
||||
"""启动配置
|
||||
@ -64,8 +63,7 @@ class SessionPage(BasePage):
|
||||
def _s_set_runtime_settings(self):
|
||||
"""设置运行时用到的属性"""
|
||||
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._download_path = str(Path(self._session_options.download_path or '.').absolute())
|
||||
self.retry_times = self._session_options.retry_times
|
||||
self.retry_interval = self._session_options.retry_interval
|
||||
|
||||
@ -146,6 +144,11 @@ class SessionPage(BasePage):
|
||||
self._set = SessionPageSetter(self)
|
||||
return self._set
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回超时设置"""
|
||||
return self._timeout
|
||||
|
||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
||||
"""用get方式跳转到url,可输入文件路径
|
||||
:param url: 目标url,可指定本地文件路径
|
||||
@ -220,9 +223,8 @@ class SessionPage(BasePage):
|
||||
"""
|
||||
return locator if isinstance(locator, SessionElement) else make_session_ele(self, locator, index=index)
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
def cookies(self, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
@ -233,17 +235,16 @@ class SessionPage(BasePage):
|
||||
if self.url:
|
||||
ex_url = extract(self._session_url)
|
||||
domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain
|
||||
|
||||
cookies = tuple(x for x in self.session.cookies if domain in x.domain or x.domain == '')
|
||||
cookies = tuple(c for c in self.session.cookies if domain in c.domain or c.domain == '')
|
||||
else:
|
||||
cookies = tuple(x for x in self.session.cookies)
|
||||
cookies = tuple(c for c in self.session.cookies)
|
||||
|
||||
if as_dict:
|
||||
return {x.name: x.value for x in cookies}
|
||||
elif all_info:
|
||||
return [cookie_to_dict(cookie) for cookie in cookies]
|
||||
if all_info:
|
||||
r = CookiesList()
|
||||
for c in cookies:
|
||||
r.append(cookie_to_dict(c))
|
||||
else:
|
||||
r = []
|
||||
r = CookiesList()
|
||||
for c in cookies:
|
||||
c = cookie_to_dict(c)
|
||||
r.append({'name': c['name'], 'value': c['value'], 'domain': c['domain']})
|
||||
|
@ -14,21 +14,21 @@ from requests.structures import CaseInsensitiveDict
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._functions.elements import SessionElementsList
|
||||
from .._units.setter import SessionPageSetter
|
||||
|
||||
|
||||
class SessionPage(BasePage):
|
||||
def __init__(self,
|
||||
session_or_options: Union[Session, SessionOptions] = None,
|
||||
timeout: float = None):
|
||||
session_or_options: Union[Session, SessionOptions] = None):
|
||||
self._headers: Optional[CaseInsensitiveDict] = ...
|
||||
self._session: Session = ...
|
||||
self._session_options: SessionOptions = ...
|
||||
self._url: str = ...
|
||||
self._response: Response = ...
|
||||
self._url_available: bool = ...
|
||||
self.timeout: float = ...
|
||||
self._timeout: float = ...
|
||||
self.retry_times: int = ...
|
||||
self.retry_interval: float = ...
|
||||
self._set: SessionPageSetter = ...
|
||||
@ -114,9 +114,8 @@ class SessionPage(BasePage):
|
||||
raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ...
|
||||
|
||||
def cookies(self,
|
||||
as_dict: bool = False,
|
||||
all_domains: bool = False,
|
||||
all_info: bool = False) -> Union[dict, list]: ...
|
||||
all_info: bool = False) -> CookiesList: ...
|
||||
|
||||
# ----------------session独有属性和方法-----------------------
|
||||
@property
|
||||
@ -131,6 +130,9 @@ class SessionPage(BasePage):
|
||||
@property
|
||||
def set(self) -> SessionPageSetter: ...
|
||||
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
def post(self,
|
||||
url: str,
|
||||
show_errmsg: bool = False,
|
||||
|
@ -10,11 +10,12 @@ from time import sleep
|
||||
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._functions.cookies import set_session_cookies, set_tab_cookies
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.web import set_session_cookies, set_browser_cookies, save_page
|
||||
from .._functions.web import save_page
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._units.setter import TabSetter, WebPageTabSetter
|
||||
from .._units.setter import TabSetter, MixTabSetter
|
||||
from .._units.waiter import TabWaiter
|
||||
|
||||
|
||||
@ -22,10 +23,10 @@ class ChromiumTab(ChromiumBase):
|
||||
"""实现浏览器标签页的类"""
|
||||
_TABS = {}
|
||||
|
||||
def __new__(cls, page, tab_id):
|
||||
def __new__(cls, browser, tab_id):
|
||||
"""
|
||||
:param page: ChromiumPage对象
|
||||
:param tab_id: 要控制的标签页id
|
||||
:param browser: Browser对象
|
||||
:param tab_id: 标签页id
|
||||
"""
|
||||
if Settings.singleton_tab_obj and tab_id in cls._TABS:
|
||||
r = cls._TABS[tab_id]
|
||||
@ -36,38 +37,30 @@ class ChromiumTab(ChromiumBase):
|
||||
cls._TABS[tab_id] = r
|
||||
return r
|
||||
|
||||
def __init__(self, page, tab_id):
|
||||
def __init__(self, browser, tab_id):
|
||||
"""
|
||||
:param page: ChromiumPage对象
|
||||
:param tab_id: 要控制的标签页id
|
||||
:param browser: Browser对象
|
||||
:param tab_id: 标签页id
|
||||
"""
|
||||
if Settings.singleton_tab_obj and hasattr(self, '_created'):
|
||||
return
|
||||
self._created = True
|
||||
|
||||
self._page = page
|
||||
self.tab = self
|
||||
self._browser = page.browser
|
||||
super().__init__(page.address, tab_id, page.timeout)
|
||||
self._rect = None
|
||||
super().__init__(browser, tab_id)
|
||||
self._tab = self
|
||||
self._type = 'ChromiumTab'
|
||||
|
||||
def _d_set_runtime_settings(self):
|
||||
"""重写设置浏览器运行参数方法"""
|
||||
self._timeouts = copy(self.page.timeouts)
|
||||
self.retry_times = self.page.retry_times
|
||||
self.retry_interval = self.page.retry_interval
|
||||
self._load_mode = self.page._load_mode
|
||||
self._download_path = self.page.download_path
|
||||
self._timeouts = copy(self.browser.timeouts)
|
||||
self.retry_times = self.browser.retry_times
|
||||
self.retry_interval = self.browser.retry_interval
|
||||
self._load_mode = self.browser._load_mode
|
||||
self._download_path = self.browser.download_path
|
||||
|
||||
def close(self):
|
||||
"""关闭当前标签页"""
|
||||
self.page.close_tabs(self.tab_id)
|
||||
|
||||
@property
|
||||
def page(self):
|
||||
"""返回总体page对象"""
|
||||
return self._page
|
||||
self.browser.close_tabs(self.tab_id)
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
@ -100,11 +93,11 @@ class ChromiumTab(ChromiumBase):
|
||||
ChromiumTab._TABS.pop(self.tab_id, None)
|
||||
|
||||
|
||||
class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
def __init__(self, page, tab_id):
|
||||
class MixTab(SessionPage, ChromiumTab, BasePage):
|
||||
def __init__(self, browser, tab_id):
|
||||
"""
|
||||
:param page: WebPage对象
|
||||
:param tab_id: 要控制的标签页id
|
||||
:param browser: Chromium对象
|
||||
:param tab_id: 标签页id
|
||||
"""
|
||||
if Settings.singleton_tab_obj and hasattr(self, '_created'):
|
||||
return
|
||||
@ -112,10 +105,9 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
self._mode = 'd'
|
||||
self._has_driver = True
|
||||
self._has_session = True
|
||||
super().__init__(session_or_options=SessionOptions(read_file=False).from_session(copy(page.session),
|
||||
page._headers))
|
||||
super(SessionPage, self).__init__(page=page, tab_id=tab_id)
|
||||
self._type = 'WebPageTab'
|
||||
super().__init__(session_or_options=browser._session_options if browser._session_options else SessionOptions())
|
||||
super(SessionPage, self).__init__(browser=browser, tab_id=tab_id)
|
||||
self._type = 'MixTab'
|
||||
|
||||
def __call__(self, locator, index=1, timeout=None):
|
||||
"""在内部查找元素
|
||||
@ -134,7 +126,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
def set(self):
|
||||
"""返回用于设置的对象"""
|
||||
if self._set is None:
|
||||
self._set = WebPageTabSetter(self)
|
||||
self._set = MixTabSetter(self)
|
||||
return self._set
|
||||
|
||||
@property
|
||||
@ -215,15 +207,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
@property
|
||||
def timeout(self):
|
||||
"""返回通用timeout设置"""
|
||||
return self.timeouts.base
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second):
|
||||
"""设置通用超时时间
|
||||
:param second: 秒数
|
||||
:return: None
|
||||
"""
|
||||
self.set.timeouts(base=second)
|
||||
return self._timeout if self._mode == 's' else self.timeouts.base
|
||||
|
||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
||||
"""跳转到一个url
|
||||
@ -318,7 +302,9 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
# s模式转d模式
|
||||
if self._mode == 'd':
|
||||
if self._driver is None:
|
||||
self._connect_browser(self.page._chromium_options)
|
||||
tabs = self.browser.tab_ids
|
||||
tid = self.tab_id if self.tab_id in tabs else tabs[0]
|
||||
self._connect_browser(tid)
|
||||
|
||||
self._url = None if not self._has_driver else super(SessionPage, self).url
|
||||
self._has_driver = True
|
||||
@ -353,7 +339,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
return
|
||||
|
||||
if copy_user_agent:
|
||||
user_agent = self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
|
||||
self._headers.update({"User-Agent": user_agent})
|
||||
|
||||
set_session_cookies(self.session, super(SessionPage, self).cookies())
|
||||
@ -362,23 +348,22 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
"""把session对象的cookies复制到浏览器"""
|
||||
if not self._has_driver:
|
||||
return
|
||||
set_browser_cookies(self, super().cookies())
|
||||
set_tab_cookies(self, super().cookies())
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
def cookies(self, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
"""
|
||||
if self._mode == 's':
|
||||
return super().cookies(as_dict, all_domains, all_info)
|
||||
return super().cookies(all_domains, all_info)
|
||||
elif self._mode == 'd':
|
||||
return super(SessionPage, self).cookies(as_dict, all_domains, all_info)
|
||||
return super(SessionPage, self).cookies(all_domains, all_info)
|
||||
|
||||
def close(self):
|
||||
"""关闭当前标签页"""
|
||||
self.page.close_tabs(self.tab_id)
|
||||
self.browser.close_tabs(self.tab_id)
|
||||
self._session.close()
|
||||
if self._response is not None:
|
||||
self._response.close()
|
||||
@ -388,7 +373,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
:param locator: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间(秒),d模式专用
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param relative: MixTab用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: 元素对象或属性、文本节点文本
|
||||
"""
|
||||
@ -398,4 +383,4 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
return super(SessionPage, self)._find_elements(locator, timeout=timeout, index=index, relative=relative)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<WebPageTab browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
return f'<MixTab browser_id={self.browser.id} tab_id={self.tab_id}>'
|
@ -12,35 +12,30 @@ from requests import Session, Response
|
||||
|
||||
from .chromium_base import ChromiumBase
|
||||
from .chromium_frame import ChromiumFrame
|
||||
from .chromium_page import ChromiumPage
|
||||
from .session_page import SessionPage
|
||||
from .web_page import WebPage
|
||||
from .._base.browser import Browser
|
||||
from .._base.browser import Chromium
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.cookies import CookiesList
|
||||
from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._units.rect import TabRect
|
||||
from .._units.setter import TabSetter, WebPageTabSetter
|
||||
from .._units.setter import TabSetter, MixTabSetter
|
||||
from .._units.waiter import TabWaiter
|
||||
|
||||
|
||||
class ChromiumTab(ChromiumBase):
|
||||
_TABS: dict = ...
|
||||
|
||||
def __new__(cls, page: ChromiumPage, tab_id: str): ...
|
||||
def __new__(cls, browser: Chromium, tab_id: str): ...
|
||||
|
||||
def __init__(self, page: ChromiumPage, tab_id: str):
|
||||
self._page: ChromiumPage = ...
|
||||
self._browser: Browser = ...
|
||||
def __init__(self, browser: Chromium, tab_id: str):
|
||||
self._tab: ChromiumTab = ...
|
||||
self._rect: Optional[TabRect] = ...
|
||||
|
||||
def _d_set_runtime_settings(self) -> None: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
@property
|
||||
def page(self) -> ChromiumPage: ...
|
||||
|
||||
@property
|
||||
def set(self) -> TabSetter: ...
|
||||
|
||||
@ -69,22 +64,19 @@ class ChromiumTab(ChromiumBase):
|
||||
generateDocumentOutline: bool = ...) -> Union[bytes, str]: ...
|
||||
|
||||
|
||||
class WebPageTab(SessionPage, ChromiumTab):
|
||||
def __init__(self, page: WebPage, tab_id: str):
|
||||
self._page: WebPage = ...
|
||||
self._browser: Browser = ...
|
||||
self._mode: str = ...
|
||||
self._has_driver = ...
|
||||
self._has_session = ...
|
||||
class MixTab(SessionPage, ChromiumTab):
|
||||
_tab: MixTab = ...
|
||||
_mode: str = ...
|
||||
_has_driver: bool = ...
|
||||
_has_session: bool = ...
|
||||
|
||||
def __init__(self, browser: Chromium, tab_id: str): ...
|
||||
|
||||
def __call__(self,
|
||||
locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement],
|
||||
index: int = 1,
|
||||
timeout: float = None) -> Union[ChromiumElement, SessionElement]: ...
|
||||
|
||||
@property
|
||||
def page(self) -> WebPage: ...
|
||||
|
||||
@property
|
||||
def url(self) -> Union[str, None]: ...
|
||||
|
||||
@ -121,9 +113,6 @@ class WebPageTab(SessionPage, ChromiumTab):
|
||||
@property
|
||||
def timeout(self) -> float: ...
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, second: float) -> None: ...
|
||||
|
||||
def get(self,
|
||||
url: str,
|
||||
show_errmsg: bool = False,
|
||||
@ -165,8 +154,7 @@ class WebPageTab(SessionPage, ChromiumTab):
|
||||
|
||||
def cookies_to_browser(self) -> None: ...
|
||||
|
||||
def cookies(self, as_dict: bool = False, all_domains: bool = False,
|
||||
all_info: bool = False) -> Union[dict, list]: ...
|
||||
def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
@ -192,7 +180,7 @@ class WebPageTab(SessionPage, ChromiumTab):
|
||||
cert: Any | None = ...) -> Union[bool, Response]: ...
|
||||
|
||||
@property
|
||||
def set(self) -> WebPageTabSetter: ...
|
||||
def set(self) -> MixTabSetter: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement, ChromiumFrame],
|
@ -7,8 +7,7 @@
|
||||
"""
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from ..errors import AlertExistsError
|
||||
from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions
|
||||
from .._functions.keys import modifierBit, make_input_data, input_text_or_keys, Keys
|
||||
from .._functions.web import location_in_viewport
|
||||
|
||||
|
||||
@ -26,7 +25,7 @@ class Actions:
|
||||
self.curr_y = 0
|
||||
self._holding = 'left'
|
||||
|
||||
def move_to(self, ele_or_loc, offset_x=0, offset_y=0, duration=.5):
|
||||
def move_to(self, ele_or_loc, offset_x=None, offset_y=None, duration=.5):
|
||||
"""鼠标移动到元素中点,或页面上的某个绝对坐标。可设置偏移量
|
||||
当带偏移量时,偏移量相对于元素左上角坐标
|
||||
:param ele_or_loc: 元素对象、绝对坐标或文本定位符,坐标为tuple(int, int)形式
|
||||
@ -36,6 +35,11 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
is_loc = False
|
||||
mid_point = offset_x == offset_y is None
|
||||
if offset_x is None:
|
||||
offset_x = 0
|
||||
if offset_y is None:
|
||||
offset_y = 0
|
||||
if isinstance(ele_or_loc, (tuple, list)):
|
||||
is_loc = True
|
||||
lx = ele_or_loc[0] + offset_x
|
||||
@ -43,7 +47,7 @@ class Actions:
|
||||
elif isinstance(ele_or_loc, str) or ele_or_loc._type == 'ChromiumElement':
|
||||
ele_or_loc = self.owner(ele_or_loc)
|
||||
self.owner.scroll.to_see(ele_or_loc)
|
||||
x, y = ele_or_loc.rect.location if offset_x or offset_y else ele_or_loc.rect.midpoint
|
||||
x, y = ele_or_loc.rect.midpoint if mid_point else ele_or_loc.rect.location
|
||||
lx = x + offset_x
|
||||
ly = y + offset_y
|
||||
else:
|
||||
@ -51,16 +55,15 @@ class Actions:
|
||||
|
||||
if not location_in_viewport(self.owner, lx, ly):
|
||||
# 把坐标滚动到页面中间
|
||||
clientWidth = self.owner.run_js('return document.body.clientWidth;')
|
||||
clientHeight = self.owner.run_js('return document.body.clientHeight;')
|
||||
clientWidth = self.owner._run_js('return document.body.clientWidth;')
|
||||
clientHeight = self.owner._run_js('return document.body.clientHeight;')
|
||||
self.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)
|
||||
|
||||
# 这样设计为了应付那些不随滚动条滚动的元素
|
||||
if is_loc:
|
||||
cx, cy = location_to_client(self.owner, lx, ly)
|
||||
else:
|
||||
x, y = ele_or_loc.rect.viewport_location if offset_x or offset_y \
|
||||
else ele_or_loc.rect.viewport_midpoint
|
||||
x, y = ele_or_loc.rect.viewport_midpoint if mid_point else ele_or_loc.rect.viewport_location
|
||||
cx = x + offset_x
|
||||
cy = y + offset_y
|
||||
|
||||
@ -95,36 +98,31 @@ class Actions:
|
||||
|
||||
return self
|
||||
|
||||
def click(self, on_ele=None):
|
||||
def click(self, on_ele=None, times=1):
|
||||
"""点击鼠标左键,可先移动到元素上
|
||||
:param on_ele: ChromiumElement元素或文本定位符
|
||||
:param times: 点击次数
|
||||
:return: self
|
||||
"""
|
||||
self._hold(on_ele, 'left').wait(.05)._release('left')
|
||||
self._hold(on_ele, 'left', times).wait(.05)._release('left')
|
||||
return self
|
||||
|
||||
def r_click(self, on_ele=None):
|
||||
def r_click(self, on_ele=None, times=1):
|
||||
"""点击鼠标右键,可先移动到元素上
|
||||
:param on_ele: ChromiumElement元素或文本定位符
|
||||
:param times: 点击次数
|
||||
:return: self
|
||||
"""
|
||||
self._hold(on_ele, 'right').wait(.05)._release('right')
|
||||
self._hold(on_ele, 'right', times).wait(.05)._release('right')
|
||||
return self
|
||||
|
||||
def m_click(self, on_ele=None):
|
||||
def m_click(self, on_ele=None, times=1):
|
||||
"""点击鼠标中键,可先移动到元素上
|
||||
:param on_ele: ChromiumElement元素或文本定位符
|
||||
:param times: 点击次数
|
||||
:return: self
|
||||
"""
|
||||
self._hold(on_ele, 'middle').wait(.05)._release('middle')
|
||||
return self
|
||||
|
||||
def db_click(self, on_ele=None):
|
||||
"""双击鼠标左键,可先移动到元素上
|
||||
:param on_ele: ChromiumElement元素或文本定位符
|
||||
:return: self
|
||||
"""
|
||||
self._hold(on_ele, 'left', 2).wait(.05)._release('left')
|
||||
self._hold(on_ele, 'middle', times).wait(.05)._release('middle')
|
||||
return self
|
||||
|
||||
def hold(self, on_ele=None):
|
||||
@ -256,9 +254,10 @@ class Actions:
|
||||
self.modifier |= modifierBit.get(key, 0)
|
||||
return self
|
||||
|
||||
data = self._get_key_data(key, 'keyDown')
|
||||
data['_ignore'] = AlertExistsError
|
||||
self.owner.run_cdp('Input.dispatchKeyEvent', **data)
|
||||
data = make_input_data(self.modifier, key, False)
|
||||
if not data:
|
||||
raise ValueError(f'没有这个按键:{key}')
|
||||
self.owner._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
return self
|
||||
|
||||
def key_up(self, key):
|
||||
@ -271,9 +270,10 @@ class Actions:
|
||||
self.modifier ^= modifierBit.get(key, 0)
|
||||
return self
|
||||
|
||||
data = self._get_key_data(key, 'keyUp')
|
||||
data['_ignore'] = AlertExistsError
|
||||
self.owner.run_cdp('Input.dispatchKeyEvent', **data)
|
||||
data = make_input_data(self.modifier, key, True)
|
||||
if not data:
|
||||
raise ValueError(f'没有这个按键:{key}')
|
||||
self.owner._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
return self
|
||||
|
||||
def type(self, keys):
|
||||
@ -282,17 +282,22 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
modifiers = []
|
||||
if not isinstance(keys, (str, tuple, list)):
|
||||
keys = str(keys)
|
||||
for i in keys:
|
||||
for character in i:
|
||||
if character in keyDefinitions:
|
||||
self.key_down(character)
|
||||
if character in ('\ue009', '\ue008', '\ue00a', '\ue03d'):
|
||||
self.modifier |= modifierBit.get(character, 0)
|
||||
modifiers.append(character)
|
||||
else:
|
||||
self.key_up(character)
|
||||
data = make_input_data(self.modifier, character, False)
|
||||
if data:
|
||||
self.owner._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
if character not in ('\ue009', '\ue008', '\ue00a', '\ue03d'):
|
||||
data['type'] = 'keyUp'
|
||||
self.owner._run_cdp('Input.dispatchKeyEvent', **data)
|
||||
|
||||
else:
|
||||
self.owner.run_cdp('Input.dispatchKeyEvent', type='char', text=character)
|
||||
self.owner._run_cdp('Input.dispatchKeyEvent', type='char', text=character)
|
||||
|
||||
for m in modifiers:
|
||||
self.key_up(m)
|
||||
@ -315,30 +320,9 @@ class Actions:
|
||||
self.owner.wait(second=second, scope=scope)
|
||||
return self
|
||||
|
||||
def _get_key_data(self, key, action):
|
||||
"""获取用于发送的按键信息
|
||||
:param key: 按键
|
||||
:param action: 'keyDown' 或 'keyUp'
|
||||
:return: 按键信息
|
||||
"""
|
||||
description = keyDescriptionForString(self.modifier, key)
|
||||
text = description['text']
|
||||
if action != 'keyUp':
|
||||
action = 'keyDown' if text else 'rawKeyDown'
|
||||
return {'type': action,
|
||||
'modifiers': self.modifier,
|
||||
'windowsVirtualKeyCode': description['keyCode'],
|
||||
'code': description['code'],
|
||||
'key': description['key'],
|
||||
'text': text,
|
||||
'autoRepeat': False,
|
||||
'unmodifiedText': text,
|
||||
'location': description['location'],
|
||||
'isKeypad': description['location'] == 3}
|
||||
|
||||
|
||||
def location_to_client(page, lx, ly):
|
||||
"""绝对坐标转换为视口坐标"""
|
||||
scroll_x = page.run_js('return document.documentElement.scrollLeft;')
|
||||
scroll_y = page.run_js('return document.documentElement.scrollTop;')
|
||||
scroll_x = page._run_js('return document.documentElement.scrollLeft;')
|
||||
scroll_y = page._run_js('return document.documentElement.scrollTop;')
|
||||
return lx - scroll_x, ly - scroll_y
|
||||
|
@ -11,19 +11,19 @@ from .._base.driver import Driver
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
KEYS = Literal['NULL', 'CANCEL', 'HELP', 'BACKSPACE', 'BACK_SPACE', 'meta',
|
||||
'TAB', 'CLEAR', 'RETURN', 'ENTER', 'SHIFT', 'LEFT_SHIFT', 'CONTROL', 'command ',
|
||||
'CTRL', 'LEFT_CONTROL', 'ALT', 'LEFT_ALT', 'PAUSE', 'ESCAPE', 'SPACE',
|
||||
'PAGE_UP', 'PAGE_DOWN', 'END', 'HOME', 'LEFT', 'ARROW_LEFT', 'UP',
|
||||
'ARROW_UP', 'RIGHT', 'ARROW_RIGHT', 'DOWN', 'ARROW_DOWN', 'INSERT',
|
||||
KEYS = Literal['NULL', 'CANCEL', 'HELP', 'BACKSPACE', 'meta',
|
||||
'TAB', 'CLEAR', 'RETURN', 'ENTER', 'SHIFT', 'CONTROL', 'command ',
|
||||
'CTRL', 'ALT', 'PAUSE', 'ESCAPE', 'SPACE',
|
||||
'PAGE_UP', 'PAGE_DOWN', 'END', 'HOME', 'LEFT', 'UP',
|
||||
'RIGHT', 'DOWN', 'INSERT',
|
||||
'DELETE', 'DEL', 'SEMICOLON', 'EQUALS', 'NUMPAD0', 'NUMPAD1', 'NUMPAD2',
|
||||
'NUMPAD3', 'NUMPAD4', 'NUMPAD5', 'NUMPAD6', 'NUMPAD7', 'NUMPAD8', 'NUMPAD9',
|
||||
'MULTIPLY', 'ADD', 'SUBTRACT', 'DECIMAL', 'DIVIDE', 'F1', 'F2',
|
||||
'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'META', 'COMMAND ',
|
||||
'null', 'cancel', 'help', 'backspace', 'back_space', 'tab', 'clear', 'return', 'enter',
|
||||
'shift', 'left_shift', 'control', 'ctrl', 'left_control', 'alt', 'left_alt', 'pause',
|
||||
'escape', 'space', 'page_up', 'page_down', 'end', 'home', 'left', 'arrow_left', 'up',
|
||||
'arrow_up', 'right', 'arrow_right', 'down', 'arrow_down', 'insert', 'delete', 'del',
|
||||
'null', 'cancel', 'help', 'backspace', 'tab', 'clear', 'return', 'enter',
|
||||
'shift', 'control', 'ctrl', 'alt', 'pause',
|
||||
'escape', 'space', 'page_up', 'page_down', 'end', 'home', 'left', 'up',
|
||||
'right', 'down', 'insert', 'delete', 'del',
|
||||
'semicolon', 'equals', 'numpad0', 'numpad1', 'numpad2', 'numpad3', 'numpad4', 'numpad5',
|
||||
'numpad6', 'numpad7', 'numpad8', 'numpad9', 'multiply', 'add', 'subtract', 'decimal',
|
||||
'divide', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
|
||||
@ -57,13 +57,11 @@ class Actions:
|
||||
|
||||
def move(self, offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions: ...
|
||||
|
||||
def click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
def click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions: ...
|
||||
|
||||
def r_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
def r_click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions: ...
|
||||
|
||||
def m_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
|
||||
def db_click(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
def m_click(self, on_ele: Union[ChromiumElement, str] = None, times: int = 1) -> Actions: ...
|
||||
|
||||
def hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
|
||||
@ -103,7 +101,5 @@ class Actions:
|
||||
|
||||
def wait(self, second: float, scope: float = None) -> Actions: ...
|
||||
|
||||
def _get_key_data(self, key: str, action: str) -> dict: ...
|
||||
|
||||
|
||||
def location_to_client(page, lx: int, ly: int) -> tuple: ...
|
||||
|
@ -43,7 +43,7 @@ class Clicker(object):
|
||||
select = self._ele.parent('t:select')
|
||||
if select.select.is_multi:
|
||||
self._ele.parent('t:select').select.cancel_by_option(self._ele)
|
||||
return
|
||||
return self._ele
|
||||
|
||||
if not by_js: # 模拟点击
|
||||
can_click = False
|
||||
@ -87,22 +87,25 @@ class Clicker(object):
|
||||
x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2
|
||||
y = rect[0][0] + 3
|
||||
try:
|
||||
r = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y),
|
||||
r = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y),
|
||||
includeUserAgentShadowDOM=True, ignorePointerEventsNone=True)
|
||||
if r['backendNodeId'] != self._ele._backend_id:
|
||||
vx, vy = self._ele.rect.viewport_midpoint
|
||||
lx, ly = self._ele.rect._get_page_coord(vx, vy)
|
||||
else:
|
||||
vx, vy = self._ele.rect.viewport_click_point
|
||||
lx, ly = self._ele.rect._get_page_coord(vx, vy)
|
||||
|
||||
except CDPError:
|
||||
vx, vy = self._ele.rect.viewport_midpoint
|
||||
lx, ly = self._ele.rect._get_page_coord(vx, vy)
|
||||
|
||||
self._click(vx, vy)
|
||||
return True
|
||||
self._click(lx, ly, vx, vy)
|
||||
return self._ele
|
||||
|
||||
if by_js is not False:
|
||||
self._ele.run_js('this.click();')
|
||||
return True
|
||||
self._ele._run_js('this.click();')
|
||||
return self._ele
|
||||
if Settings.raise_when_click_failed:
|
||||
raise CanNotClickError
|
||||
return False
|
||||
@ -110,8 +113,7 @@ class Clicker(object):
|
||||
def right(self):
|
||||
"""右键单击"""
|
||||
self._ele.owner.scroll.to_see(self._ele)
|
||||
x, y = self._ele.rect.viewport_click_point
|
||||
self._click(x, y, 'right')
|
||||
return self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='right')
|
||||
|
||||
def middle(self, get_tab=True):
|
||||
"""中键单击,默认返回新出现的tab对象
|
||||
@ -119,13 +121,14 @@ class Clicker(object):
|
||||
:return: Tab对象或None
|
||||
"""
|
||||
self._ele.owner.scroll.to_see(self._ele)
|
||||
x, y = self._ele.rect.viewport_click_point
|
||||
self._click(x, y, 'middle')
|
||||
curr_tid = self._ele.tab.browser.tab_ids[0]
|
||||
self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='middle')
|
||||
if get_tab:
|
||||
tid = self._ele.page.wait.new_tab()
|
||||
tid = self._ele.tab.browser.wait.new_tab(curr_tab=curr_tid)
|
||||
if not tid:
|
||||
raise RuntimeError('没有出现新标签页。')
|
||||
return self._ele.page.get_tab(tid)
|
||||
return (self._ele.tab.browser.get_mix_tab(tid) if self._ele.tab._type == 'MixTab'
|
||||
else self._ele.tab.browser.get_tab(tid))
|
||||
|
||||
def at(self, offset_x=None, offset_y=None, button='left', count=1):
|
||||
"""带偏移量点击本元素,相对于左上角坐标。不传入x或y值时点击元素中间点
|
||||
@ -140,15 +143,14 @@ class Clicker(object):
|
||||
w, h = self._ele.rect.size
|
||||
offset_x = w // 2
|
||||
offset_y = h // 2
|
||||
x, y = offset_scroll(self._ele, offset_x, offset_y)
|
||||
self._click(x, y, button, count)
|
||||
return self._click(*offset_scroll(self._ele, offset_x, offset_y), button=button, count=count)
|
||||
|
||||
def multi(self, times=2):
|
||||
"""多次点击
|
||||
:param times: 默认双击
|
||||
:return: None
|
||||
"""
|
||||
self.at(count=times)
|
||||
return self.at(count=times)
|
||||
|
||||
def to_download(self, save_path=None, rename=None, suffix=None, new_tab=False, by_js=False, timeout=None):
|
||||
"""点击触发下载
|
||||
@ -161,17 +163,16 @@ class Clicker(object):
|
||||
:return: DownloadMission对象
|
||||
"""
|
||||
if save_path:
|
||||
self._ele.owner.tab.set.download_path(save_path)
|
||||
elif not self._ele.page._browser._dl_mgr._running:
|
||||
self._ele.page.set.download_path('.')
|
||||
self._ele.tab.set.download_path(save_path)
|
||||
elif not self._ele.tab._browser._dl_mgr._running:
|
||||
self._ele.tab._browser.set.download_path('.')
|
||||
|
||||
obj = self._ele.tab._browser if new_tab else self._ele.owner._tab
|
||||
if rename or suffix:
|
||||
self._ele.owner.tab.set.download_file_name(rename, suffix)
|
||||
|
||||
tab = self._ele.page if new_tab else self._ele.owner
|
||||
obj.set.download_file_name(rename, suffix)
|
||||
|
||||
self.left(by_js=by_js)
|
||||
return tab.wait.download_begin(timeout=timeout)
|
||||
return obj.wait.download_begin(timeout=timeout)
|
||||
|
||||
def to_upload(self, file_paths, by_js=False):
|
||||
"""触发上传文件选择框并自动填入指定路径
|
||||
@ -183,26 +184,61 @@ class Clicker(object):
|
||||
self.left(by_js=by_js)
|
||||
self._ele.owner.wait.upload_paths_inputted()
|
||||
|
||||
def for_new_tab(self, by_js=False):
|
||||
def for_new_tab(self, by_js=False, timeout=3):
|
||||
"""点击后等待新tab出现并返回其对象
|
||||
:param by_js: 是否使用js点击,逻辑与click()一致
|
||||
:param timeout: 等待超时时间
|
||||
:return: 新标签页对象,如果没有等到新标签页出现则抛出异常
|
||||
"""
|
||||
curr_tid = self._ele.tab.browser.tab_ids[0]
|
||||
self.left(by_js=by_js)
|
||||
tid = self._ele.page.wait.new_tab()
|
||||
tid = self._ele.tab.browser.wait.new_tab(timeout=timeout, curr_tab=curr_tid)
|
||||
if not tid:
|
||||
raise RuntimeError('没有出现新标签页。')
|
||||
return self._ele.page.get_tab(tid)
|
||||
return (self._ele.tab.browser.get_mix_tab(tid) if self._ele.tab._type == 'MixTab'
|
||||
else self._ele.tab.browser.get_tab(tid))
|
||||
|
||||
def _click(self, client_x, client_y, button='left', count=1):
|
||||
def for_url_change(self, text=None, exclude=False, by_js=False, timeout=None):
|
||||
"""点击并等待tab的url变成包含或不包含指定文本
|
||||
:param text: 用于识别的文本,为None等待当前url变化
|
||||
:param exclude: 是否排除,为True时当url不包含text指定文本时返回True,text为None时自动设为True
|
||||
:param by_js: 是否用js点击
|
||||
:param timeout: 超时时间(秒),为None使用页面设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if text is None:
|
||||
exclude = True
|
||||
text = self._ele.tab.url
|
||||
self.left(by_js=by_js)
|
||||
return True if self._ele.tab.wait.url_change(text=text, exclude=exclude, timeout=timeout) else False
|
||||
|
||||
def for_title_change(self, text=None, exclude=False, by_js=False, timeout=None):
|
||||
"""点击并等待tab的title变成包含或不包含指定文本
|
||||
:param text: 用于识别的文本,为None等待当前title变化
|
||||
:param exclude: 是否排除,为True时当title不包含text指定文本时返回True,text为None时自动设为True
|
||||
:param by_js: 是否用js点击
|
||||
:param timeout: 超时时间(秒),为None使用页面设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if text is None:
|
||||
exclude = True
|
||||
text = self._ele.tab.title
|
||||
self.left(by_js=by_js)
|
||||
return True if self._ele.tab.wait.title_change(text=text, exclude=exclude, timeout=timeout) else False
|
||||
|
||||
def _click(self, loc_x, loc_y, view_x, view_y, button='left', count=1):
|
||||
"""实施点击
|
||||
:param client_x: 视口中的x坐标
|
||||
:param client_y: 视口中的y坐标
|
||||
:param loc_x: 绝对x坐标
|
||||
:param loc_y: 绝对y坐标
|
||||
:param view_x: 视口x坐标
|
||||
:param view_y: 视口y坐标
|
||||
:param button: 'left' 'right' 'middle' 'back' 'forward'
|
||||
:param count: 点击次数
|
||||
:return: None
|
||||
"""
|
||||
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x,
|
||||
y=client_y, button=button, clickCount=count, _ignore=AlertExistsError)
|
||||
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x,
|
||||
y=client_y, button=button, _ignore=AlertExistsError)
|
||||
self._ele.owner.actions.move_to((loc_x, loc_y), duration=.05)
|
||||
self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=view_x,
|
||||
y=view_y, button=button, clickCount=count, _ignore=AlertExistsError)
|
||||
self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=view_x,
|
||||
y=view_y, button=button, _ignore=AlertExistsError)
|
||||
return self._ele
|
||||
|
@ -10,28 +10,30 @@ from typing import Union
|
||||
|
||||
from .downloader import DownloadMission
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_tab import WebPageTab, ChromiumTab
|
||||
from .._pages.tabs import MixTab, ChromiumTab
|
||||
|
||||
|
||||
class Clicker(object):
|
||||
def __init__(self, ele: ChromiumElement):
|
||||
self._ele: ChromiumElement = ...
|
||||
|
||||
def __call__(self, by_js: Union[bool, str, None] = False, timeout: float = 1.5, wait_stop: bool = True) -> bool: ...
|
||||
def __call__(self, by_js: Union[bool, str, None] = False,
|
||||
timeout: float = 1.5, wait_stop: bool = True) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def left(self, by_js: Union[bool, str, None] = False, timeout: float = 1.5, wait_stop: bool = True) -> bool: ...
|
||||
def left(self, by_js: Union[bool, str, None] = False,
|
||||
timeout: float = 1.5, wait_stop: bool = True) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def right(self) -> None: ...
|
||||
def right(self) -> ChromiumElement: ...
|
||||
|
||||
def middle(self, get_tab: bool = True) -> Union[ChromiumTab, WebPageTab, None]: ...
|
||||
def middle(self, get_tab: bool = True) -> Union[ChromiumTab, MixTab, None]: ...
|
||||
|
||||
def at(self,
|
||||
offset_x: float = None,
|
||||
offset_y: float = None,
|
||||
button: str = 'left',
|
||||
count: int = 1) -> None: ...
|
||||
count: int = 1) -> ChromiumElement: ...
|
||||
|
||||
def multi(self, times: int = 2) -> None: ...
|
||||
def multi(self, times: int = 2) -> ChromiumElement: ...
|
||||
|
||||
def to_download(self,
|
||||
save_path: Union[str, Path] = None,
|
||||
@ -43,6 +45,17 @@ class Clicker(object):
|
||||
|
||||
def to_upload(self, file_paths: Union[str, Path, list, tuple], by_js: bool = False) -> None: ...
|
||||
|
||||
def for_new_tab(self, by_js: bool = False) -> Union[ChromiumTab, WebPageTab]: ...
|
||||
def for_new_tab(self, by_js: bool = False, timeout: float = 3) -> Union[ChromiumTab, MixTab]: ...
|
||||
|
||||
def _click(self, client_x: float, client_y: float, button: str = 'left', count: int = 1) -> None: ...
|
||||
def for_url_change(self, text: str = None, exclude: bool = False,
|
||||
by_js: bool = False, timeout: float = None) -> bool: ...
|
||||
|
||||
def for_title_change(self, text: str = None, exclude: bool = False,
|
||||
by_js: bool = False, timeout: float = None) -> bool: ...
|
||||
|
||||
def _click(self, loc_x: float,
|
||||
loc_y: float,
|
||||
view_x: float,
|
||||
view_y: float,
|
||||
button: str = 'left',
|
||||
count: int = 1) -> ChromiumElement: ...
|
||||
|
@ -5,13 +5,13 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from .._functions.web import set_browser_cookies, set_session_cookies
|
||||
from .._functions.cookies import set_tab_cookies, set_session_cookies, set_browser_cookies
|
||||
|
||||
|
||||
class CookiesSetter(object):
|
||||
class BrowserCookiesSetter(object):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: ChromiumBase对象
|
||||
:param owner: Chromium对象
|
||||
"""
|
||||
self._owner = owner
|
||||
|
||||
@ -22,6 +22,24 @@ class CookiesSetter(object):
|
||||
"""
|
||||
set_browser_cookies(self._owner, cookies)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
self._owner._run_cdp('Storage.clearCookies')
|
||||
|
||||
|
||||
class CookiesSetter(BrowserCookiesSetter):
|
||||
|
||||
def __call__(self, cookies):
|
||||
"""设置一个或多个cookie
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
set_tab_cookies(self._owner, cookies)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
self._owner._run_cdp('Network.clearBrowserCookies')
|
||||
|
||||
def remove(self, name, url=None, domain=None, path=None):
|
||||
"""删除一个cookie
|
||||
:param name: cookie的name字段
|
||||
@ -37,13 +55,11 @@ class CookiesSetter(object):
|
||||
d['domain'] = domain
|
||||
if not url and not domain:
|
||||
d['url'] = self._owner.url
|
||||
if not d['url'].startswith('http'):
|
||||
raise ValueError('需设置domain或url值。如设置url值,需以http开头。')
|
||||
if path is not None:
|
||||
d['path'] = path
|
||||
self._owner.run_cdp('Network.deleteCookies', **d)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
self._owner.run_cdp('Network.clearBrowserCookies')
|
||||
self._owner._run_cdp('Network.deleteCookies', **d)
|
||||
|
||||
|
||||
class SessionCookiesSetter(object):
|
||||
@ -69,7 +85,7 @@ class SessionCookiesSetter(object):
|
||||
self._owner.session.cookies.clear()
|
||||
|
||||
|
||||
class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
class MixPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
|
||||
def __call__(self, cookies):
|
||||
"""设置多个cookie,注意不要传入单个
|
||||
|
@ -8,26 +8,35 @@
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from typing import Union
|
||||
|
||||
from .._base.browser import Chromium
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_tab import WebPageTab
|
||||
from .._pages.tabs import MixTab
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._pages.web_page import WebPage
|
||||
from .._pages.mix_page import MixPage
|
||||
|
||||
|
||||
class CookiesSetter(object):
|
||||
_owner: ChromiumBase
|
||||
class BrowserCookiesSetter(object):
|
||||
_owner: Chromium = ...
|
||||
|
||||
def __init__(self, page: ChromiumBase): ...
|
||||
def __init__(self, page: Chromium): ...
|
||||
|
||||
def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
def clear(self) -> None: ...
|
||||
|
||||
|
||||
class CookiesSetter(BrowserCookiesSetter):
|
||||
_owner: ChromiumBase = ...
|
||||
|
||||
def __init__(self, page: ChromiumBase): ...
|
||||
|
||||
def remove(self, name: str, url: str = None, domain: str = None, path: str = None) -> None: ...
|
||||
|
||||
def clear(self) -> None: ...
|
||||
|
||||
|
||||
class SessionCookiesSetter(object):
|
||||
_owner: SessionPage
|
||||
_owner: SessionPage = ...
|
||||
|
||||
def __init__(self, page: SessionPage): ...
|
||||
|
||||
@ -38,8 +47,8 @@ class SessionCookiesSetter(object):
|
||||
def clear(self) -> None: ...
|
||||
|
||||
|
||||
class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
_owner: Union[WebPage, WebPageTab]
|
||||
class MixPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
_owner: Union[MixPage, MixTab] = ...
|
||||
|
||||
def __init__(self, page: SessionPage): ...
|
||||
|
||||
|
@ -20,20 +20,23 @@ class DownloadManager(object):
|
||||
:param browser: Browser对象
|
||||
"""
|
||||
self._browser = browser
|
||||
self._page = browser.page
|
||||
self._when_download_file_exists = 'rename'
|
||||
self._save_path = None
|
||||
# self._page = browser.page
|
||||
# self._when_download_file_exists = 'rename'
|
||||
# self._save_path = None
|
||||
|
||||
t = TabDownloadSettings('browser')
|
||||
t.path = self._browser.download_path
|
||||
t.rename = None
|
||||
t.suffix = None
|
||||
t.when_file_exists = 'rename'
|
||||
|
||||
t = TabDownloadSettings(self._page.tab_id)
|
||||
t.path = self._page.download_path
|
||||
self._missions = {} # {guid: DownloadMission}
|
||||
self._tab_missions = {} # {tab_id: DownloadMission}
|
||||
self._flags = {} # {tab_id: [bool, DownloadMission]}
|
||||
|
||||
if self._page.download_path:
|
||||
self.set_path(self._page, self._page.download_path)
|
||||
# if self._page.download_path:
|
||||
# self.set_path(self._page, self._page.download_path)
|
||||
|
||||
else:
|
||||
self._running = False
|
||||
|
||||
@property
|
||||
@ -47,13 +50,13 @@ class DownloadManager(object):
|
||||
:param path: 下载路径(绝对路径str)
|
||||
:return: None
|
||||
"""
|
||||
TabDownloadSettings(tab.tab_id).path = path
|
||||
if tab is self._page or not self._running:
|
||||
self._browser.driver.set_callback('Browser.downloadProgress', self._onDownloadProgress)
|
||||
self._browser.driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin)
|
||||
r = self._browser.run_cdp('Browser.setDownloadBehavior', downloadPath=path,
|
||||
tid = tab if isinstance(tab, str) else tab.tab_id
|
||||
TabDownloadSettings(tid).path = path
|
||||
if not self._running or tid == 'browser':
|
||||
self._browser._driver.set_callback('Browser.downloadProgress', self._onDownloadProgress)
|
||||
self._browser._driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin)
|
||||
r = self._browser._run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path,
|
||||
behavior='allowAndName', eventsEnabled=True)
|
||||
self._save_path = path
|
||||
if 'error' in r:
|
||||
print('浏览器版本太低无法使用下载管理功能。')
|
||||
self._running = True
|
||||
@ -121,7 +124,7 @@ class DownloadManager(object):
|
||||
"""
|
||||
mission.state = 'canceled'
|
||||
try:
|
||||
self._browser.run_cdp('Browser.cancelDownload', guid=mission.id)
|
||||
self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)
|
||||
except:
|
||||
pass
|
||||
if mission.final_path:
|
||||
@ -134,7 +137,7 @@ class DownloadManager(object):
|
||||
"""
|
||||
mission.state = 'skipped'
|
||||
try:
|
||||
self._browser.run_cdp('Browser.cancelDownload', guid=mission.id)
|
||||
self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -149,10 +152,11 @@ class DownloadManager(object):
|
||||
|
||||
def _onDownloadWillBegin(self, **kwargs):
|
||||
"""用于获取弹出新标签页触发的下载任务"""
|
||||
# print(kwargs)
|
||||
guid = kwargs['guid']
|
||||
tab_id = self._browser._frames.get(kwargs['frameId'], self._page.tab_id)
|
||||
tab_id = self._browser._frames.get(kwargs['frameId'], 'browser')
|
||||
|
||||
settings = TabDownloadSettings(tab_id if tab_id in TabDownloadSettings.TABS else self._page.tab_id)
|
||||
settings = TabDownloadSettings(tab_id if tab_id in TabDownloadSettings.TABS else 'browser')
|
||||
if settings.rename:
|
||||
if settings.suffix is not None:
|
||||
name = f'{settings.rename}.{settings.suffix}' if settings.suffix else settings.rename
|
||||
@ -184,7 +188,7 @@ class DownloadManager(object):
|
||||
elif settings.when_file_exists == 'overwrite':
|
||||
goal_path.unlink()
|
||||
|
||||
m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._save_path)
|
||||
m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._browser.download_path)
|
||||
self._missions[guid] = m
|
||||
|
||||
if self.get_flag(tab_id) is False: # 取消该任务
|
||||
@ -214,7 +218,17 @@ class DownloadManager(object):
|
||||
mission.total_bytes = kwargs['totalBytes']
|
||||
form_path = f'{mission.save_path}{sep}{mission.id}'
|
||||
to_path = str(get_usable_path(f'{mission.path}{sep}{mission.name}'))
|
||||
not_moved = True
|
||||
for _ in range(10):
|
||||
try:
|
||||
move(form_path, to_path)
|
||||
not_moved = False
|
||||
break
|
||||
except PermissionError:
|
||||
sleep(.5)
|
||||
if not_moved:
|
||||
from shutil import copy
|
||||
copy(form_path, to_path)
|
||||
self.set_done(mission, 'completed', final_path=to_path)
|
||||
|
||||
else: # 'canceled'
|
||||
|
@ -7,30 +7,30 @@
|
||||
"""
|
||||
from typing import Dict, Optional, Union, Literal
|
||||
|
||||
from .._base.browser import Browser
|
||||
from .._base.browser import Chromium
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
|
||||
# from .._pages.chromium_page import ChromiumPage
|
||||
FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']
|
||||
|
||||
class DownloadManager(object):
|
||||
_browser: Browser = ...
|
||||
_page: ChromiumPage = ...
|
||||
_browser: Chromium = ...
|
||||
# _page: ChromiumPage = ...
|
||||
_missions: Dict[str, DownloadMission] = ...
|
||||
_tab_missions: dict = ...
|
||||
_flags: dict = ...
|
||||
_running: bool = ...
|
||||
_save_path: Optional[str] = ...
|
||||
# _save_path: Optional[str] = ...
|
||||
|
||||
def __init__(self, browser: Browser): ...
|
||||
def __init__(self, browser: Chromium): ...
|
||||
|
||||
@property
|
||||
def missions(self) -> Dict[str, DownloadMission]: ...
|
||||
|
||||
def set_path(self, tab: ChromiumBase, path: str) -> None: ...
|
||||
def set_path(self, tab: Union[str,ChromiumBase], path: str) -> None: ...
|
||||
|
||||
def set_rename(self, tab_id: str, rename: str = None, suffix: str = None) -> None: ...
|
||||
|
||||
def set_file_exists(self, tab_id: str, mode: Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']) -> None: ...
|
||||
def set_file_exists(self, tab_id: str, mode: FILE_EXISTS) -> None: ...
|
||||
|
||||
def set_flag(self, tab_id: str, flag: Union[bool, DownloadMission, None]) -> None: ...
|
||||
|
||||
|
@ -26,7 +26,7 @@ class Listener(object):
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
self._owner = owner
|
||||
self._address = owner.address
|
||||
self._address = owner.browser.address
|
||||
self._target_id = owner._target_id
|
||||
self._driver = None
|
||||
self._running_requests = 0
|
||||
@ -127,13 +127,13 @@ class Listener(object):
|
||||
if not self.listening:
|
||||
raise RuntimeError('监听未启动或已暂停。')
|
||||
if not timeout:
|
||||
while self._caught.qsize() < count:
|
||||
while self._driver.is_running and self._caught.qsize() < count:
|
||||
sleep(.03)
|
||||
fail = False
|
||||
|
||||
else:
|
||||
end = perf_counter() + timeout
|
||||
while True:
|
||||
while self._driver.is_running:
|
||||
if perf_counter() > end:
|
||||
fail = True
|
||||
break
|
||||
@ -167,8 +167,8 @@ class Listener(object):
|
||||
raise RuntimeError('监听未启动或已暂停。')
|
||||
caught = 0
|
||||
end = perf_counter() + timeout if timeout else None
|
||||
while True:
|
||||
if (timeout and perf_counter() > end) or self._driver._stopped.is_set():
|
||||
while self._driver.is_running:
|
||||
if (timeout and perf_counter() > end) or not self._driver.is_running:
|
||||
return
|
||||
if self._caught.qsize() >= gap:
|
||||
yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)]
|
||||
|
@ -18,7 +18,7 @@ class ElementRect(object):
|
||||
def corners(self):
|
||||
"""返回元素四个角坐标,顺序:左上、右上、右下、左下,没有大小的元素抛出NoRectError"""
|
||||
vr = self._get_viewport_rect('border')
|
||||
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
|
||||
r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
|
||||
sx = r['pageX']
|
||||
sy = r['pageY']
|
||||
return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)]
|
||||
@ -32,27 +32,24 @@ class ElementRect(object):
|
||||
@property
|
||||
def size(self):
|
||||
"""返回元素大小,格式(宽, 高)"""
|
||||
border = self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
|
||||
border = self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
|
||||
nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model']['border']
|
||||
return border[2] - border[0], border[5] - border[1]
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
"""返回元素左上角的绝对坐标"""
|
||||
cl = self.viewport_location
|
||||
return self._get_page_coord(cl[0], cl[1])
|
||||
return self._get_page_coord(*self.viewport_location)
|
||||
|
||||
@property
|
||||
def midpoint(self):
|
||||
"""返回元素中间点的绝对坐标"""
|
||||
cl = self.viewport_midpoint
|
||||
return self._get_page_coord(cl[0], cl[1])
|
||||
return self._get_page_coord(*self.viewport_midpoint)
|
||||
|
||||
@property
|
||||
def click_point(self):
|
||||
"""返回元素接受点击的点的绝对坐标"""
|
||||
cl = self.viewport_click_point
|
||||
return self._get_page_coord(cl[0], cl[1])
|
||||
return self._get_page_coord(*self.viewport_click_point)
|
||||
|
||||
@property
|
||||
def viewport_location(self):
|
||||
@ -77,7 +74,7 @@ class ElementRect(object):
|
||||
"""返回元素左上角在屏幕上坐标,左上角为(0, 0)"""
|
||||
vx, vy = self._ele.owner.rect.viewport_location
|
||||
ex, ey = self.viewport_location
|
||||
pr = self._ele.owner.run_js('return window.devicePixelRatio;')
|
||||
pr = self._ele.owner._run_js('return window.devicePixelRatio;')
|
||||
return (vx + ex) * pr, (ey + vy) * pr
|
||||
|
||||
@property
|
||||
@ -85,7 +82,7 @@ class ElementRect(object):
|
||||
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
|
||||
vx, vy = self._ele.owner.rect.viewport_location
|
||||
ex, ey = self.viewport_midpoint
|
||||
pr = self._ele.owner.run_js('return window.devicePixelRatio;')
|
||||
pr = self._ele.owner._run_js('return window.devicePixelRatio;')
|
||||
return (vx + ex) * pr, (ey + vy) * pr
|
||||
|
||||
@property
|
||||
@ -93,21 +90,26 @@ class ElementRect(object):
|
||||
"""返回元素中点在屏幕上坐标,左上角为(0, 0)"""
|
||||
vx, vy = self._ele.owner.rect.viewport_location
|
||||
ex, ey = self.viewport_click_point
|
||||
pr = self._ele.owner.run_js('return window.devicePixelRatio;')
|
||||
pr = self._ele.owner._run_js('return window.devicePixelRatio;')
|
||||
return (vx + ex) * pr, (ey + vy) * pr
|
||||
|
||||
@property
|
||||
def scroll_position(self):
|
||||
"""返回滚动条位置,格式:(x, y)"""
|
||||
r = self._ele._run_js('return this.scrollLeft.toString() + " " + this.scrollTop.toString();')
|
||||
w, h = r.split(' ')
|
||||
return int(w), int(h)
|
||||
|
||||
def _get_viewport_rect(self, quad):
|
||||
"""按照类型返回在可视窗口中的范围
|
||||
:param quad: 方框类型,margin border padding
|
||||
:return: 四个角坐标
|
||||
"""
|
||||
return self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
|
||||
# nodeId=self._ele._node_id, objectId=self._ele._obj_id
|
||||
)['model'][quad]
|
||||
return self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id)['model'][quad]
|
||||
|
||||
def _get_page_coord(self, x, y):
|
||||
"""根据视口坐标获取绝对坐标"""
|
||||
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
|
||||
r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
|
||||
sx = r['pageX']
|
||||
sy = r['pageY']
|
||||
return x + sx, y + sy
|
||||
@ -174,17 +176,23 @@ class TabRect(object):
|
||||
@property
|
||||
def viewport_size_with_scrollbar(self):
|
||||
"""返回视口宽高,包括滚动条,格式:(宽, 高)"""
|
||||
r = self._owner.run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();')
|
||||
r = self._owner._run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();')
|
||||
w, h = r.split(' ')
|
||||
return int(w), int(h)
|
||||
|
||||
@property
|
||||
def scroll_position(self):
|
||||
"""返回滚动条位置,格式:(x, y)"""
|
||||
r = self._get_page_rect()['visualViewport']
|
||||
return r['pageX'], r['pageY']
|
||||
|
||||
def _get_page_rect(self):
|
||||
"""获取页面范围信息"""
|
||||
return self._owner.run_cdp_loaded('Page.getLayoutMetrics')
|
||||
return self._owner._run_cdp_loaded('Page.getLayoutMetrics')
|
||||
|
||||
def _get_window_rect(self):
|
||||
"""获取窗口范围信息"""
|
||||
return self._owner.browser.get_window_bounds(self._owner.tab_id)
|
||||
return self._owner.browser._driver.run('Browser.getWindowForTarget', targetId=self._owner.tab_id)['bounds']
|
||||
|
||||
|
||||
class FrameRect(object):
|
||||
@ -214,8 +222,8 @@ class FrameRect(object):
|
||||
@property
|
||||
def size(self):
|
||||
"""返回frame内页面尺寸,格式:(宽, 高)"""
|
||||
w = self._frame.doc_ele.run_js('return this.body.scrollWidth')
|
||||
h = self._frame.doc_ele.run_js('return this.body.scrollHeight')
|
||||
w = self._frame.doc_ele._run_js('return this.body.scrollWidth')
|
||||
h = self._frame.doc_ele._run_js('return this.body.scrollHeight')
|
||||
return w, h
|
||||
|
||||
@property
|
||||
@ -232,3 +240,11 @@ class FrameRect(object):
|
||||
def viewport_corners(self):
|
||||
"""返回元素四个角视口坐标,顺序:左上、右上、右下、左下"""
|
||||
return self._frame.frame_ele.rect.viewport_corners
|
||||
|
||||
@property
|
||||
def scroll_position(self):
|
||||
"""返回滚动条位置,格式:(x, y)"""
|
||||
r = self._frame.doc_ele._run_js('return this.documentElement.scrollLeft.toString() + " " '
|
||||
'+ this.documentElement.scrollTop.toString();')
|
||||
w, h = r.split(' ')
|
||||
return int(w), int(h)
|
||||
|
@ -12,8 +12,8 @@ from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.chromium_tab import ChromiumTab, WebPageTab
|
||||
from .._pages.web_page import WebPage
|
||||
from .._pages.tabs import ChromiumTab, MixTab
|
||||
from .._pages.mix_page import MixPage
|
||||
|
||||
|
||||
class ElementRect(object):
|
||||
@ -56,6 +56,9 @@ class ElementRect(object):
|
||||
@property
|
||||
def viewport_corners(self) -> Tuple[Tuple[float, float], ...]: ...
|
||||
|
||||
@property
|
||||
def scroll_position(self) -> Tuple[float, float]: ...
|
||||
|
||||
def _get_viewport_rect(self, quad: str) -> Union[list, None]: ...
|
||||
|
||||
def _get_page_coord(self, x: float, y: float) -> Tuple[float, float]: ...
|
||||
@ -63,7 +66,7 @@ class ElementRect(object):
|
||||
|
||||
class TabRect(object):
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: Union[ChromiumPage, ChromiumTab, WebPage, WebPageTab] = ...
|
||||
self._owner: Union[ChromiumPage, ChromiumTab, MixPage, MixTab] = ...
|
||||
|
||||
@property
|
||||
def window_state(self) -> str: ...
|
||||
@ -89,6 +92,9 @@ class TabRect(object):
|
||||
@property
|
||||
def viewport_size_with_scrollbar(self) -> Tuple[int, int]: ...
|
||||
|
||||
@property
|
||||
def scroll_position(self) -> Tuple[int, int]: ...
|
||||
|
||||
def _get_page_rect(self) -> dict: ...
|
||||
|
||||
def _get_window_rect(self) -> dict: ...
|
||||
@ -118,3 +124,6 @@ class FrameRect(object):
|
||||
|
||||
@property
|
||||
def viewport_corners(self) -> Tuple[Tuple[float, float], ...]: ...
|
||||
|
||||
@property
|
||||
def scroll_position(self) -> Tuple[float, float]: ...
|
||||
|
@ -39,16 +39,16 @@ class Screencast(object):
|
||||
raise ValueError('save_path必须设置。')
|
||||
|
||||
if self._mode in ('frugal_video', 'video'):
|
||||
if self._owner.browser.page._chromium_options.tmp_path:
|
||||
if self._owner.browser._chromium_options.tmp_path:
|
||||
self._tmp_path = Path(
|
||||
self._owner.browser.page._chromium_options.tmp_path) / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
self._owner.browser._chromium_options.tmp_path) / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
else:
|
||||
self._tmp_path = Path(gettempdir()) / 'DrissionPage' / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
self._tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if self._mode.startswith('frugal'):
|
||||
self._owner.driver.set_callback('Page.screencastFrame', self._onScreencastFrame)
|
||||
self._owner.run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)
|
||||
self._owner._run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)
|
||||
|
||||
elif not self._mode.startswith('js'):
|
||||
self._running = True
|
||||
@ -79,8 +79,8 @@ class Screencast(object):
|
||||
}
|
||||
'''
|
||||
print('请手动选择要录制的目标。')
|
||||
self._owner.run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
|
||||
self._owner.run_js(js)
|
||||
self._owner._run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
|
||||
self._owner._run_js(js)
|
||||
|
||||
def stop(self, video_name=None):
|
||||
"""停止录屏
|
||||
@ -93,19 +93,19 @@ class Screencast(object):
|
||||
path = f'{self._path}{sep}{name}'
|
||||
|
||||
if self._mode.startswith('js'):
|
||||
self._owner.run_js('mediaRecorder.stop();', as_expr=True)
|
||||
while not self._owner.run_js('return DrissionPage_Screencast_blob_ok;'):
|
||||
self._owner._run_js('mediaRecorder.stop();', as_expr=True)
|
||||
while not self._owner._run_js('return DrissionPage_Screencast_blob_ok;'):
|
||||
sleep(.1)
|
||||
blob = self._owner.run_js('return DrissionPage_Screencast_blob;')
|
||||
uuid = self._owner.run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid']
|
||||
data = self._owner.run_cdp('IO.read', handle=f'blob:{uuid}')['data']
|
||||
blob = self._owner._run_js('return DrissionPage_Screencast_blob;')
|
||||
uuid = self._owner._run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid']
|
||||
data = self._owner._run_cdp('IO.read', handle=f'blob:{uuid}')['data']
|
||||
with open(path, 'wb') as f:
|
||||
f.write(b64decode(data))
|
||||
return path
|
||||
|
||||
if self._mode.startswith('frugal'):
|
||||
self._owner.driver.set_callback('Page.screencastFrame', None)
|
||||
self._owner.run_cdp('Page.stopScreencast')
|
||||
self._owner._run_cdp('Page.stopScreencast')
|
||||
else:
|
||||
self._enable = False
|
||||
while self._running:
|
||||
@ -164,7 +164,7 @@ class Screencast(object):
|
||||
path = self._tmp_path or self._path
|
||||
with open(f'{path}{sep}{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f:
|
||||
f.write(b64decode(kwargs['data']))
|
||||
self._owner.run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
|
||||
self._owner._run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
|
||||
|
||||
|
||||
class ScreencastMode(object):
|
||||
|
@ -16,12 +16,12 @@ class Scroller(object):
|
||||
:param ele: 元素对象
|
||||
"""
|
||||
self._driver = ele
|
||||
self.t1 = self.t2 = 'this'
|
||||
self._t1 = self._t2 = 'this'
|
||||
self._wait_complete = False
|
||||
|
||||
def _run_js(self, js):
|
||||
js = js.format(self.t1, self.t2, self.t2)
|
||||
self._driver.run_js(js)
|
||||
js = js.format(self._t1, self._t2, self._t2)
|
||||
self._driver._run_js(js)
|
||||
self._wait_scrolled()
|
||||
|
||||
def to_top(self):
|
||||
@ -88,14 +88,14 @@ class Scroller(object):
|
||||
return
|
||||
|
||||
owner = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver
|
||||
r = owner.run_cdp('Page.getLayoutMetrics')
|
||||
r = owner._run_cdp('Page.getLayoutMetrics')
|
||||
x = r['layoutViewport']['pageX']
|
||||
y = r['layoutViewport']['pageY']
|
||||
|
||||
end_time = perf_counter() + owner.timeout
|
||||
while perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
r = owner.run_cdp('Page.getLayoutMetrics')
|
||||
r = owner._run_cdp('Page.getLayoutMetrics')
|
||||
x1 = r['layoutViewport']['pageX']
|
||||
y1 = r['layoutViewport']['pageY']
|
||||
|
||||
@ -125,8 +125,8 @@ class PageScroller(Scroller):
|
||||
:param owner: 页面对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
self.t1 = 'window'
|
||||
self.t2 = 'document.documentElement'
|
||||
self._t1 = 'window'
|
||||
self._t2 = 'document.documentElement'
|
||||
|
||||
def to_see(self, loc_or_ele, center=None):
|
||||
"""滚动页面直到元素可见
|
||||
@ -144,9 +144,9 @@ class PageScroller(Scroller):
|
||||
:return: None
|
||||
"""
|
||||
txt = 'true' if center else 'false'
|
||||
ele.run_js(f'this.scrollIntoViewIfNeeded({txt});')
|
||||
ele._run_js(f'this.scrollIntoViewIfNeeded({txt});')
|
||||
if center or (center is not False and ele.states.is_covered):
|
||||
ele.run_js('''function getWindowScrollTop() {let scroll_top = 0;
|
||||
ele._run_js('''function getWindowScrollTop() {let scroll_top = 0;
|
||||
if (document.documentElement && document.documentElement.scrollTop) {
|
||||
scroll_top = document.documentElement.scrollTop;
|
||||
} else if (document.body) {scroll_top = document.body.scrollTop;}
|
||||
@ -165,7 +165,7 @@ class FrameScroller(PageScroller):
|
||||
:param frame: ChromiumFrame对象
|
||||
"""
|
||||
super().__init__(frame.doc_ele)
|
||||
self.t1 = self.t2 = 'this.documentElement'
|
||||
self._t1 = self._t2 = 'this.documentElement'
|
||||
|
||||
def to_see(self, loc_or_ele, center=None):
|
||||
"""滚动页面直到元素可见
|
||||
@ -173,5 +173,5 @@ class FrameScroller(PageScroller):
|
||||
:param center: 是否尽量滚动到页面正中,为None时如果被遮挡,则滚动到页面正中
|
||||
:return: None
|
||||
"""
|
||||
ele = loc_or_ele if loc_or_ele._type == 'ChromiumElement' else self._driver._ele(loc_or_ele)
|
||||
ele = self._driver._ele(loc_or_ele)
|
||||
self._to_see(ele, center)
|
||||
|
@ -13,8 +13,8 @@ from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
class Scroller(object):
|
||||
def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement]):
|
||||
self.t1: str = ...
|
||||
self.t2: str = ...
|
||||
self._t1: str = ...
|
||||
self._t2: str = ...
|
||||
self._driver: Union[ChromiumBase, ChromiumElement] = ...
|
||||
self._wait_complete: bool = ...
|
||||
|
||||
@ -64,7 +64,7 @@ class FrameScroller(PageScroller):
|
||||
:param frame: ChromiumFrame对象
|
||||
"""
|
||||
self._driver = frame.doc_ele
|
||||
self.t1 = self.t2 = 'this.documentElement'
|
||||
self._t1 = self._t2 = 'this.documentElement'
|
||||
self._wait_complete = False
|
||||
|
||||
def to_see(self, loc_or_ele, center=None):
|
||||
|
@ -45,7 +45,7 @@ class SelectElement(object):
|
||||
"""返回第一个被选中的option元素
|
||||
:return: ChromiumElement对象或None
|
||||
"""
|
||||
ele = self._ele.run_js('return this.options[this.selectedIndex];')
|
||||
ele = self._ele._run_js('return this.options[this.selectedIndex];')
|
||||
return ele
|
||||
|
||||
@property
|
||||
@ -69,7 +69,7 @@ class SelectElement(object):
|
||||
for i in self.options:
|
||||
change = True
|
||||
mode = 'false' if i.states.is_selected else 'true'
|
||||
i.run_js(f'this.selected={mode};')
|
||||
i._run_js(f'this.selected={mode};')
|
||||
if change:
|
||||
self._dispatch_change()
|
||||
|
||||
@ -258,12 +258,12 @@ class SelectElement(object):
|
||||
if not self.is_multi and len(option) > 1:
|
||||
option = option[:1]
|
||||
for o in option:
|
||||
o.run_js(f'this.selected={mode};')
|
||||
o._run_js(f'this.selected={mode};')
|
||||
self._dispatch_change()
|
||||
else:
|
||||
option.run_js(f'this.selected={mode};')
|
||||
option._run_js(f'this.selected={mode};')
|
||||
self._dispatch_change()
|
||||
|
||||
def _dispatch_change(self):
|
||||
"""触发修改动作"""
|
||||
self._ele.run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));')
|
||||
self._ele._run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));')
|
||||
|
@ -10,14 +10,14 @@ from time import sleep
|
||||
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter
|
||||
from .cookies_setter import SessionCookiesSetter, CookiesSetter, MixPageCookiesSetter, BrowserCookiesSetter
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import show_or_hide_browser
|
||||
from .._functions.web import format_headers
|
||||
from ..errors import ElementLostError, JavaScriptError
|
||||
|
||||
|
||||
class BasePageSetter(object):
|
||||
class BaseSetter(object):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: BasePage对象
|
||||
@ -33,32 +33,6 @@ class BasePageSetter(object):
|
||||
self._owner._none_ele_return_value = on_off
|
||||
self._owner._none_ele_value = value
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BasePageSetter):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
self._cookies_setter = None
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
"""返回用于设置页面加载策略的对象"""
|
||||
return LoadMode(self._owner)
|
||||
|
||||
@property
|
||||
def scroll(self):
|
||||
"""返回用于设置页面滚动设置的对象"""
|
||||
return PageScrollSetter(self._owner.scroll)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = CookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def retry_times(self, times):
|
||||
"""设置连接失败重连次数"""
|
||||
self._owner.retry_times = times
|
||||
@ -67,189 +41,17 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
"""设置连接失败重连间隔"""
|
||||
self._owner.retry_interval = interval
|
||||
|
||||
def timeouts(self, base=None, page_load=None, script=None, implicit=None):
|
||||
"""设置超时时间,单位为秒
|
||||
:param base: 基本等待时间,除页面加载和脚本超时,其它等待默认使用
|
||||
:param page_load: 页面加载超时时间
|
||||
:param script: 脚本运行超时时间
|
||||
:return: None
|
||||
"""
|
||||
base = base if base is not None else implicit
|
||||
if base is not None:
|
||||
self._owner.timeouts.base = base
|
||||
self._owner._timeout = base
|
||||
|
||||
if page_load is not None:
|
||||
self._owner.timeouts.page_load = page_load
|
||||
|
||||
if script is not None:
|
||||
self._owner.timeouts.script = script
|
||||
|
||||
def user_agent(self, ua, platform=None):
|
||||
"""为当前tab设置user agent,只在当前tab有效
|
||||
:param ua: user agent字符串
|
||||
:param platform: platform字符串
|
||||
:return: None
|
||||
"""
|
||||
keys = {'userAgent': ua}
|
||||
if platform:
|
||||
keys['platform'] = platform
|
||||
self._owner.run_cdp('Emulation.setUserAgentOverride', **keys)
|
||||
|
||||
def session_storage(self, item, value):
|
||||
"""设置或删除某项sessionStorage信息
|
||||
:param item: 要设置的项
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._owner.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._owner.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': False}, key=item)
|
||||
else:
|
||||
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},
|
||||
key=item, value=value)
|
||||
self._owner.run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def local_storage(self, item, value):
|
||||
"""设置或删除某项localStorage信息
|
||||
:param item: 要设置的项
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._owner.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._owner.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': True}, key=item)
|
||||
else:
|
||||
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},
|
||||
key=item, value=value)
|
||||
self._owner.run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def upload_files(self, files):
|
||||
"""等待上传的文件路径
|
||||
:param files: 文件路径列表或字符串,字符串时多个文件用回车分隔
|
||||
:return: None
|
||||
"""
|
||||
if not self._owner._upload_list:
|
||||
self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened)
|
||||
self._owner.run_cdp('Page.setInterceptFileChooserDialog', enabled=True)
|
||||
|
||||
if isinstance(files, str):
|
||||
files = files.split('\n')
|
||||
elif isinstance(files, Path):
|
||||
files = (files,)
|
||||
self._owner._upload_list = [str(Path(i).absolute()) for i in files]
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
"""设置固定发送的headers
|
||||
:param headers: dict格式的headers数据
|
||||
:return: None
|
||||
"""
|
||||
self._owner.run_cdp('Network.enable')
|
||||
self._owner.run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers))
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:return: None
|
||||
"""
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
def blocked_urls(self, urls):
|
||||
"""设置要忽略的url
|
||||
:param urls: 要忽略的url,可用*通配符,可输入多个,传入None时清空已设置的内容
|
||||
:return: None
|
||||
"""
|
||||
if not urls:
|
||||
urls = []
|
||||
elif isinstance(urls, str):
|
||||
urls = (urls,)
|
||||
if not isinstance(urls, (list, tuple)):
|
||||
raise TypeError('urls需传入str、list或tuple类型。')
|
||||
self._owner.run_cdp('Network.enable')
|
||||
self._owner.run_cdp('Network.setBlockedURLs', urls=urls)
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: 标签页对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
"""返回用于设置浏览器窗口的对象"""
|
||||
return WindowSetter(self._owner)
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
:param path: 下载路径
|
||||
:return: None
|
||||
"""
|
||||
path = str(Path(path).absolute())
|
||||
self._owner._download_path = path
|
||||
self._owner.browser._dl_mgr.set_path(self._owner, path)
|
||||
if self._owner._DownloadKit:
|
||||
self._owner._DownloadKit.set.goal_path(path)
|
||||
|
||||
def download_file_name(self, name=None, suffix=None):
|
||||
"""设置下一个被下载文件的名称
|
||||
:param name: 文件名,可不含后缀,会自动使用远程文件后缀
|
||||
:param suffix: 后缀名,显式设置后缀名,不使用远程文件后缀
|
||||
:return: None
|
||||
"""
|
||||
self._owner.browser._dl_mgr.set_rename(self._owner.tab_id, name, suffix)
|
||||
|
||||
def when_download_file_exists(self, mode):
|
||||
"""设置当存在同名文件时的处理方式
|
||||
:param mode: 可在 'rename', 'overwrite', 'skip', 'r', 'o', 's'中选择
|
||||
:return: None
|
||||
"""
|
||||
types = {'rename': 'rename', 'overwrite': 'overwrite', 'skip': 'skip', 'r': 'rename', 'o': 'overwrite',
|
||||
's': 'skip'}
|
||||
mode = types.get(mode, mode)
|
||||
if mode not in types:
|
||||
raise ValueError(f'''mode参数只能是 '{"', '".join(types.keys())}' 之一,现在是:{mode}''')
|
||||
|
||||
self._owner.browser._dl_mgr.set_file_exists(self._owner.tab_id, mode)
|
||||
|
||||
def activate(self):
|
||||
"""使标签页处于最前面"""
|
||||
self._owner.browser.activate_tab(self._owner.tab_id)
|
||||
if path is None:
|
||||
path = '.'
|
||||
self._owner._download_path = str(Path(path).absolute())
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
|
||||
def tab_to_front(self, tab_or_id=None):
|
||||
"""激活标签页使其处于最前面
|
||||
:param tab_or_id: 标签页对象或id,为None表示当前标签页
|
||||
:return: None
|
||||
"""
|
||||
if not tab_or_id:
|
||||
tab_or_id = self._owner.tab_id
|
||||
elif not isinstance(tab_or_id, str): # 传入Tab对象
|
||||
tab_or_id = tab_or_id.tab_id
|
||||
self._owner.browser.activate_tab(tab_or_id)
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True, all_tabs=False):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:param all_tabs: 是否为全局设置
|
||||
:return: None
|
||||
"""
|
||||
if all_tabs:
|
||||
Settings.auto_handle_alert = on_off
|
||||
else:
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
|
||||
class SessionPageSetter(BasePageSetter):
|
||||
class SessionPageSetter(BaseSetter):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: SessionPage对象
|
||||
@ -264,30 +66,21 @@ class SessionPageSetter(BasePageSetter):
|
||||
self._cookies_setter = SessionCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def retry_times(self, times):
|
||||
"""设置连接失败时重连次数"""
|
||||
self._owner.retry_times = times
|
||||
|
||||
def retry_interval(self, interval):
|
||||
"""设置连接失败时重连间隔"""
|
||||
self._owner.retry_interval = interval
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
:param path: 下载路径
|
||||
:return: None
|
||||
"""
|
||||
path = str(Path(path).absolute())
|
||||
self._owner._download_path = path
|
||||
super().download_path(path)
|
||||
if self._owner._DownloadKit:
|
||||
self._owner._DownloadKit.set.goal_path(path)
|
||||
self._owner._DownloadKit.set.goal_path(self._owner._download_path)
|
||||
|
||||
def timeout(self, second):
|
||||
"""设置连接超时时间
|
||||
:param second: 秒数
|
||||
:return: None
|
||||
"""
|
||||
self._owner.timeout = second
|
||||
self._owner._timeout = second
|
||||
|
||||
def encoding(self, encoding, set_all=True):
|
||||
"""设置编码
|
||||
@ -395,7 +188,272 @@ class SessionPageSetter(BasePageSetter):
|
||||
self._owner.session.mount(url, adapter)
|
||||
|
||||
|
||||
class WebPageSetter(ChromiumPageSetter):
|
||||
class BrowserBaseSetter(BaseSetter):
|
||||
"""Browser和ChromiumBase设置"""
|
||||
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
self._cookies_setter = None
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
"""返回用于设置页面加载策略的对象"""
|
||||
return LoadMode(self._owner)
|
||||
|
||||
def timeouts(self, base=None, page_load=None, script=None):
|
||||
"""设置超时时间,单位为秒
|
||||
:param base: 基本等待时间,除页面加载和脚本超时,其它等待默认使用
|
||||
:param page_load: 页面加载超时时间
|
||||
:param script: 脚本运行超时时间
|
||||
:return: None
|
||||
"""
|
||||
if base is not None:
|
||||
self._owner.timeouts.base = base
|
||||
|
||||
if page_load is not None:
|
||||
self._owner.timeouts.page_load = page_load
|
||||
|
||||
if script is not None:
|
||||
self._owner.timeouts.script = script
|
||||
|
||||
|
||||
class BrowserSetter(BrowserBaseSetter):
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = BrowserCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:return: None
|
||||
"""
|
||||
Settings.auto_handle_alert = accept if on_off else None
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
:param path: 下载路径
|
||||
:return: None
|
||||
"""
|
||||
super().download_path(path)
|
||||
self._owner._dl_mgr.set_path('browser', self._owner._download_path)
|
||||
|
||||
def download_file_name(self, name=None, suffix=None):
|
||||
"""设置下一个被下载文件的名称
|
||||
:param name: 文件名,可不含后缀,会自动使用远程文件后缀
|
||||
:param suffix: 后缀名,显式设置后缀名,不使用远程文件后缀
|
||||
:return: None
|
||||
"""
|
||||
self._owner._dl_mgr.set_rename('browser', name, suffix)
|
||||
|
||||
def when_download_file_exists(self, mode):
|
||||
"""设置当存在同名文件时的处理方式
|
||||
:param mode: 可在 'rename', 'overwrite', 'skip', 'r', 'o', 's'中选择
|
||||
:return: None
|
||||
"""
|
||||
types = {'rename': 'rename', 'overwrite': 'overwrite', 'skip': 'skip', 'r': 'rename', 'o': 'overwrite',
|
||||
's': 'skip'}
|
||||
mode = types.get(mode, mode)
|
||||
if mode not in types:
|
||||
raise ValueError(f'''mode参数只能是 '{"', '".join(types.keys())}' 之一,现在是:{mode}''')
|
||||
self._owner._dl_mgr.set_file_exists('browser', mode)
|
||||
|
||||
# ---------- 即将废弃 ----------
|
||||
def tab_to_front(self, tab_or_id):
|
||||
"""激活标签页使其处于最前面
|
||||
:param tab_or_id: 标签页对象或id
|
||||
:return: None
|
||||
"""
|
||||
if not isinstance(tab_or_id, str): # 传入Tab对象
|
||||
tab_or_id = tab_or_id.tab_id
|
||||
self._owner.activate_tab(tab_or_id)
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BrowserBaseSetter):
|
||||
|
||||
@property
|
||||
def scroll(self):
|
||||
"""返回用于设置页面滚动设置的对象"""
|
||||
return PageScrollSetter(self._owner.scroll)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = CookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def user_agent(self, ua, platform=None):
|
||||
"""为当前tab设置user agent,只在当前tab有效
|
||||
:param ua: user agent字符串
|
||||
:param platform: platform字符串
|
||||
:return: None
|
||||
"""
|
||||
keys = {'userAgent': ua}
|
||||
if platform:
|
||||
keys['platform'] = platform
|
||||
self._owner._run_cdp('Emulation.setUserAgentOverride', **keys)
|
||||
|
||||
def session_storage(self, item, value):
|
||||
"""设置或删除某项sessionStorage信息
|
||||
:param item: 要设置的项
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._owner._run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._owner._run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': False}, key=item)
|
||||
else:
|
||||
self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},
|
||||
key=item, value=value)
|
||||
self._owner._run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def local_storage(self, item, value):
|
||||
"""设置或删除某项localStorage信息
|
||||
:param item: 要设置的项
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._owner._run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._owner._run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': True}, key=item)
|
||||
else:
|
||||
self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},
|
||||
key=item, value=value)
|
||||
self._owner._run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def upload_files(self, files):
|
||||
"""等待上传的文件路径
|
||||
:param files: 文件路径列表或字符串,字符串时多个文件用回车分隔
|
||||
:return: None
|
||||
"""
|
||||
if not self._owner._upload_list:
|
||||
self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened)
|
||||
self._owner._run_cdp('Page.setInterceptFileChooserDialog', enabled=True)
|
||||
|
||||
if isinstance(files, str):
|
||||
files = files.split('\n')
|
||||
elif isinstance(files, Path):
|
||||
files = (files,)
|
||||
self._owner._upload_list = [str(Path(i).absolute()) for i in files]
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
"""设置固定发送的headers
|
||||
:param headers: dict格式的headers数据,或从浏览器复制的headers文本(\n分行)
|
||||
:return: None
|
||||
"""
|
||||
self._owner._run_cdp('Network.enable')
|
||||
self._owner._run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers))
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:return: None
|
||||
"""
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
def blocked_urls(self, urls):
|
||||
"""设置要忽略的url
|
||||
:param urls: 要忽略的url,可用*通配符,可输入多个,传入None时清空已设置的内容
|
||||
:return: None
|
||||
"""
|
||||
if not urls:
|
||||
urls = []
|
||||
elif isinstance(urls, str):
|
||||
urls = (urls,)
|
||||
if not isinstance(urls, (list, tuple)):
|
||||
raise TypeError('urls需传入str、list或tuple类型。')
|
||||
self._owner._run_cdp('Network.enable')
|
||||
self._owner._run_cdp('Network.setBlockedURLs', urls=urls)
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: 标签页对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
"""返回用于设置浏览器窗口的对象"""
|
||||
return WindowSetter(self._owner)
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
:param path: 下载路径
|
||||
:return: None
|
||||
"""
|
||||
super().download_path(path)
|
||||
self._owner.browser._dl_mgr.set_path(self._owner, self._owner._download_path)
|
||||
if self._owner._DownloadKit:
|
||||
self._owner._DownloadKit.set.goal_path(self._owner._download_path)
|
||||
|
||||
def download_file_name(self, name=None, suffix=None):
|
||||
"""设置下一个被下载文件的名称
|
||||
:param name: 文件名,可不含后缀,会自动使用远程文件后缀
|
||||
:param suffix: 后缀名,显式设置后缀名,不使用远程文件后缀
|
||||
:return: None
|
||||
"""
|
||||
self._owner.browser._dl_mgr.set_rename(self._owner.tab_id, name, suffix)
|
||||
|
||||
def when_download_file_exists(self, mode):
|
||||
"""设置当存在同名文件时的处理方式
|
||||
:param mode: 可在 'rename', 'overwrite', 'skip', 'r', 'o', 's'中选择
|
||||
:return: None
|
||||
"""
|
||||
types = {'rename': 'rename', 'overwrite': 'overwrite', 'skip': 'skip', 'r': 'rename', 'o': 'overwrite',
|
||||
's': 'skip'}
|
||||
mode = types.get(mode, mode)
|
||||
if mode not in types:
|
||||
raise ValueError(f'''mode参数只能是 '{"', '".join(types.keys())}' 之一,现在是:{mode}''')
|
||||
self._owner.browser._dl_mgr.set_file_exists(self._owner.tab_id, mode)
|
||||
|
||||
def activate(self):
|
||||
"""使标签页处于最前面"""
|
||||
self._owner.browser.activate_tab(self._owner.tab_id)
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True, all_tabs=False):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:param all_tabs: 是否为全局设置
|
||||
:return: None
|
||||
"""
|
||||
if all_tabs:
|
||||
Settings.auto_handle_alert = on_off
|
||||
else:
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
# ---------- 即将废弃 ----------
|
||||
def tab_to_front(self, tab_or_id=None):
|
||||
"""激活标签页使其处于最前面
|
||||
:param tab_or_id: 标签页对象或id,为None表示当前标签页
|
||||
:return: None
|
||||
"""
|
||||
if not tab_or_id:
|
||||
tab_or_id = self._owner.tab_id
|
||||
elif not isinstance(tab_or_id, str): # 传入Tab对象
|
||||
tab_or_id = tab_or_id.tab_id
|
||||
self._owner.browser.activate_tab(tab_or_id)
|
||||
|
||||
|
||||
class MixPageSetter(ChromiumPageSetter):
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._session_setter = SessionPageSetter(self._owner)
|
||||
@ -405,7 +463,7 @@ class WebPageSetter(ChromiumPageSetter):
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = WebPageCookiesSetter(self._owner)
|
||||
self._cookies_setter = MixPageCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
@ -426,7 +484,7 @@ class WebPageSetter(ChromiumPageSetter):
|
||||
self._chromium_setter.user_agent(ua, platform)
|
||||
|
||||
|
||||
class WebPageTabSetter(TabSetter):
|
||||
class MixTabSetter(TabSetter):
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._session_setter = SessionPageSetter(self._owner)
|
||||
@ -436,7 +494,7 @@ class WebPageTabSetter(TabSetter):
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = WebPageCookiesSetter(self._owner)
|
||||
self._cookies_setter = MixPageCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
@ -456,6 +514,17 @@ class WebPageTabSetter(TabSetter):
|
||||
if self._owner._has_driver:
|
||||
self._chromium_setter.user_agent(ua, platform)
|
||||
|
||||
def timeouts(self, base=None, page_load=None, script=None):
|
||||
"""设置超时时间,单位为秒
|
||||
:param base: 基本等待时间,除页面加载和脚本超时,其它等待默认使用
|
||||
:param page_load: 页面加载超时时间
|
||||
:param script: 脚本运行超时时间
|
||||
:return: None
|
||||
"""
|
||||
super().timeouts(base=base, page_load=page_load, script=script)
|
||||
if base is not None:
|
||||
self._owner._timeout = base
|
||||
|
||||
|
||||
class ChromiumElementSetter(object):
|
||||
def __init__(self, ele):
|
||||
@ -464,18 +533,18 @@ class ChromiumElementSetter(object):
|
||||
"""
|
||||
self._ele = ele
|
||||
|
||||
def attr(self, name, value):
|
||||
def attr(self, name, value=''):
|
||||
"""设置元素attribute属性
|
||||
:param name: 属性名
|
||||
:param value: 属性值
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._ele.owner.run_cdp('DOM.setAttributeValue',
|
||||
self._ele.owner._run_cdp('DOM.setAttributeValue',
|
||||
nodeId=self._ele._node_id, name=name, value=str(value))
|
||||
except ElementLostError:
|
||||
self._ele._refresh_id()
|
||||
self._ele.owner.run_cdp('DOM.setAttributeValue',
|
||||
self._ele.owner._run_cdp('DOM.setAttributeValue',
|
||||
nodeId=self._ele._node_id, name=name, value=str(value))
|
||||
|
||||
def property(self, name, value):
|
||||
@ -485,7 +554,7 @@ class ChromiumElementSetter(object):
|
||||
:return: None
|
||||
"""
|
||||
value = value.replace('"', r'\"')
|
||||
self._ele.run_js(f'this.{name}="{value}";')
|
||||
self._ele._run_js(f'this.{name}="{value}";')
|
||||
|
||||
def style(self, name, value):
|
||||
"""设置元素style样式
|
||||
@ -494,7 +563,7 @@ class ChromiumElementSetter(object):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._ele.run_js(f'this.style.{name}="{value}";')
|
||||
self._ele._run_js(f'this.style.{name}="{value}";')
|
||||
except JavaScriptError:
|
||||
raise ValueError(f'设置失败,请检查属性名{name}')
|
||||
|
||||
@ -522,6 +591,22 @@ class ChromiumFrameSetter(ChromiumBaseSetter):
|
||||
"""
|
||||
self._owner.frame_ele.set.attr(name, value)
|
||||
|
||||
def property(self, name, value):
|
||||
"""设置元素property属性
|
||||
:param name: 属性名
|
||||
:param value: 属性值
|
||||
:return: None
|
||||
"""
|
||||
self._owner.frame_ele.set.property(name=name, value=value)
|
||||
|
||||
def style(self, name, value):
|
||||
"""设置元素style样式
|
||||
:param name: 样式名称
|
||||
:param value: 样式值
|
||||
:return: None
|
||||
"""
|
||||
self._owner.frame_ele.set.style(name=name, value=value)
|
||||
|
||||
|
||||
class LoadMode(object):
|
||||
"""用于设置页面加载策略的类"""
|
||||
@ -578,7 +663,7 @@ class PageScrollSetter(object):
|
||||
if not isinstance(on_off, bool):
|
||||
raise TypeError('on_off必须为bool。')
|
||||
b = 'smooth' if on_off else 'auto'
|
||||
self._scroll._driver.run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");')
|
||||
self._scroll._driver._run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");')
|
||||
self._scroll._wait_complete = on_off
|
||||
|
||||
|
||||
@ -652,7 +737,7 @@ class WindowSetter(object):
|
||||
"""获取窗口位置及大小信息"""
|
||||
for _ in range(50):
|
||||
try:
|
||||
return self._owner.run_cdp('Browser.getWindowForTarget')
|
||||
return self._owner._run_cdp('Browser.getWindowForTarget')
|
||||
except:
|
||||
sleep(.1)
|
||||
|
||||
@ -662,7 +747,7 @@ class WindowSetter(object):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._owner.run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)
|
||||
self._owner._run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)
|
||||
except:
|
||||
raise RuntimeError('浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。')
|
||||
|
||||
|
@ -11,88 +11,35 @@ from typing import Union, Tuple, Literal, Any, Optional
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter
|
||||
from .cookies_setter import SessionCookiesSetter, CookiesSetter, MixPageCookiesSetter, BrowserCookiesSetter
|
||||
from .scroller import PageScroller
|
||||
from .._base.base import BasePage
|
||||
from .._base.browser import Chromium
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.chromium_tab import ChromiumTab, WebPageTab
|
||||
from .._pages.tabs import ChromiumTab, MixTab
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._pages.web_page import WebPage
|
||||
from .._pages.mix_page import MixPage
|
||||
|
||||
FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']
|
||||
|
||||
|
||||
class BasePageSetter(object):
|
||||
def __init__(self, owner: BasePage):
|
||||
self._owner: BasePage = ...
|
||||
class BaseSetter(object):
|
||||
def __init__(self, owner: Union[Chromium, BasePage]):
|
||||
self._owner: Union[Chromium, BasePage] = ...
|
||||
|
||||
def NoneElement_value(self, value: Any = None, on_off: bool = True) -> None: ...
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BasePageSetter):
|
||||
def __init__(self, owner):
|
||||
self._owner: ChromiumBase = ...
|
||||
self._cookies_setter: CookiesSetter = ...
|
||||
|
||||
@property
|
||||
def load_mode(self) -> LoadMode: ...
|
||||
|
||||
@property
|
||||
def scroll(self) -> PageScrollSetter: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> CookiesSetter: ...
|
||||
|
||||
def retry_times(self, times: int) -> None: ...
|
||||
|
||||
def retry_interval(self, interval: float) -> 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 session_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
||||
|
||||
def local_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
||||
|
||||
def headers(self, headers: Union[dict, str]) -> None: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True) -> None: ...
|
||||
|
||||
def upload_files(self, files: Union[str, Path, list, tuple]) -> None: ...
|
||||
|
||||
def blocked_urls(self, urls: Union[list, tuple, str, None]) -> None: ...
|
||||
def download_path(self, path: Union[str, Path, None]) -> None: ...
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
_owner: ChromiumTab = ...
|
||||
|
||||
def __init__(self, owner: Union[ChromiumTab, WebPageTab, WebPage, ChromiumPage]): ...
|
||||
|
||||
@property
|
||||
def window(self) -> WindowSetter: ...
|
||||
|
||||
def download_path(self, path: Union[str, Path]) -> None: ...
|
||||
|
||||
def download_file_name(self, name: str = None, suffix: str = None) -> None: ...
|
||||
|
||||
def when_download_file_exists(self, mode: FILE_EXISTS) -> None: ...
|
||||
|
||||
def activate(self) -> None: ...
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
_owner: ChromiumPage = ...
|
||||
|
||||
def tab_to_front(self, tab_or_id: Union[str, ChromiumTab] = None) -> None: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True, all_tabs: bool = False) -> None: ...
|
||||
|
||||
|
||||
class SessionPageSetter(BasePageSetter):
|
||||
class SessionPageSetter(BaseSetter):
|
||||
_owner: SessionPage = ...
|
||||
_cookies_setter: Optional[SessionCookiesSetter] = ...
|
||||
|
||||
@ -105,7 +52,7 @@ class SessionPageSetter(BasePageSetter):
|
||||
|
||||
def retry_interval(self, interval: float) -> None: ...
|
||||
|
||||
def download_path(self, path: Union[str, Path]) -> None: ...
|
||||
def download_path(self, path: Union[str, Path, None]) -> None: ...
|
||||
|
||||
def timeout(self, second: float) -> None: ...
|
||||
|
||||
@ -138,8 +85,83 @@ class SessionPageSetter(BasePageSetter):
|
||||
def add_adapter(self, url: str, adapter: HTTPAdapter) -> None: ...
|
||||
|
||||
|
||||
class WebPageSetter(ChromiumPageSetter):
|
||||
_owner: WebPage = ...
|
||||
class BrowserBaseSetter(BaseSetter):
|
||||
_cookies_setter: Optional[CookiesSetter] = ...
|
||||
|
||||
@property
|
||||
def load_mode(self) -> LoadMode: ...
|
||||
|
||||
def timeouts(self, base=None, page_load=None, script=None) -> None: ...
|
||||
|
||||
|
||||
class BrowserSetter(BrowserBaseSetter):
|
||||
_owner: Chromium = ...
|
||||
_cookies_setter: BrowserCookiesSetter = ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> BrowserCookiesSetter: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True): ...
|
||||
|
||||
def download_path(self, path: Union[Path, str, None]): ...
|
||||
|
||||
def download_file_name(self, name: str = None, suffix: str = None): ...
|
||||
|
||||
def when_download_file_exists(self, mode: FILE_EXISTS): ...
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BrowserBaseSetter):
|
||||
_owner: ChromiumBase = ...
|
||||
_cookies_setter: CookiesSetter = ...
|
||||
|
||||
def __init__(self, owner): ...
|
||||
|
||||
@property
|
||||
def scroll(self) -> PageScrollSetter: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> CookiesSetter: ...
|
||||
|
||||
def user_agent(self, ua: str, platform: str = None) -> None: ...
|
||||
|
||||
def session_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
||||
|
||||
def local_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
||||
|
||||
def headers(self, headers: Union[dict, str]) -> None: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True) -> None: ...
|
||||
|
||||
def upload_files(self, files: Union[str, Path, list, tuple]) -> None: ...
|
||||
|
||||
def blocked_urls(self, urls: Union[list, tuple, str, None]) -> None: ...
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
_owner: ChromiumTab = ...
|
||||
|
||||
def __init__(self, owner: Union[ChromiumTab, MixTab, MixPage, ChromiumPage]): ...
|
||||
|
||||
@property
|
||||
def window(self) -> WindowSetter: ...
|
||||
|
||||
def download_path(self, path: Union[str, Path, None]) -> None: ...
|
||||
|
||||
def download_file_name(self, name: str = None, suffix: str = None) -> None: ...
|
||||
|
||||
def when_download_file_exists(self, mode: FILE_EXISTS) -> None: ...
|
||||
|
||||
def activate(self) -> None: ...
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
_owner: ChromiumPage = ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True, all_tabs: bool = False) -> None: ...
|
||||
|
||||
|
||||
class MixPageSetter(ChromiumPageSetter):
|
||||
_owner: MixPage = ...
|
||||
_session_setter: SessionPageSetter = ...
|
||||
_chromium_setter: ChromiumPageSetter = ...
|
||||
|
||||
@ -148,11 +170,11 @@ class WebPageSetter(ChromiumPageSetter):
|
||||
def headers(self, headers: Union[str, dict]) -> None: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> WebPageCookiesSetter: ...
|
||||
def cookies(self) -> MixPageCookiesSetter: ...
|
||||
|
||||
|
||||
class WebPageTabSetter(TabSetter):
|
||||
_owner: WebPageTab = ...
|
||||
class MixTabSetter(TabSetter):
|
||||
_owner: MixTab = ...
|
||||
_session_setter: SessionPageSetter = ...
|
||||
_chromium_setter: ChromiumBaseSetter = ...
|
||||
|
||||
@ -161,14 +183,16 @@ class WebPageTabSetter(TabSetter):
|
||||
def headers(self, headers: Union[str, dict]) -> None: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> WebPageCookiesSetter: ...
|
||||
def cookies(self) -> MixPageCookiesSetter: ...
|
||||
|
||||
def timeouts(self, base: float = None, page_load: float = None, script: float = None) -> None: ...
|
||||
|
||||
|
||||
class ChromiumElementSetter(object):
|
||||
def __init__(self, ele: ChromiumElement):
|
||||
self._ele: ChromiumElement = ...
|
||||
|
||||
def attr(self, name: str, value: str) -> None: ...
|
||||
def attr(self, name: str, value: str = '') -> None: ...
|
||||
|
||||
def property(self, name: str, value: str) -> None: ...
|
||||
|
||||
@ -186,8 +210,9 @@ class ChromiumFrameSetter(ChromiumBaseSetter):
|
||||
|
||||
|
||||
class LoadMode(object):
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
_owner: Union[Chromium, ChromiumBase] = ...
|
||||
|
||||
def __init__(self, owner: Union[Chromium, ChromiumBase]): ...
|
||||
|
||||
def __call__(self, value: str) -> None: ...
|
||||
|
||||
|
@ -19,30 +19,30 @@ class ElementStates(object):
|
||||
@property
|
||||
def is_selected(self):
|
||||
"""返回列表元素是否被选择"""
|
||||
return self._ele.run_js('return this.selected;')
|
||||
return self._ele._run_js('return this.selected;')
|
||||
|
||||
@property
|
||||
def is_checked(self):
|
||||
"""返回元素是否被选择"""
|
||||
return self._ele.run_js('return this.checked;')
|
||||
return self._ele._run_js('return this.checked;')
|
||||
|
||||
@property
|
||||
def is_displayed(self):
|
||||
"""返回元素是否显示"""
|
||||
return not (self._ele.style('visibility') == 'hidden' or
|
||||
self._ele.run_js('return this.offsetParent === null;')
|
||||
self._ele._run_js('return this.offsetParent === null;')
|
||||
or self._ele.style('display') == 'none' or self._ele.property('hidden'))
|
||||
|
||||
@property
|
||||
def is_enabled(self):
|
||||
"""返回元素是否可用"""
|
||||
return not self._ele.run_js('return this.disabled;')
|
||||
return not self._ele._run_js('return this.disabled;')
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""返回元素是否仍在DOM中"""
|
||||
try:
|
||||
return self._ele.owner.run_cdp('DOM.describeNode',
|
||||
return self._ele.owner._run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
|
||||
except ElementLostError:
|
||||
return False
|
||||
@ -66,7 +66,7 @@ class ElementStates(object):
|
||||
"""返回元素是否被覆盖,与是否在视口中无关,如被覆盖返回覆盖元素的backend id,否则返回False"""
|
||||
lx, ly = self._ele.rect.click_point
|
||||
try:
|
||||
bid = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(lx), y=int(ly)).get('backendNodeId')
|
||||
bid = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(lx), y=int(ly)).get('backendNodeId')
|
||||
return bid if bid != self._ele._backend_id else False
|
||||
except CDPError:
|
||||
return False
|
||||
@ -95,13 +95,13 @@ class ShadowRootStates(object):
|
||||
@property
|
||||
def is_enabled(self):
|
||||
"""返回元素是否可用"""
|
||||
return not self._ele.run_js('return this.disabled;')
|
||||
return not self._ele._run_js('return this.disabled;')
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""返回元素是否仍在DOM中"""
|
||||
try:
|
||||
return self._ele.owner.run_cdp('DOM.describeNode',
|
||||
return self._ele.owner._run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
|
||||
except ElementLostError:
|
||||
return False
|
||||
@ -125,7 +125,7 @@ class PageStates(object):
|
||||
def is_alive(self):
|
||||
"""返回页面对象是否仍然可用"""
|
||||
try:
|
||||
self._owner.run_cdp('Page.getLayoutMetrics')
|
||||
self._owner._run_cdp('Page.getLayoutMetrics')
|
||||
return True
|
||||
except PageDisconnectedError:
|
||||
return False
|
||||
@ -157,7 +157,7 @@ class FrameStates(object):
|
||||
def is_alive(self):
|
||||
"""返回frame元素是否可用,且里面仍挂载有frame"""
|
||||
try:
|
||||
node = self._frame._target_page.run_cdp('DOM.describeNode',
|
||||
node = self._frame._target_page._run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._frame._frame_ele._backend_id)['node']
|
||||
except (ElementLostError, PageDisconnectedError):
|
||||
return False
|
||||
@ -172,7 +172,7 @@ class FrameStates(object):
|
||||
def is_displayed(self):
|
||||
"""返回iframe是否显示"""
|
||||
return not (self._frame.frame_ele.style('visibility') == 'hidden'
|
||||
or self._frame.frame_ele.run_js('return this.offsetParent === null;')
|
||||
or self._frame.frame_ele._run_js('return this.offsetParent === null;')
|
||||
or self._frame.frame_ele.style('display') == 'none')
|
||||
|
||||
@property
|
||||
|
@ -13,6 +13,9 @@ from ..errors import WaitTimeoutError, NoRectError
|
||||
|
||||
|
||||
class OriginWaiter(object):
|
||||
def __init__(self, owner):
|
||||
self._owner = owner
|
||||
|
||||
def __call__(self, second, scope=None):
|
||||
"""等待若干秒,如传入两个参数,等待时间为这两个数间的一个随机数
|
||||
:param second: 秒数
|
||||
@ -24,14 +27,86 @@ class OriginWaiter(object):
|
||||
else:
|
||||
from random import uniform
|
||||
sleep(uniform(second, scope))
|
||||
return self._owner
|
||||
|
||||
|
||||
class BrowserWaiter(OriginWaiter):
|
||||
|
||||
def new_tab(self, timeout=None, curr_tab=None, raise_err=None):
|
||||
"""等待新标签页出现
|
||||
:param timeout: 超时时间(秒),为None则使用页面对象timeout属性
|
||||
:param curr_tab: 指定当前最新的tab id,用于判断新tab出现,为None自动获取
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 等到新标签页返回其id,否则返回False
|
||||
"""
|
||||
curr_tid = curr_tab if curr_tab else self._owner.tab_ids[0]
|
||||
timeout = timeout if timeout is not None else self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
latest_tid = self._owner.tab_ids[0]
|
||||
if curr_tid != latest_tid:
|
||||
return latest_tid
|
||||
sleep(.01)
|
||||
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(f'等待新标签页失败(等待{timeout}秒)。')
|
||||
else:
|
||||
return False
|
||||
|
||||
def download_begin(self, timeout=None, cancel_it=False):
|
||||
"""等待浏览器下载开始,可将其拦截
|
||||
:param timeout: 超时时间(秒),None使用页面对象超时时间
|
||||
:param cancel_it: 是否取消该任务
|
||||
:return: 成功返回任务对象,失败返回False
|
||||
"""
|
||||
if not self._owner._dl_mgr._running:
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
self._owner._dl_mgr.set_flag('browser', False if cancel_it else True)
|
||||
if timeout is None:
|
||||
timeout = self._owner.timeout
|
||||
|
||||
r = False
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
v = self._owner._dl_mgr.get_flag('browser')
|
||||
if not isinstance(v, bool):
|
||||
r = v
|
||||
break
|
||||
sleep(.005)
|
||||
|
||||
self._owner._dl_mgr.set_flag('browser', None)
|
||||
return r
|
||||
|
||||
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
|
||||
"""等待所有浏览器下载任务结束
|
||||
:param timeout: 超时时间(秒),为None时无限等待
|
||||
:param cancel_if_timeout: 超时时是否取消剩余任务
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if not self._owner._dl_mgr._running:
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
if not timeout:
|
||||
while self._owner._dl_mgr._missions:
|
||||
sleep(.5)
|
||||
return True
|
||||
|
||||
else:
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._owner._dl_mgr._missions:
|
||||
return True
|
||||
sleep(.5)
|
||||
|
||||
if self._owner._dl_mgr._missions:
|
||||
if cancel_if_timeout:
|
||||
for m in list(self._owner._dl_mgr._missions.values()):
|
||||
m.cancel()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class BaseWaiter(OriginWaiter):
|
||||
def __init__(self, page_or_ele):
|
||||
"""
|
||||
:param page_or_ele: 页面对象或元素对象
|
||||
"""
|
||||
self._driver = page_or_ele
|
||||
|
||||
def ele_deleted(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
"""等待元素从DOM中删除
|
||||
@ -40,7 +115,7 @@ class BaseWaiter(OriginWaiter):
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
ele = self._driver._ele(loc_or_ele, raise_err=False, timeout=0)
|
||||
ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=0)
|
||||
return ele.wait.deleted(timeout, raise_err=raise_err) if ele else True
|
||||
|
||||
def ele_displayed(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
@ -51,9 +126,9 @@ class BaseWaiter(OriginWaiter):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self._driver.timeout
|
||||
timeout = self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
ele = self._driver._ele(loc_or_ele, raise_err=False, timeout=timeout)
|
||||
ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout)
|
||||
timeout = end_time - perf_counter()
|
||||
if timeout <= 0:
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
@ -70,9 +145,9 @@ class BaseWaiter(OriginWaiter):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self._driver.timeout
|
||||
timeout = self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
ele = self._driver._ele(loc_or_ele, raise_err=False, timeout=timeout)
|
||||
ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout)
|
||||
timeout = end_time - perf_counter()
|
||||
if timeout <= 0:
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
@ -121,10 +196,10 @@ class BaseWaiter(OriginWaiter):
|
||||
else [get_loc(l)[1] for l in locators])
|
||||
method = any if any_one else all
|
||||
|
||||
timeout = self._driver.timeout if timeout is None else timeout
|
||||
timeout = self._owner.timeout if timeout is None else timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if method([_find(l, self._driver.driver) for l in locators]):
|
||||
if method([_find(l, self._owner.driver) for l in locators]):
|
||||
return True
|
||||
sleep(.01)
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
@ -150,9 +225,9 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
def upload_paths_inputted(self):
|
||||
"""等待自动填写上传文件路径"""
|
||||
end_time = perf_counter() + self._driver.timeout
|
||||
end_time = perf_counter() + self._owner.timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._driver._upload_list:
|
||||
if not self._owner._upload_list:
|
||||
return True
|
||||
sleep(.01)
|
||||
return False
|
||||
@ -163,22 +238,22 @@ class BaseWaiter(OriginWaiter):
|
||||
:param cancel_it: 是否取消该任务
|
||||
:return: 成功返回任务对象,失败返回False
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
if not self._owner.browser._dl_mgr._running:
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
self._driver.browser._dl_mgr.set_flag(self._driver.tab_id, False if cancel_it else True)
|
||||
self._owner.browser._dl_mgr.set_flag(self._owner.tab_id, False if cancel_it else True)
|
||||
if timeout is None:
|
||||
timeout = self._driver.timeout
|
||||
timeout = self._owner.timeout
|
||||
|
||||
r = False
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
v = self._driver.browser._dl_mgr.get_flag(self._driver.tab_id)
|
||||
v = self._owner.browser._dl_mgr.get_flag(self._owner.tab_id)
|
||||
if not isinstance(v, bool):
|
||||
r = v
|
||||
break
|
||||
sleep(.005)
|
||||
|
||||
self._driver.browser._dl_mgr.set_flag(self._driver.tab_id, None)
|
||||
self._owner.browser._dl_mgr.set_flag(self._owner.tab_id, None)
|
||||
return r
|
||||
|
||||
def url_change(self, text, exclude=False, timeout=None, raise_err=None):
|
||||
@ -187,19 +262,19 @@ class BaseWaiter(OriginWaiter):
|
||||
:param exclude: 是否排除,为True时当url不包含text指定文本时返回True
|
||||
:param timeout: 超时时间(秒)
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 等待成功返回页面对象,否则返回False
|
||||
"""
|
||||
return self._change('url', text, exclude, timeout, raise_err)
|
||||
return self._owner if self._change('url', text, exclude, timeout, raise_err) else False
|
||||
|
||||
def title_change(self, text, exclude=False, timeout=None, raise_err=None):
|
||||
"""等待title变成包含或不包含指定文本
|
||||
:param text: 用于识别的文本
|
||||
:param exclude: 是否排除,为True时当title不包含text指定文本时返回True
|
||||
:param timeout: 超时时间(秒)
|
||||
:param timeout: 超时时间(秒),为None使用页面设置
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 等待成功返回页面对象,否则返回False
|
||||
"""
|
||||
return self._change('title', text, exclude, timeout, raise_err)
|
||||
return self._owner if self._change('title', text, exclude, timeout, raise_err) else False
|
||||
|
||||
def _change(self, arg, text, exclude=False, timeout=None, raise_err=None):
|
||||
"""等待指定属性变成包含或不包含指定文本
|
||||
@ -210,18 +285,26 @@ class BaseWaiter(OriginWaiter):
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
|
||||
def do():
|
||||
if arg == 'url':
|
||||
v = self._owner.url
|
||||
elif arg == 'title':
|
||||
v = self._owner.title
|
||||
else:
|
||||
raise ValueError
|
||||
if (not exclude and text in v) or (exclude and text not in v):
|
||||
return True
|
||||
|
||||
if do():
|
||||
return True
|
||||
|
||||
if timeout is None:
|
||||
timeout = self._driver.timeout
|
||||
timeout = self._owner.timeout
|
||||
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if arg == 'url':
|
||||
val = self._driver.url
|
||||
elif arg == 'title':
|
||||
val = self._driver.title
|
||||
else:
|
||||
raise ValueError
|
||||
if (not exclude and text in val) or (exclude and text not in val):
|
||||
if do():
|
||||
return True
|
||||
sleep(.05)
|
||||
|
||||
@ -238,12 +321,11 @@ class BaseWaiter(OriginWaiter):
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout != 0:
|
||||
if timeout is None or timeout is True:
|
||||
timeout = self._driver.timeout
|
||||
timeout = timeout if timeout is not None else self._owner.timeout
|
||||
timeout = .1 if timeout <= 0 else timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if self._driver._is_loading == start:
|
||||
if self._owner._is_loading == start:
|
||||
return True
|
||||
sleep(gap)
|
||||
|
||||
@ -254,6 +336,7 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
|
||||
class TabWaiter(BaseWaiter):
|
||||
"""标签页对象等待对象"""
|
||||
|
||||
def downloads_done(self, timeout=None, cancel_if_timeout=True):
|
||||
"""等待所有浏览器下载任务结束
|
||||
@ -261,23 +344,23 @@ class TabWaiter(BaseWaiter):
|
||||
:param cancel_if_timeout: 超时时是否取消剩余任务
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
if not self._owner.browser._dl_mgr._running:
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
if not timeout:
|
||||
while self._driver.browser._dl_mgr.get_tab_missions(self._driver.tab_id):
|
||||
while self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):
|
||||
sleep(.5)
|
||||
return True
|
||||
|
||||
else:
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._driver.browser._dl_mgr.get_tab_missions(self._driver.tab_id):
|
||||
if not self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):
|
||||
return True
|
||||
sleep(.5)
|
||||
|
||||
if self._driver.browser._dl_mgr.get_tab_missions(self._driver.tab_id):
|
||||
if self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):
|
||||
if cancel_if_timeout:
|
||||
for m in self._driver.browser._dl_mgr.get_tab_missions(self._driver.tab_id):
|
||||
for m in self._owner.browser._dl_mgr.get_tab_missions(self._owner.tab_id):
|
||||
m.cancel()
|
||||
return False
|
||||
else:
|
||||
@ -285,15 +368,14 @@ class TabWaiter(BaseWaiter):
|
||||
|
||||
def alert_closed(self):
|
||||
"""等待弹出框关闭"""
|
||||
while not self._driver.states.has_alert:
|
||||
while not self._owner.states.has_alert:
|
||||
sleep(.2)
|
||||
while self._driver.states.has_alert:
|
||||
while self._owner.states.has_alert:
|
||||
sleep(.2)
|
||||
|
||||
|
||||
class PageWaiter(TabWaiter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
"""ChromiumPage和MixPage的等待对象"""
|
||||
|
||||
def new_tab(self, timeout=None, raise_err=None):
|
||||
"""等待新标签页出现
|
||||
@ -301,18 +383,7 @@ class PageWaiter(TabWaiter):
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 等到新标签页返回其id,否则返回False
|
||||
"""
|
||||
timeout = timeout if timeout is not None else self._driver.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
latest_tid = self._driver.tab_ids[0]
|
||||
if self._driver.tab_id != latest_tid:
|
||||
return latest_tid
|
||||
sleep(.01)
|
||||
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(f'等待新标签页失败(等待{timeout}秒)。')
|
||||
else:
|
||||
return False
|
||||
return self._owner.browser.wait.new_tab(timeout=timeout, raise_err=raise_err)
|
||||
|
||||
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
|
||||
"""等待所有浏览器下载任务结束
|
||||
@ -320,45 +391,26 @@ class PageWaiter(TabWaiter):
|
||||
:param cancel_if_timeout: 超时时是否取消剩余任务
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
if not timeout:
|
||||
while self._driver.browser._dl_mgr._missions:
|
||||
sleep(.5)
|
||||
return True
|
||||
|
||||
else:
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._driver.browser._dl_mgr._missions:
|
||||
return True
|
||||
sleep(.5)
|
||||
|
||||
if self._driver.browser._dl_mgr._missions:
|
||||
if cancel_if_timeout:
|
||||
for m in list(self._driver.browser._dl_mgr._missions.values()):
|
||||
m.cancel()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return self._owner.browser.wait.all_downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout)
|
||||
|
||||
|
||||
class ElementWaiter(OriginWaiter):
|
||||
"""等待元素在dom中某种状态,如删除、显示、隐藏"""
|
||||
|
||||
def __init__(self, owner, ele):
|
||||
"""等待元素在dom中某种状态,如删除、显示、隐藏
|
||||
:param owner: 元素所在页面
|
||||
:param ele: 要等待的元素
|
||||
"""
|
||||
self._owner = owner
|
||||
self._ele = ele
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._ele = owner
|
||||
|
||||
@property
|
||||
def _timeout(self):
|
||||
"""返回超时设置"""
|
||||
return self._ele.owner.timeout
|
||||
|
||||
def deleted(self, timeout=None, raise_err=None):
|
||||
"""等待元素从dom删除
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_alive', False, timeout, raise_err, err_text='等待元素被删除失败。')
|
||||
|
||||
@ -366,7 +418,7 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待元素从dom显示
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_displayed', True, timeout, raise_err, err_text='等待元素显示失败。')
|
||||
|
||||
@ -374,7 +426,7 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待元素从dom隐藏
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_displayed', False, timeout, raise_err, err_text='等待元素隐藏失败。')
|
||||
|
||||
@ -390,7 +442,7 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待当前元素不被遮盖
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_covered', False, timeout, raise_err, err_text='等待元素不被覆盖失败。')
|
||||
|
||||
@ -398,7 +450,7 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待当前元素变成可用
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_enabled', True, timeout, raise_err, err_text='等待元素变成可用失败。')
|
||||
|
||||
@ -406,7 +458,7 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待当前元素变成不可用
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
return self._wait_state('is_enabled', False, timeout, raise_err, err_text='等待元素变成不可用失败。')
|
||||
|
||||
@ -414,14 +466,17 @@ class ElementWaiter(OriginWaiter):
|
||||
"""等待当前元素变成不可用或从DOM移除
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
if not self._ele.states.is_enabled or not self._ele.states.is_alive:
|
||||
return self._ele
|
||||
|
||||
if timeout is None:
|
||||
timeout = self._owner.timeout
|
||||
timeout = self._timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._ele.states.is_enabled or not self._ele.states.is_alive:
|
||||
return True
|
||||
return self._ele
|
||||
sleep(.05)
|
||||
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
@ -434,10 +489,13 @@ class ElementWaiter(OriginWaiter):
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param gap: 检测间隔时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self._owner.timeout
|
||||
timeout = self._timeout
|
||||
if timeout <= 0:
|
||||
timeout = .1
|
||||
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
try:
|
||||
@ -453,7 +511,7 @@ class ElementWaiter(OriginWaiter):
|
||||
while perf_counter() < end_time:
|
||||
sleep(gap)
|
||||
if self._ele.rect.size == size and self._ele.rect.location == location:
|
||||
return True
|
||||
return self._ele
|
||||
size = self._ele.rect.size
|
||||
location = self._ele.rect.location
|
||||
|
||||
@ -467,11 +525,12 @@ class ElementWaiter(OriginWaiter):
|
||||
:param wait_moved: 是否等待元素运动结束
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
timeout = timeout if timeout is not None else self._timeout
|
||||
t1 = perf_counter()
|
||||
r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。')
|
||||
r = self.stop_moving(timeout=perf_counter() - t1) if wait_moved and r else r
|
||||
r = self.stop_moving(timeout=timeout - perf_counter() + t1) if wait_moved and r else r
|
||||
if raise_err and not r:
|
||||
raise WaitTimeoutError(f'等待元素可点击失败(等{timeout}秒)。')
|
||||
return r
|
||||
@ -491,19 +550,19 @@ class ElementWaiter(OriginWaiter):
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:param err_text: 抛出错误时显示的信息
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
a = self._ele.states.__getattribute__(attr)
|
||||
if (a and mode) or (not a and not mode):
|
||||
return True if isinstance(a, bool) else a
|
||||
return self._ele if isinstance(a, bool) else a
|
||||
|
||||
if timeout is None:
|
||||
timeout = self._owner.timeout
|
||||
timeout = self._timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
a = self._ele.states.__getattribute__(attr)
|
||||
if (a and mode) or (not a and not mode):
|
||||
return True if isinstance(a, bool) else a
|
||||
return self._ele if isinstance(a, bool) else a
|
||||
sleep(.05)
|
||||
|
||||
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
|
||||
@ -514,9 +573,11 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
|
||||
class FrameWaiter(BaseWaiter, ElementWaiter):
|
||||
def __init__(self, frame):
|
||||
"""
|
||||
:param frame: ChromiumFrame对象
|
||||
"""
|
||||
super().__init__(frame)
|
||||
super(BaseWaiter, self).__init__(frame, frame.frame_ele)
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._ele = owner.frame_ele
|
||||
|
||||
@property
|
||||
def _timeout(self):
|
||||
"""返回超时设置"""
|
||||
return self._owner.timeout
|
||||
|
@ -5,24 +5,42 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from typing import Union, Tuple, Literal, List
|
||||
from typing import Union, Tuple, List
|
||||
|
||||
from .downloader import DownloadMission
|
||||
from .._base.browser import Chromium
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.mix_page import MixPage
|
||||
from .._pages.tabs import ChromiumTab, MixTab
|
||||
|
||||
|
||||
class OriginWaiter(object):
|
||||
def __call__(self, second: float, scope: float = None) -> None: ...
|
||||
_owner = ...
|
||||
|
||||
def __init__(self, owner): ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None): ...
|
||||
|
||||
|
||||
class BrowserWaiter(OriginWaiter):
|
||||
_owner: Chromium = ...
|
||||
|
||||
def __init__(self, owner: Chromium): ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> Chromium: ...
|
||||
|
||||
def download_begin(self, timeout: float = None, cancel_it: bool = False) -> DownloadMission: ...
|
||||
|
||||
def new_tab(self, timeout: float = None, curr_tab: str = None, raise_err: bool = None) -> Union[str, bool]: ...
|
||||
|
||||
def all_downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
|
||||
|
||||
|
||||
class BaseWaiter(OriginWaiter):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._driver: ChromiumBase = ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> None: ...
|
||||
_owner: ChromiumBase = ...
|
||||
|
||||
def ele_deleted(self,
|
||||
loc_or_ele: Union[str, tuple, ChromiumElement],
|
||||
@ -64,58 +82,94 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
|
||||
class TabWaiter(BaseWaiter):
|
||||
_owner: Union[ChromiumTab, MixTab] = ...
|
||||
|
||||
def __init__(self, owner: Union[ChromiumTab, MixTab]): ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> Union[ChromiumTab, MixTab]: ...
|
||||
|
||||
def downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
|
||||
|
||||
def alert_closed(self) -> None: ...
|
||||
|
||||
def url_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumTab, MixTab]: ...
|
||||
|
||||
def title_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumTab, MixTab]: ...
|
||||
|
||||
|
||||
class PageWaiter(TabWaiter):
|
||||
_driver: ChromiumPage = ...
|
||||
_owner: Union[ChromiumPage, MixPage] = ...
|
||||
|
||||
def __init__(self, owner: Union[ChromiumPage, MixPage]): ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> Union[ChromiumPage, MixPage]: ...
|
||||
|
||||
def new_tab(self, timeout: float = None, raise_err: bool = None) -> Union[str, bool]: ...
|
||||
|
||||
def all_downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
|
||||
|
||||
def url_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumPage, MixPage]: ...
|
||||
|
||||
def title_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumPage, MixPage]: ...
|
||||
|
||||
|
||||
class ElementWaiter(OriginWaiter):
|
||||
def __init__(self, owner: ChromiumBase, ele: ChromiumElement):
|
||||
self._ele: ChromiumElement = ...
|
||||
self._owner: ChromiumBase = ...
|
||||
_owner: ChromiumElement = ...
|
||||
_ele: ChromiumElement = ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> None: ...
|
||||
def __init__(self, owner: ChromiumElement): ...
|
||||
|
||||
def deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def __call__(self, second: float, scope: float = None) -> ChromiumElement: ...
|
||||
|
||||
def displayed(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
@property
|
||||
def _timeout(self) -> float: ...
|
||||
|
||||
def hidden(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def deleted(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def covered(self, timeout: float = None, raise_err: bool = None) -> Union[Literal[False], int]: ...
|
||||
def displayed(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def not_covered(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def hidden(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def enabled(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def covered(self, timeout: float = None, raise_err: bool = None) -> Union[False, int]: ...
|
||||
|
||||
def disabled(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def not_covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def clickable(self, wait_moved: bool = True, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def enabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def disabled(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def clickable(self, wait_moved: bool = True,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def has_rect(self,
|
||||
timeout: float = None,
|
||||
raise_err: bool = None) -> Union[Literal[False], List[Tuple[float, float]]]: ...
|
||||
raise_err: bool = None) -> Union[False, List[Tuple[float, float]]]: ...
|
||||
|
||||
def disabled_or_deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def stop_moving(self, timeout: float = None, gap: float = .1, raise_err: bool = None) -> bool: ...
|
||||
def stop_moving(self, timeout: float = None, gap: float = .1, raise_err: bool = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
def _wait_state(self,
|
||||
attr: str,
|
||||
mode: bool = False,
|
||||
timeout: float = None,
|
||||
raise_err: bool = None,
|
||||
err_text: str = None) -> bool: ...
|
||||
err_text: str = None) -> Union[ChromiumElement, False]: ...
|
||||
|
||||
|
||||
class FrameWaiter(BaseWaiter, ElementWaiter):
|
||||
def __init__(self, frame: ChromiumFrame): ...
|
||||
_owner: ChromiumFrame = ...
|
||||
|
||||
def __init__(self, owner: ChromiumFrame): ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> ChromiumFrame: ...
|
||||
|
||||
def url_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumFrame]: ...
|
||||
|
||||
def title_change(self, text: str, exclude: bool = False,
|
||||
timeout: float = None, raise_err: bool = None) -> Union[False, ChromiumFrame]: ...
|
||||
|
@ -5,6 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from ._base.browser import Chromium
|
||||
from ._elements.session_element import make_session_ele
|
||||
from ._functions.by import By
|
||||
from ._functions.elements import get_eles
|
||||
@ -12,7 +13,7 @@ from ._functions.keys import Keys
|
||||
from ._functions.settings import Settings
|
||||
from ._functions.tools import wait_until, configs_to_here
|
||||
from ._functions.web import get_blob, tree
|
||||
from ._pages.chromium_page import ChromiumPage
|
||||
|
||||
from ._units.actions import Actions
|
||||
|
||||
__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here', 'get_blob',
|
||||
@ -20,15 +21,15 @@ __all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until'
|
||||
|
||||
|
||||
def from_selenium(driver):
|
||||
"""从selenium的WebDriver对象生成ChromiumPage对象"""
|
||||
"""从selenium的WebDriver对象生成Chromium对象"""
|
||||
address, port = driver.caps.get('goog:chromeOptions', {}).get('debuggerAddress', ':').split(':')
|
||||
if not address:
|
||||
raise RuntimeError('获取失败。')
|
||||
return ChromiumPage(f'{address}:{port}')
|
||||
return Chromium(f'{address}:{port}')
|
||||
|
||||
|
||||
def from_playwright(page_or_browser):
|
||||
"""从playwright的Page或Browser对象生成ChromiumPage对象"""
|
||||
"""从playwright的Page或Browser对象生成Chromium对象"""
|
||||
if hasattr(page_or_browser, 'context'):
|
||||
page_or_browser = page_or_browser.context.browser
|
||||
try:
|
||||
@ -49,4 +50,4 @@ def from_playwright(page_or_browser):
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('获取失败。')
|
||||
return ChromiumPage(f'127.0.0.1:{port}')
|
||||
return Chromium(f'127.0.0.1:{port}')
|
||||
|
@ -9,7 +9,7 @@ from ._elements.chromium_element import ChromiumElement, ShadowRoot
|
||||
from ._elements.none_element import NoneElement
|
||||
from ._elements.session_element import SessionElement
|
||||
from ._pages.chromium_frame import ChromiumFrame
|
||||
from ._pages.chromium_tab import ChromiumTab, WebPageTab
|
||||
from ._pages.tabs import ChromiumTab, MixTab
|
||||
|
||||
__all__ = ['ChromiumElement', 'ShadowRoot', 'NoneElement', 'SessionElement', 'ChromiumFrame', 'ChromiumTab',
|
||||
'WebPageTab']
|
||||
'MixTab']
|
||||
|
Loading…
x
Reference in New Issue
Block a user