优化waiter,待测试;修复某些情况下获取不到sr里的元素问题;优化css_path;Driver的_stopped改为is_running

This commit is contained in:
g1879 2024-07-21 23:51:15 +08:00
parent 2dd44d1982
commit 982dee6246
8 changed files with 68 additions and 39 deletions

View File

@ -14,4 +14,4 @@ from ._pages.chromium_page import ChromiumPage
from ._pages.mix_page import MixPage from ._pages.mix_page import MixPage
from ._pages.mix_page import MixPage as WebPage from ._pages.mix_page import MixPage as WebPage
__version__ = '4.1.0.0b10' __version__ = '4.1.0.0b11'

View File

@ -7,7 +7,7 @@
""" """
from json import dumps, loads, JSONDecodeError from json import dumps, loads, JSONDecodeError
from queue import Queue, Empty from queue import Queue, Empty
from threading import Thread, Event from threading import Thread
from time import perf_counter, sleep from time import perf_counter, sleep
from requests import Session from requests import Session
@ -44,7 +44,7 @@ class Driver(object):
self._handle_event_th.daemon = True self._handle_event_th.daemon = True
self._handle_immediate_event_th = None self._handle_immediate_event_th = None
self._stopped = Event() self.is_running = False
self.event_handlers = {} self.event_handlers = {}
self.immediate_event_handlers = {} self.immediate_event_handlers = {}
@ -87,7 +87,7 @@ class Driver(object):
self.method_results.pop(ws_id, None) self.method_results.pop(ws_id, None)
return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'} return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'}
while not self._stopped.is_set(): while self.is_running:
try: try:
result = self.method_results[ws_id].get(timeout=.2) result = self.method_results[ws_id].get(timeout=.2)
self.method_results.pop(ws_id, None) self.method_results.pop(ws_id, None)
@ -108,7 +108,7 @@ class Driver(object):
def _recv_loop(self): def _recv_loop(self):
"""接收浏览器信息的守护线程方法""" """接收浏览器信息的守护线程方法"""
while not self._stopped.is_set(): while self.is_running:
try: try:
# self._ws.settimeout(1) # self._ws.settimeout(1)
msg_json = self._ws.recv() msg_json = self._ws.recv()
@ -146,7 +146,7 @@ class Driver(object):
def _handle_event_loop(self): def _handle_event_loop(self):
"""当接收到浏览器信息,执行已绑定的方法""" """当接收到浏览器信息,执行已绑定的方法"""
while not self._stopped.is_set(): while self.is_running:
try: try:
event = self.event_queue.get(timeout=1) event = self.event_queue.get(timeout=1)
except Empty: except Empty:
@ -184,7 +184,7 @@ class Driver(object):
:param kwargs: cdp参数 :param kwargs: cdp参数
:return: 执行结果 :return: 执行结果
""" """
if self._stopped.is_set(): if not self.is_running:
return {'error': 'connection disconnected', 'type': 'connection_error'} return {'error': 'connection disconnected', 'type': 'connection_error'}
timeout = kwargs.pop('_timeout', Settings.cdp_timeout) timeout = kwargs.pop('_timeout', Settings.cdp_timeout)
@ -198,7 +198,7 @@ class Driver(object):
def start(self): def start(self):
"""启动连接""" """启动连接"""
self._stopped.clear() self.is_running = True
try: try:
self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True) self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True)
except WebSocketBadStatusException as e: except WebSocketBadStatusException as e:
@ -221,10 +221,10 @@ class Driver(object):
def _stop(self): def _stop(self):
"""中断连接""" """中断连接"""
if self._stopped.is_set(): if not self.is_running:
return False return False
self._stopped.set() self.is_running = False
if self._ws: if self._ws:
self._ws.close() self._ws.close()
self._ws = None self._ws = None

View File

@ -6,7 +6,7 @@
@License : BSD 3-Clause. @License : BSD 3-Clause.
""" """
from queue import Queue from queue import Queue
from threading import Thread, Event from threading import Thread
from typing import Union, Callable, Dict, Optional from typing import Union, Callable, Dict, Optional
from requests import Response, Session from requests import Response, Session
@ -35,7 +35,8 @@ class Driver(object):
_recv_th: Thread _recv_th: Thread
_handle_event_th: Thread _handle_event_th: Thread
_handle_immediate_event_th: Optional[Thread] _handle_immediate_event_th: Optional[Thread]
_stopped: Event # _stopped: Event
is_running: bool
event_handlers: dict event_handlers: dict
immediate_event_handlers: dict immediate_event_handlers: dict
method_results: dict method_results: dict

View File

