diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index a071967..9bb8baf 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,4 @@ from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions __all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__'] -__version__ = '4.0.4.21' +__version__ = '4.0.5.3' diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 205baaa..1fcbd40 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -169,14 +169,8 @@ class DrissionElement(BaseElement): loc = loc[1].lstrip('./') node = self._ele(f'xpath:./{loc}', timeout=timeout, index=index, relative=True, raise_err=False) - if node: - return node - - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 'child()', {'locator': locator, 'index': index, - 'ele_only': ele_only}) - else: - return NoneElement(self.owner, 'child()', {'locator': locator, 'index': index, 'ele_only': ele_only}) + return node if node else NoneElement(self.owner, 'child()', + {'locator': locator, 'index': index, 'ele_only': ele_only}) def prev(self, locator='', index=1, timeout=None, ele_only=True): """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 @@ -289,12 +283,8 @@ class DrissionElement(BaseElement): index = locator locator = '' node = self._get_relatives(index, locator, direction, brother, timeout, ele_only) - if node: - return node - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, func, {'locator': locator, 'index': index, 'ele_only': ele_only}) - else: - return NoneElement(self.owner, func, {'locator': locator, 'index': index, 'ele_only': ele_only}) + return node if node else NoneElement(self.owner, func, + {'locator': locator, 'index': index, 'ele_only': ele_only}) def _get_relatives(self, index=None, locator='', direction='following', brother=True, timeout=.5, ele_only=True): """按要求返回兄弟元素或节点组成的列表 @@ -411,7 +401,8 @@ class BasePage(BaseParser): if p.exists(): url = str(p.absolute()) is_file = True - self._url = quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%') + + self._url = url if is_file else quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%') retry = retry if retry is not None else self.retry_times interval = interval if interval is not None else self.retry_interval return retry, interval, is_file diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index ea4d22d..8de4f35 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -12,6 +12,7 @@ from DownloadKit import DownloadKit from .._elements.none_element import NoneElement 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 @@ -37,7 +38,7 @@ class BaseParser(object): locator: Union[Tuple[str, str], str, BaseElement, None] = None, index: int = 1) -> SessionElement: ... - def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ... + def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ... def _ele(self, locator: Union[Tuple[str, str], str], diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index c877013..00649d5 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -5,7 +5,6 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from os import waitpid from pathlib import Path from shutil import rmtree from time import perf_counter, sleep @@ -135,7 +134,8 @@ 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 process_id(self): @@ -143,7 +143,7 @@ class Browser(object): return self._process_id def find_tabs(self, title=None, url=None, tab_type=None): - """查找符合条件的tab,返回它们组成的列表 + """查找符合条件的tab,返回它们组成的列表,title和url是与关系 :param title: 要匹配title的文本 :param url: 要匹配url的文本 :param tab_type: tab类型,可用列表输入多个 @@ -274,10 +274,6 @@ class Browser(object): if ok: break - sleep(.05) - - if self.process_id: - waitpid(self.process_id, 0) def _on_disconnect(self): self.page._on_disconnect() @@ -293,4 +289,4 @@ class Browser(object): break except (PermissionError, FileNotFoundError, OSError): pass - sleep(.05) + sleep(.03) diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index d98bc31..3044a87 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -10,12 +10,12 @@ from queue import Queue, Empty from threading import Thread, Event from time import perf_counter, sleep -from requests import get +from requests import Session from websocket import (WebSocketTimeoutException, WebSocketConnectionClosedException, create_connection, WebSocketException, WebSocketBadStatusException) from .._functions.settings import Settings -from ..errors import PageDisconnectedError, TargetNotFoundError +from ..errors import PageDisconnectedError class Driver(object): @@ -201,13 +201,10 @@ class Driver(object): try: self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True) except WebSocketBadStatusException as e: - txt = str(e) - if 'No such target id' in txt: - raise TargetNotFoundError(f'找不到页面:{self.id}。') - elif 'Handshake status 403 Forbidden' in txt: + if 'Handshake status 403 Forbidden' in str(e): raise RuntimeError('请升级websocket-client库。') else: - raise e + return self._recv_th.start() self._handle_event_th.start() return True @@ -274,11 +271,13 @@ class BrowserDriver(Driver): self._created = True BrowserDriver.BROWSERS[tab_id] = self super().__init__(tab_id, tab_type, address, owner) + self._control_session = Session() + self._control_session.trust_env = False def __repr__(self): return f'' def get(self, url): - r = get(url, headers={'Connection': 'close'}) + r = self._control_session.get(url, headers={'Connection': 'close'}) r.close() return r diff --git a/DrissionPage/_base/driver.pyi b/DrissionPage/_base/driver.pyi index 329280b..b3f44f9 100644 --- a/DrissionPage/_base/driver.pyi +++ b/DrissionPage/_base/driver.pyi @@ -9,7 +9,7 @@ from queue import Queue from threading import Thread, Event from typing import Union, Callable, Dict, Optional -from requests import Response +from requests import Response, Session from websocket import WebSocket from .browser import Browser @@ -68,14 +68,10 @@ class Driver(object): class BrowserDriver(Driver): BROWSERS: Dict[str, Driver] = ... owner: Browser = ... + _control_session: Session = ... def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Browser): ... - def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser): - """ - - :rtype: object - """ - ... + def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser): ... def get(self, url) -> Response: ... diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 0a5a9e7..86e64d2 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -389,7 +389,7 @@ class ChromiumOptions(object): return self def set_paths(self, browser_path=None, local_port=None, address=None, download_path=None, - user_data_path=None, cache_path=None, debugger_address=None): + user_data_path=None, cache_path=None): """快捷的路径设置函数 :param browser_path: 浏览器可执行文件路径 :param local_port: 本地端口号 @@ -399,7 +399,6 @@ class ChromiumOptions(object): :param cache_path: 缓存路径 :return: 当前对象 """ - address = address or debugger_address if browser_path is not None: self.set_browser_path(browser_path) @@ -568,50 +567,3 @@ class ChromiumOptions(object): def __repr__(self): return f'' - - # ---------------即将废弃-------------- - - @property - def debugger_address(self): - """返回浏览器地址,ip:port""" - return self._address - - @debugger_address.setter - def debugger_address(self, address): - """设置浏览器地址,格式ip:port""" - self.set_address(address) - - def set_page_load_strategy(self, value): - return self.set_load_mode(value) - - def set_headless(self, on_off=True): - """设置是否隐藏浏览器界面 - :param on_off: 开或关 - :return: 当前对象 - """ - on_off = 'new' if on_off else 'false' - return self.set_argument('--headless', on_off) - - def set_no_imgs(self, on_off=True): - """设置是否加载图片 - :param on_off: 开或关 - :return: 当前对象 - """ - on_off = None if on_off else False - return self.set_argument('--blink-settings=imagesEnabled=false', on_off) - - def set_no_js(self, on_off=True): - """设置是否禁用js - :param on_off: 开或关 - :return: 当前对象 - """ - on_off = None if on_off else False - return self.set_argument('--disable-javascript', on_off) - - def set_mute(self, on_off=True): - """设置是否静音 - :param on_off: 开或关 - :return: 当前对象 - """ - on_off = None if on_off else False - return self.set_argument('--mute-audio', on_off) diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index ee9b0f8..a533e4d 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -457,17 +457,6 @@ class SessionOptions(object): self._adapters = [(k, i) for k, i in session.adapters.items()] return self - # --------------即将废弃--------------- - - def set_paths(self, download_path=None): - """设置默认下载路径 - :param download_path: 下载路径 - :return: 返回当前对象 - """ - if download_path is not None: - self._download_path = str(download_path) - return self - def __repr__(self): return f'' diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index b56263b..3f42b5f 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -17,8 +17,8 @@ from .none_element import NoneElement from .session_element import make_session_ele from .._base.base import DrissionElement, BaseElement from .._functions.keys import input_text_or_keys -from .._functions.locator import get_loc -from .._functions.settings import Settings +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 @@ -27,8 +27,8 @@ from .._units.selector import SelectElement from .._units.setter import ChromiumElementSetter from .._units.states import ElementStates, ShadowRootStates from .._units.waiter import ElementWaiter -from ..errors import (ContextLostError, ElementLostError, JavaScriptError, ElementNotFoundError, - CDPError, NoResourceError, AlertExistsError) +from ..errors import ContextLostError, ElementLostError, JavaScriptError, CDPError, NoResourceError, AlertExistsError, \ + NoRectError __FRAME_ELEMENT__ = ('iframe', 'frame') @@ -55,6 +55,7 @@ class ChromiumElement(DrissionElement): self._tag = None self._wait = None self._type = 'ChromiumElement' + self._doc_id = None if node_id and obj_id and backend_id: self._node_id = node_id @@ -75,9 +76,6 @@ class ChromiumElement(DrissionElement): else: raise ElementLostError - doc = self.run_js('return this.ownerDocument;') - self._doc_id = doc['objectId'] if doc else None - def __repr__(self): attrs = [f"{k}='{v}'" for k, v in self.attrs.items()] return f'' @@ -93,14 +91,6 @@ class ChromiumElement(DrissionElement): def __eq__(self, other): return self._backend_id == getattr(other, '_backend_id', None) - def __getattr__(self, item): - """获取元素属性 - :param item: 属性名 - :return: 属性值 - """ - a = self.attr(item) - return a if a is not None else self.property(item) - @property def tag(self): """返回元素tag""" @@ -221,25 +211,6 @@ class ChromiumElement(DrissionElement): def value(self): return self.property('value') - # -----即将废弃开始-------- - @property - def location(self): - """返回元素左上角的绝对坐标""" - return self.rect.location - - @property - def size(self): - """返回元素宽和高组成的元组""" - return self.rect.size - - def prop(self, prop): - return self.property(prop) - - def get_src(self, timeout=None, base64_to_bytes=True): - return self.src(timeout=timeout, base64_to_bytes=base64_to_bytes) - - # -----即将废弃结束-------- - def check(self, uncheck=False, by_js=False): """选中或取消选中当前元素 :param uncheck: 是否取消选中 @@ -328,7 +299,7 @@ class ChromiumElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 直接子元素或节点文本组成的列表 """ - return super().children(locator, timeout, ele_only=ele_only) + return ChromiumElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only)) def prevs(self, locator='', timeout=None, ele_only=True): """返回当前元素前面符合条件的同级元素或节点组成的列表,可用查询语法筛选 @@ -337,7 +308,7 @@ class ChromiumElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 兄弟元素或节点文本组成的列表 """ - return super().prevs(locator, timeout, ele_only=ele_only) + return ChromiumElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only)) def nexts(self, locator='', timeout=None, ele_only=True): """返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选 @@ -346,7 +317,7 @@ class ChromiumElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 兄弟元素或节点文本组成的列表 """ - return super().nexts(locator, timeout, ele_only=ele_only) + return ChromiumElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only)) def befores(self, locator='', timeout=None, ele_only=True): """返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选 @@ -356,7 +327,7 @@ class ChromiumElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 本元素前面的元素或节点组成的列表 """ - return super().befores(locator, timeout, ele_only=ele_only) + return ChromiumElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only)) def afters(self, locator='', timeout=None, ele_only=True): """返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选 @@ -366,7 +337,137 @@ class ChromiumElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 本元素后面的元素或节点组成的列表 """ - return super().afters(locator, timeout, ele_only=ele_only) + return ChromiumElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only)) + + def over(self, timeout=None): + """获取覆盖在本元素上最上层的元素 + :param timeout: 等待元素出现的超时时间(秒) + :return: 元素对象 + """ + timeout = timeout if timeout is None else self.owner.timeout + bid = self.wait.covered(timeout=timeout) + if bid: + return ChromiumElement(owner=self.owner, backend_id=bid) + else: + return NoneElement(page=self.owner, method='on()', args={'timeout': timeout}) + + def offset(self, offset_x, offset_y): + """获取相对本元素左上角左边指定偏移量位置的元素 + :param offset_x: 横坐标偏移量,向右为正 + :param offset_y: 纵坐标偏移量,向下为正 + :return: 元素对象 + """ + 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']) + except CDPError: + return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y}) + + def east(self, loc_or_pixel=None, index=1): + """获取元素右边某个指定元素 + :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 + :param index: 第几个,从1开始 + :return: 获取到的元素对象 + """ + return self._get_relative_eles(mode='east', locator=loc_or_pixel, index=index) + + def south(self, loc_or_pixel=None, index=1): + """获取元素下方某个指定元素 + :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 + :param index: 第几个,从1开始 + :return: 获取到的元素对象 + """ + return self._get_relative_eles(mode='south', locator=loc_or_pixel, index=index) + + def west(self, loc_or_pixel=None, index=1): + """获取元素左边某个指定元素 + :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 + :param index: 第几个,从1开始 + :return: 获取到的元素对象 + """ + return self._get_relative_eles(mode='west', locator=loc_or_pixel, index=index) + + def north(self, loc_or_pixel=None, index=1): + """获取元素上方某个指定元素 + :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 + :param index: 第几个,从1开始 + :return: 获取到的元素对象 + """ + return self._get_relative_eles(mode='north', locator=loc_or_pixel, index=index) + + def _get_relative_eles(self, mode='north', locator=None, index=1): + """获取元素下方某个指定元素 + :param locator: 定位符,只支持str或int,且不支持xpath和css方式 + :param index: 第几个,从1开始 + :return: 获取到的元素对象 + """ + if locator and not (isinstance(locator, str) and not locator.startswith( + ('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css=')) or isinstance(locator, int)): + raise ValueError('locator参数只能是str格式且不支持xpath和css形式。') + rect = self.states.has_rect + if not rect: + raise NoRectError + + if mode == 'east': + cdp_data = {'x': int(rect[1][0]), 'y': int(self.rect.midpoint[1]), + 'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False} + variable = 'x' + minus = False + elif mode == 'south': + cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[2][1]), + 'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False} + variable = 'y' + minus = False + elif mode == 'west': + cdp_data = {'x': int(rect[0][0]), 'y': int(self.rect.midpoint[1]), + 'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False} + variable = 'x' + minus = True + else: # north + cdp_data = {'x': int(self.rect.midpoint[0]), 'y': int(rect[0][1]), + 'includeUserAgentShadowDOM': True, 'ignorePointerEventsNone': False} + variable = 'y' + minus = True + + if isinstance(locator, int): + if minus: + cdp_data[variable] -= locator + else: + cdp_data[variable] += locator + try: + return ChromiumElement(owner=self.owner, + backend_id=self.owner.run_cdp('DOM.getNodeForLocation', + **cdp_data)['backendNodeId']) + except CDPError: + return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator}) + + num = 0 + value = -8 if minus else 8 + size = self.owner.rect.size + max_len = size[0] if mode == 'east' else size[1] + loc_data = locator_to_tuple(locator) if locator else None + curr_ele = None + while 0 < cdp_data[variable] < max_len: + cdp_data[variable] += value + try: + bid = self.owner.run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId'] + if bid == curr_ele: + continue + else: + curr_ele = bid + ele = ChromiumElement(self.owner, backend_id=bid) + + if loc_data is None or _check_ele(ele, loc_data): + num += 1 + if num == index: + return ele + except: + pass + + return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator}) def attr(self, attr): """返回一个attribute属性值 @@ -375,14 +476,14 @@ class ChromiumElement(DrissionElement): """ attrs = self.attrs if attr == 'href': # 获取href属性时返回绝对url - link = attrs.get('href', None) + link = attrs.get('href') if not link or link.lower().startswith(('javascript:', 'mailto:')): return link else: return make_absolute_link(link, self.property('baseURI')) elif attr == 'src': - return make_absolute_link(attrs.get('src', None), self.property('baseURI')) + return make_absolute_link(attrs.get('src'), self.property('baseURI')) elif attr == 'text': return self.text @@ -459,14 +560,7 @@ class ChromiumElement(DrissionElement): :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 :return: SessionElement对象或属性、文本 """ - r = make_session_ele(self, locator, index=index) - if isinstance(r, NoneElement): - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 's_ele()', {'locator': locator}) - else: - r.method = 's_ele()' - r.args = {'locator': locator} - return r + return make_session_ele(self, locator, index=index, method='s_ele()') def s_eles(self, locator=None): """查找所有符合条件的元素,以SessionElement列表形式返回 @@ -638,6 +732,7 @@ class ChromiumElement(DrissionElement): self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') return + self.wait.clickable(wait_moved=False, timeout=.5) if clear and vals not in ('\n', '\ue007'): self.clear(by_js=False) else: @@ -686,7 +781,7 @@ class ChromiumElement(DrissionElement): """拖拽当前元素到相对位置 :param offset_x: x变化值 :param offset_y: y变化值 - :param duration: 拖动用时,传入0即瞬间到j达 + :param duration: 拖动用时,传入0即瞬间到达 :return: None """ curr_x, curr_y = self.rect.midpoint @@ -704,7 +799,6 @@ class ChromiumElement(DrissionElement): ele_or_loc = ele_or_loc.rect.midpoint elif not isinstance(ele_or_loc, (list, tuple)): raise TypeError('需要ChromiumElement对象或坐标。') - self.owner.actions.hold(self).move_to(ele_or_loc, duration=duration).release() def _get_obj_id(self, node_id=None, backend_id=None): @@ -917,13 +1011,8 @@ class ShadowRoot(BaseElement): loc = f'xpath:./{loc}' ele = self._ele(loc, index=index, relative=True) - if ele: - return ele - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 'child()', {'locator': locator, 'index': index}) - else: - return NoneElement(self.owner, 'child()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'child()', {'locator': locator, 'index': index}) def next(self, locator='', index=1): """返回当前元素后面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 @@ -938,13 +1027,8 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./{loc}' ele = self.parent_ele._ele(xpath, index=index, relative=True) - if ele: - return ele - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 'next()', {'locator': locator, 'index': index}) - else: - return NoneElement(self.owner, 'next()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'next()', {'locator': locator, 'index': index}) def before(self, locator='', index=1): """返回文档中当前元素前面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 @@ -960,13 +1044,8 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') xpath = f'xpath:./preceding::{loc}' ele = self.parent_ele._ele(xpath, index=index, relative=True) - if ele: - return ele - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 'before()', {'locator': locator, 'index': index}) - else: - return NoneElement(self.owner, 'before()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'before()', {'locator': locator, 'index': index}) def after(self, locator='', index=1): """返回文档中此当前元素后面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 @@ -976,12 +1055,7 @@ class ShadowRoot(BaseElement): :return: 本元素后面的某个元素或节点 """ nodes = self.afters(locator=locator) - if nodes: - return nodes[index - 1] - if Settings.raise_when_ele_not_found: - raise ElementNotFoundError(None, 'after()', {'locator': locator, 'index': index}) - else: - return NoneElement(self.owner, 'after()', {'locator': locator, 'index': index}) + return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', {'locator': locator, 'index': index}) def children(self, locator=''): """返回当前元素符合条件的直接子元素或节点组成的列表,可用查询语法筛选 @@ -1115,8 +1189,8 @@ class ShadowRoot(BaseElement): 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'] for i in css] + 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 r = make_chromium_eles(self.owner, _ids=node_ids, index=index, is_obj_id=False) @@ -1219,8 +1293,12 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True): res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], ownProperties=True)['result'][:-1] if index is None: - r = [make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True) - if i['value']['type'] == 'object' else i['value']['value'] for i in res] + r = ChromiumElementsList(page=ele.owner) + for i in res: + if i['value']['type'] == 'object': + r.append(make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True)) + else: + r.append(i['value']['value']) return None if False in r else r else: @@ -1244,7 +1322,7 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True): if result: return result - return NoneElement(ele.owner) if index is not None else [] + return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner) def find_by_css(ele, selector, index, timeout): @@ -1290,7 +1368,7 @@ def find_by_css(ele, selector, index, timeout): if result: return result - return NoneElement(ele.owner) if index is not None else [] + return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner) def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False): @@ -1322,7 +1400,7 @@ def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False): return get_node_func(page, obj_id, ele_only) else: # 获取全部 - nodes = [] + nodes = ChromiumElementsList(page=page) for obj_id in _ids: tmp = get_node_func(page, obj_id, ele_only) if tmp is False: @@ -1569,3 +1647,59 @@ class Pseudo(object): def after(self): """返回当前元素的::after伪元素内容""" return self._ele.style('content', 'after') + + +def _check_ele(ele, loc_data): + """检查元素是否符合loc_data指定的要求 + :param ele: 元素对象 + :param loc_data: 格式: {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]} + :return: bool + """ + attrs = ele.attrs + if loc_data['and']: + ok = True + for i in loc_data['args']: + name, symbol, value, deny = i + if name == 'tag()': + arg = ele.tag + symbol = '=' + elif name == 'text()': + arg = ele.raw_text + elif name is None: + arg = None + else: + arg = attrs.get(name, '') + + if ((symbol == '=' and ((deny and arg == value) or (not deny and arg != value))) + or (symbol == ':' and ((deny and value in arg) or (not deny and value not in arg))) + or (symbol == '^' and ((deny and arg.startswith(value)) + or (not deny and not arg.startswith(value)))) + or (symbol == '$' and ((deny and arg.endswith(value)) or (not deny and not arg.endswith(value)))) + or (arg is None and attrs)): + ok = False + break + + else: + ok = False + for i in loc_data['args']: + name, value, symbol, deny = i + if name == 'tag()': + arg = ele.tag + symbol = '=' + elif name == 'text()': + arg = ele.text + elif name is None: + arg = None + else: + arg = attrs.get(name, '') + + if ((symbol == '=' and ((not deny and arg == value) or (deny and arg != value))) + or (symbol == ':' and ((not deny and value in arg) or (deny and value not in arg))) + or (symbol == '^' and ((not deny and arg.startswith(value)) + or (deny and not arg.startswith(value)))) + or (symbol == '$' and ((not deny and arg.endswith(value)) or (deny and not arg.endswith(value)))) + or (arg is None and not attrs)): + ok = True + break + + return ok diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index e6a83a8..1be5553 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -10,6 +10,7 @@ from typing import Union, Tuple, List, Any, Literal, Optional from .._base.base import DrissionElement, BaseElement from .._elements.session_element import SessionElement +from .._functions.elements import SessionElementsList, ChromiumElementsList from .._pages.chromium_base import ChromiumBase from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage @@ -56,8 +57,6 @@ class ChromiumElement(DrissionElement): def __eq__(self, other: ChromiumElement) -> bool: ... - def __getattr__(self, item: str) -> str: ... - @property def tag(self) -> str: ... @@ -138,27 +137,44 @@ class ChromiumElement(DrissionElement): def children(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ... + ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ... def prevs(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ... + ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ... def nexts(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ... + ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ... def befores(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ... + ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ... def afters(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[ChromiumElement, str]]: ... + ele_only: bool = True) -> Union[ChromiumElementsList, List[Union[ChromiumElement, str]]]: ... + + def over(self, timeout: float = None) -> ChromiumElement: ... + + def south(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ... + + def north(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ... + + def west(self, loc_or_pixel: Union[str, int, None] = None, index: int = 1) -> ChromiumElement: ... + + 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 _get_relative_eles(self, + mode: str = 'north', + locator: Union[int, str] = None, + index: int = 1) -> ChromiumElement: ... @property def wait(self) -> ElementWaiter: ... @@ -188,21 +204,20 @@ class ChromiumElement(DrissionElement): def eles(self, locator: Union[Tuple[str, str], str], - timeout: float = None) -> List[ChromiumElement]: ... + timeout: float = None) -> ChromiumElementsList: ... def s_ele(self, locator: Union[Tuple[str, str], str] = None, index: int = 1) -> SessionElement: ... - def s_eles(self, locator: Union[Tuple[str, str], str] = None) -> List[SessionElement]: ... + def s_eles(self, locator: Union[Tuple[str, str], str] = None) -> SessionElementsList: ... def _find_elements(self, locator: Union[Tuple[str, str], str], timeout: float = None, index: Optional[int] = 1, relative: bool = False, - raise_err: bool = False) -> Union[ChromiumElement, ChromiumFrame, - List[Union[ChromiumElement, ChromiumFrame]]]: ... + raise_err: bool = False) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ... def style(self, style: str, pseudo_ele: str = '') -> str: ... @@ -318,21 +333,20 @@ class ShadowRoot(BaseElement): def eles(self, locator: Union[Tuple[str, str], str], - timeout: float = None) -> List[ChromiumElement]: ... + timeout: float = None) -> ChromiumElementsList: ... def s_ele(self, locator: Union[Tuple[str, str], str] = None, index: int = 1) -> SessionElement: ... - def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ... + def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ... def _find_elements(self, locator: Union[Tuple[str, str], str], timeout: float = None, index: Optional[int] = 1, relative: bool = False, - raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, str, - List[Union[ChromiumElement, ChromiumFrame, str]]]: ... + raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, str, ChromiumElementsList]: ... def _get_node_id(self, obj_id: str) -> int: ... @@ -366,7 +380,7 @@ def make_chromium_eles(page: Union[ChromiumBase, ChromiumPage, WebPage, Chromium index: Optional[int] = 1, is_obj_id: bool = True, ele_only: bool = False - ) -> Union[ChromiumElement, ChromiumFrame, List[Union[ChromiumElement, ChromiumFrame]]]: ... + ) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ... def make_js_for_find_ele_by_xpath(xpath: str, type_txt: str, node_txt: str) -> str: ... diff --git a/DrissionPage/_elements/none_element.py b/DrissionPage/_elements/none_element.py index 15502f3..31e04e6 100644 --- a/DrissionPage/_elements/none_element.py +++ b/DrissionPage/_elements/none_element.py @@ -5,11 +5,20 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ +from .._functions.settings import Settings from ..errors import ElementNotFoundError class NoneElement(object): def __init__(self, page=None, method=None, args=None): + """ + :param page: 元素所在页面 + :param method: 查找元素的方法 + :param args: 查找元素的参数 + """ + if method and Settings.raise_when_ele_not_found: # 无传入method时不自动抛出,由调用者处理 + raise ElementNotFoundError(None, method=method, arguments=args) + if page: self._none_ele_value = page._none_ele_value self._none_ele_return_value = page._none_ele_return_value diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index 4fe9c77..888269a 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -6,13 +6,14 @@ @License : BSD 3-Clause. """ from html import unescape -from re import match, sub, DOTALL +from re import match, sub, DOTALL, search from lxml.etree import tostring from lxml.html import HtmlElement, fromstring from .none_element import NoneElement from .._base.base import DrissionElement, BasePage, BaseElement +from .._functions.elements import SessionElementsList from .._functions.locator import get_loc from .._functions.web import get_ele_txt, make_absolute_link @@ -50,13 +51,6 @@ class SessionElement(DrissionElement): def __eq__(self, other): return self.xpath == getattr(other, 'xpath', None) - def __getattr__(self, item): - """获取元素属性 - :param item: 属性名 - :return: 属性值 - """ - return self.attr(item) - @property def tag(self): """返回元素类型""" @@ -156,7 +150,7 @@ class SessionElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 直接子元素或节点文本组成的列表 """ - return super().children(locator, timeout, ele_only=ele_only) + return SessionElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only)) def prevs(self, locator='', timeout=None, ele_only=True): """返回当前元素前面符合条件的同级元素或节点组成的列表,可用查询语法筛选 @@ -165,7 +159,7 @@ class SessionElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 同级元素或节点文本组成的列表 """ - return super().prevs(locator, timeout, ele_only=ele_only) + return SessionElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only)) def nexts(self, locator='', timeout=None, ele_only=True): """返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选 @@ -174,7 +168,7 @@ class SessionElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 同级元素或节点文本组成的列表 """ - return super().nexts(locator, timeout, ele_only=ele_only) + return SessionElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only)) def befores(self, locator='', timeout=None, ele_only=True): """返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选 @@ -184,7 +178,7 @@ class SessionElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 本元素前面的元素或节点组成的列表 """ - return super().befores(locator, timeout, ele_only=ele_only) + return SessionElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only)) def afters(self, locator='', timeout=None, ele_only=True): """返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选 @@ -194,7 +188,7 @@ class SessionElement(DrissionElement): :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 :return: 本元素后面的元素或节点组成的列表 """ - return super().afters(locator, timeout, ele_only=ele_only) + return SessionElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only)) def attr(self, name): """返回attribute属性值 @@ -293,12 +287,13 @@ class SessionElement(DrissionElement): return f'{path_str[1:]}' if mode == 'css' else path_str -def make_session_ele(html_or_ele, loc=None, index=1): +def make_session_ele(html_or_ele, loc=None, index=1, method=None): """从接收到的对象或html文本中查找元素,返回SessionElement对象 如要直接从html生成SessionElement而不在下级查找,loc输入None即可 :param html_or_ele: html文本、BaseParser对象 :param loc: 定位元组或字符串,为None时不在下级查找,返回根元素 :param index: 获取第几个元素,从1开始,可传入负数获取倒数第几个,None获取所有 + :param method: 调用此方法的方法 :return: 返回SessionElement元素或列表,或属性文本 """ # ---------------处理定位符--------------- @@ -353,8 +348,14 @@ def make_session_ele(html_or_ele, loc=None, index=1): page = html_or_ele.owner xpath = html_or_ele.xpath # ChromiumElement,兼容传入的元素在iframe内的情况 - html = html_or_ele.owner.run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML'] \ - if html_or_ele._doc_id else html_or_ele.owner.html + if html_or_ele._doc_id is None: + 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'] + else: + html = html_or_ele.owner.html html_or_ele = fromstring(html) html_or_ele = html_or_ele.xpath(xpath)[0] @@ -373,7 +374,11 @@ def make_session_ele(html_or_ele, loc=None, index=1): # ShadowRoot elif isinstance(html_or_ele, BaseElement): page = html_or_ele.owner - html_or_ele = fromstring(html_or_ele.html) + html = html_or_ele.html + r = search(r'^[ \n]*?[ \n]*?(.*?)[ \n]*?[ \n]*?$', html) + if r: + html = r.group(1) + html_or_ele = fromstring(html) else: raise TypeError('html_or_ele参数只能是元素、页面对象或html文本。') @@ -390,12 +395,16 @@ def make_session_ele(html_or_ele, loc=None, index=1): # 把lxml元素对象包装成SessionElement对象并按需要返回一个或全部 if index is None: - return [SessionElement(e, page) if isinstance(e, HtmlElement) else e for e in eles if e != '\n'] + r = SessionElementsList(page=page) + for e in eles: + if e != '\n': + r.append(SessionElement(e, page) if isinstance(e, HtmlElement) else e) + return r else: eles_count = len(eles) if eles_count == 0 or abs(index) > eles_count: - return NoneElement(page) + return NoneElement(page, method=method, args={'locator': loc, 'index': index}) if index < 0: index = eles_count + index + 1 @@ -405,7 +414,7 @@ def make_session_ele(html_or_ele, loc=None, index=1): elif isinstance(ele, str): return ele else: - return NoneElement(page) + return NoneElement(page, method=method, args={'locator': loc, 'index': index}) except Exception as e: if 'Invalid expression' in str(e): diff --git a/DrissionPage/_elements/session_element.pyi b/DrissionPage/_elements/session_element.pyi index 74034a9..18eef9d 100644 --- a/DrissionPage/_elements/session_element.pyi +++ b/DrissionPage/_elements/session_element.pyi @@ -11,6 +11,7 @@ from lxml.html import HtmlElement from .._base.base import DrissionElement, BaseElement from .._elements.chromium_element import ChromiumElement +from .._functions.elements import SessionElementsList from .._pages.chromium_base import ChromiumBase from .._pages.chromium_frame import ChromiumFrame from .._pages.session_page import SessionPage @@ -35,8 +36,6 @@ class SessionElement(DrissionElement): def __eq__(self, other: SessionElement) -> bool: ... - def __getattr__(self, item: str) -> str: ... - @property def tag(self) -> str: ... @@ -92,27 +91,27 @@ class SessionElement(DrissionElement): def children(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[SessionElement, str]]: ... + ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ... def prevs(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[SessionElement, str]]: ... + ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ... def nexts(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[SessionElement, str]]: ... + ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ... def befores(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[SessionElement, str]]: ... + ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ... def afters(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[SessionElement, str]]: ... + ele_only: bool = True) -> Union[SessionElementsList, List[Union[SessionElement, str]]]: ... def attr(self, name: str) -> Optional[str]: ... @@ -123,20 +122,20 @@ class SessionElement(DrissionElement): def eles(self, locator: Union[Tuple[str, str], str], - timeout: float = None) -> List[SessionElement]: ... + timeout: float = None) -> SessionElementsList: ... def s_ele(self, locator: Union[Tuple[str, str], str] = None, index: int = 1) -> SessionElement: ... - def s_eles(self, locator: Union[Tuple[str, str], str]) -> List[SessionElement]: ... + def s_eles(self, locator: Union[Tuple[str, str], str]) -> SessionElementsList: ... def _find_elements(self, locator: Union[Tuple[str, str], str], timeout: float = None, index: Optional[int] = 1, relative: bool = False, - raise_err: bool = None) -> Union[SessionElement, List[SessionElement]]: ... + raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ... def _get_ele_path(self, mode: str) -> str: ... @@ -144,4 +143,5 @@ class SessionElement(DrissionElement): def make_session_ele(html_or_ele: Union[str, SessionElement, SessionPage, ChromiumElement, BaseElement, ChromiumFrame, ChromiumBase], loc: Union[str, Tuple[str, str]] = None, - index: Optional[int] = 1) -> Union[SessionElement, List[SessionElement]]: ... + index: Optional[int] = 1, + method: Optional[str] = None) -> Union[SessionElement, SessionElementsList]: ... diff --git a/DrissionPage/_functions/browser.py b/DrissionPage/_functions/browser.py index d0829b8..a8f79d4 100644 --- a/DrissionPage/_functions/browser.py +++ b/DrissionPage/_functions/browser.py @@ -8,12 +8,11 @@ from json import load, dump, JSONDecodeError from os import environ from pathlib import Path -from platform import system from subprocess import Popen, DEVNULL from tempfile import gettempdir from time import perf_counter, sleep -from requests import get as requests_get +from requests import Session from .tools import port_is_using from .._configs.options_manage import OptionsManager @@ -200,16 +199,21 @@ def test_connect(ip, port, timeout=30): :return: None """ end_time = perf_counter() + timeout + s = Session() + s.trust_env = False while perf_counter() < end_time: try: - tabs = requests_get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'}, - proxies={'http': None, 'https': None}).json() - for tab in tabs: + r = s.get(f'http://{ip}:{port}/json', timeout=10, headers={'Connection': 'close'}) + for tab in r.json(): if tab['type'] in ('page', 'webview'): + r.close() + s.close() return + 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' diff --git a/DrissionPage/_functions/elements.py b/DrissionPage/_functions/elements.py new file mode 100644 index 0000000..d77d359 --- /dev/null +++ b/DrissionPage/_functions/elements.py @@ -0,0 +1,507 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. +@License : BSD 3-Clause. +""" +from time import perf_counter + +from .._elements.none_element import NoneElement + + +class SessionElementsList(list): + def __init__(self, page=None, *args): + super().__init__(*args) + self._page = page + + @property + def get(self): + return Getter(self) + + @property + def filter(self): + return SessionFilter(self) + + @property + def filter_one(self): + return SessionFilterOne(self) + + +class ChromiumElementsList(SessionElementsList): + + @property + def filter(self): + return ChromiumFilter(self) + + @property + def filter_one(self): + return ChromiumFilterOne(self) + + def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None, + have_rect=None, have_text=None): + """或关系筛选元素 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :return: 筛选结果 + """ + return _search(self, displayed=displayed, checked=checked, selected=selected, enabled=enabled, + clickable=clickable, have_rect=have_rect, have_text=have_text) + + def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None, + have_rect=None, have_text=None): + """或关系筛选元素,获取一个结果 + :param index: 元素序号,从1开始 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :return: 筛选结果 + """ + return _search_one(self, index=index, displayed=displayed, checked=checked, selected=selected, + enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text) + + +class SessionFilterOne(object): + def __init__(self, _list): + self._list = _list + self._index = 1 + + def __call__(self, index=1): + """返回结果中第几个元素 + :param index: 元素序号,从1开始 + :return: 对象自身 + """ + self._index = index + return self + + def attr(self, name, value, equal=True): + """以是否拥有某个attribute值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + return self._get_attr(name, value, 'attr', equal=equal) + + def text(self, text, fuzzy=True, contain=True): + """以是否含有指定文本为条件筛选元素 + :param text: 用于匹配的文本 + :param fuzzy: 是否模糊匹配 + :param contain: 是否包含该字符串,False表示不包含 + :return: 筛选结果 + """ + num = 0 + if contain: + for i in self._list: + t = i if isinstance(i, str) else i.raw_text + if (fuzzy and text in t) or (not fuzzy and text == t): + num += 1 + if self._index == num: + return i + else: + for i in self._list: + t = i if isinstance(i, str) else i.raw_text + if (fuzzy and text not in t) or (not fuzzy and text != t): + num += 1 + if self._index == num: + return i + return NoneElement(self._list._page, 'text()', + args={'text': text, 'fuzzy': fuzzy, 'contain': contain, 'index': self._index}) + + def _get_attr(self, name, value, method, equal=True): + """返回通过某个方法可获得某个值的元素 + :param name: 属性名称 + :param value: 属性值 + :param method: 方法名称 + :return: 筛选结果 + """ + num = 0 + if equal: + for i in self._list: + if not isinstance(i, str) and getattr(i, method)(name) == value: + num += 1 + if self._index == num: + return i + else: + for i in self._list: + if not isinstance(i, str) and getattr(i, method)(name) != value: + num += 1 + if self._index == num: + return i + return NoneElement(self._list._page, f'{method}()', + args={'name': name, 'value': value, 'equal': equal, 'index': self._index}) + + +class SessionFilter(SessionFilterOne): + + def __iter__(self): + return iter(self._list) + + def __next__(self): + return next(self._list) + + def __len__(self): + return len(self._list) + + def __getitem__(self, item): + return self._list[item] + + @property + def get(self): + """返回用于获取元素属性的对象""" + return self._list.get + + def text(self, text, fuzzy=True, contain=True): + """以是否含有指定文本为条件筛选元素 + :param text: 用于匹配的文本 + :param fuzzy: 是否模糊匹配 + :param contain: 是否包含该字符串,False表示不包含 + :return: 筛选结果 + """ + self._list = _text_all(self._list, SessionElementsList(page=self._list._page), + text=text, fuzzy=fuzzy, contain=contain) + + def _get_attr(self, name, value, method, equal=True): + """返回通过某个方法可获得某个值的元素 + :param name: 属性名称 + :param value: 属性值 + :param method: 方法名称 + :return: 筛选结果 + """ + self._list = _get_attr_all(self._list, SessionElementsList(page=self._list._page), + name=name, value=value, method=method, equal=equal) + return self + + +class ChromiumFilterOne(SessionFilterOne): + + def displayed(self, equal=True): + """以是否显示为条件筛选元素 + :param equal: 是否匹配显示的元素,False匹配不显示的 + :return: 筛选结果 + """ + return self._any_state('is_displayed', equal=equal) + + def checked(self, equal=True): + """以是否被选中为条件筛选元素 + :param equal: 是否匹配被选中的元素,False匹配不被选中的 + :return: 筛选结果 + """ + return self._any_state('is_checked', equal=equal) + + def selected(self, equal=True): + """以是否被选择为条件筛选元素,用于