修复异域iframe跳转到同域时问题;其它修改,未完成

This commit is contained in:
g1879 2024-07-01 00:35:22 +08:00
parent 139bee5a91
commit 6d552330cd
39 changed files with 708 additions and 513 deletions

View File

@ -12,10 +12,10 @@ from urllib.parse import quote
from DownloadKit import DownloadKit from DownloadKit import DownloadKit
from .._functions.settings import Settings
from .._functions.locator import get_loc
from .._functions.web import format_html
from .._elements.none_element import NoneElement from .._elements.none_element import NoneElement
from .._functions.locator import get_loc
from .._functions.settings import Settings
from .._functions.web import format_html
from ..errors import ElementNotFoundError from ..errors import ElementNotFoundError
@ -414,10 +414,6 @@ class BasePage(BaseParser):
def user_agent(self): def user_agent(self):
return return
@abstractmethod
def cookies(self, as_dict=False, all_info=False):
return {}
@abstractmethod @abstractmethod
def get(self, url, show_errmsg=False, retry=None, interval=None): def get(self, url, show_errmsg=False, retry=None, interval=None):
pass pass

View File

@ -234,9 +234,6 @@ class BasePage(BaseParser):
@property @property
def user_agent(self) -> str: ... def user_agent(self) -> str: ...
@abstractmethod
def cookies(self, as_dict: bool = False, all_info: bool = False) -> Union[list, dict]: ...
@abstractmethod @abstractmethod
def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ... def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ...

View File

@ -17,6 +17,7 @@ from .driver import BrowserDriver, Driver
from .._configs.chromium_options import ChromiumOptions from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions from .._configs.session_options import SessionOptions
from .._functions.browser import connect_browser from .._functions.browser import connect_browser
from .._functions.cookies import CookiesList
from .._functions.settings import Settings from .._functions.settings import Settings
from .._functions.tools import PortFinder from .._functions.tools import PortFinder
from .._functions.tools import raise_error from .._functions.tools import raise_error
@ -161,6 +162,15 @@ class Browser(object):
当Settings.singleton_tab_obj==True时返回Tab对象否则返回tab id""" 当Settings.singleton_tab_obj==True时返回Tab对象否则返回tab id"""
return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj) return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj)
def cookies(self, all_info=False):
"""以list格式返回所有域名的cookies
:param all_info: 是否返回所有内容False则只返回name, value, domain
:return: cookies组成的列表
"""
cks = self._run_cdp(f'Storage.getCookies')['cookies']
r = cks if all_info else [{'name': c['name'], 'value': c['value'], 'domain': c['domain']} for c in cks]
return CookiesList(r)
def new_tab(self, url=None, new_window=False, background=False, new_context=False): def new_tab(self, url=None, new_window=False, background=False, new_context=False):
"""新建一个标签页 """新建一个标签页
:param url: 新标签页跳转到的网址 :param url: 新标签页跳转到的网址

View File

@ -11,6 +11,7 @@ from typing import List, Optional, Set, Dict, Union, Tuple
from .driver import BrowserDriver, Driver from .driver import BrowserDriver, Driver
from .._configs.chromium_options import ChromiumOptions from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions from .._configs.session_options import SessionOptions
from .._functions.web import CookiesList
from .._pages.chromium_base import Timeout from .._pages.chromium_base import Timeout
from .._pages.chromium_tab import ChromiumTab, MixTab from .._pages.chromium_tab import ChromiumTab, MixTab
from .._units.downloader import DownloadManager from .._units.downloader import DownloadManager
@ -82,6 +83,8 @@ class Browser(object):
@property @property
def latest_tab(self) -> Union[ChromiumTab, str]: ... def latest_tab(self) -> Union[ChromiumTab, str]: ...
def cookies(self, all_info: bool = False) -> CookiesList: ...
def close_tabs(self, def close_tabs(self,
tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],
Tuple[Union[str, ChromiumTab]]] = None, Tuple[Union[str, ChromiumTab]]] = None,

View File

@ -30,6 +30,7 @@ class Driver(object):
self.address = address self.address = address
self.type = tab_type self.type = tab_type
self.owner = owner self.owner = owner
# self._debug = True
# self._debug = False # self._debug = False
self.alert_flag = False # 标记alert出现跳过一条请求后复原 self.alert_flag = False # 标记alert出现跳过一条请求后复原
@ -158,7 +159,7 @@ class Driver(object):
self.event_queue.task_done() self.event_queue.task_done()
def _handle_immediate_event_loop(self): def _handle_immediate_event_loop(self):
while not self._stopped.is_set() and not self.immediate_event_queue.empty(): while not self.immediate_event_queue.empty():
function, kwargs = self.immediate_event_queue.get(timeout=1) function, kwargs = self.immediate_event_queue.get(timeout=1)
try: try:
function(**kwargs) function(**kwargs)
@ -227,6 +228,15 @@ class Driver(object):
self._ws = None self._ws = None
# try: # try:
# while not self.immediate_event_queue.empty():
# function, kwargs = self.immediate_event_queue.get_nowait()
# try:
# function(**kwargs)
# except PageDisconnectedError:
# raise
# pass
# sleep(.1)
#
# while not self.event_queue.empty(): # while not self.event_queue.empty():
# event = self.event_queue.get_nowait() # event = self.event_queue.get_nowait()
# function = self.event_handlers.get(event['method']) # function = self.event_handlers.get(event['method'])

View File

@ -12,7 +12,8 @@ from requests import Session
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from .options_manage import OptionsManager from .options_manage import OptionsManager
from .._functions.web import cookies_to_tuple, set_session_cookies, format_headers from .._functions.cookies import cookies_to_tuple, set_session_cookies
from .._functions.web import format_headers
class SessionOptions(object): class SessionOptions(object):

View File

