mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
commit
21b391ef94
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
|
@ -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)
|
||||
|
@ -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'<BrowserDriver {self.id}>'
|
||||
|
||||
def get(self, url):
|
||||
r = get(url, headers={'Connection': 'close'})
|
||||
r = self._control_session.get(url, headers={'Connection': 'close'})
|
||||
r.close()
|
||||
return r
|
||||
|
@ -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: ...
|
||||
|
@ -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'<ChromiumOptions at {id(self)}>'
|
||||
|
||||
# ---------------即将废弃--------------
|
||||
|
||||
@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)
|
||||
|
@ -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'<SessionOptions at {id(self)}>'
|
||||
|
||||
|
@ -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'<ChromiumElement {self.tag} {" ".join(attrs)}>'
|
||||
@ -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
|
||||
|
@ -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: ...
|
||||
|
@ -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
|
||||
|
@ -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'^<shadow_root>[ \n]*?<html>[ \n]*?(.*?)[ \n]*?</html>[ \n]*?</shadow_root>$', 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):
|
||||
|
@ -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]: ...
|
||||
|
@ -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'
|
||||
|
507
DrissionPage/_functions/elements.py
Normal file
507
DrissionPage/_functions/elements.py
Normal file
@ -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):
|
||||
"""以是否被选择为条件筛选元素,用于<select>元素项目
|
||||
:param equal: 是否匹配被选择的元素,False匹配不被选择的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._any_state('is_selected', equal=equal)
|
||||
|
||||
def enabled(self, equal=True):
|
||||
"""以是否可用为条件筛选元素
|
||||
:param equal: 是否匹配可用的元素,False表示匹配disabled状态的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._any_state('is_enabled', equal=equal)
|
||||
|
||||
def clickable(self, equal=True):
|
||||
"""以是否可点击为条件筛选元素
|
||||
:param equal: 是否匹配可点击的元素,False表示匹配不是可点击的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._any_state('is_clickable', equal=equal)
|
||||
|
||||
def have_rect(self, equal=True):
|
||||
"""以是否有大小为条件筛选元素
|
||||
:param equal: 是否匹配有大小的元素,False表示匹配没有大小的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._any_state('has_rect', equal=equal)
|
||||
|
||||
def style(self, name, value, equal=True):
|
||||
"""以是否拥有某个style值为条件筛选元素
|
||||
:param name: 属性名称
|
||||
:param value: 属性值
|
||||
:param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._get_attr(name, value, 'style', equal=equal)
|
||||
|
||||
def property(self, name, value, equal=True):
|
||||
"""以是否拥有某个property值为条件筛选元素
|
||||
:param name: 属性名称
|
||||
:param value: 属性值
|
||||
:param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的
|
||||
:return: 筛选结果
|
||||
"""
|
||||
return self._get_attr(name, value, 'property', equal=equal)
|
||||
|
||||
def _any_state(self, name, equal=True):
|
||||
"""
|
||||
:param name: 状态名称
|
||||
:param equal: 是否是指定状态,False表示否定状态
|
||||
:return: 选中的元素
|
||||
"""
|
||||
num = 0
|
||||
if equal:
|
||||
for i in self._list:
|
||||
if not isinstance(i, str) and getattr(i.states, name):
|
||||
num += 1
|
||||
if self._index == num:
|
||||
return i
|
||||
else:
|
||||
for i in self._list:
|
||||
if not isinstance(i, str) and not getattr(i.states, name):
|
||||
num += 1
|
||||
if self._index == num:
|
||||
return i
|
||||
return NoneElement(self._list._page, f'{name}()', args={'equal': equal, 'index': self._index})
|
||||
|
||||
|
||||
class ChromiumFilter(ChromiumFilterOne):
|
||||
|
||||
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 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._list, index=index, displayed=displayed, checked=checked, selected=selected,
|
||||
enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text)
|
||||
|
||||
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._list, displayed=displayed, checked=checked, selected=selected, enabled=enabled,
|
||||
clickable=clickable, have_rect=have_rect, have_text=have_text)
|
||||
|
||||
def text(self, text, fuzzy=True, contain=True):
|
||||
"""以是否含有指定文本为条件筛选元素
|
||||
:param text: 用于匹配的文本
|
||||
:param fuzzy: 是否模糊匹配
|
||||
:param contain: 是否包含该字符串,False表示不包含
|
||||
:return: 筛选结果
|
||||
"""
|
||||
self._list = _text_all(self._list, ChromiumElementsList(page=self._list._page),
|
||||
text=text, fuzzy=fuzzy, contain=contain)
|
||||
return self
|
||||
|
||||
def _get_attr(self, name, value, method, equal=True):
|
||||
"""返回通过某个方法可获得某个值的元素
|
||||
:param name: 属性名称
|
||||
:param value: 属性值
|
||||
:param method: 方法名称
|
||||
:return: 筛选结果
|
||||
"""
|
||||
self._list = _get_attr_all(self._list, ChromiumElementsList(page=self._list._page),
|
||||
name=name, value=value, method=method, equal=equal)
|
||||
return self
|
||||
|
||||
def _any_state(self, name, equal=True):
|
||||
"""
|
||||
:param name: 状态名称
|
||||
:param equal: 是否是指定状态,False表示否定状态
|
||||
:return: 选中的列表
|
||||
"""
|
||||
r = ChromiumElementsList(page=self._list._page)
|
||||
if equal:
|
||||
for i in self._list:
|
||||
if not isinstance(i, str) and getattr(i.states, name):
|
||||
r.append(i)
|
||||
else:
|
||||
for i in self._list:
|
||||
if not isinstance(i, str) and not getattr(i.states, name):
|
||||
r.append(i)
|
||||
self._list = r
|
||||
return self
|
||||
|
||||
|
||||
class Getter(object):
|
||||
def __init__(self, _list):
|
||||
self._list = _list
|
||||
|
||||
def links(self):
|
||||
"""返回所有元素的link属性组成的列表"""
|
||||
return [e.link for e in self._list if not isinstance(e, str)]
|
||||
|
||||
def texts(self):
|
||||
"""返回所有元素的text属性组成的列表"""
|
||||
return [e if isinstance(e, str) else e.text for e in self._list]
|
||||
|
||||
def attrs(self, name):
|
||||
"""返回所有元素指定的attr属性组成的列表
|
||||
:param name: 属性名称
|
||||
:return: 属性文本组成的列表
|
||||
"""
|
||||
return [e.attr(name) for e in self._list if not isinstance(e, str)]
|
||||
|
||||
|
||||
def get_eles(locators, owner, any_one=False, first_ele=True, timeout=10):
|
||||
"""传入多个定位符,获取多个ele
|
||||
:param locators: 定位符组成的列表
|
||||
:param owner: 页面或元素对象
|
||||
:param any_one: 是否找到任何一个即返回
|
||||
:param first_ele: 每个定位符是否只获取第一个元素
|
||||
:param timeout: 超时时间(秒)
|
||||
:return: 多个定位符组成的dict
|
||||
"""
|
||||
res = {loc: False for loc in locators}
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() <= end_time:
|
||||
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)
|
||||
if ele:
|
||||
res[loc] = ele
|
||||
if any_one:
|
||||
return res
|
||||
if False not in res.values():
|
||||
break
|
||||
return res
|
||||
|
||||
|
||||
def _get_attr_all(src_list, aim_list, name, value, method, equal=True):
|
||||
if equal:
|
||||
for i in src_list:
|
||||
if not isinstance(i, str) and getattr(i, method)(name) == value:
|
||||
aim_list.append(i)
|
||||
else:
|
||||
for i in src_list:
|
||||
if not isinstance(i, str) and getattr(i, method)(name) != value:
|
||||
aim_list.append(i)
|
||||
return aim_list
|
||||
|
||||
|
||||
def _text_all(src_list, aim_list, text, fuzzy=True, contain=True):
|
||||
"""以是否含有指定文本为条件筛选元素
|
||||
:param text: 用于匹配的文本
|
||||
:param fuzzy: 是否模糊匹配
|
||||
:param contain: 是否包含该字符串,False表示不包含
|
||||
:return: 筛选结果
|
||||
"""
|
||||
if contain:
|
||||
for i in src_list:
|
||||
t = i if isinstance(i, str) else i.raw_text
|
||||
if (fuzzy and text in t) or (not fuzzy and text == t):
|
||||
aim_list.append(i)
|
||||
else:
|
||||
for i in src_list:
|
||||
t = i if isinstance(i, str) else i.raw_text
|
||||
if (fuzzy and text not in t) or (not fuzzy and text != t):
|
||||
aim_list.append(i)
|
||||
return aim_list
|
||||
|
||||
|
||||
def _search(_list, 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: 筛选结果
|
||||
"""
|
||||
r = ChromiumElementsList(page=_list._page)
|
||||
for i in _list:
|
||||
if not isinstance(i, str) and (
|
||||
(displayed is not None and (displayed is True and i.states.is_displayed)
|
||||
or (displayed is False and not i.states.is_displayed))
|
||||
or (checked is not None and (checked is True and i.states.is_checked)
|
||||
or (checked is False and not i.states.is_checked))
|
||||
or (selected is not None and (selected is True and i.states.is_selected)
|
||||
or (selected is False and not i.states.is_selected))
|
||||
or (enabled is not None and (enabled is True and i.states.is_enabled)
|
||||
or (enabled is False and not i.states.is_enabled))
|
||||
or (clickable is not None and (clickable is True and i.states.is_clickable)
|
||||
or (clickable is False and not i.states.is_clickable))
|
||||
or (have_rect is not None and (have_rect is True and i.states.has_rect)
|
||||
or (have_rect is False and not i.states.has_rect))
|
||||
or (have_text is not None and (have_text is True and i.raw_text)
|
||||
or (have_text is False and not i.raw_text))):
|
||||
r.append(i)
|
||||
return ChromiumFilter(r)
|
||||
|
||||
|
||||
def _search_one(_list, 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: 筛选结果
|
||||
"""
|
||||
num = 0
|
||||
for i in _list:
|
||||
if not isinstance(i, str) and (
|
||||
(displayed is not None and (displayed is True and i.states.is_displayed)
|
||||
or (displayed is False and not i.states.is_displayed))
|
||||
or (checked is not None and (checked is True and i.states.is_checked)
|
||||
or (checked is False and not i.states.is_checked))
|
||||
or (selected is not None and (selected is True and i.states.is_selected)
|
||||
or (selected is False and not i.states.is_selected))
|
||||
or (enabled is not None and (enabled is True and i.states.is_enabled)
|
||||
or (enabled is False and not i.states.is_enabled))
|
||||
or (clickable is not None and (clickable is True and i.states.is_clickable)
|
||||
or (clickable is False and not i.states.is_clickable))
|
||||
or (have_rect is not None and (have_rect is True and i.states.has_rect)
|
||||
or (have_rect is False and not i.states.has_rect))
|
||||
or (have_text is not None and (have_text is True and i.raw_text)
|
||||
or (have_text is False and not i.raw_text))):
|
||||
num += 1
|
||||
if num == index:
|
||||
return i
|
||||
|
||||
return NoneElement(_list._page, method='filter()', args={'displayed': displayed,
|
||||
'checked': checked, 'selected': selected,
|
||||
'enabled': enabled, 'clickable': clickable,
|
||||
'have_rect': have_rect, 'have_text': have_text})
|
220
DrissionPage/_functions/elements.pyi
Normal file
220
DrissionPage/_functions/elements.pyi
Normal file
@ -0,0 +1,220 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Author : g1879
|
||||
@Contact : g1879@qq.com
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from typing import Union, List, Optional, Iterable
|
||||
|
||||
from .._base.base import BaseParser
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
|
||||
|
||||
def get_eles(locators: Union[List[str], tuple],
|
||||
owner: BaseParser,
|
||||
any_one: bool = False,
|
||||
first_ele: bool = True,
|
||||
timeout: float = 10) -> dict: ...
|
||||
|
||||
|
||||
class SessionElementsList(list):
|
||||
_page = ...
|
||||
|
||||
def __init__(self, page=None, *args): ...
|
||||
|
||||
@property
|
||||
def get(self) -> Getter: ...
|
||||
|
||||
@property
|
||||
def filter(self) -> SessionFilter: ...
|
||||
|
||||
@property
|
||||
def filter_one(self) -> SessionFilterOne: ...
|
||||
|
||||
def __next__(self) -> SessionElement: ...
|
||||
|
||||
|
||||
class ChromiumElementsList(SessionElementsList):
|
||||
|
||||
@property
|
||||
def filter(self) -> ChromiumFilter: ...
|
||||
|
||||
@property
|
||||
def filter_one(self) -> ChromiumFilterOne: ...
|
||||
|
||||
def search(self,
|
||||
displayed: Optional[bool] = None,
|
||||
checked: Optional[bool] = None,
|
||||
selected: Optional[bool] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
clickable: Optional[bool] = None,
|
||||
have_rect: Optional[bool] = None,
|
||||
have_text: Optional[bool] = None) -> ChromiumFilter: ...
|
||||
|
||||
def search_one(self,
|
||||
index: int = 1,
|
||||
displayed: Optional[bool] = None,
|
||||
checked: Optional[bool] = None,
|
||||
selected: Optional[bool] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
clickable: Optional[bool] = None,
|
||||
have_rect: Optional[bool] = None,
|
||||
have_text: Optional[bool] = None) -> ChromiumElement: ...
|
||||
|
||||
def __next__(self) -> ChromiumElement: ...
|
||||
|
||||
|
||||
class SessionFilterOne(object):
|
||||
_list: SessionElementsList = ...
|
||||
_index: int = ...
|
||||
|
||||
def __init__(self, _list: SessionElementsList, index: int = 1): ...
|
||||
|
||||
def __call__(self, index: int = 1) -> SessionFilterOne: ...
|
||||
|
||||
def attr(self, name: str, value: str, equal: bool = True) -> SessionElement: ...
|
||||
|
||||
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionElement: ...
|
||||
|
||||
def _get_attr(self,
|
||||
name: str,
|
||||
value: str,
|
||||
method: str,
|
||||
equal: bool = True) -> SessionElement: ...
|
||||
|
||||
|
||||
class SessionFilter(SessionFilterOne):
|
||||
|
||||
def __iter__(self) -> Iterable[SessionElement]: ...
|
||||
|
||||
def __next__(self) -> SessionElement: ...
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
|
||||
def __getitem__(self, item: int) -> SessionElement: ...
|
||||
|
||||
@property
|
||||
def get(self) -> Getter: ...
|
||||
|
||||
def attr(self, name: str, value: str, equal: bool = True) -> SessionFilter: ...
|
||||
|
||||
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionFilter: ...
|
||||
|
||||
def _get_attr(self,
|
||||
name: str,
|
||||
value: str,
|
||||
method: str,
|
||||
equal: bool = True) -> SessionFilter: ...
|
||||
|
||||
|
||||
class ChromiumFilterOne(SessionFilterOne):
|
||||
_list: ChromiumElementsList = ...
|
||||
|
||||
def __init__(self, _list: ChromiumElementsList): ...
|
||||
|
||||
def __call__(self, index: int = 1) -> ChromiumFilterOne: ...
|
||||
|
||||
def displayed(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def checked(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def selected(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def enabled(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def clickable(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def have_rect(self, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def style(self, name: str, value: str, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def property(self,
|
||||
name: str,
|
||||
value: str, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def attr(self, name: str, value: str, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def _get_attr(self,
|
||||
name: str,
|
||||
value: str,
|
||||
method: str, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
def _any_state(self, name: str, equal: bool = True) -> ChromiumElement: ...
|
||||
|
||||
|
||||
class ChromiumFilter(ChromiumFilterOne):
|
||||
|
||||
def __iter__(self) -> Iterable[ChromiumElement]: ...
|
||||
|
||||
def __next__(self) -> ChromiumElement: ...
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
|
||||
def __getitem__(self, item: int) -> ChromiumElement: ...
|
||||
|
||||
@property
|
||||
def get(self) -> Getter: ...
|
||||
|
||||
def displayed(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def checked(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def selected(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def enabled(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def clickable(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def have_rect(self, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def style(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def property(self,
|
||||
name: str,
|
||||
value: str, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def attr(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def search(self,
|
||||
displayed: Optional[bool] = None,
|
||||
checked: Optional[bool] = None,
|
||||
selected: Optional[bool] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
clickable: Optional[bool] = None,
|
||||
have_rect: Optional[bool] = None,
|
||||
have_text: Optional[bool] = None) -> ChromiumFilter: ...
|
||||
|
||||
def search_one(self,
|
||||
index: int = 1,
|
||||
displayed: Optional[bool] = None,
|
||||
checked: Optional[bool] = None,
|
||||
selected: Optional[bool] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
clickable: Optional[bool] = None,
|
||||
have_rect: Optional[bool] = None,
|
||||
have_text: Optional[bool] = None) -> ChromiumElement: ...
|
||||
|
||||
def _get_attr(self,
|
||||
name: str,
|
||||
value: str,
|
||||
method: str, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
def _any_state(self, name: str, equal: bool = True) -> ChromiumFilter: ...
|
||||
|
||||
|
||||
class Getter(object):
|
||||
_list: SessionElementsList = ...
|
||||
|
||||
def __init__(self, _list: SessionElementsList): ...
|
||||
|
||||
def links(self) -> List[str]: ...
|
||||
|
||||
def texts(self) -> List[str]: ...
|
||||
|
||||
def attrs(self, name: str) -> List[str]: ...
|
@ -9,6 +9,80 @@ from re import split
|
||||
from .by import By
|
||||
|
||||
|
||||
def locator_to_tuple(loc):
|
||||
"""解析定位字符串生成dict格式数据
|
||||
:param loc: 待处理的字符串
|
||||
:return: 格式: {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}
|
||||
"""
|
||||
loc = _preprocess(loc)
|
||||
|
||||
# 多属性查找
|
||||
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
|
||||
args = _get_args(loc)
|
||||
|
||||
# 单属性查找
|
||||
elif loc.startswith('@') and loc != '@':
|
||||
arg = _get_arg(loc[1:])
|
||||
arg.append(False)
|
||||
args = {'and': True, 'args': [arg]}
|
||||
|
||||
# 根据tag name查找
|
||||
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
|
||||
at_ind = loc.find('@')
|
||||
if at_ind == -1:
|
||||
args = {'and': True, 'args': [['tag()', '=', loc[4:].lower(), False]]}
|
||||
else:
|
||||
args_str = loc[at_ind:]
|
||||
if args_str.startswith(('@@', '@|', '@!')):
|
||||
args = _get_args(args_str)
|
||||
args['args'].append([f'tag()', '=', loc[4:at_ind].lower(), False])
|
||||
else: # t:div@aa=bb的格式
|
||||
arg = _get_arg(loc[at_ind + 1:])
|
||||
arg.append(False)
|
||||
args = {'and': True, 'args': [['tag()', '=', loc[4:at_ind].lower(), False], arg]}
|
||||
|
||||
# 根据文本查找
|
||||
elif loc.startswith(('text=', 'text:', 'text^', 'text$')):
|
||||
args = {'and': True, 'args': [['text()', loc[4], loc[5:], False]]}
|
||||
|
||||
# 根据文本模糊查找
|
||||
else:
|
||||
args = {'and': True, 'args': [['text()', '=', loc, False]]}
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def _get_args(text: str = '') -> dict:
|
||||
"""解析定位参数字符串生成dict格式数据
|
||||
:param text: 待处理的字符串
|
||||
:return: 格式: {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]}
|
||||
"""
|
||||
arg_list = []
|
||||
args = split(r'(@!|@@|@\|)', text)[1:]
|
||||
if '@@' in args and '@|' in args:
|
||||
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
|
||||
_and = '@|' not in args
|
||||
|
||||
for k in range(0, len(args) - 1, 2):
|
||||
arg = _get_arg(args[k + 1])
|
||||
if arg:
|
||||
arg.append(True if args[k] == '@!' else False) # 是否去除某个属性
|
||||
arg_list.append(arg)
|
||||
|
||||
return {'and': _and, 'args': arg_list}
|
||||
|
||||
|
||||
def _get_arg(text) -> list:
|
||||
"""解析arg=abc格式字符串,生成格式:['属性名称', '匹配方式', '属性值', 是否否定],不是式子的返回None"""
|
||||
r = split(r'([:=$^])', text, maxsplit=1)
|
||||
if not r[0]:
|
||||
return [None, None, None, None]
|
||||
# !=时只有属性名没有属性内容,查询是否存在该属性
|
||||
name = r[0] if r[0] != 'tx()' else 'text()'
|
||||
name = name if name != 't()' else 'teg()'
|
||||
return [name, None, None] if len(r) != 3 else [name, r[1], r[2]]
|
||||
|
||||
|
||||
def is_loc(text):
|
||||
"""返回text是否定位符"""
|
||||
return text.startswith(('.', '#', '@', 't:', 't=', 'tag:', 'tag=', 'tx:', 'tx=', 'tx^', 'tx$', 'text:', 'text=',
|
||||
@ -49,26 +123,8 @@ def str_to_xpath_loc(loc):
|
||||
:return: 匹配符元组
|
||||
"""
|
||||
loc_by = 'xpath'
|
||||
loc = _preprocess(loc)
|
||||
|
||||
if loc.startswith('.'):
|
||||
if loc.startswith(('.=', '.:', '.^', '.$')):
|
||||
loc = loc.replace('.', '@class', 1)
|
||||
else:
|
||||
loc = loc.replace('.', '@class=', 1)
|
||||
|
||||
elif loc.startswith('#'):
|
||||
if loc.startswith(('#=', '#:', '#^', '#$')):
|
||||
loc = loc.replace('#', '@id', 1)
|
||||
else:
|
||||
loc = loc.replace('#', '@id=', 1)
|
||||
|
||||
elif loc.startswith(('t:', 't=')):
|
||||
loc = f'tag:{loc[2:]}'
|
||||
|
||||
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
|
||||
loc = f'text{loc[2:]}'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 多属性查找
|
||||
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
|
||||
loc_str = _make_multi_xpath_str('*', loc)[1]
|
||||
@ -78,7 +134,7 @@ def str_to_xpath_loc(loc):
|
||||
loc_str = _make_single_xpath_str('*', loc)[1]
|
||||
|
||||
# 根据tag name查找
|
||||
elif loc.startswith(('tag:', 'tag=')) and loc not in ('tag:', 'tag='):
|
||||
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
|
||||
at_ind = loc.find('@')
|
||||
if at_ind == -1:
|
||||
loc_str = f'//*[name()="{loc[4:]}"]'
|
||||
@ -101,16 +157,11 @@ def str_to_xpath_loc(loc):
|
||||
# 用xpath查找
|
||||
elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):
|
||||
loc_str = loc[6:]
|
||||
elif loc.startswith(('x:', 'x=')) and loc not in ('x:', 'x='):
|
||||
loc_str = loc[2:]
|
||||
|
||||
# 用css selector查找
|
||||
elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):
|
||||
loc_by = 'css selector'
|
||||
loc_str = loc[4:]
|
||||
elif loc.startswith(('c:', 'c=')) and loc not in ('c:', 'c='):
|
||||
loc_by = 'css selector'
|
||||
loc_str = loc[2:]
|
||||
|
||||
# 根据文本模糊查找
|
||||
elif loc:
|
||||
@ -127,26 +178,8 @@ def str_to_css_loc(loc):
|
||||
:return: 匹配符元组
|
||||
"""
|
||||
loc_by = 'css selector'
|
||||
loc = _preprocess(loc)
|
||||
|
||||
if loc.startswith('.'):
|
||||
if loc.startswith(('.=', '.:', '.^', '.$')):
|
||||
loc = loc.replace('.', '@class', 1)
|
||||
else:
|
||||
loc = loc.replace('.', '@class=', 1)
|
||||
|
||||
elif loc.startswith('#'):
|
||||
if loc.startswith(('#=', '#:', '#^', '#$')):
|
||||
loc = loc.replace('#', '@id', 1)
|
||||
else:
|
||||
loc = loc.replace('#', '@id=', 1)
|
||||
|
||||
elif loc.startswith(('t:', 't=')):
|
||||
loc = f'tag:{loc[2:]}'
|
||||
|
||||
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
|
||||
loc = f'text{loc[2:]}'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 多属性查找
|
||||
if loc.startswith(('@@', '@|', '@!')) and loc not in ('@@', '@|', '@!'):
|
||||
loc_str = _make_multi_css_str('*', loc)[1]
|
||||
@ -156,7 +189,7 @@ def str_to_css_loc(loc):
|
||||
loc_by, loc_str = _make_single_css_str('*', loc)
|
||||
|
||||
# 根据tag name查找
|
||||
elif loc.startswith(('tag:', 'tag=')) and loc not in ('tag:', 'tag='):
|
||||
elif loc.startswith(('tag:', 'tag=', 'tag^', 'tag$')) and loc not in ('tag:', 'tag=', 'tag^', 'tag$'):
|
||||
at_ind = loc.find('@')
|
||||
if at_ind == -1:
|
||||
loc_str = loc[4:]
|
||||
@ -166,14 +199,12 @@ def str_to_css_loc(loc):
|
||||
loc_by, loc_str = _make_single_css_str(loc[4:at_ind], loc[at_ind:])
|
||||
|
||||
# 根据文本查找
|
||||
elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:', 'x:', 'x=')):
|
||||
elif loc.startswith(('text=', 'text:', 'text^', 'text$', 'xpath=', 'xpath:')):
|
||||
loc_by, loc_str = str_to_xpath_loc(loc)
|
||||
|
||||
# 用css selector查找
|
||||
elif loc.startswith(('css:', 'css=')) and loc not in ('css:', 'css='):
|
||||
loc_str = loc[4:]
|
||||
elif loc.startswith(('c:', 'c=')) and loc not in ('c:', 'c='):
|
||||
loc_str = loc[2:]
|
||||
|
||||
# 根据文本模糊查找
|
||||
elif loc:
|
||||
@ -202,39 +233,45 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
|
||||
len_r = len(r)
|
||||
len_r0 = len(r[0])
|
||||
if len_r == 3 and len_r0 > 1:
|
||||
symbol = r[1]
|
||||
if symbol == '=': # 精确查找
|
||||
arg = '.' if r[0] in ('@text()', '@tx()') else r[0]
|
||||
arg_str = f'{arg}={_make_search_str(r[2])}'
|
||||
|
||||
elif symbol == '^': # 匹配开头
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = f'/text()[starts-with(., {_make_search_str(r[2])})]/..'
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = f"starts-with({r[0]},{_make_search_str(r[2])})"
|
||||
|
||||
elif symbol == '$': # 匹配结尾
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = f'/text()[substring(., string-length(.) - string-length({_make_search_str(r[2])}) +1) ' \
|
||||
f'= {_make_search_str(r[2])}]/..'
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = f'substring({r[0]}, string-length({r[0]}) - string-length({_make_search_str(r[2])}) +1)' \
|
||||
f' = {_make_search_str(r[2])}'
|
||||
|
||||
elif symbol == ':': # 模糊查找
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = f'/text()[contains(., {_make_search_str(r[2])})]/..'
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = f"contains({r[0]},{_make_search_str(r[2])})"
|
||||
|
||||
if r[0] in ('@tag()', '@t()'):
|
||||
arg_str = f'name()="{r[2].lower()}"'
|
||||
else:
|
||||
raise ValueError(f'符号不正确:{symbol}')
|
||||
symbol = r[1]
|
||||
if symbol == '=': # 精确查找
|
||||
arg = '.' if r[0] in ('@text()', '@tx()') else r[0]
|
||||
arg_str = f'{arg}={_make_search_str(r[2])}'
|
||||
|
||||
elif symbol == '^': # 匹配开头
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = f'/text()[starts-with(., {_make_search_str(r[2])})]/..'
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = f"starts-with({r[0]},{_make_search_str(r[2])})"
|
||||
|
||||
elif symbol == '$': # 匹配结尾
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = (f'/text()[substring(., string-length(.) - string-length({_make_search_str(r[2])}) '
|
||||
f'+1) = {_make_search_str(r[2])}]/..')
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = (f'substring({r[0]}, string-length({r[0]}) - string-length({_make_search_str(r[2])}) '
|
||||
f'+1) = {_make_search_str(r[2])}')
|
||||
|
||||
elif symbol == ':': # 模糊查找
|
||||
if r[0] in ('@text()', '@tx()'):
|
||||
txt_str = f'/text()[contains(., {_make_search_str(r[2])})]/..'
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = f"contains({r[0]},{_make_search_str(r[2])})"
|
||||
|
||||
else:
|
||||
raise ValueError(f'符号不正确:{symbol}')
|
||||
|
||||
elif len_r != 3 and len_r0 > 1:
|
||||
arg_str = 'normalize-space(text())' if r[0] in ('@text()', '@tx()') else f'{r[0]}'
|
||||
if r[0] in ('@tag()', '@t()'):
|
||||
arg_str = ''
|
||||
else:
|
||||
arg_str = 'normalize-space(text())' if r[0] in ('@text()', '@tx()') else f'{r[0]}'
|
||||
|
||||
if arg_str:
|
||||
arg_list.append(arg_str)
|
||||
@ -252,10 +289,9 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
|
||||
args = split(r'(@!|@@|@\|)', text)[1:]
|
||||
if '@@' in args and '@|' in args:
|
||||
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
|
||||
elif '@@' in args:
|
||||
_and = True
|
||||
else: # @|
|
||||
_and = False
|
||||
_and = '@|' not in args
|
||||
tags = [] if tag == '*' else [f'name()="{tag}"']
|
||||
tags_connect = ' or '
|
||||
|
||||
for k in range(0, len(args) - 1, 2):
|
||||
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
|
||||
@ -268,23 +304,39 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
|
||||
else:
|
||||
ignore = True if args[k] == '@!' else False # 是否去除某个属性
|
||||
if len_r != 3: # 只有属性名没有属性内容,查询是否存在该属性
|
||||
if r[0] in ('tag()', 't()'):
|
||||
continue
|
||||
arg_str = 'normalize-space(text())' if r[0] in ('text()', 'tx()') else f'@{r[0]}'
|
||||
|
||||
elif len_r == 3: # 属性名和内容都有
|
||||
arg = '.' if r[0] in ('text()', 'tx()') else f'@{r[0]}'
|
||||
if r[0] in ('tag()', 't()'):
|
||||
if ignore:
|
||||
tags.append(f'not(name()="{r[2]}")')
|
||||
tags_connect = ' and '
|
||||
else:
|
||||
tags.append(f'name()="{r[2]}"')
|
||||
continue
|
||||
|
||||
symbol = r[1]
|
||||
if r[0] in ('text()', 'tx()'):
|
||||
arg = '.'
|
||||
txt = r[2]
|
||||
else:
|
||||
arg = f'@{r[0]}'
|
||||
txt = r[2]
|
||||
|
||||
if symbol == '=':
|
||||
arg_str = f'{arg}={_make_search_str(r[2])}'
|
||||
arg_str = f'{arg}={_make_search_str(txt)}'
|
||||
|
||||
elif symbol == ':':
|
||||
arg_str = f'contains({arg},{_make_search_str(r[2])})'
|
||||
arg_str = f'contains({arg},{_make_search_str(txt)})'
|
||||
|
||||
elif symbol == '^':
|
||||
arg_str = f'starts-with({arg},{_make_search_str(r[2])})'
|
||||
arg_str = f'starts-with({arg},{_make_search_str(txt)})'
|
||||
|
||||
elif symbol == '$':
|
||||
arg_str = f'substring({arg}, string-length({arg}) - string-length({_make_search_str(r[2])}) +1) ' \
|
||||
f'= {_make_search_str(r[2])}'
|
||||
arg_str = f'substring({arg}, string-length({arg}) - string-length({_make_search_str(txt)}) +1) ' \
|
||||
f'= {_make_search_str(txt)}'
|
||||
|
||||
else:
|
||||
raise ValueError(f'符号不正确:{symbol}')
|
||||
@ -296,9 +348,9 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
|
||||
arg_list.append(arg_str)
|
||||
|
||||
arg_str = ' and '.join(arg_list) if _and else ' or '.join(arg_list)
|
||||
if tag != '*':
|
||||
if tags:
|
||||
condition = f' and ({arg_str})' if arg_str else ''
|
||||
arg_str = f'name()="{tag}"{condition}'
|
||||
arg_str = f'({tags_connect.join(tags)}){condition}'
|
||||
|
||||
return 'xpath', f'//*[{arg_str}]' if arg_str else f'//*'
|
||||
|
||||
@ -330,10 +382,7 @@ def _make_multi_css_str(tag: str, text: str) -> tuple:
|
||||
args = split(r'(@!|@@|@\|)', text)[1:]
|
||||
if '@@' in args and '@|' in args:
|
||||
raise ValueError('@@和@|不能同时出现在一个定位语句中。')
|
||||
elif '@@' in args:
|
||||
_and = True
|
||||
else: # @|
|
||||
_and = False
|
||||
_and = '@|' not in args
|
||||
|
||||
for k in range(0, len(args) - 1, 2):
|
||||
r = split(r'([:=$^])', args[k + 1], maxsplit=1)
|
||||
@ -344,9 +393,18 @@ def _make_multi_css_str(tag: str, text: str) -> tuple:
|
||||
len_r = len(r)
|
||||
ignore = True if args[k] == '@!' else False # 是否去除某个属性
|
||||
if len_r != 3: # 只有属性名没有属性内容,查询是否存在该属性
|
||||
if r[0] in ('tag()', 't()'):
|
||||
continue
|
||||
arg_str = f'[{r[0]}]'
|
||||
|
||||
elif len_r == 3: # 属性名和内容都有
|
||||
if r[0] in ('tag()', 't()'):
|
||||
if tag == '*':
|
||||
tag = f':not({r[2].lower()})' if ignore else f'{r[2]}'
|
||||
else:
|
||||
tag += f',:not({r[2].lower()})' if ignore else f',{r[2]}'
|
||||
continue
|
||||
|
||||
d = {'=': '', '^': '^', '$': '$', ':': '*'}
|
||||
arg_str = f'[{r[0]}{d[r[1]]}={css_trans(r[2])}]'
|
||||
|
||||
@ -372,6 +430,9 @@ def _make_single_css_str(tag: str, text: str) -> tuple:
|
||||
return _make_single_xpath_str(tag, text)
|
||||
|
||||
r = split(r'([:=$^])', text, maxsplit=1)
|
||||
if r[0] in ('@tag()', '@t()'):
|
||||
return 'css selector', r[2]
|
||||
|
||||
if len(r) == 3:
|
||||
d = {'=': '', '^': '^', '$': '$', ':': '*'}
|
||||
arg_str = f'[{r[0][1:]}{d[r[1]]}={css_trans(r[2])}]'
|
||||
@ -472,3 +533,32 @@ def css_trans(txt):
|
||||
c = ('!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@',
|
||||
'[', '\\', ']', '^', '`', ',', '{', '|', '}', '~', ' ')
|
||||
return ''.join([fr'\{i}' if i in c else i for i in txt])
|
||||
|
||||
|
||||
def _preprocess(loc):
|
||||
"""对缩写进行处理,替换回完整写法"""
|
||||
if loc.startswith('.'):
|
||||
if loc.startswith(('.=', '.:', '.^', '.$')):
|
||||
loc = loc.replace('.', '@class', 1)
|
||||
else:
|
||||
loc = loc.replace('.', '@class=', 1)
|
||||
|
||||
elif loc.startswith('#'):
|
||||
if loc.startswith(('#=', '#:', '#^', '#$')):
|
||||
loc = loc.replace('#', '@id', 1)
|
||||
else:
|
||||
loc = loc.replace('#', '@id=', 1)
|
||||
|
||||
elif loc.startswith(('t:', 't=')):
|
||||
loc = f'tag:{loc[2:]}'
|
||||
|
||||
elif loc.startswith(('tx:', 'tx=', 'tx^', 'tx$')):
|
||||
loc = f'text{loc[2:]}'
|
||||
|
||||
elif loc.startswith(('c:', 'c=')):
|
||||
loc = f'css:{loc[2:]}'
|
||||
|
||||
elif loc.startswith(('x:', 'x=')):
|
||||
loc = f'xpath:{loc[2:]}'
|
||||
|
||||
return loc
|
||||
|
@ -8,6 +8,9 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def locator_to_tuple(loc: str) -> dict: ...
|
||||
|
||||
|
||||
def is_loc(text: str) -> bool: ...
|
||||
|
||||
|
||||
@ -17,6 +20,9 @@ def get_loc(loc: Union[tuple, str], translate_css: bool = False, css_mode: bool
|
||||
def str_to_xpath_loc(loc: str) -> tuple: ...
|
||||
|
||||
|
||||
def str_to_css_loc(loc: str) -> tuple: ...
|
||||
|
||||
|
||||
def translate_loc(loc: tuple) -> tuple: ...
|
||||
|
||||
|
||||
|
@ -10,7 +10,6 @@ from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Union, Tuple
|
||||
|
||||
from ..errors import BaseError
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
|
||||
@ -46,4 +45,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: BaseError = None) -> None: ...
|
||||
def raise_error(result: dict, ignore=None) -> None: ...
|
||||
|
@ -8,9 +8,12 @@
|
||||
from datetime import datetime
|
||||
from html import unescape
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from re import sub
|
||||
from os.path import sep
|
||||
from pathlib import Path
|
||||
from re import sub, match
|
||||
from urllib.parse import urlparse, urljoin, urlunparse
|
||||
|
||||
from DataRecorder.tools import make_valid_name
|
||||
from tldextract import extract
|
||||
|
||||
|
||||
@ -138,8 +141,11 @@ def make_absolute_link(link, baseURI=None):
|
||||
if not link:
|
||||
return link
|
||||
|
||||
link = link.strip()
|
||||
link = link.strip().replace('\\', '/')
|
||||
parsed = urlparse(link)._asdict()
|
||||
if baseURI:
|
||||
p = urlparse(baseURI)._asdict()
|
||||
baseURI = f'{p["scheme"]}://{p["netloc"]}'
|
||||
|
||||
# 是相对路径,与页面url拼接并返回
|
||||
if not parsed['netloc']:
|
||||
@ -207,7 +213,16 @@ def cookies_to_tuple(cookies):
|
||||
|
||||
elif isinstance(cookies, str):
|
||||
c_dict = {}
|
||||
for attr in cookies.strip().rstrip(';, ').split(',' if ',' in cookies else ';'):
|
||||
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)
|
||||
@ -314,8 +329,7 @@ def set_browser_cookies(page, cookies):
|
||||
tmp.append(i)
|
||||
|
||||
for i in range(len(tmp)):
|
||||
d = ''.join(tmp[i:])
|
||||
cookie['domain'] = d
|
||||
cookie['domain'] = ''.join(tmp[i:])
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
if is_cookie_in_driver(page, cookie):
|
||||
break
|
||||
@ -374,9 +388,92 @@ def get_blob(page, url, as_bytes=True):
|
||||
return result
|
||||
|
||||
|
||||
def tree(ele_or_page):
|
||||
def save_page(tab, path=None, name=None, as_pdf=False, kwargs=None):
|
||||
"""把当前页面保存为文件,如果path和name参数都为None,只返回文本
|
||||
:param tab: Tab或Page对象
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:param as_pdf: 为Ture保存为pdf,否则为mhtml且忽略kwargs参数
|
||||
:param kwargs: pdf生成参数
|
||||
:return: as_pdf为True时返回bytes,否则返回文件文本
|
||||
"""
|
||||
if name:
|
||||
if name.endswith('.pdf'):
|
||||
name = name[:-4]
|
||||
as_pdf = True
|
||||
elif name.endswith('.mhtml'):
|
||||
name = name[:-6]
|
||||
as_pdf = False
|
||||
|
||||
if path:
|
||||
path = Path(path)
|
||||
if path.suffix.lower() == '.mhtml':
|
||||
name = path.stem
|
||||
path = path.parent
|
||||
as_pdf = False
|
||||
elif path.suffix.lower() == '.pdf':
|
||||
name = path.stem
|
||||
path = path.parent
|
||||
as_pdf = True
|
||||
|
||||
return get_pdf(tab, path, name, kwargs) if as_pdf else get_mhtml(tab, path, name)
|
||||
|
||||
|
||||
def get_mhtml(page, path=None, name=None):
|
||||
"""把当前页面保存为mhtml文件,如果path和name参数都为None,只返回mhtml文本
|
||||
:param page: 要保存的页面对象
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:return: mhtml文本
|
||||
"""
|
||||
r = page.run_cdp('Page.captureSnapshot')['data']
|
||||
if path is None and name is None:
|
||||
return r
|
||||
|
||||
path = path or '.'
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
name = make_valid_name(name or page.title)
|
||||
with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8') as f:
|
||||
f.write(r.replace('\r\n', '\n'))
|
||||
return r
|
||||
|
||||
|
||||
def get_pdf(page, path=None, name=None, kwargs=None):
|
||||
"""把当前页面保存为pdf文件,如果path和name参数都为None,只返回字节
|
||||
:param page: 要保存的页面对象
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:param kwargs: pdf生成参数
|
||||
:return: pdf文本
|
||||
"""
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
kwargs['transferMode'] = 'ReturnAsBase64'
|
||||
if 'printBackground' not in kwargs:
|
||||
kwargs['printBackground'] = True
|
||||
try:
|
||||
r = page.run_cdp('Page.printToPDF', **kwargs)['data']
|
||||
except:
|
||||
raise RuntimeError('保存失败,可能浏览器版本不支持。')
|
||||
from base64 import b64decode
|
||||
r = b64decode(r)
|
||||
if path is None and name is None:
|
||||
return r
|
||||
|
||||
path = path or '.'
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
name = make_valid_name(name or page.title)
|
||||
with open(f'{path}{sep}{name}.pdf', 'wb') as f:
|
||||
f.write(r)
|
||||
return r
|
||||
|
||||
|
||||
def tree(ele_or_page, text=False, show_js=False, show_css=False):
|
||||
"""把页面或元素对象DOM结构打印出来
|
||||
:param ele_or_page: 页面或元素对象
|
||||
:param text: 是否打印文本,输入数字可指定打印文本长度上线
|
||||
:param show_js: 打印文本时是否包含<script>内文本,text参数为False时无效
|
||||
:param show_css: 打印文本时是否包含<style>内文本,text参数为False时无效
|
||||
:return: None
|
||||
"""
|
||||
|
||||
@ -396,13 +493,33 @@ def tree(ele_or_page):
|
||||
e = list_ele[i]
|
||||
|
||||
attrs = ' '.join([f"{k}='{v}'" for k, v in e.attrs.items()])
|
||||
print(f'{new_body}{tail}<{e.tag} {attrs}>'.replace('\n', ' '))
|
||||
show_text = f'{new_body}{tail}<{e.tag} {attrs}>'.replace('\n', ' ')
|
||||
if text:
|
||||
t = e('x:/text()')
|
||||
if t:
|
||||
t = t.replace('\n', ' ')
|
||||
if (e.tag not in ('script', 'style') or (e.tag == 'script' and show_js)
|
||||
or (e.tag == 'style' and show_css)):
|
||||
if text is not True:
|
||||
t = t[:text]
|
||||
show_text = f'{show_text} {t}'
|
||||
print(show_text)
|
||||
|
||||
_tree(e, new_last_one, new_body)
|
||||
|
||||
ele = ele_or_page.s_ele()
|
||||
attrs = ' '.join([f"{k}='{v}'" for k, v in ele.attrs.items()])
|
||||
print(f'<{ele.tag} {attrs}>'.replace('\n', ' '))
|
||||
show_text = f'<{ele.tag} {attrs}>'.replace('\n', ' ')
|
||||
if text:
|
||||
t = ele('x:/text()')
|
||||
if t:
|
||||
t = t.replace('\n', ' ')
|
||||
if (ele.tag not in ('script', 'style') or (ele.tag == 'script' and show_js)
|
||||
or (ele.tag == 'style' and show_css)):
|
||||
if text is not True:
|
||||
t = t[:text]
|
||||
show_text = f'{show_text} {t}'
|
||||
print(show_text)
|
||||
_tree(ele)
|
||||
|
||||
|
||||
|
@ -6,7 +6,8 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import Cookie
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
|
||||
from requests import Session
|
||||
from requests.cookies import RequestsCookieJar
|
||||
@ -14,6 +15,8 @@ from requests.cookies import RequestsCookieJar
|
||||
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
|
||||
|
||||
|
||||
def get_ele_txt(e: DrissionElement) -> str: ...
|
||||
@ -52,7 +55,28 @@ def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
|
||||
def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ...
|
||||
|
||||
|
||||
def tree(ele_or_page: BaseParser) -> None: ...
|
||||
def save_page(tab: Union[ChromiumPage, ChromiumTab],
|
||||
path: Union[Path, str, None] = None,
|
||||
name: Optional[str] = None,
|
||||
as_pdf: bool = False,
|
||||
kwargs: dict = None) -> Union[bytes, str]: ...
|
||||
|
||||
|
||||
def get_mhtml(page: Union[ChromiumPage, ChromiumTab],
|
||||
path: Optional[Path] = None,
|
||||
name: Optional[str] = None) -> Union[bytes, str]: ...
|
||||
|
||||
|
||||
def get_pdf(page: Union[ChromiumPage, ChromiumTab],
|
||||
path: Optional[Path] = None,
|
||||
name: Optional[str] = None,
|
||||
kwargs: dict = None) -> Union[bytes, str]: ...
|
||||
|
||||
|
||||
def tree(ele_or_page: BaseParser,
|
||||
text: Union[int, bool] = False,
|
||||
show_js: bool = False,
|
||||
show_css: bool = False) -> None: ...
|
||||
|
||||
|
||||
def format_headers(txt: str) -> dict: ...
|
||||
|
@ -30,7 +30,7 @@ from .._units.scroller import PageScroller
|
||||
from .._units.setter import ChromiumBaseSetter
|
||||
from .._units.states import PageStates
|
||||
from .._units.waiter import BaseWaiter
|
||||
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementNotFoundError, ElementLostError
|
||||
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementLostError
|
||||
|
||||
__ERROR__ = 'error'
|
||||
|
||||
@ -478,7 +478,7 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
"""返回cookies信息
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param as_dict: 为True时以dict格式返回且all_info无效,为False时返回list
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,为False时只返回name、value、domain
|
||||
:return: cookies信息
|
||||
@ -517,14 +517,7 @@ class ChromiumBase(BasePage):
|
||||
: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):
|
||||
"""查找所有符合条件的元素以SessionElement列表形式返回
|
||||
@ -585,6 +578,9 @@ class ChromiumBase(BasePage):
|
||||
if r is not False:
|
||||
break
|
||||
|
||||
elif nIds[__ERROR__] == 'connection disconnected':
|
||||
raise PageDisconnectedError
|
||||
|
||||
if perf_counter() >= end_time:
|
||||
return NoneElement(self) if index is not None else []
|
||||
|
||||
@ -592,9 +588,11 @@ class ChromiumBase(BasePage):
|
||||
timeout = end_time - perf_counter()
|
||||
timeout = .5 if timeout <= 0 else timeout
|
||||
result = self.driver.run('DOM.performSearch', query=loc, _timeout=timeout, includeUserAgentShadowDOM=True)
|
||||
if not result or __ERROR__ not in result:
|
||||
if result and __ERROR__ not in result:
|
||||
num = result['resultCount']
|
||||
search_ids.append(result['searchId'])
|
||||
elif result and result[__ERROR__] == 'connection disconnected':
|
||||
raise PageDisconnectedError
|
||||
|
||||
for _id in search_ids:
|
||||
self._driver.run('DOM.discardSearchResults', searchId=_id)
|
||||
@ -1065,7 +1063,7 @@ class ChromiumBase(BasePage):
|
||||
name = f'{self.title}.jpg'
|
||||
elif not name.endswith(('.jpg', '.jpeg', '.png', '.webp')):
|
||||
name = f'{name}.jpg'
|
||||
path = f'{path}{sep}{name}'
|
||||
path = f'{path}{sep}{make_valid_name(name)}'
|
||||
|
||||
path = Path(path)
|
||||
pic_type = path.suffix.lower()
|
||||
@ -1117,50 +1115,6 @@ class ChromiumBase(BasePage):
|
||||
f.write(png)
|
||||
return str(path.absolute())
|
||||
|
||||
# --------------------即将废弃---------------------
|
||||
|
||||
@property
|
||||
def page_load_strategy(self):
|
||||
return self._load_mode
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
return self.states.is_alive
|
||||
|
||||
@property
|
||||
def is_loading(self):
|
||||
"""返回页面是否正在加载状态"""
|
||||
return self._is_loading
|
||||
|
||||
@property
|
||||
def ready_state(self):
|
||||
return self._ready_state
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""返回页面总宽高,格式:(宽, 高)"""
|
||||
return self.rect.size
|
||||
|
||||
def get_session_storage(self, item=None):
|
||||
return self.session_storage(item)
|
||||
|
||||
def get_local_storage(self, item=None):
|
||||
return self.local_storage(item)
|
||||
|
||||
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
|
||||
|
||||
def upload(self, loc_or_ele, file_paths, by_js=False):
|
||||
"""触发上传文件选择框并自动填入指定路径
|
||||
:param loc_or_ele: 被点击后会触发文件选择框的元素或它的定位符
|
||||
:param file_paths: 文件路径,如果上传框支持多文件,可传入列表或字符串,字符串时多个文件用回车分隔
|
||||
:param by_js: 是否用js方式点击
|
||||
:return: None
|
||||
"""
|
||||
self.set.upload_files(file_paths)
|
||||
self.ele(loc_or_ele).click(by_js=by_js)
|
||||
self.wait.upload_paths_inputted()
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""用于保存d模式timeout信息的类"""
|
||||
@ -1228,50 +1182,3 @@ def close_privacy_dialog(page, tid):
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def get_mhtml(page, path=None, name=None):
|
||||
"""把当前页面保存为mhtml文件,如果path和name参数都为None,只返回mhtml文本
|
||||
:param page: 要保存的页面对象
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:return: mhtml文本
|
||||
"""
|
||||
r = page.run_cdp('Page.captureSnapshot')['data']
|
||||
if path is None and name is None:
|
||||
return r
|
||||
path = path or '.'
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
name = make_valid_name(name or page.title)
|
||||
with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8') as f:
|
||||
f.write(r.replace('\r\n', '\n'))
|
||||
return r
|
||||
|
||||
|
||||
def get_pdf(page, path=None, name=None, kwargs=None):
|
||||
"""把当前页面保存为pdf文件,如果path和name参数都为None,只返回字节
|
||||
:param page: 要保存的页面对象
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
:param name: 文件名,为None且path不为None时用title属性值
|
||||
:param kwargs: pdf生成参数
|
||||
:return: pdf文本
|
||||
"""
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
kwargs['transferMode'] = 'ReturnAsBase64'
|
||||
if 'printBackground' not in kwargs:
|
||||
kwargs['printBackground'] = True
|
||||
try:
|
||||
r = page.run_cdp('Page.printToPDF', **kwargs)['data']
|
||||
except:
|
||||
raise RuntimeError('保存失败,可能浏览器版本不支持。')
|
||||
from base64 import b64decode
|
||||
r = b64decode(r)
|
||||
if path is None and name is None:
|
||||
return r
|
||||
path = path or '.'
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
name = make_valid_name(name or page.title)
|
||||
with open(f'{path}{sep}{name}.pdf', 'wb') as f:
|
||||
f.write(r)
|
||||
return r
|
||||
|
@ -14,6 +14,7 @@ from .._base.browser import Browser
|
||||
from .._base.driver import Driver
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._units.actions import Actions
|
||||
@ -187,21 +188,20 @@ class ChromiumBase(BasePage):
|
||||
|
||||
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, ChromiumElement, ChromiumFrame],
|
||||
timeout: float = None,
|
||||
index: Optional[int] = 1,
|
||||
relative: bool = False,
|
||||
raise_err: bool = None) \
|
||||
-> Union[ChromiumElement, ChromiumFrame, List[Union[ChromiumElement, ChromiumFrame]]]: ...
|
||||
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, ChromiumElementsList]: ...
|
||||
|
||||
def refresh(self, ignore_cache: bool = False) -> None: ...
|
||||
|
||||
|
@ -58,11 +58,11 @@ class ChromiumFrame(ChromiumBase):
|
||||
|
||||
self._rect = None
|
||||
self._type = 'ChromiumFrame'
|
||||
end_time = perf_counter() + 2
|
||||
while perf_counter() < end_time: # todo: 优化
|
||||
if self.url not in (None, 'about:blank'):
|
||||
break
|
||||
sleep(.1)
|
||||
# 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):
|
||||
"""在内部查找元素
|
||||
@ -343,33 +343,6 @@ class ChromiumFrame(ChromiumBase):
|
||||
except:
|
||||
return None
|
||||
|
||||
# ----------------即将废弃-----------------
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""返回是否仍可用"""
|
||||
return self.states.is_alive
|
||||
|
||||
@property
|
||||
def page_size(self):
|
||||
"""返回frame内页面尺寸,格式:(宽,, 高)"""
|
||||
return self.rect.size
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""返回frame元素大小"""
|
||||
return self.frame_ele.rect.size
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
"""返回frame元素左上角的绝对坐标"""
|
||||
return self.frame_ele.rect.location
|
||||
|
||||
@property
|
||||
def locations(self):
|
||||
"""返回用于获取元素位置的对象"""
|
||||
return self.frame_ele.rect
|
||||
# ----------------即将废弃结束-----------------
|
||||
|
||||
def refresh(self):
|
||||
"""刷新frame页面"""
|
||||
self.doc_ele.run_js('this.location.reload();')
|
||||
@ -480,7 +453,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
"""返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选
|
||||
查找范围不限同级元素,而是整个DOM文档
|
||||
:param locator: 用于筛选的查询语法
|
||||
:param timeout: 查找节点的超时时间
|
||||
:param timeout: 查找节点的超时时间(秒)
|
||||
:param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入
|
||||
:return: 本元素前面的元素或节点组成的列表
|
||||
"""
|
||||
@ -490,7 +463,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
"""返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选
|
||||
查找范围不限同级元素,而是整个DOM文档
|
||||
:param locator: 用于筛选的查询语法
|
||||
:param timeout: 查找节点的超时时间
|
||||
:param timeout: 查找节点的超时时间(秒)
|
||||
:param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入
|
||||
:return: 本元素前面的元素或节点组成的列表
|
||||
"""
|
||||
@ -586,7 +559,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
"""在frame内查找单个元素
|
||||
:param locator: 定位符或元素对象
|
||||
:param timeout: 查找超时时间
|
||||
:param timeout: 查找超时时间(秒)
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
|
@ -10,9 +10,10 @@ from typing import Union, Tuple, List, Any, Optional
|
||||
|
||||
from .chromium_base import ChromiumBase
|
||||
from .chromium_page import ChromiumPage
|
||||
from .chromium_tab import ChromiumTab, WebPageTab
|
||||
from .chromium_tab import ChromiumTab
|
||||
from .web_page import WebPage
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._functions.elements import ChromiumElementsList
|
||||
from .._units.listener import FrameListener
|
||||
from .._units.rect import FrameRect
|
||||
from .._units.scroller import FrameScroller
|
||||
@ -214,7 +215,6 @@ class ChromiumFrame(ChromiumBase):
|
||||
timeout: float = None,
|
||||
index: Optional[int] = 1,
|
||||
relative: bool = False,
|
||||
raise_err: bool = None) \
|
||||
-> Union[ChromiumElement, ChromiumFrame, None, List[Union[ChromiumElement, ChromiumFrame]]]: ...
|
||||
raise_err: bool = None) -> Union[ChromiumElement, ChromiumFrame, None, ChromiumElementsList]: ...
|
||||
|
||||
def _is_inner_frame(self) -> bool: ...
|
||||
|
@ -9,14 +9,15 @@ from pathlib import Path
|
||||
from threading import Lock
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from requests import get
|
||||
from requests import Session
|
||||
|
||||
from .._base.browser import Browser
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._functions.browser import connect_browser
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import PortFinder
|
||||
from .._pages.chromium_base import ChromiumBase, get_mhtml, get_pdf, Timeout
|
||||
from .._functions.web import save_page
|
||||
from .._pages.chromium_base import ChromiumBase, Timeout
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._units.setter import ChromiumPageSetter
|
||||
from .._units.waiter import PageWaiter
|
||||
@ -27,13 +28,12 @@ class ChromiumPage(ChromiumBase):
|
||||
"""用于管理浏览器的类"""
|
||||
_PAGES = {}
|
||||
|
||||
def __new__(cls, addr_or_opts=None, tab_id=None, timeout=None, addr_driver_opts=None):
|
||||
def __new__(cls, addr_or_opts=None, tab_id=None, timeout=None):
|
||||
"""
|
||||
:param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int)
|
||||
:param tab_id: 要控制的标签页id,不指定默认为激活的
|
||||
:param timeout: 超时时间(秒)
|
||||
"""
|
||||
addr_or_opts = addr_or_opts or addr_driver_opts
|
||||
opt = handle_options(addr_or_opts)
|
||||
is_exist, browser_id = run_browser(opt)
|
||||
if browser_id in cls._PAGES:
|
||||
@ -49,7 +49,7 @@ class ChromiumPage(ChromiumBase):
|
||||
cls._PAGES[browser_id] = r
|
||||
return r
|
||||
|
||||
def __init__(self, addr_or_opts=None, tab_id=None, timeout=None, addr_driver_opts=None):
|
||||
def __init__(self, addr_or_opts=None, tab_id=None, timeout=None):
|
||||
"""
|
||||
:param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int)
|
||||
:param tab_id: 要控制的标签页id,不指定默认为激活的
|
||||
@ -76,9 +76,13 @@ class ChromiumPage(ChromiumBase):
|
||||
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)
|
||||
ws = get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
|
||||
ws = ws.json()['webSocketDebuggerUrl'].split('/')[-1]
|
||||
self._browser = Browser(self._chromium_options.address, ws, self)
|
||||
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()
|
||||
|
||||
def _d_set_runtime_settings(self):
|
||||
"""设置运行时用到的属性"""
|
||||
@ -154,7 +158,7 @@ class ChromiumPage(ChromiumBase):
|
||||
:param kwargs: pdf生成参数
|
||||
:return: as_pdf为True时返回bytes,否则返回文件文本
|
||||
"""
|
||||
return get_pdf(self, path, name, kwargs) if as_pdf else get_mhtml(self, path, name)
|
||||
return save_page(self, path, name, as_pdf, kwargs)
|
||||
|
||||
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
@ -272,30 +276,6 @@ class ChromiumPage(ChromiumBase):
|
||||
def __repr__(self):
|
||||
return f'<ChromiumPage browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
|
||||
# ----------即将废弃-----------
|
||||
def close_other_tabs(self, tabs_or_ids=None):
|
||||
"""关闭传入的标签页以外标签页,默认保留当前页。可传入多个
|
||||
:param tabs_or_ids: 要保留的标签页对象或id,可传入列表或元组,为None时保存当前页
|
||||
:return: None
|
||||
"""
|
||||
self.close_tabs(tabs_or_ids, True)
|
||||
|
||||
@property
|
||||
def tabs(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
return self.browser.tab_ids
|
||||
|
||||
def find_tabs(self, title=None, url=None, tab_type=None, single=True):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param single: 是否返回首个结果的id,为False返回所有信息
|
||||
:return: tab id或tab列表
|
||||
"""
|
||||
r = self._browser.find_tabs(title, url, tab_type)
|
||||
return r[0]['id'] if r and single else r
|
||||
|
||||
|
||||
def handle_options(addr_or_opts):
|
||||
"""设置浏览器启动属性
|
||||
@ -336,12 +316,16 @@ def run_browser(chromium_options):
|
||||
"""连接浏览器"""
|
||||
is_exist = connect_browser(chromium_options)
|
||||
try:
|
||||
ws = get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'})
|
||||
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('浏览器版本太旧,请升级。')
|
||||
raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。')
|
||||
except:
|
||||
raise BrowserConnectError('\n浏览器连接失败,如使用全局代理,须设置不代理127.0.0.1地址。')
|
||||
return is_exist, browser_id
|
||||
|
@ -11,8 +11,8 @@ from time import sleep
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.web import set_session_cookies, set_browser_cookies
|
||||
from .._pages.chromium_base import ChromiumBase, get_mhtml, get_pdf
|
||||
from .._functions.web import set_session_cookies, set_browser_cookies, save_page
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._units.setter import TabSetter, WebPageTabSetter
|
||||
from .._units.waiter import TabWaiter
|
||||
@ -91,7 +91,7 @@ class ChromiumTab(ChromiumBase):
|
||||
:param kwargs: pdf生成参数
|
||||
:return: as_pdf为True时返回bytes,否则返回文件文本
|
||||
"""
|
||||
return get_pdf(self, path, name, kwargs) if as_pdf else get_mhtml(self, path, name)
|
||||
return save_page(self, path, name, as_pdf, kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ChromiumTab browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
@ -399,7 +399,3 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
|
||||
def __repr__(self):
|
||||
return f'<WebPageTab browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
|
||||
# --------即将废弃-------
|
||||
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
|
||||
|
@ -6,7 +6,7 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple, Any, List, Optional
|
||||
from typing import Union, Tuple, Any, Optional
|
||||
|
||||
from requests import Session, Response
|
||||
|
||||
@ -18,6 +18,7 @@ from .web_page import WebPage
|
||||
from .._base.browser import Browser
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.elements import SessionElementsList, ChromiumElementsList
|
||||
from .._units.rect import TabRect
|
||||
from .._units.setter import TabSetter, WebPageTabSetter
|
||||
from .._units.waiter import TabWaiter
|
||||
@ -150,13 +151,13 @@ class WebPageTab(SessionPage, ChromiumTab):
|
||||
|
||||
def eles(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
timeout: float = None) -> List[Union[ChromiumElement, SessionElement]]: ...
|
||||
timeout: float = None) -> Union[SessionElementsList, 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 change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None: ...
|
||||
|
||||
@ -199,5 +200,4 @@ class WebPageTab(SessionPage, ChromiumTab):
|
||||
index: Optional[int] = 1,
|
||||
relative: bool = False,
|
||||
raise_err: bool = None) \
|
||||
-> Union[ChromiumElement, SessionElement, ChromiumFrame, List[SessionElement], List[
|
||||
Union[ChromiumElement, ChromiumFrame]]]: ...
|
||||
-> Union[ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]: ...
|
||||
|
@ -354,10 +354,6 @@ class SessionPage(BasePage):
|
||||
def __repr__(self):
|
||||
return f'<SessionPage url={self.url}>'
|
||||
|
||||
# ---------即将废弃---------
|
||||
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
|
||||
|
||||
|
||||
def check_headers(kwargs, headers, arg):
|
||||
"""检查kwargs或headers中是否有arg所示属性"""
|
||||
|
@ -6,7 +6,7 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Any, Union, Tuple, List, Optional
|
||||
from typing import Any, Union, Tuple, Optional
|
||||
|
||||
from requests import Session, Response
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
@ -14,6 +14,7 @@ from requests.structures import CaseInsensitiveDict
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._elements.session_element import SessionElement
|
||||
from .._functions.elements import SessionElementsList
|
||||
from .._units.setter import SessionPageSetter
|
||||
|
||||
|
||||
@ -97,21 +98,20 @@ class SessionPage(BasePage):
|
||||
|
||||
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, SessionElement] = None,
|
||||
index: int = 1) -> SessionElement: ...
|
||||
|
||||
def s_eles(self, loc: Union[Tuple[str, str], str]) -> List[SessionElement]: ...
|
||||
def s_eles(self, loc: Union[Tuple[str, str], str]) -> SessionElementsList: ...
|
||||
|
||||
def _find_elements(self,
|
||||
locator: Union[Tuple[str, str], str, SessionElement],
|
||||
timeout: float = None,
|
||||
index: Optional[int] = 1,
|
||||
relative: bool = True,
|
||||
raise_err: bool = None) \
|
||||
-> Union[SessionElement, List[SessionElement]]: ...
|
||||
raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ...
|
||||
|
||||
def cookies(self,
|
||||
as_dict: bool = False,
|
||||
|
@ -17,15 +17,14 @@ from .._units.setter import WebPageSetter
|
||||
class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
"""整合浏览器和request的页面类"""
|
||||
|
||||
def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
|
||||
def __new__(cls, mode='d', timeout=None, chromium_options=None, session_or_options=None):
|
||||
"""初始化函数
|
||||
:param mode: 'd' 或 's',即driver模式和session模式
|
||||
:param timeout: 超时时间(秒),d模式时为寻找元素时间,s模式时为连接时间,默认10秒
|
||||
:param chromium_options: Driver对象,只使用s模式时应传入False
|
||||
:param session_or_options: Session对象或SessionOptions对象,只使用d模式时应传入False
|
||||
"""
|
||||
opts = chromium_options or driver_or_options
|
||||
return super().__new__(cls, opts)
|
||||
return super().__new__(cls, chromium_options)
|
||||
|
||||
def __init__(self, mode='d', timeout=None, chromium_options=None, session_or_options=None, driver_or_options=None):
|
||||
"""初始化函数
|
||||
@ -402,7 +401,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
"""返回页面中符合条件的元素、属性或节点文本,默认返回第一个
|
||||
:param locator: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
||||
:param timeout: 查找元素超时时间,d模式专用
|
||||
:param timeout: 查找元素超时时间(秒),d模式专用
|
||||
:param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有
|
||||
:param relative: WebPage用的表示是否相对定位的参数
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
@ -415,7 +414,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
|
||||
def quit(self, timeout=5, force=True):
|
||||
"""关闭浏览器和Session
|
||||
:param timeout: 等待浏览器关闭超时时间
|
||||
:param timeout: 等待浏览器关闭超时时间(秒)
|
||||
:param force: 关闭超时是否强制终止进程
|
||||
:return: None
|
||||
"""
|
||||
@ -431,7 +430,3 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
|
||||
def __repr__(self):
|
||||
return f'<WebPage browser_id={self.browser.id} tab_id={self.tab_id}>'
|
||||
|
||||
# -------即将废弃--------
|
||||
def get_cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
return self.cookies(as_dict=as_dict, all_domains=all_domains, all_info=all_info)
|
||||
|
@ -19,6 +19,7 @@ from .._configs.chromium_options import ChromiumOptions
|
||||
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
|
||||
|
||||
|
||||
@ -108,13 +109,13 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
|
||||
def eles(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
timeout: float = None) -> List[Union[ChromiumElement, SessionElement]]: ...
|
||||
timeout: float = None) -> Union[SessionElementsList, 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 change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None: ...
|
||||
|
||||
@ -185,8 +186,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
index: Optional[int] = 1,
|
||||
relative: bool = False,
|
||||
raise_err: bool = None) \
|
||||
-> Union[ChromiumElement, SessionElement, ChromiumFrame, List[SessionElement],
|
||||
List[Union[ChromiumElement, ChromiumFrame]]]: ...
|
||||
-> Union[ChromiumElement, SessionElement, ChromiumFrame, SessionElementsList, ChromiumElementsList]: ...
|
||||
|
||||
def _set_start_options(self,
|
||||
dr_opt: Union[Driver, bool, None],
|
||||
|
@ -24,6 +24,7 @@ class Actions:
|
||||
self.modifier = 0 # 修饰符,Alt=1, Ctrl=2, Meta/Command=4, Shift=8
|
||||
self.curr_x = 0 # 视口坐标
|
||||
self.curr_y = 0
|
||||
self._holding = 'left'
|
||||
|
||||
def move_to(self, ele_or_loc, offset_x=0, offset_y=0, duration=.5):
|
||||
"""鼠标移动到元素中点,或页面上的某个绝对坐标。可设置偏移量
|
||||
@ -86,7 +87,7 @@ class Actions:
|
||||
t = perf_counter()
|
||||
self.curr_x = x
|
||||
self.curr_y = y
|
||||
self._dr.run('Input.dispatchMouseEvent', type='mouseMoved',
|
||||
self._dr.run('Input.dispatchMouseEvent', type='mouseMoved', button=self._holding,
|
||||
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
|
||||
ss = .02 - perf_counter() + t
|
||||
if ss > 0:
|
||||
@ -140,7 +141,7 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
if on_ele:
|
||||
self.move_to(on_ele, duration=0)
|
||||
self.move_to(on_ele, duration=.2)
|
||||
self._release('left')
|
||||
return self
|
||||
|
||||
@ -158,7 +159,7 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
if on_ele:
|
||||
self.move_to(on_ele, duration=0)
|
||||
self.move_to(on_ele, duration=.2)
|
||||
self._release('right')
|
||||
return self
|
||||
|
||||
@ -176,7 +177,7 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
if on_ele:
|
||||
self.move_to(on_ele, duration=0)
|
||||
self.move_to(on_ele, duration=.2)
|
||||
self._release('middle')
|
||||
return self
|
||||
|
||||
@ -188,9 +189,10 @@ class Actions:
|
||||
:return: self
|
||||
"""
|
||||
if on_ele:
|
||||
self.move_to(on_ele, duration=0)
|
||||
self.move_to(on_ele, duration=.2)
|
||||
self._dr.run('Input.dispatchMouseEvent', type='mousePressed', button=button, clickCount=count,
|
||||
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
|
||||
self._holding = button
|
||||
return self
|
||||
|
||||
def _release(self, button):
|
||||
@ -200,17 +202,18 @@ class Actions:
|
||||
"""
|
||||
self._dr.run('Input.dispatchMouseEvent', type='mouseReleased', button=button, clickCount=1,
|
||||
x=self.curr_x, y=self.curr_y, modifiers=self.modifier)
|
||||
self._holding = 'left'
|
||||
return self
|
||||
|
||||
def scroll(self, delta_x=0, delta_y=0, on_ele=None):
|
||||
def scroll(self, delta_y=0, delta_x=0, on_ele=None):
|
||||
"""滚动鼠标滚轮,可先移动到元素上
|
||||
:param delta_x: 滚轮变化值x
|
||||
:param delta_y: 滚轮变化值y
|
||||
:param delta_x: 滚轮变化值x
|
||||
:param on_ele: ChromiumElement元素
|
||||
:return: self
|
||||
"""
|
||||
if on_ele:
|
||||
self.move_to(on_ele, duration=0)
|
||||
self.move_to(on_ele, duration=.2)
|
||||
self._dr.run('Input.dispatchMouseEvent', type='mouseWheel', x=self.curr_x, y=self.curr_y,
|
||||
deltaX=delta_x, deltaY=delta_y, modifiers=self.modifier)
|
||||
return self
|
||||
|
@ -50,11 +50,12 @@ class Actions:
|
||||
self.modifier: int = ...
|
||||
self.curr_x: int = ...
|
||||
self.curr_y: int = ...
|
||||
self._holding: str = ...
|
||||
|
||||
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[int, int], str],
|
||||
offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> Actions: ...
|
||||
def move_to(self, ele_or_loc: Union[ChromiumElement, Tuple[float, float], str],
|
||||
offset_x: float = 0, offset_y: float = 0, duration: float = .5) -> Actions: ...
|
||||
|
||||
def move(self, offset_x: int = 0, offset_y: int = 0, duration: float = .5) -> 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: ...
|
||||
|
||||
@ -81,7 +82,7 @@ class Actions:
|
||||
|
||||
def _release(self, button: str) -> Actions: ...
|
||||
|
||||
def scroll(self, delta_x: int = 0, delta_y: int = 0,
|
||||
def scroll(self, delta_y: int = 0, delta_x: int = 0,
|
||||
on_ele: Union[ChromiumElement, str] = None) -> Actions: ...
|
||||
|
||||
def up(self, pixel: int) -> Actions: ...
|
||||
|
@ -87,8 +87,8 @@ 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=x, y=y, includeUserAgentShadowDOM=True,
|
||||
ignorePointerEventsNone=True)
|
||||
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
|
||||
else:
|
||||
@ -113,11 +113,19 @@ class Clicker(object):
|
||||
x, y = self._ele.rect.viewport_click_point
|
||||
self._click(x, y, 'right')
|
||||
|
||||
def middle(self):
|
||||
"""中键单击"""
|
||||
def middle(self, get_tab=True):
|
||||
"""中键单击,默认返回新出现的tab对象
|
||||
:param get_tab: 是否返回新tab对象,为False则返回None
|
||||
:return: Tab对象或None
|
||||
"""
|
||||
self._ele.owner.scroll.to_see(self._ele)
|
||||
x, y = self._ele.rect.viewport_click_point
|
||||
self._click(x, y, 'middle')
|
||||
if get_tab:
|
||||
tid = self._ele.page.wait.new_tab()
|
||||
if not tid:
|
||||
raise RuntimeError('没有出现新标签页。')
|
||||
return self._ele.page.get_tab(tid)
|
||||
|
||||
def at(self, offset_x=None, offset_y=None, button='left', count=1):
|
||||
"""带偏移量点击本元素,相对于左上角坐标。不传入x或y值时点击元素中间点
|
||||
@ -196,19 +204,5 @@ class Clicker(object):
|
||||
"""
|
||||
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x,
|
||||
y=client_y, button=button, clickCount=count, _ignore=AlertExistsError)
|
||||
# sleep(.05)
|
||||
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x,
|
||||
y=client_y, button=button, _ignore=AlertExistsError)
|
||||
|
||||
# -------------即将废弃--------------
|
||||
|
||||
def twice(self):
|
||||
"""双击元素"""
|
||||
self.at(count=2)
|
||||
|
||||
def multiple(self, times=2):
|
||||
"""多次点击
|
||||
:param times: 默认双击
|
||||
:return: None
|
||||
"""
|
||||
self.at(count=times)
|
||||
|
@ -23,7 +23,7 @@ class Clicker(object):
|
||||
|
||||
def right(self) -> None: ...
|
||||
|
||||
def middle(self) -> None: ...
|
||||
def middle(self, get_tab: bool = True) -> Union[ChromiumTab, WebPageTab, None]: ...
|
||||
|
||||
def at(self,
|
||||
offset_x: float = None,
|
||||
@ -39,10 +39,10 @@ class Clicker(object):
|
||||
suffix: str = None,
|
||||
new_tab: bool = False,
|
||||
by_js: bool = False,
|
||||
timeout:float=None) -> DownloadMission: ...
|
||||
timeout: float = None) -> DownloadMission: ...
|
||||
|
||||
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) -> Union[ChromiumTab, WebPageTab]: ...
|
||||
|
||||
def _click(self, client_x: float, client_y: float, button: str = 'left', count: int = 1) -> None: ...
|
||||
|
@ -292,7 +292,7 @@ class DownloadMission(object):
|
||||
def wait(self, show=True, timeout=None, cancel_if_timeout=True):
|
||||
"""等待任务结束
|
||||
:param show: 是否显示下载信息
|
||||
:param timeout: 超时时间,为None则无限等待
|
||||
:param timeout: 超时时间(秒),为None则无限等待
|
||||
:param cancel_if_timeout: 超时时是否取消任务
|
||||
:return: 等待成功返回完整路径,否则返回False
|
||||
"""
|
||||
|
@ -119,7 +119,7 @@ class Listener(object):
|
||||
def wait(self, count=1, timeout=None, fit_count=True, raise_err=None):
|
||||
"""等待符合要求的数据包到达指定数量
|
||||
:param count: 需要捕捉的数据包数量
|
||||
:param timeout: 超时时间,为None无限等待
|
||||
:param timeout: 超时时间(秒),为None无限等待
|
||||
:param fit_count: 是否必须满足总数要求,发生超时,为True返回False,为False返回已捕捉到的数据包
|
||||
:param raise_err: 超时时是否抛出错误,为None时根据Settings设置
|
||||
:return: count为1时返回数据包对象,大于1时返回列表,超时且fit_count为True时返回False
|
||||
@ -128,7 +128,7 @@ class Listener(object):
|
||||
raise RuntimeError('监听未启动或已暂停。')
|
||||
if not timeout:
|
||||
while self._caught.qsize() < count:
|
||||
sleep(.05)
|
||||
sleep(.03)
|
||||
fail = False
|
||||
|
||||
else:
|
||||
@ -140,6 +140,7 @@ class Listener(object):
|
||||
if self._caught.qsize() >= count:
|
||||
fail = False
|
||||
break
|
||||
sleep(.03)
|
||||
|
||||
if fail:
|
||||
if fit_count or not self._caught.qsize():
|
||||
@ -158,7 +159,7 @@ class Listener(object):
|
||||
def steps(self, count=None, timeout=None, gap=1):
|
||||
"""用于单步操作,可实现每收到若干个数据包执行一步操作(如翻页)
|
||||
:param count: 需捕获的数据包总数,为None表示无限
|
||||
:param timeout: 每个数据包等待时间,为None表示无限
|
||||
:param timeout: 每个数据包等待时间(秒),为None表示无限
|
||||
:param gap: 每接收到多少个数据包返回一次数据
|
||||
:return: 用于在接收到监听目标时触发动作的可迭代对象
|
||||
"""
|
||||
@ -177,7 +178,7 @@ class Listener(object):
|
||||
caught += gap
|
||||
if caught >= count:
|
||||
return
|
||||
sleep(.05)
|
||||
sleep(.03)
|
||||
|
||||
def stop(self):
|
||||
"""停止监听,清空已监听到的列表"""
|
||||
@ -218,7 +219,7 @@ class Listener(object):
|
||||
|
||||
def wait_silent(self, timeout=None, targets_only=False, limit=0):
|
||||
"""等待所有请求结束
|
||||
:param timeout: 超时,为None时无限等待
|
||||
:param timeout: 超时时间(秒),为None时无限等待
|
||||
:param targets_only: 是否只等待targets指定的请求结束
|
||||
:param limit: 剩下多少个连接时视为结束
|
||||
:return: 返回是否等待成功
|
||||
@ -250,13 +251,13 @@ class Listener(object):
|
||||
self._target_id = target_id
|
||||
self._address = address
|
||||
self._owner = owner
|
||||
debug = False
|
||||
# debug = False
|
||||
if self._driver:
|
||||
debug = self._driver._debug
|
||||
# debug = self._driver._debug
|
||||
self._driver.stop()
|
||||
if self.listening:
|
||||
self._driver = Driver(self._target_id, 'page', self._address)
|
||||
self._driver._debug = debug
|
||||
# self._driver._debug = debug
|
||||
self._driver.run('Network.enable')
|
||||
self._set_callback()
|
||||
|
||||
@ -479,7 +480,7 @@ class DataPacket(object):
|
||||
|
||||
def wait_extra_info(self, timeout=None):
|
||||
"""等待额外的信息加载完成
|
||||
:param timeout: 超时时间,None为无限等待
|
||||
:param timeout: 超时时间(秒),None为无限等待
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout is None:
|
||||
|
@ -16,7 +16,7 @@ class ElementRect(object):
|
||||
|
||||
@property
|
||||
def corners(self):
|
||||
"""返回元素四个角坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError"""
|
||||
"""返回元素四个角坐标,顺序:左上、右上、右下、左下,没有大小的元素抛出NoRectError"""
|
||||
vr = self._get_viewport_rect('border')
|
||||
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
|
||||
sx = r['pageX']
|
||||
@ -25,7 +25,7 @@ class ElementRect(object):
|
||||
|
||||
@property
|
||||
def viewport_corners(self):
|
||||
"""返回元素四个角视口坐标,顺序:坐上、右上、右下、左下,没有大小的元素抛出NoRectError"""
|
||||
"""返回元素四个角视口坐标,顺序:左上、右上、右下、左下,没有大小的元素抛出NoRectError"""
|
||||
r = self._get_viewport_rect('border')
|
||||
return (r[0], r[1]), (r[2], r[3]), (r[4], r[5]), (r[6], r[7])
|
||||
|
||||
@ -225,10 +225,10 @@ class FrameRect(object):
|
||||
|
||||
@property
|
||||
def corners(self):
|
||||
"""返回元素四个角坐标,顺序:坐上、右上、右下、左下"""
|
||||
"""返回元素四个角坐标,顺序:左上、右上、右下、左下"""
|
||||
return self._frame.frame_ele.rect.corners
|
||||
|
||||
@property
|
||||
def viewport_corners(self):
|
||||
"""返回元素四个角视口坐标,顺序:坐上、右上、右下、左下"""
|
||||
"""返回元素四个角视口坐标,顺序:左上、右上、右下、左下"""
|
||||
return self._frame.frame_ele.rect.viewport_corners
|
||||
|
@ -23,7 +23,7 @@ class SelectElement(object):
|
||||
def __call__(self, text_or_index, timeout=None):
|
||||
"""选定下拉列表中子元素
|
||||
:param text_or_index: 根据文本、值选或序号择选项,若允许多选,传入list或tuple可多选
|
||||
:param timeout: 超时时间,不输入默认实用页面超时时间
|
||||
:param timeout: 超时时间(秒),不输入默认实用页面超时时间
|
||||
:return: None
|
||||
"""
|
||||
para_type = 'index' if isinstance(text_or_index, int) else 'text'
|
||||
@ -82,7 +82,7 @@ class SelectElement(object):
|
||||
def by_text(self, text, timeout=None):
|
||||
"""此方法用于根据text值选择项。当元素是多选列表时,可以接收list或tuple
|
||||
:param text: text属性值,传入list或tuple可选择多项
|
||||
:param timeout: 超时时间,为None默认使用页面超时时间
|
||||
:param timeout: 超时时间(秒),为None默认使用页面超时时间
|
||||
:return: 是否选择成功
|
||||
"""
|
||||
return self._select(text, 'text', False, timeout)
|
||||
|
@ -14,7 +14,7 @@ from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesS
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import show_or_hide_browser
|
||||
from .._functions.web import format_headers
|
||||
from ..errors import ElementLostError
|
||||
from ..errors import ElementLostError, JavaScriptError
|
||||
|
||||
|
||||
class BasePageSetter(object):
|
||||
@ -173,13 +173,6 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
self._owner.run_cdp('Network.enable')
|
||||
self._owner.run_cdp('Network.setBlockedURLs', urls=urls)
|
||||
|
||||
# --------------即将废弃---------------
|
||||
|
||||
@property
|
||||
def load_strategy(self):
|
||||
"""返回用于设置页面加载策略的对象"""
|
||||
return LoadMode(self._owner)
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
def __init__(self, owner):
|
||||
@ -494,6 +487,17 @@ class ChromiumElementSetter(object):
|
||||
value = value.replace('"', r'\"')
|
||||
self._ele.run_js(f'this.{name}="{value}";')
|
||||
|
||||
def style(self, name, value):
|
||||
"""设置元素style样式
|
||||
:param name: 样式名称
|
||||
:param value: 样式值
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._ele.run_js(f'this.style.{name}="{value}";')
|
||||
except JavaScriptError:
|
||||
raise ValueError(f'设置失败,请检查属性名{name}')
|
||||
|
||||
def innerHTML(self, html):
|
||||
"""设置元素innerHTML
|
||||
:param html: html文本
|
||||
|
@ -172,6 +172,8 @@ class ChromiumElementSetter(object):
|
||||
|
||||
def property(self, name: str, value: str) -> None: ...
|
||||
|
||||
def style(self, name: str, value: str) -> None: ...
|
||||
|
||||
def innerHTML(self, html: str) -> None: ...
|
||||
|
||||
def value(self, value: str) -> None: ...
|
||||
|
@ -18,7 +18,7 @@ class ElementStates(object):
|
||||
|
||||
@property
|
||||
def is_selected(self):
|
||||
"""返回元素是否被选择"""
|
||||
"""返回列表元素是否被选择"""
|
||||
return self._ele.run_js('return this.selected;')
|
||||
|
||||
@property
|
||||
@ -42,9 +42,9 @@ class ElementStates(object):
|
||||
def is_alive(self):
|
||||
"""返回元素是否仍在DOM中"""
|
||||
try:
|
||||
self._ele.attrs
|
||||
return True
|
||||
except Exception:
|
||||
return self._ele.owner.run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
|
||||
except ElementLostError:
|
||||
return False
|
||||
|
||||
@property
|
||||
@ -71,6 +71,11 @@ class ElementStates(object):
|
||||
except CDPError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_clickable(self):
|
||||
"""返回元素是否可被模拟点击,从是否有大小、是否可用、是否显示、是否响应点击判断,不判断是否被遮挡"""
|
||||
return self.has_rect and self.is_enabled and self.is_displayed and self._ele.style('pointer-events') != 'none'
|
||||
|
||||
@property
|
||||
def has_rect(self):
|
||||
"""返回元素是否拥有位置和大小,没有返回False,有返回四个角在页面中坐标组成的列表"""
|
||||
@ -96,9 +101,9 @@ class ShadowRootStates(object):
|
||||
def is_alive(self):
|
||||
"""返回元素是否仍在DOM中"""
|
||||
try:
|
||||
self._ele.owner.run_cdp('DOM.describeNode', backendNodeId=self._ele._backend_id)
|
||||
return True
|
||||
except Exception:
|
||||
return self._ele.owner.run_cdp('DOM.describeNode',
|
||||
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
|
||||
except ElementLostError:
|
||||
return False
|
||||
|
||||
|
||||
|
@ -41,7 +41,10 @@ class ElementStates(object):
|
||||
def is_covered(self) -> Union[Literal[False], int]: ...
|
||||
|
||||
@property
|
||||
def has_rect(self) -> Union[bool, List[Tuple[float, float]]]: ...
|
||||
def is_clickable(self) -> bool: ...
|
||||
|
||||
@property
|
||||
def has_rect(self) -> Union[Literal[False], List[Tuple[float, float]]]: ...
|
||||
|
||||
|
||||
class ShadowRootStates(object):
|
||||
|
@ -36,7 +36,7 @@ class BaseWaiter(OriginWaiter):
|
||||
def ele_deleted(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
"""等待元素从DOM中删除
|
||||
:param loc_or_ele: 要等待的元素,可以是已有元素、定位符
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param timeout: 超时时间(秒),默认读取页面超时时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -46,7 +46,7 @@ class BaseWaiter(OriginWaiter):
|
||||
def ele_displayed(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
"""等待元素变成显示状态
|
||||
:param loc_or_ele: 要等待的元素,可以是已有元素、定位符
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param timeout: 超时时间(秒),默认读取页面超时时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -65,7 +65,7 @@ class BaseWaiter(OriginWaiter):
|
||||
def ele_hidden(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
"""等待元素变成隐藏状态
|
||||
:param loc_or_ele: 要等待的元素,可以是已有元素、定位符
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param timeout: 超时时间(秒),默认读取页面超时时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -84,7 +84,7 @@ class BaseWaiter(OriginWaiter):
|
||||
def eles_loaded(self, locators, timeout=None, any_one=False, raise_err=None):
|
||||
"""等待元素加载到DOM,可等待全部或任意一个
|
||||
:param locators: 要等待的元素,输入定位符,用list输入多个
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param timeout: 超时时间(秒),默认读取页面超时时间
|
||||
:param any_one: 是否等待到一个就返回
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 成功返回True,失败返回False
|
||||
@ -119,9 +119,10 @@ class BaseWaiter(OriginWaiter):
|
||||
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])
|
||||
method = any if any_one else all
|
||||
|
||||
timeout = self._driver.timeout if timeout is None else timeout
|
||||
end_time = perf_counter() + timeout
|
||||
method = any if any_one else all
|
||||
while perf_counter() < end_time:
|
||||
if method([_find(l, self._driver.driver) for l in locators]):
|
||||
return True
|
||||
@ -133,7 +134,7 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
def load_start(self, timeout=None, raise_err=None):
|
||||
"""等待页面开始加载
|
||||
:param timeout: 超时时间,为None时使用页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None时使用页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -141,7 +142,7 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
def doc_loaded(self, timeout=None, raise_err=None):
|
||||
"""等待页面加载完成
|
||||
:param timeout: 超时时间,为None时使用页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None时使用页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -158,7 +159,7 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
def download_begin(self, timeout=None, cancel_it=False):
|
||||
"""等待浏览器下载开始,可将其拦截
|
||||
:param timeout: 超时时间,None使用页面对象超时时间
|
||||
:param timeout: 超时时间(秒),None使用页面对象超时时间
|
||||
:param cancel_it: 是否取消该任务
|
||||
:return: 成功返回任务对象,失败返回False
|
||||
"""
|
||||
@ -184,7 +185,7 @@ class BaseWaiter(OriginWaiter):
|
||||
"""等待url变成包含或不包含指定文本
|
||||
:param text: 用于识别的文本
|
||||
:param exclude: 是否排除,为True时当url不包含text指定文本时返回True
|
||||
:param timeout: 超时时间
|
||||
:param timeout: 超时时间(秒)
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -194,7 +195,7 @@ class BaseWaiter(OriginWaiter):
|
||||
"""等待title变成包含或不包含指定文本
|
||||
:param text: 用于识别的文本
|
||||
:param exclude: 是否排除,为True时当title不包含text指定文本时返回True
|
||||
:param timeout: 超时时间
|
||||
:param timeout: 超时时间(秒)
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -205,7 +206,7 @@ class BaseWaiter(OriginWaiter):
|
||||
:param arg: 要被匹配的属性
|
||||
:param text: 用于识别的文本
|
||||
:param exclude: 是否排除,为True时当属性不包含text指定文本时返回True
|
||||
:param timeout: 超时时间
|
||||
:param timeout: 超时时间(秒)
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -231,7 +232,7 @@ class BaseWaiter(OriginWaiter):
|
||||
|
||||
def _loading(self, timeout=None, start=True, gap=.01, raise_err=None):
|
||||
"""等待页面开始加载或加载完成
|
||||
:param timeout: 超时时间,为None时使用页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None时使用页面timeout属性
|
||||
:param start: 等待开始还是结束
|
||||
:param gap: 间隔秒数
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
@ -251,45 +252,12 @@ class BaseWaiter(OriginWaiter):
|
||||
else:
|
||||
return False
|
||||
|
||||
# -----------即将废弃-----------
|
||||
|
||||
def data_packets(self, count=1, timeout=None, fix_count: bool = True):
|
||||
"""等待符合要求的数据包到达指定数量
|
||||
:param count: 需要捕捉的数据包数量
|
||||
:param timeout: 超时时间,为None无限等待
|
||||
:param fix_count: 是否必须满足总数要求,发生超时,为True返回False,为False返回已捕捉到的数据包
|
||||
:return: count为1时返回数据包对象,大于1时返回列表,超时且fix_count为True时返回False"""
|
||||
return self._driver.listen.wait(count, timeout, fix_count)
|
||||
|
||||
def load_complete(self, timeout=None, raise_err=None):
|
||||
"""等待页面加载完成
|
||||
:param timeout: 超时时间,为None时使用页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
return self._loading(timeout=timeout, start=False, raise_err=raise_err)
|
||||
|
||||
def ele_loaded(self, locator, timeout=None, raise_err=None):
|
||||
"""等待元素加载到DOM
|
||||
:param locator: 要等待的元素,输入定位符
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
ele = self._driver._ele(locator, raise_err=False, timeout=timeout)
|
||||
if ele:
|
||||
return ele
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(f'等待元素加载失败(等待{timeout}秒)。')
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class TabWaiter(BaseWaiter):
|
||||
|
||||
def downloads_done(self, timeout=None, cancel_if_timeout=True):
|
||||
"""等待所有浏览器下载任务结束
|
||||
:param timeout: 超时时间,为None时无限等待
|
||||
:param timeout: 超时时间(秒),为None时无限等待
|
||||
:param cancel_if_timeout: 超时时是否取消剩余任务
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -329,7 +297,7 @@ class PageWaiter(TabWaiter):
|
||||
|
||||
def new_tab(self, timeout=None, raise_err=None):
|
||||
"""等待新标签页出现
|
||||
:param timeout: 等待超时时间,为None则使用页面对象timeout属性
|
||||
:param timeout: 超时时间(秒),为None则使用页面对象timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 等到新标签页返回其id,否则返回False
|
||||
"""
|
||||
@ -348,7 +316,7 @@ class PageWaiter(TabWaiter):
|
||||
|
||||
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
|
||||
"""等待所有浏览器下载任务结束
|
||||
:param timeout: 超时时间,为None时无限等待
|
||||
:param timeout: 超时时间(秒),为None时无限等待
|
||||
:param cancel_if_timeout: 超时时是否取消剩余任务
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -388,7 +356,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def deleted(self, timeout=None, raise_err=None):
|
||||
"""等待元素从dom删除
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -396,7 +364,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def displayed(self, timeout=None, raise_err=None):
|
||||
"""等待元素从dom显示
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -404,7 +372,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def hidden(self, timeout=None, raise_err=None):
|
||||
"""等待元素从dom隐藏
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -412,15 +380,15 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def covered(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素被遮盖
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
:return: 成功返回覆盖元素id,返回False
|
||||
"""
|
||||
return self._wait_state('is_covered', True, timeout, raise_err, err_text='等待元素被覆盖失败。')
|
||||
|
||||
def not_covered(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素不被遮盖
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -428,7 +396,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def enabled(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素变成可用
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -436,7 +404,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def disabled(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素变成不可用
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -444,7 +412,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def disabled_or_deleted(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素变成不可用或从DOM移除
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -461,10 +429,10 @@ class ElementWaiter(OriginWaiter):
|
||||
else:
|
||||
return False
|
||||
|
||||
def stop_moving(self, gap=.1, timeout=None, raise_err=None):
|
||||
def stop_moving(self, timeout=None, gap=.1, raise_err=None):
|
||||
"""等待当前元素停止运动
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param gap: 检测间隔时间
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
@ -494,33 +462,51 @@ class ElementWaiter(OriginWaiter):
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_rect(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素有大小及位置属性
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
def clickable(self, wait_moved=True, timeout=None, raise_err=None):
|
||||
"""等待当前元素可被点击
|
||||
:param wait_moved: 是否等待元素运动结束
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
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
|
||||
if raise_err and not r:
|
||||
raise WaitTimeoutError(f'等待元素可点击失败(等{timeout}秒)。')
|
||||
return r
|
||||
|
||||
def has_rect(self, timeout=None, raise_err=None):
|
||||
"""等待当前元素有大小及位置属性
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 成功返回元素四角坐标(左上 右上 右下 左下),失败返回False
|
||||
"""
|
||||
return self._wait_state('has_rect', True, timeout, raise_err, err_text='等待元素拥有大小及位置失败(等{}秒)。')
|
||||
|
||||
def _wait_state(self, attr, mode=False, timeout=None, raise_err=None, err_text=None):
|
||||
"""等待元素某个元素状态到达指定状态
|
||||
:param attr: 状态名称
|
||||
:param mode: True或False
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
:param mode: 等待True还是False
|
||||
:param timeout: 超时时间(秒),为None使用元素所在页面timeout属性
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:param err_text: 抛出错误时显示的信息
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
|
||||
a = self._ele.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
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
a = self._ele.states.__getattribute__(attr)
|
||||
if (a and mode) or (not a and not mode):
|
||||
return True
|
||||
return True if isinstance(a, bool) else a
|
||||
sleep(.05)
|
||||
|
||||
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(err_text.format(timeout))
|
||||
else:
|
||||
|
@ -5,7 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from typing import Union, Tuple
|
||||
from typing import Union, Tuple, Literal, List
|
||||
|
||||
from .downloader import DownloadMission
|
||||
from .._elements.chromium_element import ChromiumElement
|
||||
@ -91,7 +91,7 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def hidden(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def covered(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def covered(self, timeout: float = None, raise_err: bool = None) -> Union[Literal[False], int]: ...
|
||||
|
||||
def not_covered(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
@ -99,11 +99,15 @@ class ElementWaiter(OriginWaiter):
|
||||
|
||||
def disabled(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def has_rect(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def clickable(self, wait_moved: bool = True, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def has_rect(self,
|
||||
timeout: float = None,
|
||||
raise_err: bool = None) -> Union[Literal[False], List[Tuple[float, float]]]: ...
|
||||
|
||||
def disabled_or_deleted(self, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def stop_moving(self, gap: float = .1, timeout: float = None, raise_err: bool = None) -> bool: ...
|
||||
def stop_moving(self, timeout: float = None, gap: float = .1, raise_err: bool = None) -> bool: ...
|
||||
|
||||
def _wait_state(self,
|
||||
attr: str,
|
||||
|
@ -7,6 +7,7 @@
|
||||
"""
|
||||
from ._elements.session_element import make_session_ele
|
||||
from ._functions.by import By
|
||||
from ._functions.elements import get_eles
|
||||
from ._functions.keys import Keys
|
||||
from ._functions.settings import Settings
|
||||
from ._functions.tools import wait_until, configs_to_here
|
||||
@ -15,7 +16,7 @@ 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',
|
||||
'tree', 'from_selenium', 'from_playwright']
|
||||
'tree', 'from_selenium', 'from_playwright', 'get_eles']
|
||||
|
||||
|
||||
def from_selenium(driver):
|
||||
|
Loading…
x
Reference in New Issue
Block a user