From 9f49f874cabe405f9c45889e313774b11fcd177d Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 28 Jun 2024 15:39:09 +0800 Subject: [PATCH 001/114] =?UTF-8?q?=E5=AF=B9Page=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=A7=A3=E8=80=A6=EF=BC=8C=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 14 +- DrissionPage/_base/base.py | 7 +- DrissionPage/_base/base.pyi | 2 +- DrissionPage/_base/browser.py | 403 ++++++++++++++++---- DrissionPage/_base/browser.pyi | 121 +++++- DrissionPage/_configs/chromium_options.py | 7 +- DrissionPage/_configs/session_options.py | 8 +- DrissionPage/_elements/chromium_element.pyi | 2 +- DrissionPage/_pages/chromium_base.py | 15 +- DrissionPage/_pages/chromium_base.pyi | 6 +- DrissionPage/_pages/chromium_frame.py | 27 +- DrissionPage/_pages/chromium_frame.pyi | 13 +- DrissionPage/_pages/chromium_page.py | 28 +- DrissionPage/_pages/chromium_page.pyi | 14 +- DrissionPage/_pages/chromium_tab.py | 57 ++- DrissionPage/_pages/chromium_tab.pyi | 30 +- DrissionPage/_pages/web_page.py | 18 +- DrissionPage/_pages/web_page.pyi | 12 +- DrissionPage/_units/clicker.py | 24 +- DrissionPage/_units/clicker.pyi | 6 +- DrissionPage/_units/cookies_setter.pyi | 4 +- DrissionPage/_units/downloader.py | 41 +- DrissionPage/_units/downloader.pyi | 12 +- DrissionPage/_units/rect.py | 2 +- DrissionPage/_units/rect.pyi | 4 +- DrissionPage/_units/screencast.py | 4 +- DrissionPage/_units/setter.py | 181 ++++++--- DrissionPage/_units/setter.pyi | 52 ++- DrissionPage/_units/waiter.py | 78 ++++ DrissionPage/_units/waiter.pyi | 12 + DrissionPage/items.py | 4 +- 31 files changed, 853 insertions(+), 355 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index e173e0d..66b3008 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -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 Browser 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.web_page import WebPage + +__all__ = ['Browser', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] +__version__ = '4.0.5.3' diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 1fcbd40..c30293e 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -54,7 +54,7 @@ class BaseElement(BaseParser): def __init__(self, owner=None): self.owner = owner - self.page = owner._page if owner else None + # self.page = owner._page if owner else None self._type = 'BaseElement' # ----------------以下属性或方法由后代实现---------------- @@ -366,11 +366,6 @@ class BasePage(BaseParser): """返回查找元素时等待的秒数""" return self._timeout - @timeout.setter - def timeout(self, second): - """设置查找元素时等待的秒数""" - self._timeout = second - @property def url_available(self): """返回当前访问的url有效性""" diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 8de4f35..764ccb3 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -59,7 +59,7 @@ class BaseElement(BaseParser): def __init__(self, owner: BasePage = None): self.owner: BasePage = ... - self.page: Union[ChromiumPage, SessionPage, WebPage] = ... + # self.page: Union[ChromiumPage, SessionPage, WebPage] = ... # ----------------以下属性或方法由后代实现---------------- @property diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 00649d5..1ae5c87 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -7,50 +7,80 @@ """ 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.settings import Settings +from .._functions.tools import PortFinder from .._functions.tools import raise_error +from .._pages.chromium_base import Timeout +from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager +from .._units.setter import BrowserSetter +from .._units.waiter import BrowserWaiter +from ..errors import BrowserConnectError from ..errors import PageDisconnectedError __ERROR__ = 'error' class Browser(object): - BROWSERS = {} + _BROWSERS = {} - 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_exist, browser_id = run_browser(opt) + if browser_id in cls._BROWSERS: + r = cls._BROWSERS[browser_id] + return r + r = object.__new__(cls) + r._chromium_options = opt + r._is_exist = is_exist + r.id = browser_id + r.address = opt.address + 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 = 'Browser' + self._driver = BrowserDriver(self.id, 'browser', self.address, self) + self.version = self.run_cdp('Browser.getVersion')['product'] + self._frames = {} self._drivers = {} self._all_drivers = {} - self._connected = False + self._lock = Lock() + + self._set = None + self._wait = None + self._timeouts = Timeout(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 = 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._process_id = None try: @@ -65,6 +95,9 @@ class Browser(object): 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 def _get_driver(self, tab_id, owner=None): """新建并返回指定tab id的Driver @@ -104,12 +137,6 @@ 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): """执行Chrome DevTools Protocol语句 :param cmd: 协议项目 @@ -121,8 +148,37 @@ class Browser(object): return r if __ERROR__ not in r else raise_error(r, ignore) @property - def driver(self): - return self._driver + 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 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): @@ -138,16 +194,154 @@ class Browser(object): and not i['url'].startswith('devtools://')] @property - def process_id(self): - """返回浏览器进程id""" - return self._process_id + 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 find_tabs(self, title=None, url=None, tab_type=None): + 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 + + tab = self.run_cdp('Target.createTarget', **kwargs)['targetId'] + 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而不是标签页对象,dual_mode=False时无效 + :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类型,可用列表输入多个 - :return: dict格式的tab信息列表列表 + :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :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而不是标签页对象,dual_mode=False时无效 + :return: Tab对象 + """ + return self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, dual_mode=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而不是标签页对象,dual_mode=False时无效 + :return: Tab对象列表 + """ + return self._get_tabs(title=title, url=url, tab_type=tab_type, dual_mode=True, as_id=as_id) + + def _get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', + dual_mode=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 dual_mode: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=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): + 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) # todo: 循环调用 + + elif title == url == tab_type is None: + 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 dual_mode else ChromiumTab(self, id_or_num) + + def _get_tabs(self, title=None, url=None, tab_type='page', dual_mode=False, as_id=False): + """查找符合条件的tab,返回它们组成的列表,title和url是与关系 + :param title: 要匹配title的文本 + :param url: 要匹配url的文本 + :param tab_type: tab类型,可用列表输入多个 + :param dual_mode: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :return: Tab对象列表 """ tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp @@ -158,24 +352,49 @@ class Browser(object): 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']) + 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 dual_mode: + return [MixTab(self, tab['id']) for tab in tabs] + else: + return [ChromiumTab(self, tab['id']) for tab in tabs] - def close_tab(self, tab_id): - """关闭标签页 - :param tab_id: 标签页id + def close_tabs(self, tabs_or_ids=None, others=False): + """关闭传入的标签页,默认关闭当前页。可传入多个 + :param tabs_or_ids: 要关闭的标签页对象或id,可传入列表或元组,为None时关闭最后操作的 + :param others: 是否关闭指定标签页之外的 :return: None """ - self._onTargetDestroyed(targetId=tab_id) - self.driver.run('Target.closeTarget', targetId=tab_id) + 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。') - def stop_driver(self, driver): - """停止一个Driver - :param driver: Driver对象 - :return: None - """ - driver.stop() - self._all_drivers.get(driver.id, set()).discard(driver) + 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, tab_id): """使标签页变为活动状态 @@ -184,37 +403,6 @@ class Browser(object): """ 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() @@ -234,7 +422,7 @@ class Browser(object): self.run_cdp('Browser.close') except PageDisconnectedError: pass - self.driver.stop() + self._driver.stop() drivers = list(self._all_drivers.values()) for tab in drivers: @@ -276,10 +464,9 @@ class Browser(object): 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) + Browser._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 +477,57 @@ 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_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 diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 170f88b..9973f82 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -5,36 +5,71 @@ @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 .._pages.chromium_base import Timeout, ChromiumBase +from .._pages.chromium_tab 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 = ... id: str = ... address: str = ... + version: str = ... + retry_times: int = ... + retry_interval: float = ... + + _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 = ... - 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: ... @property - def driver(self) -> BrowserDriver: ... + def process_id(self) -> Optional[int]: ... + + @property + def timeout(self) -> float: ... + + @property + def timeouts(self) -> Timeout: ... + + @property + def download_path(self) -> str: ... + + @property + def set(self) -> BrowserSetter: ... + + @property + def wait(self) -> BrowserWaiter: ... @property def tabs_count(self) -> int: ... @@ -43,25 +78,75 @@ 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 close_tabs(self, + tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], + Tuple[Union[str, ChromiumTab]]] = None, + others: bool = False) -> None: ... - def close_tab(self, tab_id: str) -> 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 stop_driver(self, driver: Driver) -> None: ... + def get_tabs(self, + title: str = None, + url: str = None, + tab_type: str = 'page', + as_id: bool = False) -> List[ChromiumTab, str]: ... + + 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 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', + dual_mode: bool = False, + as_id: bool = False) -> Union[ChromiumTab, str]: ... + + def _get_tabs(self, + title: str = None, + url: str = None, + tab_type: str = 'page', + dual_mode: bool = False, + as_id: bool = False) -> List[ChromiumTab, str]: ... def activate_tab(self, tab_id: str) -> None: ... - def get_window_bounds(self, tab_id: str = None) -> dict: ... + 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, new_window: bool = False, background: bool = False, new_context: bool = False) -> str: ... + 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: ... diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 86e64d2..acb7295 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -36,7 +36,7 @@ class ChromiumOptions(object): 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', '') @@ -282,14 +282,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: @@ -450,7 +449,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): diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index a533e4d..25ea8cd 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -24,7 +24,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 +83,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 +100,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 +419,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() diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index 1be5553..a629c85 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -183,7 +183,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: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 96a1173..08ec207 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -38,11 +38,10 @@ __ERROR__ = 'error' class ChromiumBase(BasePage): """标签页、frame、页面基类""" - def __init__(self, address, tab_id=None, timeout=None): + def __init__(self, address, tab_id=None): """ :param address: 浏览器 ip:port :param tab_id: 要控制的标签页id,不指定默认为激活的 - :param timeout: 超时时间(秒) """ super().__init__() self._is_loading = None @@ -71,8 +70,6 @@ class ChromiumBase(BasePage): 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): """设置浏览器启动属性 @@ -93,7 +90,7 @@ class ChromiumBase(BasePage): self._is_reading = False if not tab_id: - tabs = self.browser.driver.get(f'http://{self.address}/json').json() + tabs = self.browser._driver.get(f'http://{self.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 @@ -861,7 +858,8 @@ class ChromiumBase(BasePage): 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): """断开与页面原来的页面,重新建立连接 @@ -1119,15 +1117,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 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 17b14bb..beaadad 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -32,8 +32,7 @@ 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): + tab_id: str = None): self._browser: Browser = ... self._page: ChromiumPage = ... self.tab: Union[ChromiumPage, ChromiumTab] = ... @@ -267,8 +266,7 @@ 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 = ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index f3e1ab5..aa537c8 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -27,14 +27,17 @@ 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._target_page = owner - self.tab = owner.tab if owner._type == 'ChromiumFrame' else owner + self.tab = owner.tab + self._browser = owner.browser + self._target_page = owner + # if owner._type in ('ChromiumPage', 'WebPage'): + # self._target_page = self.tab = owner + # self._browser = owner.browser + # else: # ChromiumTab、Frame + # # self._page = owner.page + # self._browser = self._page.browser + # self._target_page = owner + # self.tab = owner.tab if owner._type == 'ChromiumFrame' else owner self.address = owner.address self._tab_id = owner.tab_id @@ -250,10 +253,10 @@ class ChromiumFrame(ChromiumBase): """返回cdp中的node id""" return self.frame_ele._node_id - @property - def page(self): - """返回所属Page对象""" - return self._page + # @property + # def page(self): + # """返回所属Page对象""" + # return self._page @property def owner(self): diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index b0c1a83..9e55d22 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -9,7 +9,7 @@ from pathlib import Path from typing import Union, Tuple, List, Any, Optional from .chromium_base import ChromiumBase -from .chromium_page import ChromiumPage +# from .chromium_page import ChromiumPage from .chromium_tab import ChromiumTab from .web_page import WebPage from .._elements.chromium_element import ChromiumElement @@ -25,13 +25,12 @@ 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.tab: ChromiumTab = ... + # self._tab_id: str = ... self._set: ChromiumFrameSetter = ... self._frame_ele: ChromiumElement = ... self._backend_id: int = ... @@ -66,8 +65,8 @@ class ChromiumFrame(ChromiumBase): def _onInspectorDetached(self, **kwargs): ... - @property - def page(self) -> Union[ChromiumPage, WebPage]: ... + # @property + # def page(self) -> Union[ChromiumPage, WebPage]: ... @property def owner(self) -> ChromiumBase: ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 7bd7277..b4fea5d 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -34,19 +34,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 = Browser(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,12 +56,9 @@ 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.address, tab_id) self._type = 'ChromiumPage' - self._lock = Lock() self.set.timeouts(base=timeout) self._page_init() @@ -86,7 +80,7 @@ class ChromiumPage(ChromiumBase): def _d_set_runtime_settings(self): """设置运行时用到的属性""" - self._timeouts = Timeout(self, page_load=self._chromium_options.timeouts['page_load'], + self._timeouts = Timeout(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: @@ -196,7 +190,7 @@ class ChromiumPage(ChromiumBase): return id_or_num with self._lock: - return ChromiumTab(self, id_or_num) + return ChromiumTab(self.browser, id_or_num) def get_tabs(self, title=None, url=None, tab_type='page', as_id=False): """查找符合条件的tab,返回它们组成的列表 @@ -209,7 +203,7 @@ class ChromiumPage(ChromiumBase): 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 [ChromiumTab(self.browser, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)] def new_tab(self, url=None, new_window=False, background=False, new_context=False): """新建一个标签页 @@ -219,7 +213,7 @@ class ChromiumPage(ChromiumBase): :param new_context: 是否创建新的上下文 :return: 新标签页对象 """ - tab = ChromiumTab(self, tab_id=self.browser.new_tab(new_window, background, new_context)) + tab = ChromiumTab(self.browser, tab_id=self.browser.new_tab(new_window, background, new_context)) if url: tab.get(url) return tab diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 7117b9e..2aab4c1 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -20,6 +20,10 @@ from .._units.waiter import PageWaiter class ChromiumPage(ChromiumBase): _PAGES: dict = ... + tab: ChromiumPage = ... + _browser: Browser = ... + _rect: Optional[TabRect] = ... + _is_exist: bool = ... def __new__(cls, addr_or_opts: Union[str, int, ChromiumOptions] = None, @@ -29,15 +33,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: ... diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index d965a5d..d4084bb 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -22,10 +22,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 +36,32 @@ 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._browser = browser + super().__init__(browser.address, tab_id, browser.timeout) self._rect = None 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 +94,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: Browser对象 + :param tab_id: 标签页id """ if Settings.singleton_tab_obj and hasattr(self, '_created'): return @@ -112,10 +106,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): """在内部查找元素 @@ -318,7 +311,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 @@ -378,7 +373,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage): 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() @@ -398,4 +393,4 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage): return super(SessionPage, self)._find_elements(locator, timeout=timeout, index=index, relative=relative) def __repr__(self): - return f'' + return f'' diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index 2161070..738b712 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -12,9 +12,7 @@ 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 .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement @@ -27,10 +25,9 @@ from .._units.waiter import TabWaiter class ChromiumTab(ChromiumBase): _TABS: dict = ... - def __new__(cls, page: ChromiumPage, tab_id: str): ... + def __new__(cls, browser: Browser, tab_id: str): ... - def __init__(self, page: ChromiumPage, tab_id: str): - self._page: ChromiumPage = ... + def __init__(self, browser: Browser, tab_id: str): self._browser: Browser = ... self._rect: Optional[TabRect] = ... @@ -38,9 +35,6 @@ class ChromiumTab(ChromiumBase): def close(self) -> None: ... - @property - def page(self) -> ChromiumPage: ... - @property def set(self) -> TabSetter: ... @@ -69,22 +63,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): + _browser: Browser = ... + _mode: str = ... + _has_driver: bool = ... + _has_session: bool = ... + + def __init__(self, browser: Browser, 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 +112,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, diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index ea9e83d..fbcd2fb 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -6,7 +6,7 @@ @License : BSD 3-Clause. """ from .chromium_page import ChromiumPage -from .chromium_tab import WebPageTab +from .chromium_tab import MixTab from .session_page import SessionPage from .._base.base import BasePage from .._configs.chromium_options import ChromiumOptions @@ -150,14 +150,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage): """返回通用timeout设置""" return self.timeouts.base - @timeout.setter - def timeout(self, second): - """设置通用超时时间 - :param second: 秒数 - :return: None - """ - self.set.timeouts(base=second) - def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs): """跳转到一个url :param url: 目标url @@ -321,7 +313,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): 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): + elif isinstance(id_or_num, MixTab): return id_or_num.tab_id if as_id else id_or_num elif title == url == tab_type is None: @@ -338,7 +330,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): return id_or_num with self._lock: - return WebPageTab(self, id_or_num) + return MixTab(self, id_or_num) def get_tabs(self, title=None, url=None, tab_type='page', as_id=False): """查找符合条件的tab,返回它们组成的列表 @@ -351,7 +343,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): 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 [MixTab(self, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)] def new_tab(self, url=None, new_window=False, background=False, new_context=False): """新建一个标签页 @@ -361,7 +353,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): :param new_context: 是否创建新的上下文 :return: 新标签页对象 """ - tab = WebPageTab(self, tab_id=self.browser.new_tab(new_window, background, new_context)) + tab = MixTab(self, tab_id=self.browser.new_tab(new_window, background, new_context)) if url: tab.get(url) return tab diff --git a/DrissionPage/_pages/web_page.pyi b/DrissionPage/_pages/web_page.pyi index e1046d8..658c976 100644 --- a/DrissionPage/_pages/web_page.pyi +++ b/DrissionPage/_pages/web_page.pyi @@ -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 .chromium_tab import MixTab from .session_page import SessionPage from .._base.base import BasePage from .._base.driver import Driver @@ -129,23 +129,23 @@ class WebPage(SessionPage, ChromiumPage, BasePage): 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,7 +175,7 @@ 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, WebPage]: ... @property def set(self) -> WebPageSetter: ... diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 6006b4c..eb97ba9 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -120,12 +120,13 @@ class Clicker(object): """ self._ele.owner.scroll.to_see(self._ele) x, y = self._ele.rect.viewport_click_point + curr_tid = self._ele.tab.browser.tab_ids[0] self._click(x, y, '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_tab(tid) def at(self, offset_x=None, offset_y=None, button='left', count=1): """带偏移量点击本元素,相对于左上角坐标。不传入x或y值时点击元素中间点 @@ -162,16 +163,15 @@ class Clicker(object): """ 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('.') + elif not self._ele.tab._browser._dl_mgr._running: + self._ele.owner.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,16 +183,18 @@ 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_tab(tid) def _click(self, client_x, client_y, button='left', count=1): """实施点击 diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index 15fc212..5031e27 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -10,7 +10,7 @@ from typing import Union from .downloader import DownloadMission from .._elements.chromium_element import ChromiumElement -from .._pages.chromium_tab import WebPageTab, ChromiumTab +from .._pages.chromium_tab import MixTab, ChromiumTab class Clicker(object): @@ -23,7 +23,7 @@ class Clicker(object): def right(self) -> None: ... - 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, @@ -43,6 +43,6 @@ 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: ... diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index cbe37de..c929448 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -9,7 +9,7 @@ from http.cookiejar import Cookie, CookieJar from typing import Union from .._pages.chromium_base import ChromiumBase -from .._pages.chromium_tab import WebPageTab +from .._pages.chromium_tab import MixTab from .._pages.session_page import SessionPage from .._pages.web_page import WebPage @@ -39,7 +39,7 @@ class SessionCookiesSetter(object): class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): - _owner: Union[WebPage, WebPageTab] + _owner: Union[WebPage, MixTab] def __init__(self, page: SessionPage): ... diff --git a/DrissionPage/_units/downloader.py b/DrissionPage/_units/downloader.py index ec41cc4..0318266 100644 --- a/DrissionPage/_units/downloader.py +++ b/DrissionPage/_units/downloader.py @@ -20,21 +20,24 @@ 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 + self._running = False @property def missions(self): @@ -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 @@ -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: # 取消该任务 @@ -199,6 +203,7 @@ class DownloadManager(object): def _onDownloadProgress(self, **kwargs): """下载状态变化时执行""" + # print(kwargs) if kwargs['guid'] in self._missions: mission = self._missions[kwargs['guid']] if kwargs['state'] == 'inProgress': diff --git a/DrissionPage/_units/downloader.pyi b/DrissionPage/_units/downloader.pyi index eadcc44..10fd5e3 100644 --- a/DrissionPage/_units/downloader.pyi +++ b/DrissionPage/_units/downloader.pyi @@ -9,28 +9,28 @@ from typing import Dict, Optional, Union, Literal from .._base.browser import Browser 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 = ... + # _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): ... @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: ... diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index b751d4b..b8bbbb8 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -184,7 +184,7 @@ class TabRect(object): 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): diff --git a/DrissionPage/_units/rect.pyi b/DrissionPage/_units/rect.pyi index 4fa4e73..c1df2d5 100644 --- a/DrissionPage/_units/rect.pyi +++ b/DrissionPage/_units/rect.pyi @@ -12,7 +12,7 @@ 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.chromium_tab import ChromiumTab, MixTab from .._pages.web_page import WebPage @@ -63,7 +63,7 @@ class ElementRect(object): class TabRect(object): def __init__(self, owner: ChromiumBase): - self._owner: Union[ChromiumPage, ChromiumTab, WebPage, WebPageTab] = ... + self._owner: Union[ChromiumPage, ChromiumTab, WebPage, MixTab] = ... @property def window_state(self) -> str: ... diff --git a/DrissionPage/_units/screencast.py b/DrissionPage/_units/screencast.py index fca2b68..bd12711 100644 --- a/DrissionPage/_units/screencast.py +++ b/DrissionPage/_units/screencast.py @@ -39,9 +39,9 @@ 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) diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 9f0e443..e6e6695 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -33,8 +33,101 @@ class BasePageSetter(object): self._owner._none_ele_return_value = on_off self._owner._none_ele_value = value + def retry_times(self, times): + """设置连接失败重连次数""" + self._owner.retry_times = times -class ChromiumBaseSetter(BasePageSetter): + def retry_interval(self, interval): + """设置连接失败重连间隔""" + self._owner.retry_interval = interval + + def download_path(self, path): + """设置下载路径 + :param path: 下载路径 + :return: None + """ + if path is None: + path = '.' + self._owner._download_path = str(Path(path).absolute()) + + +class BrowserBaseSetter(BasePageSetter): + + @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 + 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 + + +class BrowserSetter(BrowserBaseSetter): + + def cookies(self, cookies): + pass # todo: 研究Storage.setCookies和Network.setCookies差别 + + 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) + + 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) + + +class ChromiumBaseSetter(BrowserBaseSetter): def __init__(self, owner): """ :param owner: ChromiumBase对象 @@ -42,10 +135,10 @@ class ChromiumBaseSetter(BasePageSetter): super().__init__(owner) self._cookies_setter = None - @property - def load_mode(self): - """返回用于设置页面加载策略的对象""" - return LoadMode(self._owner) + # @property + # def load_mode(self): + # """返回用于设置页面加载策略的对象""" + # return LoadMode(self._owner) @property def scroll(self): @@ -59,31 +152,30 @@ class ChromiumBaseSetter(BasePageSetter): self._cookies_setter = CookiesSetter(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 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 retry_times(self, times): + # """设置连接失败重连次数""" + # self._owner.retry_times = times + # + # def retry_interval(self, interval): + # """设置连接失败重连间隔""" + # self._owner.retry_interval = interval + # + # 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 + # 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有效 @@ -191,11 +283,10 @@ class TabSetter(ChromiumBaseSetter): :param path: 下载路径 :return: None """ - path = str(Path(path).absolute()) - self._owner._download_path = path - self._owner.browser._dl_mgr.set_path(self._owner, path) + 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(path) + self._owner._DownloadKit.set.goal_path(self._owner._download_path) def download_file_name(self, name=None, suffix=None): """设置下一个被下载文件的名称 @@ -215,7 +306,6 @@ class TabSetter(ChromiumBaseSetter): 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): @@ -264,23 +354,22 @@ 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 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): """设置连接超时时间 @@ -464,7 +553,7 @@ class ChromiumElementSetter(object): """ self._ele = ele - def attr(self, name, value): + def attr(self, name, value=''): """设置元素attribute属性 :param name: 属性名 :param value: 属性值 diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index f371bf6..f01dbaf 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -14,11 +14,12 @@ from requests.auth import HTTPBasicAuth from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter from .scroller import PageScroller from .._base.base import BasePage +from .._base.browser import Browser 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.chromium_tab import ChromiumTab, MixTab from .._pages.session_page import SessionPage from .._pages.web_page import WebPage @@ -26,11 +27,41 @@ FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o'] class BasePageSetter(object): - def __init__(self, owner: BasePage): - self._owner: BasePage = ... + def __init__(self, owner: Union[Browser, BasePage]): + self._owner: Union[Browser, BasePage] = ... def NoneElement_value(self, value: Any = None, on_off: bool = True) -> None: ... + def retry_times(self, times: int) -> None: ... + + def retry_interval(self, interval: float) -> None: ... + + def download_path(self, path: Union[str, Path, None]) -> None: ... + + +class BrowserBaseSetter(BasePageSetter): + + @property + def load_mode(self) -> LoadMode: ... + + def timeouts(self, base=None, page_load=None, script=None) -> None: ... + + +class BrowserSetter(BasePageSetter): + _owner: Browser = ... + + def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... + + def cookies(self, cookies): ... + + 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(BasePageSetter): def __init__(self, owner): @@ -70,12 +101,12 @@ class ChromiumBaseSetter(BasePageSetter): class TabSetter(ChromiumBaseSetter): _owner: ChromiumTab = ... - def __init__(self, owner: Union[ChromiumTab, WebPageTab, WebPage, ChromiumPage]): ... + def __init__(self, owner: Union[ChromiumTab, MixTab, WebPage, ChromiumPage]): ... @property def window(self) -> WindowSetter: ... - def download_path(self, path: Union[str, Path]) -> None: ... + def download_path(self, path: Union[str, Path, None]) -> None: ... def download_file_name(self, name: str = None, suffix: str = None) -> None: ... @@ -105,7 +136,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: ... @@ -152,7 +183,7 @@ class WebPageSetter(ChromiumPageSetter): class WebPageTabSetter(TabSetter): - _owner: WebPageTab = ... + _owner: MixTab = ... _session_setter: SessionPageSetter = ... _chromium_setter: ChromiumBaseSetter = ... @@ -168,7 +199,7 @@ 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 +217,9 @@ class ChromiumFrameSetter(ChromiumBaseSetter): class LoadMode(object): - def __init__(self, owner: ChromiumBase): - self._owner: ChromiumBase = ... + _owner: Union[Browser, ChromiumBase] = ... + + def __init__(self, owner: Union[Browser, ChromiumBase]): ... def __call__(self, value: str) -> None: ... diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 35bf3b4..9a26ecc 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -26,6 +26,84 @@ class OriginWaiter(object): sleep(uniform(second, scope)) +class BrowserWaiter(OriginWaiter): + def __init__(self, owner): + self._owner = owner + + 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 new_tab(self, timeout=None, curr_tab=None, raise_err=None): + """等待新标签页出现 + :param timeout: 超时时间(秒),为None则使用页面对象timeout属性 + :param curr_tab: 指定当前最新的tab id,为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 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): """ diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index f05ca94..f4c3966 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -8,6 +8,7 @@ from typing import Union, Tuple, Literal, List from .downloader import DownloadMission +from .._base.browser import Browser from .._elements.chromium_element import ChromiumElement from .._pages.chromium_base import ChromiumBase from .._pages.chromium_frame import ChromiumFrame @@ -18,6 +19,17 @@ class OriginWaiter(object): def __call__(self, second: float, scope: float = None) -> None: ... +class BrowserWaiter(OriginWaiter): + def __init__(self, owner: Browser): + self._owner = owner + + 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): ... + + class BaseWaiter(OriginWaiter): def __init__(self, page: ChromiumBase): self._driver: ChromiumBase = ... diff --git a/DrissionPage/items.py b/DrissionPage/items.py index 0715e91..5b680e8 100644 --- a/DrissionPage/items.py +++ b/DrissionPage/items.py @@ -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.chromium_tab import ChromiumTab, MixTab __all__ = ['ChromiumElement', 'ShadowRoot', 'NoneElement', 'SessionElement', 'ChromiumFrame', 'ChromiumTab', - 'WebPageTab'] + 'MixTab'] From ac3a8ec27c5641ae40eac738eb06b265b0b56493 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 28 Jun 2024 17:41:16 +0800 Subject: [PATCH 002/114] =?UTF-8?q?SessionPage=E5=88=A0=E9=99=A4timeout?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 4 ++-- DrissionPage/_pages/chromium_base.py | 25 +++++++++++++------------ DrissionPage/_pages/chromium_base.pyi | 6 ++---- DrissionPage/_pages/chromium_frame.py | 21 ++++++++++----------- DrissionPage/_pages/chromium_frame.pyi | 3 +-- DrissionPage/_pages/chromium_tab.py | 2 +- DrissionPage/_pages/session_page.py | 5 +---- DrissionPage/_pages/session_page.pyi | 3 +-- 8 files changed, 31 insertions(+), 38 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 1ae5c87..efd965d 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -317,9 +317,9 @@ class Browser(object): elif Settings.singleton_tab_obj: return id_or_num else: - return self._get_tab(id_or_num.tab_id) # todo: 循环调用 + return self._get_tab(id_or_num.tab_id) # fixme: 这里会出现循环调用 - elif title == url == tab_type is None: + elif title == url is None and tab_type == 'page': id_or_num = self.tab_ids[0] else: diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 08ec207..a775124 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -38,12 +38,13 @@ __ERROR__ = 'error' class ChromiumBase(BasePage): """标签页、frame、页面基类""" - def __init__(self, address, tab_id=None): + def __init__(self, browser, tab_id=None): """ - :param address: 浏览器 ip:port + :param browser: Browser :param tab_id: 要控制的标签页id,不指定默认为激活的 """ super().__init__() + self._browser = browser self._is_loading = None self._root_id = None # object id self._set = None @@ -64,19 +65,19 @@ 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}' + # 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_start_options(address) self._d_set_runtime_settings() self._connect_browser(tab_id) - 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://') + # 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://') def _d_set_runtime_settings(self): self._timeouts = Timeout(self) @@ -90,7 +91,7 @@ class ChromiumBase(BasePage): self._is_reading = False if not tab_id: - tabs = self.browser._driver.get(f'http://{self.address}/json').json() + 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 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index beaadad..8b9039b 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -31,12 +31,10 @@ PIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True] class ChromiumBase(BasePage): def __init__(self, - address: Union[str, int], + browser: Browser, tab_id: str = None): self._browser: Browser = ... - self._page: ChromiumPage = ... self.tab: Union[ChromiumPage, ChromiumTab] = ... - self.address: str = ... self._driver: Driver = ... self._frame_id: str = ... self._is_reading: bool = ... @@ -90,7 +88,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: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index aa537c8..4bc5eb5 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -28,7 +28,6 @@ class ChromiumFrame(ChromiumBase): :param info: frame所在元素信息 """ self.tab = owner.tab - self._browser = owner.browser self._target_page = owner # if owner._type in ('ChromiumPage', 'WebPage'): # self._target_page = self.tab = owner @@ -39,8 +38,8 @@ class ChromiumFrame(ChromiumBase): # 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.address = owner.address + # self._tab_id = owner.tab_id self._backend_id = ele._backend_id self._frame_ele = ele self._states = None @@ -51,11 +50,11 @@ class ChromiumFrame(ChromiumBase): 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.tab_id) else: self._is_diff_domain = True delattr(self, '_frame_id') - super().__init__(owner.address, node['frameId'], owner.timeout) + 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) @@ -101,7 +100,7 @@ class ChromiumFrame(ChromiumBase): try: super()._driver_init(tab_id) except: - self.browser.driver.get(f'http://{self.address}/json') + self.browser._driver.get(f'http://{self._browser.address}/json') super()._driver_init(tab_id) self._driver.set_callback('Inspector.detached', self._onInspectorDetached, immediate=True) self._driver.set_callback('Page.frameDetached', None) @@ -135,16 +134,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 @@ -323,7 +322,7 @@ class ChromiumFrame(ChromiumBase): @property def tab_id(self): """返回frame所在tab的id""" - return self._tab_id + return self.tab.tab_id @property def download_path(self): diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index 9e55d22..3e44b41 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -28,9 +28,8 @@ class ChromiumFrame(ChromiumBase): owner: Union[ChromiumTab, ChromiumFrame], ele: ChromiumElement, info: dict = None): - self._target_page: ChromiumBase = ... + self._target_page: Union[ChromiumTab, ChromiumFrame] = ... self.tab: ChromiumTab = ... - # self._tab_id: str = ... self._set: ChromiumFrameSetter = ... self._frame_ele: ChromiumElement = ... self._backend_id: int = ... diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index d4084bb..f99fe21 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -47,7 +47,7 @@ class ChromiumTab(ChromiumBase): self.tab = self self._browser = browser - super().__init__(browser.address, tab_id, browser.timeout) + super().__init__(browser, tab_id) self._rect = None self._type = 'ChromiumTab' diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index 94a7d07..c2bb376 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -25,10 +25,9 @@ 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 @@ -41,8 +40,6 @@ class SessionPage(BasePage): 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): """启动配置 diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 16f95be..4527827 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -20,8 +20,7 @@ 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 = ... From 61dce186c615b1966ef60d4680bec85669ea4018 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 28 Jun 2024 23:34:30 +0800 Subject: [PATCH 003/114] =?UTF-8?q?ChromiumOptions=E5=92=8CBrowser?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0is=5Fheadless=E5=B1=9E=E6=80=A7=EF=BC=9B?= =?UTF-8?q?=E6=8E=A5=E7=AE=A1=E6=B5=8F=E8=A7=88=E5=99=A8=E6=97=B6=E5=A6=82?= =?UTF-8?q?=E6=97=A0=E5=A4=B4=E7=8A=B6=E6=80=81=E5=92=8C=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4=EF=BC=8C=E4=BC=9A=E6=8C=89=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=87=8D=E5=90=AF=E6=B5=8F=E8=A7=88=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 56 ++++++++++++++-------- DrissionPage/_base/browser.pyi | 3 +- DrissionPage/_configs/chromium_options.py | 25 ++++++++-- DrissionPage/_configs/chromium_options.pyi | 52 ++++++++++---------- DrissionPage/_functions/browser.py | 24 ---------- 5 files changed, 87 insertions(+), 73 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index efd965d..96f8c96 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -40,13 +40,12 @@ class Browser(object): :param session_options: 使用双模Tab时使用的默认Session配置,为True使用ini文件配置 """ opt = handle_options(addr_or_opts) - is_exist, browser_id = run_browser(opt) + is_headless, browser_id = run_browser(opt) if browser_id in cls._BROWSERS: - r = cls._BROWSERS[browser_id] - return r + return cls._BROWSERS[browser_id] r = object.__new__(cls) r._chromium_options = opt - r._is_exist = is_exist + r.is_headless = is_headless r.id = browser_id r.address = opt.address cls._BROWSERS[browser_id] = r @@ -63,7 +62,6 @@ class Browser(object): self._type = 'Browser' self._driver = BrowserDriver(self.id, 'browser', self.address, self) - self.version = self.run_cdp('Browser.getVersion')['product'] self._frames = {} self._drivers = {} @@ -82,9 +80,25 @@ class Browser(object): self.retry_times = self._chromium_options.retry_times self.retry_interval = self._chromium_options.retry_interval + if self.is_headless != self._chromium_options.is_headless: + self.quit(3, True) + connect_browser(self._chromium_options) + s = Session() + s.trust_env = False + ws = s.get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'}) + self.id = ws.json()['webSocketDebuggerUrl'].split('/')[-1] + self._driver = BrowserDriver(self.id, 'browser', self.address, self) + ws.close() + s.close() + self._frames = {} + self._drivers = {} + self._all_drivers = {} + + self.version = self._run_cdp('Browser.getVersion')['product'] + self._process_id = None try: - r = self.run_cdp('SystemInfo.getProcessInfo') + r = self._run_cdp('SystemInfo.getProcessInfo') for i in r.get('processInfo', []): if i['type'] == 'browser': self._process_id = i['id'] @@ -92,7 +106,7 @@ class Browser(object): except: pass - self.run_cdp('Target.setDiscoverTargets', discover=True) + self._run_cdp('Target.setDiscoverTargets', discover=True) self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) self._driver.set_callback('Target.targetCreated', self._onTargetCreated) self._dl_mgr = DownloadManager(self) @@ -137,7 +151,7 @@ class Browser(object): self._drivers.pop(tab_id, None) self._all_drivers.pop(tab_id, None) - def run_cdp(self, cmd, **cmd_args): + def _run_cdp(self, cmd, **cmd_args): """执行Chrome DevTools Protocol语句 :param cmd: 协议项目 :param cmd_args: 参数 @@ -183,7 +197,7 @@ class Browser(object): @property def tabs_count(self): """返回标签页数量""" - j = self.run_cdp('Target.getTargets')['targetInfos'] # 不要改用get,避免卡死 + j = self._run_cdp('Target.getTargets')['targetInfos'] # 不要改用get,避免卡死 return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]) @property @@ -235,7 +249,7 @@ class Browser(object): """ tab = None if new_context: - tab = self.run_cdp('Target.createBrowserContext')['browserContextId'] + tab = self._run_cdp('Target.createBrowserContext')['browserContextId'] kwargs = {'url': ''} if new_window: @@ -245,7 +259,7 @@ class Browser(object): if tab: kwargs['browserContextId'] = tab - tab = self.run_cdp('Target.createTarget', **kwargs)['targetId'] + tab = self._run_cdp('Target.createTarget', **kwargs)['targetId'] while tab not in self._drivers: sleep(.1) tab = obj(self, tab) @@ -401,14 +415,14 @@ class Browser(object): :param tab_id: 标签页id :return: None """ - self.run_cdp('Target.activateTarget', targetId=tab_id) + self._run_cdp('Target.activateTarget', targetId=tab_id) def reconnect(self): """断开重连""" self._driver.stop() BrowserDriver.BROWSERS.pop(self.id) self._driver = BrowserDriver(self.id, 'browser', self.address, self) - self.run_cdp('Target.setDiscoverTargets', discover=True) + self._run_cdp('Target.setDiscoverTargets', discover=True) self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) self._driver.set_callback('Target.targetCreated', self._onTargetCreated) @@ -419,7 +433,7 @@ class Browser(object): :return: None """ try: - self.run_cdp('Browser.close') + self._run_cdp('Browser.close') except PageDisconnectedError: pass self._driver.stop() @@ -433,7 +447,7 @@ class Browser(object): return try: - pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']] + pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']] except: return @@ -516,18 +530,20 @@ def handle_options(addr_or_opts): def run_browser(chromium_options): """连接浏览器""" - is_exist = connect_browser(chromium_options) + connect_browser(chromium_options) try: s = Session() s.trust_env = False ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'}) if not ws: - raise BrowserConnectError('\n浏览器连接失败,如使用全局代理,须设置不代理127.0.0.1地址。') - browser_id = ws.json()['webSocketDebuggerUrl'].split('/')[-1] + raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') + json = ws.json() + browser_id = json['webSocketDebuggerUrl'].split('/')[-1] + is_headless = 'headless' in json['User-Agent'].lower() ws.close() s.close() except KeyError: raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。') except: - raise BrowserConnectError('\n浏览器连接失败,如使用全局代理,须设置不代理127.0.0.1地址。') - return is_exist, browser_id + raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') + return is_headless, browser_id diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 9973f82..bde7e8b 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -24,6 +24,7 @@ class Browser(object): version: str = ... retry_times: int = ... retry_interval: float = ... + is_headless:bool = ... _BROWSERS: dict = ... _chromium_options: ChromiumOptions = ... @@ -51,7 +52,7 @@ class Browser(object): def _get_driver(self, tab_id: str, owner=None) -> Driver: ... - def run_cdp(self, cmd, **cmd_args) -> dict: ... + def _run_cdp(self, cmd, **cmd_args) -> dict: ... @property def process_id(self) -> Optional[int]: ... diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index acb7295..03f1152 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -21,7 +21,7 @@ class ChromiumOptions(object): self._user = 'Default' self._prefs_to_del = [] self.clear_file_flags = False - self._headless = None + self._is_headless = False if read_file is False: ini_path = False @@ -47,6 +47,10 @@ class ChromiumOptions(object): self._load_mode = options.get('load_mode', 'normal') self._system_user_path = options.get('system_user_path', False) self._existing_only = options.get('existing_only', False) + for i in self._arguments: + if i.startswith('--headless'): + self._is_headless = True + break self._proxy = om.proxies.get('http', None) or om.proxies.get('https', None) @@ -164,6 +168,11 @@ class ChromiumOptions(object): """返回连接失败时的重试间隔(秒)""" return self._retry_interval + @property + def is_headless(self): + """返回是否无头模式""" + return self._is_headless + def set_retry(self, times=None, interval=None): """设置连接失败时的重试操作 :param times: 重试次数 @@ -184,11 +193,19 @@ class ChromiumOptions(object): """ self.remove_argument(arg) if value is not False: - if arg == '--headless' and value is None: - self._arguments.append('--headless=new') + if arg == '--headless': + if value == 'false': + self._is_headless = False + else: + if value is None: + value = 'new' + self._arguments.append(f'--headless={value}') + self._is_headless = True else: arg_str = arg if value is None else f'{arg}={value}' self._arguments.append(arg_str) + elif arg == '--headless': + self._is_headless = False return self def remove_argument(self, value): @@ -312,7 +329,7 @@ class ChromiumOptions(object): :param on_off: 开或关 :return: 当前对象 """ - on_off = 'new' if on_off else 'false' + on_off = 'new' if on_off else on_off return self.set_argument('--headless', on_off) def no_imgs(self, on_off=True): diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index c682a27..56fca1e 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -10,30 +10,31 @@ from typing import Union, Any, Literal, Optional, Tuple class ChromiumOptions(object): - def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): - self.ini_path: str = ... - self._driver_path: str = ... - self._user_data_path: str = ... - self._download_path: str = ... - self._tmp_path: str = ... - self._arguments: list = ... - self._browser_path: str = ... - self._user: str = ... - self._load_mode: str = ... - self._timeouts: dict = ... - self._proxy: str = ... - self._address: str = ... - self._extensions: list = ... - self._prefs: dict = ... - self._flags: dict = ... - self._prefs_to_del: list = ... - self.clear_file_flags: bool = ... - self._auto_port: bool = ... - self._system_user_path: bool = ... - self._existing_only: bool = ... - self._headless: bool = ... - self._retry_times: int = ... - self._retry_interval: float = ... + ini_path: Optional[str] = ... + _driver_path: str = ... + _user_data_path: Optional[str] = ... + _download_path: str = ... + _tmp_path: str = ... + _arguments: list = ... + _browser_path: str = ... + _user: str = ... + _load_mode: str = ... + _timeouts: dict = ... + _proxy: str = ... + _address: str = ... + _extensions: list = ... + _prefs: dict = ... + _flags: dict = ... + _prefs_to_del: list = ... + clear_file_flags: bool = ... + _auto_port: bool = ... + _system_user_path: bool = ... + _existing_only: bool = ... + _retry_times: int = ... + _retry_interval: float = ... + _is_headless: bool = ... + + def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): ... @property def download_path(self) -> str: ... @@ -89,6 +90,9 @@ class ChromiumOptions(object): @property def retry_interval(self) -> float: ... + @property + def is_headless(self) -> bool: ... + def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions: ... def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions: ... diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index a8f79d4..4211748 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -30,11 +30,6 @@ def connect_browser(option): ip, port = address.split(':') if ip != '127.0.0.1' or port_is_using(ip, port) or option.is_existing_only: test_connect(ip, port) - option._headless = False - for i in option.arguments: - if i.startswith('--headless') and not i.endswith('=false'): - option._headless = True - break return True # ----------创建浏览器进程---------- @@ -65,7 +60,6 @@ def get_launch_args(opt): # ----------处理arguments----------- result = set() has_user_path = False - headless = None for i in opt.arguments: if i.startswith(('--load-extension=', '--remote-debugging-port=')): continue @@ -73,16 +67,6 @@ def get_launch_args(opt): result.add(f'--user-data-dir={Path(i[16:]).absolute()}') has_user_path = True continue - elif i.startswith('--headless'): - if i == '--headless=false': - headless = False - continue - elif i == '--headless': - i = '--headless=new' - headless = True - else: - headless = True - result.add(i) if not has_user_path and not opt.system_user_path: @@ -93,15 +77,7 @@ def get_launch_args(opt): opt.set_user_data_path(path) result.add(f'--user-data-dir={path}') - # if headless is None and system().lower() == 'linux': # 无界面Linux自动加入无头 - # from os import popen - # r = popen('systemctl list-units | grep graphical.target') - # if 'graphical.target' not in r.read(): - # headless = True - # result.add('--headless=new') - result = list(result) - opt._headless = headless # ----------处理插件extensions------------- ext = [str(Path(e).absolute()) for e in opt.extensions] From a877f646e0a959fe2f79c0e0637e2ebec4fab288 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 28 Jun 2024 23:54:29 +0800 Subject: [PATCH 004/114] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=8E=B7=E5=8F=96lin?= =?UTF-8?q?k=E6=97=B6./=E5=BC=80=E5=A4=B4=E9=93=BE=E6=8E=A5=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_functions/web.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index 461f143..a9ee8e5 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -10,7 +10,7 @@ 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 @@ -144,8 +144,11 @@ def make_absolute_link(link, baseURI=None): link = link.strip().replace('\\', '/') parsed = urlparse(link)._asdict() if baseURI: - p = urlparse(baseURI)._asdict() - baseURI = f'{p["scheme"]}://{p["netloc"]}' + if link.startswith('./'): + baseURI = baseURI[:baseURI.rfind('/') + 1] + else: + p = urlparse(baseURI)._asdict() + baseURI = f'{p["scheme"]}://{p["netloc"]}' # 是相对路径,与页面url拼接并返回 if not parsed['netloc']: From fddc472ad5c47024876fcd8a7f3a1bcd2607fc20 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sat, 29 Jun 2024 17:22:22 +0800 Subject: [PATCH 005/114] =?UTF-8?q?=E4=BC=98=E5=8C=96auto=5Fport()?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_configs/chromium_options.py | 2 +- DrissionPage/_configs/chromium_options.pyi | 2 +- DrissionPage/_functions/tools.py | 49 ++++++++++++---------- DrissionPage/_functions/tools.pyi | 4 +- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 03f1152..994639d 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -512,7 +512,7 @@ class ChromiumOptions(object): :return: 当前对象 """ if on_off: - self._auto_port = scope if scope else True + self._auto_port = scope if scope else (9600, 19600) if tmp_path: self._tmp_path = str(tmp_path) else: diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index 56fca1e..42013b5 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -27,7 +27,7 @@ class ChromiumOptions(object): _flags: dict = ... _prefs_to_del: list = ... clear_file_flags: bool = ... - _auto_port: bool = ... + _auto_port: Union[Tuple[int, int], False] = ... _system_user_path: bool = ... _existing_only: bool = ... _retry_times: int = ... diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 2486718..4f6b13a 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -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,8 +18,10 @@ from ..errors import (ContextLostError, ElementLostError, CDPError, PageDisconne class PortFinder(object): - used_port = {} + used_port = set() + prev_time = None lock = Lock() + checked_paths = set() def __init__(self, path=None): """ @@ -28,34 +30,39 @@ class PortFinder(object): tmp = Path(path) if path else Path(gettempdir()) / 'DrissionPage' self.tmp_dir = tmp / 'UserTempFolder' 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 i.stem.startswith('AutoPort') and not port_is_using('127.0.0.1', i.name[8:]): + 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) :return: 可以使用的端口和用户文件夹路径组成的元组 """ + from random import randint with PortFinder.lock: + if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 30: + PortFinder.used_port.clear() + PortFinder.prev_time = perf_counter() if scope in (True, None): scope = (9600, 19600) - for i in range(scope[0], scope[1]): - if i in PortFinder.used_port: + msx_times = scope[1] - scope[0] + times = 0 + while times < msx_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 - 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 - - raise OSError('未找到可用端口。') + path = self.tmp_dir / f'AutoPort{port}' + if path.exists(): + try: + rmtree(path) + except: + continue + return port, str(path) + raise OSError('未找到可用端口。') def port_is_using(ip, port): @@ -95,7 +102,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': diff --git a/DrissionPage/_functions/tools.pyi b/DrissionPage/_functions/tools.pyi index a6fc535..4fa536b 100644 --- a/DrissionPage/_functions/tools.pyi +++ b/DrissionPage/_functions/tools.pyi @@ -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): ... From 139bee5a914d2ebcb3b8f7bc1c389187704ecf84 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sat, 29 Jun 2024 23:01:42 +0800 Subject: [PATCH 006/114] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 1 - DrissionPage/_base/base.pyi | 3 - DrissionPage/_base/browser.py | 154 ++++++++++----------- DrissionPage/_base/browser.pyi | 9 +- DrissionPage/_elements/chromium_element.py | 4 +- DrissionPage/_pages/chromium_base.py | 14 +- DrissionPage/_pages/chromium_base.pyi | 4 +- DrissionPage/_pages/chromium_frame.py | 30 +--- DrissionPage/_pages/chromium_frame.pyi | 14 +- DrissionPage/_pages/chromium_tab.py | 12 +- DrissionPage/_pages/chromium_tab.pyi | 4 +- DrissionPage/_pages/session_page.py | 3 +- DrissionPage/_pages/web_page.pyi | 3 - DrissionPage/_units/clicker.py | 4 +- DrissionPage/_units/setter.py | 42 +----- 15 files changed, 100 insertions(+), 201 deletions(-) diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index c30293e..a3e915e 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -54,7 +54,6 @@ class BaseElement(BaseParser): def __init__(self, owner=None): self.owner = owner - # self.page = owner._page if owner else None self._type = 'BaseElement' # ----------------以下属性或方法由后代实现---------------- diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 764ccb3..e435c16 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -213,9 +213,6 @@ class BasePage(BaseParser): @property def timeout(self) -> float: ... - @timeout.setter - def timeout(self, second: float) -> None: ... - @property def url_available(self) -> bool: ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 96f8c96..2c43b70 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -47,7 +47,6 @@ class Browser(object): r._chromium_options = opt r.is_headless = is_headless r.id = browser_id - r.address = opt.address cls._BROWSERS[browser_id] = r return r @@ -61,7 +60,6 @@ class Browser(object): self._created = True self._type = 'Browser' - self._driver = BrowserDriver(self.id, 'browser', self.address, self) self._frames = {} self._drivers = {} @@ -70,15 +68,14 @@ class Browser(object): self._set = None self._wait = None - self._timeouts = Timeout(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._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.user_data_path = self._chromium_options.user_data_path + self.address = self._chromium_options.address + self._driver = BrowserDriver(self.id, 'browser', self.address, self) if self.is_headless != self._chromium_options.is_headless: self.quit(3, True) @@ -113,54 +110,6 @@ class Browser(object): self._session_options = SessionOptions() if session_options is True else session_options - def _get_driver(self, tab_id, owner=None): - """新建并返回指定tab id的Driver - :param tab_id: 标签页id - :param owner: 使用该驱动的对象 - :return: Driver对象 - """ - d = self._drivers.pop(tab_id, None) - if not d: - d = Driver(tab_id, 'page', self.address) - d.owner = owner - self._all_drivers.setdefault(tab_id, set()).add(d) - return d - - def _onTargetCreated(self, **kwargs): - """标签页创建时执行""" - if (kwargs['targetInfo']['type'] in ('page', 'webview') - and kwargs['targetInfo']['targetId'] not in self._all_drivers - and not kwargs['targetInfo']['url'].startswith('devtools://')): - try: - tab_id = kwargs['targetInfo']['targetId'] - d = Driver(tab_id, 'page', self.address) - self._drivers[tab_id] = d - self._all_drivers.setdefault(tab_id, set()).add(d) - except WebSocketBadStatusException: - pass - - 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) - for d in self._all_drivers.get(tab_id, tuple()): - d.stop() - self._drivers.pop(tab_id, None) - self._all_drivers.pop(tab_id, None) - - 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) - @property def process_id(self): """返回浏览器进程id""" @@ -204,8 +153,7 @@ class Browser(object): 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://')] + return [i['id'] for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')] @property def latest_tab(self): @@ -213,8 +161,7 @@ class Browser(object): 当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id""" return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj) - def new_tab(self, url=None, new_window=False, background=False, - new_context=False): + def new_tab(self, url=None, new_window=False, background=False, new_context=False): """新建一个标签页 :param url: 新标签页跳转到的网址 :param new_window: 是否在新窗口打开标签页 @@ -225,8 +172,7 @@ class Browser(object): 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): + def new_mix_tab(self, url=None, new_window=False, background=False, new_context=False): """新建一个标签页 :param url: 新标签页跳转到的网址 :param new_window: 是否在新窗口打开标签页 @@ -237,8 +183,7 @@ class Browser(object): 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): + def _new_tab(self, obj, url=None, new_window=False, background=False, new_context=False): """新建一个标签页 :param obj: 要创建的Tab类型 :param url: 新标签页跳转到的网址 @@ -273,7 +218,7 @@ class Browser(object): :param title: 要匹配title的文本,模糊匹配,为None则匹配所有 :param url: 要匹配url的文本,模糊匹配,为None则匹配所有 :param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有 - :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :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) @@ -283,7 +228,7 @@ class Browser(object): :param title: 要匹配title的文本 :param url: 要匹配url的文本 :param tab_type: tab类型,可用列表输入多个 - :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :param as_id: 是否返回标签页id而不是标签页对象 :return: Tab对象列表 """ return self._get_tabs(title=title, url=url, tab_type=tab_type, as_id=as_id) @@ -294,30 +239,29 @@ class Browser(object): :param title: 要匹配title的文本,模糊匹配,为None则匹配所有 :param url: 要匹配url的文本,模糊匹配,为None则匹配所有 :param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有 - :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :param as_id: 是否返回标签页id而不是标签页对象 :return: Tab对象 """ - return self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, dual_mode=True, as_id=as_id) + 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而不是标签页对象,dual_mode=False时无效 + :param as_id: 是否返回标签页id而不是标签页对象 :return: Tab对象列表 """ - return self._get_tabs(title=title, url=url, tab_type=tab_type, dual_mode=True, as_id=as_id) + 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', - dual_mode=False, as_id=False): + 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 dual_mode: 是否返回可切换模式的Tab对象 - :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :param mix: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效 :return: Tab对象 """ if id_or_num is not None: @@ -326,12 +270,7 @@ class Browser(object): 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) # fixme: 这里会出现循环调用 + 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] @@ -346,15 +285,15 @@ class Browser(object): if as_id: return id_or_num with self._lock: - return MixTab(self, id_or_num) if dual_mode else ChromiumTab(self, id_or_num) + 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', dual_mode=False, as_id=False): + 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 dual_mode: 是否返回可切换模式的Tab对象 - :param as_id: 是否返回标签页id而不是标签页对象,dual_mode=False时无效 + :param mix: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效 :return: Tab对象列表 """ tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp @@ -371,7 +310,7 @@ class Browser(object): if as_id: return [tab['id'] for tab in tabs] with self._lock: - if dual_mode: + if mix: return [MixTab(self, tab['id']) for tab in tabs] else: return [ChromiumTab(self, tab['id']) for tab in tabs] @@ -477,6 +416,53 @@ class Browser(object): if ok: break + def _get_driver(self, tab_id, owner=None): + """新建并返回指定tab id的Driver + :param tab_id: 标签页id + :param owner: 使用该驱动的对象 + :return: Driver对象 + """ + d = self._drivers.pop(tab_id, None) + if not d: + d = Driver(tab_id, 'page', self.address) + d.owner = owner + self._all_drivers.setdefault(tab_id, set()).add(d) + return d + + def _onTargetCreated(self, **kwargs): + """标签页创建时执行""" + if (kwargs['targetInfo']['type'] in ('page', 'webview') + and kwargs['targetInfo']['targetId'] not in self._all_drivers + and not kwargs['targetInfo']['url'].startswith('devtools://')): + try: + tab_id = kwargs['targetInfo']['targetId'] + d = Driver(tab_id, 'page', self.address) + self._drivers[tab_id] = d + self._all_drivers.setdefault(tab_id, set()).add(d) + except WebSocketBadStatusException: + pass + + def _onTargetDestroyed(self, **kwargs): + """标签页关闭时执行""" + tab_id = kwargs['targetId'] + 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) + for d in self._all_drivers.get(tab_id, tuple()): + d.stop() + self._drivers.pop(tab_id, None) + self._all_drivers.pop(tab_id, None) + + 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 _on_disconnect(self): Browser._BROWSERS.pop(self.id, None) if self._chromium_options.is_auto_port and self._chromium_options.user_data_path: diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index bde7e8b..1e8e000 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -11,7 +11,7 @@ from typing import List, Optional, Set, Dict, Union, Tuple from .driver import BrowserDriver, Driver from .._configs.chromium_options import ChromiumOptions from .._configs.session_options import SessionOptions -from .._pages.chromium_base import Timeout, ChromiumBase +from .._pages.chromium_base import Timeout from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager from .._units.setter import BrowserSetter @@ -24,7 +24,8 @@ class Browser(object): version: str = ... retry_times: int = ... retry_interval: float = ... - is_headless:bool = ... + is_headless: bool = ... + user_data_path: str = ... _BROWSERS: dict = ... _chromium_options: ChromiumOptions = ... @@ -115,14 +116,14 @@ class Browser(object): title: str = None, url: str = None, tab_type: str = 'page', - dual_mode: bool = False, + mix: bool = False, as_id: bool = False) -> Union[ChromiumTab, str]: ... def _get_tabs(self, title: str = None, url: str = None, tab_type: str = 'page', - dual_mode: bool = False, + mix: bool = False, as_id: bool = False) -> List[ChromiumTab, str]: ... def activate_tab(self, tab_id: str) -> None: ... diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 3f42b5f..da78f5d 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -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 @@ -900,7 +900,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 diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index a775124..bc7abf2 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -65,23 +65,11 @@ 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) - # 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://') - def _d_set_runtime_settings(self): - self._timeouts = Timeout(self) - self._load_mode = 'normal' + pass def _connect_browser(self, tab_id=None): """连接浏览器,在第一次时运行 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 8b9039b..176ccd3 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -8,7 +8,7 @@ from pathlib import Path from typing import Union, Tuple, List, Any, Optional, Literal -from .chromium_tab import ChromiumTab +from .chromium_tab import ChromiumTab, MixTab from .._base.base import BasePage from .._base.browser import Browser from .._base.driver import Driver @@ -33,8 +33,8 @@ class ChromiumBase(BasePage): def __init__(self, browser: Browser, tab_id: str = None): + self._tab:Union[ChromiumTab, MixTab, ChromiumFrame] = ... self._browser: Browser = ... - self.tab: Union[ChromiumPage, ChromiumTab] = ... self._driver: Driver = ... self._frame_id: str = ... self._is_reading: bool = ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 4bc5eb5..5ae97b4 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -27,22 +27,10 @@ class ChromiumFrame(ChromiumBase): :param ele: frame所在元素 :param info: frame所在元素信息 """ - self.tab = owner.tab + self._tab = owner._tab self._target_page = owner - # if owner._type in ('ChromiumPage', 'WebPage'): - # self._target_page = self.tab = owner - # self._browser = owner.browser - # else: # ChromiumTab、Frame - # # self._page = owner.page - # self._browser = self._page.browser - # 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'] @@ -58,13 +46,7 @@ class ChromiumFrame(ChromiumBase): 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): """在内部查找元素 @@ -252,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): """返回所属页面对象""" @@ -319,6 +296,11 @@ 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""" diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index 3e44b41..b7e6183 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -9,9 +9,7 @@ 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 .chromium_tab import ChromiumTab, MixTab from .._elements.chromium_element import ChromiumElement from .._functions.elements import ChromiumElementsList from .._units.listener import FrameListener @@ -29,7 +27,7 @@ class ChromiumFrame(ChromiumBase): ele: ChromiumElement, info: dict = None): self._target_page: Union[ChromiumTab, ChromiumFrame] = ... - self.tab: ChromiumTab = ... + self._tab: Union[MixTab, ChromiumTab] = ... self._set: ChromiumFrameSetter = ... self._frame_ele: ChromiumElement = ... self._backend_id: int = ... @@ -38,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, @@ -64,9 +62,6 @@ class ChromiumFrame(ChromiumBase): def _onInspectorDetached(self, **kwargs): ... - # @property - # def page(self) -> Union[ChromiumPage, WebPage]: ... - @property def owner(self) -> ChromiumBase: ... @@ -124,6 +119,9 @@ class ChromiumFrame(ChromiumBase): @property def wait(self) -> FrameWaiter: ... + @property + def tab(self) -> Union[ChromiumTab, MixTab]: ... + @property def tab_id(self) -> str: ... diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index f99fe21..3504877 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -45,10 +45,8 @@ class ChromiumTab(ChromiumBase): return self._created = True - self.tab = self - self._browser = browser super().__init__(browser, tab_id) - self._rect = None + self._tab = self self._type = 'ChromiumTab' def _d_set_runtime_settings(self): @@ -210,14 +208,6 @@ class MixTab(SessionPage, ChromiumTab, BasePage): """返回通用timeout设置""" return self.timeouts.base - @timeout.setter - def timeout(self, second): - """设置通用超时时间 - :param second: 秒数 - :return: None - """ - self.set.timeouts(base=second) - def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs): """跳转到一个url :param url: 目标url diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index 738b712..cb94132 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -28,7 +28,7 @@ class ChromiumTab(ChromiumBase): def __new__(cls, browser: Browser, tab_id: str): ... def __init__(self, browser: Browser, tab_id: str): - self._browser: Browser = ... + self._tab: ChromiumTab = ... self._rect: Optional[TabRect] = ... def _d_set_runtime_settings(self) -> None: ... @@ -64,7 +64,7 @@ class ChromiumTab(ChromiumBase): class MixTab(SessionPage, ChromiumTab): - _browser: Browser = ... + _tab: MixTab = ... _mode: str = ... _has_driver: bool = ... _has_session: bool = ... diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index c2bb376..e34f6a7 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -61,8 +61,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 diff --git a/DrissionPage/_pages/web_page.pyi b/DrissionPage/_pages/web_page.pyi index 658c976..4adce01 100644 --- a/DrissionPage/_pages/web_page.pyi +++ b/DrissionPage/_pages/web_page.pyi @@ -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, diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index eb97ba9..fd5f9c7 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -162,9 +162,9 @@ class Clicker(object): :return: DownloadMission对象 """ if save_path: - self._ele.owner.tab.set.download_path(save_path) + self._ele.tab.set.download_path(save_path) elif not self._ele.tab._browser._dl_mgr._running: - self._ele.owner.tab._browser.set.download_path('.') + self._ele.tab._browser.set.download_path('.') obj = self._ele.tab._browser if new_tab else self._ele.owner.tab if rename or suffix: diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index e6e6695..8b7da82 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -52,6 +52,7 @@ class BasePageSetter(object): class BrowserBaseSetter(BasePageSetter): + """Browser和ChromiumBase设置""" @property def load_mode(self): @@ -67,7 +68,6 @@ class BrowserBaseSetter(BasePageSetter): """ 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 @@ -135,11 +135,6 @@ class ChromiumBaseSetter(BrowserBaseSetter): super().__init__(owner) self._cookies_setter = None - # @property - # def load_mode(self): - # """返回用于设置页面加载策略的对象""" - # return LoadMode(self._owner) - @property def scroll(self): """返回用于设置页面滚动设置的对象""" @@ -152,31 +147,6 @@ class ChromiumBaseSetter(BrowserBaseSetter): self._cookies_setter = CookiesSetter(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 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 - # 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字符串 @@ -354,14 +324,6 @@ 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: 下载路径 @@ -376,7 +338,7 @@ class SessionPageSetter(BasePageSetter): :param second: 秒数 :return: None """ - self._owner.timeout = second + self._owner._timeout = second def encoding(self, encoding, set_all=True): """设置编码 From 6d552330cdd3ba933ca155651c50e1b18c59ef58 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 1 Jul 2024 00:35:22 +0800 Subject: [PATCH 007/114] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=9F=9Fifr?= =?UTF-8?q?ame=E8=B7=B3=E8=BD=AC=E5=88=B0=E5=90=8C=E5=9F=9F=E6=97=B6?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B=E5=85=B6=E5=AE=83=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 10 +- DrissionPage/_base/base.pyi | 5 +- DrissionPage/_base/browser.py | 10 + DrissionPage/_base/browser.pyi | 3 + DrissionPage/_base/driver.py | 12 +- DrissionPage/_configs/session_options.py | 3 +- DrissionPage/_elements/chromium_element.py | 154 +++++++------ DrissionPage/_elements/chromium_element.pyi | 4 + DrissionPage/_elements/session_element.py | 4 +- DrissionPage/_functions/cookies.py | 233 ++++++++++++++++++++ DrissionPage/_functions/cookies.pyi | 44 ++++ DrissionPage/_functions/keys.py | 10 +- DrissionPage/_functions/tools.py | 14 +- DrissionPage/_functions/tools.pyi | 2 +- DrissionPage/_functions/web.py | 209 +----------------- DrissionPage/_functions/web.pyi | 19 -- DrissionPage/_pages/chromium_base.py | 135 ++++++++---- DrissionPage/_pages/chromium_base.pyi | 12 +- DrissionPage/_pages/chromium_frame.py | 48 ++-- DrissionPage/_pages/chromium_frame.pyi | 6 + DrissionPage/_pages/chromium_page.py | 3 +- DrissionPage/_pages/chromium_tab.py | 14 +- DrissionPage/_pages/chromium_tab.pyi | 4 +- DrissionPage/_pages/session_page.py | 23 +- DrissionPage/_pages/session_page.pyi | 6 +- DrissionPage/_pages/web_page.py | 6 +- DrissionPage/_units/actions.py | 16 +- DrissionPage/_units/clicker.py | 8 +- DrissionPage/_units/cookies_setter.py | 18 +- DrissionPage/_units/cookies_setter.pyi | 11 +- DrissionPage/_units/downloader.py | 6 +- DrissionPage/_units/listener.py | 2 +- DrissionPage/_units/rect.py | 22 +- DrissionPage/_units/screencast.py | 20 +- DrissionPage/_units/scroller.py | 10 +- DrissionPage/_units/selector.py | 10 +- DrissionPage/_units/setter.py | 69 +++--- DrissionPage/_units/setter.pyi | 14 +- DrissionPage/_units/states.py | 22 +- 39 files changed, 708 insertions(+), 513 deletions(-) create mode 100644 DrissionPage/_functions/cookies.py create mode 100644 DrissionPage/_functions/cookies.pyi diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index a3e915e..ccd0e5b 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -12,10 +12,10 @@ 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.locator import get_loc +from .._functions.settings import Settings +from .._functions.web import format_html from ..errors import ElementNotFoundError @@ -414,10 +414,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 diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index e435c16..27fcb1f 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -205,7 +205,7 @@ class BasePage(BaseParser): 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, WebPage] = ... @property def title(self) -> Union[str, None]: ... @@ -234,9 +234,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): ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 2c43b70..93b85ae 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -17,6 +17,7 @@ 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 @@ -161,6 +162,15 @@ class Browser(object): 当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: 新标签页跳转到的网址 diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 1e8e000..fbe2d65 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -11,6 +11,7 @@ from typing import List, Optional, Set, Dict, Union, Tuple from .driver import BrowserDriver, Driver from .._configs.chromium_options import ChromiumOptions from .._configs.session_options import SessionOptions +from .._functions.web import CookiesList from .._pages.chromium_base import Timeout from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager @@ -82,6 +83,8 @@ class Browser(object): @property def latest_tab(self) -> Union[ChromiumTab, str]: ... + def cookies(self, all_info: bool = False) -> CookiesList: ... + def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], Tuple[Union[str, ChromiumTab]]] = None, diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 3044a87..8f421f5 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -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出现,跳过一条请求后复原 @@ -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) @@ -227,6 +228,15 @@ class Driver(object): 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']) diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index 25ea8cd..9794395 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -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): diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index da78f5d..9c0b5cd 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -16,9 +16,9 @@ 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 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 .._units.clicker import Clicker from .._units.rect import ElementRect @@ -95,29 +95,29 @@ class ChromiumElement(DrissionElement): def tag(self): """返回元素tag""" if self._tag is None: - self._tag = self.owner.run_cdp('DOM.describeNode', - backendNodeId=self._backend_id)['node']['localName'].lower() + 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 {} @@ -164,7 +164,7 @@ class ChromiumElement(DrissionElement): @property def shadow_root(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 @@ -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): @@ -360,9 +360,9 @@ class ChromiumElement(DrissionElement): x, y = self.rect.location try: return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x, - y=y + offset_y, includeUserAgentShadowDOM=True, - ignorePointerEventsNone=False)['backendNodeId']) + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x + offset_x, + y=y + offset_y, includeUserAgentShadowDOM=True, + ignorePointerEventsNone=False)['backendNodeId']) except CDPError: return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y}) @@ -439,8 +439,8 @@ class ChromiumElement(DrissionElement): cdp_data[variable] += locator try: return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', - **cdp_data)['backendNodeId']) + 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 +453,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 +505,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 +513,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]... @@ -588,7 +598,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 +612,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 +641,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 +708,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 +739,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) @@ -746,8 +756,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,16 +766,16 @@ 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值时悬停在元素中点 @@ -775,7 +785,7 @@ class ChromiumElement(DrissionElement): """ 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._run_cdp('Input.dispatchMouseEvent', type='mouseMoved', x=x, y=y, _ignore=AlertExistsError) def drag(self, offset_x=0, offset_y=0, duration=.5): """拖拽当前元素到相对位置 @@ -808,9 +818,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 +829,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 +840,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'] @@ -876,7 +886,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 +897,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): @@ -942,7 +952,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 +962,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]... @@ -1163,14 +1183,14 @@ 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', - nodeId=self._node_id, selector=loc[1])['nodeId'] + 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 @@ -1182,14 +1202,14 @@ class ShadowRoot(BaseElement): css = [i.css_path[61:] for i in eles] if index is not None: try: - node_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, - selector=css[index - 1])['nodeId'] + 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 +1229,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'] @@ -1269,15 +1289,15 @@ 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, - returnByValue=False, awaitPromise=True, userGesture=True) + 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, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) return res['result']['value'] else: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1290,8 +1310,8 @@ 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'], - ownProperties=True)['result'][:-1] + res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], + ownProperties=True)['result'][:-1] if index is None: r = ChromiumElementsList(page=ele.owner) for i in res: @@ -1341,8 +1361,8 @@ 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, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) if 'exceptionDetails' in res: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1354,9 +1374,9 @@ 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', - objectId=res['result']['objectId'], - ownProperties=True)['result']] + 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) return None if r is False else r @@ -1535,16 +1555,16 @@ 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, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + 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, - arguments=[convert_argument(arg) for arg in args], returnByValue=False, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + 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: raise TimeoutError(f'执行js超时(等待{timeout}秒)。') except ContextLostError: @@ -1591,7 +1611,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,9 +1619,9 @@ 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'], - returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, - _timeout=timeout) + 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)) else: diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index a629c85..648c68e 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -195,6 +195,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, @@ -298,6 +300,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: ... diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index 888269a..f0d35a1 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -349,11 +349,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) diff --git a/DrissionPage/_functions/cookies.py b/DrissionPage/_functions/cookies.py new file mode 100644 index 0000000..fad305d --- /dev/null +++ b/DrissionPage/_functions/cookies.py @@ -0,0 +1,233 @@ +# -*- 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 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 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) diff --git a/DrissionPage/_functions/cookies.pyi b/DrissionPage/_functions/cookies.pyi new file mode 100644 index 0000000..fca7a77 --- /dev/null +++ b/DrissionPage/_functions/cookies.pyi @@ -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 Browser +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: Browser, 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: ... diff --git a/DrissionPage/_functions/keys.py b/DrissionPage/_functions/keys.py index d55267c..edde4d9 100644 --- a/DrissionPage/_functions/keys.py +++ b/DrissionPage/_functions/keys.py @@ -427,12 +427,12 @@ def send_key(page, modifier, key): 'isKeypad': description['location'] == 3, '_ignore': AlertExistsError} - page.run_cdp('Input.dispatchKeyEvent', **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 +451,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) diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 4f6b13a..84a2e1f 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -198,10 +198,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'] @@ -227,13 +228,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) diff --git a/DrissionPage/_functions/tools.pyi b/DrissionPage/_functions/tools.pyi index 4fa536b..5e344f3 100644 --- a/DrissionPage/_functions/tools.pyi +++ b/DrissionPage/_functions/tools.pyi @@ -47,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: ... diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index a9ee8e5..7c1854d 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -5,16 +5,13 @@ @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 from urllib.parse import urlparse, urljoin, urlunparse from DataRecorder.tools import make_valid_name -from tldextract import extract def get_ele_txt(e): @@ -106,7 +103,7 @@ 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): @@ -122,8 +119,8 @@ def offset_scroll(ele, offset_x, offset_y): 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 @@ -174,188 +171,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: 资源所在页面对象 @@ -381,7 +196,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: @@ -429,7 +244,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 @@ -455,7 +270,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 @@ -539,15 +354,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) diff --git a/DrissionPage/_functions/web.pyi b/DrissionPage/_functions/web.pyi index 3fdca32..fdd2d76 100644 --- a/DrissionPage/_functions/web.pyi +++ b/DrissionPage/_functions/web.pyi @@ -5,13 +5,9 @@ @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 .._base.base import DrissionElement, BaseParser from .._elements.chromium_element import ChromiumElement from .._pages.chromium_base import ChromiumBase @@ -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: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index bc7abf2..285bbef 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -18,6 +18,7 @@ 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.cookies import CookiesList from .._functions.locator import get_loc, is_loc from .._functions.settings import Settings from .._functions.tools import raise_error @@ -120,7 +121,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'): @@ -146,10 +147,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 @@ -166,7 +167,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 @@ -203,7 +204,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' @@ -228,10 +229,10 @@ class ChromiumBase(BasePage): if 'backendNodeId' not in kwargs: raise TypeError('该输入框无法接管,请改用对元素输入路径的方法设置。') 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): @@ -333,12 +334,12 @@ 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): @@ -349,7 +350,7 @@ class ChromiumBase(BasePage): 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): @@ -372,7 +373,7 @@ class ChromiumBase(BasePage): @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): @@ -382,7 +383,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): @@ -393,7 +394,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: @@ -405,9 +406,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语句,执行前等待页面加载完毕 @@ -416,7 +416,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代码 @@ -426,9 +446,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]... @@ -462,23 +503,21 @@ 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): """获取一个符合条件的元素对象 @@ -591,7 +630,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): @@ -616,7 +655,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 @@ -632,12 +671,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) @@ -655,7 +694,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): """新建一个元素 @@ -711,7 +750,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): @@ -772,7 +811,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则获取全部 @@ -780,7 +819,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): @@ -802,7 +841,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 @@ -814,11 +853,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): @@ -830,19 +869,19 @@ 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): """断开与页面的连接,不关闭页面""" @@ -974,7 +1013,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: @@ -1074,8 +1113,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} @@ -1086,7 +1125,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 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 176ccd3..efde024 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -15,6 +15,7 @@ from .._base.driver import Driver from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList, ChromiumElementsList +from .._functions.web import CookiesList from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage from .._units.actions import Actions @@ -170,13 +171,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], @@ -225,6 +229,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]: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 5ae97b4..8cf684b 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -33,7 +33,7 @@ class ChromiumFrame(ChromiumBase): self._frame_ele = ele 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 @@ -43,7 +43,7 @@ class ChromiumFrame(ChromiumBase): self._is_diff_domain = True delattr(self, '_frame_id') super().__init__(owner.browser, node['frameId']) - obj_id = super().run_js('document;', as_expr=True)['objectId'] + obj_id = super()._run_js('document;', as_expr=True)['objectId'] self.doc_ele = ChromiumElement(self, obj_id=obj_id) self._type = 'ChromiumFrame' @@ -100,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) @@ -145,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 @@ -253,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 @@ -261,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}' @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): @@ -284,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): @@ -318,18 +318,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属性值 @@ -353,6 +353,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]... @@ -361,9 +371,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): """返回上面某一级父元素,可指定层数或用查询语法定位 @@ -526,12 +536,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, @@ -557,4 +567,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']) diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index b7e6183..a23ed33 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -142,6 +142,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: ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index b4fea5d..76eec26 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -6,7 +6,6 @@ @License : BSD 3-Clause. """ from pathlib import Path -from threading import Lock from time import sleep, perf_counter from requests import Session @@ -65,7 +64,7 @@ class ChromiumPage(ChromiumBase): def _run_browser(self): """连接浏览器""" self._browser = Browser(self._chromium_options.address, self._browser_id, self) - r = self._browser.run_cdp('Browser.getVersion') + 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) diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index 3504877..5b42498 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -10,8 +10,9 @@ 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 @@ -338,7 +339,7 @@ class MixTab(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()) @@ -347,19 +348,18 @@ class MixTab(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): """关闭当前标签页""" diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index cb94132..072de9f 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -17,6 +17,7 @@ from .._base.browser import Browser from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList, ChromiumElementsList +from .._functions.web import CookiesList from .._units.rect import TabRect from .._units.setter import TabSetter, WebPageTabSetter from .._units.waiter import TabWaiter @@ -153,8 +154,7 @@ class MixTab(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: ... diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index e34f6a7..0697bea 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -18,7 +18,8 @@ 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 @@ -216,9 +217,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信息 @@ -229,21 +229,20 @@ 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']}) - return r + return r def close(self): """关闭Session对象""" diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 4527827..385ac2b 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -15,6 +15,7 @@ from .._base.base import BasePage from .._configs.session_options import SessionOptions from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList +from .._functions.web import CookiesList from .._units.setter import SessionPageSetter @@ -27,7 +28,7 @@ class SessionPage(BasePage): 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 = ... @@ -113,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 diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index fbcd2fb..e993f35 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -10,7 +10,7 @@ from .chromium_tab import MixTab 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 .._functions.cookies import set_session_cookies, set_tab_cookies from .._units.setter import WebPageSetter @@ -276,7 +276,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()) @@ -285,7 +285,7 @@ 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): """返回cookies diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index e1c951c..6942cde 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -7,9 +7,9 @@ """ from time import sleep, perf_counter -from ..errors import AlertExistsError from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions from .._functions.web import location_in_viewport +from ..errors import AlertExistsError class Actions: @@ -51,8 +51,8 @@ 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) # 这样设计为了应付那些不随滚动条滚动的元素 @@ -258,7 +258,7 @@ class Actions: data = self._get_key_data(key, 'keyDown') data['_ignore'] = AlertExistsError - self.owner.run_cdp('Input.dispatchKeyEvent', **data) + self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self def key_up(self, key): @@ -273,7 +273,7 @@ class Actions: data = self._get_key_data(key, 'keyUp') data['_ignore'] = AlertExistsError - self.owner.run_cdp('Input.dispatchKeyEvent', **data) + self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self def type(self, keys): @@ -292,7 +292,7 @@ class Actions: self.key_up(character) 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) @@ -339,6 +339,6 @@ class Actions: 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 diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index fd5f9c7..12e85f3 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -87,7 +87,7 @@ 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 @@ -101,7 +101,7 @@ class Clicker(object): return True if by_js is not False: - self._ele.run_js('this.click();') + self._ele._run_js('this.click();') return True if Settings.raise_when_click_failed: raise CanNotClickError @@ -204,7 +204,7 @@ class Clicker(object): :param count: 点击次数 :return: None """ - self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x, + 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, + self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x, y=client_y, button=button, _ignore=AlertExistsError) diff --git a/DrissionPage/_units/cookies_setter.py b/DrissionPage/_units/cookies_setter.py index c9d3c41..cb48d53 100644 --- a/DrissionPage/_units/cookies_setter.py +++ b/DrissionPage/_units/cookies_setter.py @@ -5,7 +5,7 @@ @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): @@ -20,7 +20,7 @@ class CookiesSetter(object): :param cookies: cookies信息 :return: None """ - set_browser_cookies(self._owner, cookies) + set_tab_cookies(self._owner, cookies) def remove(self, name, url=None, domain=None, path=None): """删除一个cookie @@ -39,11 +39,21 @@ class CookiesSetter(object): d['url'] = self._owner.url if path is not None: d['path'] = path - self._owner.run_cdp('Network.deleteCookies', **d) + self._owner._run_cdp('Storage.deleteCookies', **d) def clear(self): """清除cookies""" - self._owner.run_cdp('Network.clearBrowserCookies') + self._owner._run_cdp('Storage.clearBrowserCookies') + + +class BrowserCookiesSetter(CookiesSetter): + + def __call__(self, cookies): + """设置一个或多个cookie + :param cookies: cookies信息 + :return: None + """ + set_browser_cookies(self._owner, cookies) class SessionCookiesSetter(object): diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index c929448..1b467b1 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -8,6 +8,7 @@ from http.cookiejar import Cookie, CookieJar from typing import Union +from .._base.browser import Browser from .._pages.chromium_base import ChromiumBase from .._pages.chromium_tab import MixTab from .._pages.session_page import SessionPage @@ -26,8 +27,14 @@ class CookiesSetter(object): def clear(self) -> None: ... +class BrowserCookiesSetter(CookiesSetter): + _owner: Browser = ... + + def __init__(self, page: Browser): ... + + class SessionCookiesSetter(object): - _owner: SessionPage + _owner: SessionPage = ... def __init__(self, page: SessionPage): ... @@ -39,7 +46,7 @@ class SessionCookiesSetter(object): class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): - _owner: Union[WebPage, MixTab] + _owner: Union[WebPage, MixTab] = ... def __init__(self, page: SessionPage): ... diff --git a/DrissionPage/_units/downloader.py b/DrissionPage/_units/downloader.py index 0318266..9e190c4 100644 --- a/DrissionPage/_units/downloader.py +++ b/DrissionPage/_units/downloader.py @@ -55,7 +55,7 @@ class DownloadManager(object): 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, + r = self._browser._run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path, behavior='allowAndName', eventsEnabled=True) if 'error' in r: print('浏览器版本太低无法使用下载管理功能。') @@ -124,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: @@ -137,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 diff --git a/DrissionPage/_units/listener.py b/DrissionPage/_units/listener.py index ef49b35..504dc54 100644 --- a/DrissionPage/_units/listener.py +++ b/DrissionPage/_units/listener.py @@ -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 diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index b8bbbb8..2d8601e 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -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,7 +32,7 @@ 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] @@ -77,7 +77,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 +85,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,7 +93,7 @@ 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 def _get_viewport_rect(self, quad): @@ -101,13 +101,13 @@ class ElementRect(object): :param quad: 方框类型,margin border padding :return: 四个角坐标 """ - return self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id, + 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] 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,13 +174,13 @@ 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) 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): """获取窗口范围信息""" @@ -214,8 +214,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 diff --git a/DrissionPage/_units/screencast.py b/DrissionPage/_units/screencast.py index bd12711..e67df23 100644 --- a/DrissionPage/_units/screencast.py +++ b/DrissionPage/_units/screencast.py @@ -48,7 +48,7 @@ class Screencast(object): 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): diff --git a/DrissionPage/_units/scroller.py b/DrissionPage/_units/scroller.py index 224d640..6ec48c4 100644 --- a/DrissionPage/_units/scroller.py +++ b/DrissionPage/_units/scroller.py @@ -21,7 +21,7 @@ class Scroller(object): def _run_js(self, js): js = js.format(self.t1, self.t2, self.t2) - self._driver.run_js(js) + 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'] @@ -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;} diff --git a/DrissionPage/_units/selector.py b/DrissionPage/_units/selector.py index c97aded..27301ad 100644 --- a/DrissionPage/_units/selector.py +++ b/DrissionPage/_units/selector.py @@ -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}));') diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 8b7da82..04c4660 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -10,7 +10,7 @@ from time import sleep from requests.structures import CaseInsensitiveDict -from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter +from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter from .._functions.settings import Settings from .._functions.tools import show_or_hide_browser from .._functions.web import format_headers @@ -54,6 +54,13 @@ class BasePageSetter(object): class BrowserBaseSetter(BasePageSetter): """Browser和ChromiumBase设置""" + def __init__(self, owner): + """ + :param owner: ChromiumBase对象 + """ + super().__init__(owner) + self._cookies_setter = None + @property def load_mode(self): """返回用于设置页面加载策略的对象""" @@ -78,8 +85,12 @@ class BrowserBaseSetter(BasePageSetter): class BrowserSetter(BrowserBaseSetter): - def cookies(self, cookies): - pass # todo: 研究Storage.setCookies和Network.setCookies差别 + @property + def cookies(self): + """返回用于设置cookies的对象""" + if self._cookies_setter is None: + self._cookies_setter = BrowserCookiesSetter(self._owner) + return self._cookies_setter def tab_to_front(self, tab_or_id): """激活标签页使其处于最前面 @@ -128,12 +139,6 @@ class BrowserSetter(BrowserBaseSetter): class ChromiumBaseSetter(BrowserBaseSetter): - def __init__(self, owner): - """ - :param owner: ChromiumBase对象 - """ - super().__init__(owner) - self._cookies_setter = None @property def scroll(self): @@ -156,7 +161,7 @@ class ChromiumBaseSetter(BrowserBaseSetter): keys = {'userAgent': ua} if platform: keys['platform'] = platform - self._owner.run_cdp('Emulation.setUserAgentOverride', **keys) + self._owner._run_cdp('Emulation.setUserAgentOverride', **keys) def session_storage(self, item, value): """设置或删除某项sessionStorage信息 @@ -164,15 +169,15 @@ class ChromiumBaseSetter(BrowserBaseSetter): :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'] + 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', + 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}, + self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False}, key=item, value=value) - self._owner.run_cdp_loaded('DOMStorage.disable') + self._owner._run_cdp_loaded('DOMStorage.disable') def local_storage(self, item, value): """设置或删除某项localStorage信息 @@ -180,15 +185,15 @@ class ChromiumBaseSetter(BrowserBaseSetter): :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'] + 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', + 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}, + self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True}, key=item, value=value) - self._owner.run_cdp_loaded('DOMStorage.disable') + self._owner._run_cdp_loaded('DOMStorage.disable') def upload_files(self, files): """等待上传的文件路径 @@ -197,7 +202,7 @@ class ChromiumBaseSetter(BrowserBaseSetter): """ if not self._owner._upload_list: self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened) - self._owner.run_cdp('Page.setInterceptFileChooserDialog', enabled=True) + self._owner._run_cdp('Page.setInterceptFileChooserDialog', enabled=True) if isinstance(files, str): files = files.split('\n') @@ -210,8 +215,8 @@ class ChromiumBaseSetter(BrowserBaseSetter): :param headers: dict格式的headers数据 :return: None """ - self._owner.run_cdp('Network.enable') - self._owner.run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers)) + 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): """设置是否启用自动处理弹窗 @@ -232,8 +237,8 @@ class ChromiumBaseSetter(BrowserBaseSetter): 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) + self._owner._run_cdp('Network.enable') + self._owner._run_cdp('Network.setBlockedURLs', urls=urls) class TabSetter(ChromiumBaseSetter): @@ -522,11 +527,11 @@ class ChromiumElementSetter(object): :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): @@ -536,7 +541,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样式 @@ -545,7 +550,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}') @@ -629,7 +634,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 @@ -703,7 +708,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) @@ -713,7 +718,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()恢复正常状态。') diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index f01dbaf..f39540f 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -11,7 +11,7 @@ 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, WebPageCookiesSetter, BrowserCookiesSetter from .scroller import PageScroller from .._base.base import BasePage from .._base.browser import Browser @@ -40,6 +40,7 @@ class BasePageSetter(object): class BrowserBaseSetter(BasePageSetter): + _cookies_setter: Optional[CookiesSetter] = ... @property def load_mode(self) -> LoadMode: ... @@ -49,10 +50,12 @@ class BrowserBaseSetter(BasePageSetter): class BrowserSetter(BasePageSetter): _owner: Browser = ... + _cookies_setter: BrowserCookiesSetter = ... def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... - def cookies(self, cookies): ... + @property + def cookies(self) -> BrowserCookiesSetter: ... def auto_handle_alert(self, on_off: bool = True, accept: bool = True): ... @@ -64,9 +67,10 @@ class BrowserSetter(BasePageSetter): class ChromiumBaseSetter(BasePageSetter): - def __init__(self, owner): - self._owner: ChromiumBase = ... - self._cookies_setter: CookiesSetter = ... + _owner: ChromiumBase = ... + _cookies_setter: CookiesSetter = ... + + def __init__(self, owner): ... @property def load_mode(self) -> LoadMode: ... diff --git a/DrissionPage/_units/states.py b/DrissionPage/_units/states.py index 763e27f..4a75c1c 100644 --- a/DrissionPage/_units/states.py +++ b/DrissionPage/_units/states.py @@ -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 From cc3ff505cfad0f305a566c7ceee86046968b7e2d Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 1 Jul 2024 17:22:58 +0800 Subject: [PATCH 008/114] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E6=96=87=E4=BB=B6=E7=A7=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_units/downloader.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/DrissionPage/_units/downloader.py b/DrissionPage/_units/downloader.py index 9e190c4..48a5095 100644 --- a/DrissionPage/_units/downloader.py +++ b/DrissionPage/_units/downloader.py @@ -56,7 +56,7 @@ class DownloadManager(object): 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) + behavior='allowAndName', eventsEnabled=True) if 'error' in r: print('浏览器版本太低无法使用下载管理功能。') self._running = True @@ -203,7 +203,6 @@ class DownloadManager(object): def _onDownloadProgress(self, **kwargs): """下载状态变化时执行""" - # print(kwargs) if kwargs['guid'] in self._missions: mission = self._missions[kwargs['guid']] if kwargs['state'] == 'inProgress': @@ -219,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}')) - move(form_path, to_path) + 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' From 94a4e6871f15d3c2fe960a252e742842b8575d1a Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 2 Jul 2024 00:05:14 +0800 Subject: [PATCH 009/114] =?UTF-8?q?=E8=AE=BE=E7=BD=AEcookies=E6=97=B6?= =?UTF-8?q?=E5=BF=BD=E7=95=A5=E8=BF=87=E6=9C=9F=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_functions/cookies.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DrissionPage/_functions/cookies.py b/DrissionPage/_functions/cookies.py index fad305d..722a52f 100644 --- a/DrissionPage/_functions/cookies.py +++ b/DrissionPage/_functions/cookies.py @@ -127,8 +127,7 @@ def set_tab_cookies(page, cookies): if cookie.get('domain', None): try: page._run_cdp_loaded('Network.setCookie', **cookie) - if is_cookie_in_driver(page, cookie): - continue + continue except Exception: pass From 503b3f1d7051fe47aee1d5b123bc7075ab27bfcb Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 2 Jul 2024 13:59:50 +0800 Subject: [PATCH 010/114] =?UTF-8?q?=E9=87=8D=E6=9E=84ChromiumPage=E5=92=8C?= =?UTF-8?q?WebPage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 8 +- DrissionPage/__init__.pyi | 17 +++ DrissionPage/_base/browser.py | 5 + DrissionPage/_base/browser.pyi | 3 + DrissionPage/_pages/chromium_base.py | 4 + DrissionPage/_pages/chromium_base.pyi | 5 +- DrissionPage/_pages/chromium_page.py | 189 +++----------------------- DrissionPage/_pages/chromium_page.pyi | 9 -- DrissionPage/_pages/web_page.py | 43 +----- DrissionPage/_pages/web_page.pyi | 1 - 10 files changed, 61 insertions(+), 223 deletions(-) create mode 100644 DrissionPage/__init__.pyi diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 66b3008..9660a96 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -10,8 +10,8 @@ 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.web_page import WebPage +# 即将废弃 +from ._pages.chromium_page import ChromiumPage +from ._pages.web_page import WebPage -__all__ = ['Browser', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] -__version__ = '4.0.5.3' +__version__ = '4.1.0.0b0' diff --git a/DrissionPage/__init__.pyi b/DrissionPage/__init__.pyi new file mode 100644 index 0000000..437adc7 --- /dev/null +++ b/DrissionPage/__init__.pyi @@ -0,0 +1,17 @@ +# -*- 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 Browser +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.web_page import WebPage + +__all__ = ['WebPage', 'ChromiumPage', 'Browser', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] +__version__: str = ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 93b85ae..9d232f0 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -126,6 +126,11 @@ class Browser(object): """返回timeouts设置""" return self._timeouts + @property + def load_mode(self): + """返回加载模式""" + return self._load_mode + @property def download_path(self): """返回默认下载路径""" diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index fbe2d65..82b5611 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -65,6 +65,9 @@ class Browser(object): @property def timeouts(self) -> Timeout: ... + @property + def load_mode(self) -> str: ... + @property def download_path(self) -> str: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 285bbef..27b2e3c 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -1158,6 +1158,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信息的类""" diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index efde024..6653a38 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -34,7 +34,7 @@ class ChromiumBase(BasePage): def __init__(self, browser: Browser, tab_id: str = None): - self._tab:Union[ChromiumTab, MixTab, ChromiumFrame] = ... + self._tab: Union[ChromiumTab, MixTab, ChromiumFrame] = ... self._browser: Browser = ... self._driver: Driver = ... self._frame_id: str = ... @@ -277,6 +277,9 @@ class Timeout(object): self.page_load: float = ... self.script: float = ... + @property + def as_dict(self) -> dict: ... + class Alert(object): diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 76eec26..a8bfda8 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -5,22 +5,13 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from pathlib import Path -from time import sleep, perf_counter - -from requests import Session +from time import sleep 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 .._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): @@ -56,45 +47,18 @@ class ChromiumPage(ChromiumBase): self._created = True self.tab = self - super().__init__(self.browser.address, tab_id) + super().__init__(self.browser, tab_id) self._type = 'ChromiumPage' 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(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): @@ -131,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): @@ -141,7 +105,7 @@ class ChromiumPage(ChromiumBase): @property def browser_version(self): """返回所控制的浏览器版本号""" - return self._browser_version + return self._browser.version def save(self, path=None, name=None, as_pdf=False, **kwargs): """把当前页面保存为文件,如果path和name参数都为None,只返回文本 @@ -162,34 +126,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.browser, 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,返回它们组成的列表 @@ -199,10 +136,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.browser, 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): """新建一个标签页 @@ -212,10 +146,7 @@ class ChromiumPage(ChromiumBase): :param new_context: 是否创建新的上下文 :return: 新标签页对象 """ - tab = ChromiumTab(self.browser, 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 close(self): """关闭Page管理的标签页""" @@ -227,32 +158,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): """关闭浏览器 @@ -264,69 +170,8 @@ class ChromiumPage(ChromiumBase): def _on_disconnect(self): """浏览器退出时执行""" - ChromiumPage._PAGES.pop(self._browser_id, None) + print('kkk') + ChromiumPage._PAGES.pop(self._browser.id, None) def __repr__(self): return f'' - - -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}' diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 2aab4c1..335a604 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -110,12 +110,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: ... diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index e993f35..90f3790 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -6,7 +6,6 @@ @License : BSD 3-Clause. """ from .chromium_page import ChromiumPage -from .chromium_tab import MixTab from .session_page import SessionPage from .._base.base import BasePage from .._configs.chromium_options import ChromiumOptions @@ -287,17 +286,16 @@ class WebPage(SessionPage, ChromiumPage, BasePage): return 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时,后面几个参数无效 @@ -308,29 +306,8 @@ class WebPage(SessionPage, ChromiumPage, BasePage): :param as_id: 是否返回标签页id而不是标签页对象 :return: WebPageTab对象 """ - 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, MixTab): - 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 MixTab(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,返回它们组成的列表 @@ -340,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 [MixTab(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): """新建一个标签页 @@ -353,10 +327,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): :param new_context: 是否创建新的上下文 :return: 新标签页对象 """ - tab = MixTab(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及浏览器""" diff --git a/DrissionPage/_pages/web_page.pyi b/DrissionPage/_pages/web_page.pyi index 4adce01..1b0e28c 100644 --- a/DrissionPage/_pages/web_page.pyi +++ b/DrissionPage/_pages/web_page.pyi @@ -121,7 +121,6 @@ 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]: ... From 231cd0724bf92685e962cf8252d13597f669f6e4 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 3 Jul 2024 16:44:19 +0800 Subject: [PATCH 011/114] =?UTF-8?q?4.1.0.0b1=E4=BC=98=E5=8C=96input()?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=9B=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?Frame=E5=AF=B9=E8=B1=A1=E6=BB=9A=E5=8A=A8=E5=A1=AB=E5=85=A5tupl?= =?UTF-8?q?e=E5=AE=9A=E4=BD=8D=E7=AC=A6=E6=8A=A5=E9=94=99=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 2 + DrissionPage/_elements/chromium_element.py | 6 +- DrissionPage/_functions/cookies.py | 2 + DrissionPage/_functions/keys.py | 276 ++++++++++----------- DrissionPage/_functions/keys.pyi | 12 +- DrissionPage/_units/actions.py | 49 ++-- DrissionPage/_units/actions.pyi | 20 +- DrissionPage/_units/scroller.py | 2 +- 8 files changed, 170 insertions(+), 199 deletions(-) diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index ccd0e5b..d4f8d78 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -80,6 +80,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 diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 9c0b5cd..7326e75 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -748,7 +748,11 @@ class ChromiumElement(DrissionElement): else: self._input_focus() - input_text_or_keys(self.owner, vals) + 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): """清空元素文本 diff --git a/DrissionPage/_functions/cookies.py b/DrissionPage/_functions/cookies.py index 722a52f..be9d374 100644 --- a/DrissionPage/_functions/cookies.py +++ b/DrissionPage/_functions/cookies.py @@ -127,6 +127,8 @@ def set_tab_cookies(page, cookies): 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 diff --git a/DrissionPage/_functions/keys.py b/DrissionPage/_functions/keys.py index edde4d9..2e9313a 100644 --- a/DrissionPage/_functions/keys.py +++ b/DrissionPage/_functions/keys.py @@ -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,65 +360,63 @@ 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} - + data = make_input_data(modifier, key) + if data: page._run_cdp('Input.dispatchKeyEvent', **data) data['type'] = 'keyUp' page._run_cdp('Input.dispatchKeyEvent', **data) diff --git a/DrissionPage/_functions/keys.pyi b/DrissionPage/_functions/keys.pyi index a06c8be..c9254d1 100644 --- a/DrissionPage/_functions/keys.pyi +++ b/DrissionPage/_functions/keys.pyi @@ -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: ... diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index 6942cde..5f3ef99 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -7,9 +7,8 @@ """ from time import sleep, perf_counter -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 -from ..errors import AlertExistsError class Actions: @@ -256,8 +255,9 @@ class Actions: self.modifier |= modifierBit.get(key, 0) return self - data = self._get_key_data(key, 'keyDown') - data['_ignore'] = AlertExistsError + data = make_input_data(self.modifier, key, False) + if not data: + raise ValueError(f'没有这个按键:{key}') self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self @@ -271,8 +271,9 @@ class Actions: self.modifier ^= modifierBit.get(key, 0) return self - data = self._get_key_data(key, 'keyUp') - data['_ignore'] = AlertExistsError + data = make_input_data(self.modifier, key, True) + if not data: + raise ValueError(f'没有这个按键:{key}') self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self @@ -284,12 +285,15 @@ class Actions: modifiers = [] for i in keys: for character in i: - if character in keyDefinitions: - self.key_down(character) - if character in ('\ue009', '\ue008', '\ue00a', '\ue03d'): - modifiers.append(character) - else: - self.key_up(character) + if character in ('\ue009', '\ue008', '\ue00a', '\ue03d'): + self.modifier |= modifierBit.get(character, 0) + modifiers.append(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) @@ -315,27 +319,6 @@ 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): """绝对坐标转换为视口坐标""" diff --git a/DrissionPage/_units/actions.pyi b/DrissionPage/_units/actions.pyi index bc610eb..ba07789 100644 --- a/DrissionPage/_units/actions.pyi +++ b/DrissionPage/_units/actions.pyi @@ -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', @@ -103,7 +103,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: ... diff --git a/DrissionPage/_units/scroller.py b/DrissionPage/_units/scroller.py index 6ec48c4..edf0e82 100644 --- a/DrissionPage/_units/scroller.py +++ b/DrissionPage/_units/scroller.py @@ -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) From c26a3d78b2ee3aba23186f6823d045d04f19bacf Mon Sep 17 00:00:00 2001 From: g1879 Date: Thu, 4 Jul 2024 14:19:25 +0800 Subject: [PATCH 012/114] =?UTF-8?q?=E6=94=B9=E8=BF=9Bnew=5Ftab()=EF=BC=8C?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitee/ISSUE_TEMPLATE.zh-CN.md | 3 +++ DrissionPage/_base/browser.py | 11 +++++++++-- DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_pages/chromium_base.py | 5 +++-- DrissionPage/_pages/chromium_base.pyi | 2 +- DrissionPage/_pages/chromium_page.py | 1 - DrissionPage/_pages/chromium_page.pyi | 1 - DrissionPage/_pages/chromium_tab.pyi | 2 +- DrissionPage/_pages/session_page.pyi | 2 +- DrissionPage/_pages/web_page.py | 2 +- DrissionPage/_units/clicker.py | 8 ++++---- DrissionPage/_units/rect.py | 6 ++---- DrissionPage/_units/setter.py | 12 ++++++------ DrissionPage/_units/states.py | 6 +++--- 14 files changed, 35 insertions(+), 28 deletions(-) diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md index c6e48e4..69c29c4 100644 --- a/.gitee/ISSUE_TEMPLATE.zh-CN.md +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -9,3 +9,6 @@ 2. 请附上代码和报错信息(如有) 3. DrissionPage、浏览器、python版本号是多少? 4. 有什么意见建议? + +请在下方写正文,不要把内容插入到上面的问题中。 +--- \ No newline at end of file diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 9d232f0..e5bc502 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -26,7 +26,7 @@ from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager from .._units.setter import BrowserSetter from .._units.waiter import BrowserWaiter -from ..errors import BrowserConnectError +from ..errors import BrowserConnectError, CDPError from ..errors import PageDisconnectedError __ERROR__ = 'error' @@ -219,7 +219,14 @@ class Browser(object): if tab: kwargs['browserContextId'] = tab - tab = self._run_cdp('Target.createTarget', **kwargs)['targetId'] + try: + tab = self._run_cdp('Target.createTarget', **kwargs)['targetId'] + except CDPError: + url = url or 'https://#' + tab = self.get_tab().add_ele(('a', {'href': url, + 'target': '_blank'})).click.for_new_tab(by_js=True) + return tab + while tab not in self._drivers: sleep(.1) tab = obj(self, tab) diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 82b5611..1da87c4 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -11,7 +11,7 @@ from typing import List, Optional, Set, Dict, Union, Tuple from .driver import BrowserDriver, Driver from .._configs.chromium_options import ChromiumOptions from .._configs.session_options import SessionOptions -from .._functions.web import CookiesList +from .._functions.cookies import CookiesList from .._pages.chromium_base import Timeout from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 27b2e3c..01e908a 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -151,7 +151,7 @@ class ChromiumBase(BasePage): timeout = end_time - perf_counter() timeout = 1 if timeout <= 1 else timeout self._root_id = self._run_cdp('DOM.resolveNode', backendNodeId=b_id, - _timeout=timeout)['object']['objectId'] + _timeout=timeout)['object']['objectId'] result = True break @@ -842,7 +842,7 @@ class ChromiumBase(BasePage): :return: 添加的脚本的id """ js_id = self._run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script, - includeCommandLineAPI=True)['identifier'] + includeCommandLineAPI=True)['identifier'] self._init_jss.append(js_id) return js_id @@ -1185,6 +1185,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') diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 6653a38..4d3f739 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -14,8 +14,8 @@ from .._base.browser import Browser 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 .._functions.web import CookiesList from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage from .._units.actions import Actions diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index a8bfda8..3aca524 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -170,7 +170,6 @@ class ChromiumPage(ChromiumBase): def _on_disconnect(self): """浏览器退出时执行""" - print('kkk') ChromiumPage._PAGES.pop(self._browser.id, None) def __repr__(self): diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 335a604..77e4924 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -6,7 +6,6 @@ @License : BSD 3-Clause. """ from pathlib import Path -from threading import Lock from typing import Union, Tuple, List, Optional from .._base.browser import Browser diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index 072de9f..288fdfe 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -16,8 +16,8 @@ from .session_page import SessionPage from .._base.browser import Browser from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement +from .._functions.cookies import CookiesList from .._functions.elements import SessionElementsList, ChromiumElementsList -from .._functions.web import CookiesList from .._units.rect import TabRect from .._units.setter import TabSetter, WebPageTabSetter from .._units.waiter import TabWaiter diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 385ac2b..2324ac0 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -14,8 +14,8 @@ 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 .._functions.web import CookiesList from .._units.setter import SessionPageSetter diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index 90f3790..c79ae8d 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -25,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秒 diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 12e85f3..f18014a 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -88,7 +88,7 @@ class Clicker(object): y = rect[0][0] + 3 try: r = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y), - includeUserAgentShadowDOM=True, ignorePointerEventsNone=True) + includeUserAgentShadowDOM=True, ignorePointerEventsNone=True) if r['backendNodeId'] != self._ele._backend_id: vx, vy = self._ele.rect.viewport_midpoint else: @@ -166,7 +166,7 @@ class Clicker(object): 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 + obj = self._ele.tab._browser if new_tab else self._ele.owner._tab if rename or suffix: obj.set.download_file_name(rename, suffix) @@ -205,6 +205,6 @@ class Clicker(object): :return: None """ self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x, - y=client_y, button=button, clickCount=count, _ignore=AlertExistsError) + 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) + y=client_y, button=button, _ignore=AlertExistsError) diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index 2d8601e..68e8683 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -33,7 +33,7 @@ class ElementRect(object): def size(self): """返回元素大小,格式(宽, 高)""" 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'] + nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model']['border'] return border[2] - border[0], border[5] - border[1] @property @@ -101,9 +101,7 @@ class ElementRect(object): :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): """根据视口坐标获取绝对坐标""" diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 04c4660..46353d0 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -173,10 +173,10 @@ class ChromiumBaseSetter(BrowserBaseSetter): 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) + storageId={'storageKey': i, 'isLocalStorage': False}, key=item) else: self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False}, - key=item, value=value) + key=item, value=value) self._owner._run_cdp_loaded('DOMStorage.disable') def local_storage(self, item, value): @@ -189,10 +189,10 @@ class ChromiumBaseSetter(BrowserBaseSetter): 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) + storageId={'storageKey': i, 'isLocalStorage': True}, key=item) else: self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True}, - key=item, value=value) + key=item, value=value) self._owner._run_cdp_loaded('DOMStorage.disable') def upload_files(self, files): @@ -528,11 +528,11 @@ class ChromiumElementSetter(object): """ try: self._ele.owner._run_cdp('DOM.setAttributeValue', - nodeId=self._ele._node_id, name=name, value=str(value)) + nodeId=self._ele._node_id, name=name, value=str(value)) except ElementLostError: self._ele._refresh_id() self._ele.owner._run_cdp('DOM.setAttributeValue', - nodeId=self._ele._node_id, name=name, value=str(value)) + nodeId=self._ele._node_id, name=name, value=str(value)) def property(self, name, value): """设置元素property属性 diff --git a/DrissionPage/_units/states.py b/DrissionPage/_units/states.py index 4a75c1c..0a664b1 100644 --- a/DrissionPage/_units/states.py +++ b/DrissionPage/_units/states.py @@ -43,7 +43,7 @@ class ElementStates(object): """返回元素是否仍在DOM中""" try: return self._ele.owner._run_cdp('DOM.describeNode', - backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 + backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 except ElementLostError: return False @@ -102,7 +102,7 @@ class ShadowRootStates(object): """返回元素是否仍在DOM中""" try: return self._ele.owner._run_cdp('DOM.describeNode', - backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 + backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 except ElementLostError: return False @@ -158,7 +158,7 @@ class FrameStates(object): """返回frame元素是否可用,且里面仍挂载有frame""" try: node = self._frame._target_page._run_cdp('DOM.describeNode', - backendNodeId=self._frame._frame_ele._backend_id)['node'] + backendNodeId=self._frame._frame_ele._backend_id)['node'] except (ElementLostError, PageDisconnectedError): return False return 'frameId' in node From 4114e6826c57cdbe464e97e980df8e98095c9ee5 Mon Sep 17 00:00:00 2001 From: g1879 Date: Thu, 4 Jul 2024 18:11:45 +0800 Subject: [PATCH 013/114] =?UTF-8?q?4.1.0.0b1=E4=BC=98=E5=8C=96new=5Ftab()?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 5 ++--- DrissionPage/_base/browser.py | 7 +++---- DrissionPage/_units/clicker.py | 6 ++++-- DrissionPage/_units/waiter.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 9660a96..027baa7 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -8,10 +8,9 @@ from ._base.browser import Browser 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.session_page import SessionPage from ._pages.web_page import WebPage -__version__ = '4.1.0.0b0' +__version__ = '4.1.0.0b1' diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index e5bc502..53ecc9e 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -222,10 +222,9 @@ class Browser(object): try: tab = self._run_cdp('Target.createTarget', **kwargs)['targetId'] except CDPError: - url = url or 'https://#' - tab = self.get_tab().add_ele(('a', {'href': url, - 'target': '_blank'})).click.for_new_tab(by_js=True) - return tab + 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) diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index f18014a..5f136bc 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -126,7 +126,8 @@ class Clicker(object): tid = self._ele.tab.browser.wait.new_tab(curr_tab=curr_tid) if not tid: raise RuntimeError('没有出现新标签页。') - return self._ele.tab.browser.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值时点击元素中间点 @@ -194,7 +195,8 @@ class Clicker(object): tid = self._ele.tab.browser.wait.new_tab(timeout=timeout, curr_tab=curr_tid) if not tid: raise RuntimeError('没有出现新标签页。') - return self._ele.tab.browser.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): """实施点击 diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 9a26ecc..f471114 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -57,7 +57,7 @@ class BrowserWaiter(OriginWaiter): def new_tab(self, timeout=None, curr_tab=None, raise_err=None): """等待新标签页出现 :param timeout: 超时时间(秒),为None则使用页面对象timeout属性 - :param curr_tab: 指定当前最新的tab id,为None自动获取 + :param curr_tab: 指定当前最新的tab id,用于判断新tab出现,为None自动获取 :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :return: 等到新标签页返回其id,否则返回False """ From c6e3e0b71fa98bffaa3c01d943a6a09fa17a37b7 Mon Sep 17 00:00:00 2001 From: g1879 Date: Thu, 4 Jul 2024 23:58:01 +0800 Subject: [PATCH 014/114] =?UTF-8?q?ChromiumOption=E5=A2=9E=E5=8A=A0new=5Fe?= =?UTF-8?q?nv()=EF=BC=9Bauto=5Fport()=E5=88=A0=E9=99=A4tmp=5Fpath=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=9Bini=E5=A2=9E=E5=8A=A0new=5Fenv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 16 ++++++++++----- DrissionPage/_base/browser.pyi | 5 ++++- DrissionPage/_base/driver.py | 2 +- DrissionPage/_configs/chromium_options.py | 22 ++++++++++++-------- DrissionPage/_configs/chromium_options.pyi | 4 +++- DrissionPage/_configs/configs.ini | 1 + DrissionPage/_configs/options_manage.py | 1 + DrissionPage/_functions/browser.py | 24 ++++++++++++---------- DrissionPage/_functions/tools.py | 10 ++++----- DrissionPage/_units/actions.py | 2 ++ 10 files changed, 55 insertions(+), 32 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 53ecc9e..912e652 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -41,12 +41,13 @@ class Browser(object): :param session_options: 使用双模Tab时使用的默认Session配置,为True使用ini文件配置 """ opt = handle_options(addr_or_opts) - is_headless, browser_id = run_browser(opt) + is_headless, browser_id, is_exists = run_browser(opt) if browser_id in cls._BROWSERS: return cls._BROWSERS[browser_id] 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 @@ -74,11 +75,11 @@ class Browser(object): 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.user_data_path = self._chromium_options.user_data_path self.address = self._chromium_options.address self._driver = BrowserDriver(self.id, 'browser', self.address, self) - if self.is_headless != self._chromium_options.is_headless: + 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() @@ -111,6 +112,11 @@ class Browser(object): 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""" @@ -537,7 +543,7 @@ def handle_options(addr_or_opts): def run_browser(chromium_options): """连接浏览器""" - connect_browser(chromium_options) + is_exists = connect_browser(chromium_options) try: s = Session() s.trust_env = False @@ -553,4 +559,4 @@ def run_browser(chromium_options): raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。') except: raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') - return is_headless, browser_id + return is_headless, browser_id, is_exists diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 1da87c4..b8fcf75 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -26,7 +26,6 @@ class Browser(object): retry_times: int = ... retry_interval: float = ... is_headless: bool = ... - user_data_path: str = ... _BROWSERS: dict = ... _chromium_options: ChromiumOptions = ... @@ -44,6 +43,7 @@ class Browser(object): _timeouts: Timeout = ... _load_mode: str = ... _download_path: str = ... + _is_exists: bool = ... def __new__(cls, addr_or_opts: Union[str, int, ChromiumOptions] = None, @@ -56,6 +56,9 @@ class Browser(object): def _run_cdp(self, cmd, **cmd_args) -> dict: ... + @property + def user_data_path(self) -> str: ... + @property def process_id(self) -> Optional[int]: ... diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 8f421f5..0cd3471 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -192,7 +192,7 @@ 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']['data']} else: return result['result'] diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 994639d..0d7bed9 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -33,8 +33,8 @@ 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', '.') or '.' self._tmp_path = om.paths.get('tmp_path', None) or None @@ -47,6 +47,7 @@ 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 @@ -364,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: 开或关 @@ -504,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 (9600, 19600) - 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 @@ -553,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}')) # 设置代理 diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index 42013b5..10ba9a8 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -26,6 +26,7 @@ class ChromiumOptions(object): _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 = ... @@ -136,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: ... @@ -166,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: ... diff --git a/DrissionPage/_configs/configs.ini b/DrissionPage/_configs/configs.ini index f2de400..9e516e5 100644 --- a/DrissionPage/_configs/configs.ini +++ b/DrissionPage/_configs/configs.ini @@ -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'} diff --git a/DrissionPage/_configs/options_manage.py b/DrissionPage/_configs/options_manage.py index f9c85f4..6638c50 100644 --- a/DrissionPage/_configs/options_manage.py +++ b/DrissionPage/_configs/options_manage.py @@ -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" diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index 4211748..2b91d53 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -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 @@ -33,7 +34,9 @@ def connect_browser(option): return True # ----------创建浏览器进程---------- - 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: @@ -42,10 +45,8 @@ 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) @@ -59,23 +60,24 @@ def get_launch_args(opt): """ # ----------处理arguments----------- result = set() - has_user_path = False + 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 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}') + user_path = path.absolute() + opt.set_user_data_path(user_path) + result.add(f'--user-data-dir={user_path}') result = list(result) @@ -86,7 +88,7 @@ def get_launch_args(opt): ext = f'--load-extension={ext}' result.append(ext) - return result + return result, user_path def set_prefs(opt): diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 84a2e1f..cfe4c1e 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -28,17 +28,17 @@ class PortFinder(object): :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 str(self.tmp_dir.absolute()) not in PortFinder.checked_paths: for i in self.tmp_dir.iterdir(): - if i.is_dir() and i.stem.startswith('AutoPort') and not port_is_using('127.0.0.1', i.name[8:]): + 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 @@ -47,7 +47,7 @@ class PortFinder(object): PortFinder.used_port.clear() PortFinder.prev_time = perf_counter() if scope in (True, None): - scope = (9600, 19600) + scope = (9600, 59600) msx_times = scope[1] - scope[0] times = 0 while times < msx_times: @@ -55,7 +55,7 @@ class PortFinder(object): port = randint(*scope) if port in PortFinder.used_port or port_is_using('127.0.0.1', port): continue - path = self.tmp_dir / f'AutoPort{port}' + path = self.tmp_dir / str(port) if path.exists(): try: rmtree(path) diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index 5f3ef99..72f90b8 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -283,6 +283,8 @@ 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 ('\ue009', '\ue008', '\ue00a', '\ue03d'): From 8456f7ad39056b3e37c411104d023ab99fe343e2 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 5 Jul 2024 11:27:15 +0800 Subject: [PATCH 015/114] =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E9=93=BE=E5=88=A0?= =?UTF-8?q?=E9=99=A4db=5Fclick()=EF=BC=9B=E5=90=84=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=A2=9E=E5=8A=A0times=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=9BTab=E5=AF=B9=E8=B1=A1=E5=A2=9E=E5=8A=A0rect.scrollbar?= =?UTF-8?q?=5Fposition=E5=B1=9E=E6=80=A7=EF=BC=9B=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=82=B9=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/driver.py | 2 +- DrissionPage/_elements/chromium_element.py | 6 ++--- DrissionPage/_functions/web.py | 10 +++----- DrissionPage/_units/actions.py | 23 +++++++---------- DrissionPage/_units/actions.pyi | 8 +++--- DrissionPage/_units/clicker.py | 30 ++++++++++++---------- DrissionPage/_units/rect.py | 6 +++++ DrissionPage/_units/rect.pyi | 3 +++ 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 0cd3471..22b2b31 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -192,7 +192,7 @@ 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, 'data': result['error']['data']} + 'method': _method, 'args': kwargs, 'data': result['error'].get('data')} else: return result['result'] diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 7326e75..091593f 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -19,7 +19,7 @@ from .._base.base import DrissionElement, BaseElement from .._functions.elements import ChromiumElementsList from .._functions.keys import input_text_or_keys from .._functions.locator import get_loc, locator_to_tuple -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 @@ -787,9 +787,7 @@ class ChromiumElement(DrissionElement): :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): """拖拽当前元素到相对位置 diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index 7c1854d..c9b1987 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -107,12 +107,12 @@ def location_in_viewport(page, loc_x, loc_y): 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 @@ -122,11 +122,7 @@ def offset_scroll(ele, offset_x, offset_y): 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 def make_absolute_link(link, baseURI=None): diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index 72f90b8..e920529 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -94,36 +94,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): diff --git a/DrissionPage/_units/actions.pyi b/DrissionPage/_units/actions.pyi index ba07789..ad8f54a 100644 --- a/DrissionPage/_units/actions.pyi +++ b/DrissionPage/_units/actions.pyi @@ -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: ... diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 5f136bc..b748ad9 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -53,7 +53,7 @@ class Clicker(object): try: self._ele.scroll.to_see() if self._ele.states.is_enabled and self._ele.states.is_displayed: - rect = self._ele.rect.viewport_corners + rect = self._ele.rect.corners can_click = True except NoRectError: if by_js is False: @@ -90,12 +90,12 @@ class Clicker(object): 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 + vx, vy = self._ele.rect.midpoint else: - vx, vy = self._ele.rect.viewport_click_point + vx, vy = self._ele.rect.click_point except CDPError: - vx, vy = self._ele.rect.viewport_midpoint + vx, vy = self._ele.rect.midpoint self._click(vx, vy) return True @@ -110,7 +110,7 @@ class Clicker(object): def right(self): """右键单击""" self._ele.owner.scroll.to_see(self._ele) - x, y = self._ele.rect.viewport_click_point + x, y = self._ele.rect.click_point self._click(x, y, 'right') def middle(self, get_tab=True): @@ -119,7 +119,7 @@ class Clicker(object): :return: Tab对象或None """ self._ele.owner.scroll.to_see(self._ele) - x, y = self._ele.rect.viewport_click_point + x, y = self._ele.rect.click_point curr_tid = self._ele.tab.browser.tab_ids[0] self._click(x, y, 'middle') if get_tab: @@ -198,15 +198,19 @@ class Clicker(object): 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 _click(self, loc_x, loc_y, button='left', count=1): """实施点击 - :param client_x: 视口中的x坐标 - :param client_y: 视口中的y坐标 + :param loc_x: 绝对x坐标 + :param loc_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=.1) + sx, sy = self._ele.owner.rect.scrollbar_position + x = loc_x - sx + y = loc_y - sy + self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=x, + y=y, button=button, clickCount=count, _ignore=AlertExistsError) + self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=x, + y=y, button=button, _ignore=AlertExistsError) diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index 68e8683..43bfaef 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -176,6 +176,12 @@ class TabRect(object): w, h = r.split(' ') return int(w), int(h) + @property + def scrollbar_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') diff --git a/DrissionPage/_units/rect.pyi b/DrissionPage/_units/rect.pyi index c1df2d5..dff526d 100644 --- a/DrissionPage/_units/rect.pyi +++ b/DrissionPage/_units/rect.pyi @@ -89,6 +89,9 @@ class TabRect(object): @property def viewport_size_with_scrollbar(self) -> Tuple[int, int]: ... + @property + def scrollbar_position(self) -> Tuple[int, int]: ... + def _get_page_rect(self) -> dict: ... def _get_window_rect(self) -> dict: ... From 07de9eb1315f1a32910a44268d53aba0da53e78f Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 5 Jul 2024 17:49:53 +0800 Subject: [PATCH 016/114] =?UTF-8?q?=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_units/rect.py | 6 ++++++ DrissionPage/_units/setter.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index 43bfaef..8250642 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -236,3 +236,9 @@ class FrameRect(object): def viewport_corners(self): """返回元素四个角视口坐标,顺序:左上、右上、右下、左下""" return self._frame.frame_ele.rect.viewport_corners + + @property + def scrollbar_position(self): + """返回滚动条位置,格式:(x, y)""" + r = self._frame._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] + return r['pageX'], r['pageY'] diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 46353d0..25a7571 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -212,7 +212,7 @@ class ChromiumBaseSetter(BrowserBaseSetter): def headers(self, headers) -> None: """设置固定发送的headers - :param headers: dict格式的headers数据 + :param headers: dict格式的headers数据,或从浏览器复制的headers文本(\n分行) :return: None """ self._owner._run_cdp('Network.enable') From 89af82dc2f592fa30a36e317b07fb2107c36ff8a Mon Sep 17 00:00:00 2001 From: g1879 Date: Sat, 6 Jul 2024 09:42:45 +0800 Subject: [PATCH 017/114] =?UTF-8?q?rect=E5=A2=9E=E5=8A=A0scroll=5Fposition?= =?UTF-8?q?=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_functions/web.py | 10 ++++++--- DrissionPage/_functions/web.pyi | 4 ++-- DrissionPage/_units/clicker.py | 37 ++++++++++++++++---------------- DrissionPage/_units/clicker.pyi | 7 +++++- DrissionPage/_units/rect.py | 26 +++++++++++++--------- DrissionPage/_units/rect.pyi | 8 ++++++- DrissionPage/_units/scroller.py | 10 ++++----- DrissionPage/_units/scroller.pyi | 6 +++--- 8 files changed, 64 insertions(+), 44 deletions(-) diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index c9b1987..8527bb4 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -107,12 +107,12 @@ def location_in_viewport(page, loc_x, loc_y): 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 @@ -122,7 +122,11 @@ def offset_scroll(ele, offset_x, offset_y): 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) - return lx, ly + 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 lx, ly, cx, cy def make_absolute_link(link, baseURI=None): diff --git a/DrissionPage/_functions/web.pyi b/DrissionPage/_functions/web.pyi index fdd2d76..2f5e702 100644 --- a/DrissionPage/_functions/web.pyi +++ b/DrissionPage/_functions/web.pyi @@ -6,7 +6,7 @@ @License : BSD 3-Clause. """ from pathlib import Path -from typing import Union, Optional +from typing import Union, Optional, Tuple from .._base.base import DrissionElement, BaseParser from .._elements.chromium_element import ChromiumElement @@ -24,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: ... diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index b748ad9..15a6b9d 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -53,7 +53,7 @@ class Clicker(object): try: self._ele.scroll.to_see() if self._ele.states.is_enabled and self._ele.states.is_displayed: - rect = self._ele.rect.corners + rect = self._ele.rect.viewport_corners can_click = True except NoRectError: if by_js is False: @@ -90,14 +90,17 @@ class Clicker(object): 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.midpoint + vx, vy = self._ele.rect.viewport_midpoint + lx, ly = self._ele.rect._get_page_coord(vx, vy) else: - vx, vy = self._ele.rect.click_point + 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.midpoint + vx, vy = self._ele.rect.viewport_midpoint + lx, ly = self._ele.rect._get_page_coord(vx, vy) - self._click(vx, vy) + self._click(lx, ly, vx, vy) return True if by_js is not False: @@ -110,8 +113,7 @@ class Clicker(object): def right(self): """右键单击""" self._ele.owner.scroll.to_see(self._ele) - x, y = self._ele.rect.click_point - self._click(x, y, 'right') + self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='right') def middle(self, get_tab=True): """中键单击,默认返回新出现的tab对象 @@ -119,9 +121,8 @@ class Clicker(object): :return: Tab对象或None """ self._ele.owner.scroll.to_see(self._ele) - x, y = self._ele.rect.click_point curr_tid = self._ele.tab.browser.tab_ids[0] - self._click(x, y, 'middle') + self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='middle') if get_tab: tid = self._ele.tab.browser.wait.new_tab(curr_tab=curr_tid) if not tid: @@ -142,8 +143,7 @@ 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) + self._click(*offset_scroll(self._ele, offset_x, offset_y), button=button, count=count) def multi(self, times=2): """多次点击 @@ -198,19 +198,18 @@ class Clicker(object): 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, loc_x, loc_y, button='left', count=1): + def _click(self, loc_x, loc_y, view_x, view_y, button='left', count=1): """实施点击 :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.actions.move_to((loc_x, loc_y), duration=.1) - sx, sy = self._ele.owner.rect.scrollbar_position - x = loc_x - sx - y = loc_y - sy - self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=x, - y=y, button=button, clickCount=count, _ignore=AlertExistsError) - self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=x, - y=y, button=button, _ignore=AlertExistsError) + 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) diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index 5031e27..af3c7b4 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -45,4 +45,9 @@ class Clicker(object): 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 _click(self, loc_x: float, + loc_y: float, + view_x: float, + view_y: float, + button: str = 'left', + count: int = 1) -> None: ... diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index 8250642..f94a94b 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -39,20 +39,17 @@ class ElementRect(object): @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): @@ -96,6 +93,13 @@ class ElementRect(object): 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 @@ -177,7 +181,7 @@ class TabRect(object): return int(w), int(h) @property - def scrollbar_position(self): + def scroll_position(self): """返回滚动条位置,格式:(x, y)""" r = self._get_page_rect()['visualViewport'] return r['pageX'], r['pageY'] @@ -238,7 +242,9 @@ class FrameRect(object): return self._frame.frame_ele.rect.viewport_corners @property - def scrollbar_position(self): + def scroll_position(self): """返回滚动条位置,格式:(x, y)""" - r = self._frame._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] - return r['pageX'], r['pageY'] + 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) diff --git a/DrissionPage/_units/rect.pyi b/DrissionPage/_units/rect.pyi index dff526d..0b2c767 100644 --- a/DrissionPage/_units/rect.pyi +++ b/DrissionPage/_units/rect.pyi @@ -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]: ... @@ -90,7 +93,7 @@ class TabRect(object): def viewport_size_with_scrollbar(self) -> Tuple[int, int]: ... @property - def scrollbar_position(self) -> Tuple[int, int]: ... + def scroll_position(self) -> Tuple[int, int]: ... def _get_page_rect(self) -> dict: ... @@ -121,3 +124,6 @@ class FrameRect(object): @property def viewport_corners(self) -> Tuple[Tuple[float, float], ...]: ... + + @property + def scroll_position(self) -> Tuple[float, float]: ... diff --git a/DrissionPage/_units/scroller.py b/DrissionPage/_units/scroller.py index edf0e82..bc5f334 100644 --- a/DrissionPage/_units/scroller.py +++ b/DrissionPage/_units/scroller.py @@ -16,11 +16,11 @@ 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) + js = js.format(self._t1, self._t2, self._t2) self._driver._run_js(js) self._wait_scrolled() @@ -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): """滚动页面直到元素可见 @@ -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): """滚动页面直到元素可见 diff --git a/DrissionPage/_units/scroller.pyi b/DrissionPage/_units/scroller.pyi index 7e8b7c6..e48c976 100644 --- a/DrissionPage/_units/scroller.pyi +++ b/DrissionPage/_units/scroller.pyi @@ -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): From 7fdd976854163faee370766496ac835ac397a3b4 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 7 Jul 2024 09:18:11 +0800 Subject: [PATCH 018/114] =?UTF-8?q?Browser=E6=94=B9=E5=90=8D=E4=B8=BAChrom?= =?UTF-8?q?ium?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/__init__.pyi | 4 ++-- DrissionPage/_base/browser.py | 6 +++--- DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_base/driver.pyi | 8 ++++---- DrissionPage/_functions/cookies.pyi | 4 ++-- DrissionPage/_functions/web.py | 4 +++- DrissionPage/_pages/chromium_base.py | 2 +- DrissionPage/_pages/chromium_base.pyi | 8 ++++---- DrissionPage/_pages/chromium_page.py | 4 ++-- DrissionPage/_pages/chromium_page.pyi | 6 +++--- DrissionPage/_pages/chromium_tab.py | 3 ++- DrissionPage/_pages/chromium_tab.pyi | 8 ++++---- DrissionPage/_units/cookies_setter.pyi | 6 +++--- DrissionPage/_units/downloader.pyi | 6 +++--- DrissionPage/_units/setter.pyi | 14 +++++++------- DrissionPage/_units/waiter.pyi | 4 ++-- DrissionPage/common.py | 11 ++++++----- 18 files changed, 53 insertions(+), 49 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 027baa7..b29f1e8 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -5,7 +5,7 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from ._base.browser import Browser +from ._base.browser import Chromium from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions # 即将废弃 diff --git a/DrissionPage/__init__.pyi b/DrissionPage/__init__.pyi index 437adc7..055e4fd 100644 --- a/DrissionPage/__init__.pyi +++ b/DrissionPage/__init__.pyi @@ -5,7 +5,7 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from ._base.browser import Browser +from ._base.browser import Chromium from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions from ._pages.session_page import SessionPage @@ -13,5 +13,5 @@ from ._pages.session_page import SessionPage from ._pages.chromium_page import ChromiumPage from ._pages.web_page import WebPage -__all__ = ['WebPage', 'ChromiumPage', 'Browser', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] +__all__ = ['WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] __version__: str = ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 912e652..f3c2a86 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -32,7 +32,7 @@ from ..errors import PageDisconnectedError __ERROR__ = 'error' -class Browser(object): +class Chromium(object): _BROWSERS = {} def __new__(cls, addr_or_opts=None, session_options=None): @@ -61,7 +61,7 @@ class Browser(object): return self._created = True - self._type = 'Browser' + self._type = 'Chromium' self._frames = {} self._drivers = {} @@ -491,7 +491,7 @@ class Browser(object): return r if __ERROR__ not in r else raise_error(r, ignore) def _on_disconnect(self): - Browser._BROWSERS.pop(self.id, None) + 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 diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index b8fcf75..d772f9e 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -19,7 +19,7 @@ from .._units.setter import BrowserSetter from .._units.waiter import BrowserWaiter -class Browser(object): +class Chromium(object): id: str = ... address: str = ... version: str = ... diff --git a/DrissionPage/_base/driver.pyi b/DrissionPage/_base/driver.pyi index b3f44f9..b3e09f0 100644 --- a/DrissionPage/_base/driver.pyi +++ b/DrissionPage/_base/driver.pyi @@ -12,7 +12,7 @@ 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): @@ -67,11 +67,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: ... diff --git a/DrissionPage/_functions/cookies.pyi b/DrissionPage/_functions/cookies.pyi index fca7a77..cfee879 100644 --- a/DrissionPage/_functions/cookies.pyi +++ b/DrissionPage/_functions/cookies.pyi @@ -11,7 +11,7 @@ from typing import Union from requests import Session from requests.cookies import RequestsCookieJar -from .._base.browser import Browser +from .._base.browser import Chromium from .._pages.chromium_base import ChromiumBase @@ -24,7 +24,7 @@ def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, C def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ... -def set_browser_cookies(browser: Browser, 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: ... diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index 8527bb4..c4c6242 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -346,7 +346,9 @@ def format_headers(txt): :param txt: 从浏览器复制的原始文本格式headers :return: dict格式headers """ - if not isinstance(txt, str): + if isinstance(txt, dict): + for k, v in txt.items(): + txt[k] = str(v) return txt headers = {} for header in txt.split('\n'): diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 01e908a..6aea14a 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -41,7 +41,7 @@ class ChromiumBase(BasePage): def __init__(self, browser, tab_id=None): """ - :param browser: Browser + :param browser: Chromium :param tab_id: 要控制的标签页id,不指定默认为激活的 """ super().__init__() diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 4d3f739..3f946c1 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -10,7 +10,7 @@ from typing import Union, Tuple, List, Any, Optional, Literal from .chromium_tab 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 @@ -32,10 +32,10 @@ PIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True] class ChromiumBase(BasePage): def __init__(self, - browser: Browser, + browser: Chromium, tab_id: str = None): self._tab: Union[ChromiumTab, MixTab, ChromiumFrame] = ... - self._browser: Browser = ... + self._browser: Chromium = ... self._driver: Driver = ... self._frame_id: str = ... self._is_reading: bool = ... @@ -102,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: ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 3aca524..7552aea 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -7,7 +7,7 @@ """ from time import sleep -from .._base.browser import Browser +from .._base.browser import Chromium from .._functions.web import save_page from .._pages.chromium_base import ChromiumBase from .._units.setter import ChromiumPageSetter @@ -24,7 +24,7 @@ class ChromiumPage(ChromiumBase): :param tab_id: 要控制的标签页id,不指定默认为激活的 :param timeout: 超时时间(秒) """ - browser = Browser(addr_or_opts=addr_or_opts) + 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'): diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 77e4924..241a30b 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -8,7 +8,7 @@ from pathlib import Path 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 @@ -20,7 +20,7 @@ from .._units.waiter import PageWaiter class ChromiumPage(ChromiumBase): _PAGES: dict = ... tab: ChromiumPage = ... - _browser: Browser = ... + _browser: Chromium = ... _rect: Optional[TabRect] = ... _is_exist: bool = ... @@ -41,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: ... diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index 5b42498..f41ea7e 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -52,6 +52,7 @@ class ChromiumTab(ChromiumBase): def _d_set_runtime_settings(self): """重写设置浏览器运行参数方法""" + print('a') self._timeouts = copy(self.browser.timeouts) self.retry_times = self.browser.retry_times self.retry_interval = self.browser.retry_interval @@ -96,7 +97,7 @@ class ChromiumTab(ChromiumBase): class MixTab(SessionPage, ChromiumTab, BasePage): def __init__(self, browser, tab_id): """ - :param browser: Browser对象 + :param browser: Chromium对象 :param tab_id: 标签页id """ if Settings.singleton_tab_obj and hasattr(self, '_created'): diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index 288fdfe..7f0a234 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -13,7 +13,7 @@ from requests import Session, Response from .chromium_base import ChromiumBase from .chromium_frame import ChromiumFrame from .session_page import SessionPage -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 @@ -26,9 +26,9 @@ from .._units.waiter import TabWaiter class ChromiumTab(ChromiumBase): _TABS: dict = ... - def __new__(cls, browser: Browser, tab_id: str): ... + def __new__(cls, browser: Chromium, tab_id: str): ... - def __init__(self, browser: Browser, tab_id: str): + def __init__(self, browser: Chromium, tab_id: str): self._tab: ChromiumTab = ... self._rect: Optional[TabRect] = ... @@ -70,7 +70,7 @@ class MixTab(SessionPage, ChromiumTab): _has_driver: bool = ... _has_session: bool = ... - def __init__(self, browser: Browser, tab_id: str): ... + def __init__(self, browser: Chromium, tab_id: str): ... def __call__(self, locator: Union[Tuple[str, str], str, ChromiumElement, SessionElement], diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index 1b467b1..dcc4215 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -8,7 +8,7 @@ from http.cookiejar import Cookie, CookieJar from typing import Union -from .._base.browser import Browser +from .._base.browser import Chromium from .._pages.chromium_base import ChromiumBase from .._pages.chromium_tab import MixTab from .._pages.session_page import SessionPage @@ -28,9 +28,9 @@ class CookiesSetter(object): class BrowserCookiesSetter(CookiesSetter): - _owner: Browser = ... + _owner: Chromium = ... - def __init__(self, page: Browser): ... + def __init__(self, page: Chromium): ... class SessionCookiesSetter(object): diff --git a/DrissionPage/_units/downloader.pyi b/DrissionPage/_units/downloader.pyi index 10fd5e3..d9768c4 100644 --- a/DrissionPage/_units/downloader.pyi +++ b/DrissionPage/_units/downloader.pyi @@ -7,13 +7,13 @@ """ 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 FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o'] class DownloadManager(object): - _browser: Browser = ... + _browser: Chromium = ... # _page: ChromiumPage = ... _missions: Dict[str, DownloadMission] = ... _tab_missions: dict = ... @@ -21,7 +21,7 @@ class DownloadManager(object): _running: bool = ... # _save_path: Optional[str] = ... - def __init__(self, browser: Browser): ... + def __init__(self, browser: Chromium): ... @property def missions(self) -> Dict[str, DownloadMission]: ... diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index f39540f..1653ae7 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -14,7 +14,7 @@ from requests.auth import HTTPBasicAuth from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter from .scroller import PageScroller from .._base.base import BasePage -from .._base.browser import Browser +from .._base.browser import Chromium from .._elements.chromium_element import ChromiumElement from .._pages.chromium_base import ChromiumBase from .._pages.chromium_frame import ChromiumFrame @@ -27,8 +27,8 @@ FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o'] class BasePageSetter(object): - def __init__(self, owner: Union[Browser, BasePage]): - self._owner: Union[Browser, BasePage] = ... + def __init__(self, owner: Union[Chromium, BasePage]): + self._owner: Union[Chromium, BasePage] = ... def NoneElement_value(self, value: Any = None, on_off: bool = True) -> None: ... @@ -48,8 +48,8 @@ class BrowserBaseSetter(BasePageSetter): def timeouts(self, base=None, page_load=None, script=None) -> None: ... -class BrowserSetter(BasePageSetter): - _owner: Browser = ... +class BrowserSetter(BrowserBaseSetter): + _owner: Chromium = ... _cookies_setter: BrowserCookiesSetter = ... def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... @@ -221,9 +221,9 @@ class ChromiumFrameSetter(ChromiumBaseSetter): class LoadMode(object): - _owner: Union[Browser, ChromiumBase] = ... + _owner: Union[Chromium, ChromiumBase] = ... - def __init__(self, owner: Union[Browser, ChromiumBase]): ... + def __init__(self, owner: Union[Chromium, ChromiumBase]): ... def __call__(self, value: str) -> None: ... diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index f4c3966..243a014 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -8,7 +8,7 @@ from typing import Union, Tuple, Literal, List from .downloader import DownloadMission -from .._base.browser import Browser +from .._base.browser import Chromium from .._elements.chromium_element import ChromiumElement from .._pages.chromium_base import ChromiumBase from .._pages.chromium_frame import ChromiumFrame @@ -20,7 +20,7 @@ class OriginWaiter(object): class BrowserWaiter(OriginWaiter): - def __init__(self, owner: Browser): + def __init__(self, owner: Chromium): self._owner = owner def download_begin(self, timeout: float = None, cancel_it: bool = False) -> DownloadMission: ... diff --git a/DrissionPage/common.py b/DrissionPage/common.py index eb9adac..e7a0717 100644 --- a/DrissionPage/common.py +++ b/DrissionPage/common.py @@ -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}') From 4cf63e9b08cbfd0945964ae4b49a98554b78b787 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 7 Jul 2024 11:10:24 +0800 Subject: [PATCH 019/114] =?UTF-8?q?WebPage=E6=94=B9=E5=90=8D=E4=B8=BAMixPa?= =?UTF-8?q?ge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 7 +- DrissionPage/__init__.pyi | 5 +- DrissionPage/_base/base.py | 6 - DrissionPage/_base/base.pyi | 9 +- DrissionPage/_base/browser.py | 2 +- DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_elements/chromium_element.py | 6 +- DrissionPage/_elements/chromium_element.pyi | 10 +- DrissionPage/_elements/session_element.py | 2 +- DrissionPage/_functions/web.pyi | 2 +- DrissionPage/_pages/chromium_base.py | 9 +- DrissionPage/_pages/chromium_base.pyi | 5 +- DrissionPage/_pages/chromium_frame.py | 2 +- DrissionPage/_pages/chromium_frame.pyi | 2 +- DrissionPage/_pages/chromium_page.pyi | 2 +- .../_pages/{web_page.py => mix_page.py} | 16 +- .../_pages/{web_page.pyi => mix_page.pyi} | 12 +- DrissionPage/_pages/session_page.py | 6 + DrissionPage/_pages/session_page.pyi | 3 + .../_pages/{chromium_tab.py => tabs.py} | 9 +- .../_pages/{chromium_tab.pyi => tabs.pyi} | 4 +- DrissionPage/_units/clicker.pyi | 2 +- DrissionPage/_units/cookies_setter.py | 2 +- DrissionPage/_units/cookies_setter.pyi | 8 +- DrissionPage/_units/rect.pyi | 6 +- DrissionPage/_units/setter.py | 299 +++++++++--------- DrissionPage/_units/setter.pyi | 189 ++++++----- DrissionPage/items.py | 2 +- 28 files changed, 319 insertions(+), 310 deletions(-) rename DrissionPage/_pages/{web_page.py => mix_page.py} (97%) rename DrissionPage/_pages/{web_page.pyi => mix_page.pyi} (96%) rename DrissionPage/_pages/{chromium_tab.py => tabs.py} (98%) rename DrissionPage/_pages/{chromium_tab.pyi => tabs.pyi} (98%) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index b29f1e8..83e7a10 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -8,9 +8,10 @@ from ._base.browser import Chromium from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions -# 即将废弃 -from ._pages.chromium_page import ChromiumPage from ._pages.session_page import SessionPage -from ._pages.web_page import WebPage + +from ._pages.chromium_page import ChromiumPage +from ._pages.mix_page import MixPage +from ._pages.mix_page import MixPage as WebPage __version__ = '4.1.0.0b1' diff --git a/DrissionPage/__init__.pyi b/DrissionPage/__init__.pyi index 055e4fd..5fc228d 100644 --- a/DrissionPage/__init__.pyi +++ b/DrissionPage/__init__.pyi @@ -11,7 +11,8 @@ from ._configs.session_options import SessionOptions from ._pages.session_page import SessionPage from ._pages.chromium_page import ChromiumPage -from ._pages.web_page import WebPage +from ._pages.mix_page import MixPage +from ._pages.mix_page import MixPage as WebPage -__all__ = ['WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] +__all__ = ['MixPage', 'WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] __version__: str = ... diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index d4f8d78..b0e69ca 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -346,7 +346,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 @@ -362,11 +361,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 - @property def url_available(self): """返回当前访问的url有效性""" diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 27fcb1f..39cd123 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -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,19 +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: ... - @property def url_available(self) -> bool: ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index f3c2a86..fb6e940 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -22,7 +22,7 @@ from .._functions.settings import Settings from .._functions.tools import PortFinder from .._functions.tools import raise_error from .._pages.chromium_base import Timeout -from .._pages.chromium_tab import ChromiumTab, MixTab +from .._pages.tabs import ChromiumTab, MixTab from .._units.downloader import DownloadManager from .._units.setter import BrowserSetter from .._units.waiter import BrowserWaiter diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index d772f9e..aa3978c 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -13,7 +13,7 @@ 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.chromium_tab import ChromiumTab, MixTab +from .._pages.tabs import ChromiumTab, MixTab from .._units.downloader import DownloadManager from .._units.setter import BrowserSetter from .._units.waiter import BrowserWaiter diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 091593f..ffbc68d 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -584,7 +584,7 @@ class ChromiumElement(DrissionElement): :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间(秒) :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 + :param relative: MixTab用的表示是否相对定位的参数 :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 :return: ChromiumElement对象或文本、属性或其组成的列表 """ @@ -1174,7 +1174,7 @@ class ShadowRoot(BaseElement): :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间(秒) :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 + :param relative: MixTab用的表示是否相对定位的参数 :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 :return: ChromiumElement对象或其组成的列表 """ @@ -1250,7 +1250,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元素或它们组成的列表 """ # ---------------处理定位符--------------- diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index 648c68e..cec49db 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -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 = ... @@ -268,7 +267,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 = ... @@ -379,7 +377,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, diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index f0d35a1..d746dd5 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -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对象 """ diff --git a/DrissionPage/_functions/web.pyi b/DrissionPage/_functions/web.pyi index 2f5e702..c7a40db 100644 --- a/DrissionPage/_functions/web.pyi +++ b/DrissionPage/_functions/web.pyi @@ -12,7 +12,7 @@ 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: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 6aea14a..98107c5 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -313,6 +313,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设置""" @@ -343,7 +348,7 @@ class ChromiumBase(BasePage): @property def _browser_url(self): - """用于被WebPage覆盖""" + """用于被MixTab覆盖""" return self.url @property @@ -556,7 +561,7 @@ class ChromiumBase(BasePage): :param locator: 定位符或元素对象 :param timeout: 查找超时时间(秒) :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 + :param relative: MixTab用的表示是否相对定位的参数 :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 :return: ChromiumElement对象或元素对象组成的列表 """ diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 3f946c1..2bc1a8d 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -8,7 +8,7 @@ from pathlib import Path from typing import Union, Tuple, List, Any, Optional, Literal -from .chromium_tab import ChromiumTab, MixTab +from .tabs import ChromiumTab, MixTab from .._base.base import BasePage from .._base.browser import Chromium from .._base.driver import Driver @@ -143,6 +143,9 @@ class ChromiumBase(BasePage): @property def rect(self) -> TabRect: ... + @property + def timeout(self) -> float: ... + @property def timeouts(self) -> Timeout: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 8cf684b..695934e 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -555,7 +555,7 @@ class ChromiumFrame(ChromiumBase): :param locator: 定位符或元素对象 :param timeout: 查找超时时间(秒) :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 + :param relative: MixTab用的表示是否相对定位的参数 :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 :return: ChromiumElement对象 """ diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index a23ed33..61b44ad 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -9,7 +9,7 @@ from pathlib import Path from typing import Union, Tuple, List, Any, Optional from .chromium_base import ChromiumBase -from .chromium_tab import ChromiumTab, MixTab +from .tabs import ChromiumTab, MixTab from .._elements.chromium_element import ChromiumElement from .._functions.elements import ChromiumElementsList from .._units.listener import FrameListener diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 241a30b..22c31fc 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -11,7 +11,7 @@ from typing import Union, Tuple, List, Optional 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 diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/mix_page.py similarity index 97% rename from DrissionPage/_pages/web_page.py rename to DrissionPage/_pages/mix_page.py index c79ae8d..0afbaed 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/mix_page.py @@ -10,10 +10,10 @@ from .session_page import SessionPage from .._base.base import BasePage from .._configs.chromium_options import ChromiumOptions from .._functions.cookies import set_session_cookies, set_tab_cookies -from .._units.setter import WebPageSetter +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): @@ -46,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): @@ -66,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 @@ -147,7 +147,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): @property def timeout(self): """返回通用timeout设置""" - return self.timeouts.base + return super()._timeout if self._mode == 's' else self.timeouts.base def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs): """跳转到一个url @@ -304,7 +304,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): :param url: 要匹配url的文本,模糊匹配,为None则匹配所有 :param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有 :param as_id: 是否返回标签页id而不是标签页对象 - :return: WebPageTab对象 + :return: MixTab对象 """ 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) @@ -366,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: 元素对象或属性、文本节点文本 """ @@ -392,4 +392,4 @@ class WebPage(SessionPage, ChromiumPage, BasePage): self._has_driver = None def __repr__(self): - return f'' + return f'' diff --git a/DrissionPage/_pages/web_page.pyi b/DrissionPage/_pages/mix_page.pyi similarity index 96% rename from DrissionPage/_pages/web_page.pyi rename to DrissionPage/_pages/mix_page.pyi index 1b0e28c..7d335da 100644 --- a/DrissionPage/_pages/web_page.pyi +++ b/DrissionPage/_pages/mix_page.pyi @@ -11,7 +11,7 @@ from requests import Session, Response from .chromium_frame import ChromiumFrame from .chromium_page import ChromiumPage -from .chromium_tab import MixTab +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] = ... @@ -171,10 +171,10 @@ class WebPage(SessionPage, ChromiumPage, BasePage): cert: Any | None = ...) -> Union[bool, Response]: ... @property - def latest_tab(self) -> Union[MixTab, 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], diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index 0697bea..4e39598 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -38,6 +38,7 @@ 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() @@ -143,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,可指定本地文件路径 diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 2324ac0..cec9098 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -130,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, diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/tabs.py similarity index 98% rename from DrissionPage/_pages/chromium_tab.py rename to DrissionPage/_pages/tabs.py index f41ea7e..80638cf 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/tabs.py @@ -15,7 +15,7 @@ from .._functions.settings import Settings 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 @@ -52,7 +52,6 @@ class ChromiumTab(ChromiumBase): def _d_set_runtime_settings(self): """重写设置浏览器运行参数方法""" - print('a') self._timeouts = copy(self.browser.timeouts) self.retry_times = self.browser.retry_times self.retry_interval = self.browser.retry_interval @@ -127,7 +126,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage): def set(self): """返回用于设置的对象""" if self._set is None: - self._set = WebPageTabSetter(self) + self._set = MixTabSetter(self) return self._set @property @@ -208,7 +207,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage): @property def timeout(self): """返回通用timeout设置""" - return self.timeouts.base + return super()._timeout if self._mode == 's' else self.timeouts.base def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs): """跳转到一个url @@ -374,7 +373,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage): :param locator: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 :param timeout: 查找元素超时时间(秒),d模式专用 :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 + :param relative: MixTab用的表示是否相对定位的参数 :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 :return: 元素对象或属性、文本节点文本 """ diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/tabs.pyi similarity index 98% rename from DrissionPage/_pages/chromium_tab.pyi rename to DrissionPage/_pages/tabs.pyi index 7f0a234..3bba12b 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/tabs.pyi @@ -19,7 +19,7 @@ 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 @@ -180,7 +180,7 @@ class MixTab(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], diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index af3c7b4..557189b 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -10,7 +10,7 @@ from typing import Union from .downloader import DownloadMission from .._elements.chromium_element import ChromiumElement -from .._pages.chromium_tab import MixTab, ChromiumTab +from .._pages.tabs import MixTab, ChromiumTab class Clicker(object): diff --git a/DrissionPage/_units/cookies_setter.py b/DrissionPage/_units/cookies_setter.py index cb48d53..cc100c2 100644 --- a/DrissionPage/_units/cookies_setter.py +++ b/DrissionPage/_units/cookies_setter.py @@ -79,7 +79,7 @@ class SessionCookiesSetter(object): self._owner.session.cookies.clear() -class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): +class MixPageCookiesSetter(CookiesSetter, SessionCookiesSetter): def __call__(self, cookies): """设置多个cookie,注意不要传入单个 diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index dcc4215..3eb6c40 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -10,9 +10,9 @@ from typing import Union from .._base.browser import Chromium from .._pages.chromium_base import ChromiumBase -from .._pages.chromium_tab import MixTab +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): @@ -45,8 +45,8 @@ class SessionCookiesSetter(object): def clear(self) -> None: ... -class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): - _owner: Union[WebPage, MixTab] = ... +class MixPageCookiesSetter(CookiesSetter, SessionCookiesSetter): + _owner: Union[MixPage, MixTab] = ... def __init__(self, page: SessionPage): ... diff --git a/DrissionPage/_units/rect.pyi b/DrissionPage/_units/rect.pyi index 0b2c767..abd6047 100644 --- a/DrissionPage/_units/rect.pyi +++ b/DrissionPage/_units/rect.pyi @@ -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, MixTab -from .._pages.web_page import WebPage +from .._pages.tabs import ChromiumTab, MixTab +from .._pages.mix_page import MixPage class ElementRect(object): @@ -66,7 +66,7 @@ class ElementRect(object): class TabRect(object): def __init__(self, owner: ChromiumBase): - self._owner: Union[ChromiumPage, ChromiumTab, WebPage, MixTab] = ... + self._owner: Union[ChromiumPage, ChromiumTab, MixPage, MixTab] = ... @property def window_state(self) -> str: ... diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 25a7571..3f0e441 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -10,14 +10,14 @@ from time import sleep from requests.structures import CaseInsensitiveDict -from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter +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对象 @@ -51,7 +51,144 @@ class BasePageSetter(object): self._owner._download_path = str(Path(path).absolute()) -class BrowserBaseSetter(BasePageSetter): +class SessionPageSetter(BaseSetter): + def __init__(self, owner): + """ + :param owner: SessionPage对象 + """ + super().__init__(owner) + self._cookies_setter = None + + @property + def cookies(self): + """返回用于设置cookies的对象""" + if self._cookies_setter is None: + self._cookies_setter = SessionCookiesSetter(self._owner) + return self._cookies_setter + + def download_path(self, path): + """设置下载路径 + :param path: 下载路径 + :return: None + """ + super().download_path(path) + if self._owner._DownloadKit: + self._owner._DownloadKit.set.goal_path(self._owner._download_path) + + def timeout(self, second): + """设置连接超时时间 + :param second: 秒数 + :return: None + """ + self._owner._timeout = second + + def encoding(self, encoding, set_all=True): + """设置编码 + :param encoding: 编码名称,如果要取消之前的设置,传入None + :param set_all: 是否设置对象参数,为False则只设置当前Response + :return: None + """ + if set_all: + self._owner._encoding = encoding if encoding else None + if self._owner.response: + self._owner.response.encoding = encoding + + def headers(self, headers): + """设置通用的headers + :param headers: dict形式的headers + :return: None + """ + self._owner._headers = CaseInsensitiveDict(format_headers(headers)) + + def header(self, name, value): + """设置headers中一个项 + :param name: 设置名称 + :param value: 设置值 + :return: None + """ + self._owner._headers[name] = value + + def user_agent(self, ua): + """设置user agent + :param ua: user agent + :return: None + """ + self._owner._headers['user-agent'] = ua + + def proxies(self, http=None, https=None): + """设置proxies参数 + :param http: http代理地址 + :param https: https代理地址 + :return: None + """ + self._owner.session.proxies = {'http': http, 'https': https} + + def auth(self, auth): + """设置认证元组或对象 + :param auth: 认证元组或对象 + :return: None + """ + self._owner.session.auth = auth + + def hooks(self, hooks): + """设置回调方法 + :param hooks: 回调方法 + :return: None + """ + self._owner.session.hooks = hooks + + def params(self, params): + """设置查询参数字典 + :param params: 查询参数字典 + :return: None + """ + self._owner.session.params = params + + def verify(self, on_off): + """设置是否验证SSL证书 + :param on_off: 是否验证 SSL 证书 + :return: None + """ + self._owner.session.verify = on_off + + def cert(self, cert): + """SSL客户端证书文件的路径(.pem格式),或(‘cert’, ‘key’)元组 + :param cert: 证书路径或元组 + :return: None + """ + self._owner.session.cert = cert + + def stream(self, on_off): + """设置是否使用流式响应内容 + :param on_off: 是否使用流式响应内容 + :return: None + """ + self._owner.session.stream = on_off + + def trust_env(self, on_off): + """设置是否信任环境 + :param on_off: 是否信任环境 + :return: None + """ + self._owner.session.trust_env = on_off + + def max_redirects(self, times): + """设置最大重定向次数 + :param times: 最大重定向次数 + :return: None + """ + self._owner.session.max_redirects = times + + def add_adapter(self, url, adapter): + """添加适配器 + :param url: 适配器对应url + :param adapter: 适配器对象 + :return: None + """ + self._owner.session.mount(url, adapter) + + +class BrowserBaseSetter(BaseSetter): """Browser和ChromiumBase设置""" def __init__(self, owner): @@ -314,144 +451,7 @@ class ChromiumPageSetter(TabSetter): self._owner._alert.auto = accept if on_off else None -class SessionPageSetter(BasePageSetter): - def __init__(self, owner): - """ - :param owner: SessionPage对象 - """ - super().__init__(owner) - self._cookies_setter = None - - @property - def cookies(self): - """返回用于设置cookies的对象""" - if self._cookies_setter is None: - self._cookies_setter = SessionCookiesSetter(self._owner) - return self._cookies_setter - - def download_path(self, path): - """设置下载路径 - :param path: 下载路径 - :return: None - """ - super().download_path(path) - if self._owner._DownloadKit: - self._owner._DownloadKit.set.goal_path(self._owner._download_path) - - def timeout(self, second): - """设置连接超时时间 - :param second: 秒数 - :return: None - """ - self._owner._timeout = second - - def encoding(self, encoding, set_all=True): - """设置编码 - :param encoding: 编码名称,如果要取消之前的设置,传入None - :param set_all: 是否设置对象参数,为False则只设置当前Response - :return: None - """ - if set_all: - self._owner._encoding = encoding if encoding else None - if self._owner.response: - self._owner.response.encoding = encoding - - def headers(self, headers): - """设置通用的headers - :param headers: dict形式的headers - :return: None - """ - self._owner._headers = CaseInsensitiveDict(format_headers(headers)) - - def header(self, name, value): - """设置headers中一个项 - :param name: 设置名称 - :param value: 设置值 - :return: None - """ - self._owner._headers[name] = value - - def user_agent(self, ua): - """设置user agent - :param ua: user agent - :return: None - """ - self._owner._headers['user-agent'] = ua - - def proxies(self, http=None, https=None): - """设置proxies参数 - :param http: http代理地址 - :param https: https代理地址 - :return: None - """ - self._owner.session.proxies = {'http': http, 'https': https} - - def auth(self, auth): - """设置认证元组或对象 - :param auth: 认证元组或对象 - :return: None - """ - self._owner.session.auth = auth - - def hooks(self, hooks): - """设置回调方法 - :param hooks: 回调方法 - :return: None - """ - self._owner.session.hooks = hooks - - def params(self, params): - """设置查询参数字典 - :param params: 查询参数字典 - :return: None - """ - self._owner.session.params = params - - def verify(self, on_off): - """设置是否验证SSL证书 - :param on_off: 是否验证 SSL 证书 - :return: None - """ - self._owner.session.verify = on_off - - def cert(self, cert): - """SSL客户端证书文件的路径(.pem格式),或(‘cert’, ‘key’)元组 - :param cert: 证书路径或元组 - :return: None - """ - self._owner.session.cert = cert - - def stream(self, on_off): - """设置是否使用流式响应内容 - :param on_off: 是否使用流式响应内容 - :return: None - """ - self._owner.session.stream = on_off - - def trust_env(self, on_off): - """设置是否信任环境 - :param on_off: 是否信任环境 - :return: None - """ - self._owner.session.trust_env = on_off - - def max_redirects(self, times): - """设置最大重定向次数 - :param times: 最大重定向次数 - :return: None - """ - self._owner.session.max_redirects = times - - def add_adapter(self, url, adapter): - """添加适配器 - :param url: 适配器对应url - :param adapter: 适配器对象 - :return: None - """ - self._owner.session.mount(url, adapter) - - -class WebPageSetter(ChromiumPageSetter): +class MixPageSetter(ChromiumPageSetter): def __init__(self, owner): super().__init__(owner) self._session_setter = SessionPageSetter(self._owner) @@ -461,7 +461,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: @@ -482,7 +482,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) @@ -492,7 +492,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: @@ -512,6 +512,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): diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index 1653ae7..c6304b5 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -11,7 +11,7 @@ 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, BrowserCookiesSetter +from .cookies_setter import SessionCookiesSetter, CookiesSetter, MixPageCookiesSetter, BrowserCookiesSetter from .scroller import PageScroller from .._base.base import BasePage from .._base.browser import Chromium @@ -19,14 +19,14 @@ 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, MixTab +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): +class BaseSetter(object): def __init__(self, owner: Union[Chromium, BasePage]): self._owner: Union[Chromium, BasePage] = ... @@ -39,95 +39,7 @@ class BasePageSetter(object): def download_path(self, path: Union[str, Path, None]) -> None: ... -class BrowserBaseSetter(BasePageSetter): - _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 = ... - - def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... - - @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(BasePageSetter): - _owner: ChromiumBase = ... - _cookies_setter: CookiesSetter = ... - - def __init__(self, owner): ... - - @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: ... - - -class TabSetter(ChromiumBaseSetter): - _owner: ChromiumTab = ... - - def __init__(self, owner: Union[ChromiumTab, MixTab, WebPage, 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 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] = ... @@ -173,8 +85,87 @@ 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 = ... + + def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... + + @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 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 MixPageSetter(ChromiumPageSetter): + _owner: MixPage = ... _session_setter: SessionPageSetter = ... _chromium_setter: ChromiumPageSetter = ... @@ -183,10 +174,10 @@ class WebPageSetter(ChromiumPageSetter): def headers(self, headers: Union[str, dict]) -> None: ... @property - def cookies(self) -> WebPageCookiesSetter: ... + def cookies(self) -> MixPageCookiesSetter: ... -class WebPageTabSetter(TabSetter): +class MixTabSetter(TabSetter): _owner: MixTab = ... _session_setter: SessionPageSetter = ... _chromium_setter: ChromiumBaseSetter = ... @@ -196,7 +187,9 @@ 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): diff --git a/DrissionPage/items.py b/DrissionPage/items.py index 5b680e8..6881812 100644 --- a/DrissionPage/items.py +++ b/DrissionPage/items.py @@ -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, MixTab +from ._pages.tabs import ChromiumTab, MixTab __all__ = ['ChromiumElement', 'ShadowRoot', 'NoneElement', 'SessionElement', 'ChromiumFrame', 'ChromiumTab', 'MixTab'] From ea397923f66ed215592d5fc739d7823516b5f777 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 8 Jul 2024 07:16:41 +0800 Subject: [PATCH 020/114] =?UTF-8?q?activate=5Ftab=E7=9A=84tab=5Fid?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=94=B9=E4=B8=BAid=5For=5Find?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 9 ++++++--- DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_pages/chromium_page.py | 7 +++++++ DrissionPage/_pages/chromium_page.pyi | 2 ++ DrissionPage/_units/clicker.py | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index fb6e940..91ffa56 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -376,12 +376,15 @@ class Chromium(object): while self.tabs_count != end_len and perf_counter() < end_time: sleep(.1) - def activate_tab(self, tab_id): + def activate_tab(self, id_or_ind): """使标签页变为活动状态 - :param tab_id: 标签页id + :param id_or_ind: 标签页id(str)或标签页序号(int),序号从1开始 :return: None """ - self._run_cdp('Target.activateTarget', targetId=tab_id) + if isinstance(id_or_ind, int): + id_or_ind += -1 if id_or_ind else 1 + id_or_ind = self.tab_ids[id_or_ind] + self._run_cdp('Target.activateTarget', targetId=id_or_ind) def reconnect(self): """断开重连""" diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index aa3978c..9ecfb3f 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -135,7 +135,7 @@ class Chromium(object): mix: bool = False, as_id: bool = False) -> List[ChromiumTab, str]: ... - def activate_tab(self, tab_id: str) -> None: ... + def activate_tab(self, tab_id: Union[int, str]) -> None: ... def _new_tab(self, obj, diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 7552aea..f201cf4 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -148,6 +148,13 @@ class ChromiumPage(ChromiumBase): """ return self.browser.new_tab(url=url, new_window=new_window, background=background, new_context=new_context) + def activate_tab(self, id_or_ind): + """使标签页变为活动状态 + :param id_or_ind: 标签页id(str)或标签页序号(int),序号从1开始 + :return: None + """ + self.browser.activate_tab(id_or_ind) + def close(self): """关闭Page管理的标签页""" self.close_tabs(self.tab_id) diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 22c31fc..94ceb25 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -101,6 +101,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, tab_id: Union[int, str]) -> None: ... + def close(self) -> None: ... def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 15a6b9d..73de7c3 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -208,7 +208,7 @@ class Clicker(object): :param count: 点击次数 :return: None """ - self._ele.owner.actions.move_to((loc_x, loc_y), duration=.1) + 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, From 5feb58bc65cb1c9940d56eefb9dc8e33158f4266 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 8 Jul 2024 17:35:42 +0800 Subject: [PATCH 021/114] =?UTF-8?q?=E4=BC=98=E5=8C=96text=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=EF=BC=9Bs=5Fele()=E4=BC=9A=E7=AD=89=E5=BE=85=E5=85=83?= =?UTF-8?q?=E7=B4=A0=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 9 ++------- DrissionPage/_functions/web.py | 24 +++++++++++++++++++++--- DrissionPage/_pages/chromium_base.py | 3 +++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index b0e69ca..2dd52e4 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -121,11 +121,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): @@ -139,10 +136,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: diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index c4c6242..a53ae98 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -56,7 +56,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) @@ -77,8 +77,26 @@ 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) + + re_str = [i if i is not True else '\n' for i in re_str] + l = len(re_str) + if l > 1: + r = [] + for i in range(0, l - 1, 2): + i1 = re_str[i] + i2 = re_str[i + 1] + if i1.endswith(' ') and i2.startswith(' '): + i1 = i1[:-1] + r.append(i1) + r.append(i2) + re_str = ''.join(r) + + elif not l: + re_str = '' + else: + re_str = re_str[0] + + return format_html(re_str.strip()) def format_html(text): diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 98107c5..925df69 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -547,6 +547,8 @@ class ChromiumBase(BasePage): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :return: SessionElement对象或属性、文本 """ + if locator: + self.wait.eles_loaded(locator) return make_session_ele(self, locator, index=index, method='s_ele()') def s_eles(self, locator): @@ -554,6 +556,7 @@ class ChromiumBase(BasePage): :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象组成的列表 """ + self.wait.eles_loaded(locator) return make_session_ele(self, locator, index=None) def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): From 73b6f1c475434b8b53d38422f5d83fcd93c84c86 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 8 Jul 2024 23:49:24 +0800 Subject: [PATCH 022/114] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_functions/web.py | 13 +++++++++---- DrissionPage/_pages/chromium_base.py | 8 +++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index a53ae98..bebe132 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -78,23 +78,28 @@ def get_ele_txt(e): if re_str and re_str[-1] == '\n': re_str.pop() - re_str = [i if i is not True else '\n' for i in re_str] l = len(re_str) if l > 1: r = [] - for i in range(0, l - 1, 2): + 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(i2) + 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] + re_str = re_str[0] if re_str[0] is not True else '\n' return format_html(re_str.strip()) diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 925df69..da75ee2 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -19,6 +19,7 @@ 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.cookies import CookiesList +from .._functions.elements import SessionElementsList from .._functions.locator import get_loc, is_loc from .._functions.settings import Settings from .._functions.tools import raise_error @@ -547,8 +548,8 @@ class ChromiumBase(BasePage): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :return: SessionElement对象或属性、文本 """ - if locator: - self.wait.eles_loaded(locator) + if locator and not self.wait.eles_loaded(locator): + return NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index}) return make_session_ele(self, locator, index=index, method='s_ele()') def s_eles(self, locator): @@ -556,7 +557,8 @@ class ChromiumBase(BasePage): :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象组成的列表 """ - self.wait.eles_loaded(locator) + if not self.wait.eles_loaded(locator): + return SessionElementsList() return make_session_ele(self, locator, index=None) def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): From d758932910bf5dc3ea82ad59e09c6d2e85e1e6b5 Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 9 Jul 2024 00:04:41 +0800 Subject: [PATCH 023/114] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_elements/chromium_element.py | 18 +++++++++--------- DrissionPage/_pages/chromium_base.py | 10 ++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index ffbc68d..c532807 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -16,7 +16,7 @@ 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 +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.web import make_absolute_link, get_ele_txt, format_html, is_js_func, get_blob @@ -570,14 +570,16 @@ class ChromiumElement(DrissionElement): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :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) + else NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index})) def s_eles(self, locator=None): """查找所有符合条件的元素,以SessionElement列表形式返回 :param locator: 定位符 :return: SessionElement或属性、文本组成的列表 """ - return make_session_ele(self, locator, index=None) + return make_session_ele(self, locator, index=None) if self.ele(locator) else SessionElementsList() def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 @@ -1156,18 +1158,16 @@ class ShadowRoot(BaseElement): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :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) + else NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index})) def s_eles(self, locator): """查找所有符合条件的元素以SessionElement列表形式返回,处理复杂页面时效率很高 :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象 """ - return make_session_ele(self, locator, index=None) + return make_session_ele(self, locator, index=None) if self.ele(locator) else SessionElementsList() def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index da75ee2..58b1d5a 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -548,18 +548,16 @@ class ChromiumBase(BasePage): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :return: SessionElement对象或属性、文本 """ - if locator and not self.wait.eles_loaded(locator): - return NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index}) - 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) + else make_session_ele(self, locator, index=index, method='s_ele()')) def s_eles(self, locator): """查找所有符合条件的元素以SessionElement列表形式返回 :param locator: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象组成的列表 """ - if not self.wait.eles_loaded(locator): - return SessionElementsList() - return make_session_ele(self, locator, index=None) + return make_session_ele(self, locator, index=None) if self.wait.eles_loaded(locator) else SessionElementsList() def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): """执行元素查找 From 2ea2be782fe7ad45fd73ba9db3e2376e728ba6c5 Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 9 Jul 2024 17:36:23 +0800 Subject: [PATCH 024/114] =?UTF-8?q?4.1.0.0b2s=5Fele()=E5=92=8Cs=5Feles()?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0timeout=E5=8F=82=E6=95=B0=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=BF=9E=E6=8E=A5=E6=B5=8F=E8=A7=88=E5=99=A8=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_elements/chromium_element.py | 28 ++++++++----- DrissionPage/_elements/chromium_element.pyi | 12 ++++-- DrissionPage/_functions/browser.py | 30 +++++++++----- DrissionPage/_functions/browser.pyi | 2 +- DrissionPage/_pages/chromium_base.py | 45 +++++++++++---------- DrissionPage/_pages/chromium_base.pyi | 15 ++++--- DrissionPage/_pages/chromium_frame.py | 20 ++++++--- DrissionPage/_pages/chromium_frame.pyi | 18 ++++++--- 9 files changed, 107 insertions(+), 65 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 83e7a10..e22b175 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b1' +__version__ = '4.1.0.0b2' diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index c532807..c86a693 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -162,7 +162,7 @@ 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'] if not info.get('shadowRoots', None): @@ -171,9 +171,9 @@ class ChromiumElement(DrissionElement): 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): @@ -564,22 +564,25 @@ 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()') - if self.ele(locator, index=index) + 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) if self.ele(locator) else SessionElementsList() + 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): """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 @@ -1152,22 +1155,25 @@ 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对象或属性、文本 """ return (make_session_ele(self, locator, index=index, method='s_ele()') - if self.ele(locator, index=index) + 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) if self.ele(locator) else SessionElementsList() + 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): """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index cec49db..fa3b86e 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -209,9 +209,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], @@ -339,9 +342,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], diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index 2b91d53..9acae5d 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -29,9 +29,17 @@ 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) - return True + 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, user_path = get_launch_args(option) @@ -49,7 +57,12 @@ def connect_browser(option): 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 @@ -186,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: diff --git a/DrissionPage/_functions/browser.pyi b/DrissionPage/_functions/browser.pyi index 8815ab4..ff7bdc6 100644 --- a/DrissionPage/_functions/browser.pyi +++ b/DrissionPage/_functions/browser.pyi @@ -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]: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 58b1d5a..008d35a 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -38,12 +38,12 @@ __ERROR__ = 'error' class ChromiumBase(BasePage): - """标签页、frame、页面基类""" + """标签页、Frame、Page基类""" - def __init__(self, browser, tab_id=None): + def __init__(self, browser, target_id=None): """ :param browser: Chromium - :param tab_id: 要控制的标签页id,不指定默认为激活的 + :param target_id: 要控制的target id,不指定默认为激活的标签页 """ super().__init__() self._browser = browser @@ -68,19 +68,19 @@ class ChromiumBase(BasePage): self._listener = None self._d_set_runtime_settings() - self._connect_browser(tab_id) + self._connect_browser(target_id) def _d_set_runtime_settings(self): 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: + 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://')] @@ -89,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) @@ -529,7 +529,7 @@ class ChromiumBase(BasePage): """获取一个符合条件的元素对象 :param locator: 定位符或元素对象 :param index: 获取第几个元素,从1开始,可传入负数获取倒数第几个 - :param timeout: 查找超时时间(秒) + :param timeout: 查找超时时间(秒),默认与页面等待时间一致 :return: ChromiumElement对象 """ return self._ele(locator, timeout=timeout, index=index, method='ele()') @@ -537,27 +537,30 @@ 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 (NoneElement(self, method='s_ele()', args={'locator': locator, 'index': index}) - if locator and not self.wait.eles_loaded(locator) + 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) if self.wait.eles_loaded(locator) else SessionElementsList() + 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): """执行元素查找 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 2bc1a8d..2ab5fb2 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -63,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: ... @@ -196,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], @@ -224,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]: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 695934e..2f30aff 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -38,7 +38,7 @@ class ChromiumFrame(ChromiumBase): 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.browser, owner.tab_id) + super().__init__(owner.browser, owner.driver.id) else: self._is_diff_domain = True delattr(self, '_frame_id') @@ -74,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._browser.address}/json') - super()._driver_init(tab_id) + 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) @@ -310,6 +310,16 @@ class ChromiumFrame(ChromiumBase): 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'""" diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index 61b44ad..54e3594 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -10,7 +10,7 @@ from typing import Union, Tuple, List, Any, Optional from .chromium_base import ChromiumBase from .tabs import ChromiumTab, MixTab -from .._elements.chromium_element import ChromiumElement +from .._elements.chromium_element import ChromiumElement, ShadowRoot from .._functions.elements import ChromiumElementsList from .._units.listener import FrameListener from .._units.rect import FrameRect @@ -52,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: ... @@ -128,6 +128,12 @@ class ChromiumFrame(ChromiumBase): @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]: ... @@ -143,10 +149,10 @@ class ChromiumFrame(ChromiumBase): timeout: float = None) -> Any: ... def _run_js(self, - script: str, - *args, - as_expr: bool = False, - timeout: float = None) -> Any: ... + script: str, + *args, + as_expr: bool = False, + timeout: float = None) -> Any: ... def parent(self, level_or_loc: Union[Tuple[str, str], str, int] = 1, From 58285fc0adfb477eba479665820aa56c8b9b3079 Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 9 Jul 2024 21:43:12 +0800 Subject: [PATCH 025/114] =?UTF-8?q?4.1.0.0b3=E4=BF=AE=E5=A4=8D=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=E5=92=8C=E5=88=A0=E9=99=A4cookies=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_units/cookies_setter.py | 40 +++++++------ DrissionPage/_units/cookies_setter.pyi | 18 +++--- DrissionPage/_units/waiter.py | 82 +++++++++----------------- 4 files changed, 61 insertions(+), 81 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index e22b175..2fcdf31 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b2' +__version__ = '4.1.0.0b3' diff --git a/DrissionPage/_units/cookies_setter.py b/DrissionPage/_units/cookies_setter.py index cc100c2..dd9764c 100644 --- a/DrissionPage/_units/cookies_setter.py +++ b/DrissionPage/_units/cookies_setter.py @@ -8,13 +8,27 @@ 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 + def __call__(self, cookies): + """设置一个或多个cookie + :param cookies: cookies信息 + :return: None + """ + 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信息 @@ -22,6 +36,10 @@ class CookiesSetter(object): """ 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,23 +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('Storage.deleteCookies', **d) - - def clear(self): - """清除cookies""" - self._owner._run_cdp('Storage.clearBrowserCookies') - - -class BrowserCookiesSetter(CookiesSetter): - - def __call__(self, cookies): - """设置一个或多个cookie - :param cookies: cookies信息 - :return: None - """ - set_browser_cookies(self._owner, cookies) + self._owner._run_cdp('Network.deleteCookies', **d) class SessionCookiesSetter(object): diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index 3eb6c40..8ce3d58 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -15,22 +15,24 @@ from .._pages.session_page import SessionPage 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 remove(self, name: str, url: str = None, domain: str = None, path: str = None) -> None: ... - def clear(self) -> None: ... -class BrowserCookiesSetter(CookiesSetter): - _owner: Chromium = ... +class CookiesSetter(BrowserCookiesSetter): + _owner: ChromiumBase = ... - def __init__(self, page: Chromium): ... + 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): diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index f471114..3486cb6 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -30,6 +30,27 @@ class BrowserWaiter(OriginWaiter): def __init__(self, owner): self._owner = owner + 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使用页面对象超时时间 @@ -54,27 +75,6 @@ class BrowserWaiter(OriginWaiter): self._owner._dl_mgr.set_flag('browser', None) return r - 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 all_downloads_done(self, timeout=None, cancel_if_timeout=True): """等待所有浏览器下载任务结束 :param timeout: 超时时间(秒),为None时无限等待 @@ -332,6 +332,7 @@ class BaseWaiter(OriginWaiter): class TabWaiter(BaseWaiter): + """标签页对象等待对象""" def downloads_done(self, timeout=None, cancel_if_timeout=True): """等待所有浏览器下载任务结束 @@ -370,6 +371,8 @@ class TabWaiter(BaseWaiter): class PageWaiter(TabWaiter): + """ChromiumPage和MixPage的等待对象""" + def __init__(self, page): super().__init__(page) @@ -379,18 +382,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._driver.browser.wait.new_tab(timeout=timeout, raise_err=raise_err) def all_downloads_done(self, timeout=None, cancel_if_timeout=True): """等待所有浏览器下载任务结束 @@ -398,27 +390,7 @@ 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._driver.browser.wait.all_downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout) class ElementWaiter(OriginWaiter): @@ -427,7 +399,7 @@ class ElementWaiter(OriginWaiter): def __init__(self, owner, ele): """等待元素在dom中某种状态,如删除、显示、隐藏 :param owner: 元素所在页面 - :param ele: 要等待的元素 + :param ele: 要执行等待的元素 """ self._owner = owner self._ele = ele From 523f92f6409aba66bb5d80493f4f608feea046a9 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 10 Jul 2024 14:44:59 +0800 Subject: [PATCH 026/114] =?UTF-8?q?4.1.0.0b4set.tab=5Fto=5Ffront()?= =?UTF-8?q?=E6=A0=87=E8=AE=B0=E5=8D=B3=E5=B0=86=E5=BA=9F=E5=BC=83=EF=BC=8C?= =?UTF-8?q?=E7=94=A8activate=5Ftab()=E4=BB=A3=E6=9B=BF=EF=BC=9BFrame?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0set.property()=E5=92=8Cset.style()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/browser.py | 14 ++++--- DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_pages/chromium_page.py | 6 +-- DrissionPage/_pages/chromium_page.pyi | 2 +- DrissionPage/_units/setter.py | 58 ++++++++++++++++++--------- DrissionPage/_units/setter.pyi | 4 -- 7 files changed, 52 insertions(+), 36 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 2fcdf31..e3d2e75 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b3' +__version__ = '4.1.0.0b4' diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 91ffa56..20bda69 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -376,15 +376,17 @@ class Chromium(object): while self.tabs_count != end_len and perf_counter() < end_time: sleep(.1) - def activate_tab(self, id_or_ind): + def activate_tab(self, id_ind_tab): """使标签页变为活动状态 - :param id_or_ind: 标签页id(str)或标签页序号(int),序号从1开始 + :param id_ind_tab: 标签页id(str)、Tab对象或标签页序号(int),序号从1开始 :return: None """ - if isinstance(id_or_ind, int): - id_or_ind += -1 if id_or_ind else 1 - id_or_ind = self.tab_ids[id_or_ind] - self._run_cdp('Target.activateTarget', targetId=id_or_ind) + 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): """断开重连""" diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 9ecfb3f..fa906ed 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -135,7 +135,7 @@ class Chromium(object): mix: bool = False, as_id: bool = False) -> List[ChromiumTab, str]: ... - def activate_tab(self, tab_id: Union[int, str]) -> None: ... + def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None: ... def _new_tab(self, obj, diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index f201cf4..aae5dbe 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -148,12 +148,12 @@ class ChromiumPage(ChromiumBase): """ return self.browser.new_tab(url=url, new_window=new_window, background=background, new_context=new_context) - def activate_tab(self, id_or_ind): + def activate_tab(self, id_ind_tab): """使标签页变为活动状态 - :param id_or_ind: 标签页id(str)或标签页序号(int),序号从1开始 + :param id_ind_tab: 标签页id(str)、Tab对象或标签页序号(int),序号从1开始 :return: None """ - self.browser.activate_tab(id_or_ind) + self.browser.activate_tab(id_ind_tab) def close(self): """关闭Page管理的标签页""" diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 94ceb25..589a58d 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -101,7 +101,7 @@ 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, tab_id: Union[int, str]) -> None: ... + def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None: ... def close(self) -> None: ... diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 3f0e441..972ab75 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -229,15 +229,6 @@ class BrowserSetter(BrowserBaseSetter): self._cookies_setter = BrowserCookiesSetter(self._owner) return self._cookies_setter - 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) - def auto_handle_alert(self, on_off=True, accept=True): """设置是否启用自动处理弹窗 :param on_off: bool表示开或关 @@ -274,6 +265,16 @@ class BrowserSetter(BrowserBaseSetter): 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): @@ -427,17 +428,6 @@ class TabSetter(ChromiumBaseSetter): 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表示开或关 @@ -450,6 +440,18 @@ class ChromiumPageSetter(TabSetter): 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): @@ -589,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): """用于设置页面加载策略的类""" diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index c6304b5..ca43f30 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -98,8 +98,6 @@ class BrowserSetter(BrowserBaseSetter): _owner: Chromium = ... _cookies_setter: BrowserCookiesSetter = ... - def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... - @property def cookies(self) -> BrowserCookiesSetter: ... @@ -159,8 +157,6 @@ class TabSetter(ChromiumBaseSetter): 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: ... From 54ee00f4b06f6f1f071b6edc380fb39bd3116cdf Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 12 Jul 2024 19:58:34 +0800 Subject: [PATCH 027/114] =?UTF-8?q?offset()=E5=A2=9E=E5=8A=A0locator,timeo?= =?UTF-8?q?ut=E5=8F=82=E6=95=B0=EF=BC=8Coffset=5Fx=E5=92=8Coffset=5Fy?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=94=B9=E4=B8=BAx,y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_elements/chromium_element.py | 55 +++++++++++++++++---- DrissionPage/_elements/chromium_element.pyi | 6 ++- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index c86a693..d26a8a6 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -351,20 +351,55 @@ 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 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, - ignorePointerEventsNone=False)['backendNodeId']) + 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): """获取元素右边某个指定元素 diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index fa3b86e..c04e13d 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -168,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', From d2bf4f8f13b25bbfa7e359e5debeb1605889e035 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 12 Jul 2024 20:12:27 +0800 Subject: [PATCH 028/114] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_elements/chromium_element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index d26a8a6..995e0ba 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -359,7 +359,8 @@ class ChromiumElement(DrissionElement): :param timeout: 超时时间(秒),为None使用所在页面设置 :return: 元素对象 """ - if locator and not locator.startswith(('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css=')): + 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: From daf435cc9a982336e2ff4d0c2c496dfea0686eda Mon Sep 17 00:00:00 2001 From: g1879 Date: Sat, 13 Jul 2024 10:22:08 +0800 Subject: [PATCH 029/114] =?UTF-8?q?4.1.0.0b5wait()=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 11 +- DrissionPage/_elements/chromium_element.py | 5 +- DrissionPage/_units/waiter.py | 116 ++++++++++----------- DrissionPage/_units/waiter.pyi | 50 ++++++--- 4 files changed, 104 insertions(+), 78 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 20bda69..b01aec3 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -34,6 +34,7 @@ __ERROR__ = 'error' class Chromium(object): _BROWSERS = {} + _lock = Lock() def __new__(cls, addr_or_opts=None, session_options=None): """ @@ -42,8 +43,12 @@ class Chromium(object): """ opt = handle_options(addr_or_opts) is_headless, browser_id, is_exists = run_browser(opt) - if browser_id in cls._BROWSERS: - return cls._BROWSERS[browser_id] + 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 @@ -62,11 +67,9 @@ class Chromium(object): self._created = True self._type = 'Chromium' - self._frames = {} self._drivers = {} self._all_drivers = {} - self._lock = Lock() self._set = None self._wait = None diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 995e0ba..08f22fb 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -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 @@ -1674,6 +1674,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'] diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 3486cb6..2483e07 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -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,11 +27,10 @@ class OriginWaiter(object): else: from random import uniform sleep(uniform(second, scope)) + return self._owner class BrowserWaiter(OriginWaiter): - def __init__(self, owner): - self._owner = owner def new_tab(self, timeout=None, curr_tab=None, raise_err=None): """等待新标签页出现 @@ -105,11 +107,6 @@ class BrowserWaiter(OriginWaiter): 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中删除 @@ -118,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): @@ -129,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: @@ -148,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: @@ -199,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: @@ -228,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 @@ -241,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): @@ -289,14 +286,14 @@ class BaseWaiter(OriginWaiter): :return: 是否等待成功 """ 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 + val = self._owner.url elif arg == 'title': - val = self._driver.title + val = self._owner.title else: raise ValueError if (not exclude and text in val) or (exclude and text not in val): @@ -318,10 +315,10 @@ class BaseWaiter(OriginWaiter): """ if timeout != 0: if timeout is None or timeout is True: - timeout = self._driver.timeout + timeout = self._owner.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) @@ -340,23 +337,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: @@ -364,25 +361,22 @@ 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): """ChromiumPage和MixPage的等待对象""" - def __init__(self, page): - super().__init__(page) - def new_tab(self, timeout=None, raise_err=None): """等待新标签页出现 :param timeout: 超时时间(秒),为None则使用页面对象timeout属性 :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :return: 等到新标签页返回其id,否则返回False """ - return self._driver.browser.wait.new_tab(timeout=timeout, raise_err=raise_err) + return self._owner.browser.wait.new_tab(timeout=timeout, raise_err=raise_err) def all_downloads_done(self, timeout=None, cancel_if_timeout=True): """等待所有浏览器下载任务结束 @@ -390,19 +384,16 @@ class PageWaiter(TabWaiter): :param cancel_if_timeout: 超时时是否取消剩余任务 :return: 是否等待成功 """ - return self._driver.browser.wait.all_downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout) + 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 + @property + def _timeout(self): + """返回超时设置""" + return self._owner.owner.timeout def deleted(self, timeout=None, raise_err=None): """等待元素从dom删除 @@ -467,10 +458,10 @@ class ElementWaiter(OriginWaiter): :return: 是否等待成功 """ 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: + if not self._owner.states.is_enabled or not self._owner.states.is_alive: return True sleep(.05) @@ -487,12 +478,12 @@ class ElementWaiter(OriginWaiter): :return: 是否等待成功 """ if timeout is None: - timeout = self._owner.timeout + timeout = self._timeout end_time = perf_counter() + timeout while perf_counter() < end_time: try: - size = self._ele.states.has_rect - location = self._ele.rect.location + size = self._owner.states.has_rect + location = self._owner.rect.location break except NoRectError: pass @@ -502,10 +493,10 @@ class ElementWaiter(OriginWaiter): while perf_counter() < end_time: sleep(gap) - if self._ele.rect.size == size and self._ele.rect.location == location: + if self._owner.rect.size == size and self._owner.rect.location == location: return True - size = self._ele.rect.size - location = self._ele.rect.location + size = self._owner.rect.size + location = self._owner.rect.location if raise_err is True or Settings.raise_when_wait_failed is True: raise WaitTimeoutError(f'等待元素停止运动失败(等待{timeout}秒)。') @@ -543,15 +534,15 @@ class ElementWaiter(OriginWaiter): :param err_text: 抛出错误时显示的信息 :return: 是否等待成功 """ - a = self._ele.states.__getattribute__(attr) + a = self._owner.states.__getattribute__(attr) if (a and mode) or (not a and not mode): return True 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) + a = self._owner.states.__getattribute__(attr) if (a and mode) or (not a and not mode): return True if isinstance(a, bool) else a sleep(.05) @@ -564,9 +555,14 @@ class ElementWaiter(OriginWaiter): class FrameWaiter(BaseWaiter, ElementWaiter): - def __init__(self, frame): + def __init__(self, owner): """ - :param frame: ChromiumFrame对象 + :param owner: ChromiumFrame对象 """ - super().__init__(frame) - super(BaseWaiter, self).__init__(frame, frame.frame_ele) + super().__init__(owner) + super(BaseWaiter, self).__init__(owner.frame_ele) + + @property + def _timeout(self): + """返回超时设置""" + return self._owner.timeout diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index 243a014..d8824ac 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -13,28 +13,36 @@ 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): - def __init__(self, owner: Chromium): - self._owner = owner + _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): ... + 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 = ... + _owner: ChromiumBase = ... - def __call__(self, second: float, scope: float = None) -> None: ... + def __call__(self, second: float, scope: float = None) -> ChromiumBase: ... def ele_deleted(self, loc_or_ele: Union[str, tuple, ChromiumElement], @@ -76,6 +84,11 @@ 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: ... @@ -83,7 +96,11 @@ class TabWaiter(BaseWaiter): 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]: ... @@ -91,11 +108,14 @@ class PageWaiter(TabWaiter): class ElementWaiter(OriginWaiter): - def __init__(self, owner: ChromiumBase, ele: ChromiumElement): - self._ele: ChromiumElement = ... - self._owner: ChromiumBase = ... + _owner: ChromiumElement = ... - def __call__(self, second: float, scope: float = None) -> None: ... + def __init__(self, owner: ChromiumElement): ... + + def __call__(self, second: float, scope: float = None) -> ChromiumElement: ... + + @property + def _timeout(self) -> float: ... def deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ... @@ -130,4 +150,8 @@ class ElementWaiter(OriginWaiter): 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: ... From 329e49ea7c908da89c5411887aebfd150e50a065 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 14 Jul 2024 23:01:55 +0800 Subject: [PATCH 030/114] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E7=AD=89=E5=BE=85=E5=8A=9F=E8=83=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_functions/tools.py | 4 ++-- DrissionPage/_units/waiter.py | 27 ++++++++++++++------------- DrissionPage/_units/waiter.pyi | 1 + 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index cfe4c1e..e9a4be5 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -48,9 +48,9 @@ class PortFinder(object): PortFinder.prev_time = perf_counter() if scope in (True, None): scope = (9600, 59600) - msx_times = scope[1] - scope[0] + max_times = scope[1] - scope[0] times = 0 - while times < msx_times: + while times < max_times: times += 1 port = randint(*scope) if port in PortFinder.used_port or port_is_using('127.0.0.1', port): diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 2483e07..e702946 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -390,10 +390,14 @@ class PageWaiter(TabWaiter): class ElementWaiter(OriginWaiter): """等待元素在dom中某种状态,如删除、显示、隐藏""" + def __init__(self, owner): + super().__init__(owner) + self._ele = owner + @property def _timeout(self): """返回超时设置""" - return self._owner.owner.timeout + return self._ele.owner.timeout def deleted(self, timeout=None, raise_err=None): """等待元素从dom删除 @@ -461,7 +465,7 @@ class ElementWaiter(OriginWaiter): timeout = self._timeout end_time = perf_counter() + timeout while perf_counter() < end_time: - if not self._owner.states.is_enabled or not self._owner.states.is_alive: + if not self._ele.states.is_enabled or not self._ele.states.is_alive: return True sleep(.05) @@ -482,8 +486,8 @@ class ElementWaiter(OriginWaiter): end_time = perf_counter() + timeout while perf_counter() < end_time: try: - size = self._owner.states.has_rect - location = self._owner.rect.location + size = self._ele.states.has_rect + location = self._ele.rect.location break except NoRectError: pass @@ -493,10 +497,10 @@ class ElementWaiter(OriginWaiter): while perf_counter() < end_time: sleep(gap) - if self._owner.rect.size == size and self._owner.rect.location == location: + if self._ele.rect.size == size and self._ele.rect.location == location: return True - size = self._owner.rect.size - location = self._owner.rect.location + size = self._ele.rect.size + location = self._ele.rect.location if raise_err is True or Settings.raise_when_wait_failed is True: raise WaitTimeoutError(f'等待元素停止运动失败(等待{timeout}秒)。') @@ -534,7 +538,7 @@ class ElementWaiter(OriginWaiter): :param err_text: 抛出错误时显示的信息 :return: 是否等待成功 """ - a = self._owner.states.__getattribute__(attr) + a = self._ele.states.__getattribute__(attr) if (a and mode) or (not a and not mode): return True if isinstance(a, bool) else a @@ -542,7 +546,7 @@ class ElementWaiter(OriginWaiter): timeout = self._timeout end_time = perf_counter() + timeout while perf_counter() < end_time: - a = self._owner.states.__getattribute__(attr) + a = self._ele.states.__getattribute__(attr) if (a and mode) or (not a and not mode): return True if isinstance(a, bool) else a sleep(.05) @@ -556,11 +560,8 @@ class ElementWaiter(OriginWaiter): class FrameWaiter(BaseWaiter, ElementWaiter): def __init__(self, owner): - """ - :param owner: ChromiumFrame对象 - """ super().__init__(owner) - super(BaseWaiter, self).__init__(owner.frame_ele) + self._ele = owner.frame_ele @property def _timeout(self): diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index d8824ac..8eb4849 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -109,6 +109,7 @@ class PageWaiter(TabWaiter): class ElementWaiter(OriginWaiter): _owner: ChromiumElement = ... + _ele: ChromiumElement = ... def __init__(self, owner: ChromiumElement): ... From d0d3fea3be0c32348ce32d7ee7bfc7ded6ef191b Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 15 Jul 2024 16:59:16 +0800 Subject: [PATCH 031/114] =?UTF-8?q?4.1.0.0b7=E5=85=83=E7=B4=A0=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E4=B9=9F=E5=8A=A0=E4=B8=8Aget=5Fframe()=EF=BC=9Bmove?= =?UTF-8?q?=5Fto()=E7=9A=84=E5=81=8F=E7=A7=BB=E9=87=8F=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=BANone=EF=BC=9B=E4=BF=AE=E5=A4=8Dauto?= =?UTF-8?q?=5Fport()=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/base.py | 11 ++++++ DrissionPage/_elements/chromium_element.py | 2 +- DrissionPage/_functions/elements.py | 45 ++++++++++++++++++++++ DrissionPage/_functions/elements.pyi | 6 +++ DrissionPage/_functions/tools.py | 7 ++-- DrissionPage/_pages/chromium_base.py | 40 ++----------------- DrissionPage/_units/actions.py | 12 ++++-- DrissionPage/_units/waiter.pyi | 2 - 9 files changed, 79 insertions(+), 48 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index e3d2e75..f97d719 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b4' +__version__ = '4.1.0.0b7' diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 2dd52e4..8fa9a7c 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -13,6 +13,7 @@ from urllib.parse import quote from DownloadKit import DownloadKit 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 @@ -70,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: 定位符 diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 08f22fb..5409f46 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -823,7 +823,7 @@ class ChromiumElement(DrissionElement): 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 diff --git a/DrissionPage/_functions/elements.py b/DrissionPage/_functions/elements.py index d77d359..cccb8da 100644 --- a/DrissionPage/_functions/elements.py +++ b/DrissionPage/_functions/elements.py @@ -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: diff --git a/DrissionPage/_functions/elements.pyi b/DrissionPage/_functions/elements.pyi index 41562e5..69f08a6 100644 --- a/DrissionPage/_functions/elements.pyi +++ b/DrissionPage/_functions/elements.pyi @@ -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 = ... diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index e9a4be5..29ffb9e 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -19,7 +19,7 @@ from ..errors import (ContextLostError, ElementLostError, CDPError, PageDisconne class PortFinder(object): used_port = set() - prev_time = None + prev_time = 0 lock = Lock() checked_paths = set() @@ -43,9 +43,8 @@ class PortFinder(object): """ from random import randint with PortFinder.lock: - if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 30: + if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 60: PortFinder.used_port.clear() - PortFinder.prev_time = perf_counter() if scope in (True, None): scope = (9600, 59600) max_times = scope[1] - scope[0] @@ -61,6 +60,8 @@ class PortFinder(object): rmtree(path) except: continue + PortFinder.used_port.add(port) + PortFinder.prev_time = perf_counter() return port, str(path) raise OSError('未找到可用端口。') diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 008d35a..229ff0f 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -19,8 +19,8 @@ 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.cookies import CookiesList -from .._functions.elements import SessionElementsList -from .._functions.locator import get_loc, is_loc +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 @@ -770,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对象 diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index e920529..c92aa90 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -25,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)形式 @@ -35,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 @@ -42,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: @@ -58,8 +63,7 @@ class Actions: 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 diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index 8eb4849..69e33de 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -42,8 +42,6 @@ class BrowserWaiter(OriginWaiter): class BaseWaiter(OriginWaiter): _owner: ChromiumBase = ... - def __call__(self, second: float, scope: float = None) -> ChromiumBase: ... - def ele_deleted(self, loc_or_ele: Union[str, tuple, ChromiumElement], timeout: float = None, From c320028f15059464e6861df1bd763571615e12d5 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 17 Jul 2024 00:00:49 +0800 Subject: [PATCH 032/114] =?UTF-8?q?4.1.0.0b8=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_functions/web.py | 3 ++- DrissionPage/_pages/mix_page.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index f97d719..b292279 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b7' +__version__ = '4.1.0.0b8' diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index bebe132..f86605e 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -12,6 +12,7 @@ from re import sub from urllib.parse import urlparse, urljoin, urlunparse from DataRecorder.tools import make_valid_name +from requests.structures import CaseInsensitiveDict def get_ele_txt(e): @@ -369,7 +370,7 @@ def format_headers(txt): :param txt: 从浏览器复制的原始文本格式headers :return: dict格式headers """ - if isinstance(txt, dict): + if isinstance(txt, (dict, CaseInsensitiveDict)): for k, v in txt.items(): txt[k] = str(v) return txt diff --git a/DrissionPage/_pages/mix_page.py b/DrissionPage/_pages/mix_page.py index 0afbaed..e4e834c 100644 --- a/DrissionPage/_pages/mix_page.py +++ b/DrissionPage/_pages/mix_page.py @@ -147,7 +147,7 @@ class MixPage(SessionPage, ChromiumPage, BasePage): @property def timeout(self): """返回通用timeout设置""" - return super()._timeout if self._mode == 's' else self.timeouts.base + 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 From 362566d2b60da32d34a26214301c9780ff6cc559 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 17 Jul 2024 18:10:50 +0800 Subject: [PATCH 033/114] =?UTF-8?q?4.1.0.0b9=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_pages/tabs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index b292279..a241082 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b8' +__version__ = '4.1.0.0b9' diff --git a/DrissionPage/_pages/tabs.py b/DrissionPage/_pages/tabs.py index 80638cf..38188a2 100644 --- a/DrissionPage/_pages/tabs.py +++ b/DrissionPage/_pages/tabs.py @@ -207,7 +207,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage): @property def timeout(self): """返回通用timeout设置""" - return super()._timeout if self._mode == 's' else self.timeouts.base + 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 From 71ac85d3bf10453bacf432a2e90a09fcd4c69a11 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 19 Jul 2024 14:24:52 +0800 Subject: [PATCH 034/114] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E7=AD=89=E5=BE=85url=E6=88=96title=E5=8F=98=E5=8C=96=E6=96=B9?= =?UTF-8?q?=E6=B3=95=EF=BC=9B=E4=B8=80=E4=BA=9B=E7=AD=89=E5=BE=85=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E6=97=B6=E8=BF=94=E5=9B=9E=E5=AF=B9=E8=B1=A1=E6=9C=AC?= =?UTF-8?q?=E8=BA=AB=EF=BC=9B=E7=82=B9=E5=87=BB=E6=88=90=E5=8A=9F=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=85=83=E7=B4=A0=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_pages/chromium_page.py | 5 ++++ DrissionPage/_pages/chromium_page.pyi | 3 ++ DrissionPage/_units/clicker.py | 41 +++++++++++++++++++++---- DrissionPage/_units/clicker.pyi | 20 +++++++++---- DrissionPage/_units/waiter.py | 38 +++++++++++------------ DrissionPage/_units/waiter.pyi | 43 +++++++++++++++++++-------- 6 files changed, 107 insertions(+), 43 deletions(-) diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index aae5dbe..48bcbf9 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -107,6 +107,11 @@ class ChromiumPage(ChromiumBase): """返回所控制的浏览器版本号""" 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,只返回文本 :param path: 保存路径,为None且name不为None时保存在当前路径 diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 589a58d..8178e46 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -61,6 +61,9 @@ class ChromiumPage(ChromiumBase): @property def browser_version(self) -> str: ... + @property + def address(self) -> str: ... + @property def set(self) -> ChromiumPageSetter: ... diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 73de7c3..cb889ee 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -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 @@ -101,11 +101,11 @@ class Clicker(object): lx, ly = self._ele.rect._get_page_coord(vx, vy) self._click(lx, ly, vx, vy) - return True + return self._ele if by_js is not False: self._ele._run_js('this.click();') - return True + return self._ele if Settings.raise_when_click_failed: raise CanNotClickError return False @@ -113,7 +113,7 @@ class Clicker(object): def right(self): """右键单击""" self._ele.owner.scroll.to_see(self._ele) - self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='right') + return self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='right') def middle(self, get_tab=True): """中键单击,默认返回新出现的tab对象 @@ -143,14 +143,14 @@ class Clicker(object): w, h = self._ele.rect.size offset_x = w // 2 offset_y = h // 2 - self._click(*offset_scroll(self._ele, offset_x, offset_y), button=button, count=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): """点击触发下载 @@ -198,6 +198,34 @@ class Clicker(object): return (self._ele.tab.browser.get_mix_tab(tid) if self._ele.tab._type == 'MixTab' else self._ele.tab.browser.get_tab(tid)) + 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 loc_x: 绝对x坐标 @@ -213,3 +241,4 @@ class Clicker(object): 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 diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index 557189b..1511100 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -17,11 +17,13 @@ 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, MixTab, None]: ... @@ -29,9 +31,9 @@ class Clicker(object): 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, @@ -45,9 +47,15 @@ class Clicker(object): def for_new_tab(self, by_js: bool = False, timeout: float = 3) -> Union[ChromiumTab, MixTab]: ... + 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) -> None: ... + count: int = 1) -> ChromiumElement: ... diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index e702946..e8afbd1 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -262,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): """等待指定属性变成包含或不包含指定文本 @@ -403,7 +403,7 @@ class ElementWaiter(OriginWaiter): """等待元素从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='等待元素被删除失败。') @@ -411,7 +411,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='等待元素显示失败。') @@ -419,7 +419,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='等待元素隐藏失败。') @@ -435,7 +435,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='等待元素不被覆盖失败。') @@ -443,7 +443,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='等待元素变成可用失败。') @@ -451,7 +451,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='等待元素变成不可用失败。') @@ -459,14 +459,14 @@ class ElementWaiter(OriginWaiter): """等待当前元素变成不可用或从DOM移除 :param timeout: 超时时间(秒),为None使用元素所在页面timeout属性 :param raise_err: 等待失败时是否报错,为None时根据Settings设置 - :return: 是否等待成功 + :return: 成功返回元素对象,失败返回False """ if timeout is None: 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: @@ -479,7 +479,7 @@ class ElementWaiter(OriginWaiter): :param timeout: 超时时间(秒),为None使用元素所在页面timeout属性 :param gap: 检测间隔时间 :param raise_err: 等待失败时是否报错,为None时根据Settings设置 - :return: 是否等待成功 + :return: 成功返回元素对象,失败返回False """ if timeout is None: timeout = self._timeout @@ -498,7 +498,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 @@ -512,7 +512,7 @@ class ElementWaiter(OriginWaiter): :param wait_moved: 是否等待元素运动结束 :param timeout: 超时时间(秒),为None使用元素所在页面timeout属性 :param raise_err: 等待失败时是否报错,为None时根据Settings设置 - :return: 是否等待成功 + :return: 成功返回元素对象,失败返回False """ t1 = perf_counter() r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。') @@ -536,11 +536,11 @@ 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._timeout @@ -548,7 +548,7 @@ class ElementWaiter(OriginWaiter): 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 '等待元素状态改变失败(等待{}秒)。' diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index 69e33de..9e3a70f 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -5,7 +5,7 @@ @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 @@ -92,6 +92,12 @@ class TabWaiter(BaseWaiter): 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): _owner: Union[ChromiumPage, MixPage] = ... @@ -104,6 +110,12 @@ class PageWaiter(TabWaiter): 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): _owner: ChromiumElement = ... @@ -116,36 +128,37 @@ class ElementWaiter(OriginWaiter): @property def _timeout(self) -> float: ... - def deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ... + def deleted(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ... - def displayed(self, timeout: float = None, raise_err: bool = None) -> bool: ... + def displayed(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ... - def hidden(self, timeout: float = None, raise_err: bool = None) -> bool: ... + def hidden(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 covered(self, timeout: float = None, raise_err: bool = None) -> Union[False, int]: ... - def not_covered(self, timeout: float = None, raise_err: bool = None) -> bool: ... + def not_covered(self, timeout: float = None, raise_err: bool = None) -> Union[ChromiumElement, False]: ... - def enabled(self, 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) -> bool: ... + 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) -> bool: ... + 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): @@ -154,3 +167,9 @@ class FrameWaiter(BaseWaiter, ElementWaiter): 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]: ... From 2dd44d1982c124952d8eeaf9ab2f0fbf804ee78c Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 19 Jul 2024 17:35:55 +0800 Subject: [PATCH 035/114] =?UTF-8?q?4.1.0.0b10=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/driver.py | 4 +++- DrissionPage/_configs/chromium_options.py | 14 +++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index a241082..4a88739 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b9' +__version__ = '4.1.0.0b10' diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 22b2b31..bf2916b 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -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): @@ -206,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 diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 0d7bed9..bec890e 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -214,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 From 982dee6246a77b5843be56ad475679533f32688f Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 21 Jul 2024 23:51:15 +0800 Subject: [PATCH 036/114] =?UTF-8?q?=E4=BC=98=E5=8C=96waiter=EF=BC=8C?= =?UTF-8?q?=E5=BE=85=E6=B5=8B=E8=AF=95=EF=BC=9B=E4=BF=AE=E5=A4=8D=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E6=83=85=E5=86=B5=E4=B8=8B=E8=8E=B7=E5=8F=96=E4=B8=8D?= =?UTF-8?q?=E5=88=B0sr=E9=87=8C=E7=9A=84=E5=85=83=E7=B4=A0=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9B=E4=BC=98=E5=8C=96css=5Fpath=EF=BC=9BDriver?= =?UTF-8?q?=E7=9A=84=5Fstopped=E6=94=B9=E4=B8=BAis=5Frunning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/driver.py | 18 ++++---- DrissionPage/_base/driver.pyi | 5 +- DrissionPage/_elements/chromium_element.py | 14 +++++- DrissionPage/_elements/session_element.py | 4 ++ DrissionPage/_pages/chromium_base.py | 2 +- DrissionPage/_units/listener.py | 8 ++-- DrissionPage/_units/waiter.py | 54 ++++++++++++++-------- 8 files changed, 68 insertions(+), 39 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 4a88739..2617ea8 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b10' +__version__ = '4.1.0.0b11' diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index bf2916b..c95485a 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -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 @@ -44,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 = {} @@ -87,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) @@ -108,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() @@ -146,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: @@ -184,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) @@ -198,7 +198,7 @@ class Driver(object): 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: @@ -221,10 +221,10 @@ 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 diff --git a/DrissionPage/_base/driver.pyi b/DrissionPage/_base/driver.pyi index b3e09f0..be5f7c1 100644 --- a/DrissionPage/_base/driver.pyi +++ b/DrissionPage/_base/driver.pyi @@ -6,7 +6,7 @@ @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 @@ -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 diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 5409f46..539a028 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -903,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);''' @@ -913,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) { @@ -1243,7 +1248,12 @@ 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, diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index d746dd5..29257c8 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -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: diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 229ff0f..9ae334b 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -374,7 +374,7 @@ 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): diff --git a/DrissionPage/_units/listener.py b/DrissionPage/_units/listener.py index 504dc54..0166e15 100644 --- a/DrissionPage/_units/listener.py +++ b/DrissionPage/_units/listener.py @@ -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)] diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index e8afbd1..d988c01 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -285,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._owner.timeout end_time = perf_counter() + timeout while perf_counter() < end_time: - if arg == 'url': - val = self._owner.url - elif arg == 'title': - val = self._owner.title - else: - raise ValueError - if (not exclude and text in val) or (exclude and text not in val): + if do(): return True sleep(.05) @@ -313,19 +321,18 @@ class BaseWaiter(OriginWaiter): :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :return: 是否等待成功 """ - if timeout != 0: - if timeout is None or timeout is True: - timeout = self._owner.timeout - end_time = perf_counter() + timeout - while perf_counter() < end_time: - if self._owner._is_loading == start: - return True - sleep(gap) + 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._owner._is_loading == start: + return True + sleep(gap) - if raise_err is True or Settings.raise_when_wait_failed is True: - raise WaitTimeoutError(f'等待页面加载失败(等待{timeout}秒)。') - else: - return False + if raise_err is True or Settings.raise_when_wait_failed is True: + raise WaitTimeoutError(f'等待页面加载失败(等待{timeout}秒)。') + else: + return False class TabWaiter(BaseWaiter): @@ -461,6 +468,9 @@ class ElementWaiter(OriginWaiter): :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :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._timeout end_time = perf_counter() + timeout @@ -483,6 +493,9 @@ class ElementWaiter(OriginWaiter): """ if timeout is None: timeout = self._timeout + if timeout <= 0: + timeout = .1 + end_time = perf_counter() + timeout while perf_counter() < end_time: try: @@ -514,9 +527,10 @@ class ElementWaiter(OriginWaiter): :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :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 From 0c1fc25fafdf9b508b440d81b21254c8a88db12b Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 23 Jul 2024 07:26:33 +0800 Subject: [PATCH 037/114] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_pages/chromium_base.py | 2 +- DrissionPage/_pages/chromium_frame.py | 2 +- DrissionPage/_units/waiter.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 9ae334b..d04785b 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -168,7 +168,7 @@ class ChromiumBase(BasePage): result = False if result: - r = self._run_cdp('Page.getFrameTree') + r = self._run_cdp('Page.getFrameTree', _ignore=PageDisconnectedError) for i in findall(r"'id': '(.*?)'", str(r)): self.browser._frames[i] = self.tab_id diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 2f30aff..ca49f58 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -155,7 +155,7 @@ class ChromiumFrame(ChromiumBase): self._root_id = self.doc_ele._obj_id - r = self._run_cdp('Page.getFrameTree') + r = self._run_cdp('Page.getFrameTree', _ignore=PageDisconnectedError) for i in findall(r"'id': '(.*?)'", str(r)): self.browser._frames[i] = self.tab_id return True diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index d988c01..1016a5f 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -60,7 +60,7 @@ class BrowserWaiter(OriginWaiter): :return: 成功返回任务对象,失败返回False """ if not self._owner._dl_mgr._running: - raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。') + 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 @@ -130,7 +130,7 @@ class BaseWaiter(OriginWaiter): end_time = perf_counter() + timeout ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout) timeout = end_time - perf_counter() - if timeout <= 0: + if not ele: if raise_err is True or Settings.raise_when_wait_failed is True: raise WaitTimeoutError(f'等待元素显示失败(等待{timeout}秒)。') else: From 6255a096ba1b6e98752673ec5819bf0635b56f41 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 24 Jul 2024 11:53:11 +0800 Subject: [PATCH 038/114] =?UTF-8?q?ele.click()=E5=8F=96=E6=B6=88=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E8=BD=A8=E8=BF=B9=EF=BC=9Bwait.new=5Ftab()=E7=9A=84cu?= =?UTF-8?q?rr=5Ftab=E5=8F=82=E6=95=B0=E5=8F=AF=E6=8E=A5=E6=94=B6Tab?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=EF=BC=9B=E4=BC=98=E5=8C=96=E7=AD=89=E5=BE=85?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E5=A4=8D=E8=AE=BE=E7=BD=AE=E5=90=8C=E6=97=B6?= =?UTF-8?q?ua=E5=92=8C=E6=97=A0=E5=A4=B4=E6=97=B6=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/browser.py | 2 +- DrissionPage/_configs/chromium_options.py | 1 + DrissionPage/_configs/chromium_options.pyi | 1 + DrissionPage/_functions/browser.py | 2 ++ DrissionPage/_functions/elements.py | 2 +- DrissionPage/_functions/web.py | 4 ++-- DrissionPage/_functions/web.pyi | 2 +- DrissionPage/_units/clicker.py | 14 ++++---------- DrissionPage/_units/clicker.pyi | 3 +-- DrissionPage/_units/waiter.py | 17 +++++++++++------ DrissionPage/_units/waiter.pyi | 5 ++++- 11 files changed, 29 insertions(+), 24 deletions(-) diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index b01aec3..a22810e 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -81,7 +81,7 @@ class Chromium(object): 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 ( + if (not self._chromium_options._ua_set and 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) diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index bec890e..35e14c2 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -22,6 +22,7 @@ class ChromiumOptions(object): self._prefs_to_del = [] self.clear_file_flags = False self._is_headless = False + self._ua_set = False if read_file is False: ini_path = False diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index 10ba9a8..06875a1 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -34,6 +34,7 @@ class ChromiumOptions(object): _retry_times: int = ... _retry_interval: float = ... _is_headless: bool = ... + _ua_set: bool = ... def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): ... diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index 9acae5d..9958bf0 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -81,6 +81,8 @@ def get_launch_args(opt): user_path = f'--user-data-dir={Path(i[16:]).absolute()}' result.add(user_path) continue + elif i.startswith('--user-agent='): + opt._ua_set = True result.add(i) if not user_path and not opt.system_user_path: diff --git a/DrissionPage/_functions/elements.py b/DrissionPage/_functions/elements.py index cccb8da..c91abfe 100644 --- a/DrissionPage/_functions/elements.py +++ b/DrissionPage/_functions/elements.py @@ -393,7 +393,7 @@ def get_eles(locators, owner, any_one=False, first_ele=True, timeout=10): for loc in locators: if res[loc] is not False: continue - ele = owner.ele(loc, timeout=0) if first_ele else owner.eles(loc, timeout=0) + ele = owner._ele(loc, timeout=0, raise_err=False, index=1 if first_ele else None) if ele: res[loc] = ele if any_one: diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index f86605e..ccbc84c 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -136,7 +136,7 @@ def offset_scroll(ele, offset_x, offset_y): :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 @@ -150,7 +150,7 @@ def offset_scroll(ele, offset_x, offset_y): 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 lx, ly, cx, cy + return cx, cy def make_absolute_link(link, baseURI=None): diff --git a/DrissionPage/_functions/web.pyi b/DrissionPage/_functions/web.pyi index c7a40db..d09dfac 100644 --- a/DrissionPage/_functions/web.pyi +++ b/DrissionPage/_functions/web.pyi @@ -24,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[int, int, int, int]: ... +def offset_scroll(ele: ChromiumElement, offset_x: float, offset_y: float) -> Tuple[int, int]: ... def make_absolute_link(link: str, baseURI: str = None) -> str: ... diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index cb889ee..e020278 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -91,16 +91,13 @@ class Clicker(object): 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(lx, ly, vx, vy) + self._click(vx, vy) return self._ele if by_js is not False: @@ -113,7 +110,7 @@ class Clicker(object): def right(self): """右键单击""" self._ele.owner.scroll.to_see(self._ele) - return self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='right') + return self._click(*self._ele.rect.viewport_click_point, button='right') def middle(self, get_tab=True): """中键单击,默认返回新出现的tab对象 @@ -122,7 +119,7 @@ class Clicker(object): """ self._ele.owner.scroll.to_see(self._ele) curr_tid = self._ele.tab.browser.tab_ids[0] - self._click(*self._ele.rect.click_point, *self._ele.rect.viewport_click_point, button='middle') + self._click(*self._ele.rect.viewport_click_point, button='middle') if get_tab: tid = self._ele.tab.browser.wait.new_tab(curr_tab=curr_tid) if not tid: @@ -226,17 +223,14 @@ class Clicker(object): 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): + def _click(self, view_x, view_y, button='left', count=1): """实施点击 - :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.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, diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index 1511100..325b2ac 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -53,8 +53,7 @@ class Clicker(object): 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, + def _click(self, view_x: float, view_y: float, button: str = 'left', diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index d988c01..101dd01 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -35,16 +35,19 @@ 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 curr_tab: 指定当前最新的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] + if not curr_tab: + curr_tab = self._owner.tab_ids[0] + elif hasattr(curr_tab, '_type'): + curr_tab = curr_tab.tab_id 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: + if curr_tab != latest_tid: return latest_tid sleep(.01) @@ -193,7 +196,7 @@ class BaseWaiter(OriginWaiter): by = ('id', 'xpath', 'link text', 'partial link text', 'name', 'tag name', 'class name', 'css selector') locators = ((get_loc(locators)[1],) if (isinstance(locators, str) or isinstance(locators, tuple) and locators[0] in by and len(locators) == 2) - else [get_loc(l)[1] for l in locators]) + else [get_loc(x)[1] for x in locators]) method = any if any_one else all timeout = self._owner.timeout if timeout is None else timeout @@ -321,7 +324,8 @@ class BaseWaiter(OriginWaiter): :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :return: 是否等待成功 """ - timeout = timeout if timeout is not None else self._owner.timeout + if timeout is None: + timeout = self._owner.timeout timeout = .1 if timeout <= 0 else timeout end_time = perf_counter() + timeout while perf_counter() < end_time: @@ -527,7 +531,8 @@ class ElementWaiter(OriginWaiter): :param raise_err: 等待失败时是否报错,为None时根据Settings设置 :return: 成功返回元素对象,失败返回False """ - timeout = timeout if timeout is not None else self._timeout + if timeout is None: + timeout = self._timeout t1 = perf_counter() r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。') r = self.stop_moving(timeout=timeout - perf_counter() + t1) if wait_moved and r else r diff --git a/DrissionPage/_units/waiter.pyi b/DrissionPage/_units/waiter.pyi index 9e3a70f..2e36810 100644 --- a/DrissionPage/_units/waiter.pyi +++ b/DrissionPage/_units/waiter.pyi @@ -34,7 +34,10 @@ class BrowserWaiter(OriginWaiter): 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 new_tab(self, + timeout: float = None, + curr_tab: Union[str, ChromiumTab] = None, + raise_err: bool = None) -> Union[str, bool]: ... def all_downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ... From 0b40011a00f6ab6abef4cfb634f5b3802717acbb Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 24 Jul 2024 15:48:21 +0800 Subject: [PATCH 039/114] 4.1.0.0b11 --- DrissionPage/_units/waiter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 101dd01..002bede 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -133,7 +133,7 @@ class BaseWaiter(OriginWaiter): end_time = perf_counter() + timeout ele = self._owner._ele(loc_or_ele, raise_err=False, timeout=timeout) timeout = end_time - perf_counter() - if timeout <= 0: + if timeout <= 0 or not ele: if raise_err is True or Settings.raise_when_wait_failed is True: raise WaitTimeoutError(f'等待元素显示失败(等待{timeout}秒)。') else: From 0812f5233f8d9f6ace0f228067b0893ce30c2106 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 24 Jul 2024 17:43:01 +0800 Subject: [PATCH 040/114] =?UTF-8?q?4.1.0.0b12=E4=BF=AE=E5=A4=8DChromiumPag?= =?UTF-8?q?e=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_units/setter.py | 36 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 2617ea8..cd53866 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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' +__version__ = '4.1.0.0b12' diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 972ab75..eea1870 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -428,6 +428,34 @@ class TabSetter(ChromiumBaseSetter): class ChromiumPageSetter(TabSetter): + def NoneElement_value(self, value=None, on_off=True): + """设置空元素是否返回设定值 + :param value: 返回的设定值 + :param on_off: 是否启用 + :return: None + """ + super().NoneElement_value(value, on_off) + self._owner.browser._none_ele_return_value = on_off + self._owner.browser._none_ele_value = value + + def retry_times(self, times): + """设置连接失败重连次数""" + super().retry_times(times) + self._owner.browser.retry_times = times + + def retry_interval(self, interval): + """设置连接失败重连间隔""" + super().retry_interval(interval) + self._owner.browser.retry_interval = interval + + def download_path(self, path): + """设置下载路径 + :param path: 下载路径 + :return: None + """ + super().download_path(path) + self._owner.browser._download_path = self._owner._download_path + def auto_handle_alert(self, on_off=True, accept=True, all_tabs=False): """设置是否启用自动处理弹窗 :param on_off: bool表示开或关 @@ -625,18 +653,20 @@ class LoadMode(object): if value.lower() not in ('normal', 'eager', 'none'): raise ValueError("只能选择 'normal', 'eager', 'none'。") self._owner._load_mode = value + if self._owner._type in ('ChromiumPage', 'MixPage'): + self._owner.browser._load_mode = value def normal(self): """设置页面加载策略为normal""" - self._owner._load_mode = 'normal' + self.__call__('normal') def eager(self): """设置页面加载策略为eager""" - self._owner._load_mode = 'eager' + self.__call__('eager') def none(self): """设置页面加载策略为none""" - self._owner._load_mode = 'none' + self.__call__('none') class PageScrollSetter(object): From 955c72d61c178d9271f84593bca589c6745dff2d Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 26 Jul 2024 19:07:30 +0800 Subject: [PATCH 041/114] =?UTF-8?q?4.1.0.0b13Frame=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8D=95=E4=BE=8B=EF=BC=9Bparent()=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0timeout=E5=8F=82=E6=95=B0=EF=BC=9Bsr=E5=86=85=E6=89=BE?= =?UTF-8?q?=E5=85=83=E7=B4=A0=E5=85=83=E7=B4=A0=E5=8A=A0=E4=B8=8Atimeout?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=9B=E4=BF=AE=E5=A4=8D=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/base.py | 5 +- DrissionPage/_base/base.pyi | 8 ++- DrissionPage/_elements/chromium_element.py | 72 +++++++++++++-------- DrissionPage/_elements/chromium_element.pyi | 21 +++--- DrissionPage/_functions/elements.py | 1 + DrissionPage/_functions/tools.py | 2 + DrissionPage/_functions/web.py | 8 ++- DrissionPage/_pages/chromium_frame.py | 24 ++++++- DrissionPage/_pages/chromium_frame.pyi | 4 +- DrissionPage/_units/setter.py | 1 + 11 files changed, 102 insertions(+), 46 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index cd53866..a8db515 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b12' +__version__ = '4.1.0.0b13' diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 8fa9a7c..32fb08e 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -136,10 +136,11 @@ class DrissionElement(BaseElement): 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): + def parent(self, level_or_loc=1, index=1, timeout=None): """返回上面某一级父元素,可指定层数或用查询语法定位 :param level_or_loc: 第几级父元素,1开始,或定位符 :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 + :param timeout: 时间(秒) :return: 上级元素对象 """ if isinstance(level_or_loc, int): @@ -154,7 +155,7 @@ class DrissionElement(BaseElement): else: raise TypeError('level_or_loc参数只能是tuple、int或str。') - return self._ele(loc, timeout=0, relative=True, raise_err=False, method='parent()') + return self._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()') def child(self, locator='', index=1, timeout=None, ele_only=True): """返回直接子元素元素或节点组成的列表,可用查询语法筛选 diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 39cd123..05eb3e1 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -13,9 +13,10 @@ from DownloadKit import DownloadKit from .._elements.none_element import NoneElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList +from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage -from .._pages.session_page import SessionPage from .._pages.mix_page import MixPage +from .._pages.session_page import SessionPage class BaseParser(object): @@ -82,6 +83,8 @@ class BaseElement(BaseParser): def nexts(self): ... + def get_frame(self, loc_or_ind, timeout=None) -> ChromiumFrame: ... + class DrissionElement(BaseElement): @@ -103,7 +106,8 @@ class DrissionElement(BaseElement): def parent(self, level_or_loc: Union[tuple, str, int] = 1, - index: int = 1) -> Union[DrissionElement, None]: ... + index: int = 1, + timeout: float = None) -> Union[DrissionElement, None]: ... def child(self, locator: Union[Tuple[str, str], str, int] = '', diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 539a028..810d1df 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -232,13 +232,14 @@ class ChromiumElement(DrissionElement): if (is_checked and uncheck) or (not is_checked and not uncheck): self.click() - def parent(self, level_or_loc=1, index=1): + def parent(self, level_or_loc=1, index=1, timeout=0): """返回上面某一级父元素,可指定层数或用查询语法定位 :param level_or_loc: 第几级父元素,1开始,或定位符 :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 + :param timeout: 查找超时时间(秒) :return: 上级元素对象 """ - return super().parent(level_or_loc, index) + return super().parent(level_or_loc, index, timeout=timeout) def child(self, locator='', index=1, timeout=None, ele_only=True): """返回当前元素的一个符合条件的直接子元素,可用查询语法筛选,可指定返回筛选结果的第几个 @@ -1041,10 +1042,11 @@ class ShadowRoot(BaseElement): Thread(target=run_js, args=(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args)).start() - def parent(self, level_or_loc=1, index=1): + def parent(self, level_or_loc=1, index=1, timeout=0): """返回上面某一级父元素,可指定层数或用查询语法定位 :param level_or_loc: 第几级父元素,或定位符 :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果 + :param timeout: 查找超时时间(秒) :return: ChromiumElement对象 """ if isinstance(level_or_loc, int): @@ -1061,12 +1063,13 @@ class ShadowRoot(BaseElement): else: raise TypeError('level_or_loc参数只能是tuple、int或str。') - return self.parent_ele._ele(loc, timeout=0, relative=True, raise_err=False, method='parent()') + return self.parent_ele._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()') - def child(self, locator='', index=1): + def child(self, locator='', index=1, timeout=None): """返回直接子元素元素或节点组成的列表,可用查询语法筛选 :param locator: 用于筛选的查询语法 :param index: 第几个查询结果,1开始 + :param timeout: 查找超时时间(秒) :return: 直接子元素或节点文本组成的列表 """ if not locator: @@ -1078,14 +1081,16 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') loc = f'xpath:./{loc}' - ele = self._ele(loc, index=index, relative=True) + ele = self._ele(loc, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'child()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'child()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def next(self, locator='', index=1): + def next(self, locator='', index=1, timeout=None): """返回当前元素后面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 :param locator: 用于筛选的查询语法 :param index: 第几个查询结果,1开始 + :param timeout: 查找超时时间(秒) :return: ChromiumElement对象 """ loc = get_loc(locator, True) @@ -1094,15 +1099,17 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./{loc}' - ele = self.parent_ele._ele(xpath, index=index, relative=True) + ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'next()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'next()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def before(self, locator='', index=1): + def before(self, locator='', index=1, timeout=None): """返回文档中当前元素前面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 查找范围不限同级元素,而是整个DOM文档 :param locator: 用于筛选的查询语法 :param index: 前面第几个查询结果,1开始 + :param timeout: 查找超时时间(秒) :return: 本元素前面的某个元素或节点 """ loc = get_loc(locator, True) @@ -1111,23 +1118,27 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./preceding::{loc}' - ele = self.parent_ele._ele(xpath, index=index, relative=True) + ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'before()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'before()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def after(self, locator='', index=1): + def after(self, locator='', index=1, timeout=None): """返回文档中此当前元素后面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 查找范围不限同级元素,而是整个DOM文档 :param locator: 用于筛选的查询语法 :param index: 后面第几个查询结果,1开始 + :param timeout: 查找超时时间(秒) :return: 本元素后面的某个元素或节点 """ - nodes = self.afters(locator=locator) - return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', {'locator': locator, 'index': index}) + nodes = self.afters(locator=locator, timeout=timeout) + return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def children(self, locator=''): + def children(self, locator='', timeout=None): """返回当前元素符合条件的直接子元素或节点组成的列表,可用查询语法筛选 :param locator: 用于筛选的查询语法 + :param timeout: 查找超时时间(秒) :return: 直接子元素或节点文本组成的列表 """ if not locator: @@ -1139,11 +1150,12 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') loc = f'xpath:./{loc}' - return self._ele(loc, index=None, relative=True) + return self._ele(loc, index=None, relative=True, timeout=timeout) - def nexts(self, locator=''): + def nexts(self, locator='', timeout=None): """返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选 :param locator: 用于筛选的查询语法 + :param timeout: 查找超时时间(秒) :return: ChromiumElement对象组成的列表 """ loc = get_loc(locator, True) @@ -1152,12 +1164,13 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./{loc}' - return self.parent_ele._ele(xpath, index=None, relative=True) + return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) - def befores(self, locator=''): + def befores(self, locator='', timeout=None): """返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选 查找范围不限同级元素,而是整个DOM文档 :param locator: 用于筛选的查询语法 + :param timeout: 查找超时时间(秒) :return: 本元素前面的元素或节点组成的列表 """ loc = get_loc(locator, True) @@ -1166,18 +1179,19 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./preceding::{loc}' - return self.parent_ele._ele(xpath, index=None, relative=True) + return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) - def afters(self, locator=''): + def afters(self, locator='', timeout=None): """返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选 查找范围不限同级元素,而是整个DOM文档 :param locator: 用于筛选的查询语法 + :param timeout: 查找超时时间(秒) :return: 本元素后面的元素或节点组成的列表 """ eles1 = self.nexts(locator) loc = get_loc(locator, True)[1].lstrip('./') xpath = f'xpath:./following::{loc}' - return eles1 + self.parent_ele._ele(xpath, index=None, relative=True) + return eles1 + self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) def ele(self, locator, index=1, timeout=None): """返回当前元素下级符合条件的一个元素 @@ -1251,7 +1265,10 @@ class ShadowRoot(BaseElement): 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)'): + if c in ('html:nth-child(1)', 'html:nth-child(1)>body:nth-child(1)', + 'html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'): + continue + elif 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: @@ -1265,7 +1282,8 @@ class ShadowRoot(BaseElement): else: node_ids = [self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] for i in css] - if 0 in node_ids: + node_ids = [i for i in node_ids if i] + if not node_ids: return None r = make_chromium_eles(self.owner, _ids=node_ids, index=index, is_obj_id=False) return None if r is False else r @@ -1476,6 +1494,8 @@ def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False): else: # 获取全部 nodes = ChromiumElementsList(page=page) for obj_id in _ids: + # if obj_id == 0: + # continue tmp = get_node_func(page, obj_id, ele_only) if tmp is False: return False diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index c04e13d..1af2011 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -101,7 +101,8 @@ class ChromiumElement(DrissionElement): def parent(self, level_or_loc: Union[tuple, str, int] = 1, - index: int = 1) -> ChromiumElement: ... + index: int = 1, + timeout: float = 0) -> ChromiumElement: ... def child(self, locator: Union[Tuple[str, str], str, int] = '', @@ -309,31 +310,31 @@ class ShadowRoot(BaseElement): 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: ... + def parent(self, level_or_loc: Union[str, int] = 1, index: int = 1, timeout: float = 0) -> ChromiumElement: ... def child(self, locator: Union[Tuple[str, str], str] = '', - index: int = 1) -> ChromiumElement: ... + index: int = 1, timeout:float=None) -> ChromiumElement: ... def next(self, locator: Union[Tuple[str, str], str] = '', - index: int = 1) -> ChromiumElement: ... + index: int = 1, timeout:float=None) -> ChromiumElement: ... def before(self, locator: Union[Tuple[str, str], str] = '', - index: int = 1) -> ChromiumElement: ... + index: int = 1, timeout:float=None) -> ChromiumElement: ... def after(self, locator: Union[Tuple[str, str], str] = '', - index: int = 1) -> ChromiumElement: ... + index: int = 1, timeout:float=None) -> ChromiumElement: ... - def children(self, locator: Union[Tuple[str, str], str] = '') -> List[ChromiumElement]: ... + def children(self, locator: Union[Tuple[str, str], str] = '', timeout:float=None) -> List[ChromiumElement]: ... - def nexts(self, locator: Union[Tuple[str, str], str] = '') -> List[ChromiumElement]: ... + def nexts(self, locator: Union[Tuple[str, str], str] = '', timeout:float=None) -> List[ChromiumElement]: ... - def befores(self, locator: Union[Tuple[str, str], str] = '') -> List[ChromiumElement]: ... + def befores(self, locator: Union[Tuple[str, str], str] = '', timeout:float=None) -> List[ChromiumElement]: ... - def afters(self, locator: Union[Tuple[str, str], str] = '') -> List[ChromiumElement]: ... + def afters(self, locator: Union[Tuple[str, str], str] = '', timeout:float=None) -> List[ChromiumElement]: ... def ele(self, locator: Union[Tuple[str, str], str], diff --git a/DrissionPage/_functions/elements.py b/DrissionPage/_functions/elements.py index c91abfe..2a459b4 100644 --- a/DrissionPage/_functions/elements.py +++ b/DrissionPage/_functions/elements.py @@ -170,6 +170,7 @@ class SessionFilter(SessionFilterOne): """ self._list = _text_all(self._list, SessionElementsList(page=self._list._page), text=text, fuzzy=fuzzy, contain=contain) + return self def _get_attr(self, name, value, method, equal=True): """返回通过某个方法可获得某个值的元素 diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 29ffb9e..ed97953 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -226,6 +226,8 @@ def raise_error(result, ignore=None, user=False): r = StorageError() elif error == 'Sanitizing cookie failed': r = CookieFormatError(f'cookie格式不正确:{result["args"]}') + elif error == 'Invalid header name': + r = ValueError(f'header名不正确。\n参数:{result["args"]["headers"]}') elif error == 'Given expression does not evaluate to a function': r = JavaScriptError(f'传入的js无法解析成函数:\n{result["args"]["functionDeclaration"]}') elif error.endswith("' wasn't found"): diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index ccbc84c..98cd105 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -372,11 +372,15 @@ def format_headers(txt): """ if isinstance(txt, (dict, CaseInsensitiveDict)): for k, v in txt.items(): - txt[k] = str(v) + if k in (':method', ':scheme', ':authority', ':path'): + txt.pop(k) + else: + txt[k] = str(v) return txt headers = {} for header in txt.split('\n'): if header: name, value = header.split(': ', maxsplit=1) - headers[name] = value + if name not in (':method', ':scheme', ':authority', ':path'): + headers[name] = value return headers diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 2f30aff..62264bd 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -10,6 +10,7 @@ from re import search, findall, DOTALL from time import sleep, perf_counter from .._elements.chromium_element import ChromiumElement +from .._functions.settings import Settings from .._pages.chromium_base import ChromiumBase from .._units.listener import FrameListener from .._units.rect import FrameRect @@ -21,6 +22,24 @@ from ..errors import ContextLostError, ElementLostError, PageDisconnectedError, class ChromiumFrame(ChromiumBase): + _Frames = {} + + def __new__(cls, owner, ele, info=None): + """ + :param owner: frame所在的页面对象 + :param ele: frame所在元素 + :param info: frame所在元素信息 + """ + node = info['node'] if info else owner._run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node'] + if Settings.singleton_tab_obj and node['frameId'] in cls._Frames: + r = cls._Frames[node['frameId']] + while not hasattr(r, '_frame_id'): + sleep(.1) + return r + r = object.__new__(cls) + cls._Frames[node['frameId']] = r + return r + def __init__(self, owner, ele, info=None): """ :param owner: frame所在的页面对象 @@ -33,7 +52,7 @@ class ChromiumFrame(ChromiumBase): self._frame_ele = ele self._reloading = False - node = info['node'] if not info else owner._run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node'] + node = info['node'] if 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 @@ -385,10 +404,11 @@ class ChromiumFrame(ChromiumBase): else: return self.doc_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout) - def parent(self, level_or_loc=1, index=1): + def parent(self, level_or_loc=1, index=1, timeout=0): """返回上面某一级父元素,可指定层数或用查询语法定位 :param level_or_loc: 第几级父元素,1开始,或定位符 :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 + :param timeout: 查找超时时间(秒) :return: 上级元素对象 """ return self.frame_ele.parent(level_or_loc, index) diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index 54e3594..76b9bce 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -21,6 +21,7 @@ from .._units.waiter import FrameWaiter class ChromiumFrame(ChromiumBase): + _Frames: dict = ... def __init__(self, owner: Union[ChromiumTab, ChromiumFrame], @@ -156,7 +157,8 @@ class ChromiumFrame(ChromiumBase): def parent(self, level_or_loc: Union[Tuple[str, str], str, int] = 1, - index: int = 1) -> ChromiumElement: ... + index: int = 1, + timeout: float = 0) -> ChromiumElement: ... def prev(self, locator: Union[Tuple[str, str], str, int] = '', diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index eea1870..1c46abd 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -770,6 +770,7 @@ class WindowSetter(object): return self._owner._run_cdp('Browser.getWindowForTarget') except: sleep(.1) + raise RuntimeError('获取窗口信息失败。') def _perform(self, bounds): """执行改变窗口大小操作 From e7ab81092b5d4b9423fdcf0e65d975d7608ca8fa Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 26 Jul 2024 19:44:26 +0800 Subject: [PATCH 042/114] =?UTF-8?q?4.1.0.0b14=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_pages/chromium_frame.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index a8db515..1774967 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b13' +__version__ = '4.1.0.0b14' diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 62264bd..f97dea3 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -194,6 +194,7 @@ class ChromiumFrame(ChromiumBase): def _onFrameDetached(self, **kwargs): """同域变异域""" self.browser._frames.pop(kwargs['frameId'], None) + ChromiumFrame._Frames.pop(kwargs['frameId'], None) if kwargs['frameId'] == self._frame_id: self._reload() @@ -411,7 +412,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找超时时间(秒) :return: 上级元素对象 """ - return self.frame_ele.parent(level_or_loc, index) + return self.frame_ele.parent(level_or_loc, index, timeout=timeout) def prev(self, locator='', index=1, timeout=0, ele_only=True): """返回当前元素前面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 From 0d6a8b67f2528bd659196aea763372e4e22dab4b Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 28 Jul 2024 21:44:42 +0800 Subject: [PATCH 043/114] =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=80=85=EF=BC=9BChromiumPage=E7=9A=84close?= =?UTF-8?q?=5Ftabs()=E9=BB=98=E8=AE=A4=E6=8C=87=E5=AE=9A=E8=87=AA=E5=B7=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_pages/chromium_page.py | 2 +- DrissionPage/_units/scroller.py | 32 ++++++--- DrissionPage/_units/scroller.pyi | 102 ++++++++++++++++++++------- 3 files changed, 102 insertions(+), 34 deletions(-) diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index 48bcbf9..d0ddee4 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -170,7 +170,7 @@ class ChromiumPage(ChromiumBase): :param others: 是否关闭指定标签页之外的 :return: None """ - self.browser.close_tabs(tabs_or_ids=tabs_or_ids, others=others) + self.browser.close_tabs(tabs_or_ids=tabs_or_ids or self.tab_id, others=others) def quit(self, timeout=5, force=True): """关闭浏览器 diff --git a/DrissionPage/_units/scroller.py b/DrissionPage/_units/scroller.py index bc5f334..84e6e04 100644 --- a/DrissionPage/_units/scroller.py +++ b/DrissionPage/_units/scroller.py @@ -11,38 +11,43 @@ from time import sleep, perf_counter class Scroller(object): """用于滚动的对象""" - def __init__(self, ele): + def __init__(self, owner): """ - :param ele: 元素对象 + :param owner: 元素对象 """ - self._driver = ele + self._owner = owner 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) + self._owner._run_js(js) self._wait_scrolled() def to_top(self): """滚动到顶端,水平位置不变""" self._run_js('{}.scrollTo({}.scrollLeft, 0);') + return self._owner def to_bottom(self): """滚动到底端,水平位置不变""" self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight);') + return self._owner def to_half(self): """滚动到垂直中间位置,水平位置不变""" self._run_js('{}.scrollTo({}.scrollLeft, {}.scrollHeight/2);') + return self._owner def to_rightmost(self): """滚动到最右边,垂直位置不变""" self._run_js('{}.scrollTo({}.scrollWidth, {}.scrollTop);') + return self._owner def to_leftmost(self): """滚动到最左边,垂直位置不变""" self._run_js('{}.scrollTo(0, {}.scrollTop);') + return self._owner def to_location(self, x, y): """滚动到指定位置 @@ -51,6 +56,7 @@ class Scroller(object): :return: None """ self._run_js(f'{{}}.scrollTo({x}, {y});') + return self._owner def up(self, pixel=300): """向上滚动若干像素,水平位置不变 @@ -59,6 +65,7 @@ class Scroller(object): """ pixel = -pixel self._run_js(f'{{}}.scrollBy(0, {pixel});') + return self._owner def down(self, pixel=300): """向下滚动若干像素,水平位置不变 @@ -66,6 +73,7 @@ class Scroller(object): :return: None """ self._run_js(f'{{}}.scrollBy(0, {pixel});') + return self._owner def left(self, pixel=300): """向左滚动若干像素,垂直位置不变 @@ -74,6 +82,7 @@ class Scroller(object): """ pixel = -pixel self._run_js(f'{{}}.scrollBy({pixel}, 0);') + return self._owner def right(self, pixel=300): """向右滚动若干像素,垂直位置不变 @@ -81,13 +90,14 @@ class Scroller(object): :return: None """ self._run_js(f'{{}}.scrollBy({pixel}, 0);') + return self._owner def _wait_scrolled(self): """等待滚动结束""" if not self._wait_complete: return - owner = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver + owner = self._owner.owner if self._owner._type == 'ChromiumElement' else self._owner r = owner._run_cdp('Page.getLayoutMetrics') x = r['layoutViewport']['pageX'] y = r['layoutViewport']['pageY'] @@ -112,11 +122,13 @@ class ElementScroller(Scroller): :param center: 是否尽量滚动到页面正中,为None时如果被遮挡,则滚动到页面正中 :return: None """ - self._driver.owner.scroll.to_see(self._driver, center=center) + self._owner.owner.scroll.to_see(self._owner, center=center) + return self._owner def to_center(self): """元素尽量滚动到视口中间""" - self._driver.owner.scroll.to_see(self._driver, center=True) + self._owner.owner.scroll.to_see(self._owner, center=True) + return self._owner class PageScroller(Scroller): @@ -134,8 +146,9 @@ class PageScroller(Scroller): :param center: 是否尽量滚动到页面正中,为None时如果被遮挡,则滚动到页面正中 :return: None """ - ele = self._driver._ele(loc_or_ele) + ele = self._owner._ele(loc_or_ele) self._to_see(ele, center) + return self._owner def _to_see(self, ele, center): """执行滚动页面直到元素可见 @@ -173,5 +186,6 @@ class FrameScroller(PageScroller): :param center: 是否尽量滚动到页面正中,为None时如果被遮挡,则滚动到页面正中 :return: None """ - ele = self._driver._ele(loc_or_ele) + ele = self._owner._ele(loc_or_ele) self._to_see(ele, center) + return self._owner diff --git a/DrissionPage/_units/scroller.pyi b/DrissionPage/_units/scroller.pyi index e48c976..4aab16b 100644 --- a/DrissionPage/_units/scroller.pyi +++ b/DrissionPage/_units/scroller.pyi @@ -9,14 +9,19 @@ from typing import Union 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 Scroller(object): - def __init__(self, page_or_ele: Union[ChromiumBase, ChromiumElement]): - self._t1: str = ... - self._t2: str = ... - self._driver: Union[ChromiumBase, ChromiumElement] = ... - self._wait_complete: bool = ... + _t1: str = ... + _t2: str = ... + _owner: Union[ChromiumBase, ChromiumElement] = ... + _wait_complete: bool = ... + + def __init__(self, owner: Union[ChromiumBase, ChromiumElement]): ... def _run_js(self, js: str): ... @@ -45,33 +50,82 @@ class Scroller(object): class ElementScroller(Scroller): - def to_see(self, center: Union[bool, None] = None) -> None: ... + def to_see(self, center: Union[bool, None] = None) -> ChromiumElement: ... - def to_center(self) -> None: ... + def to_center(self) -> ChromiumElement: ... + + def to_top(self) -> ChromiumElement: ... + + def to_bottom(self) -> ChromiumElement: ... + + def to_half(self) -> ChromiumElement: ... + + def to_rightmost(self) -> ChromiumElement: ... + + def to_leftmost(self) -> ChromiumElement: ... + + def to_location(self, x: int, y: int) -> ChromiumElement: ... + + def up(self, pixel: int = 300) -> ChromiumElement: ... + + def down(self, pixel: int = 300) -> ChromiumElement: ... + + def left(self, pixel: int = 300) -> ChromiumElement: ... + + def right(self, pixel: int = 300) -> ChromiumElement: ... class PageScroller(Scroller): - def __init__(self, owner: ChromiumBase): ... + def __init__(self, owner: Union[ChromiumBase, ChromiumElement]): ... - def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: Union[bool, None] = None) -> None: ... + def to_see(self, + loc_or_ele: Union[str, tuple, ChromiumElement], + center: Union[bool, None] = None) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_top(self) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_bottom(self) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_half(self) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_rightmost(self) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_leftmost(self) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def to_location(self, x: int, y: int) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def up(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def down(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def left(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... + + def right(self, pixel: int = 300) -> Union[ChromiumTab, MixTab, ChromiumPage, MixPage]: ... def _to_see(self, ele: ChromiumElement, center: Union[bool, None]) -> None: ... class FrameScroller(PageScroller): - def __init__(self, frame): - """ - :param frame: ChromiumFrame对象 - """ - self._driver = frame.doc_ele - self._t1 = self._t2 = 'this.documentElement' - self._wait_complete = False + def __init__(self, frame: ChromiumFrame): ... - def to_see(self, loc_or_ele, center=None): - """滚动页面直到元素可见 - :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 - :param center: 是否尽量滚动到页面正中,为None时如果被遮挡,则滚动到页面正中 - :return: None - """ - ele = loc_or_ele if isinstance(loc_or_ele, ChromiumElement) else self._driver._ele(loc_or_ele) - self._to_see(ele, center) + def to_top(self) -> ChromiumFrame: ... + + def to_bottom(self) -> ChromiumFrame: ... + + def to_half(self) -> ChromiumFrame: ... + + def to_rightmost(self) -> ChromiumFrame: ... + + def to_leftmost(self) -> ChromiumFrame: ... + + def to_location(self, x: int, y: int) -> ChromiumFrame: ... + + def up(self, pixel: int = 300) -> ChromiumFrame: ... + + def down(self, pixel: int = 300) -> ChromiumFrame: ... + + def left(self, pixel: int = 300) -> ChromiumFrame: ... + + def right(self, pixel: int = 300) -> ChromiumFrame: ... + + def to_see(self, loc_or_ele, center=None) -> ChromiumFrame: ... From 9736f16e8e73bcc4e64a9d872ebbb7e0467f6697 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 29 Jul 2024 16:21:30 +0800 Subject: [PATCH 044/114] =?UTF-8?q?4.1.0.0b15=20quit()=E5=A2=9E=E5=8A=A0de?= =?UTF-8?q?l=5Fdata=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_base/browser.py | 65 +++++++++++++++------------ DrissionPage/_base/browser.pyi | 2 +- DrissionPage/_base/driver.py | 1 + DrissionPage/_functions/browser.py | 1 + DrissionPage/_pages/chromium_page.py | 5 ++- DrissionPage/_pages/chromium_page.pyi | 2 +- 7 files changed, 44 insertions(+), 34 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 1774967..b4bb936 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b14' +__version__ = '4.1.0.0b15' diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index a22810e..b3be05e 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -87,6 +87,7 @@ class Chromium(object): connect_browser(self._chromium_options) s = Session() s.trust_env = False + s.keep_alive = 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) @@ -400,10 +401,11 @@ class Chromium(object): self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) self._driver.set_callback('Target.targetCreated', self._onTargetCreated) - def quit(self, timeout=5, force=False): + def quit(self, timeout=5, force=False, del_data=False): """关闭浏览器 :param timeout: 等待浏览器关闭超时时间(秒) :param force: 是否立刻强制终止进程 + :param del_data: 是否删除用户文件夹 :return: None """ try: @@ -417,39 +419,43 @@ class Chromium(object): 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: + if force: + pids = None try: - Process(pid).kill() + pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']] 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 pids: + from psutil import Process + for pid in pids: + try: + Process(pid).kill() + except: + pass - if ok: - break + 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 + + if del_data and not self._chromium_options.is_auto_port and self._chromium_options.user_data_path: + path = Path(self._chromium_options.user_data_path) + rmtree(path, True) def _get_driver(self, tab_id, owner=None): """新建并返回指定tab id的Driver @@ -555,6 +561,7 @@ def run_browser(chromium_options): try: s = Session() s.trust_env = False + s.keep_alive = False ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'}) if not ws: raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index fa906ed..9bd107c 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -162,6 +162,6 @@ class Chromium(object): def _onTargetDestroyed(self, **kwargs) -> None: ... - def quit(self, timeout: float = 5, force: bool = False) -> None: ... + def quit(self, timeout: float = 5, force: bool = False, del_data: bool = False) -> None: ... def _on_disconnect(self) -> None: ... diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index c95485a..8a75f01 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -285,6 +285,7 @@ class BrowserDriver(Driver): super().__init__(tab_id, tab_type, address, owner) self._control_session = Session() self._control_session.trust_env = False + self._control_session.keep_alive = False def __repr__(self): return f'' diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index 9958bf0..895bd94 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -194,6 +194,7 @@ def test_connect(ip, port, timeout=30): end_time = perf_counter() + timeout s = Session() s.trust_env = False + s.keep_alive = False while perf_counter() < end_time: try: r = s.get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'}) diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index d0ddee4..ef4ee2f 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -172,13 +172,14 @@ class ChromiumPage(ChromiumBase): """ self.browser.close_tabs(tabs_or_ids=tabs_or_ids or self.tab_id, others=others) - def quit(self, timeout=5, force=True): + def quit(self, timeout=5, force=True, del_data=False): """关闭浏览器 :param timeout: 等待浏览器关闭超时时间(秒) :param force: 关闭超时是否强制终止进程 + :param del_data: 是否删除用户文件夹 :return: None """ - self.browser.quit(timeout, force) + self.browser.quit(timeout, force, del_data=del_data) def _on_disconnect(self): """浏览器退出时执行""" diff --git a/DrissionPage/_pages/chromium_page.pyi b/DrissionPage/_pages/chromium_page.pyi index 8178e46..992ced3 100644 --- a/DrissionPage/_pages/chromium_page.pyi +++ b/DrissionPage/_pages/chromium_page.pyi @@ -111,6 +111,6 @@ class ChromiumPage(ChromiumBase): def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], Tuple[Union[str, ChromiumTab]]] = None, others: bool = False) -> None: ... - def quit(self, timeout: float = 5, force: bool = True) -> None: ... + def quit(self, timeout: float = 5, force: bool = True, del_data: bool = False) -> None: ... def _on_disconnect(self) -> None: ... From cf858d557406fc278e965dc74d6f7ffc099f1bcf Mon Sep 17 00:00:00 2001 From: g1879 Date: Tue, 30 Jul 2024 11:19:56 +0800 Subject: [PATCH 045/114] =?UTF-8?q?4.1.0.0b16=E5=A2=9E=E5=8A=A0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E7=9B=91=E5=90=AC=EF=BC=9B=E4=BC=98=E5=8C=96?= =?UTF-8?q?Frame=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_pages/chromium_base.py | 9 ++++ DrissionPage/_pages/chromium_base.pyi | 5 +++ DrissionPage/_pages/chromium_frame.py | 4 +- DrissionPage/_pages/mix_page.py | 5 ++- DrissionPage/_pages/mix_page.pyi | 2 +- DrissionPage/_units/console.py | 61 +++++++++++++++++++++++++++ DrissionPage/_units/console.pyi | 45 ++++++++++++++++++++ DrissionPage/_units/listener.py | 2 +- README.md | 30 +++++++------ 10 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 DrissionPage/_units/console.py create mode 100644 DrissionPage/_units/console.pyi diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index b4bb936..a4ee172 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,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.0b15' +__version__ = '4.1.0.0b16' diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index d04785b..f99dafb 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -25,6 +25,7 @@ from .._functions.settings import Settings from .._functions.tools import raise_error from .._functions.web import location_in_viewport from .._units.actions import Actions +from .._units.console import Console from .._units.listener import Listener from .._units.rect import TabRect from .._units.screencast import Screencast @@ -58,6 +59,7 @@ class ChromiumBase(BasePage): self._rect = None self._wait = None self._scroll = None + self._console = None self._upload_list = None self._doc_got = False # 用于在LoadEventFired和FrameStoppedLoading间标记是否已获取doc self._download_path = None @@ -314,6 +316,13 @@ class ChromiumBase(BasePage): self._rect = TabRect(self) return self._rect + @property + def console(self): + """返回获取控制台信息的对象""" + if self._console is None: + self._console = Console(self) + return self._console + @property def timeout(self): """返回timeout设置""" diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 2ab5fb2..aa2cf48 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -19,6 +19,7 @@ from .._functions.elements import SessionElementsList, ChromiumElementsList from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage from .._units.actions import Actions +from .._units.console import Console from .._units.listener import Listener from .._units.rect import TabRect from .._units.screencast import Screencast @@ -61,6 +62,7 @@ class ChromiumBase(BasePage): self._init_jss: list = ... self._ready_state: Optional[str] = ... self._rect: TabRect = ... + self._console: Console = ... self._type: str = ... def _connect_browser(self, target_id: str = None) -> None: ... @@ -143,6 +145,9 @@ class ChromiumBase(BasePage): @property def rect(self) -> TabRect: ... + @property + def console(self) -> Console: ... + @property def timeout(self) -> float: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 95117d4..2a50d7f 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -37,6 +37,7 @@ class ChromiumFrame(ChromiumBase): sleep(.1) return r r = object.__new__(cls) + r._frame_id = node['frameId'] cls._Frames[node['frameId']] = r return r @@ -53,7 +54,6 @@ class ChromiumFrame(ChromiumBase): self._reloading = False node = info['node'] if 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']) @@ -195,6 +195,8 @@ class ChromiumFrame(ChromiumBase): """同域变异域""" self.browser._frames.pop(kwargs['frameId'], None) ChromiumFrame._Frames.pop(kwargs['frameId'], None) + while not hasattr(self, '_frame_id'): + sleep(.1) if kwargs['frameId'] == self._frame_id: self._reload() diff --git a/DrissionPage/_pages/mix_page.py b/DrissionPage/_pages/mix_page.py index e4e834c..24f9295 100644 --- a/DrissionPage/_pages/mix_page.py +++ b/DrissionPage/_pages/mix_page.py @@ -375,10 +375,11 @@ class MixPage(SessionPage, ChromiumPage, BasePage): elif self._mode == 'd': return super(SessionPage, self)._find_elements(locator, timeout=timeout, index=index, relative=relative) - def quit(self, timeout=5, force=True): + def quit(self, timeout=5, force=True, del_data=False): """关闭浏览器和Session :param timeout: 等待浏览器关闭超时时间(秒) :param force: 关闭超时是否强制终止进程 + :param del_data: 是否删除用户文件夹 :return: None """ if self._has_session: @@ -387,7 +388,7 @@ class MixPage(SessionPage, ChromiumPage, BasePage): self._response = None self._has_session = None if self._has_driver: - super(SessionPage, self).quit(timeout, force) + super(SessionPage, self).quit(timeout, force, del_data=del_data) self._driver = None self._has_driver = None diff --git a/DrissionPage/_pages/mix_page.pyi b/DrissionPage/_pages/mix_page.pyi index 7d335da..727a155 100644 --- a/DrissionPage/_pages/mix_page.pyi +++ b/DrissionPage/_pages/mix_page.pyi @@ -188,4 +188,4 @@ class MixPage(SessionPage, ChromiumPage, BasePage): dr_opt: Union[Driver, bool, None], se_opt: Union[Session, SessionOptions, bool, None]) -> None: ... - def quit(self, timeout: float = 5, force: bool = True) -> None: ... + def quit(self, timeout: float = 5, force: bool = True, del_data: bool = False) -> None: ... diff --git a/DrissionPage/_units/console.py b/DrissionPage/_units/console.py new file mode 100644 index 0000000..5dba0a5 --- /dev/null +++ b/DrissionPage/_units/console.py @@ -0,0 +1,61 @@ +# -*- coding:utf-8 -*- +from queue import Queue +from time import perf_counter, sleep + + +class Console(object): + def __init__(self, owner): + self.owner = owner + self.listening = False + self._caught = None + + @property + def messages(self): + if self._caught is None: + return [] + lst = [] + while not self._caught.empty(): + lst.append(self._caught.get_nowait()) + return lst + + def start(self): + self._caught = Queue(maxsize=0) + self.owner._driver.set_callback("Console.messageAdded", self._console) + self.owner._run_cdp("Console.enable") + self.listening = True + + def stop(self): + if self.listening: + self.owner._run_cdp("Console.disable") + self.owner._driver.set_callback('Console.messageAdded', None) + self.listening = False + + def clear(self): + self._caught = Queue(maxsize=0) + + def steps(self, timeout=None): + end = perf_counter() + timeout if timeout else None + while self.owner._driver.is_running: + if timeout and perf_counter() > end: + return + if self._caught.qsize(): + yield self._caught.get_nowait() + sleep(0.05) + + def _console(self, **kwargs): + self._caught.put(ConsoleData(kwargs['message'])) + + +class ConsoleData(object): + __slots__ = ('_data', 'source', 'level', 'text', 'url', 'line', 'column') + + def __init__(self, data): + self._data = data + + def __getattr__(self, item): + """获取属性""" + return self._data.get(item, None) + + def __repr__(self): + return (f'') diff --git a/DrissionPage/_units/console.pyi b/DrissionPage/_units/console.pyi new file mode 100644 index 0000000..56a82bb --- /dev/null +++ b/DrissionPage/_units/console.pyi @@ -0,0 +1,45 @@ +# -*- coding:utf-8 -*- +from queue import Queue +from typing import Optional, Iterable, List + +from .._pages.chromium_base import ChromiumBase + + +class Console(object): + listening: bool = ... + owner: ChromiumBase = ... + _caught: Optional[Queue] = ... + + def __init__(self, owner: ChromiumBase) -> None: ... + + @property + def messages(self) -> List[ConsoleData]: ... + + def start(self) -> None: + """开启console监听""" + ... + + def stop(self) -> None: + """停止监听,清空已监听到的列表""" + ... + + def clear(self) -> None: + """清空已获取但未返回的信息""" + ... + + def steps(self, timeout: Optional[float] = None) -> Iterable[ConsoleData]: + """每监听到一个信息就返回,用于for循环 + :param timeout: 等待一个信息的超时时间,为None无限等待 + :return: None + """ + ... + + def _console(self, **kwargs) -> None: ... + + +class ConsoleData(object): + __slots__ = ('_data', 'source', 'level', 'text', 'url', 'line', 'column') + + def __init__(self, data: dict) -> None: ... + + def __getattr__(self, item: str) -> str: ... diff --git a/DrissionPage/_units/listener.py b/DrissionPage/_units/listener.py index 0166e15..a12f144 100644 --- a/DrissionPage/_units/listener.py +++ b/DrissionPage/_units/listener.py @@ -168,7 +168,7 @@ class Listener(object): caught = 0 end = perf_counter() + timeout if timeout else None while self._driver.is_running: - if (timeout and perf_counter() > end) or not self._driver.is_running: + if timeout and perf_counter() > end: return if self._caught.qsize() >= gap: yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)] diff --git a/README.md b/README.md index 95698a4..b44f3cb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ DrissionPage 是一个基于 python 的网页自动化工具。 --- -官方网站:[https://drissionpage.cn](https://drissionpage.cn) +官方网站:[https://DrissionPage.cn](https://drissionpage.cn) star fork @@ -32,7 +32,7 @@ python 版本:3.6 及以上 # 🛠 如何使用 -**📖 使用文档:** [点击查看](https://g1879.gitee.io/drissionpagedocs) +**📖 使用文档:** [点击查看](https://DrissionPage.cn) **交流 QQ 群:** 636361957 @@ -55,27 +55,27 @@ python 版本:3.6 及以上 - 不基于 webdriver - 无需为不同版本的浏览器下载不同的驱动 - 运行速度更快 -- 可以跨`