@ -16,9 +16,9 @@ from DataRecorder.tools import get_usable_path, make_valid_name
from .none_element import NoneElement from .none_element import NoneElement
from .session_element import make_session_ele from .session_element import make_session_ele
from .._base.base import DrissionElement, BaseElement from .._base.base import DrissionElement, BaseElement
from .._functions.elements import ChromiumElementsList
from .._functions.keys import input_text_or_keys from .._functions.keys import input_text_or_keys
from .._functions.locator import get_loc, locator_to_tuple 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 .._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.clicker import Clicker
from .._units.rect import ElementRect from .._units.rect import ElementRect
@ -95,29 +95,29 @@ class ChromiumElement(DrissionElement):
def tag(self): def tag(self):
"""返回元素tag""" """返回元素tag"""
if self._tag is None: if self._tag is None:
self._tag = self.owner.run_cdp('DOM.describeNode', self._tag = self.owner._run_cdp('DOM.describeNode',
backendNodeId=self._backend_id)['node']['localName'].lower() backendNodeId=self._backend_id)['node']['localName'].lower()
return self._tag return self._tag
@property @property
def html(self): def html(self):
"""返回元素outerHTML文本""" """返回元素outerHTML文本"""
return self.owner.run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML'] return self.owner._run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML']
@property @property
def inner_html(self): def inner_html(self):
"""返回元素innerHTML文本""" """返回元素innerHTML文本"""
return self.run_js('return this.innerHTML;') return self._run_js('return this.innerHTML;')
@property @property
def attrs(self): def attrs(self):
"""返回元素所有attribute属性""" """返回元素所有attribute属性"""
try: try:
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)} return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
except ElementLostError: except ElementLostError:
self._refresh_id() self._refresh_id()
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)} return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
except CDPError: # 文档根元素不能调用此方法 except CDPError: # 文档根元素不能调用此方法
return {} return {}
@ -164,7 +164,7 @@ class ChromiumElement(DrissionElement):
@property @property
def shadow_root(self): def shadow_root(self):
"""返回当前元素的shadow_root元素对象""" """返回当前元素的shadow_root元素对象"""
info = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] info = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
if not info.get('shadowRoots', None): if not info.get('shadowRoots', None):
return None return None
@ -225,8 +225,8 @@ class ChromiumElement(DrissionElement):
elif not is_checked and not uncheck: elif not is_checked and not uncheck:
js = 'this.checked=true' js = 'this.checked=true'
if js: if js:
self.run_js(js) self._run_js(js)
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
else: else:
if (is_checked and uncheck) or (not is_checked and not uncheck): if (is_checked and uncheck) or (not is_checked and not uncheck):
@ -360,7 +360,7 @@ class ChromiumElement(DrissionElement):
x, y = self.rect.location x, y = self.rect.location
try: try:
return ChromiumElement(owner=self.owner, return ChromiumElement(owner=self.owner,
backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x, backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x + offset_x,
y=y + offset_y, includeUserAgentShadowDOM=True, y=y + offset_y, includeUserAgentShadowDOM=True,
ignorePointerEventsNone=False)['backendNodeId']) ignorePointerEventsNone=False)['backendNodeId'])
except CDPError: except CDPError:
@ -439,7 +439,7 @@ class ChromiumElement(DrissionElement):
cdp_data[variable] += locator cdp_data[variable] += locator
try: try:
return ChromiumElement(owner=self.owner, return ChromiumElement(owner=self.owner,
backend_id=self.owner.run_cdp('DOM.getNodeForLocation', backend_id=self.owner._run_cdp('DOM.getNodeForLocation',
**cdp_data)['backendNodeId']) **cdp_data)['backendNodeId'])
except CDPError: except CDPError:
return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator}) return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator})
@ -453,7 +453,7 @@ class ChromiumElement(DrissionElement):
while 0 < cdp_data[variable] < max_len: while 0 < cdp_data[variable] < max_len:
cdp_data[variable] += value cdp_data[variable] += value
try: try:
bid = self.owner.run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId'] bid = self.owner._run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId']
if bid == curr_ele: if bid == curr_ele:
continue continue
else: else:
@ -505,7 +505,7 @@ class ChromiumElement(DrissionElement):
:param name: 属性名 :param name: 属性名
:return: None :return: None
""" """
self.run_js(f'this.removeAttribute("{name}");') self._run_js(f'this.removeAttribute("{name}");')
def property(self, name): def property(self, name):
"""获取一个property属性值 """获取一个property属性值
@ -513,12 +513,22 @@ class ChromiumElement(DrissionElement):
:return: 属性值文本 :return: 属性值文本
""" """
try: try:
value = self.run_js(f'return this.{name};') value = self._run_js(f'return this.{name};')
return format_html(value) if isinstance(value, str) else value return format_html(value) if isinstance(value, str) else value
except: except:
return None return None
def run_js(self, script, *args, as_expr=False, timeout=None): def run_js(self, script, *args, as_expr=False, timeout=None):
"""对本元素执行javascript代码
:param script: js文本文本中用this表示本元素
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
:param as_expr: 是否作为表达式运行为True时args无效
:param timeout: js超时时间为None则使用页面timeouts.script设置
:return: 运行的结果
"""
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def _run_js(self, script, *args, as_expr=False, timeout=None):
"""对本元素执行javascript代码 """对本元素执行javascript代码
:param script: js文本文本中用this表示本元素 :param script: js文本文本中用this表示本元素
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]... :param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
@ -588,7 +598,7 @@ class ChromiumElement(DrissionElement):
""" """
if pseudo_ele: if pseudo_ele:
pseudo_ele = f', "{pseudo_ele}"' if pseudo_ele.startswith(':') else f', "::{pseudo_ele}"' pseudo_ele = f', "{pseudo_ele}"' if pseudo_ele.startswith(':') else f', "::{pseudo_ele}"'
return self.run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");') return self._run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");')
def src(self, timeout=None, base64_to_bytes=True): def src(self, timeout=None, base64_to_bytes=True):
"""返回元素src资源base64的可转为bytes返回其它返回str """返回元素src资源base64的可转为bytes返回其它返回str
@ -602,7 +612,7 @@ class ChromiumElement(DrissionElement):
'&& this.naturalWidth > 0 && typeof this.naturalHeight != "undefined" ' '&& this.naturalWidth > 0 && typeof this.naturalHeight != "undefined" '
'&& this.naturalHeight > 0') '&& this.naturalHeight > 0')
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
while not self.run_js(js) and perf_counter() < end_time: while not self._run_js(js) and perf_counter() < end_time:
sleep(.1) sleep(.1)
src = self.attr('src') src = self.attr('src')
@ -631,11 +641,11 @@ class ChromiumElement(DrissionElement):
if not src: if not src:
continue continue
node = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] node = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
frame = node.get('frameId', None) or self.owner._frame_id frame = node.get('frameId', None) or self.owner._frame_id
try: try:
result = self.owner.run_cdp('Page.getResourceContent', frameId=frame, url=src) result = self.owner._run_cdp('Page.getResourceContent', frameId=frame, url=src)
break break
except CDPError: except CDPError:
pass pass
@ -698,7 +708,7 @@ class ChromiumElement(DrissionElement):
js = ('return this.complete && typeof this.naturalWidth != "undefined" && this.naturalWidth > 0 ' js = ('return this.complete && typeof this.naturalWidth != "undefined" && this.naturalWidth > 0 '
'&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0') '&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0')
end_time = perf_counter() + self.owner.timeout end_time = perf_counter() + self.owner.timeout
while not self.run_js(js) and perf_counter() < end_time: while not self._run_js(js) and perf_counter() < end_time:
sleep(.1) sleep(.1)
if scroll_to_center: if scroll_to_center:
self.scroll.to_see(center=True) self.scroll.to_see(center=True)
@ -729,7 +739,7 @@ class ChromiumElement(DrissionElement):
if isinstance(vals, (list, tuple)): if isinstance(vals, (list, tuple)):
vals = ''.join([str(i) for i in vals]) vals = ''.join([str(i) for i in vals])
self.set.property('value', str(vals)) self.set.property('value', str(vals))
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return return
self.wait.clickable(wait_moved=False, timeout=.5) self.wait.clickable(wait_moved=False, timeout=.5)
@ -746,8 +756,8 @@ class ChromiumElement(DrissionElement):
:return: None :return: None
""" """
if by_js: if by_js:
self.run_js("this.value='';") self._run_js("this.value='';")
self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return return
self._input_focus() self._input_focus()
@ -756,16 +766,16 @@ class ChromiumElement(DrissionElement):
def _input_focus(self): def _input_focus(self):
"""输入前使元素获取焦点""" """输入前使元素获取焦点"""
try: try:
self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)
except Exception: except Exception:
self.click(by_js=None) self.click(by_js=None)
def focus(self): def focus(self):
"""使元素获取焦点""" """使元素获取焦点"""
try: try:
self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id)
except Exception: except Exception:
self.run_js('this.focus();') self._run_js('this.focus();')
def hover(self, offset_x=None, offset_y=None): def hover(self, offset_x=None, offset_y=None):
"""鼠标悬停可接受偏移量偏移量相对于元素左上角坐标。不传入x或y值时悬停在元素中点 """鼠标悬停可接受偏移量偏移量相对于元素左上角坐标。不传入x或y值时悬停在元素中点
@ -775,7 +785,7 @@ class ChromiumElement(DrissionElement):
""" """
self.owner.scroll.to_see(self) self.owner.scroll.to_see(self)
x, y = offset_scroll(self, offset_x, offset_y) x, y = offset_scroll(self, offset_x, offset_y)
self.owner.run_cdp('Input.dispatchMouseEvent', type='mouseMoved', x=x, y=y, _ignore=AlertExistsError) self.owner._run_cdp('Input.dispatchMouseEvent', type='mouseMoved', x=x, y=y, _ignore=AlertExistsError)
def drag(self, offset_x=0, offset_y=0, duration=.5): def drag(self, offset_x=0, offset_y=0, duration=.5):
"""拖拽当前元素到相对位置 """拖拽当前元素到相对位置
@ -808,9 +818,9 @@ class ChromiumElement(DrissionElement):
:return: js中的object id :return: js中的object id
""" """
if node_id: if node_id:
return self.owner.run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId'] return self.owner._run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId']
else: else:
return self.owner.run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId'] return self.owner._run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId']
def _get_node_id(self, obj_id=None, backend_id=None): def _get_node_id(self, obj_id=None, backend_id=None):
"""根据传入object id或backend id获取cdp中的node id """根据传入object id或backend id获取cdp中的node id
@ -819,9 +829,9 @@ class ChromiumElement(DrissionElement):
:return: cdp中的node id :return: cdp中的node id
""" """
if obj_id: if obj_id:
return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
else: else:
n = self.owner.run_cdp('DOM.describeNode', backendNodeId=backend_id)['node'] n = self.owner._run_cdp('DOM.describeNode', backendNodeId=backend_id)['node']
self._tag = n['localName'] self._tag = n['localName']
return n['nodeId'] return n['nodeId']
@ -830,7 +840,7 @@ class ChromiumElement(DrissionElement):
:param node_id: :param node_id:
:return: backend id :return: backend id
""" """
n = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node'] n = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']
self._tag = n['localName'] self._tag = n['localName']
return n['backendNodeId'] return n['backendNodeId']
@ -876,7 +886,7 @@ class ChromiumElement(DrissionElement):
} }
return e(this);} return e(this);}
''' '''
t = self.run_js(js) t = self._run_js(js)
return f'{t}' if mode == 'css' else t return f'{t}' if mode == 'css' else t
def _set_file_input(self, files): def _set_file_input(self, files):
@ -887,7 +897,7 @@ class ChromiumElement(DrissionElement):
if isinstance(files, str): if isinstance(files, str):
files = files.split('\n') files = files.split('\n')
files = [str(Path(i).absolute()) for i in files] files = [str(Path(i).absolute()) for i in files]
self.owner.run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id) self.owner._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id)
class ShadowRoot(BaseElement): class ShadowRoot(BaseElement):
@ -942,7 +952,7 @@ class ShadowRoot(BaseElement):
@property @property
def inner_html(self): def inner_html(self):
"""返回内部的html文本""" """返回内部的html文本"""
return self.run_js('return this.innerHTML;') return self._run_js('return this.innerHTML;')
@property @property
def states(self): def states(self):
@ -952,6 +962,16 @@ class ShadowRoot(BaseElement):
return self._states return self._states
def run_js(self, script, *args, as_expr=False, timeout=None): def run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码
:param script: js文本
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
:param as_expr: 是否作为表达式运行为True时args无效
:param timeout: js超时时间为None则使用页面timeouts.script设置
:return: 运行的结果
"""
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def _run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码 """运行javascript代码
:param script: js文本 :param script: js文本
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]... :param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
@ -1163,13 +1183,13 @@ class ShadowRoot(BaseElement):
def do_find(): def do_find():
if loc[0] == 'css selector': if loc[0] == 'css selector':
if index == 1: if index == 1:
nod_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId'] nod_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId']
if nod_id: if nod_id:
r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False) r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False)
return None if r is False else r return None if r is False else r
else: else:
nod_ids = self.owner.run_cdp('DOM.querySelectorAll', nod_ids = self.owner._run_cdp('DOM.querySelectorAll',
nodeId=self._node_id, selector=loc[1])['nodeId'] nodeId=self._node_id, selector=loc[1])['nodeId']
r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False) r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False)
return None if r is False else r return None if r is False else r
@ -1182,14 +1202,14 @@ class ShadowRoot(BaseElement):
css = [i.css_path[61:] for i in eles] css = [i.css_path[61:] for i in eles]
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,
selector=css[index - 1])['nodeId'] selector=css[index - 1])['nodeId']
except IndexError: except IndexError:
return None return None
r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False) r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)
return None if r is False else r return None if r is False else r
else: else:
node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] node_ids = [self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
for i in css] for i in css]
if 0 in node_ids: if 0 in node_ids:
return None return None
@ -1209,15 +1229,15 @@ class ShadowRoot(BaseElement):
def _get_node_id(self, obj_id): def _get_node_id(self, obj_id):
"""返回元素node id""" """返回元素node id"""
return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId']
def _get_obj_id(self, back_id): def _get_obj_id(self, back_id):
"""返回元素object id""" """返回元素object id"""
return self.owner.run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId'] return self.owner._run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId']
def _get_backend_id(self, node_id): def _get_backend_id(self, node_id):
"""返回元素object id""" """返回元素object id"""
r = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node'] r = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node']
self._tag = r['localName'].lower() self._tag = r['localName'].lower()
return r['backendNodeId'] return r['backendNodeId']
@ -1269,14 +1289,14 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
ele.owner.wait.doc_loaded() ele.owner.wait.doc_loaded()
def do_find(): def do_find():
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
returnByValue=False, awaitPromise=True, userGesture=True) returnByValue=False, awaitPromise=True, userGesture=True)
if res['result']['type'] == 'string': if res['result']['type'] == 'string':
return res['result']['value'] return res['result']['value']
if 'exceptionDetails' in res: if 'exceptionDetails' in res:
if 'The result is not a node set' in res['result']['description']: if 'The result is not a node set' in res['result']['description']:
js1 = make_js_for_find_ele_by_xpath(xpath, '1', node_txt) js1 = make_js_for_find_ele_by_xpath(xpath, '1', node_txt)
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id, res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id,
returnByValue=False, awaitPromise=True, userGesture=True) returnByValue=False, awaitPromise=True, userGesture=True)
return res['result']['value'] return res['result']['value']
else: else:
@ -1290,7 +1310,7 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
return None if r is False else r return None if r is False else r
else: else:
res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'],
ownProperties=True)['result'][:-1] ownProperties=True)['result'][:-1]
if index is None: if index is None:
r = ChromiumElementsList(page=ele.owner) r = ChromiumElementsList(page=ele.owner)
@ -1341,7 +1361,7 @@ def find_by_css(ele, selector, index, timeout):
ele.owner.wait.doc_loaded() ele.owner.wait.doc_loaded()
def do_find(): def do_find():
res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id,
returnByValue=False, awaitPromise=True, userGesture=True) returnByValue=False, awaitPromise=True, userGesture=True)
if 'exceptionDetails' in res: if 'exceptionDetails' in res:
@ -1354,7 +1374,7 @@ def find_by_css(ele, selector, index, timeout):
return None if r is False else r return None if r is False else r
else: else:
obj_ids = [i['value']['objectId'] for i in ele.owner.run_cdp('Runtime.getProperties', obj_ids = [i['value']['objectId'] for i in ele.owner._run_cdp('Runtime.getProperties',
objectId=res['result']['objectId'], objectId=res['result']['objectId'],
ownProperties=True)['result']] ownProperties=True)['result']]
r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True) r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True)
@ -1535,14 +1555,14 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None):
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
try: try:
if as_expr: if as_expr:
res = page.run_cdp('Runtime.evaluate', expression=script, returnByValue=False, res = page._run_cdp('Runtime.evaluate', expression=script, returnByValue=False,
awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)
else: else:
args = args or () args = args or ()
if not is_js_func(script): if not is_js_func(script):
script = f'function(){{{script}}}' script = f'function(){{{script}}}'
res = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id, res = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id,
arguments=[convert_argument(arg) for arg in args], returnByValue=False, arguments=[convert_argument(arg) for arg in args], returnByValue=False,
awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError)
except TimeoutError: except TimeoutError:
@ -1591,7 +1611,7 @@ def parse_js_result(page, ele, result, end_time):
return r return r
elif sub_type == 'array': elif sub_type == 'array':
r = page.run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result'] r = page._run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result']
return [parse_js_result(page, ele, result=i['value'], end_time=end_time) for i in r if i['name'].isdigit()] return [parse_js_result(page, ele, result=i['value'], end_time=end_time) for i in r if i['name'].isdigit()]
elif 'objectId' in result: elif 'objectId' in result:
@ -1599,7 +1619,7 @@ def parse_js_result(page, ele, result, end_time):
if timeout < 0: if timeout < 0:
return return
js = 'function(){return JSON.stringify(this);}' js = 'function(){return JSON.stringify(this);}'
r = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'], r = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'],
returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError,
_timeout=timeout) _timeout=timeout)
return loads(parse_js_result(page, ele, r['result'], end_time)) return loads(parse_js_result(page, ele, r['result'], end_time))

View File

@ -195,6 +195,8 @@ class ChromiumElement(DrissionElement):
def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ... def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def _run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def run_async_js(self, script: str, *args, as_expr: bool = False) -> None: ... def run_async_js(self, script: str, *args, as_expr: bool = False) -> None: ...
def ele(self, def ele(self,
@ -298,6 +300,8 @@ class ShadowRoot(BaseElement):
def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ... def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def _run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def run_async_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> None: ... def run_async_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> None: ...
def parent(self, level_or_loc: Union[str, int] = 1, index: int = 1) -> ChromiumElement: ... def parent(self, level_or_loc: Union[str, int] = 1, index: int = 1) -> ChromiumElement: ...

View File

@ -349,11 +349,11 @@ def make_session_ele(html_or_ele, loc=None, index=1, method=None):
xpath = html_or_ele.xpath xpath = html_or_ele.xpath
# ChromiumElement兼容传入的元素在iframe内的情况 # ChromiumElement兼容传入的元素在iframe内的情况
if html_or_ele._doc_id is None: if html_or_ele._doc_id is None:
doc = html_or_ele.run_js('return this.ownerDocument;') doc = html_or_ele._run_js('return this.ownerDocument;')
html_or_ele._doc_id = doc['objectId'] if doc else False html_or_ele._doc_id = doc['objectId'] if doc else False
if html_or_ele._doc_id: if html_or_ele._doc_id:
html = html_or_ele.owner.run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML'] html = html_or_ele.owner._run_cdp('DOM.getOuterHTML', objectId=html_or_ele._doc_id)['outerHTML']
else: else:
html = html_or_ele.owner.html html = html_or_ele.owner.html
html_or_ele = fromstring(html) html_or_ele = fromstring(html)

View File

@ -0,0 +1,233 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from datetime import datetime
from http.cookiejar import Cookie, CookieJar
from tldextract import extract
def cookie_to_dict(cookie):
"""把Cookie对象转为dict格式
:param cookie: Cookie对象字符串或字典
:return: cookie字典
"""
if isinstance(cookie, Cookie):
cookie_dict = cookie.__dict__.copy()
cookie_dict.pop('rfc2109', None)
cookie_dict.pop('_rest', None)
return cookie_dict
elif isinstance(cookie, dict):
cookie_dict = cookie
elif isinstance(cookie, str):
cookie_dict = {}
for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):
attr_val = attr.strip().split('=', 1)
if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
else:
cookie_dict['name'] = attr_val[0]
cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''
return cookie_dict
else:
raise TypeError('cookie参数必须为Cookie、str或dict类型。')
return cookie_dict
def cookies_to_tuple(cookies):
"""把cookies转为tuple格式
:param cookies: cookies信息可为CookieJar, list, tuple, str, dict
:return: 返回tuple形式的cookies
"""
if isinstance(cookies, (list, tuple, CookieJar)):
cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)
elif isinstance(cookies, str):
c_dict = {}
cookies = cookies.rstrip('; ')
cookies = cookies.split(';')
for attr in cookies:
attr_val = attr.strip().split('=', 1)
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
cookies = _dict_cookies_to_tuple(c_dict)
elif isinstance(cookies, dict):
cookies = _dict_cookies_to_tuple(cookies)
elif isinstance(cookies, Cookie):
cookies = (cookie_to_dict(cookies),)
else:
raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。')
return cookies
def set_session_cookies(session, cookies):
"""设置Session对象的cookies
:param session: Session对象
:param cookies: cookies信息
:return: None
"""
for cookie in cookies_to_tuple(cookies):
if cookie['value'] is None:
cookie['value'] = ''
kwargs = {x: cookie[x] for x in cookie
if x.lower() in ('version', 'port', 'domain', 'path', 'secure',
'expires', 'discard', 'comment', 'comment_url', 'rest')}
if 'expiry' in cookie:
kwargs['expires'] = cookie['expiry']
session.cookies.set(cookie['name'], cookie['value'], **kwargs)
def set_browser_cookies(browser, cookies):
"""设置cookies值
:param browser: 页面对象
:param cookies: cookies信息
:return: None
"""
c = []
for cookie in cookies_to_tuple(cookies):
if 'domain' not in cookie and 'url' not in cookie:
raise ValueError(f"cookie必须带有'domain''url'字段:{cookie}")
c.append(format_cookie(cookie))
browser._run_cdp('Storage.setCookies', cookies=c)
def set_tab_cookies(page, cookies):
"""设置cookies值
:param page: 页面对象
:param cookies: cookies信息
:return: None
"""
for cookie in cookies_to_tuple(cookies):
cookie = format_cookie(cookie)
if cookie['name'].startswith('__Host-'):
if not page.url.startswith('http'):
cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)
else:
cookie['url'] = page.url
page._run_cdp_loaded('Network.setCookie', **cookie)
continue # 不用设置域名,可退出
if cookie.get('domain', None):
try:
page._run_cdp_loaded('Network.setCookie', **cookie)
if is_cookie_in_driver(page, cookie):
continue
except Exception:
pass
url = page._browser_url
if not url.startswith('http'):
raise RuntimeError(f'未设置域名请设置cookie的domain参数或先访问一个网站。{cookie}')
ex_url = extract(url)
d_list = ex_url.subdomain.split('.')
d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)
tmp = [d_list[0]]
if len(d_list) > 1:
for i in d_list[1:]:
tmp.append('.')
tmp.append(i)
for i in range(len(tmp)):
cookie['domain'] = ''.join(tmp[i:])
page._run_cdp_loaded('Network.setCookie', **cookie)
if is_cookie_in_driver(page, cookie):
break
def is_cookie_in_driver(page, cookie):
"""查询cookie是否在浏览器内
:param page: BasePage对象
:param cookie: dict格式cookie
:return: bool
"""
if 'domain' in cookie:
for c in page.cookies(all_domains=True):
if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain',
None):
return True
else:
for c in page.cookies(all_domains=True):
if cookie['name'] == c['name'] and cookie['value'] == c['value']:
return True
return False
def format_cookie(cookie):
"""设置cookie为可用格式
:param cookie: dict格式cookie
:return: 格式化后的cookie字典
"""
if 'expiry' in cookie:
cookie['expires'] = int(cookie['expiry'])
cookie.pop('expiry')
if 'expires' in cookie:
if not cookie['expires']:
cookie.pop('expires')
elif isinstance(cookie['expires'], str):
if cookie['expires'].isdigit():
cookie['expires'] = int(cookie['expires'])
elif cookie['expires'].replace('.', '').isdigit():
cookie['expires'] = float(cookie['expires'])
else:
try:
cookie['expires'] = datetime.strptime(cookie['expires'], '%a, %d %b %Y %H:%M:%S GMT').timestamp()
except ValueError:
cookie['expires'] = datetime.strptime(cookie['expires'], '%a, %d %b %y %H:%M:%S GMT').timestamp()
if cookie['value'] is None:
cookie['value'] = ''
elif not isinstance(cookie['value'], str):
cookie['value'] = str(cookie['value'])
if cookie['name'].startswith('__Host-'):
cookie['path'] = '/'
cookie['secure'] = True
elif cookie['name'].startswith('__Secure-'):
cookie['secure'] = True
return cookie
class CookiesList(list):
def as_dict(self):
"""以dict格式返回只包含name和value字段"""
return {c['name']: c['value'] for c in self}
def as_str(self):
"""以str格式返回只包含name和value字段"""
return '; '.join([f'{c["name"]}={c["value"]}' for c in self])
def _dict_cookies_to_tuple(cookies: dict):
"""把dict形式的cookies转换为tuple形式
:param cookies: 单个或多个cookies单个时包含'name''value'
:return: 多个dict格式cookies组成的列表
"""
if 'name' in cookies and 'value' in cookies: # 单个cookie
return (cookies,)
keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')
template = {k: v for k, v in cookies.items() if k in keys}
return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)