@ -903,7 +903,11 @@ class ChromiumElement(DrissionElement):
txt5 = '''return path;''' txt5 = '''return path;'''
elif mode == 'css': elif mode == 'css':
txt1 = '' txt1 = '''
let i = el.getAttribute("id");
if (i){path = '>' + el.tagName.toLowerCase() + "#" + i + path;
break;}
'''
txt3 = '' txt3 = ''
txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;''' txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;'''
txt5 = '''return path.substr(1);''' txt5 = '''return path.substr(1);'''
@ -913,6 +917,7 @@ class ChromiumElement(DrissionElement):
js = '''function(){ js = '''function(){
function e(el) { function e(el) {
//return el;
if (!(el instanceof Element)) return; if (!(el instanceof Element)) return;
let path = ''; let path = '';
while (el.nodeType === Node.ELEMENT_NODE) { while (el.nodeType === Node.ELEMENT_NODE) {
@ -1243,7 +1248,12 @@ class ShadowRoot(BaseElement):
if not eles: if not eles:
return None return None
css = [i.css_path[61:] for i in eles] css = []
for i in eles:
c = i.css_path
if c.startswith('html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'):
c = c[61:]
css.append(c)
if index is not None: if index is not None:
try: try:
node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id,

View File

@ -276,6 +276,10 @@ class SessionElement(DrissionElement):
while ele: while ele:
if mode == 'css': if mode == 'css':
id_ = ele.attr('id')
if id_:
path_str = f'>{ele.tag}#{id_}{path_str}'
break
brothers = len(ele.eles(f'xpath:./preceding-sibling::*')) brothers = len(ele.eles(f'xpath:./preceding-sibling::*'))
path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}' path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}'
else: else:

View File

@ -374,7 +374,7 @@ class ChromiumBase(BasePage):
@property @property
def _target_id(self): def _target_id(self):
"""返回当前标签页id""" """返回当前标签页id"""
return self.driver.id if not self.driver._stopped.is_set() else '' return self.driver.id if self.driver.is_running else ''
@property @property
def active_ele(self): def active_ele(self):

View File

@ -127,13 +127,13 @@ class Listener(object):
if not self.listening: if not self.listening:
raise RuntimeError('监听未启动或已暂停。') raise RuntimeError('监听未启动或已暂停。')
if not timeout: if not timeout:
while self._caught.qsize() < count: while self._driver.is_running and self._caught.qsize() < count:
sleep(.03) sleep(.03)
fail = False fail = False
else: else:
end = perf_counter() + timeout end = perf_counter() + timeout
while True: while self._driver.is_running:
if perf_counter() > end: if perf_counter() > end:
fail = True fail = True
break break
@ -167,8 +167,8 @@ class Listener(object):
raise RuntimeError('监听未启动或已暂停。') raise RuntimeError('监听未启动或已暂停。')
caught = 0 caught = 0
end = perf_counter() + timeout if timeout else None end = perf_counter() + timeout if timeout else None
while True: while self._driver.is_running:
if (timeout and perf_counter() > end) or self._driver._stopped.is_set(): if (timeout and perf_counter() > end) or not self._driver.is_running:
return return
if self._caught.qsize() >= gap: if self._caught.qsize() >= gap:
yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)] yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)]

View File

@ -285,18 +285,26 @@ class BaseWaiter(OriginWaiter):
:param raise_err: 等待失败时是否报错为None时根据Settings设置 :param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功 :return: 是否等待成功
""" """
def do():
if arg == 'url':
v = self._owner.url
elif arg == 'title':
v = self._owner.title
else:
raise ValueError
if (not exclude and text in v) or (exclude and text not in v):
return True
if do():
return True
if timeout is None: if timeout is None:
timeout = self._owner.timeout timeout = self._owner.timeout
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
while perf_counter() < end_time: while perf_counter() < end_time:
if arg == 'url': if do():
val = self._owner.url
elif arg == 'title':
val = self._owner.title
else:
raise ValueError
if (not exclude and text in val) or (exclude and text not in val):
return True return True
sleep(.05) sleep(.05)
@ -313,19 +321,18 @@ class BaseWaiter(OriginWaiter):
:param raise_err: 等待失败时是否报错为None时根据Settings设置 :param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 是否等待成功 :return: 是否等待成功
""" """
if timeout != 0: timeout = timeout if timeout is not None else self._owner.timeout
if timeout is None or timeout is True: timeout = .1 if timeout <= 0 else timeout
timeout = self._owner.timeout end_time = perf_counter() + timeout
end_time = perf_counter() + timeout while perf_counter() < end_time:
while perf_counter() < end_time: if self._owner._is_loading == start:
if self._owner._is_loading == start: return True
return True sleep(gap)
sleep(gap)
if raise_err is True or Settings.raise_when_wait_failed is True: if raise_err is True or Settings.raise_when_wait_failed is True:
raise WaitTimeoutError(f'等待页面加载失败(等待{timeout}秒)。') raise WaitTimeoutError(f'等待页面加载失败(等待{timeout}秒)。')
else: else:
return False return False
class TabWaiter(BaseWaiter): class TabWaiter(BaseWaiter):
@ -461,6 +468,9 @@ class ElementWaiter(OriginWaiter):
:param raise_err: 等待失败时是否报错为None时根据Settings设置 :param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 成功返回元素对象失败返回False :return: 成功返回元素对象失败返回False
""" """
if not self._ele.states.is_enabled or not self._ele.states.is_alive:
return self._ele
if timeout is None: if timeout is None:
timeout = self._timeout timeout = self._timeout
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
@ -483,6 +493,9 @@ class ElementWaiter(OriginWaiter):
""" """
if timeout is None: if timeout is None:
timeout = self._timeout timeout = self._timeout
if timeout <= 0:
timeout = .1
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
while perf_counter() < end_time: while perf_counter() < end_time:
try: try:
@ -514,9 +527,10 @@ class ElementWaiter(OriginWaiter):
:param raise_err: 等待失败时是否报错为None时根据Settings设置 :param raise_err: 等待失败时是否报错为None时根据Settings设置
:return: 成功返回元素对象失败返回False :return: 成功返回元素对象失败返回False
""" """
timeout = timeout if timeout is not None else self._timeout
t1 = perf_counter() t1 = perf_counter()
r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。') r = self._wait_state('is_clickable', True, timeout, raise_err, err_text='等待元素可点击失败(等{}秒)。')
r = self.stop_moving(timeout=perf_counter() - t1) if wait_moved and r else r r = self.stop_moving(timeout=timeout - perf_counter() + t1) if wait_moved and r else r
if raise_err and not r: if raise_err and not r:
raise WaitTimeoutError(f'等待元素可点击失败(等{timeout}秒)。') raise WaitTimeoutError(f'等待元素可点击失败(等{timeout}秒)。')
return r return r