From 6d552330cdd3ba933ca155651c50e1b18c59ef58 Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 1 Jul 2024 00:35:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=9F=9Fiframe?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=88=B0=E5=90=8C=E5=9F=9F=E6=97=B6=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9B=E5=85=B6=E5=AE=83=E4=BF=AE=E6=94=B9=EF=BC=8C?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 10 +- DrissionPage/_base/base.pyi | 5 +- DrissionPage/_base/browser.py | 10 + DrissionPage/_base/browser.pyi | 3 + DrissionPage/_base/driver.py | 12 +- DrissionPage/_configs/session_options.py | 3 +- DrissionPage/_elements/chromium_element.py | 154 +++++++------ DrissionPage/_elements/chromium_element.pyi | 4 + DrissionPage/_elements/session_element.py | 4 +- DrissionPage/_functions/cookies.py | 233 ++++++++++++++++++++ DrissionPage/_functions/cookies.pyi | 44 ++++ DrissionPage/_functions/keys.py | 10 +- DrissionPage/_functions/tools.py | 14 +- DrissionPage/_functions/tools.pyi | 2 +- DrissionPage/_functions/web.py | 209 +----------------- DrissionPage/_functions/web.pyi | 19 -- DrissionPage/_pages/chromium_base.py | 135 ++++++++---- DrissionPage/_pages/chromium_base.pyi | 12 +- DrissionPage/_pages/chromium_frame.py | 48 ++-- DrissionPage/_pages/chromium_frame.pyi | 6 + DrissionPage/_pages/chromium_page.py | 3 +- DrissionPage/_pages/chromium_tab.py | 14 +- DrissionPage/_pages/chromium_tab.pyi | 4 +- DrissionPage/_pages/session_page.py | 23 +- DrissionPage/_pages/session_page.pyi | 6 +- DrissionPage/_pages/web_page.py | 6 +- DrissionPage/_units/actions.py | 16 +- DrissionPage/_units/clicker.py | 8 +- DrissionPage/_units/cookies_setter.py | 18 +- DrissionPage/_units/cookies_setter.pyi | 11 +- DrissionPage/_units/downloader.py | 6 +- DrissionPage/_units/listener.py | 2 +- DrissionPage/_units/rect.py | 22 +- DrissionPage/_units/screencast.py | 20 +- DrissionPage/_units/scroller.py | 10 +- DrissionPage/_units/selector.py | 10 +- DrissionPage/_units/setter.py | 69 +++--- DrissionPage/_units/setter.pyi | 14 +- DrissionPage/_units/states.py | 22 +- 39 files changed, 708 insertions(+), 513 deletions(-) create mode 100644 DrissionPage/_functions/cookies.py create mode 100644 DrissionPage/_functions/cookies.pyi diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index a3e915e..ccd0e5b 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -12,10 +12,10 @@ from urllib.parse import quote 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 .._functions.locator import get_loc +from .._functions.settings import Settings +from .._functions.web import format_html from ..errors import ElementNotFoundError @@ -414,10 +414,6 @@ class BasePage(BaseParser): def user_agent(self): return - @abstractmethod - def cookies(self, as_dict=False, all_info=False): - return {} - @abstractmethod def get(self, url, show_errmsg=False, retry=None, interval=None): pass diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index e435c16..27fcb1f 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -205,7 +205,7 @@ class BasePage(BaseParser): self._DownloadKit: DownloadKit = ... self._none_ele_return_value: bool = ... self._none_ele_value: Any = ... - self._page: Union[ChromiumPage, SessionPage, WebPage]=... + self._page: Union[ChromiumPage, SessionPage, WebPage] = ... @property def title(self) -> Union[str, None]: ... @@ -234,9 +234,6 @@ class BasePage(BaseParser): @property def user_agent(self) -> str: ... - @abstractmethod - def cookies(self, as_dict: bool = False, all_info: bool = False) -> Union[list, dict]: ... - @abstractmethod def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py index 2c43b70..93b85ae 100644 --- a/DrissionPage/_base/browser.py +++ b/DrissionPage/_base/browser.py @@ -17,6 +17,7 @@ from .driver import BrowserDriver, Driver from .._configs.chromium_options import ChromiumOptions from .._configs.session_options import SessionOptions from .._functions.browser import connect_browser +from .._functions.cookies import CookiesList from .._functions.settings import Settings from .._functions.tools import PortFinder from .._functions.tools import raise_error @@ -161,6 +162,15 @@ class Browser(object): 当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id""" 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): """新建一个标签页 :param url: 新标签页跳转到的网址 diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi index 1e8e000..fbe2d65 100644 --- a/DrissionPage/_base/browser.pyi +++ b/DrissionPage/_base/browser.pyi @@ -11,6 +11,7 @@ from typing import List, Optional, Set, Dict, Union, Tuple from .driver import BrowserDriver, Driver from .._configs.chromium_options import ChromiumOptions from .._configs.session_options import SessionOptions +from .._functions.web import CookiesList from .._pages.chromium_base import Timeout from .._pages.chromium_tab import ChromiumTab, MixTab from .._units.downloader import DownloadManager @@ -82,6 +83,8 @@ class Browser(object): @property def latest_tab(self) -> Union[ChromiumTab, str]: ... + def cookies(self, all_info: bool = False) -> CookiesList: ... + def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], Tuple[Union[str, ChromiumTab]]] = None, diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 3044a87..8f421f5 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -30,6 +30,7 @@ class Driver(object): self.address = address self.type = tab_type self.owner = owner + # self._debug = True # self._debug = False self.alert_flag = False # 标记alert出现,跳过一条请求后复原 @@ -158,7 +159,7 @@ class Driver(object): self.event_queue.task_done() 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) try: function(**kwargs) @@ -227,6 +228,15 @@ class Driver(object): self._ws = None # 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(): # event = self.event_queue.get_nowait() # function = self.event_handlers.get(event['method']) diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index 25ea8cd..9794395 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -12,7 +12,8 @@ from requests import Session from requests.structures import CaseInsensitiveDict 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): diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index da78f5d..9c0b5cd 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -16,9 +16,9 @@ from DataRecorder.tools import get_usable_path, make_valid_name from .none_element import NoneElement from .session_element import make_session_ele from .._base.base import DrissionElement, BaseElement +from .._functions.elements import ChromiumElementsList from .._functions.keys import input_text_or_keys from .._functions.locator import get_loc, locator_to_tuple -from .._functions.elements import ChromiumElementsList from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll, get_blob from .._units.clicker import Clicker from .._units.rect import ElementRect @@ -95,29 +95,29 @@ class ChromiumElement(DrissionElement): def tag(self): """返回元素tag""" if self._tag is None: - self._tag = self.owner.run_cdp('DOM.describeNode', - backendNodeId=self._backend_id)['node']['localName'].lower() + self._tag = self.owner._run_cdp('DOM.describeNode', + backendNodeId=self._backend_id)['node']['localName'].lower() return self._tag @property def html(self): """返回元素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 def inner_html(self): """返回元素innerHTML文本""" - return self.run_js('return this.innerHTML;') + return self._run_js('return this.innerHTML;') @property def attrs(self): """返回元素所有attribute属性""" 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)} except ElementLostError: 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)} except CDPError: # 文档根元素不能调用此方法 return {} @@ -164,7 +164,7 @@ class ChromiumElement(DrissionElement): @property def shadow_root(self): """返回当前元素的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): return None @@ -225,8 +225,8 @@ class ChromiumElement(DrissionElement): elif not is_checked and not uncheck: js = 'this.checked=true' if js: - self.run_js(js) - self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') + self._run_js(js) + self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') else: if (is_checked and uncheck) or (not is_checked and not uncheck): @@ -360,9 +360,9 @@ class ChromiumElement(DrissionElement): x, y = self.rect.location try: return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x, - y=y + offset_y, includeUserAgentShadowDOM=True, - ignorePointerEventsNone=False)['backendNodeId']) + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x + offset_x, + y=y + offset_y, includeUserAgentShadowDOM=True, + ignorePointerEventsNone=False)['backendNodeId']) except CDPError: return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y}) @@ -439,8 +439,8 @@ class ChromiumElement(DrissionElement): cdp_data[variable] += locator try: return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', - **cdp_data)['backendNodeId']) + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', + **cdp_data)['backendNodeId']) except CDPError: 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: cdp_data[variable] += value 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: continue else: @@ -505,7 +505,7 @@ class ChromiumElement(DrissionElement): :param name: 属性名 :return: None """ - self.run_js(f'this.removeAttribute("{name}");') + self._run_js(f'this.removeAttribute("{name}");') def property(self, name): """获取一个property属性值 @@ -513,12 +513,22 @@ class ChromiumElement(DrissionElement): :return: 属性值文本 """ 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 except: return 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代码 :param script: js文本,文本中用this表示本元素 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... @@ -588,7 +598,7 @@ class ChromiumElement(DrissionElement): """ if 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): """返回元素src资源,base64的可转为bytes返回,其它返回str @@ -602,7 +612,7 @@ class ChromiumElement(DrissionElement): '&& this.naturalWidth > 0 && typeof this.naturalHeight != "undefined" ' '&& this.naturalHeight > 0') 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) src = self.attr('src') @@ -631,11 +641,11 @@ class ChromiumElement(DrissionElement): if not src: 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 try: - result = self.owner.run_cdp('Page.getResourceContent', frameId=frame, url=src) + result = self.owner._run_cdp('Page.getResourceContent', frameId=frame, url=src) break except CDPError: pass @@ -698,7 +708,7 @@ class ChromiumElement(DrissionElement): js = ('return this.complete && typeof this.naturalWidth != "undefined" && this.naturalWidth > 0 ' '&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0') 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) if scroll_to_center: self.scroll.to_see(center=True) @@ -729,7 +739,7 @@ class ChromiumElement(DrissionElement): if isinstance(vals, (list, tuple)): vals = ''.join([str(i) for i in 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 self.wait.clickable(wait_moved=False, timeout=.5) @@ -746,8 +756,8 @@ class ChromiumElement(DrissionElement): :return: None """ if by_js: - self.run_js("this.value='';") - self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') + self._run_js("this.value='';") + self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') return self._input_focus() @@ -756,16 +766,16 @@ class ChromiumElement(DrissionElement): def _input_focus(self): """输入前使元素获取焦点""" try: - self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) + self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id) except Exception: self.click(by_js=None) def focus(self): """使元素获取焦点""" try: - self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) + self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id) except Exception: - self.run_js('this.focus();') + self._run_js('this.focus();') def hover(self, offset_x=None, offset_y=None): """鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入x或y值时悬停在元素中点 @@ -775,7 +785,7 @@ class ChromiumElement(DrissionElement): """ self.owner.scroll.to_see(self) 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): """拖拽当前元素到相对位置 @@ -808,9 +818,9 @@ class ChromiumElement(DrissionElement): :return: js中的object 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: - 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): """根据传入object id或backend id获取cdp中的node id @@ -819,9 +829,9 @@ class ChromiumElement(DrissionElement): :return: cdp中的node 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: - 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'] return n['nodeId'] @@ -830,7 +840,7 @@ class ChromiumElement(DrissionElement): :param node_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'] return n['backendNodeId'] @@ -876,7 +886,7 @@ class ChromiumElement(DrissionElement): } return e(this);} ''' - t = self.run_js(js) + t = self._run_js(js) return f'{t}' if mode == 'css' else t def _set_file_input(self, files): @@ -887,7 +897,7 @@ class ChromiumElement(DrissionElement): if isinstance(files, str): files = files.split('\n') 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): @@ -942,7 +952,7 @@ class ShadowRoot(BaseElement): @property def inner_html(self): """返回内部的html文本""" - return self.run_js('return this.innerHTML;') + return self._run_js('return this.innerHTML;') @property def states(self): @@ -952,6 +962,16 @@ class ShadowRoot(BaseElement): return self._states 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代码 :param script: js文本 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... @@ -1163,14 +1183,14 @@ class ShadowRoot(BaseElement): def do_find(): if loc[0] == 'css selector': 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: r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False) return None if r is False else r else: - nod_ids = self.owner.run_cdp('DOM.querySelectorAll', - nodeId=self._node_id, selector=loc[1])['nodeId'] + nod_ids = self.owner._run_cdp('DOM.querySelectorAll', + nodeId=self._node_id, selector=loc[1])['nodeId'] r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False) return None if r is False else r @@ -1182,14 +1202,14 @@ class ShadowRoot(BaseElement): css = [i.css_path[61:] for i in eles] if index is not None: try: - node_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, - selector=css[index - 1])['nodeId'] + node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, + selector=css[index - 1])['nodeId'] except IndexError: return None r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False) return None if r is False else r else: - node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] + node_ids = [self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] for i in css] if 0 in node_ids: return None @@ -1209,15 +1229,15 @@ class ShadowRoot(BaseElement): def _get_node_id(self, obj_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): """返回元素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): """返回元素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() return r['backendNodeId'] @@ -1269,15 +1289,15 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True): ele.owner.wait.doc_loaded() def do_find(): - res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) if res['result']['type'] == 'string': return res['result']['value'] if 'exceptionDetails' in res: if 'The result is not a node set' in res['result']['description']: 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, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) return res['result']['value'] else: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1290,8 +1310,8 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True): return None if r is False else r else: - res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], - ownProperties=True)['result'][:-1] + res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], + ownProperties=True)['result'][:-1] if index is None: r = ChromiumElementsList(page=ele.owner) for i in res: @@ -1341,8 +1361,8 @@ def find_by_css(ele, selector, index, timeout): ele.owner.wait.doc_loaded() def do_find(): - res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) if 'exceptionDetails' in res: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1354,9 +1374,9 @@ def find_by_css(ele, selector, index, timeout): return None if r is False else r else: - obj_ids = [i['value']['objectId'] for i in ele.owner.run_cdp('Runtime.getProperties', - objectId=res['result']['objectId'], - ownProperties=True)['result']] + obj_ids = [i['value']['objectId'] for i in ele.owner._run_cdp('Runtime.getProperties', + objectId=res['result']['objectId'], + ownProperties=True)['result']] r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True) return None if r is False else r @@ -1535,16 +1555,16 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None): end_time = perf_counter() + timeout try: if as_expr: - res = page.run_cdp('Runtime.evaluate', expression=script, returnByValue=False, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + res = page._run_cdp('Runtime.evaluate', expression=script, returnByValue=False, + awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) else: args = args or () if not is_js_func(script): script = f'function(){{{script}}}' - res = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id, - arguments=[convert_argument(arg) for arg in args], returnByValue=False, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + res = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id, + arguments=[convert_argument(arg) for arg in args], returnByValue=False, + awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) except TimeoutError: raise TimeoutError(f'执行js超时(等待{timeout}秒)。') except ContextLostError: @@ -1591,7 +1611,7 @@ def parse_js_result(page, ele, result, end_time): return r 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()] elif 'objectId' in result: @@ -1599,9 +1619,9 @@ def parse_js_result(page, ele, result, end_time): if timeout < 0: return js = 'function(){return JSON.stringify(this);}' - r = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'], - returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, - _timeout=timeout) + r = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'], + returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, + _timeout=timeout) return loads(parse_js_result(page, ele, r['result'], end_time)) else: diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index a629c85..648c68e 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -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_async_js(self, script: str, *args, as_expr: bool = False) -> None: ... 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_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: ... diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index 888269a..f0d35a1 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -349,11 +349,11 @@ def make_session_ele(html_or_ele, loc=None, index=1, method=None): xpath = html_or_ele.xpath # ChromiumElement,兼容传入的元素在iframe内的情况 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 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: html = html_or_ele.owner.html html_or_ele = fromstring(html) diff --git a/DrissionPage/_functions/cookies.py b/DrissionPage/_functions/cookies.py new file mode 100644 index 0000000..fad305d --- /dev/null +++ b/DrissionPage/_functions/cookies.py @@ -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) diff --git a/DrissionPage/_functions/cookies.pyi b/DrissionPage/_functions/cookies.pyi new file mode 100644 index 0000000..fca7a77 --- /dev/null +++ b/DrissionPage/_functions/cookies.pyi @@ -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: ... diff --git a/DrissionPage/_functions/keys.py b/DrissionPage/_functions/keys.py index d55267c..edde4d9 100644 --- a/DrissionPage/_functions/keys.py +++ b/DrissionPage/_functions/keys.py @@ -427,12 +427,12 @@ def send_key(page, modifier, key): 'isKeypad': description['location'] == 3, '_ignore': AlertExistsError} - page.run_cdp('Input.dispatchKeyEvent', **data) + page._run_cdp('Input.dispatchKeyEvent', **data) data['type'] = 'keyUp' - page.run_cdp('Input.dispatchKeyEvent', **data) + page._run_cdp('Input.dispatchKeyEvent', **data) 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): @@ -451,7 +451,7 @@ def input_text_or_keys(page, text_or_keys): return 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') else: - page.run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError) + page._run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError) diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 4f6b13a..84a2e1f 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -198,10 +198,11 @@ def configs_to_here(save_name=None): om.save(save_name) -def raise_error(result, ignore=None): +def raise_error(result, ignore=None, user=False): """抛出error对应报错 :param result: 包含error的dict :param ignore: 要忽略的错误 + :param user: 是否用户调用的 :return: None """ error = result['error'] @@ -227,13 +228,18 @@ def raise_error(result, ignore=None): elif error == 'Given expression does not evaluate to a function': r = JavaScriptError(f'传入的js无法解析成函数:\n{result["args"]["functionDeclaration"]}') elif error.endswith("' wasn't found"): - r = RuntimeError(f'你的浏览器可能太旧。\n方法:{result["method"]}\n参数:{result["args"]}') - elif result['type'] in ('call_method_error', 'timeout'): + r = RuntimeError(f'没有找到对应功能,方法错误或你的浏览器太旧。\n方法:{result["method"]}\n参数:{result["args"]}') + 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__ txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \ f'版本:{__version__}\n出现这个错误可能意味着程序有bug,请把错误信息和重现方法' \ '告知作者,谢谢。\n报告网站:https://gitee.com/g1879/DrissionPage/issues' - r = TimeoutError(txt) if result['type'] == 'timeout' else CDPError(txt) + r = CDPError(txt) else: r = RuntimeError(result) diff --git a/DrissionPage/_functions/tools.pyi b/DrissionPage/_functions/tools.pyi index 4fa536b..5e344f3 100644 --- a/DrissionPage/_functions/tools.pyi +++ b/DrissionPage/_functions/tools.pyi @@ -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 raise_error(result: dict, ignore=None) -> None: ... +def raise_error(result: dict, ignore=None, user: bool = False) -> None: ... diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index a9ee8e5..7c1854d 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -5,16 +5,13 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from datetime import datetime from html import unescape -from http.cookiejar import Cookie, CookieJar from os.path import sep from pathlib import Path from re import sub from urllib.parse import urlparse, urljoin, urlunparse from DataRecorder.tools import make_valid_name -from tldextract import extract def get_ele_txt(e): @@ -106,7 +103,7 @@ def location_in_viewport(page, loc_x, loc_y): const vHeight = document.documentElement.clientHeight; if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}} return true;}}''' - return page.run_js(js) + return page._run_js(js) 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 ly = loc_y + offset_y if offset_y else cp_y if not location_in_viewport(ele.owner, lx, ly): - clientWidth = ele.owner.run_js('return document.body.clientWidth;') - clientHeight = ele.owner.run_js('return document.body.clientHeight;') + clientWidth = ele.owner._run_js('return document.body.clientWidth;') + clientHeight = ele.owner._run_js('return document.body.clientHeight;') ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2) cl_x, cl_y = ele.rect.viewport_location ccp_x, ccp_y = ele.rect.viewport_click_point @@ -174,188 +171,6 @@ def is_js_func(func): 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): """获取知道blob资源 :param page: 资源所在页面对象 @@ -381,7 +196,7 @@ def get_blob(page, url, as_bytes=True): } """ try: - result = page.run_js(js, url) + result = page._run_js(js, url) except: raise RuntimeError('无法获取该资源。') if as_bytes: @@ -429,7 +244,7 @@ def get_mhtml(page, path=None, name=None): :param name: 文件名,为None且path不为None时用title属性值 :return: mhtml文本 """ - r = page.run_cdp('Page.captureSnapshot')['data'] + r = page._run_cdp('Page.captureSnapshot')['data'] if path is None and name is None: return r @@ -455,7 +270,7 @@ def get_pdf(page, path=None, name=None, kwargs=None): if 'printBackground' not in kwargs: kwargs['printBackground'] = True try: - r = page.run_cdp('Page.printToPDF', **kwargs)['data'] + r = page._run_cdp('Page.printToPDF', **kwargs)['data'] except: raise RuntimeError('保存失败,可能浏览器版本不支持。') from base64 import b64decode @@ -539,15 +354,3 @@ def format_headers(txt): name, value = header.split(': ', maxsplit=1) headers[name] = value 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) diff --git a/DrissionPage/_functions/web.pyi b/DrissionPage/_functions/web.pyi index 3fdca32..fdd2d76 100644 --- a/DrissionPage/_functions/web.pyi +++ b/DrissionPage/_functions/web.pyi @@ -5,13 +5,9 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @License : BSD 3-Clause. """ -from http.cookiejar import Cookie from pathlib import Path from typing import Union, Optional -from requests import Session -from requests.cookies import RequestsCookieJar - from .._base.base import DrissionElement, BaseParser from .._elements.chromium_element import ChromiumElement 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 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: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index bc7abf2..285bbef 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -18,6 +18,7 @@ from .._base.base import BasePage from .._elements.chromium_element import run_js, make_chromium_eles from .._elements.none_element import NoneElement from .._elements.session_element import make_session_ele +from .._functions.cookies import CookiesList from .._functions.locator import get_loc, is_loc from .._functions.settings import Settings from .._functions.tools import raise_error @@ -120,7 +121,7 @@ class ChromiumBase(BasePage): self._driver.run('Page.enable') 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)): self.browser._frames[i] = self.tab_id if not hasattr(self, '_frame_id'): @@ -146,10 +147,10 @@ class ChromiumBase(BasePage): end_time = perf_counter() + timeout while perf_counter() < end_time: 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 = 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'] result = True break @@ -166,7 +167,7 @@ class ChromiumBase(BasePage): result = False if result: - r = self.run_cdp('Page.getFrameTree') + r = self._run_cdp('Page.getFrameTree') for i in findall(r"'id': '(.*?)'", str(r)): self.browser._frames[i] = self.tab_id @@ -203,7 +204,7 @@ class ChromiumBase(BasePage): def _onDomContentEventFired(self, **kwargs): """在页面刷新、变化后重新读取页面内容""" 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): self._doc_got = True self._ready_state = 'interactive' @@ -228,10 +229,10 @@ class ChromiumBase(BasePage): if 'backendNodeId' not in kwargs: raise TypeError('该输入框无法接管,请改用对元素输入路径的方法设置。') 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.run_cdp('Page.setInterceptFileChooserDialog', enabled=False) + self._run_cdp('Page.setInterceptFileChooserDialog', enabled=False) self._upload_list = None def __call__(self, locator, index=1, timeout=None): @@ -333,12 +334,12 @@ class ChromiumBase(BasePage): @property def title(self): """返回当前页面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 def url(self): """返回当前页面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 def _browser_url(self): @@ -349,7 +350,7 @@ class ChromiumBase(BasePage): def html(self): """返回当前页面html文本""" 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 def json(self): @@ -372,7 +373,7 @@ class ChromiumBase(BasePage): @property def active_ele(self): """返回当前焦点所在元素""" - return self.run_js_loaded('return document.activeElement;') + return self._run_js_loaded('return document.activeElement;') @property def load_mode(self): @@ -382,7 +383,7 @@ class ChromiumBase(BasePage): @property def user_agent(self): """返回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 def upload_list(self): @@ -393,7 +394,7 @@ class ChromiumBase(BasePage): def _js_ready_state(self): """返回js获取的ready state信息""" 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: return None except TimeoutError: @@ -405,9 +406,8 @@ class ChromiumBase(BasePage): :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) + return r if __ERROR__ not in r else raise_error(r, user=True) def run_cdp_loaded(self, cmd, **cmd_args): """执行Chrome DevTools Protocol语句,执行前等待页面加载完毕 @@ -416,7 +416,27 @@ class ChromiumBase(BasePage): :return: 执行的结果 """ 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): """运行javascript代码 @@ -426,9 +446,30 @@ class ChromiumBase(BasePage): :param timeout: js超时时间(秒),为None则使用页面timeouts.script设置 :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): + """运行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代码,执行前等待页面加载完毕 :param script: js文本或js文件路径 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... @@ -462,23 +503,21 @@ class ChromiumBase(BasePage): show_errmsg=show_errmsg, timeout=timeout) 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信息 - :param as_dict: 为True时以dict格式返回且all_info无效,为False时返回list :param all_domains: 是否返回所有域的cookies :param all_info: 是否返回所有信息,为False时只返回name、value、domain :return: cookies信息 """ 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: - return {cookie['name']: cookie['value'] for cookie in cookies} - elif all_info: - return cookies + if all_info: + r = cookies else: - return [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} - for cookie in cookies] + r = [{'name': cookie['name'], 'value': cookie['value'], 'domain': cookie['domain']} for cookie in cookies] + + return CookiesList(r) def ele(self, locator, index=1, timeout=None): """获取一个符合条件的元素对象 @@ -591,7 +630,7 @@ class ChromiumBase(BasePage): :return: None """ self._is_loading = True - self.run_cdp('Page.reload', ignoreCache=ignore_cache) + self._run_cdp('Page.reload', ignoreCache=ignore_cache) self.wait.load_start() def forward(self, steps=1): @@ -616,7 +655,7 @@ class ChromiumBase(BasePage): if steps == 0: return - history = self.run_cdp('Page.getNavigationHistory') + history = self._run_cdp('Page.getNavigationHistory') index = history['currentIndex'] history = history['entries'] direction = 1 if steps > 0 else -1 @@ -632,12 +671,12 @@ class ChromiumBase(BasePage): if nid: self._is_loading = True - self.run_cdp('Page.navigateToHistoryEntry', entryId=nid) + self._run_cdp('Page.navigateToHistoryEntry', entryId=nid) def stop_loading(self): """页面停止加载""" try: - self.run_cdp('Page.stopLoading') + self._run_cdp('Page.stopLoading') end_time = perf_counter() + 5 while self._ready_state != 'complete' and perf_counter() < end_time: sleep(.1) @@ -655,7 +694,7 @@ class ChromiumBase(BasePage): return ele = self._ele(loc_or_ele, raise_err=False) 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): """新建一个元素 @@ -711,7 +750,7 @@ class ChromiumBase(BasePage): else: raise TypeError('html_or_info参数必须是html文本或tuple,tuple格式为(tag, {name: value})。') - ele = self.run_js(js, *args) + ele = self._run_js(js, *args) return ele def get_frame(self, loc_ind_ele, timeout=None): @@ -772,7 +811,7 @@ class ChromiumBase(BasePage): :return: 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): """返回localStorage信息,不设置item则获取全部 @@ -780,7 +819,7 @@ class ChromiumBase(BasePage): :return: 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, full_page=False, left_top=None, right_bottom=None): @@ -802,7 +841,7 @@ class ChromiumBase(BasePage): :param script: js文本 :return: 添加的脚本的id """ - js_id = self.run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script, + js_id = self._run_cdp('Page.addScriptToEvaluateOnNewDocument', source=script, includeCommandLineAPI=True)['identifier'] self._init_jss.append(js_id) return js_id @@ -814,11 +853,11 @@ class ChromiumBase(BasePage): """ if script_id is None: 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() 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) def clear_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True): @@ -830,19 +869,19 @@ class ChromiumBase(BasePage): :return: None """ if session_storage or local_storage: - self.run_cdp_loaded('DOMStorage.enable') - i = self.run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey'] + self._run_cdp_loaded('DOMStorage.enable') + i = self._run_cdp('Storage.getStorageKeyForFrame', frameId=self._frame_id)['storageKey'] 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: - self.run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True}) - self.run_cdp_loaded('DOMStorage.disable') + self._run_cdp('DOMStorage.clear', storageId={'storageKey': i, 'isLocalStorage': True}) + self._run_cdp_loaded('DOMStorage.disable') if cache: - self.run_cdp_loaded('Network.clearBrowserCache') + self._run_cdp_loaded('Network.clearBrowserCache') if cookies: - self.run_cdp_loaded('Network.clearBrowserCookies') + self._run_cdp_loaded('Network.clearBrowserCookies') def disconnect(self): """断开与页面的连接,不关闭页面""" @@ -974,7 +1013,7 @@ class ChromiumBase(BasePage): err = None end_time = perf_counter() + timeout 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: err = ConnectionError(result['errorText']) except TimeoutError: @@ -1074,8 +1113,8 @@ class ChromiumBase(BasePage): v = not (location_in_viewport(self, x, y) and location_in_viewport(self, right_bottom[0], right_bottom[1])) - if v and (self.run_js('return document.body.scrollHeight > window.innerHeight;') and - not self.run_js('return document.body.scrollWidth > window.innerWidth;')): + if v and (self._run_js('return document.body.scrollHeight > window.innerHeight;') and + not self._run_js('return document.body.scrollWidth > window.innerWidth;')): x += 10 vp = {'x': x, 'y': y, 'width': w, 'height': h, 'scale': 1} @@ -1086,7 +1125,7 @@ class ChromiumBase(BasePage): if pic_type == 'jpeg': args['quality'] = 100 - png = self.run_cdp_loaded('Page.captureScreenshot', **args)['data'] + png = self._run_cdp_loaded('Page.captureScreenshot', **args)['data'] if as_base64: return png diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 176ccd3..efde024 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -15,6 +15,7 @@ from .._base.driver import Driver from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList, ChromiumElementsList +from .._functions.web import CookiesList from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage 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(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 get(self, url: str, show_errmsg: bool = False, retry: int = None, 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[ - list, dict]: ... + def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ... def ele(self, 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(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 local_storage(self, item: str = None) -> Union[str, dict, None]: ... diff --git a/DrissionPage/_pages/chromium_frame.py b/DrissionPage/_pages/chromium_frame.py index 5ae97b4..8cf684b 100644 --- a/DrissionPage/_pages/chromium_frame.py +++ b/DrissionPage/_pages/chromium_frame.py @@ -33,7 +33,7 @@ class ChromiumFrame(ChromiumBase): self._frame_ele = ele 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'] if self._is_inner_frame(): self._is_diff_domain = False @@ -43,7 +43,7 @@ class ChromiumFrame(ChromiumBase): self._is_diff_domain = True delattr(self, '_frame_id') 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._type = 'ChromiumFrame' @@ -100,7 +100,7 @@ class ChromiumFrame(ChromiumBase): self._frame_ele = ChromiumElement(self._target_page, backend_id=self._backend_id) end_time = perf_counter() + 2 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: break sleep(.05) @@ -145,17 +145,17 @@ class ChromiumFrame(ChromiumBase): self._is_reading = True try: 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']) else: 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._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)): self.browser._frames[i] = self.tab_id return True @@ -253,7 +253,7 @@ class ChromiumFrame(ChromiumBase): def url(self): """返回frame当前访问的url""" try: - return self.doc_ele.run_js('return this.location.href;') + return self.doc_ele._run_js('return this.location.href;') except JavaScriptError: return None @@ -261,14 +261,14 @@ class ChromiumFrame(ChromiumBase): def html(self): """返回元素outerHTML文本""" 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) return f'{sign}{self.inner_html}' @property def inner_html(self): """返回元素innerHTML文本""" - return self.doc_ele.run_js('return this.documentElement.outerHTML;') + return self.doc_ele._run_js('return this.documentElement.outerHTML;') @property def title(self): @@ -284,7 +284,7 @@ class ChromiumFrame(ChromiumBase): @property def active_ele(self): """返回当前焦点所在元素""" - return self.doc_ele.run_js('return this.activeElement;') + return self.doc_ele._run_js('return this.activeElement;') @property def xpath(self): @@ -318,18 +318,18 @@ class ChromiumFrame(ChromiumBase): else: try: - return self.doc_ele.run_js('return this.readyState;') + return self.doc_ele._run_js('return this.readyState;') except ContextLostError: 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']) - return doc.run_js('return this.readyState;') + return doc._run_js('return this.readyState;') except: return None def refresh(self): """刷新frame页面""" - self.doc_ele.run_js('this.location.reload();') + self.doc_ele._run_js('this.location.reload();') def property(self, name): """返回frame元素一个property属性值 @@ -353,6 +353,16 @@ class ChromiumFrame(ChromiumBase): self.frame_ele.remove_attr(name) 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代码 :param script: js文本 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... @@ -361,9 +371,9 @@ class ChromiumFrame(ChromiumBase): :return: 运行的结果 """ 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: - 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): """返回上面某一级父元素,可指定层数或用查询语法定位 @@ -526,12 +536,12 @@ class ChromiumFrame(ChromiumBase): img.style.setProperty("position","fixed"); arguments[0].insertBefore(img, this); 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) top = int(self.frame_ele.style('border-top').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'] sy = r['pageY'] 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): """返回当前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']) diff --git a/DrissionPage/_pages/chromium_frame.pyi b/DrissionPage/_pages/chromium_frame.pyi index b7e6183..a23ed33 100644 --- a/DrissionPage/_pages/chromium_frame.pyi +++ b/DrissionPage/_pages/chromium_frame.pyi @@ -142,6 +142,12 @@ class ChromiumFrame(ChromiumBase): as_expr: bool = False, timeout: float = None) -> Any: ... + def _run_js(self, + script: str, + *args, + as_expr: bool = False, + timeout: float = None) -> Any: ... + def parent(self, level_or_loc: Union[Tuple[str, str], str, int] = 1, index: int = 1) -> ChromiumElement: ... diff --git a/DrissionPage/_pages/chromium_page.py b/DrissionPage/_pages/chromium_page.py index b4fea5d..76eec26 100644 --- a/DrissionPage/_pages/chromium_page.py +++ b/DrissionPage/_pages/chromium_page.py @@ -6,7 +6,6 @@ @License : BSD 3-Clause. """ from pathlib import Path -from threading import Lock from time import sleep, perf_counter from requests import Session @@ -65,7 +64,7 @@ class ChromiumPage(ChromiumBase): def _run_browser(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'] if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower(): self._browser.quit(3) diff --git a/DrissionPage/_pages/chromium_tab.py b/DrissionPage/_pages/chromium_tab.py index 3504877..5b42498 100644 --- a/DrissionPage/_pages/chromium_tab.py +++ b/DrissionPage/_pages/chromium_tab.py @@ -10,8 +10,9 @@ from time import sleep from .._base.base import BasePage from .._configs.session_options import SessionOptions +from .._functions.cookies import set_session_cookies, set_tab_cookies 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.session_page import SessionPage from .._units.setter import TabSetter, WebPageTabSetter @@ -338,7 +339,7 @@ class MixTab(SessionPage, ChromiumTab, BasePage): return 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}) set_session_cookies(self.session, super(SessionPage, self).cookies()) @@ -347,19 +348,18 @@ class MixTab(SessionPage, ChromiumTab, BasePage): """把session对象的cookies复制到浏览器""" if not self._has_driver: 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 - :param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效 :param all_domains: 是否返回所有域的cookies :param all_info: 是否返回所有信息,False则只返回name、value、domain :return: cookies信息 """ if self._mode == 's': - return super().cookies(as_dict, all_domains, all_info) + return super().cookies(all_domains, all_info) 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): """关闭当前标签页""" diff --git a/DrissionPage/_pages/chromium_tab.pyi b/DrissionPage/_pages/chromium_tab.pyi index cb94132..072de9f 100644 --- a/DrissionPage/_pages/chromium_tab.pyi +++ b/DrissionPage/_pages/chromium_tab.pyi @@ -17,6 +17,7 @@ from .._base.browser import Browser from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList, ChromiumElementsList +from .._functions.web import CookiesList from .._units.rect import TabRect from .._units.setter import TabSetter, WebPageTabSetter from .._units.waiter import TabWaiter @@ -153,8 +154,7 @@ class MixTab(SessionPage, ChromiumTab): def cookies_to_browser(self) -> None: ... - def cookies(self, as_dict: bool = False, all_domains: bool = False, - all_info: bool = False) -> Union[dict, list]: ... + def cookies(self, all_domains: bool = False, all_info: bool = False) -> CookiesList: ... def close(self) -> None: ... diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index e34f6a7..0697bea 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -18,7 +18,8 @@ from tldextract import extract from .._base.base import BasePage from .._configs.session_options import SessionOptions 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 @@ -216,9 +217,8 @@ class SessionPage(BasePage): """ 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 - :param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效 :param all_domains: 是否返回所有域的cookies :param all_info: 是否返回所有信息,False则只返回name、value、domain :return: cookies信息 @@ -229,21 +229,20 @@ class SessionPage(BasePage): if self.url: ex_url = extract(self._session_url) domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain - - cookies = tuple(x for x in self.session.cookies if domain in x.domain or x.domain == '') + cookies = tuple(c for c in self.session.cookies if domain in c.domain or c.domain == '') else: - cookies = tuple(x for x in self.session.cookies) + cookies = tuple(c for c in self.session.cookies) - if as_dict: - return {x.name: x.value for x in cookies} - elif all_info: - return [cookie_to_dict(cookie) for cookie in cookies] + if all_info: + r = CookiesList() + for c in cookies: + r.append(cookie_to_dict(c)) else: - r = [] + r = CookiesList() for c in cookies: c = cookie_to_dict(c) r.append({'name': c['name'], 'value': c['value'], 'domain': c['domain']}) - return r + return r def close(self): """关闭Session对象""" diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 4527827..385ac2b 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -15,6 +15,7 @@ from .._base.base import BasePage from .._configs.session_options import SessionOptions from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList +from .._functions.web import CookiesList from .._units.setter import SessionPageSetter @@ -27,7 +28,7 @@ class SessionPage(BasePage): self._url: str = ... self._response: Response = ... self._url_available: bool = ... - self.timeout: float = ... + self._timeout: float = ... self.retry_times: int = ... self.retry_interval: float = ... self._set: SessionPageSetter = ... @@ -113,9 +114,8 @@ class SessionPage(BasePage): raise_err: bool = None) -> Union[SessionElement, SessionElementsList]: ... def cookies(self, - as_dict: bool = False, all_domains: bool = False, - all_info: bool = False) -> Union[dict, list]: ... + all_info: bool = False) -> CookiesList: ... # ----------------session独有属性和方法----------------------- @property diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index fbcd2fb..e993f35 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -10,7 +10,7 @@ from .chromium_tab import MixTab from .session_page import SessionPage from .._base.base import BasePage 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 @@ -276,7 +276,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): return 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}) set_session_cookies(self.session, super(SessionPage, self).cookies()) @@ -285,7 +285,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): """把session对象的cookies复制到浏览器""" if not self._has_driver: 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): """返回cookies diff --git a/DrissionPage/_units/actions.py b/DrissionPage/_units/actions.py index e1c951c..6942cde 100644 --- a/DrissionPage/_units/actions.py +++ b/DrissionPage/_units/actions.py @@ -7,9 +7,9 @@ """ from time import sleep, perf_counter -from ..errors import AlertExistsError from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions from .._functions.web import location_in_viewport +from ..errors import AlertExistsError class Actions: @@ -51,8 +51,8 @@ class Actions: if not location_in_viewport(self.owner, lx, ly): # 把坐标滚动到页面中间 - clientWidth = self.owner.run_js('return document.body.clientWidth;') - clientHeight = self.owner.run_js('return document.body.clientHeight;') + clientWidth = self.owner._run_js('return document.body.clientWidth;') + clientHeight = self.owner._run_js('return document.body.clientHeight;') 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['_ignore'] = AlertExistsError - self.owner.run_cdp('Input.dispatchKeyEvent', **data) + self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self def key_up(self, key): @@ -273,7 +273,7 @@ class Actions: data = self._get_key_data(key, 'keyUp') data['_ignore'] = AlertExistsError - self.owner.run_cdp('Input.dispatchKeyEvent', **data) + self.owner._run_cdp('Input.dispatchKeyEvent', **data) return self def type(self, keys): @@ -292,7 +292,7 @@ class Actions: self.key_up(character) 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: self.key_up(m) @@ -339,6 +339,6 @@ class Actions: def location_to_client(page, lx, ly): """绝对坐标转换为视口坐标""" - scroll_x = page.run_js('return document.documentElement.scrollLeft;') - scroll_y = page.run_js('return document.documentElement.scrollTop;') + scroll_x = page._run_js('return document.documentElement.scrollLeft;') + scroll_y = page._run_js('return document.documentElement.scrollTop;') return lx - scroll_x, ly - scroll_y diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index fd5f9c7..12e85f3 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -87,7 +87,7 @@ class Clicker(object): x = rect[1][0] - (rect[1][0] - rect[0][0]) / 2 y = rect[0][0] + 3 try: - r = self._ele.owner.run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y), + r = self._ele.owner._run_cdp('DOM.getNodeForLocation', x=int(x), y=int(y), includeUserAgentShadowDOM=True, ignorePointerEventsNone=True) if r['backendNodeId'] != self._ele._backend_id: vx, vy = self._ele.rect.viewport_midpoint @@ -101,7 +101,7 @@ class Clicker(object): return True if by_js is not False: - self._ele.run_js('this.click();') + self._ele._run_js('this.click();') return True if Settings.raise_when_click_failed: raise CanNotClickError @@ -204,7 +204,7 @@ class Clicker(object): :param count: 点击次数 :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) - 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) diff --git a/DrissionPage/_units/cookies_setter.py b/DrissionPage/_units/cookies_setter.py index c9d3c41..cb48d53 100644 --- a/DrissionPage/_units/cookies_setter.py +++ b/DrissionPage/_units/cookies_setter.py @@ -5,7 +5,7 @@ @Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. @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): @@ -20,7 +20,7 @@ class CookiesSetter(object): :param cookies: cookies信息 :return: None """ - set_browser_cookies(self._owner, cookies) + set_tab_cookies(self._owner, cookies) def remove(self, name, url=None, domain=None, path=None): """删除一个cookie @@ -39,11 +39,21 @@ class CookiesSetter(object): d['url'] = self._owner.url if path is not None: d['path'] = path - self._owner.run_cdp('Network.deleteCookies', **d) + self._owner._run_cdp('Storage.deleteCookies', **d) def clear(self): """清除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): diff --git a/DrissionPage/_units/cookies_setter.pyi b/DrissionPage/_units/cookies_setter.pyi index c929448..1b467b1 100644 --- a/DrissionPage/_units/cookies_setter.pyi +++ b/DrissionPage/_units/cookies_setter.pyi @@ -8,6 +8,7 @@ from http.cookiejar import Cookie, CookieJar from typing import Union +from .._base.browser import Browser from .._pages.chromium_base import ChromiumBase from .._pages.chromium_tab import MixTab from .._pages.session_page import SessionPage @@ -26,8 +27,14 @@ class CookiesSetter(object): def clear(self) -> None: ... +class BrowserCookiesSetter(CookiesSetter): + _owner: Browser = ... + + def __init__(self, page: Browser): ... + + class SessionCookiesSetter(object): - _owner: SessionPage + _owner: SessionPage = ... def __init__(self, page: SessionPage): ... @@ -39,7 +46,7 @@ class SessionCookiesSetter(object): class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter): - _owner: Union[WebPage, MixTab] + _owner: Union[WebPage, MixTab] = ... def __init__(self, page: SessionPage): ... diff --git a/DrissionPage/_units/downloader.py b/DrissionPage/_units/downloader.py index 0318266..9e190c4 100644 --- a/DrissionPage/_units/downloader.py +++ b/DrissionPage/_units/downloader.py @@ -55,7 +55,7 @@ class DownloadManager(object): if not self._running or tid == 'browser': self._browser._driver.set_callback('Browser.downloadProgress', self._onDownloadProgress) 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) if 'error' in r: print('浏览器版本太低无法使用下载管理功能。') @@ -124,7 +124,7 @@ class DownloadManager(object): """ mission.state = 'canceled' try: - self._browser.run_cdp('Browser.cancelDownload', guid=mission.id) + self._browser._run_cdp('Browser.cancelDownload', guid=mission.id) except: pass if mission.final_path: @@ -137,7 +137,7 @@ class DownloadManager(object): """ mission.state = 'skipped' try: - self._browser.run_cdp('Browser.cancelDownload', guid=mission.id) + self._browser._run_cdp('Browser.cancelDownload', guid=mission.id) except: pass diff --git a/DrissionPage/_units/listener.py b/DrissionPage/_units/listener.py index ef49b35..504dc54 100644 --- a/DrissionPage/_units/listener.py +++ b/DrissionPage/_units/listener.py @@ -26,7 +26,7 @@ class Listener(object): :param owner: ChromiumBase对象 """ self._owner = owner - self._address = owner.address + self._address = owner.browser.address self._target_id = owner._target_id self._driver = None self._running_requests = 0 diff --git a/DrissionPage/_units/rect.py b/DrissionPage/_units/rect.py index b8bbbb8..2d8601e 100644 --- a/DrissionPage/_units/rect.py +++ b/DrissionPage/_units/rect.py @@ -18,7 +18,7 @@ class ElementRect(object): def corners(self): """返回元素四个角坐标,顺序:左上、右上、右下、左下,没有大小的元素抛出NoRectError""" 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'] 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)] @@ -32,7 +32,7 @@ class ElementRect(object): @property 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'] return border[2] - border[0], border[5] - border[1] @@ -77,7 +77,7 @@ class ElementRect(object): """返回元素左上角在屏幕上坐标,左上角为(0, 0)""" vx, vy = self._ele.owner.rect.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 @property @@ -85,7 +85,7 @@ class ElementRect(object): """返回元素中点在屏幕上坐标,左上角为(0, 0)""" vx, vy = self._ele.owner.rect.viewport_location 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 @property @@ -93,7 +93,7 @@ class ElementRect(object): """返回元素中点在屏幕上坐标,左上角为(0, 0)""" vx, vy = self._ele.owner.rect.viewport_location 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 def _get_viewport_rect(self, quad): @@ -101,13 +101,13 @@ class ElementRect(object): :param quad: 方框类型,margin border padding :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 )['model'][quad] 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'] sy = r['pageY'] return x + sx, y + sy @@ -174,13 +174,13 @@ class TabRect(object): @property 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(' ') return int(w), int(h) 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): """获取窗口范围信息""" @@ -214,8 +214,8 @@ class FrameRect(object): @property def size(self): """返回frame内页面尺寸,格式:(宽, 高)""" - w = self._frame.doc_ele.run_js('return this.body.scrollWidth') - h = self._frame.doc_ele.run_js('return this.body.scrollHeight') + w = self._frame.doc_ele._run_js('return this.body.scrollWidth') + h = self._frame.doc_ele._run_js('return this.body.scrollHeight') return w, h @property diff --git a/DrissionPage/_units/screencast.py b/DrissionPage/_units/screencast.py index bd12711..e67df23 100644 --- a/DrissionPage/_units/screencast.py +++ b/DrissionPage/_units/screencast.py @@ -48,7 +48,7 @@ class Screencast(object): if self._mode.startswith('frugal'): 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'): self._running = True @@ -79,8 +79,8 @@ class Screencast(object): } ''' print('请手动选择要录制的目标。') - self._owner.run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;') - self._owner.run_js(js) + self._owner._run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;') + self._owner._run_js(js) def stop(self, video_name=None): """停止录屏 @@ -93,19 +93,19 @@ class Screencast(object): path = f'{self._path}{sep}{name}' if self._mode.startswith('js'): - self._owner.run_js('mediaRecorder.stop();', as_expr=True) - while not self._owner.run_js('return DrissionPage_Screencast_blob_ok;'): + self._owner._run_js('mediaRecorder.stop();', as_expr=True) + while not self._owner._run_js('return DrissionPage_Screencast_blob_ok;'): sleep(.1) - blob = self._owner.run_js('return DrissionPage_Screencast_blob;') - uuid = self._owner.run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid'] - data = self._owner.run_cdp('IO.read', handle=f'blob:{uuid}')['data'] + blob = self._owner._run_js('return DrissionPage_Screencast_blob;') + uuid = self._owner._run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid'] + data = self._owner._run_cdp('IO.read', handle=f'blob:{uuid}')['data'] with open(path, 'wb') as f: f.write(b64decode(data)) return path if self._mode.startswith('frugal'): self._owner.driver.set_callback('Page.screencastFrame', None) - self._owner.run_cdp('Page.stopScreencast') + self._owner._run_cdp('Page.stopScreencast') else: self._enable = False while self._running: @@ -164,7 +164,7 @@ class Screencast(object): path = self._tmp_path or self._path with open(f'{path}{sep}{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f: 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): diff --git a/DrissionPage/_units/scroller.py b/DrissionPage/_units/scroller.py index 224d640..6ec48c4 100644 --- a/DrissionPage/_units/scroller.py +++ b/DrissionPage/_units/scroller.py @@ -21,7 +21,7 @@ class Scroller(object): def _run_js(self, js): js = js.format(self.t1, self.t2, self.t2) - self._driver.run_js(js) + self._driver._run_js(js) self._wait_scrolled() def to_top(self): @@ -88,14 +88,14 @@ class Scroller(object): return 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'] y = r['layoutViewport']['pageY'] end_time = perf_counter() + owner.timeout while perf_counter() < end_time: sleep(.1) - r = owner.run_cdp('Page.getLayoutMetrics') + r = owner._run_cdp('Page.getLayoutMetrics') x1 = r['layoutViewport']['pageX'] y1 = r['layoutViewport']['pageY'] @@ -144,9 +144,9 @@ class PageScroller(Scroller): :return: None """ 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): - ele.run_js('''function getWindowScrollTop() {let scroll_top = 0; + ele._run_js('''function getWindowScrollTop() {let scroll_top = 0; if (document.documentElement && document.documentElement.scrollTop) { scroll_top = document.documentElement.scrollTop; } else if (document.body) {scroll_top = document.body.scrollTop;} diff --git a/DrissionPage/_units/selector.py b/DrissionPage/_units/selector.py index c97aded..27301ad 100644 --- a/DrissionPage/_units/selector.py +++ b/DrissionPage/_units/selector.py @@ -45,7 +45,7 @@ class SelectElement(object): """返回第一个被选中的option元素 :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 @property @@ -69,7 +69,7 @@ class SelectElement(object): for i in self.options: change = 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: self._dispatch_change() @@ -258,12 +258,12 @@ class SelectElement(object): if not self.is_multi and len(option) > 1: option = option[:1] for o in option: - o.run_js(f'this.selected={mode};') + o._run_js(f'this.selected={mode};') self._dispatch_change() else: - option.run_js(f'this.selected={mode};') + option._run_js(f'this.selected={mode};') self._dispatch_change() 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}));') diff --git a/DrissionPage/_units/setter.py b/DrissionPage/_units/setter.py index 8b7da82..04c4660 100644 --- a/DrissionPage/_units/setter.py +++ b/DrissionPage/_units/setter.py @@ -10,7 +10,7 @@ from time import sleep 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.tools import show_or_hide_browser from .._functions.web import format_headers @@ -54,6 +54,13 @@ class BasePageSetter(object): class BrowserBaseSetter(BasePageSetter): """Browser和ChromiumBase设置""" + def __init__(self, owner): + """ + :param owner: ChromiumBase对象 + """ + super().__init__(owner) + self._cookies_setter = None + @property def load_mode(self): """返回用于设置页面加载策略的对象""" @@ -78,8 +85,12 @@ class BrowserBaseSetter(BasePageSetter): class BrowserSetter(BrowserBaseSetter): - def cookies(self, cookies): - pass # todo: 研究Storage.setCookies和Network.setCookies差别 + @property + 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): """激活标签页使其处于最前面 @@ -128,12 +139,6 @@ class BrowserSetter(BrowserBaseSetter): class ChromiumBaseSetter(BrowserBaseSetter): - def __init__(self, owner): - """ - :param owner: ChromiumBase对象 - """ - super().__init__(owner) - self._cookies_setter = None @property def scroll(self): @@ -156,7 +161,7 @@ class ChromiumBaseSetter(BrowserBaseSetter): keys = {'userAgent': ua} if platform: keys['platform'] = platform - self._owner.run_cdp('Emulation.setUserAgentOverride', **keys) + self._owner._run_cdp('Emulation.setUserAgentOverride', **keys) def session_storage(self, item, value): """设置或删除某项sessionStorage信息 @@ -164,15 +169,15 @@ class ChromiumBaseSetter(BrowserBaseSetter): :param value: 项的值,设置为False时,删除该项 :return: None """ - self._owner.run_cdp_loaded('DOMStorage.enable') - i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] + self._owner._run_cdp_loaded('DOMStorage.enable') + i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] if value is False: - self._owner.run_cdp('DOMStorage.removeDOMStorageItem', + self._owner._run_cdp('DOMStorage.removeDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False}, key=item) 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) - self._owner.run_cdp_loaded('DOMStorage.disable') + self._owner._run_cdp_loaded('DOMStorage.disable') def local_storage(self, item, value): """设置或删除某项localStorage信息 @@ -180,15 +185,15 @@ class ChromiumBaseSetter(BrowserBaseSetter): :param value: 项的值,设置为False时,删除该项 :return: None """ - self._owner.run_cdp_loaded('DOMStorage.enable') - i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] + self._owner._run_cdp_loaded('DOMStorage.enable') + i = self._owner._run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey'] if value is False: - self._owner.run_cdp('DOMStorage.removeDOMStorageItem', + self._owner._run_cdp('DOMStorage.removeDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True}, key=item) 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) - self._owner.run_cdp_loaded('DOMStorage.disable') + self._owner._run_cdp_loaded('DOMStorage.disable') def upload_files(self, files): """等待上传的文件路径 @@ -197,7 +202,7 @@ class ChromiumBaseSetter(BrowserBaseSetter): """ if not self._owner._upload_list: 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): files = files.split('\n') @@ -210,8 +215,8 @@ class ChromiumBaseSetter(BrowserBaseSetter): :param headers: dict格式的headers数据 :return: None """ - self._owner.run_cdp('Network.enable') - self._owner.run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers)) + self._owner._run_cdp('Network.enable') + self._owner._run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers)) def auto_handle_alert(self, on_off=True, accept=True): """设置是否启用自动处理弹窗 @@ -232,8 +237,8 @@ class ChromiumBaseSetter(BrowserBaseSetter): urls = (urls,) if not isinstance(urls, (list, tuple)): raise TypeError('urls需传入str、list或tuple类型。') - self._owner.run_cdp('Network.enable') - self._owner.run_cdp('Network.setBlockedURLs', urls=urls) + self._owner._run_cdp('Network.enable') + self._owner._run_cdp('Network.setBlockedURLs', urls=urls) class TabSetter(ChromiumBaseSetter): @@ -522,11 +527,11 @@ class ChromiumElementSetter(object): :return: None """ 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)) except ElementLostError: 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)) def property(self, name, value): @@ -536,7 +541,7 @@ class ChromiumElementSetter(object): :return: None """ 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): """设置元素style样式 @@ -545,7 +550,7 @@ class ChromiumElementSetter(object): :return: None """ try: - self._ele.run_js(f'this.style.{name}="{value}";') + self._ele._run_js(f'this.style.{name}="{value}";') except JavaScriptError: raise ValueError(f'设置失败,请检查属性名{name}') @@ -629,7 +634,7 @@ class PageScrollSetter(object): if not isinstance(on_off, bool): raise TypeError('on_off必须为bool。') 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 @@ -703,7 +708,7 @@ class WindowSetter(object): """获取窗口位置及大小信息""" for _ in range(50): try: - return self._owner.run_cdp('Browser.getWindowForTarget') + return self._owner._run_cdp('Browser.getWindowForTarget') except: sleep(.1) @@ -713,7 +718,7 @@ class WindowSetter(object): :return: None """ 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: raise RuntimeError('浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。') diff --git a/DrissionPage/_units/setter.pyi b/DrissionPage/_units/setter.pyi index f01dbaf..f39540f 100644 --- a/DrissionPage/_units/setter.pyi +++ b/DrissionPage/_units/setter.pyi @@ -11,7 +11,7 @@ from typing import Union, Tuple, Literal, Any, Optional from requests.adapters import HTTPAdapter 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 .._base.base import BasePage from .._base.browser import Browser @@ -40,6 +40,7 @@ class BasePageSetter(object): class BrowserBaseSetter(BasePageSetter): + _cookies_setter: Optional[CookiesSetter] = ... @property def load_mode(self) -> LoadMode: ... @@ -49,10 +50,12 @@ class BrowserBaseSetter(BasePageSetter): class BrowserSetter(BasePageSetter): _owner: Browser = ... + _cookies_setter: BrowserCookiesSetter = ... 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): ... @@ -64,9 +67,10 @@ class BrowserSetter(BasePageSetter): class ChromiumBaseSetter(BasePageSetter): - def __init__(self, owner): - self._owner: ChromiumBase = ... - self._cookies_setter: CookiesSetter = ... + _owner: ChromiumBase = ... + _cookies_setter: CookiesSetter = ... + + def __init__(self, owner): ... @property def load_mode(self) -> LoadMode: ... diff --git a/DrissionPage/_units/states.py b/DrissionPage/_units/states.py index 763e27f..4a75c1c 100644 --- a/DrissionPage/_units/states.py +++ b/DrissionPage/_units/states.py @@ -19,30 +19,30 @@ class ElementStates(object): @property def is_selected(self): """返回列表元素是否被选择""" - return self._ele.run_js('return this.selected;') + return self._ele._run_js('return this.selected;') @property def is_checked(self): """返回元素是否被选择""" - return self._ele.run_js('return this.checked;') + return self._ele._run_js('return this.checked;') @property def is_displayed(self): """返回元素是否显示""" 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')) @property def is_enabled(self): """返回元素是否可用""" - return not self._ele.run_js('return this.disabled;') + return not self._ele._run_js('return this.disabled;') @property def is_alive(self): """返回元素是否仍在DOM中""" 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 except ElementLostError: return False @@ -66,7 +66,7 @@ class ElementStates(object): """返回元素是否被覆盖,与是否在视口中无关,如被覆盖返回覆盖元素的backend id,否则返回False""" lx, ly = self._ele.rect.click_point 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 except CDPError: return False @@ -95,13 +95,13 @@ class ShadowRootStates(object): @property def is_enabled(self): """返回元素是否可用""" - return not self._ele.run_js('return this.disabled;') + return not self._ele._run_js('return this.disabled;') @property def is_alive(self): """返回元素是否仍在DOM中""" 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 except ElementLostError: return False @@ -125,7 +125,7 @@ class PageStates(object): def is_alive(self): """返回页面对象是否仍然可用""" try: - self._owner.run_cdp('Page.getLayoutMetrics') + self._owner._run_cdp('Page.getLayoutMetrics') return True except PageDisconnectedError: return False @@ -157,7 +157,7 @@ class FrameStates(object): def is_alive(self): """返回frame元素是否可用,且里面仍挂载有frame""" 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'] except (ElementLostError, PageDisconnectedError): return False @@ -172,7 +172,7 @@ class FrameStates(object): def is_displayed(self): """返回iframe是否显示""" 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') @property