View File

@ -0,0 +1,44 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
"""
from http.cookiejar import Cookie
from typing import Union
from requests import Session
from requests.cookies import RequestsCookieJar
from .._base.browser import Browser
from .._pages.chromium_base import ChromiumBase
def cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict: ...
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple: ...
def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
def set_browser_cookies(browser: Browser, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
def set_tab_cookies(page: ChromiumBase, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
def format_cookie(cookie: dict) -> dict: ...
class CookiesList(list):
def as_dict(self) -> dict: ...
def as_str(self) -> str: ...
def __next__(self) -> dict: ...

View File

@ -427,12 +427,12 @@ def send_key(page, modifier, key):
'isKeypad': description['location'] == 3, 'isKeypad': description['location'] == 3,
'_ignore': AlertExistsError} '_ignore': AlertExistsError}
page.run_cdp('Input.dispatchKeyEvent', **data) page._run_cdp('Input.dispatchKeyEvent', **data)
data['type'] = 'keyUp' data['type'] = 'keyUp'
page.run_cdp('Input.dispatchKeyEvent', **data) page._run_cdp('Input.dispatchKeyEvent', **data)
else: else:
page.run_cdp('Input.insertText', text=key, _ignore=AlertExistsError) page._run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)
def input_text_or_keys(page, text_or_keys): def input_text_or_keys(page, text_or_keys):
@ -451,7 +451,7 @@ def input_text_or_keys(page, text_or_keys):
return return
if text_or_keys.endswith(('\n', '\ue007')): if text_or_keys.endswith(('\n', '\ue007')):
page.run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError) page._run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError)
send_key(page, modifier, '\n') send_key(page, modifier, '\n')
else: else:
page.run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError) page._run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError)

View File

@ -198,10 +198,11 @@ def configs_to_here(save_name=None):
om.save(save_name) om.save(save_name)
def raise_error(result, ignore=None): def raise_error(result, ignore=None, user=False):
"""抛出error对应报错 """抛出error对应报错
:param result: 包含error的dict :param result: 包含error的dict
:param ignore: 要忽略的错误 :param ignore: 要忽略的错误
:param user: 是否用户调用的
:return: None :return: None
""" """
error = result['error'] error = result['error']
@ -227,13 +228,18 @@ def raise_error(result, ignore=None):
elif error == 'Given expression does not evaluate to a function': elif error == 'Given expression does not evaluate to a function':
r = JavaScriptError(f'传入的js无法解析成函数\n{result["args"]["functionDeclaration"]}') r = JavaScriptError(f'传入的js无法解析成函数\n{result["args"]["functionDeclaration"]}')
elif error.endswith("' wasn't found"): elif error.endswith("' wasn't found"):
r = RuntimeError(f'你的浏览器可能太旧。\n方法:{result["method"]}\n参数:{result["args"]}') r = RuntimeError(f'没有找到对应功能,方法错误或你的浏览器太旧。\n方法:{result["method"]}\n参数:{result["args"]}')
elif result['type'] in ('call_method_error', 'timeout'): elif result['type'] == 'timeout':
from DrissionPage import __version__
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \
f'版本:{__version__}\n超时,可能是浏览器卡了。'
r = TimeoutError(txt)
elif result['type'] == 'call_method_error' and not user:
from DrissionPage import __version__ from DrissionPage import __version__
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \ txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \
f'版本:{__version__}\n出现这个错误可能意味着程序有bug请把错误信息和重现方法' \ f'版本:{__version__}\n出现这个错误可能意味着程序有bug请把错误信息和重现方法' \
'告知作者,谢谢。\n报告网站https://gitee.com/g1879/DrissionPage/issues' '告知作者,谢谢。\n报告网站https://gitee.com/g1879/DrissionPage/issues'
r = TimeoutError(txt) if result['type'] == 'timeout' else CDPError(txt) r = CDPError(txt)
else: else:
r = RuntimeError(result) r = RuntimeError(result)

View File

@ -47,4 +47,4 @@ def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): ..
def configs_to_here(file_name: Union[Path, str] = None) -> None: ... def configs_to_here(file_name: Union[Path, str] = None) -> None: ...
def raise_error(result: dict, ignore=None) -> None: ... def raise_error(result: dict, ignore=None, user: bool = False) -> None: ...

View File

@ -5,16 +5,13 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause. @License : BSD 3-Clause.
""" """
from datetime import datetime
from html import unescape from html import unescape
from http.cookiejar import Cookie, CookieJar
from os.path import sep from os.path import sep
from pathlib import Path from pathlib import Path
from re import sub from re import sub
from urllib.parse import urlparse, urljoin, urlunparse from urllib.parse import urlparse, urljoin, urlunparse
from DataRecorder.tools import make_valid_name from DataRecorder.tools import make_valid_name
from tldextract import extract
def get_ele_txt(e): def get_ele_txt(e):
@ -106,7 +103,7 @@ def location_in_viewport(page, loc_x, loc_y):
const vHeight = document.documentElement.clientHeight; const vHeight = document.documentElement.clientHeight;
if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}} if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}}
return true;}}''' return true;}}'''
return page.run_js(js) return page._run_js(js)
def offset_scroll(ele, offset_x, offset_y): def offset_scroll(ele, offset_x, offset_y):
@ -122,8 +119,8 @@ def offset_scroll(ele, offset_x, offset_y):
lx = loc_x + offset_x if offset_x else cp_x lx = loc_x + offset_x if offset_x else cp_x
ly = loc_y + offset_y if offset_y else cp_y ly = loc_y + offset_y if offset_y else cp_y
if not location_in_viewport(ele.owner, lx, ly): if not location_in_viewport(ele.owner, lx, ly):
clientWidth = ele.owner.run_js('return document.body.clientWidth;') clientWidth = ele.owner._run_js('return document.body.clientWidth;')
clientHeight = ele.owner.run_js('return document.body.clientHeight;') clientHeight = ele.owner._run_js('return document.body.clientHeight;')
ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2) ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)
cl_x, cl_y = ele.rect.viewport_location cl_x, cl_y = ele.rect.viewport_location
ccp_x, ccp_y = ele.rect.viewport_click_point ccp_x, ccp_y = ele.rect.viewport_click_point
@ -174,188 +171,6 @@ def is_js_func(func):
return False return False
def cookie_to_dict(cookie):
"""把Cookie对象转为dict格式
:param cookie: Cookie对象字符串或字典
:return: cookie字典
"""
if isinstance(cookie, Cookie):
cookie_dict = cookie.__dict__.copy()
cookie_dict.pop('rfc2109', None)
cookie_dict.pop('_rest', None)
return cookie_dict
elif isinstance(cookie, dict):
cookie_dict = cookie
elif isinstance(cookie, str):
cookie_dict = {}
for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):
attr_val = attr.strip().split('=', 1)
if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
else:
cookie_dict['name'] = attr_val[0]
cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''
return cookie_dict
else:
raise TypeError('cookie参数必须为Cookie、str或dict类型。')
return cookie_dict
def cookies_to_tuple(cookies):
"""把cookies转为tuple格式
:param cookies: cookies信息可为CookieJar, list, tuple, str, dict
:return: 返回tuple形式的cookies
"""
if isinstance(cookies, (list, tuple, CookieJar)):
cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)
elif isinstance(cookies, str):
c_dict = {}
cookies = cookies.rstrip('; ')
cookies = cookies.split(';')
# r = match(r'.*?=([^=]+)=', cookies)
# if not r: # 只有一个
# cookies = [cookies.rstrip(',;')]
# else:
# s = match(r'.*([,;]).*', r.group(1)).group(1)
# cookies = cookies.rstrip(s).split(s)
for attr in cookies:
attr_val = attr.strip().split('=', 1)
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
cookies = _dict_cookies_to_tuple(c_dict)
elif isinstance(cookies, dict):
cookies = _dict_cookies_to_tuple(cookies)
elif isinstance(cookies, Cookie):
cookies = (cookie_to_dict(cookies),)
else:
raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。')
return cookies
def set_session_cookies(session, cookies):
"""设置Session对象的cookies
:param session: Session对象
:param cookies: cookies信息
:return: None
"""
for cookie in cookies_to_tuple(cookies):
if cookie['value'] is None:
cookie['value'] = ''
kwargs = {x: cookie[x] for x in cookie
if x.lower() in ('version', 'port', 'domain', 'path', 'secure',
'expires', 'discard', 'comment', 'comment_url', 'rest')}
if 'expiry' in cookie:
kwargs['expires'] = cookie['expiry']
session.cookies.set(cookie['name'], cookie['value'], **kwargs)
def set_browser_cookies(page, cookies):
"""设置cookies值
:param page: 页面对象
:param cookies: cookies信息
:return: None
"""
for cookie in cookies_to_tuple(cookies):
if 'expiry' in cookie:
cookie['expires'] = int(cookie['expiry'])
cookie.pop('expiry')
if 'expires' in cookie:
if not cookie['expires']:
cookie.pop('expires')
elif isinstance(cookie['expires'], str):
if cookie['expires'].isdigit():
cookie['expires'] = int(cookie['expires'])
elif cookie['expires'].replace('.', '').isdigit():
cookie['expires'] = float(cookie['expires'])
else:
try:
cookie['expires'] = datetime.strptime(cookie['expires'],
'%a, %d %b %Y %H:%M:%S GMT').timestamp()
except ValueError:
cookie['expires'] = datetime.strptime(cookie['expires'],
'%a, %d %b %y %H:%M:%S GMT').timestamp()
if cookie['value'] is None:
cookie['value'] = ''
elif not isinstance(cookie['value'], str):
cookie['value'] = str(cookie['value'])
if cookie['name'].startswith('__Host-'):
cookie['path'] = '/'
cookie['secure'] = True
if not page.url.startswith('http'):
cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)
else:
cookie['url'] = page.url
page.run_cdp_loaded('Network.setCookie', **cookie)
continue # 不用设置域名,可退出
if cookie['name'].startswith('__Secure-'):
cookie['secure'] = True
if cookie.get('domain', None):
try:
page.run_cdp_loaded('Network.setCookie', **cookie)
if is_cookie_in_driver(page, cookie):
continue
except Exception:
pass
url = page._browser_url
if not url.startswith('http'):
raise RuntimeError(f'未设置域名请设置cookie的domain参数或先访问一个网站。{cookie}')
ex_url = extract(url)
d_list = ex_url.subdomain.split('.')
d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)
tmp = [d_list[0]]
if len(d_list) > 1:
for i in d_list[1:]:
tmp.append('.')
tmp.append(i)
for i in range(len(tmp)):
cookie['domain'] = ''.join(tmp[i:])
page.run_cdp_loaded('Network.setCookie', **cookie)
if is_cookie_in_driver(page, cookie):
break
def is_cookie_in_driver(page, cookie):
"""查询cookie是否在浏览器内
:param page: BasePage对象
:param cookie: dict格式cookie
:return: bool
"""
if 'domain' in cookie:
for c in page.cookies(all_domains=True):
if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain',
None):
return True
else:
for c in page.cookies(all_domains=True):
if cookie['name'] == c['name'] and cookie['value'] == c['value']:
return True
return False
def get_blob(page, url, as_bytes=True): def get_blob(page, url, as_bytes=True):
"""获取知道blob资源 """获取知道blob资源
:param page: 资源所在页面对象 :param page: 资源所在页面对象
@ -381,7 +196,7 @@ def get_blob(page, url, as_bytes=True):
} }
""" """
try: try:
result = page.run_js(js, url) result = page._run_js(js, url)
except: except:
raise RuntimeError('无法获取该资源。') raise RuntimeError('无法获取该资源。')
if as_bytes: if as_bytes:
@ -429,7 +244,7 @@ def get_mhtml(page, path=None, name=None):
:param name: 文件名为None且path不为None时用title属性值 :param name: 文件名为None且path不为None时用title属性值
:return: mhtml文本 :return: mhtml文本
""" """
r = page.run_cdp('Page.captureSnapshot')['data'] r = page._run_cdp('Page.captureSnapshot')['data']
if path is None and name is None: if path is None and name is None:
return r return r
@ -455,7 +270,7 @@ def get_pdf(page, path=None, name=None, kwargs=None):
if 'printBackground' not in kwargs: if 'printBackground' not in kwargs:
kwargs['printBackground'] = True kwargs['printBackground'] = True
try: try:
r = page.run_cdp('Page.printToPDF', **kwargs)['data'] r = page._run_cdp('Page.printToPDF', **kwargs)['data']
except: except:
raise RuntimeError('保存失败,可能浏览器版本不支持。') raise RuntimeError('保存失败,可能浏览器版本不支持。')
from base64 import b64decode from base64 import b64decode
@ -539,15 +354,3 @@ def format_headers(txt):
name, value = header.split(': ', maxsplit=1) name, value = header.split(': ', maxsplit=1)
headers[name] = value headers[name] = value
return headers return headers
def _dict_cookies_to_tuple(cookies: dict):
"""把dict形式的cookies转换为tuple形式
:param cookies: 单个或多个cookies单个时包含'name''value'
:return: 多个dict格式cookies组成的列表
"""
if 'name' in cookies and 'value' in cookies: # 单个cookie
return (cookies,)
keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')
template = {k: v for k, v in cookies.items() if k in keys}
return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)

View File

@ -5,13 +5,9 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause. @License : BSD 3-Clause.
""" """
from http.cookiejar import Cookie
from pathlib import Path from pathlib import Path
from typing import Union, Optional from typing import Union, Optional
from requests import Session
from requests.cookies import RequestsCookieJar
from .._base.base import DrissionElement, BaseParser from .._base.base import DrissionElement, BaseParser
from .._elements.chromium_element import ChromiumElement from .._elements.chromium_element import ChromiumElement
from .._pages.chromium_base import ChromiumBase from .._pages.chromium_base import ChromiumBase
@ -37,21 +33,6 @@ def make_absolute_link(link: str, baseURI: str = None) -> str: ...
def is_js_func(func: str) -> bool: ... def is_js_func(func: str) -> bool: ...
def cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict: ...
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple: ...
def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
def set_browser_cookies(page: ChromiumBase, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ... def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ...

View File

@ -18,6 +18,7 @@ from .._base.base import BasePage
from .._elements.chromium_element import run_js, make_chromium_eles from .._elements.chromium_element import run_js, make_chromium_eles
from .._elements.none_element import NoneElement from .._elements.none_element import NoneElement
from .._elements.session_element import make_session_ele from .._elements.session_element import make_session_ele
from .._functions.cookies import CookiesList
from .._functions.locator import get_loc, is_loc from .._functions.locator import get_loc, is_loc
from .._functions.settings import Settings from .._functions.settings import Settings
from .._functions.tools import raise_error from .._functions.tools import raise_error
@ -120,7 +121,7 @@ class ChromiumBase(BasePage):
self._driver.run('Page.enable') self._driver.run('Page.enable')
self._driver.run('Emulation.setFocusEmulationEnabled', enabled=True) self._driver.run('Emulation.setFocusEmulationEnabled', enabled=True)
r = self.run_cdp('Page.getFrameTree') r = self._run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)): for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id self.browser._frames[i] = self.tab_id
if not hasattr(self, '_frame_id'): if not hasattr(self, '_frame_id'):
@ -146,10 +147,10 @@ class ChromiumBase(BasePage):
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
while perf_counter() < end_time: while perf_counter() < end_time:
try: try:
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId'] b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
timeout = end_time - perf_counter() timeout = end_time - perf_counter()
timeout = 1 if timeout <= 1 else timeout timeout = 1 if timeout <= 1 else timeout
self._root_id = self.run_cdp('DOM.resolveNode', backendNodeId=b_id, self._root_id = self._run_cdp('DOM.resolveNode', backendNodeId=b_id,
_timeout=timeout)['object']['objectId'] _timeout=timeout)['object']['objectId']
result = True result = True
break break
@ -166,7 +167,7 @@ class ChromiumBase(BasePage):
result = False result = False
if result: if result:
r = self.run_cdp('Page.getFrameTree') r = self._run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)): for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id self.browser._frames[i] = self.tab_id
@ -203,7 +204,7 @@ class ChromiumBase(BasePage):
def _onDomContentEventFired(self, **kwargs): def _onDomContentEventFired(self, **kwargs):
"""在页面刷新、变化后重新读取页面内容""" """在页面刷新、变化后重新读取页面内容"""
if self._load_mode == 'eager': if self._load_mode == 'eager':
self.run_cdp('Page.stopLoading') self._run_cdp('Page.stopLoading')
if self._get_document(self._load_end_time - perf_counter() - .1): if self._get_document(self._load_end_time - perf_counter() - .1):
self._doc_got = True self._doc_got = True
self._ready_state = 'interactive' self._ready_state = 'interactive'
@ -228,10 +229,10 @@ class ChromiumBase(BasePage):
if 'backendNodeId' not in kwargs: if 'backendNodeId' not in kwargs:
raise TypeError('该输入框无法接管,请改用对<input>元素输入路径的方法设置。') raise TypeError('该输入框无法接管,请改用对<input>元素输入路径的方法设置。')
files = self._upload_list if kwargs['mode'] == 'selectMultiple' else self._upload_list[:1] files = self._upload_list if kwargs['mode'] == 'selectMultiple' else self._upload_list[:1]
self.run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=kwargs['backendNodeId']) self._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=kwargs['backendNodeId'])
self.driver.set_callback('Page.fileChooserOpened', None) self.driver.set_callback('Page.fileChooserOpened', None)
self.run_cdp('Page.setInterceptFileChooserDialog', enabled=False) self._run_cdp('Page.setInterceptFileChooserDialog', enabled=False)
self._upload_list = None self._upload_list = None
def __call__(self, locator, index=1, timeout=None): def __call__(self, locator, index=1, timeout=None):
@ -333,12 +334,12 @@ class ChromiumBase(BasePage):
@property @property
def title(self): def title(self):
"""返回当前页面title""" """返回当前页面title"""
return self.run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['title'] return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['title']
@property @property
def url(self): def url(self):
"""返回当前页面url""" """返回当前页面url"""
return self.run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['url'] return self._run_cdp_loaded('Target.getTargetInfo', targetId=self._target_id)['targetInfo']['url']
@property @property
def _browser_url(self): def _browser_url(self):
@ -349,7 +350,7 @@ class ChromiumBase(BasePage):
def html(self): def html(self):
"""返回当前页面html文本""" """返回当前页面html文本"""
self.wait.doc_loaded() self.wait.doc_loaded()
return self.run_cdp('DOM.getOuterHTML', objectId=self._root_id)['outerHTML'] return self._run_cdp('DOM.getOuterHTML', objectId=self._root_id)['outerHTML']
@property @property
def json(self): def json(self):
@ -372,7 +373,7 @@ class ChromiumBase(BasePage):
@property @property
def active_ele(self): def active_ele(self):
"""返回当前焦点所在元素""" """返回当前焦点所在元素"""
return self.run_js_loaded('return document.activeElement;') return self._run_js_loaded('return document.activeElement;')
@property @property
def load_mode(self): def load_mode(self):
@ -382,7 +383,7 @@ class ChromiumBase(BasePage):
@property @property
def user_agent(self): def user_agent(self):
"""返回user agent""" """返回user agent"""
return self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value'] return self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
@property @property
def upload_list(self): def upload_list(self):
@ -393,7 +394,7 @@ class ChromiumBase(BasePage):
def _js_ready_state(self): def _js_ready_state(self):
"""返回js获取的ready state信息""" """返回js获取的ready state信息"""
try: try:
return self.run_cdp('Runtime.evaluate', expression='document.readyState;', _timeout=3)['result']['value'] return self._run_cdp('Runtime.evaluate', expression='document.readyState;', _timeout=3)['result']['value']
except ContextLostError: except ContextLostError:
return None return None
except TimeoutError: except TimeoutError:
@ -405,9 +406,8 @@ class ChromiumBase(BasePage):
:param cmd_args: 参数 :param cmd_args: 参数
:return: 执行的结果 :return: 执行的结果
""" """
ignore = cmd_args.pop('_ignore', None)
r = self.driver.run(cmd, **cmd_args) r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, ignore) return r if __ERROR__ not in r else raise_error(r, user=True)
def run_cdp_loaded(self, cmd, **cmd_args): def run_cdp_loaded(self, cmd, **cmd_args):
"""执行Chrome DevTools Protocol语句执行前等待页面加载完毕 """执行Chrome DevTools Protocol语句执行前等待页面加载完毕
@ -416,7 +416,27 @@ class ChromiumBase(BasePage):
:return: 执行的结果 :return: 执行的结果
""" """
self.wait.doc_loaded() self.wait.doc_loaded()
return self.run_cdp(cmd, **cmd_args) r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, user=True)
def _run_cdp(self, cmd, **cmd_args):
"""执行Chrome DevTools Protocol语句
:param cmd: 协议项目
:param cmd_args: 参数
:return: 执行的结果
"""
ignore = cmd_args.pop('_ignore', None)
r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, ignore)
def _run_cdp_loaded(self, cmd, **cmd_args):
"""执行Chrome DevTools Protocol语句执行前等待页面加载完毕
:param cmd: 协议项目
:param cmd_args: 参数
:return: 执行的结果
"""
self.wait.doc_loaded()
return self._run_cdp(cmd, **cmd_args)
def run_js(self, script, *args, as_expr=False, timeout=None): def run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码 """运行javascript代码
@ -426,9 +446,30 @@ class ChromiumBase(BasePage):
:param timeout: js超时时间为None则使用页面timeouts.script设置 :param timeout: js超时时间为None则使用页面timeouts.script设置
:return: 运行的结果 :return: 运行的结果
""" """
return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args) return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def run_js_loaded(self, script, *args, as_expr=False, timeout=None): def run_js_loaded(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码执行前等待页面加载完毕
:param script: js文本或js文件路径
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
:param as_expr: 是否作为表达式运行为True时args无效
:param timeout: js超时时间为None则使用页面timeouts.script属性值
:return: 运行的结果
"""
self.wait.doc_loaded()
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def _run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码
:param script: js文本或js文件路径
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
:param as_expr: 是否作为表达式运行为True时args无效
:param timeout: js超时时间为None则使用页面timeouts.script设置
:return: 运行的结果
"""
return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args)
def _run_js_loaded(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码执行前等待页面加载完毕 """运行javascript代码执行前等待页面加载完毕
:param script: js文本或js文件路径 :param script: js文本或js文件路径
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]... :param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
@ -462,23 +503,21 @@ class ChromiumBase(BasePage):
show_errmsg=show_errmsg, timeout=timeout) show_errmsg=show_errmsg, timeout=timeout)
return self._url_available return self._url_available
def cookies(self, as_dict=False, all_domains=False, all_info=False): def cookies(self, all_domains=False, all_info=False):
"""返回cookies信息 """返回cookies信息
:param as_dict: 为True时以dict格式返回且all_info无效为False时返回list
:param all_domains: 是否返回所有域的cookies :param all_domains: 是否返回所有域的cookies
:param all_info: 是否返回所有信息为False时只返回namevaluedomain :param all_info: 是否返回所有信息为False时只返回namevaluedomain
:return: cookies信息 :return: cookies信息
""" """
txt = 'Storage' if all_domains else 'Network' txt = 'Storage' if all_domains else 'Network'
cookies = self.run_cdp_loaded(f'{txt}.getCookies')['cookies'] cookies = self._run_cdp_loaded(f'{txt}.getCookies')['cookies']
if as_dict: if all_info:
return {cookie['name']: cookie['value'] for cookie in cookies} r = cookies
elif all_info:
return cookies
else: else:
return [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} r = [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} for cookie in cookies]
for cookie in cookies]
return CookiesList(r)
def ele(self, locator, index=1, timeout=None): def ele(self, locator, index=1, timeout=None):
"""获取一个符合条件的元素对象 """获取一个符合条件的元素对象
@ -591,7 +630,7 @@ class ChromiumBase(BasePage):
:return: None :return: None
""" """
self._is_loading = True self._is_loading = True
self.run_cdp('Page.reload', ignoreCache=ignore_cache) self._run_cdp('Page.reload', ignoreCache=ignore_cache)
self.wait.load_start() self.wait.load_start()
def forward(self, steps=1): def forward(self, steps=1):
@ -616,7 +655,7 @@ class ChromiumBase(BasePage):
if steps == 0: if steps == 0:
return return
history = self.run_cdp('Page.getNavigationHistory') history = self._run_cdp('Page.getNavigationHistory')
index = history['currentIndex'] index = history['currentIndex']
history = history['entries'] history = history['entries']
direction = 1 if steps > 0 else -1 direction = 1 if steps > 0 else -1
@ -632,12 +671,12 @@ class ChromiumBase(BasePage):
if nid: if nid:
self._is_loading = True self._is_loading = True
self.run_cdp('Page.navigateToHistoryEntry', entryId=nid) self._run_cdp('Page.navigateToHistoryEntry', entryId=nid)
def stop_loading(self): def stop_loading(self):
"""页面停止加载""" """页面停止加载"""
try: try:
self.run_cdp('Page.stopLoading') self._run_cdp('Page.stopLoading')
end_time = perf_counter() + 5 end_time = perf_counter() + 5
while self._ready_state != 'complete' and perf_counter() < end_time: while self._ready_state != 'complete' and perf_counter() < end_time:
sleep(.1) sleep(.1)
@ -655,7 +694,7 @@ class ChromiumBase(BasePage):
return return
ele = self._ele(loc_or_ele, raise_err=False) ele = self._ele(loc_or_ele, raise_err=False)
if ele: if ele:
self.run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError) self._run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError)
def add_ele(self, html_or_info, insert_to=None, before=None): def add_ele(self, html_or_info, insert_to=None, before=None):
"""新建一个元素 """新建一个元素
@ -711,7 +750,7 @@ class ChromiumBase(BasePage):
else: else:
raise TypeError('html_or_info参数必须是html文本或tupletuple格式为(tag, {name: value})。') raise TypeError('html_or_info参数必须是html文本或tupletuple格式为(tag, {name: value})。')
ele = self.run_js(js, *args) ele = self._run_js(js, *args)
return ele return ele
def get_frame(self, loc_ind_ele, timeout=None): def get_frame(self, loc_ind_ele, timeout=None):
@ -772,7 +811,7 @@ class ChromiumBase(BasePage):
:return: sessionStorage一个或所有项内容 :return: sessionStorage一个或所有项内容
""" """
js = f'sessionStorage.getItem("{item}")' if item else 'sessionStorage' js = f'sessionStorage.getItem("{item}")' if item else 'sessionStorage'
return self.run_js_loaded(js, as_expr=True) return self._run_js_loaded(js, as_expr=True)
def local_storage(self, item=None): def local_storage(self, item=None):
"""返回localStorage信息不设置item则获取全部 """返回localStorage信息不设置item则获取全部
@ -780,7 +819,7 @@ class ChromiumBase(BasePage):
:return: localStorage一个或所有项内容 :return: localStorage一个或所有项内容
""" """
js = f'localStorage.getItem("{item}")' if item else 'localStorage' js = f'localStorage.getItem("{item}")' if item else 'localStorage'
return self.run_js_loaded(js, as_expr=True) return self._run_js_loaded(js, as_expr=True)
def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None, def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None,
full_page=False, left_top=None, right_bottom=None): full_page=False, left_top=None, right_bottom=None):
@ -802,7 +841,7 @@ class ChromiumBase(BasePage):
:param script: js文本 :param script: js文本
:return: 添加的脚本的id :return: 添加的脚本的id
""" """
js_id = self.run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script, js_id = self._run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script,
includeCommandLineAPI=True)['identifier'] includeCommandLineAPI=True)['identifier']
self._init_jss.append(js_id) self._init_jss.append(js_id)
return js_id return js_id
@ -814,11 +853,11 @@ class ChromiumBase(BasePage):
""" """
if script_id is None: if script_id is None:
for js_id in self._init_jss: for js_id in self._init_jss:
self.run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=js_id) self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=js_id)
self._init_jss.clear() self._init_jss.clear()
elif script_id in self._init_jss: elif script_id in self._init_jss:
self.run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=script_id) self._run_cdp('Page.removeScriptToEvaluateOnNewDocument', identifier=script_id)
self._init_jss.remove(script_id) self._init_jss.remove(script_id)
def clear_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True): def clear_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True):
@ -830,19 +869,19 @@ class ChromiumBase(BasePage):
:return: None :return: None
""" """
if session_storage or local_storage: if session_storage or local_storage:
self.run_cdp_loaded('DOMStorage.enable') self._run_cdp_loaded('DOMStorage.enable')
i = self.run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey'] i = self._run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey']
if session_storage: if session_storage:
self.run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': False}) self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': False})
if local_storage: if local_storage:
self.run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True}) self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True})
self.run_cdp_loaded('DOMStorage.disable') self._run_cdp_loaded('DOMStorage.disable')
if cache: if cache:
self.run_cdp_loaded('Network.clearBrowserCache') self._run_cdp_loaded('Network.clearBrowserCache')
if cookies: if cookies:
self.run_cdp_loaded('Network.clearBrowserCookies') self._run_cdp_loaded('Network.clearBrowserCookies')
def disconnect(self): def disconnect(self):
"""断开与页面的连接,不关闭页面""" """断开与页面的连接,不关闭页面"""
@ -974,7 +1013,7 @@ class ChromiumBase(BasePage):
err = None err = None
end_time = perf_counter() + timeout end_time = perf_counter() + timeout
try: try:
result = self.run_cdp('Page.navigate', frameId=self._frame_id, url=to_url, _timeout=timeout) result = self._run_cdp('Page.navigate', frameId=self._frame_id, url=to_url, _timeout=timeout)
if 'errorText' in result: if 'errorText' in result:
err = ConnectionError(result['errorText']) err = ConnectionError(result['errorText'])
except TimeoutError: except TimeoutError:
@ -1074,8 +1113,8 @@ class ChromiumBase(BasePage):
v = not (location_in_viewport(self, x, y) and v = not (location_in_viewport(self, x, y) and
location_in_viewport(self, right_bottom[0], right_bottom[1])) location_in_viewport(self, right_bottom[0], right_bottom[1]))
if v and (self.run_js('return document.body.scrollHeight > window.innerHeight;') and if v and (self._run_js('return document.body.scrollHeight > window.innerHeight;') and
not self.run_js('return document.body.scrollWidth > window.innerWidth;')): not self._run_js('return document.body.scrollWidth > window.innerWidth;')):
x += 10 x += 10
vp = {'x': x, 'y': y, 'width': w, 'height': h, 'scale': 1} vp = {'x': x, 'y': y, 'width': w, 'height': h, 'scale': 1}
@ -1086,7 +1125,7 @@ class ChromiumBase(BasePage):
if pic_type == 'jpeg': if pic_type == 'jpeg':
args['quality'] = 100 args['quality'] = 100
png = self.run_cdp_loaded('Page.captureScreenshot', **args)['data'] png = self._run_cdp_loaded('Page.captureScreenshot', **args)['data']
if as_base64: if as_base64:
return png return png

View File

@ -15,6 +15,7 @@ from .._base.driver import Driver
from .._elements.chromium_element import ChromiumElement from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._functions.web import CookiesList
from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_frame import ChromiumFrame
from .._pages.chromium_page import ChromiumPage from .._pages.chromium_page import ChromiumPage
from .._units.actions import Actions from .._units.actions import Actions
@ -170,13 +171,16 @@ class ChromiumBase(BasePage):
def run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ... def run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def _run_js(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def _run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ...
def run_async_js(self, script: Union[str, Path], *args, as_expr: bool = False) -> None: ... def run_async_js(self, script: Union[str, Path], *args, as_expr: bool = False) -> None: ...
def get(self, url: str, show_errmsg: bool = False, retry: int = None, def get(self, url: str, show_errmsg: bool = False, retry: int = None,
interval: float = None, timeout: float = None) -> Union[None, bool]: ... interval: float = None, timeout: float = None) -> Union[None, bool]: ...
def cookies(self, as_dict: bool = False, all_domains: bool = False, all_info: bool = False) -> Union[ def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ...
list, dict]: ...
def ele(self, def ele(self,
locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame], locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame],
@ -225,6 +229,10 @@ class ChromiumBase(BasePage):
def run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ... def run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ...
def _run_cdp(self, cmd: str, **cmd_args) -> dict: ...
def _run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ...
def session_storage(self, item: str = None) -> Union[str, dict, None]: ... def session_storage(self, item: str = None) -> Union[str, dict, None]: ...
def local_storage(self, item: str = None) -> Union[str, dict, None]: ... def local_storage(self, item: str = None) -> Union[str, dict, None]: ...

View File

@ -33,7 +33,7 @@ class ChromiumFrame(ChromiumBase):
self._frame_ele = ele self._frame_ele = ele
self._reloading = False self._reloading = False
node = info['node'] if not info else owner.run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node'] node = info['node'] if not info else owner._run_cdp('DOM.describeNode', backendNodeId=ele._backend_id)['node']
self._frame_id = node['frameId'] self._frame_id = node['frameId']
if self._is_inner_frame(): if self._is_inner_frame():
self._is_diff_domain = False self._is_diff_domain = False
@ -43,7 +43,7 @@ class ChromiumFrame(ChromiumBase):
self._is_diff_domain = True self._is_diff_domain = True
delattr(self, '_frame_id') delattr(self, '_frame_id')
super().__init__(owner.browser, node['frameId']) super().__init__(owner.browser, node['frameId'])
obj_id = super().run_js('document;', as_expr=True)['objectId'] obj_id = super()._run_js('document;', as_expr=True)['objectId']
self.doc_ele = ChromiumElement(self, obj_id=obj_id) self.doc_ele = ChromiumElement(self, obj_id=obj_id)
self._type = 'ChromiumFrame' self._type = 'ChromiumFrame'
@ -100,7 +100,7 @@ class ChromiumFrame(ChromiumBase):
self._frame_ele = ChromiumElement(self._target_page, backend_id=self._backend_id) self._frame_ele = ChromiumElement(self._target_page, backend_id=self._backend_id)
end_time = perf_counter() + 2 end_time = perf_counter() + 2
while perf_counter() < end_time: while perf_counter() < end_time:
node = self._target_page.run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node'] node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node']
if 'frameId' in node: if 'frameId' in node:
break break
sleep(.05) sleep(.05)
@ -145,17 +145,17 @@ class ChromiumFrame(ChromiumBase):
self._is_reading = True self._is_reading = True
try: try:
if self._is_diff_domain is False: if self._is_diff_domain is False:
node = self._target_page.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] node = self._target_page._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node']
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId']) self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
else: else:
timeout = max(timeout, 2) timeout = max(timeout, 2)
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId'] b_id = self._run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
self.doc_ele = ChromiumElement(self, backend_id=b_id) self.doc_ele = ChromiumElement(self, backend_id=b_id)
self._root_id = self.doc_ele._obj_id self._root_id = self.doc_ele._obj_id
r = self.run_cdp('Page.getFrameTree') r = self._run_cdp('Page.getFrameTree')
for i in findall(r"'id': '(.*?)'", str(r)): for i in findall(r"'id': '(.*?)'", str(r)):
self.browser._frames[i] = self.tab_id self.browser._frames[i] = self.tab_id
return True return True
@ -253,7 +253,7 @@ class ChromiumFrame(ChromiumBase):
def url(self): def url(self):
"""返回frame当前访问的url""" """返回frame当前访问的url"""
try: try:
return self.doc_ele.run_js('return this.location.href;') return self.doc_ele._run_js('return this.location.href;')
except JavaScriptError: except JavaScriptError:
return None return None
@ -261,14 +261,14 @@ class ChromiumFrame(ChromiumBase):
def html(self): def html(self):
"""返回元素outerHTML文本""" """返回元素outerHTML文本"""
tag = self.tag tag = self.tag
out_html = self._target_page.run_cdp('DOM.getOuterHTML', backendNodeId=self.frame_ele._backend_id)['outerHTML'] out_html = self._target_page._run_cdp('DOM.getOuterHTML', backendNodeId=self.frame_ele._backend_id)['outerHTML']
sign = search(rf'<{tag}.*?>', out_html, DOTALL).group(0) sign = search(rf'<{tag}.*?>', out_html, DOTALL).group(0)
return f'{sign}{self.inner_html}</{tag}>' return f'{sign}{self.inner_html}</{tag}>'
@property @property
def inner_html(self): def inner_html(self):
"""返回元素innerHTML文本""" """返回元素innerHTML文本"""
return self.doc_ele.run_js('return this.documentElement.outerHTML;') return self.doc_ele._run_js('return this.documentElement.outerHTML;')
@property @property
def title(self): def title(self):
@ -284,7 +284,7 @@ class ChromiumFrame(ChromiumBase):
@property @property
def active_ele(self): def active_ele(self):
"""返回当前焦点所在元素""" """返回当前焦点所在元素"""
return self.doc_ele.run_js('return this.activeElement;') return self.doc_ele._run_js('return this.activeElement;')
@property @property
def xpath(self): def xpath(self):
@ -318,18 +318,18 @@ class ChromiumFrame(ChromiumBase):
else: else:
try: try:
return self.doc_ele.run_js('return this.readyState;') return self.doc_ele._run_js('return this.readyState;')
except ContextLostError: except ContextLostError:
try: try:
node = self.run_cdp('DOM.describeNode', backendNodeId=self.frame_ele._backend_id)['node'] node = self._run_cdp('DOM.describeNode', backendNodeId=self.frame_ele._backend_id)['node']
doc = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId']) doc = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
return doc.run_js('return this.readyState;') return doc._run_js('return this.readyState;')
except: except:
return None return None
def refresh(self): def refresh(self):
"""刷新frame页面""" """刷新frame页面"""
self.doc_ele.run_js('this.location.reload();') self.doc_ele._run_js('this.location.reload();')
def property(self, name): def property(self, name):
"""返回frame元素一个property属性值 """返回frame元素一个property属性值
@ -353,6 +353,16 @@ class ChromiumFrame(ChromiumBase):
self.frame_ele.remove_attr(name) self.frame_ele.remove_attr(name)
def run_js(self, script, *args, as_expr=False, timeout=None): def run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码
:param script: js文本
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
:param as_expr: 是否作为表达式运行为True时args无效
:param timeout: js超时时间为None则使用页面timeouts.script设置
:return: 运行的结果
"""
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def _run_js(self, script, *args, as_expr=False, timeout=None):
"""运行javascript代码 """运行javascript代码
:param script: js文本 :param script: js文本
:param args: 参数按顺序在js文本中对应arguments[0]arguments[1]... :param args: 参数按顺序在js文本中对应arguments[0]arguments[1]...
@ -361,9 +371,9 @@ class ChromiumFrame(ChromiumBase):
:return: 运行的结果 :return: 运行的结果
""" """
if script.startswith('this.scrollIntoView'): if script.startswith('this.scrollIntoView'):
return self.frame_ele.run_js(script, *args, as_expr=as_expr, timeout=timeout) return self.frame_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)
else: else:
return self.doc_ele.run_js(script, *args, as_expr=as_expr, timeout=timeout) return self.doc_ele._run_js(script, *args, as_expr=as_expr, timeout=timeout)
def parent(self, level_or_loc=1, index=1): def parent(self, level_or_loc=1, index=1):
"""返回上面某一级父元素,可指定层数或用查询语法定位 """返回上面某一级父元素,可指定层数或用查询语法定位
@ -526,12 +536,12 @@ class ChromiumFrame(ChromiumBase):
img.style.setProperty("position","fixed"); img.style.setProperty("position","fixed");
arguments[0].insertBefore(img, this); arguments[0].insertBefore(img, this);
return img;''' return img;'''
new_ele = first_child.run_js(js, body) new_ele = first_child._run_js(js, body)
new_ele.scroll.to_see(center=True) new_ele.scroll.to_see(center=True)
top = int(self.frame_ele.style('border-top').split('px')[0]) top = int(self.frame_ele.style('border-top').split('px')[0])
left = int(self.frame_ele.style('border-left').split('px')[0]) left = int(self.frame_ele.style('border-left').split('px')[0])
r = self.tab.run_cdp('Page.getLayoutMetrics')['visualViewport'] r = self.tab._run_cdp('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX'] sx = r['pageX']
sy = r['pageY'] sy = r['pageY']
r = self.tab.get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64, r = self.tab.get_screenshot(path=path, name=name, as_bytes=as_bytes, as_base64=as_base64,
@ -557,4 +567,4 @@ class ChromiumFrame(ChromiumBase):
def _is_inner_frame(self): def _is_inner_frame(self):
"""返回当前frame是否同域""" """返回当前frame是否同域"""
return self._frame_id in str(self._target_page.run_cdp('Page.getFrameTree')['frameTree']) return self._frame_id in str(self._target_page._run_cdp('Page.getFrameTree')['frameTree'])

View File

@ -142,6 +142,12 @@ class ChromiumFrame(ChromiumBase):
as_expr: bool = False, as_expr: bool = False,
timeout: float = None) -> Any: ... timeout: float = None) -> Any: ...
def _run_js(self,
script: str,
*args,
as_expr: bool = False,
timeout: float = None) -> Any: ...
def parent(self, def parent(self,
level_or_loc: Union[Tuple[str, str], str, int] = 1, level_or_loc: Union[Tuple[str, str], str, int] = 1,
index: int = 1) -> ChromiumElement: ... index: int = 1) -> ChromiumElement: ...

