mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
改进css_path;修复sr中可能获取错元素问题,未完成
This commit is contained in:
parent
8699bc82d3
commit
75f05062fb
@ -6,9 +6,11 @@
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from .chromium_driver import BrowserDriver, ChromiumDriver
|
||||
from .._commons.tools import stop_process_on_port
|
||||
from .._commons.tools import stop_process_on_port, raise_error
|
||||
from .._units.download_manager import DownloadManager
|
||||
|
||||
__ERROR__ = 'error'
|
||||
|
||||
|
||||
class Browser(object):
|
||||
BROWSERS = {}
|
||||
@ -88,7 +90,8 @@ class Browser(object):
|
||||
:param cmd_args: 参数
|
||||
:return: 执行的结果
|
||||
"""
|
||||
return self._driver.run(cmd, **cmd_args)
|
||||
r = self._driver.run(cmd, **cmd_args)
|
||||
return r if __ERROR__ not in r else raise_error(r)
|
||||
|
||||
@property
|
||||
def driver(self):
|
||||
|
@ -13,17 +13,18 @@ def is_loc(text):
|
||||
'text^', 'text$', 'xpath:', 'xpath=', 'x:', 'x=', 'css:', 'css=', 'c:', 'c='))
|
||||
|
||||
|
||||
def get_loc(loc, translate_css=False):
|
||||
def get_loc(loc, translate_css=False, css_mode=False):
|
||||
"""接收本库定位语法或selenium定位元组,转换为标准定位元组,可翻译css selector为xpath
|
||||
:param loc: 本库定位语法或selenium定位元组
|
||||
:param translate_css: 是否翻译css selector为xpath
|
||||
:param css_mode: 是否尽量用css selector方式
|
||||
:return: DrissionPage定位元组
|
||||
"""
|
||||
if isinstance(loc, tuple):
|
||||
loc = translate_loc(loc)
|
||||
loc = translate_css_loc(loc) if css_mode else translate_loc(loc)
|
||||
|
||||
elif isinstance(loc, str):
|
||||
loc = str_to_loc(loc)
|
||||
loc = str_to_css_loc(loc) if css_mode else str_to_loc(loc)
|
||||
|
||||
else:
|
||||
raise TypeError('loc参数只能是tuple或str。')
|
||||
@ -127,6 +128,100 @@ def str_to_loc(loc):
|
||||
return loc_by, loc_str
|
||||
|
||||
|
||||
def str_to_css_loc(loc):
|
||||
"""处理元素查找语句
|
||||
:param loc: 查找语法字符串
|
||||
:return: 匹配符元组
|
||||
"""
|
||||
return str_to_loc(loc)
|
||||
loc_by = 'css selector'
|
||||
|
||||
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 != '@@':
|
||||
loc_str = _make_multi_xpath_str('*', loc)
|
||||
|
||||
elif loc.startswith('@|') and loc != '@|':
|
||||
loc_str = _make_multi_xpath_str('*', loc, False)
|
||||
|
||||
# 单属性查找
|
||||
elif loc.startswith('@') and loc != '@':
|
||||
loc_str = _make_single_xpath_str('*', loc)
|
||||
|
||||
# 根据tag name查找
|
||||
elif loc.startswith(('tag:', 'tag=')) and loc not in ('tag:', 'tag='):
|
||||
at_ind = loc.find('@')
|
||||
if at_ind == -1:
|
||||
loc_str = loc[4:]
|
||||
else:
|
||||
if loc[at_ind:].startswith('@@'):
|
||||
loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:])
|
||||
elif loc[at_ind:].startswith('@|'):
|
||||
loc_str = _make_multi_xpath_str(loc[4:at_ind], loc[at_ind:], False)
|
||||
else:
|
||||
loc_str = _make_single_xpath_str(loc[4:at_ind], loc[at_ind:])
|
||||
|
||||
# 根据文本查找
|
||||
elif loc.startswith('text='):
|
||||
loc_by = 'xpath'
|
||||
loc_str = f'//*[text()={_make_search_str(loc[5:])}]'
|
||||
|
||||
elif loc.startswith('text:') and loc != 'text:':
|
||||
loc_by = 'xpath'
|
||||
loc_str = f'//*/text()[contains(., {_make_search_str(loc[5:])})]/..'
|
||||
|
||||
elif loc.startswith('text^') and loc != 'text^':
|
||||
loc_by = 'xpath'
|
||||
loc_str = f'//*/text()[starts-with(., {_make_search_str(loc[5:])})]/..'
|
||||
|
||||
elif loc.startswith('text$') and loc != 'text$':
|
||||
loc_by = 'xpath'
|
||||
loc_str = f'//*/text()[substring(., string-length(.) - string-length({_make_search_str(loc[5:])}) +1) = ' \
|
||||
f'{_make_search_str(loc[5:])}]/..'
|
||||
|
||||
# 用xpath查找
|
||||
elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):
|
||||
loc_by = 'xpath'
|
||||
loc_str = loc[6:]
|
||||
elif loc.startswith(('x:', 'x=')) and loc not in ('x:', 'x='):
|
||||
loc_by = 'xpath'
|
||||
loc_str = loc[2:]
|
||||
|
||||
# 用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:
|
||||
loc_by = 'xpath'
|
||||
loc_str = f'//*/text()[contains(., {_make_search_str(loc)})]/..'
|
||||
|
||||
else:
|
||||
loc_str = '*'
|
||||
|
||||
return loc_by, loc_str
|
||||
|
||||
|
||||
def _make_single_xpath_str(tag: str, text: str) -> str:
|
||||
"""生成xpath语句
|
||||
:param tag: 标签名
|
||||
@ -298,3 +393,56 @@ def translate_loc(loc):
|
||||
raise ValueError('无法识别的定位符。')
|
||||
|
||||
return loc_by, loc_str
|
||||
|
||||
|
||||
def translate_css_loc(loc):
|
||||
"""把By类型的loc元组转换为css selector或xpath类型的
|
||||
:param loc: By类型的loc元组
|
||||
:return: css selector或xpath类型的loc元组
|
||||
"""
|
||||
if len(loc) != 2:
|
||||
raise ValueError('定位符长度必须为2。')
|
||||
|
||||
loc_by = By.CSS_SELECTOR
|
||||
loc_0 = loc[0].lower()
|
||||
if loc_0 == By.XPATH:
|
||||
loc_by = By.XPATH
|
||||
loc_str = loc[1]
|
||||
|
||||
elif loc_0 == By.CSS_SELECTOR:
|
||||
loc_by = loc_0
|
||||
loc_str = loc[1]
|
||||
|
||||
elif loc_0 == By.ID:
|
||||
loc_str = f'#{css_trans(loc[1])}'
|
||||
|
||||
elif loc_0 == By.CLASS_NAME:
|
||||
loc_str = f'.{css_trans(loc[1])}'
|
||||
|
||||
elif loc_0 == By.PARTIAL_LINK_TEXT:
|
||||
loc_by = By.XPATH
|
||||
loc_str = f'//a[text()="{css_trans(loc[1])}"]'
|
||||
|
||||
elif loc_0 == By.NAME:
|
||||
loc_str = f'*[@name={css_trans(loc[1])}]'
|
||||
|
||||
elif loc_0 == By.TAG_NAME:
|
||||
loc_str = loc[1]
|
||||
|
||||
elif loc_0 == By.PARTIAL_LINK_TEXT:
|
||||
loc_by = By.XPATH
|
||||
loc_str = f'//a[contains(text(),"{loc[1]}")]'
|
||||
|
||||
else:
|
||||
raise ValueError('无法识别的定位符。')
|
||||
|
||||
if loc_by == By.CSS_SELECTOR:
|
||||
pass
|
||||
|
||||
return loc_by, loc_str
|
||||
|
||||
|
||||
def css_trans(txt):
|
||||
c = ('!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@',
|
||||
'[', '\\', ']', '^', '`', ',', '{', '|', '}', '~', ' ')
|
||||
return ''.join([fr'\{i}' if i in c else i for i in txt])
|
||||
|
@ -6,13 +6,19 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def is_loc(text:str) -> bool: ...
|
||||
def is_loc(text: str) -> bool: ...
|
||||
|
||||
|
||||
def get_loc(loc: Union[tuple, str], translate_css: bool = False) -> tuple: ...
|
||||
def get_loc(loc: Union[tuple, str], translate_css: bool = False, css_mode: bool = False) -> tuple: ...
|
||||
|
||||
|
||||
def str_to_loc(loc: str) -> tuple: ...
|
||||
|
||||
|
||||
def translate_loc(loc: tuple) -> tuple: ...
|
||||
|
||||
|
||||
def translate_css_loc(loc: tuple) -> tuple: ...
|
||||
|
||||
|
||||
def css_trans(txt: str) -> str: ...
|
||||
|
@ -3,8 +3,8 @@
|
||||
@Author : g1879
|
||||
@Contact : g1879@qq.com
|
||||
"""
|
||||
from platform import system
|
||||
from pathlib import Path
|
||||
from platform import system
|
||||
from re import search, sub
|
||||
from shutil import rmtree
|
||||
from time import perf_counter, sleep
|
||||
@ -12,6 +12,7 @@ from time import perf_counter, sleep
|
||||
from psutil import process_iter, AccessDenied, NoSuchProcess, ZombieProcess
|
||||
|
||||
from .._configs.options_manage import OptionsManager
|
||||
from ..errors import ContextLostError, ElementLostError, CDPError, PageClosedError, NoRectError, AlertExistsError
|
||||
|
||||
|
||||
def get_usable_path(path, is_file=True, parents=True):
|
||||
@ -250,3 +251,29 @@ def configs_to_here(save_name=None):
|
||||
om = OptionsManager('default')
|
||||
save_name = f'{save_name}.ini' if save_name is not None else 'dp_configs.ini'
|
||||
om.save(save_name)
|
||||
|
||||
|
||||
def raise_error(r):
|
||||
"""抛出error对应报错
|
||||
:param r: 包含error的dict
|
||||
:return: None
|
||||
"""
|
||||
error = r['error']
|
||||
if error in ('Cannot find context with specified id', 'Inspected target navigated or closed'):
|
||||
raise ContextLostError
|
||||
elif error in ('Could not find node with given id', 'Could not find object with given id',
|
||||
'No node with given id found', 'Node with given id does not belong to the document',
|
||||
'No node found for given backend id'):
|
||||
raise ElementLostError
|
||||
elif error == 'tab closed':
|
||||
raise PageClosedError
|
||||
elif error == 'timeout':
|
||||
raise TimeoutError
|
||||
elif error == 'alert exists.':
|
||||
raise AlertExistsError
|
||||
elif error in ('Node does not have a layout object', 'Could not compute box model.'):
|
||||
raise NoRectError
|
||||
elif r['type'] == 'call_method_error':
|
||||
raise CDPError(f'\n错误:{r["error"]}\nmethod:{r["method"]}\nargs:{r["args"]}')
|
||||
else:
|
||||
raise RuntimeError(r)
|
||||
|
@ -42,3 +42,6 @@ def stop_process_on_port(port: Union[int, str]) -> None: ...
|
||||
|
||||
|
||||
def configs_to_here(file_name: Union[Path, str] = None) -> None: ...
|
||||
|
||||
|
||||
def raise_error(r: dict) -> None: ...
|
||||
|
@ -10,9 +10,9 @@ from time import perf_counter, sleep
|
||||
from .none_element import NoneElement
|
||||
from .session_element import make_session_ele
|
||||
from .._base.base import DrissionElement, BaseElement
|
||||
from .._commons.settings import Settings
|
||||
from .._commons.keys import keys_to_typing, keyDescriptionForString, keyDefinitions
|
||||
from .._commons.locator import get_loc
|
||||
from .._commons.settings import Settings
|
||||
from .._commons.tools import get_usable_path
|
||||
from .._commons.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll
|
||||
from .._units.clicker import Clicker
|
||||
@ -711,7 +711,7 @@ class ChromiumElement(DrissionElement):
|
||||
elif mode == 'css':
|
||||
txt1 = ''
|
||||
txt3 = ''
|
||||
txt4 = '''path = '>' + ":nth-child(" + nth + ")" + path;'''
|
||||
txt4 = '''path = '>' + el.tagName + ":nth-child(" + nth + ")" + path;'''
|
||||
txt5 = '''return path.substr(1);'''
|
||||
|
||||
else:
|
||||
@ -736,7 +736,7 @@ class ChromiumElement(DrissionElement):
|
||||
return e(this);}
|
||||
'''
|
||||
t = self.run_js(js)
|
||||
return f':root{t}' if mode == 'css' else t
|
||||
return f'{t}' if mode == 'css' else t
|
||||
|
||||
def _set_file_input(self, files):
|
||||
"""对上传控件写入路径
|
||||
@ -1022,31 +1022,42 @@ class ChromiumShadowRoot(BaseElement):
|
||||
:param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置
|
||||
:return: ChromiumElement对象或其组成的列表
|
||||
"""
|
||||
loc = get_loc(loc_or_str)
|
||||
loc = get_loc(loc_or_str, css_mode=False)
|
||||
if loc[0] == 'css selector' and str(loc[1]).startswith(':root'):
|
||||
loc = loc[0], loc[1][5:]
|
||||
|
||||
result = None
|
||||
timeout = timeout if timeout is not None else self.page.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
eles = make_session_ele(self.html).eles(loc)
|
||||
while not eles and perf_counter() <= end_time:
|
||||
eles = make_session_ele(self.html).eles(loc)
|
||||
while not result and perf_counter() <= end_time:
|
||||
if loc[0] == 'css selector':
|
||||
if single:
|
||||
nod_id = self.page.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId']
|
||||
result = make_chromium_ele(self.page, node_id=nod_id) if nod_id else NoneElement()
|
||||
|
||||
if not eles:
|
||||
return NoneElement() if single else eles
|
||||
else:
|
||||
nod_ids = self.page.run_cdp('DOM.querySelectorAll', nodeId=self._node_id, selector=loc[1])['nodeId']
|
||||
result = [make_chromium_ele(self.page, node_id=n) for n in nod_ids]
|
||||
|
||||
css_paths = [i.css_path[47:] for i in eles]
|
||||
if single:
|
||||
node_id = self.page.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=css_paths[0])['nodeId']
|
||||
return make_chromium_ele(self.page, node_id=node_id) if node_id else NoneElement()
|
||||
else:
|
||||
eles = make_session_ele(self.html).eles(loc)
|
||||
if not eles:
|
||||
result = NoneElement() if single else eles
|
||||
continue
|
||||
|
||||
else:
|
||||
results = []
|
||||
for i in css_paths:
|
||||
node_id = self.page.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
|
||||
if node_id:
|
||||
results.append(make_chromium_ele(self.page, node_id=node_id))
|
||||
return results
|
||||
css = [i.css_path[61:] for i in eles]
|
||||
if single:
|
||||
node_id = self.page.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=css[0])['nodeId']
|
||||
result = make_chromium_ele(self.page, node_id=node_id) if node_id else NoneElement()
|
||||
|
||||
else:
|
||||
result = []
|
||||
for i in css:
|
||||
node_id = self.page.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
|
||||
if node_id:
|
||||
result.append(make_chromium_ele(self.page, node_id=node_id))
|
||||
|
||||
return result
|
||||
|
||||
def _get_node_id(self, obj_id):
|
||||
"""返回元素node id"""
|
||||
|
@ -267,14 +267,14 @@ class SessionElement(DrissionElement):
|
||||
while ele:
|
||||
if mode == 'css':
|
||||
brothers = len(ele.eles(f'xpath:./preceding-sibling::*'))
|
||||
path_str = f'>:nth-child({brothers + 1}){path_str}'
|
||||
path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}'
|
||||
else:
|
||||
brothers = len(ele.eles(f'xpath:./preceding-sibling::{ele.tag}'))
|
||||
path_str = f'/{ele.tag}[{brothers + 1}]{path_str}' if brothers > 0 else f'/{ele.tag}{path_str}'
|
||||
|
||||
ele = ele.parent()
|
||||
|
||||
return f':root{path_str[1:]}' if mode == 'css' else path_str
|
||||
return f'{path_str[1:]}' if mode == 'css' else path_str
|
||||
|
||||
|
||||
def make_session_ele(html_or_ele, loc=None, single=True):
|
||||
|
@ -10,9 +10,9 @@ from threading import Thread
|
||||
from time import perf_counter, sleep
|
||||
|
||||
from .._base.base import BasePage
|
||||
from .._commons.settings import Settings
|
||||
from .._commons.locator import get_loc, is_loc
|
||||
from .._commons.tools import get_usable_path
|
||||
from .._commons.settings import Settings
|
||||
from .._commons.tools import get_usable_path, raise_error
|
||||
from .._commons.web import location_in_viewport
|
||||
from .._elements.chromium_element import ChromiumElement, run_js, make_chromium_ele
|
||||
from .._elements.none_element import NoneElement
|
||||
@ -25,8 +25,8 @@ from .._units.scroller import PageScroller
|
||||
from .._units.setter import ChromiumBaseSetter
|
||||
from .._units.states import PageStates
|
||||
from .._units.waiter import BaseWaiter
|
||||
from ..errors import (ContextLostError, ElementLostError, CDPError, PageClosedError, NoRectError, AlertExistsError,
|
||||
GetDocumentError, ElementNotFoundError)
|
||||
from ..errors import (ContextLostError, ElementLostError, CDPError, PageClosedError, GetDocumentError,
|
||||
ElementNotFoundError)
|
||||
|
||||
__ERROR__ = 'error'
|
||||
|
||||
@ -441,28 +441,7 @@ class ChromiumBase(BasePage):
|
||||
:return: 执行的结果
|
||||
"""
|
||||
r = self.driver.run(cmd, **cmd_args)
|
||||
if __ERROR__ not in r:
|
||||
return r
|
||||
|
||||
error = r[__ERROR__]
|
||||
if error in ('Cannot find context with specified id', 'Inspected target navigated or closed'):
|
||||
raise ContextLostError
|
||||
elif error in ('Could not find node with given id', 'Could not find object with given id',
|
||||
'No node with given id found', 'Node with given id does not belong to the document',
|
||||
'No node found for given backend id'):
|
||||
raise ElementLostError
|
||||
elif error == 'tab closed':
|
||||
raise PageClosedError
|
||||
elif error == 'timeout':
|
||||
raise TimeoutError
|
||||
elif error == 'alert exists.':
|
||||
raise AlertExistsError
|
||||
elif error in ('Node does not have a layout object', 'Could not compute box model.'):
|
||||
raise NoRectError
|
||||
elif r['type'] == 'call_method_error':
|
||||
raise CDPError(f'\n错误:{r["error"]}\nmethod:{r["method"]}\nargs:{r["args"]}')
|
||||
else:
|
||||
raise RuntimeError(r)
|
||||
return r if __ERROR__ not in r else raise_error(r)
|
||||
|
||||
def run_cdp_loaded(self, cmd, **cmd_args):
|
||||
"""执行Chrome DevTools Protocol语句,执行前等待页面加载完毕
|
||||
|
Loading…
x
Reference in New Issue
Block a user