View File

@ -6,7 +6,6 @@
@License : BSD 3-Clause. @License : BSD 3-Clause.
""" """
from pathlib import Path from pathlib import Path
from threading import Lock
from time import sleep, perf_counter from time import sleep, perf_counter
from requests import Session from requests import Session
@ -65,7 +64,7 @@ class ChromiumPage(ChromiumBase):
def _run_browser(self): def _run_browser(self):
"""连接浏览器""" """连接浏览器"""
self._browser = Browser(self._chromium_options.address, self._browser_id, self) self._browser = Browser(self._chromium_options.address, self._browser_id, self)
r = self._browser.run_cdp('Browser.getVersion') r = self._browser._run_cdp('Browser.getVersion')
self._browser_version = r['product'] self._browser_version = r['product']
if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower(): if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower():
self._browser.quit(3) self._browser.quit(3)

View File

@ -10,8 +10,9 @@ from time import sleep
from .._base.base import BasePage from .._base.base import BasePage
from .._configs.session_options import SessionOptions from .._configs.session_options import SessionOptions
from .._functions.cookies import set_session_cookies, set_tab_cookies
from .._functions.settings import Settings from .._functions.settings import Settings
from .._functions.web import set_session_cookies, set_browser_cookies, save_page from .._functions.web import save_page
from .._pages.chromium_base import ChromiumBase from .._pages.chromium_base import ChromiumBase
from .._pages.session_page import SessionPage from .._pages.session_page import SessionPage
from .._units.setter import TabSetter, WebPageTabSetter from .._units.setter import TabSetter, WebPageTabSetter
@ -338,7 +339,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage):
return return
if copy_user_agent: if copy_user_agent:
user_agent = self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value'] user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
self._headers.update({"User-Agent": user_agent}) self._headers.update({"User-Agent": user_agent})
set_session_cookies(self.session, super(SessionPage, self).cookies()) set_session_cookies(self.session, super(SessionPage, self).cookies())
@ -347,19 +348,18 @@ class MixTab(SessionPage, ChromiumTab, BasePage):
"""把session对象的cookies复制到浏览器""" """把session对象的cookies复制到浏览器"""
if not self._has_driver: if not self._has_driver:
return return
set_browser_cookies(self, super().cookies()) set_tab_cookies(self, super().cookies())
def cookies(self, as_dict=False, all_domains=False, all_info=False): def cookies(self, all_domains=False, all_info=False):
"""返回cookies """返回cookies
:param as_dict: 为True时以dict格式返回为False时返回list且all_info无效
:param all_domains: 是否返回所有域的cookies :param all_domains: 是否返回所有域的cookies
:param all_info: 是否返回所有信息False则只返回namevaluedomain :param all_info: 是否返回所有信息False则只返回namevaluedomain
:return: cookies信息 :return: cookies信息
""" """
if self._mode == 's': if self._mode == 's':
return super().cookies(as_dict, all_domains, all_info) return super().cookies(all_domains, all_info)
elif self._mode == 'd': elif self._mode == 'd':
return super(SessionPage, self).cookies(as_dict, all_domains, all_info) return super(SessionPage, self).cookies(all_domains, all_info)
def close(self): def close(self):
"""关闭当前标签页""" """关闭当前标签页"""

View File

@ -17,6 +17,7 @@ from .._base.browser import Browser
from .._elements.chromium_element import ChromiumElement from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList, ChromiumElementsList from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._functions.web import CookiesList
from .._units.rect import TabRect from .._units.rect import TabRect
from .._units.setter import TabSetter, WebPageTabSetter from .._units.setter import TabSetter, WebPageTabSetter
from .._units.waiter import TabWaiter from .._units.waiter import TabWaiter
@ -153,8 +154,7 @@ class MixTab(SessionPage, ChromiumTab):
def cookies_to_browser(self) -> None: ... def cookies_to_browser(self) -> None: ...
def cookies(self, as_dict: bool = False, all_domains: bool = False, def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ...
all_info: bool = False) -> Union[dict, list]: ...
def close(self) -> None: ... def close(self) -> None: ...

View File

@ -18,7 +18,8 @@ from tldextract import extract
from .._base.base import BasePage from .._base.base import BasePage
from .._configs.session_options import SessionOptions from .._configs.session_options import SessionOptions
from .._elements.session_element import SessionElement, make_session_ele from .._elements.session_element import SessionElement, make_session_ele
from .._functions.web import cookie_to_dict, format_headers from .._functions.cookies import cookie_to_dict, CookiesList
from .._functions.web import format_headers
from .._units.setter import SessionPageSetter from .._units.setter import SessionPageSetter
@ -216,9 +217,8 @@ class SessionPage(BasePage):
""" """
return locator if isinstance(locator, SessionElement) else make_session_ele(self, locator, index=index) return locator if isinstance(locator, SessionElement) else make_session_ele(self, locator, index=index)
def cookies(self, as_dict=False, all_domains=False, all_info=False): def cookies(self, all_domains=False, all_info=False):
"""返回cookies """返回cookies
:param as_dict: 为True时以dict格式返回为False时返回list且all_info无效
:param all_domains: 是否返回所有域的cookies :param all_domains: 是否返回所有域的cookies
:param all_info: 是否返回所有信息False则只返回namevaluedomain :param all_info: 是否返回所有信息False则只返回namevaluedomain
:return: cookies信息 :return: cookies信息
@ -229,17 +229,16 @@ class SessionPage(BasePage):
if self.url: if self.url:
ex_url = extract(self._session_url) ex_url = extract(self._session_url)
domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain
cookies = tuple(c for c in self.session.cookies if domain in c.domain or c.domain == '')
cookies = tuple(x for x in self.session.cookies if domain in x.domain or x.domain == '')
else: else:
cookies = tuple(x for x in self.session.cookies) cookies = tuple(c for c in self.session.cookies)
if as_dict: if all_info:
return {x.name: x.value for x in cookies} r = CookiesList()
elif all_info: for c in cookies:
return [cookie_to_dict(cookie) for cookie in cookies] r.append(cookie_to_dict(c))
else: else:
r = [] r = CookiesList()
for c in cookies: for c in cookies:
c = cookie_to_dict(c) c = cookie_to_dict(c)
r.append({'name': c['name'], 'value': c['value'], 'domain': c['domain']}) r.append({'name': c['name'], 'value': c['value'], 'domain': c['domain']})

View File

@ -15,6 +15,7 @@ from .._base.base import BasePage
from .._configs.session_options import SessionOptions from .._configs.session_options import SessionOptions
from .._elements.session_element import SessionElement from .._elements.session_element import SessionElement
from .._functions.elements import SessionElementsList from .._functions.elements import SessionElementsList
from .._functions.web import CookiesList
from .._units.setter import SessionPageSetter from .._units.setter import SessionPageSetter
@ -27,7 +28,7 @@ class SessionPage(BasePage):
self._url: str = ... self._url: str = ...
self._response: Response = ... self._response: Response = ...
self._url_available: bool = ... self._url_available: bool = ...
self.timeout: float = ... self._timeout: float = ...
self.retry_times: int = ... self.retry_times: int = ...
self.retry_interval: float = ... self.retry_interval: float = ...
self._set: SessionPageSetter = ... self._set: SessionPageSetter = ...
@ -113,9 +114,8 @@ class SessionPage(BasePage):
raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ... raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ...
def cookies(self, def cookies(self,
as_dict: bool = False,
all_domains: bool = False, all_domains: bool = False,
all_info: bool = False) -> Union[dict, list]: ... all_info: bool = False) -> CookiesList: ...
# ----------------session独有属性和方法----------------------- # ----------------session独有属性和方法-----------------------
@property @property

View File

@ -10,7 +10,7 @@ from .chromium_tab import MixTab
from .session_page import SessionPage from .session_page import SessionPage
from .._base.base import BasePage from .._base.base import BasePage
from .._configs.chromium_options import ChromiumOptions from .._configs.chromium_options import ChromiumOptions
from .._functions.web import set_session_cookies, set_browser_cookies from .._functions.cookies import set_session_cookies, set_tab_cookies
from .._units.setter import WebPageSetter from .._units.setter import WebPageSetter
@ -276,7 +276,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
return return
if copy_user_agent: if copy_user_agent:
user_agent = self.run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value'] user_agent = self._run_cdp('Runtime.evaluate', expression='navigator.userAgent;')['result']['value']
self._headers.update({"User-Agent": user_agent}) self._headers.update({"User-Agent": user_agent})
set_session_cookies(self.session, super(SessionPage, self).cookies()) set_session_cookies(self.session, super(SessionPage, self).cookies())
@ -285,7 +285,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
"""把session对象的cookies复制到浏览器""" """把session对象的cookies复制到浏览器"""
if not self._has_driver: if not self._has_driver:
return return
set_browser_cookies(self, super().cookies()) set_tab_cookies(self, super().cookies())
def cookies(self, as_dict=False, all_domains=False, all_info=False): def cookies(self, as_dict=False, all_domains=False, all_info=False):
"""返回cookies """返回cookies

View File

@ -7,9 +7,9 @@
""" """
from time import sleep, perf_counter from time import sleep, perf_counter
from ..errors import AlertExistsError
from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions
from .._functions.web import location_in_viewport from .._functions.web import location_in_viewport
from ..errors import AlertExistsError
class Actions: class Actions:
@ -51,8 +51,8 @@ class Actions:
if not location_in_viewport(self.owner, lx, ly): if not location_in_viewport(self.owner, lx, ly):
# 把坐标滚动到页面中间 # 把坐标滚动到页面中间
clientWidth = self.owner.run_js('return document.body.clientWidth;') clientWidth = self.owner._run_js('return document.body.clientWidth;')
clientHeight = self.owner.run_js('return document.body.clientHeight;') clientHeight = self.owner._run_js('return document.body.clientHeight;')
self.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2) self.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2)
# 这样设计为了应付那些不随滚动条滚动的元素 # 这样设计为了应付那些不随滚动条滚动的元素
@ -258,7 +258,7 @@ class Actions:
data = self._get_key_data(key, 'keyDown') data = self._get_key_data(key, 'keyDown')
data['_ignore'] = AlertExistsError data['_ignore'] = AlertExistsError
self.owner.run_cdp('Input.dispatchKeyEvent', **data) self.owner._run_cdp('Input.dispatchKeyEvent', **data)
return self return self
def key_up(self, key): def key_up(self, key):
@ -273,7 +273,7 @@ class Actions:
data = self._get_key_data(key, 'keyUp') data = self._get_key_data(key, 'keyUp')
data['_ignore'] = AlertExistsError data['_ignore'] = AlertExistsError
self.owner.run_cdp('Input.dispatchKeyEvent', **data) self.owner._run_cdp('Input.dispatchKeyEvent', **data)
return self return self
def type(self, keys): def type(self, keys):
@ -292,7 +292,7 @@ class Actions:
self.key_up(character) self.key_up(character)
else: else:
self.owner.run_cdp('Input.dispatchKeyEvent', type='char', text=character) self.owner._run_cdp('Input.dispatchKeyEvent', type='char', text=character)
for m in modifiers: for m in modifiers:
self.key_up(m) self.key_up(m)
@ -339,6 +339,6 @@ class Actions:
def location_to_client(page, lx, ly): def location_to_client(page, lx, ly):
"""绝对坐标转换为视口坐标""" """绝对坐标转换为视口坐标"""
scroll_x = page.run_js('return document.documentElement.scrollLeft;') scroll_x = page._run_js('return document.documentElement.scrollLeft;')
scroll_y = page.run_js('return document.documentElement.scrollTop;') scroll_y = page._run_js('return document.documentElement.scrollTop;')
return lx - scroll_x, ly - scroll_y return lx - scroll_x, ly - scroll_y

View File

@ -87,7 +87,7 @@ class Clicker(object):
x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2 x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2
y = rect[0][0] + 3 y = rect[0][0] + 3
try: try:
r = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y), r = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y),
includeUserAgentShadowDOM=True, ignorePointerEventsNone=True) includeUserAgentShadowDOM=True, ignorePointerEventsNone=True)
if r['backendNodeId'] != self._ele._backend_id: if r['backendNodeId'] != self._ele._backend_id:
vx, vy = self._ele.rect.viewport_midpoint vx, vy = self._ele.rect.viewport_midpoint
@ -101,7 +101,7 @@ class Clicker(object):
return True return True
if by_js is not False: if by_js is not False:
self._ele.run_js('this.click();') self._ele._run_js('this.click();')
return True return True
if Settings.raise_when_click_failed: if Settings.raise_when_click_failed:
raise CanNotClickError raise CanNotClickError
@ -204,7 +204,7 @@ class Clicker(object):
:param count: 点击次数 :param count: 点击次数
:return: None :return: None
""" """
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x, self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mousePressed', x=client_x,
y=client_y, button=button, clickCount=count, _ignore=AlertExistsError) y=client_y, button=button, clickCount=count, _ignore=AlertExistsError)
self._ele.owner.run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x, self._ele.owner._run_cdp('Input.dispatchMouseEvent', type='mouseReleased', x=client_x,
y=client_y, button=button, _ignore=AlertExistsError) y=client_y, button=button, _ignore=AlertExistsError)

View File

@ -5,7 +5,7 @@
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause. @License : BSD 3-Clause.
""" """
from .._functions.web import set_browser_cookies, set_session_cookies from .._functions.cookies import set_tab_cookies, set_session_cookies, set_browser_cookies
class CookiesSetter(object): class CookiesSetter(object):
@ -20,7 +20,7 @@ class CookiesSetter(object):
:param cookies: cookies信息 :param cookies: cookies信息
:return: None :return: None
""" """
set_browser_cookies(self._owner, cookies) set_tab_cookies(self._owner, cookies)
def remove(self, name, url=None, domain=None, path=None): def remove(self, name, url=None, domain=None, path=None):
"""删除一个cookie """删除一个cookie
@ -39,11 +39,21 @@ class CookiesSetter(object):
d['url'] = self._owner.url d['url'] = self._owner.url
if path is not None: if path is not None:
d['path'] = path d['path'] = path
self._owner.run_cdp('Network.deleteCookies', **d) self._owner._run_cdp('Storage.deleteCookies', **d)
def clear(self): def clear(self):
"""清除cookies""" """清除cookies"""
self._owner.run_cdp('Network.clearBrowserCookies') self._owner._run_cdp('Storage.clearBrowserCookies')
class BrowserCookiesSetter(CookiesSetter):
def __call__(self, cookies):
"""设置一个或多个cookie
:param cookies: cookies信息
:return: None
"""
set_browser_cookies(self._owner, cookies)
class SessionCookiesSetter(object): class SessionCookiesSetter(object):

View File

@ -8,6 +8,7 @@
from http.cookiejar import Cookie, CookieJar from http.cookiejar import Cookie, CookieJar
from typing import Union from typing import Union
from .._base.browser import Browser
from .._pages.chromium_base import ChromiumBase from .._pages.chromium_base import ChromiumBase
from .._pages.chromium_tab import MixTab from .._pages.chromium_tab import MixTab
from .._pages.session_page import SessionPage from .._pages.session_page import SessionPage
@ -26,8 +27,14 @@ class CookiesSetter(object):
def clear(self) -> None: ... def clear(self) -> None: ...
class BrowserCookiesSetter(CookiesSetter):
_owner: Browser = ...
def __init__(self, page: Browser): ...
class SessionCookiesSetter(object): class SessionCookiesSetter(object):
_owner: SessionPage _owner: SessionPage = ...
def __init__(self, page: SessionPage): ... def __init__(self, page: SessionPage): ...
@ -39,7 +46,7 @@ class SessionCookiesSetter(object):
class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
_owner: Union[WebPage, MixTab] _owner: Union[WebPage, MixTab] = ...
def __init__(self, page: SessionPage): ... def __init__(self, page: SessionPage): ...

View File

@ -55,7 +55,7 @@ class DownloadManager(object):
if not self._running or tid == 'browser': if not self._running or tid == 'browser':
self._browser._driver.set_callback('Browser.downloadProgress', self._onDownloadProgress) self._browser._driver.set_callback('Browser.downloadProgress', self._onDownloadProgress)
self._browser._driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin) self._browser._driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin)
r = self._browser.run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path, r = self._browser._run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path,
behavior='allowAndName', eventsEnabled=True) behavior='allowAndName', eventsEnabled=True)
if 'error' in r: if 'error' in r:
print('浏览器版本太低无法使用下载管理功能。') print('浏览器版本太低无法使用下载管理功能。')
@ -124,7 +124,7 @@ class DownloadManager(object):
""" """
mission.state = 'canceled' mission.state = 'canceled'
try: try:
self._browser.run_cdp('Browser.cancelDownload', guid=mission.id) self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)
except: except:
pass pass
if mission.final_path: if mission.final_path:
@ -137,7 +137,7 @@ class DownloadManager(object):
""" """
mission.state = 'skipped' mission.state = 'skipped'
try: try:
self._browser.run_cdp('Browser.cancelDownload', guid=mission.id) self._browser._run_cdp('Browser.cancelDownload', guid=mission.id)
except: except:
pass pass

View File

@ -26,7 +26,7 @@ class Listener(object):
:param owner: ChromiumBase对象 :param owner: ChromiumBase对象
""" """
self._owner = owner self._owner = owner
self._address = owner.address self._address = owner.browser.address
self._target_id = owner._target_id self._target_id = owner._target_id
self._driver = None self._driver = None
self._running_requests = 0 self._running_requests = 0

View File

@ -18,7 +18,7 @@ class ElementRect(object):
def corners(self): def corners(self):
"""返回元素四个角坐标顺序左上、右上、右下、左下没有大小的元素抛出NoRectError""" """返回元素四个角坐标顺序左上、右上、右下、左下没有大小的元素抛出NoRectError"""
vr = self._get_viewport_rect('border') vr = self._get_viewport_rect('border')
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX'] sx = r['pageX']
sy = r['pageY'] sy = r['pageY']
return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)] return [(vr[0] + sx, vr[1] + sy), (vr[2] + sx, vr[3] + sy), (vr[4] + sx, vr[5] + sy), (vr[6] + sx, vr[7] + sy)]
@ -32,7 +32,7 @@ class ElementRect(object):
@property @property
def size(self): def size(self):
"""返回元素大小,格式(宽, 高)""" """返回元素大小,格式(宽, 高)"""
border = self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id, border = self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model']['border'] nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model']['border']
return border[2] - border[0], border[5] - border[1] return border[2] - border[0], border[5] - border[1]
@ -77,7 +77,7 @@ class ElementRect(object):
"""返回元素左上角在屏幕上坐标,左上角为(0, 0)""" """返回元素左上角在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.owner.rect.viewport_location vx, vy = self._ele.owner.rect.viewport_location
ex, ey = self.viewport_location ex, ey = self.viewport_location
pr = self._ele.owner.run_js('return window.devicePixelRatio;') pr = self._ele.owner._run_js('return window.devicePixelRatio;')
return (vx + ex) * pr, (ey + vy) * pr return (vx + ex) * pr, (ey + vy) * pr
@property @property
@ -85,7 +85,7 @@ class ElementRect(object):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)""" """返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.owner.rect.viewport_location vx, vy = self._ele.owner.rect.viewport_location
ex, ey = self.viewport_midpoint ex, ey = self.viewport_midpoint
pr = self._ele.owner.run_js('return window.devicePixelRatio;') pr = self._ele.owner._run_js('return window.devicePixelRatio;')
return (vx + ex) * pr, (ey + vy) * pr return (vx + ex) * pr, (ey + vy) * pr
@property @property
@ -93,7 +93,7 @@ class ElementRect(object):
"""返回元素中点在屏幕上坐标,左上角为(0, 0)""" """返回元素中点在屏幕上坐标,左上角为(0, 0)"""
vx, vy = self._ele.owner.rect.viewport_location vx, vy = self._ele.owner.rect.viewport_location
ex, ey = self.viewport_click_point ex, ey = self.viewport_click_point
pr = self._ele.owner.run_js('return window.devicePixelRatio;') pr = self._ele.owner._run_js('return window.devicePixelRatio;')
return (vx + ex) * pr, (ey + vy) * pr return (vx + ex) * pr, (ey + vy) * pr
def _get_viewport_rect(self, quad): def _get_viewport_rect(self, quad):
@ -101,13 +101,13 @@ class ElementRect(object):
:param quad: 方框类型margin border padding :param quad: 方框类型margin border padding
:return: 四个角坐标 :return: 四个角坐标
""" """
return self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id, return self._ele.owner._run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
# nodeId=self._ele._node_id, objectId=self._ele._obj_id # nodeId=self._ele._node_id, objectId=self._ele._obj_id
)['model'][quad] )['model'][quad]
def _get_page_coord(self, x, y): def _get_page_coord(self, x, y):
"""根据视口坐标获取绝对坐标""" """根据视口坐标获取绝对坐标"""
r = self._ele.owner.run_cdp_loaded('Page.getLayoutMetrics')['visualViewport'] r = self._ele.owner._run_cdp_loaded('Page.getLayoutMetrics')['visualViewport']
sx = r['pageX'] sx = r['pageX']
sy = r['pageY'] sy = r['pageY']
return x + sx, y + sy return x + sx, y + sy
@ -174,13 +174,13 @@ class TabRect(object):
@property @property
def viewport_size_with_scrollbar(self): def viewport_size_with_scrollbar(self):
"""返回视口宽高,包括滚动条,格式:(宽, 高)""" """返回视口宽高,包括滚动条,格式:(宽, 高)"""
r = self._owner.run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();') r = self._owner._run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();')
w, h = r.split(' ') w, h = r.split(' ')
return int(w), int(h) return int(w), int(h)
def _get_page_rect(self): def _get_page_rect(self):
"""获取页面范围信息""" """获取页面范围信息"""
return self._owner.run_cdp_loaded('Page.getLayoutMetrics') return self._owner._run_cdp_loaded('Page.getLayoutMetrics')
def _get_window_rect(self): def _get_window_rect(self):
"""获取窗口范围信息""" """获取窗口范围信息"""
@ -214,8 +214,8 @@ class FrameRect(object):
@property @property
def size(self): def size(self):
"""返回frame内页面尺寸格式(宽, 高)""" """返回frame内页面尺寸格式(宽, 高)"""
w = self._frame.doc_ele.run_js('return this.body.scrollWidth') w = self._frame.doc_ele._run_js('return this.body.scrollWidth')
h = self._frame.doc_ele.run_js('return this.body.scrollHeight') h = self._frame.doc_ele._run_js('return this.body.scrollHeight')
return w, h return w, h
@property @property

View File

@ -48,7 +48,7 @@ class Screencast(object):
if self._mode.startswith('frugal'): if self._mode.startswith('frugal'):
self._owner.driver.set_callback('Page.screencastFrame', self._onScreencastFrame) self._owner.driver.set_callback('Page.screencastFrame', self._onScreencastFrame)
self._owner.run_cdp('Page.startScreencast', everyNthFrame=1, quality=100) self._owner._run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)
elif not self._mode.startswith('js'): elif not self._mode.startswith('js'):
self._running = True self._running = True
@ -79,8 +79,8 @@ class Screencast(object):
} }
''' '''
print('请手动选择要录制的目标。') print('请手动选择要录制的目标。')
self._owner.run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;') self._owner._run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
self._owner.run_js(js) self._owner._run_js(js)
def stop(self, video_name=None): def stop(self, video_name=None):
"""停止录屏 """停止录屏
@ -93,19 +93,19 @@ class Screencast(object):
path = f'{self._path}{sep}{name}' path = f'{self._path}{sep}{name}'
if self._mode.startswith('js'): if self._mode.startswith('js'):
self._owner.run_js('mediaRecorder.stop();', as_expr=True) self._owner._run_js('mediaRecorder.stop();', as_expr=True)
while not self._owner.run_js('return DrissionPage_Screencast_blob_ok;'): while not self._owner._run_js('return DrissionPage_Screencast_blob_ok;'):
sleep(.1) sleep(.1)
blob = self._owner.run_js('return DrissionPage_Screencast_blob;') blob = self._owner._run_js('return DrissionPage_Screencast_blob;')
uuid = self._owner.run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid'] uuid = self._owner._run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid']
data = self._owner.run_cdp('IO.read', handle=f'blob:{uuid}')['data'] data = self._owner._run_cdp('IO.read', handle=f'blob:{uuid}')['data']
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(b64decode(data)) f.write(b64decode(data))
return path return path
if self._mode.startswith('frugal'): if self._mode.startswith('frugal'):
self._owner.driver.set_callback('Page.screencastFrame', None) self._owner.driver.set_callback('Page.screencastFrame', None)
self._owner.run_cdp('Page.stopScreencast') self._owner._run_cdp('Page.stopScreencast')
else: else:
self._enable = False self._enable = False
while self._running: while self._running:
@ -164,7 +164,7 @@ class Screencast(object):
path = self._tmp_path or self._path path = self._tmp_path or self._path
with open(f'{path}{sep}{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f: with open(f'{path}{sep}{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f:
f.write(b64decode(kwargs['data'])) f.write(b64decode(kwargs['data']))
self._owner.run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId']) self._owner._run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
class ScreencastMode(object): class ScreencastMode(object):

View File

@ -21,7 +21,7 @@ class Scroller(object):
def _run_js(self, js): def _run_js(self, js):
js = js.format(self.t1, self.t2, self.t2) js = js.format(self.t1, self.t2, self.t2)
self._driver.run_js(js) self._driver._run_js(js)
self._wait_scrolled() self._wait_scrolled()
def to_top(self): def to_top(self):
@ -88,14 +88,14 @@ class Scroller(object):
return return
owner = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver owner = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver
r = owner.run_cdp('Page.getLayoutMetrics') r = owner._run_cdp('Page.getLayoutMetrics')
x = r['layoutViewport']['pageX'] x = r['layoutViewport']['pageX']
y = r['layoutViewport']['pageY'] y = r['layoutViewport']['pageY']
end_time = perf_counter() + owner.timeout end_time = perf_counter() + owner.timeout
while perf_counter() < end_time: while perf_counter() < end_time:
sleep(.1) sleep(.1)
r = owner.run_cdp('Page.getLayoutMetrics') r = owner._run_cdp('Page.getLayoutMetrics')
x1 = r['layoutViewport']['pageX'] x1 = r['layoutViewport']['pageX']
y1 = r['layoutViewport']['pageY'] y1 = r['layoutViewport']['pageY']
@ -144,9 +144,9 @@ class PageScroller(Scroller):
:return: None :return: None
""" """
txt = 'true' if center else 'false' txt = 'true' if center else 'false'
ele.run_js(f'this.scrollIntoViewIfNeeded({txt});') ele._run_js(f'this.scrollIntoViewIfNeeded({txt});')
if center or (center is not False and ele.states.is_covered): if center or (center is not False and ele.states.is_covered):
ele.run_js('''function getWindowScrollTop() {let scroll_top = 0; ele._run_js('''function getWindowScrollTop() {let scroll_top = 0;
if (document.documentElement && document.documentElement.scrollTop) { if (document.documentElement && document.documentElement.scrollTop) {
scroll_top = document.documentElement.scrollTop; scroll_top = document.documentElement.scrollTop;
} else if (document.body) {scroll_top = document.body.scrollTop;} } else if (document.body) {scroll_top = document.body.scrollTop;}

View File

@ -45,7 +45,7 @@ class SelectElement(object):
"""返回第一个被选中的option元素 """返回第一个被选中的option元素
:return: ChromiumElement对象或None :return: ChromiumElement对象或None
""" """
ele = self._ele.run_js('return this.options[this.selectedIndex];') ele = self._ele._run_js('return this.options[this.selectedIndex];')
return ele return ele
@property @property
@ -69,7 +69,7 @@ class SelectElement(object):
for i in self.options: for i in self.options:
change = True change = True
mode = 'false' if i.states.is_selected else 'true' mode = 'false' if i.states.is_selected else 'true'
i.run_js(f'this.selected={mode};') i._run_js(f'this.selected={mode};')
if change: if change:
self._dispatch_change() self._dispatch_change()
@ -258,12 +258,12 @@ class SelectElement(object):
if not self.is_multi and len(option) > 1: if not self.is_multi and len(option) > 1:
option = option[:1] option = option[:1]
for o in option: for o in option:
o.run_js(f'this.selected={mode};') o._run_js(f'this.selected={mode};')
self._dispatch_change() self._dispatch_change()
else: else:
option.run_js(f'this.selected={mode};') option._run_js(f'this.selected={mode};')
self._dispatch_change() self._dispatch_change()
def _dispatch_change(self): def _dispatch_change(self):
"""触发修改动作""" """触发修改动作"""
self._ele.run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));') self._ele._run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));')

View File

@ -10,7 +10,7 @@ from time import sleep
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter
from .._functions.settings import Settings from .._functions.settings import Settings
from .._functions.tools import show_or_hide_browser from .._functions.tools import show_or_hide_browser
from .._functions.web import format_headers from .._functions.web import format_headers
@ -54,6 +54,13 @@ class BasePageSetter(object):
class BrowserBaseSetter(BasePageSetter): class BrowserBaseSetter(BasePageSetter):
"""Browser和ChromiumBase设置""" """Browser和ChromiumBase设置"""
def __init__(self, owner):
"""
:param owner: ChromiumBase对象
"""
super().__init__(owner)
self._cookies_setter = None
@property @property
def load_mode(self): def load_mode(self):
"""返回用于设置页面加载策略的对象""" """返回用于设置页面加载策略的对象"""
@ -78,8 +85,12 @@ class BrowserBaseSetter(BasePageSetter):
class BrowserSetter(BrowserBaseSetter): class BrowserSetter(BrowserBaseSetter):
def cookies(self, cookies): @property
pass # todo: 研究Storage.setCookies和Network.setCookies差别 def cookies(self):
"""返回用于设置cookies的对象"""
if self._cookies_setter is None:
self._cookies_setter = BrowserCookiesSetter(self._owner)
return self._cookies_setter
def tab_to_front(self, tab_or_id): def tab_to_front(self, tab_or_id):
"""激活标签页使其处于最前面 """激活标签页使其处于最前面
@ -128,12 +139,6 @@ class BrowserSetter(BrowserBaseSetter):
class ChromiumBaseSetter(BrowserBaseSetter): class ChromiumBaseSetter(BrowserBaseSetter):
def __init__(self, owner):
"""
:param owner: ChromiumBase对象
"""
super().__init__(owner)
self._cookies_setter = None
@property @property
def scroll(self): def scroll(self):
@ -156,7 +161,7 @@ class ChromiumBaseSetter(BrowserBaseSetter):
keys = {'userAgent': ua} keys = {'userAgent': ua}
if platform: if platform:
keys['platform'] = platform keys['platform'] = platform
self._owner.run_cdp('Emulation.setUserAgentOverride', **keys) self._owner._run_cdp('Emulation.setUserAgentOverride', **keys)
def session_storage(self, item, value): def session_storage(self, item, value):
"""设置或删除某项sessionStorage信息 """设置或删除某项sessionStorage信息
@ -164,15 +169,15 @@ class ChromiumBaseSetter(BrowserBaseSetter):
:param value: 项的值设置为False时删除该项 :param value: 项的值设置为False时删除该项
:return: None :return: None
""" """
self._owner.run_cdp_loaded('DOMStorage.enable') self._owner._run_cdp_loaded('DOMStorage.enable')
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
if value is False: if value is False:
self._owner.run_cdp('DOMStorage.removeDOMStorageItem', self._owner._run_cdp('DOMStorage.removeDOMStorageItem',
storageId={'storageKey': i, 'isLocalStorage': False}, key=item) storageId={'storageKey': i, 'isLocalStorage': False}, key=item)
else: else:
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False}, self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},
key=item, value=value) key=item, value=value)
self._owner.run_cdp_loaded('DOMStorage.disable') self._owner._run_cdp_loaded('DOMStorage.disable')
def local_storage(self, item, value): def local_storage(self, item, value):
"""设置或删除某项localStorage信息 """设置或删除某项localStorage信息
@ -180,15 +185,15 @@ class ChromiumBaseSetter(BrowserBaseSetter):
:param value: 项的值设置为False时删除该项 :param value: 项的值设置为False时删除该项
:return: None :return: None
""" """
self._owner.run_cdp_loaded('DOMStorage.enable') self._owner._run_cdp_loaded('DOMStorage.enable')
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
if value is False: if value is False:
self._owner.run_cdp('DOMStorage.removeDOMStorageItem', self._owner._run_cdp('DOMStorage.removeDOMStorageItem',
storageId={'storageKey': i, 'isLocalStorage': True}, key=item) storageId={'storageKey': i, 'isLocalStorage': True}, key=item)
else: else:
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True}, self._owner._run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},
key=item, value=value) key=item, value=value)
self._owner.run_cdp_loaded('DOMStorage.disable') self._owner._run_cdp_loaded('DOMStorage.disable')
def upload_files(self, files): def upload_files(self, files):
"""等待上传的文件路径 """等待上传的文件路径
@ -197,7 +202,7 @@ class ChromiumBaseSetter(BrowserBaseSetter):
""" """
if not self._owner._upload_list: if not self._owner._upload_list:
self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened) self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened)
self._owner.run_cdp('Page.setInterceptFileChooserDialog', enabled=True) self._owner._run_cdp('Page.setInterceptFileChooserDialog', enabled=True)
if isinstance(files, str): if isinstance(files, str):
files = files.split('\n') files = files.split('\n')
@ -210,8 +215,8 @@ class ChromiumBaseSetter(BrowserBaseSetter):
:param headers: dict格式的headers数据 :param headers: dict格式的headers数据
:return: None :return: None
""" """
self._owner.run_cdp('Network.enable') self._owner._run_cdp('Network.enable')
self._owner.run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers)) self._owner._run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers))
def auto_handle_alert(self, on_off=True, accept=True): def auto_handle_alert(self, on_off=True, accept=True):
"""设置是否启用自动处理弹窗 """设置是否启用自动处理弹窗
@ -232,8 +237,8 @@ class ChromiumBaseSetter(BrowserBaseSetter):
urls = (urls,) urls = (urls,)
if not isinstance(urls, (list, tuple)): if not isinstance(urls, (list, tuple)):
raise TypeError('urls需传入str、list或tuple类型。') raise TypeError('urls需传入str、list或tuple类型。')
self._owner.run_cdp('Network.enable') self._owner._run_cdp('Network.enable')
self._owner.run_cdp('Network.setBlockedURLs', urls=urls) self._owner._run_cdp('Network.setBlockedURLs', urls=urls)
class TabSetter(ChromiumBaseSetter): class TabSetter(ChromiumBaseSetter):
@ -522,11 +527,11 @@ class ChromiumElementSetter(object):
:return: None :return: None
""" """
try: try:
self._ele.owner.run_cdp('DOM.setAttributeValue', self._ele.owner._run_cdp('DOM.setAttributeValue',
nodeId=self._ele._node_id, name=name, value=str(value)) nodeId=self._ele._node_id, name=name, value=str(value))
except ElementLostError: except ElementLostError:
self._ele._refresh_id() self._ele._refresh_id()
self._ele.owner.run_cdp('DOM.setAttributeValue', self._ele.owner._run_cdp('DOM.setAttributeValue',
nodeId=self._ele._node_id, name=name, value=str(value)) nodeId=self._ele._node_id, name=name, value=str(value))
def property(self, name, value): def property(self, name, value):
@ -536,7 +541,7 @@ class ChromiumElementSetter(object):
:return: None :return: None
""" """
value = value.replace('"', r'\"') value = value.replace('"', r'\"')
self._ele.run_js(f'this.{name}="{value}";') self._ele._run_js(f'this.{name}="{value}";')
def style(self, name, value): def style(self, name, value):
"""设置元素style样式 """设置元素style样式
@ -545,7 +550,7 @@ class ChromiumElementSetter(object):
:return: None :return: None
""" """
try: try:
self._ele.run_js(f'this.style.{name}="{value}";') self._ele._run_js(f'this.style.{name}="{value}";')
except JavaScriptError: except JavaScriptError:
raise ValueError(f'设置失败,请检查属性名{name}') raise ValueError(f'设置失败,请检查属性名{name}')
@ -629,7 +634,7 @@ class PageScrollSetter(object):
if not isinstance(on_off, bool): if not isinstance(on_off, bool):
raise TypeError('on_off必须为bool。') raise TypeError('on_off必须为bool。')
b = 'smooth' if on_off else 'auto' b = 'smooth' if on_off else 'auto'
self._scroll._driver.run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");') self._scroll._driver._run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");')
self._scroll._wait_complete = on_off self._scroll._wait_complete = on_off
@ -703,7 +708,7 @@ class WindowSetter(object):
"""获取窗口位置及大小信息""" """获取窗口位置及大小信息"""
for _ in range(50): for _ in range(50):
try: try:
return self._owner.run_cdp('Browser.getWindowForTarget') return self._owner._run_cdp('Browser.getWindowForTarget')
except: except:
sleep(.1) sleep(.1)
@ -713,7 +718,7 @@ class WindowSetter(object):
:return: None :return: None
""" """
try: try:
self._owner.run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds) self._owner._run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)
except: except:
raise RuntimeError('浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。') raise RuntimeError('浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。')

View File

@ -11,7 +11,7 @@ from typing import Union, Tuple, Literal, Any, Optional
from requests.adapters import HTTPAdapter from requests.adapters import HTTPAdapter
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter, BrowserCookiesSetter
from .scroller import PageScroller from .scroller import PageScroller
from .._base.base import BasePage from .._base.base import BasePage
from .._base.browser import Browser from .._base.browser import Browser
@ -40,6 +40,7 @@ class BasePageSetter(object):
class BrowserBaseSetter(BasePageSetter): class BrowserBaseSetter(BasePageSetter):
_cookies_setter: Optional[CookiesSetter] = ...
@property @property
def load_mode(self) -> LoadMode: ... def load_mode(self) -> LoadMode: ...
@ -49,10 +50,12 @@ class BrowserBaseSetter(BasePageSetter):
class BrowserSetter(BasePageSetter): class BrowserSetter(BasePageSetter):
_owner: Browser = ... _owner: Browser = ...
_cookies_setter: BrowserCookiesSetter = ...
def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ... def tab_to_front(self, tab_or_id: Union[str, ChromiumTab]) -> None: ...
def cookies(self, cookies): ... @property
def cookies(self) -> BrowserCookiesSetter: ...
def auto_handle_alert(self, on_off: bool = True, accept: bool = True): ... def auto_handle_alert(self, on_off: bool = True, accept: bool = True): ...
@ -64,9 +67,10 @@ class BrowserSetter(BasePageSetter):
class ChromiumBaseSetter(BasePageSetter): class ChromiumBaseSetter(BasePageSetter):
def __init__(self, owner): _owner: ChromiumBase = ...
self._owner: ChromiumBase = ... _cookies_setter: CookiesSetter = ...
self._cookies_setter: CookiesSetter = ...
def __init__(self, owner): ...
@property @property
def load_mode(self) -> LoadMode: ... def load_mode(self) -> LoadMode: ...

View File

@ -19,30 +19,30 @@ class ElementStates(object):
@property @property
def is_selected(self): def is_selected(self):
"""返回列表元素是否被选择""" """返回列表元素是否被选择"""
return self._ele.run_js('return this.selected;') return self._ele._run_js('return this.selected;')
@property @property
def is_checked(self): def is_checked(self):
"""返回元素是否被选择""" """返回元素是否被选择"""
return self._ele.run_js('return this.checked;') return self._ele._run_js('return this.checked;')
@property @property
def is_displayed(self): def is_displayed(self):
"""返回元素是否显示""" """返回元素是否显示"""
return not (self._ele.style('visibility') == 'hidden' or return not (self._ele.style('visibility') == 'hidden' or
self._ele.run_js('return this.offsetParent === null;') self._ele._run_js('return this.offsetParent === null;')
or self._ele.style('display') == 'none' or self._ele.property('hidden')) or self._ele.style('display') == 'none' or self._ele.property('hidden'))
@property @property
def is_enabled(self): def is_enabled(self):
"""返回元素是否可用""" """返回元素是否可用"""
return not self._ele.run_js('return this.disabled;') return not self._ele._run_js('return this.disabled;')
@property @property
def is_alive(self): def is_alive(self):
"""返回元素是否仍在DOM中""" """返回元素是否仍在DOM中"""
try: try:
return self._ele.owner.run_cdp('DOM.describeNode', return self._ele.owner._run_cdp('DOM.describeNode',
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
except ElementLostError: except ElementLostError:
return False return False
@ -66,7 +66,7 @@ class ElementStates(object):
"""返回元素是否被覆盖与是否在视口中无关如被覆盖返回覆盖元素的backend id否则返回False""" """返回元素是否被覆盖与是否在视口中无关如被覆盖返回覆盖元素的backend id否则返回False"""
lx, ly = self._ele.rect.click_point lx, ly = self._ele.rect.click_point
try: try:
bid = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(lx), y=int(ly)).get('backendNodeId') bid = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(lx), y=int(ly)).get('backendNodeId')
return bid if bid != self._ele._backend_id else False return bid if bid != self._ele._backend_id else False
except CDPError: except CDPError:
return False return False
@ -95,13 +95,13 @@ class ShadowRootStates(object):
@property @property
def is_enabled(self): def is_enabled(self):
"""返回元素是否可用""" """返回元素是否可用"""
return not self._ele.run_js('return this.disabled;') return not self._ele._run_js('return this.disabled;')
@property @property
def is_alive(self): def is_alive(self):
"""返回元素是否仍在DOM中""" """返回元素是否仍在DOM中"""
try: try:
return self._ele.owner.run_cdp('DOM.describeNode', return self._ele.owner._run_cdp('DOM.describeNode',
backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0 backendNodeId=self._ele._backend_id)['node']['nodeId'] != 0
except ElementLostError: except ElementLostError:
return False return False
@ -125,7 +125,7 @@ class PageStates(object):
def is_alive(self): def is_alive(self):
"""返回页面对象是否仍然可用""" """返回页面对象是否仍然可用"""
try: try:
self._owner.run_cdp('Page.getLayoutMetrics') self._owner._run_cdp('Page.getLayoutMetrics')
return True return True
except PageDisconnectedError: except PageDisconnectedError:
return False return False
@ -157,7 +157,7 @@ class FrameStates(object):
def is_alive(self): def is_alive(self):
"""返回frame元素是否可用且里面仍挂载有frame""" """返回frame元素是否可用且里面仍挂载有frame"""
try: try:
node = self._frame._target_page.run_cdp('DOM.describeNode', node = self._frame._target_page._run_cdp('DOM.describeNode',
backendNodeId=self._frame._frame_ele._backend_id)['node'] backendNodeId=self._frame._frame_ele._backend_id)['node']
except (ElementLostError, PageDisconnectedError): except (ElementLostError, PageDisconnectedError):
return False return False
@ -172,7 +172,7 @@ class FrameStates(object):
def is_displayed(self): def is_displayed(self):
"""返回iframe是否显示""" """返回iframe是否显示"""
return not (self._frame.frame_ele.style('visibility') == 'hidden' return not (self._frame.frame_ele.style('visibility') == 'hidden'
or self._frame.frame_ele.run_js('return this.offsetParent === null;') or self._frame.frame_ele._run_js('return this.offsetParent === null;')
or self._frame.frame_ele.style('display') == 'none') or self._frame.frame_ele.style('display') == 'none')
@property @property